PS1 (alias PSX, alias PS One) adalah generasi pertama dari konsol game PlayStation Sony dan termasuk generasi kelima dari konsol game pada umumnya. Ini menggunakan drive kecepatan 2x untuk membaca CD. Jumlah data yang begitu besar, menurut standar konsol saat ini, memungkinkan pengembang game untuk tidak secara khusus melihat kembali batasan saat membuat konten untuk game, yang membuat yang terakhir memiliki kualitas yang lebih tinggi dibandingkan dengan game dari generasi sebelumnya. konsol. Selain itu, game sekarang bisa berlangsung lama. Dan jika ada game, dengan pengecualian langka, di konsol generasi sebelumnya bisa diselesaikan dalam satu sesi game, maka dengan game PS1, semuanya berbeda. Untuk menyimpan kemajuan, PlayStation memiliki kartu memori: modul memori non-volatile yang kecil dan dapat dilepas.
Jika Anda bertanya-tanya bagaimana tepatnya kartu memori PlayStation 1 bekerja, bagaimana cara kerjanya dan bagaimana Anda bisa membuatnya sendiri, selamat datang di cat.
Jadi, kartu memori PS1 adalah perangkat periferal standar, seperti seluruh joypad, joystick, dan aksesori lainnya. Untuk memahami dengan tepat cara kerjanya, pertama-tama Anda perlu melihat apa yang ada di dalamnya.
Foto papan sirkuit cetak dari kartu memori 15 blok standar
Seperti yang Anda lihat dari foto, perangkat kartu ini sangat sederhana: pengontrol yang melayani permintaan sistem, dan, pada kenyataannya, memori non-volatile, yang diwakili oleh NOR FLASH standar. Logikanya, kartu memori dibagi menjadi 15 blok yang bisa digunakan game. Tampaknya 15 tidak logis untuk sistem biner, tetapi tidak ada kontradiksi di sini: satu blok diberikan untuk sistem file, nama file dan bahkan ikon animasi disimpan di sana, seperti aliran NTFS. Setiap blok memiliki ukuran 8 KiB, total 16 blok adalah 128 KiB, terlihat dari penandaan memori FLASH pada foto di atas.
Pada awalnya, ini cukup untuk semua orang, tetapi kemudian game yang menggunakan lebih dari satu blok mulai bermunculan. Misalnya, beberapa simulator, seperti Sega GT, menggunakan 4-5 blok, sedangkan Constructorjadi secara umum keseluruhan memory card adalah 15 blok. Hal ini memaksa untuk membeli lebih banyak kartu dan situasi terancam menjadi seperti disket atau selongsong peluru. Tapi kemudian para perompak berhenti dan mulai mengeluarkan kartu untuk 2, 4 atau 8 halaman sekaligus. Dan halaman-halaman tersebut dialihkan baik dengan kombinasi pintar di joypad, atau dengan tombol eksplisit di kartu memori itu sendiri. Namun, dalam kartu dengan kompresi lebih dari 2 halaman digunakan, dan jumlah halaman sebenarnya jauh lebih sedikit, dan beberapa kartu dapat diblokir dengan bodoh. Dan sangat sulit untuk mengeluarkan mereka dari kondisi ini, tetapi apa yang para pemain tidak lakukan demi penyelamatan mereka. Berikut adalah perwakilan tipikal dari kartu memori multi-halaman:
Di sebelah kiri adalah kartu memori dengan 2 halaman, di sebelah kanan dengan 8. Yang kanan memiliki tombol pemutar halaman perangkat keras dan indikator yang menunjukkan angka dari 1 hingga 8, yaitu tersembunyi di balik kaca gelap
Penyimpangan liris kecil
Semuanya dimulai pada tahun 2001, ketika saya membeli disk ajaib untuk PC yang disebut "Semua Emulator", di mana ada emulator PS1 termasuk: itu adalah Bleem! dan ePSXe awal. Dan komputer saya saat itu bahkan dapat memutar disk PS1 saya dengan main-main! Dan beberapa saat kemudian saya mendapatkan modem dan saya belajar tentang DirectPad Pro . Menghubungkan joystick asli ke komputer (meskipun melalui LPT) membutuhkan banyak biaya. Dan sistem ini bekerja pada 9x dan XP! Dan tidak lama kemudian, pada tahun 2002, saya belajar tentang Memory Card Capture Sakura! Program ini memungkinkan bekerja dengan kartu memori asli menggunakan skema koneksi DirectPad Pro yang sama. Saat itulah saya mendapat ide untuk membuat kartu memori "tanpa akhir" yang memungkinkan saya bertukar informasi dengan komputer tanpa perlu perangkat tambahan. Tetapi pada saat itu saya tidak memiliki cukup informasi dan basis elemen yang tersedia, dan idenya tetap hanya sebuah ide, berkilauan di suatu tempat di halaman belakang kesadaran saya.
Hampir 9 tahun telah berlalu sejak saya menyadari bahwa saya sudah cukup mengetahui dan memiliki kesempatan untuk mengimplementasikan setidaknya beberapa versi dari kartu memori tanpa akhir. Namun, faktor lain ikut bermain di sini - usia dan segala sesuatu yang berhubungan dengannya. Ada lebih sedikit waktu untuk hobi, semakin banyak kekhawatiran. Dan hanya sekarang saya dapat memberikan kepada publik setidaknya beberapa hasil, Bukti Konsep yang lengkap.
Antarmuka fisik
Jadi, kartu memori dan joypad bekerja melalui antarmuka yang sama. Jumlah sinyal di dalamnya ada 6, berikut nama dan tujuannya:
- SEL0 - Sinyal pemilihan port pertama, level aktif rendah
- SEL1 - Sinyal pemilihan port kedua, level aktif rendah;
- CLK - Sinyal jam antarmuka, tingkat tinggi status pasif, di tepi jatuh, menempel di tepi;
- CMD - Sinyal data dari konsol ke pinggiran;
- DAT - Sinyal data dari pinggiran ke konsol;
- ACK - Jabat tangan perangkat keras, aktif rendah.
Ada juga dua tegangan suplai yang berbeda pada antarmuka: 3.3v dan 7.6v. Semua sinyal kecuali SEL0 dan SEL1 adalah umum untuk semua perangkat yang terhubung. Itulah mengapa kartu memori yang tidak berfungsi atau joypad di slot kedua memengaruhi pekerja di slot pertama, meskipun setelah konsol 16-bit itu tampak aneh. Saya pikir banyak yang telah mengenali SPI standar di antarmuka - semuanya benar, itu benar. Hanya menambahkan sinyal ACK untuk mengonfirmasi operasi I / O. Berikut adalah tugas sinyal pada kontak kartu memori:
Kartu memori diperbaiki dengan FLASH 5 volt
Karakteristik teknis dari antarmuka adalah sebagai berikut:
___ ___________________________ ____
\ / \ /
X X
___/ \___________________________/ \____
___ ____________
\ / \
\ / \
\____________/ \____
| |
| tck |
|<--------------------------->|
+-------+-------+------+-------+
| | . | . | . |
+-------+-------+------+-------+
| tck | 1 | 4 | - |
+-------+-------+------+-------+
ACK:
____
SEL- |______________________________________________
______ __________ ___________
CLK |||||||| |||||||| ||||||||
| |
ACK- -----------------------|_|-------------|_|---------
| ta1 | | | ta2 |
|<------->| | |<----->|
| | ap
>|-|<-----
+-----+------+-------+--------+
| | . | . | . |
+-----+------+-------+--------+
| ta1 | 0 | - | 100 | -
+-----+------+-------+--------+
| ta2 | | 10 | 1 |
+-----+------+-------+--------+
| ap | 2 | | | ACK
+-----+------+-------+--------+
Frekuensi sinyal CLK yang diukur adalah 250 kHz, yaitu 4 ยตs per siklus. Dengan parameter fisik dari antarmuka yang diurutkan, sekarang lapisan transport. Seorang insinyur yang berpengalaman telah memperhatikan bahwa joypad dan kartu memori terhubung sepenuhnya secara paralel dan dapat saling bertentangan. Memang, ada arbitrase perangkat lunak untuk ini. Setelah sinyal SELn diaktifkan, periferal tetap diam, tetapi mendengarkan byte pertama yang dikirim. Jika byte ini sama dengan 0x01, maka joypad diaktifkan, dan kartu memori tetap diam hingga sinyal pemilihan dinonaktifkan. Dan jika byte-nya 0x81, maka yang terjadi adalah sebaliknya: kartu memori diaktifkan, dan joypad tidak bersuara. Biasanya, tuan rumah sedang menunggu sinyal ACKpada byte arbitrase ini dan tidak menunggu lama. Ini diperlukan agar ada waktu untuk menginterogasi bagian pinggiran lainnya, jika bagian dari pinggiran ini tidak ada. Faktanya adalah bahwa sistem operasi mengumpulkan pengontrol dan kartu memori secara ketat sesuai dengan sinyal dari jalur kebalikan dari beam, atau lebih dikenal sebagai VBlank . Sudah diterima bahwa game di konsol hingga generasi ke-5 terkait dengan pengaturan waktu ini, yang sama dengan frekuensi gambar. Dan frame rate sangat stabil dan dinormalisasi: 50Hz untuk PAL dan 60Hz untuk NTSC. Artinya, periode polling untuk joystick dan kartu memori adalah 20ms untuk PAL atau 16ms untuk NTSC.
Jadi, kami menemukan arbitrase, sekarang tingkat teratas yang sebenarnya. Perintah apa yang dipahami oleh kartu memori PS1 standar? Ya, nyatanya hanya ada 3 orang.
- R - 0x52 atau Baca . Membaca sebuah sektor dari sebuah kartu memori;
- W - 0x57 atau Write . Perekaman sektor kartu memori;
- S - 0x53 atau Status . Membaca status kartu memori.
Seluruh kartu memori dibagi menjadi beberapa sektor. Satu sektor 128 byte. Jadi, 128KiB cocok untuk sektor 0x400 atau 1024. Dalam kasus ini, Anda tidak perlu menghapus sektor sebelum merekam. Namun sistem dijamin memberikan waktu untuk keseluruhan frame berikutnya saat merekam. Artinya, ia dapat membaca kartu memori setiap frame, tetapi menulisnya setelah satu. Omong-omong, semua jenis "Pemecah Kode" tidak mengikuti pengaturan waktu ini untuk mempercepat. Mari menganalisis setiap perintah secara lebih detail.
Protokol kartu memori
Urutan data yang dikirimkan di setiap perintah terlihat seperti ini:
Bacaan:
CMD 0x81 0x52 0x00 0x00 MSB LSB 0x00 0x00 0x00 0x00 0x00 ... 0x00 0x00 0x00 DAT ---- FLAG 0x5A 0x5D PRV PRV 0x5C 0x4D MSB LSB DATA ... DATA CHK ACK
:
CMD 0x81 0x57 0x00 0x00 MSB LSB DATA ... DATA CHK 0x00 0x00 0x00 DAT ---- FLAG 0x5A 0x5D PRV PRV PRV ... PRV PRV 0x5C 0x5D ACK
:
CMD 0x81 0x53 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 DAT ---- FLAG 0x5A 0x5D 0x5C 0x5D 0x04 0x00 0x00 0x80
Legenda:
CMD - Data yang dikirim oleh host ke kartu.
DAT - Data yang dikirimkan kartu ke host.
FLAG - Bendera saat ini dari status peta dan hasil dari perintah sebelumnya.
PRV - Data yang diterima sebelumnya, hasil dari menyederhanakan sirkuit di peta.
MSB - Byte tinggi dari nomor sektor.
LSB - Byte signifikan terkecil dari nomor sektor.
DATA - Muatan.
CHK - Checksum blok.
ACK - Bendera pengakuan.
Byte FLAG menggunakan bit berikut:
- D5 โ Sony. .
- D3 โ . .
- D2 โ , .
Setelah dinyalakan, BENDERA adalah 0x08. Dan setelah rekaman pertama, itu disetel ulang ke nol. Sistem operasi PS1 selalu menulis ke sektor 0x003F untuk ini, sehingga menyebabkan keausan pada sektor itu. Namun dalam kerangka menandai kartu memori oleh sistem, tidak ada informasi yang berguna di sektor ini. Nomor sektor MSB : LSB 10 bit dan berkisar dari 0x0000 hingga 0x03FF. Checksum CHK adalah XOR biasa dari semua 128 byte data + MSB dan LSB . Konfirmasi ACK hanya dapat mengambil 3 nilai: G 0x47, E 0x43 dan 0xFF. G = Baik atau Oke. E = Kesalahan . Sebenarnya saat membaca dari kartu, ACK selalu sama dengan G , dan saat menulis G = OK, E = checksum error dan 0xFF berarti nomor sektor salah. Benar, sebagian besar kartu hanya membuang bit yang tidak digunakan dalam byte tinggi dari nomor sektor dan oleh karena itu tidak pernah merespons dengan 0xFF. Angka 0x0400 dan 0x0080 dalam perintah status menyarankan pemikiran tertentu bahwa ini adalah jumlah sektor dan ukuran sektor dalam byte, tetapi ini tidak diketahui secara pasti. Nah, inilah kami dan kami sampai pada hal utama:
Menyadari kartu memori Anda
Jadi, ini semua informasi yang Anda butuhkan untuk membuat kartu memori PS1 Anda. Potensi hambatan adalah sebagai berikut:
- Saat membaca, perlu waktu untuk memperbarui data. Antara nomor sektor dan transfer data aktual, kami memiliki 4 byte dari mana kami dapat meregangkan ACK sedikit . Ngomong-ngomong, untuk kartu memori asli pada NOR FLASH, semua ACK berjalan merata, untuk kartu memori dengan SPI FLASH, setelah transmisi LSB , ada penundaan ACK , di mana pengontrol mengatur perintah ke SPI FLASH dan membaca byte pertama , dan membaca sisanya selama pertukaran.
- Saat merekam, setelah transfer seluruh paket dan awal perekaman ke array, dibutuhkan waktu, tetapi di sini sistem itu sendiri memberikan penundaan yang diperlukan.
Sedangkan untuk catu daya, joypads 3.3V digunakan untuk logika dan 7.6V digunakan untuk menyalakan motor. Kartu memori biasanya hanya menggunakan satu catu daya. Jika ada FLASH 5v di dalamnya, maka 7.6v dan stabilizer digunakan. Jika ada FLASH 3.3v, maka 3.3v akan segera digunakan.
Versi pertama yang saya bangun di atas STM32F407VG, yang didukung oleh 3.3V, memiliki SPI untuk PSIO, SDIO cepat, dan memori yang cukup untuk menyimpan seluruh gambar di dalamnya, memecahkan masalah yang disebutkan di atas. Foto perangkat yang sudah jadi:
Versi pertama kartu memori saya di STM32F407
Ternyata cepat, andal, tetapi mahal. Bisakah Anda melakukannya dengan lebih murah? Baiklah, tantangan itu diterima. Mempertimbangkan tugas spesifik, saya memilih STM32F042F6. Inilah yang terjadi:
Versi kedua kartu memori saya di STM32F042
Kartu kami digerakkan, jadi stabilisasi frekuensi dengan resonator kuarsa eksternal tidak diperlukan, osilator internal sudah cukup. Pengontrol ini memiliki satu SPI perangkat keras, jadi saya memberikannya ke kartu SD untuk mengurangi penundaan transportasi. PSIO akan menjadi perangkat lunak di sini.
Implementasi perangkat lunak
Hal pertama yang harus dilakukan adalah bekerja dengan kartu SD dalam mode SPI. Saya tidak akan terlalu memikirkan ini, ini telah lama dikunyah dan tersebar di Internet. Kode init, baca dan tulis sektor diberikan di bawah ini.
Card_Init ()
// TCardType Card_Init( void ) { // TCardType Res; uint32_t Cnt,OCR; uint8_t Dat, Resp; // CARD_OFF; Res = ctNone; // SPI PCLK/128: 48/128 = 0,375 SPI1->CR1 &= ~SPI_CR1_SPE; SPI1->CR1 = SPI_CR1_MSTR | SPI_LOW_SPEED; SPI1->CR1 |= SPI_CR1_SPE; // HAL_Delay( 1 ); // 256 for (Cnt = 0;Cnt < 256;Cnt++ ) { // Card_SPI( 0xFF ); } // CARD_ON; // do { // 0xFF Dat = Card_SPI( 0xFF ); } while ( Dat != 0xFF ); // CMD0: GO_IDLE_STATE Card_SendCMD( &CARD_CMD0[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, DISABLE, 128 ); // ? if ( Resp == 0x01 ) { // IDLE_STATE, CMD8: SEND_IF_COND Card_SendCMD( &CARD_CMD8[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, ENABLE, 128 ); // if ( Resp != 0x01 ) { // SDv1/MMC do { // ACMD41: APP_SEND_OP_COND Card_SendCMD( &CARD_ACMD41[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, ENABLE, 128 ); } while ( Resp == 0x01 ); // ? if ( Resp == 0x00 ) { // SD v1 Res = ctSD1; } else { // MMC, Res = ctUnknown; } } else { // SDv2 if ( (OCR & 0x0001FF) == 0x0001AA ) { // SDv2 do { // ACMD55: APP_CMD Card_SendCMD( &CARD_CMD55[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, DISABLE, 128 ); // if ( Resp == 0x01 ) { // ACMD41: APP_SEND_OP_COND Card_SendCMD( &CARD_ACMD41[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, ENABLE, 128 ); } } while ( Resp == 0x01 ); // ? if ( Resp == 0x00 ) { // CMD58: READ_OCR Card_SendCMD( &CARD_CMD58[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, ENABLE, 128 ); // ? if ( Resp == 0x00 ) { // OCR if ( (OCR & 0x40000000) == 0x00000000 ) { // Res = ctSD2; } else { // Res = ctSD3; } } else { // Res = ctUnknown; } } else { // Res = ctUnknown; } } else { // Res = ctUnknown; } } } else { // if ( Res != 0xFF ) { Res = ctUnknown; } } // if ( (Res == ctSD1) || (Res == ctSD2) ) { // 512 // CMD16: SET_BLOCKLEN Card_SendCMD( &CARD_CMD16[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( &OCR, DISABLE, 128 ); // ? if ( Resp != 0x00 ) { // Res = ctUnknown; } } // while ( (SPI1->SR & SPI_SR_BSY) != 0x0000 ) { } CARD_OFF; // if ( (Res != ctNone) && (Res != ctUnknown) ) { // SPI PCLK/2: 48/2 = 24 SPI1->CR1 &= ~SPI_CR1_SPE; SPI1->CR1 = SPI_CR1_MSTR; SPI1->CR1 |= SPI_CR1_SPE; } // return Res; }
Card_Read ()
// DMA FunctionalState Card_Read( TCardType CardType, uint8_t *Buf, uint32_t *Loaded, uint32_t Addr ) { // FunctionalState Res; uint8_t Cmd[ 6 ]; uint8_t Dat,Resp; uint32_t Cnt; // Res = DISABLE; // , ? if ( *(Loaded) != Addr ) { // *(Loaded) = Addr; // if ( (CardType == ctSD1) || (CardType == ctSD2) ) { // LBA Addr *= 0x00000200; } // while ( 1 ) { // - if ( CardType == ctNone ) { break; } if ( CardType == ctUnknown ) { break; } // Cmd[ 0 ] = CARD_CMD17; Cmd[ 1 ] = Addr >> 24; Cmd[ 2 ] = Addr >> 16; Cmd[ 3 ] = Addr >> 8; Cmd[ 4 ] = Addr; Cmd[ 5 ] = 0xFF; // CARD_ON; // do { // 0xFF Dat = Card_SPI( 0xFF ); } while ( Dat != 0xFF ); // Card_SendCMD( &Cmd[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( (uint32_t *)&Cmd[ 0 ], DISABLE, 128 ); // if ( Resp != 0x00 ) { break; } // Cnt = 2048; do { // Dat = Card_SPI( 0xFF ); // Cnt--; } while ( (Dat == 0xFF) && (Cnt > 0) ); // ? if ( Cnt == 0 ) { break; } // ? if ( Dat != CARD_DATA_TOKEN ) { break; } // , for (Cnt = 0;Cnt < 512;Cnt++) { // *(Buf) = Card_SPI( 0xFF ); Buf++; } // CRC Cmd[ 0 ] = Card_SPI( 0xFF ); Cmd[ 1 ] = Card_SPI( 0xFF ); // Res = ENABLE; // break; } } else { // Res = ENABLE; } // while ( (SPI1->SR & SPI_SR_BSY) != 0x0000 ) { } CARD_OFF; // , if ( Res == DISABLE ) { *(Loaded) = 0xFFFFFFFF; } // return Res; }
Card_Write ()
// DMA FunctionalState Card_Write( TCardType CardType, uint8_t *Buf, uint32_t *Loaded, uint32_t Addr ) { // FunctionalState Res; uint8_t Cmd[ 6 ]; uint8_t Dat,Resp; uint32_t Cnt; // Res = DISABLE; // if ( (CardType == ctSD1) || (CardType == ctSD2) ) { // LBA Addr *= 0x00000200; } // while ( 1 ) { // - if ( CardType == ctNone ) { break; } if ( CardType == ctUnknown ) { break; } // Cmd[ 0 ] = CARD_CMD24; Cmd[ 1 ] = Addr >> 24; Cmd[ 2 ] = Addr >> 16; Cmd[ 3 ] = Addr >> 8; Cmd[ 4 ] = Addr; Cmd[ 5 ] = 0xFF; // CARD_ON; // do { // 0xFF Dat = Card_SPI( 0xFF ); } while ( Dat != 0xFF ); // Card_SendCMD( &Cmd[ 0 ], CMD_LENGTH ); Resp = Card_WaitResp( (uint32_t *)&Cmd[ 0 ], DISABLE, 128 ); // if ( Resp != 0x00 ) { break; } // Card_SPI( CARD_DATA_TOKEN ); // // , for (Cnt = 0;Cnt < 512;Cnt++) { // Card_SPI( *(Buf) ); Buf++; } // CRC Card_SPI( 0xFF ); Card_SPI( 0xFF ); // Res = ENABLE; // break; } // while ( (SPI1->SR & SPI_SR_BSY) != 0x0000 ) { } CARD_OFF; // ? if ( Res == ENABLE ) { // *(Loaded) = Addr; } else { // *(Loaded) = 0xFFFFFFFF; } // return Res; }
Kartu ini menginisialisasi pada 375kHz (PCLK / 128) dan beroperasi pada 24MHz (PCLK / 2). Pada kecepatan seperti itu, pengukuran menunjukkan bahwa SDv1 dan SDHC memberikan sektor dalam 2,8ms untuk seluruh transaksi. Ini harus diingat karena penting untuk operasi baca PSIO.
Sekarang mari kita lihat PSIO. Seperti disebutkan di atas, kami memilikinya dalam perangkat lunak. Hanya ada dua sinyal untuk dilacak: SEL dan CLK . Kami akan melacak yang pertama di kedua sisi dan membuat persiapan untuk pertukaran data:
EXTI2_3_IRQHandler ()
// SEL void EXTI2_3_IRQHandler( void ) { // EXTI->PR = 0x00000004; // SEL if ( MEM_SEL ) { // SEL = 1 EXTI->IMR &= 0xFFFFFFFE; State.PSIO.Mode = mdSync; // LED_GREEN_OFF; } else { // SEL = 0 EXTI->IMR |= 0x00000001; State.PSIO.Bits = 7; // LED_GREEN_OFF; LED_RED_OFF; } // MEM_DAT1; MEM_nACK; }
Kami hanya akan menangkap sinyal CLK di bagian depan. Faktanya adalah bahwa STM32F042 hanya beroperasi pada 48MHz dan kinerjanya terlalu kecil untuk tugas kami. Dan jika Anda melakukan interupsi di kedua sisi, maka selama transfer byte, secara praktis tidak keluar dari penangan interupsi dan semuanya bekerja dengan benar di ambang kemungkinan, terkadang gagal. Dan jika Anda hanya bereaksi ke depan, dan pekerjaan yang perlu dilakukan untuk menolak dilakukan di akhir interupsi, maka semuanya baik-baik saja dalam waktu kurang dari 55% dari periode CLK , karena beberapa pemeriksaan dapat dibuang . Saya yakin jika handler ini ditulis dalam assembler seoptimal mungkin, maka ia akan dapat bekerja bahkan pada kedua lompatan tersebut. Berikut adalah kode penangannya:
EXTI0_1_IRQHandler ()
// CLK void EXTI0_1_IRQHandler( void ) { // EXTI->PR = 0x00000001; // uint16_t AckTime; // AckTime = 0; // State.PSIO.DataIn >>= 1; if ( MEM_CMD ) { // 1 State.PSIO.DataIn |= 0x80; } else { // 0 State.PSIO.DataIn &= 0x7F; } // if ( State.PSIO.Bits > 0 ) { // State.PSIO.Bits--; } else { // ? if ( State.PSIO.Bits == 0 ) { // State.PSIO.Bits = 7; // State.PSIO.DataOut = State.PSIO.DataIn; // switch ( State.PSIO.Mode ) { // case mdSync : { // if ( State.PSIO.DataIn == 0x81 ) { // State.PSIO.Mode = mdCmd; // State.PSIO.DataOut = State.MemCard.Status; // ACK AckTime = AckNormal; } else if ( State.PSIO.DataIn == 0x01 ) { // , . State.PSIO.Mode = mdDone; } // break; } // case mdCmd : { // State.PSIO.Mode = mdParam; // State.MemCard.Cmd = State.PSIO.DataIn; State.MemCard.Bytes = 0; // State.PSIO.DataOut = 0x5A; // ACK AckTime = AckNormal; // break; } // case mdParam : { // ACK AckTime = AckNormal; // switch ( State.MemCard.Cmd ) { // : R case 0x52 : { // switch ( State.MemCard.Bytes ) { // case 0 : { State.PSIO.DataOut = 0x5D; break; } case 1 : { break; } case 2 : { State.MemCard.Sector = State.PSIO.DataIn * 0x0100; State.MemCard.Check = State.PSIO.DataIn; break; } case 3 : { State.MemCard.Sector += State.PSIO.DataIn; State.MemCard.Check ^= State.PSIO.DataIn; State.PSIO.DataOut = 0x5C; State.SDCard.CardOp = coRead; AckTime = AckDelayed; break; } case 4 : { State.PSIO.DataOut = 0x5D; AckTime = AckDelayed; break; } case 5 : { State.PSIO.DataOut = State.MemCard.Sector >> 8; AckTime = AckDelayed; break; } case 6 : { State.PSIO.DataOut = State.MemCard.Sector; AckTime = AckDelayed; State.PSIO.Mode = mdRdData; State.MemCard.Bytes = 0; break; } default : { State.PSIO.Mode = mdDone; AckTime = 0; break; } } // LED_GREEN_ON; // break; } // : W case 0x57 : { // switch ( State.MemCard.Bytes ) { // case 0 : { State.PSIO.DataOut = 0x5D; break; } case 1 : { break; } case 2 : { State.MemCard.Sector = State.PSIO.DataIn * 0x0100; State.MemCard.Check = State.PSIO.DataIn; break; } case 3 : { State.MemCard.Sector += State.PSIO.DataIn; State.MemCard.Check ^= State.PSIO.DataIn; // break; } State.PSIO.Mode = mdWrData; State.MemCard.Bytes = 0; break; } default : { State.PSIO.Mode = mdDone; AckTime = 0; break; } } // LED_RED_ON; // break; } // : S case 0x53 : { // switch ( State.MemCard.Bytes ) { // case 0 : { State.PSIO.DataOut = 0x5D; break; } case 1 : { State.PSIO.DataOut = 0x5C; break; } case 2 : { State.PSIO.DataOut = 0x5D; break; } case 3 : { State.PSIO.DataOut = 0x04; break; } case 4 : { State.PSIO.DataOut = 0x00; break; } case 5 : { State.PSIO.DataOut = 0x00; break; } case 6 : { State.PSIO.DataOut = 0x80; break; } default : { State.PSIO.Mode = mdDone; AckTime = 0; break; } } // break; } // default : { State.PSIO.Mode = mdDone; break; } } // if ( State.PSIO.Mode == mdParam ) { State.MemCard.Bytes++; } // break; } // case mdRdData : { // ACK AckTime = AckNormal; // if ( State.MemCard.Bytes < 128 ) { // State.PSIO.DataOut = State.MemCard.Data[ State.MemCard.Bytes ]; State.MemCard.Check ^= State.PSIO.DataOut; } else { // switch ( State.MemCard.Bytes ) { // case 128 : { State.PSIO.DataOut = State.MemCard.Check; break; } // case 129 : { State.PSIO.DataOut = 0x47; break; } // default : { State.PSIO.Mode = mdDone; AckTime = 0; LED_GREEN_OFF; break; } } } // State.MemCard.Bytes++; // break; } // case mdWrData : { // ACK AckTime = AckNormal; // if ( State.MemCard.Bytes < 128 ) { // State.MemCard.Data[ State.MemCard.Bytes ] = State.PSIO.DataIn; State.MemCard.Check ^= State.PSIO.DataIn; } else { // switch ( State.MemCard.Bytes ) { // case 128 : { // if ( State.MemCard.Check == State.PSIO.DataIn ) { State.MemCard.Check = 0x47; } else { State.MemCard.Check = 0x4E; } // State.PSIO.DataOut = 0x5C; // break; } // case 129 : { State.PSIO.DataOut = 0x5D; break; } // case 130 : { // , if ( State.MemCard.Sector < 0x4000 ) { // , State.PSIO.DataOut = State.MemCard.Check; // ? if ( State.MemCard.Check == 0x47 ) { // State.SDCard.CardOp = coWrite; // State.MemCard.Status &= ~StateNew; } } else { // , State.PSIO.DataOut = 0xFF; } // break; } // default : { State.PSIO.Mode = mdDone; AckTime = 0; break; } } } // State.MemCard.Bytes++; // break; } // , case mdDone : { break; } // - default : { State.PSIO.Mode = mdSync; break; } } } } // if ( State.PSIO.Mode != mdSync ) { // if ( State.PSIO.DataOut & 0x01 ) { // 1 MEM_DAT1; } else { // 0 MEM_DAT0; } // State.PSIO.DataOut >>= 1; } // ACK? if ( AckTime > 0 ) { // CNT TIM3->CNT = AckTime; // State.PSIO.Ack = DISABLE; // TIM3->SR = 0x0000; // TIM3->CR1 |= TIM_CR1_CEN; } }
Timer TIM3 akan bertanggung jawab untuk menghasilkan ACK . Ini diperlukan agar kernel bebas untuk bekerja dengan kartu SD selama penundaan ini. Pengatur interupsi pengatur waktu adalah seperti ini:
TIM3_IRQHandler ()
// TIM3 void TIM3_IRQHandler( void ) { // TIM3->SR = 0x0000; // if ( State.PSIO.Ack == ENABLE ) { // ACK MEM_nACK; } else { // ACK MEM_ACK; // State.PSIO.Ack = ENABLE; // TIM3->CNT = 0; // TIM3->CR1 |= TIM_CR1_CEN; } }
Kode cukup dikomentari dan saya pikir itu tidak membutuhkan banyak analisis. Saya hanya akan mencatat bahwa setelah menerima byte kedua dari nomor sektor dalam perintah baca, kami menetapkan bendera untuk operasi baca dari kartu SD untuk kode yang berputar di loop abadi fungsi main (). Dan segera setelah itu, diterbitkan 4 ACK berikutnya dengan perpanjangan waktu. Pada antarmuka terlihat seperti ini:
Tangkapan layar dari program penganalisis logika, 4 penundaan besar dalam transaksi disorot
Secara total, sekitar 3,5 md diketik, dan ini lebih dari cukup bagi algoritme di kode utama untuk membaca sektor tersebut. Selain itu, kode ini hanya dapat berfungsi jika tidak ada gangguan, mis. hanya dalam jeda besar itu. Selama perekaman, bendera ditetapkan di bagian paling akhir dan karena fakta bahwa sistem memungkinkan kartu memori untuk menyelesaikan perekaman, kode utama bekerja tanpa gangguan dari interupsi. Sekarang mari kita lihat kode dari main loop.
utama ()
// while ( 1 ) { // if ( CARD_nCD == 0 ) { // if ( State.SDCard.CardType == ctNone ) { // LED_GREEN_ON; LED_RED_OFF; // , State.SDCard.CardType = Card_Init(); // ? if ( State.SDCard.CardType != ctUnknown ) { // if ( Card_FSInit( &State.SDCard, &CARD_IMAGE[ 0 ] ) == ENABLE ) { // , EXTI->IMR |= 0x00000004; // LED_GREEN_OFF; LED_RED_OFF; } else { // State.SDCard.CardType = ctUnknown; // LED_GREEN_ON; LED_RED_ON; } } else { // LED_GREEN_ON; LED_RED_ON; } } } else { // if ( State.SDCard.CardType != ctNone ) { // , PSIO EXTI->IMR &= 0xFFFFFFFA; // State.PSIO.Mode = mdSync; State.PSIO.Bits = 0; State.PSIO.DataIn = 0x00; State.PSIO.DataOut = 0; State.PSIO.Ack = DISABLE; State.MemCard.Status = StateNew; State.SDCard.CardType = ctNone; State.SDCard.CardOp = coIdle; State.SDCard.LoadedLBA = 0xFFFFFFFF; } // LED_GREEN_OFF; LED_RED_OFF; } // if ( (State.SDCard.CardType != ctNone) && (State.SDCard.CardType != ctUnknown) ) { // ? if ( State.SDCard.CardOp == coWrite ) { // Ofs = State.MemCard.Sector & 0x03FF; LBA = (Ofs >> 2) & 0x000000FF; Ofs = (Ofs << 7) & 0x00000180; // Card_Read( State.SDCard.CardType, &State.SDCard.CardBuf[ 0 ], &State.SDCard.LoadedLBA, State.SDCard.CardList[ LBA ] ); // for (Cnt = 0;Cnt < 128;Cnt++) { // State.SDCard.CardBuf[ Ofs + Cnt ] = State.MemCard.Data[ Cnt ]; } // Card_Write( State.SDCard.CardType, &State.SDCard.CardBuf[ 0 ], &State.SDCard.LoadedLBA, State.SDCard.CardList[ LBA ] ); // LED_RED_OFF; // State.SDCard.CardOp = coIdle; } // ? if ( State.SDCard.CardOp == coRead ) { // Ofs = State.MemCard.Sector & 0x03FF; LBA = (Ofs >> 2) & 0x000000FF; Ofs = (Ofs << 7) & 0x00000180; // Card_Read( State.SDCard.CardType, &State.SDCard.CardBuf[ 0 ], &State.SDCard.LoadedLBA, State.SDCard.CardList[ LBA ] ); // for (Cnt = 0;Cnt < 128;Cnt++) { // State.MemCard.Data[ Cnt ] = State.SDCard.CardBuf[ Ofs + Cnt ]; } // State.SDCard.CardOp = coIdle; } } }
Dalam loop abadi, sinyal penyisipan kartu SD terus dianalisis. Jika Anda menariknya saat bepergian, kode akan menonaktifkan PSIO dan PS1 akan "kehilangan" kartunya. Jika kartu dimasukkan kembali (atau baru dihidupkan dengan kartu dimasukkan), maka pada awalnya akan ada upaya untuk menginisialisasi kartu dengan fungsi Card_Init (), yang akan mengembalikan jenis kartu yang terdeteksi. Ini penting karena metode pengalamatan SDv1 dan SDHC / SDXC lainnya berbeda. Kode inisialisasi itu sendiri tidak membawa rahasia apa pun dan telah dimata-matai dalam tumpukan contoh yang tersedia di Internet tentang FatFS dan proyek serupa.
Setelah inisialisasi kartu, fungsi rumit Card_FSInit () dipanggil. Ini adalah fitur terpenting dari proyek ini. Faktanya adalah bahwa STM32F042 memiliki kemampuan yang sederhana dan tidak akan dapat menarik dukungan FatFS penuh pada kecepatan yang diperlukan. Oleh karena itu, saya menemukan metode ini: file gambar selalu 128KiB, oleh karena itu, Anda hanya perlu mengetahui 256 sektor dari 512 byte, yang masing-masing akan memiliki persis 4 sektor kartu memori PS1 kami. Jadi, kami melakukan hal berikut:
- Kami menganalisis sektor LBA = # 0 untuk MBR. Jika ini memang MBR, maka kita mendapatkan sektor baru di mana MBS berada.
- Setelah menerima alamat MBS yang seharusnya (bisa # 0, jika tidak ada MBR, atau beberapa nomor, jika ada MBR), kami mulai menganalisisnya untuk dimiliki oleh salah satu FAT: FAT12, FAT16, FAT32 atau vFAT .
- Jika sektor telah lulus pemeriksaan, maka kami mengambil informasi tentang struktur darinya dan mencari elemen dengan nama file di direktori root. Dalam kasus ini, ini adalah 'MEMCRD00.BIN'.
- Jika file seperti itu ditemukan, maka kami memeriksa ukurannya - itu harus diperbaiki dengan ketat pada 0x20000 byte. Jika semuanya sudah jadi, kita mendapatkan nomor cluster pertama.
- Jika kita telah mencapai titik ini, maka kita sudah memiliki semua informasi yang diperlukan untuk membuat daftar sektor fisik LBA tempat gambar kita berada. Melalui rantai FAT dan menggunakan informasi struktur dari MBS, kami mengisi tabel 256 nomor sektor LBA.
Jika berhasil, PSIO mulai dan PS1 akan melihat kartu seperti biasa 15-blok kartu. Jika kesalahan terjadi pada tahap mana pun, operasi terputus, kedua LED menyala dan semuanya tetap dalam keadaan ini sampai daya dilepas atau kartu SD diganti. Berikut adalah kode untuk prosedur ini:
Card_FSInit ()
// , FAT16 FunctionalState Card_FSInit( TSDCard *SDCard, const uint8_t *FName ) { // FunctionalState Res; uint8_t *Buf; uint8_t Pos; uint16_t ClustSize,Reserv,RootSize,FATSize,Cluster; uint32_t Cnt,LBA,SysOrg,FATOrg,RootOrg,DataOrg; int Compare; // Res = DISABLE; SysOrg = 0; Cluster = 0xFFFF; // while ( 1 ) { // 0 if ( Card_Read( SDCard->CardType, &SDCard->CardBuf[ 0 ], &SDCard->LoadedLBA, SysOrg ) == DISABLE ) { break; } // #0 MBR if ( *((uint16_t *)&SDCard->CardBuf[ 0x01FE ]) != 0xAA55 ) { break; } // MBR if ( ((SDCard->CardBuf[ 0x01BE ] == 0x00) || (SDCard->CardBuf[ 0x01BE ] == 0x80)) && ((SDCard->CardBuf[ 0x01CE ] == 0x00) || (SDCard->CardBuf[ 0x01CE ] == 0x80)) && ((SDCard->CardBuf[ 0x01DE ] == 0x00) || (SDCard->CardBuf[ 0x01DE ] == 0x80)) && ((SDCard->CardBuf[ 0x01EE ] == 0x00) || (SDCard->CardBuf[ 0x01EE ] == 0x80)) ) { // MBR, for (Cnt = 0;Cnt < 4;Cnt++) { // if ( (SDCard->CardBuf[ (Cnt * 0x0010) + 0x01C2 ] == 0x01) || // 0x01: FAT12 (SDCard->CardBuf[ (Cnt * 0x0010) + 0x01C2 ] == 0x04) || // 0x04: FAT16 (SDCard->CardBuf[ (Cnt * 0x0010) + 0x01C2 ] == 0x06) || // 0x06: Big FAT16 (SDCard->CardBuf[ (Cnt * 0x0010) + 0x01C2 ] == 0x0E) ) // 0x0E: vFAT { // , MBS SysOrg = SDCard->CardBuf[ (Cnt * 0x0010) + 0x01C6 ]; SysOrg += (SDCard->CardBuf[ (Cnt * 0x0010) + 0x01C7 ] * 0x00000100); SysOrg += (SDCard->CardBuf[ (Cnt * 0x0010) + 0x01C8 ] * 0x00010000); SysOrg += (SDCard->CardBuf[ (Cnt * 0x0010) + 0x01C9 ] * 0x01000000); // break; } } } // MBS if ( Card_Read( SDCard->CardType, &SDCard->CardBuf[ 0 ], &SDCard->LoadedLBA, SysOrg ) == DISABLE ) { break; } // MBS if ( *((uint16_t *)&SDCard->CardBuf[ 0x01FE ]) != 0xAA55 ) { break; } if ( SDCard->CardBuf[ 0x000D ] == 0x00 ) { break; } if ( (SDCard->CardBuf[ 0x0010 ] == 0x00) || (SDCard->CardBuf[ 0x0010 ] > 0x02) ) { break; } if ( SDCard->CardBuf[ 0x0015 ] != 0xF8 ) { break; } if ( *((uint32_t *)&SDCard->CardBuf[ 0x001C ]) != SysOrg ) { break; } if ( SDCard->CardBuf[ 0x0026 ] != 0x29 ) { break; } if ( *((uint16_t *)&SDCard->CardBuf[ 0x0036 ]) != 0x4146 ) { break; } if ( *((uint16_t *)&SDCard->CardBuf[ 0x0038 ]) != 0x3154 ) { break; } if ( SDCard->CardBuf[ 0x003A ] != 0x36 ) { break; } // , ClustSize = SDCard->CardBuf[ 0x000D ]; Reserv = *((uint16_t *)&SDCard->CardBuf[ 0x000E ]); RootSize = (SDCard->CardBuf[ 0x0012 ] * 0x0100) + SDCard->CardBuf[ 0x0011 ]; FATSize = *((uint16_t *)&SDCard->CardBuf[ 0x0016 ]); // FAT ROOT FATOrg = SysOrg + Reserv; RootOrg = FATOrg + (FATSize * 2); DataOrg = RootOrg + (RootSize / 16 ); // , for (LBA = 0;LBA < (RootSize / 16);LBA++) { // if ( Card_Read( SDCard->CardType, &SDCard->CardBuf[ 0 ], &SDCard->LoadedLBA, RootOrg + LBA ) == ENABLE ) { // 16 , for (Cnt = 0;Cnt < 16;Cnt++) { // Compare = memcmp( &SDCard->CardBuf[ Cnt * 32 ], &CARD_IMAGE[ 0 ], 11 ); if ( Compare == 0 ) { // , if ( *((uint32_t *)&SDCard->CardBuf[ (Cnt * 32) + 0x001C ]) == 0x00020000 ) { // , Cluster = *((uint16_t *)&SDCard->CardBuf[ (Cnt * 32) + 0x001A ]); // Res = ENABLE; // break; } } } // - if ( Res == ENABLE ) { break; } } else { // - break; } } // , , if ( Res == ENABLE ) { // , Pos = 0; do { // if ( Cluster < 0x0002 ) { // , Res = DISABLE; break; } // LBA LBA = DataOrg + ((Cluster - 2) * ClustSize); // for (Cnt = 0;Cnt < ClustSize;Cnt++) { // LBA SDCard->CardList[ Pos ] = LBA + Cnt; // Pos++; if ( Pos == 0 ) { break; } } // , // , if ( Pos != 0 ) { // LBA = FATOrg; Reserv = Cluster; while ( Reserv > 256 ) { LBA++; Reserv -= 256; } // if ( Card_Read( SDCard->CardType, &SDCard->CardBuf[ 0 ], &SDCard->LoadedLBA, LBA ) == ENABLE ) { // Cluster = *((uint16_t *)&SDCard->CardBuf[ Reserv * 2 ]); } else { // Res = DISABLE; break; } } } while ( (Cluster != 0xFFFF) && (Pos != 0) ); } // break; } // return Res; }
Sejujurnya, karena ini hanya PoC, hanya pencarian FAT16 yang diterapkan di sini. FAT12, mungkin, tidak perlu didukung - microSD dengan volume sekecil itu tidak ada. Tetapi FAT32 atau vFAT dapat ditambahkan jika seseorang membutuhkannya di masa mendatang.
Nama gambar 'MEMCRD00.BIN' tidak dipilih secara kebetulan. Faktanya adalah bahwa di masa mendatang saya berencana untuk menambahkan pilihan gambar melalui kombinasi tombol standar pada joypad untuk kartu memori multi-halaman: saat PILIH ditekan, satu kali tekan pada L1 / R1 akan mengikuti. Dan dengan mengubah 2 karakter terakhir, Anda dapat mendukung 100 gambar di direktori root, dari 'MEMCRD00.BIN' menjadi 'MEMCRD99.BIN'. Ada dasar untuk ini di penangan interupsi SCKdi antarmuka PSIO, cabang tempat panggilan ke joypad dianalisis. Tidak ada masalah untuk membuat sniffer, tetapi periferal pengontrol PS1 kaya dan Anda harus mendukung hampir semuanya.
Hasilnya, perangkat itu menjadi efisien dan semua orang bisa mengulanginya jika dia mau. Tautan ke seluruh proyek ada di sini. Saya akan dengan senang hati membantu semua orang yang tertarik dengan komentar pada artikel.
PS Saya sangat ingin menunjukkan di sini daftar semua sumber informasi yang saya gunakan dalam membuat proyek ini, tetapi sayangnya, ini sangat sulit. Banyak yang terdengar secara kebetulan. Sesuatu datang dalam bentuk file TXT dengan informasi umum tentang PS1 lebih dari 15 tahun yang lalu, bagi mereka yang ingin membuat emulator sendiri. Dan sekarang semuanya ada sebagai beberapa file teks di hard drive saya. Kita dapat mengatakan bahwa seluruh Internet telah menjadi sumber informasi selama 15 tahun terakhir.