Eine endlose Speicherkarte für PS1 erstellen



PS1 (auch bekannt als PSX, auch bekannt als PS One) ist die erste Generation der PlayStation-Spielekonsolen von Sony und gehört zur fünften Generation von Spielekonsolen im Allgemeinen. Zum Lesen von CDs wird ein Laufwerk mit 2-facher Geschwindigkeit verwendet. Eine so große Datenmenge im Vergleich zu den heutigen Standards für die Konsole ermöglichte es den Spieleentwicklern, bei der Erstellung von Inhalten für Spiele nicht besonders auf die Einschränkungen zurückzublicken, die letztere im Vergleich zu den Spielen der vorherigen Generation von höherer Qualität machten von Konsolen. Außerdem können Spiele jetzt lang sein. Und wenn irgendein Spiel, mit seltenen Ausnahmen, auf Konsolen früherer Generationen in einer Spielsitzung abgeschlossen werden konnte, dann war bei PS1-Spielen alles anders. Um den Fortschritt zu speichern, verfügt PlayStation über Speicherkarten: kleine, entfernbare, nichtflüchtige Speichermodule.



Wenn Sie sich genau fragen, wie die PlayStation 1-Speicherkarte funktioniert, wie sie funktioniert und wie Sie Ihre eigene erstellen können, sind Sie bei cat willkommen.



Die PS1-Speicherkarte ist also ein Standard-Peripheriegerät, wie der gesamte Zoo mit Joypads, Joysticks und anderem Zubehör. Um genau zu verstehen, wie es funktioniert, müssen Sie sich zuerst ansehen, was es enthält.





Foto der Leiterplatte einer Standard-15-Block-Speicherkarte



Wie Sie auf dem Foto sehen können, ist das Gerät der Karte sehr einfach: ein Controller, der Systemanforderungen bedient, und tatsächlich ein nichtflüchtiger Speicher, der durch den Standard NOR FLASH dargestellt wird. Logischerweise ist die Speicherkarte in 15 Blöcke aufgeteilt, die Spiele verwenden können. Es mag scheinen, dass 15 für ein Binärsystem nicht logisch ist, aber hier gibt es keinen Widerspruch: Ein Block wird für das Dateisystem angegeben, Dateinamen und sogar animierte Symbole werden dort gespeichert, genau wie bei NTFS-Streams. Jeder Block hat eine Größe von 8 KiB, insgesamt 16 Blöcke sind 128 KiB, was aus der Markierung des FLASH-Speichers auf dem Foto oben ersichtlich ist.



Zuerst war dies genug für alle, aber dann tauchten Spiele auf, die mehr als einen Block gleichzeitig verwendeten. Beispielsweise verwenden einige Simulatoren, wie Sega GT, 4-5 Blöcke, während ConstructorIm Allgemeinen besteht die gesamte Speicherkarte aus 15 Blöcken. Dies zwang dazu, mehr Karten zu kaufen, und die Situation drohte, wie Disketten oder Kassetten zu werden. Aber dann hielten die Piraten an und begannen, Karten für 2, 4 oder 8 Seiten gleichzeitig auszugeben. Und die Seiten wurden entweder durch eine clevere Kombination auf dem Joypad oder durch eine explizite Taste auf der Speicherkarte selbst gewechselt. Bei Karten mit mehr als 2 Seiten wurde jedoch eine Komprimierung verwendet, und die tatsächliche Anzahl der Seiten war viel geringer, und einige Karten konnten dumm blockiert werden. Und es war sehr schwierig, sie aus diesem Zustand herauszuholen, aber was die Spieler nicht wollten, um zu retten. Hier sind typische Vertreter von mehrseitigen Speicherkarten:





Auf der linken Seite befindet sich eine Speicherkarte mit 2 Seiten, auf der rechten Seite mit 8. Auf der rechten Seite befindet sich ein Knopf zum Umblättern der Hardwareseite und eine Anzeige mit der Nummer 1 bis 8 versteckt hinter einem dunklen Glas



Kleiner lyrischer Exkurs



Alles begann im Jahr 2001, als ich eine Wunderdiskette für den PC namens "All Emulators" kaufte, auf der es PS1-Emulatoren gab, darunter: es war Bleem! und frühes ePSXe. Und mein damaliger Computer konnte sogar meine PS1-Festplatten spielbar abspielen! Wenig später bekam ich ein Modem und lernte DirectPad Pro kennen . Das Anschließen eines nativen Joysticks an einen Computer (allerdings über LPT) kostet viel. Und dieses System funktionierte sowohl auf 9x als auch auf XP! Und wenig später, bereits im Jahr 2002, erfuhr ich von Memory Card Capture Sakura! Dieses Programm ermöglichte die Arbeit mit echten Speicherkarten unter Verwendung des gleichen DirectPad Pro-Verbindungsschemas. Damals kam mir die Idee, eine "endlose" Speicherkarte herzustellen, mit der ich Informationen mit einem Computer austauschen kann, ohne dass zusätzliche Geräte erforderlich sind. Aber zu dieser Zeit hatte ich nicht genug Informationen und verfügbare Elementbasis, und die Idee blieb nur eine Idee, die irgendwo im Hinterhof meines Bewusstseins schimmerte.



Fast 9 Jahre sind vergangen, seit ich festgestellt habe, dass ich bereits genug weiß und die Möglichkeit habe, zumindest eine Version einer endlosen Speicherkarte zu implementieren. Hier kam jedoch ein weiterer Faktor ins Spiel - das Alter und alles, was damit zusammenhängt. Es gibt weniger Zeit für Hobbys, immer mehr Sorgen. Und erst jetzt kann ich der Öffentlichkeit zumindest ein Ergebnis liefern, einen vollwertigen Proof of Concept.



Physische Schnittstelle



Die Speicherkarte und die Joypads arbeiten also über eine gemeinsame Schnittstelle. Die Anzahl der darin enthaltenen Signale beträgt 6, hier sind ihre Namen und Zwecke:



  • SEL0 - Erstes Portauswahlsignal , aktiver Pegel niedrig
  • SEL1 - Zweites Portauswahlsignal , aktiver Pegel niedrig;
  • CLK - Taktsignal der Schnittstelle, passiver Zustand hoher Pegel, bei fallender Flanke, Verriegelung an Flanke;
  • CMD - Datensignal von der Konsole zur Peripherie;
  • DAT - Datensignal von der Peripherie zur Konsole;
  • ACK - Hardware-Handshake, aktiv niedrig.


Es gibt auch zwei verschiedene Versorgungsspannungen an der Schnittstelle: 3,3 V und 7,6 V. Alle Signale außer SEL0 und SEL1 sind allen angeschlossenen Geräten gemeinsam. Aus diesem Grund wirkte sich eine nicht funktionierende Speicherkarte oder ein Joypad im zweiten Steckplatz auf die Mitarbeiter im ersten aus, obwohl dies nach 16-Bit-Konsolen seltsam erschien. Ich denke, dass viele den Standard- SPI in der Benutzeroberfläche bereits erkannt haben - alles ist richtig, das ist es. Fügte nur ein ACK- Signal hinzu , um den E / A-Betrieb zu bestätigen. Hier sind die Zuordnungen der Signale an den Kontakten der Speicherkarte:





Reparierte Speicherkarte mit 5 Volt FLASH Die



technischen Eigenschaften der Schnittstelle sind wie folgt:



        ___ ___________________________ ____
     \ /                           \ /    
         X                             X
 ___/ \___________________________/ \____
        ___                  ____________       
           \                /            \      
       \              /              \     
             \____________/                \____
            |                             |
            |           tck               |
            |<--------------------------->|

+-------+-------+------+-------+
|       | .  | . | . |
+-------+-------+------+-------+
| tck   | 1  | 4 |   -   |
+-------+-------+------+-------+

 ACK:
     ____                                               
SEL-     |______________________________________________
     ______        __________        ___________        
CLK        ||||||||          ||||||||           ||||||||
                  |                 |
ACK- -----------------------|_|-------------|_|---------
                  |   ta1   | |     |  ta2  |
                  |<------->| |     |<----->|
                            | |  ap
                           >|-|<-----

+-----+------+-------+--------+
|     | . |  . |  . |
+-----+------+-------+--------+
| ta1 | 0 |   -   | 100 |  -
+-----+------+-------+--------+
| ta2 |      | 10 |   1  | 
+-----+------+-------+--------+
|  ap | 2 |       |        |  ACK
+-----+------+-------+--------+


Die gemessene Frequenz des CLK- Signals beträgt 250 kHz, was 4 µs pro Zyklus entspricht. Mit den physikalischen Parametern der Schnittstelle aussortiert, nun die Transportschicht. Ein erfahrener Ingenieur hat bereits bemerkt, dass das Joypad und die Speicherkarte vollständig parallel geschaltet sind und Konflikte miteinander verursachen können. In der Tat gibt es dafür ein Software-Schiedsverfahren. Nachdem das SELn- Signal aktiviert wurde, bleibt die Peripherie stumm, hört jedoch auf das erste gesendete Byte. Wenn dieses Byte gleich 0x01 ist, wird das Joypad aktiviert und die Speicherkarte bleibt stumm, bis das Auswahlsignal deaktiviert wird. Und wenn das Byte 0x81 war, ist das Gegenteil der Fall: Die Speicherkarte ist aktiviert und das Joypad ist stumm. Natürlich wartet der Host auf ein ACK- Signal auf dieses Byte der Schiedsgerichtsbarkeit und wartet nicht lange. Dies ist notwendig, um Zeit zu haben, den Rest der Peripherie abzufragen, wenn ein Teil dieser Peripherie fehlt. Tatsache ist, dass das Betriebssystem Controller und Speicherkarten streng nach dem Signal des umgekehrten Weges des Strahls oder besser bekannt als VBlank abfragt . Es wird so akzeptiert, dass Spiele in Konsolen bis zur 5. Generation an dieses Timing gebunden sind, das der Framerate entspricht. Die Bildrate ist streng stabil und normalisiert: 50 Hz für PAL und 60 Hz für NTSC. Das heißt, die Abfragezeit für Joysticks und Speicherkarten beträgt 20 ms für PAL oder 16 ms für NTSC.



Also haben wir das Schiedsverfahren herausgefunden, jetzt die eigentliche oberste Ebene. Welche Befehle versteht die Standard-PS1-Speicherkarte? Ja, tatsächlich gibt es nur drei davon.



  • R - 0x52 oder Lesen . Lesen eines Sektors einer Speicherkarte;
  • W - 0x57 oder Schreiben . Speicherkartensektoraufzeichnung;
  • S - 0x53 oder Status . Lesen des Status der Speicherkarte.


Die gesamte Speicherkarte ist in Sektoren unterteilt. Ein Sektor mit 128 Bytes. Somit passt 128 KB für 0x400 oder 1024 Sektoren. In diesem Fall müssen Sie den Sektor vor der Aufnahme nicht löschen. Das System gibt jedoch garantiert Zeit für das nächste ganze Bild bei der Aufnahme. Das heißt, es kann die Speicherkarte in jedem Frame lesen, schreibt sie jedoch nach einem. Übrigens halten sich alle Arten von "Codebreakern" nicht an diese Zeiten, um schneller zu werden. Lassen Sie uns jeden Befehl genauer analysieren.



Speicherkartenprotokoll



Die Reihenfolge der übertragenen Daten in jedem Befehl sieht folgendermaßen aus:

Lesen:

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


Legende:



CMD - Daten, die der Host an die Karte sendet.

DAT - Daten, die die Karte an den Host sendet.

FLAG - Aktuelle Flags des Kartenstatus und das Ergebnis des vorherigen Befehls.

PRV - Zuvor empfangene Daten, das Ergebnis der Vereinfachung der Schaltung in der Karte.

MSB - High Byte der Sektornummer.

LSB - Kleinstes signifikantes Byte der Sektornummer.

DATA - Nutzlast.

CHK - Prüfsumme des Blocks.

ACK - Bestätigungsflag.



Das FLAG- Byte verwendet die folgenden Bits:



  • D5 – Sony. .
  • D3 – . .
  • D2 – , .


Nach dem Einschalten ist FLAG 0x08. Und nach dem ersten Datensatz wird es auf Null zurückgesetzt. Das PS1-Betriebssystem schreibt dafür immer in den Sektor 0x003F, wodurch dieser Sektor abgenutzt wird. Im Rahmen der Kennzeichnung der Speicherkarte durch das System gibt es in diesem Bereich jedoch keine nützlichen Informationen. MSB- Sektornummer : LSB 10 Bit und reicht von 0x0000 bis 0x03FF. Die CHK- Prüfsumme ist das übliche XOR aller 128 Datenbytes + MSB und LSB . Die ACK- Bestätigung kann nur 3 Werte annehmen : G 0x47, E 0x43 und 0xFF. G = Gut oder OK. E = Fehler . Tatsächlich ist ACK beim Lesen von der Karte immer gleich G , und beim Schreiben von G = OK, E = Prüfsummenfehler und 0xFF bedeutet eine falsche Sektornummer. Richtig, die meisten Karten verwerfen einfach nicht verwendete Bits im High-Byte der Sektornummer und antworten daher niemals mit 0xFF. Die Zahlen 0x0400 und 0x0080 im Statusbefehl lassen auf bestimmte Gedanken schließen, dass dies die Anzahl der Sektoren und die Größe des Sektors in Bytes ist, dies ist jedoch nicht mit Sicherheit bekannt. Nun, hier sind wir und kommen zur Hauptsache:



Realisieren Sie Ihre Speicherkarte



Dies sind also alle Informationen, die Sie zum Erstellen Ihrer PS1-Speicherkarte benötigen. Mögliche Engpässe sind:



  1. Beim Lesen dauert es einige Zeit, um die Daten zu aktualisieren. Zwischen der Sektornummer und der tatsächlichen Datenübertragung haben wir 4 Bytes, aus denen wir die ACK ein wenig strecken können . Übrigens, für die ursprüngliche Speicherkarte in NOR FLASH gehen alle ACKs gleichmäßig, für Speicherkarten mit SPI FLASH gibt es nach der LSB- Übertragung eine ACK- Verzögerung , während der der Controller den Befehl auf SPI FLASH setzt und das erste Byte liest und liest den Rest während des Austauschs.
  2. Bei der Aufzeichnung dauert es nach der Übertragung des gesamten Pakets und dem Beginn der Aufzeichnung an das Array einige Zeit, aber hier gibt das System selbst die erforderliche Verzögerung an.


Für die Stromversorgung werden die 3,3-V-Joypads für die Logik und 7,6 V für die Stromversorgung der Motoren verwendet. Speicherkarten verwenden normalerweise nur ein Netzteil. Wenn sich im Inneren 5 V FLASH befinden, werden 7,6 V und ein Stabilisator verwendet. Wenn 3,3 V FLASH vorhanden sind, wird sofort 3,3 V verwendet.



Die erste Version, die ich auf STM32F407VG aufgebaut habe und die mit 3,3 V betrieben wird, verfügt über SPI für PSIO, schnelles SDIO und genügend Speicher, um das gesamte Bild in sich selbst zu speichern und die oben genannten Probleme zu lösen. Foto des fertigen Geräts:





Die erste Version meiner Speicherkarte auf STM32F407



Es stellte sich schnell, zuverlässig, aber teuer heraus. Schaffst du es billiger? Nun, die Herausforderung wird angenommen. In Anbetracht der Besonderheiten der Aufgabe habe ich mich für STM32F042F6 entschieden. Folgendes ist passiert:





Zweite Version meiner Speicherkarte auf STM32F042



Unsere Karte wird angesteuert, sodass keine Frequenzstabilisierung mit einem externen Quarzresonator erforderlich ist. Ein interner Oszillator reicht aus. Dieser Controller verfügt über ein Hardware-SPI, daher habe ich es der SD-Karte gegeben, um Transportverzögerungen zu reduzieren. PSIO wird hier Software sein.



Software-Implementierung



Als erstes müssen Sie mit einer SD-Karte im SPI-Modus arbeiten. Ich werde nicht zu viel darüber nachdenken, es wurde lange gekaut und über das Internet verstreut. Der Init-, Lese- und Schreibcode des Sektors ist unten angegeben.



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;
}
      
      





Die Karte wird bei 375 kHz (PCLK / 128) initialisiert und arbeitet bei 24 MHz (PCLK / 2). Bei solchen Geschwindigkeiten zeigten Messungen, dass SDv1 und SDHC für die gesamte Transaktion einen Sektor innerhalb von 2,8 ms ergeben. Dies sollte daran erinnert werden, weil wichtig für den PSIO-Lesevorgang.



Nun schauen wir uns PSIO an. Wie oben erwähnt, haben wir es sowieso in der Software. Es sind nur zwei Signale zu verfolgen: SEL und CLK . Wir werden den ersten an beiden Fronten verfolgen und Vorbereitungen für den Datenaustausch treffen:



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;
}
      
      





Wir werden nur das CLK- Signal an der Vorderseite abfangen. Tatsache ist, dass STM32F042 nur mit 48 MHz arbeitet und seine Leistung für unsere Aufgabe zu gering ist. Und wenn Sie an beiden Fronten einen Interrupt ausführen, wird dieser während der Übertragung eines Bytes praktisch nicht aus dem Interrupt-Handler entfernt, und alles funktioniert am Rande der Möglichkeit, was manchmal fehlschlägt. Und wenn Sie nur auf die Front reagieren und die Arbeit, die am Rückgang geleistet werden muss, am Ende des Interrupts erledigt ist, ist in weniger als 55% des CLK- Zeitraums alles in Ordnung , da mehrere Schecks verworfen werden können . Ich bin sicher, wenn dieser Handler so optimal wie möglich in Assembler geschrieben ist, kann er auch bei beiden Sprüngen funktionieren. Hier ist der Handler-Code:



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;
	}
}
      
      





Der TIM3-Timer ist für die Erzeugung der ACK verantwortlich . Dies ist erforderlich, damit der Kernel während dieser Verzögerung frei mit der SD-Karte arbeiten kann. Der Timer-Interrupt-Handler sieht folgendermaßen aus:

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;
	}
}

      
      







Der Code ist ausreichend kommentiert und ich denke, es braucht nicht viel Analyse. Ich werde nur bemerken, dass wir nach dem Empfang des zweiten Bytes der Sektornummer im Lesebefehl das Flag für die Leseoperation von der SD-Karte für den Code setzen, der sich in der ewigen Schleife der main () -Funktion dreht. Unmittelbar danach werden die nächsten 4 ACKs mit einer längeren Zeit ausgestellt. In der Benutzeroberfläche sieht es so aus:





Screenshot aus dem Logikanalysatorprogramm, 4 große Verzögerungen in der Transaktion werden hervorgehoben



Insgesamt werden ungefähr 3,5 ms eingegeben, und dies ist mehr als genug, damit der Algorithmus im Hauptcode den Sektor lesen kann. Darüber hinaus kann dieser Code nur funktionieren, wenn keine Unterbrechung vorliegt, d. H. nur in diesen großen Pausen. Während der Aufnahme wird das Flag ganz am Ende gesetzt und aufgrund der Tatsache, dass das System es der Speicherkarte ermöglicht, die Aufnahme abzuschließen, arbeitet der Hauptcode ohne Interferenz durch Interrupts. Schauen wir uns nun den Code der Hauptschleife an.

main ()
	//  
	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;
			}
		}
	}
      
      





In einer ewigen Schleife wird das Einstecksignal der SD-Karte ständig analysiert. Wenn Sie es unterwegs herausziehen, deaktiviert der Code das PSIO und die PS1 "verliert" die Karte. Wenn die Karte wieder eingelegt ist (oder nur mit eingelegter Karte eingeschaltet wird), wird zunächst versucht, die Karte mit der Funktion Card_Init () zu initialisieren, wodurch der Typ der erkannten Karte zurückgegeben wird. Dies ist wichtig, da SDv1 und andere SDHC / SDXC-Adressierungsmethoden unterschiedlich sind. Der Initialisierungscode selbst birgt keine Geheimnisse und wurde in einer Vielzahl von im Internet verfügbaren Beispielen zu FatFS und ähnlichen Projekten ausspioniert.



Nach der Initialisierung der Karte wird die knifflige Funktion Card_FSInit () aufgerufen. Dies ist das wichtigste Merkmal dieses Projekts. Tatsache ist, dass STM32F042 nur über bescheidene Funktionen verfügt und nicht in der Lage ist, die volle FatFS-Unterstützung mit der erforderlichen Geschwindigkeit zu nutzen. Daher habe ich mir diese Methode ausgedacht: Die Bilddatei ist immer 128 KB groß, daher müssen Sie nur 256 Sektoren mit 512 Bytes kennen, von denen jeder genau 4 Sektoren unserer PS1-Speicherkarte enthält. Wir machen also folgendes:



  1. Wir analysieren den Sektor LBA = # 0 für MBR. Wenn dies tatsächlich MBR ist, erhalten wir einen neuen Sektor, in dem sich die MBS befindet.
  2. Nachdem wir die Adresse des vermeintlichen MBS erhalten haben (es kann # 0 sein, wenn es keinen MBR gibt, oder eine Nummer, wenn es MBR gibt), beginnen wir, sie auf Zugehörigkeit zu einer der FATs zu analysieren: FAT12, FAT16, FAT32 oder vFAT .
  3. Wenn der Sektor die Prüfung bestanden hat, nehmen wir Informationen über die Struktur daraus und suchen im Stammverzeichnis nach einem Element mit dem Dateinamen. In diesem Fall ist es 'MEMCRD00.BIN'.
  4. Wenn eine solche Datei gefunden wird, überprüfen wir ihre Größe - sie muss streng auf 0x20000 Bytes festgelegt werden. Wenn alles so ist, erhalten wir die Nummer des ersten Clusters.
  5. Wenn wir diesen Punkt erreicht haben, verfügen wir bereits über alle erforderlichen Informationen, um eine Liste der physischen LBA-Sektoren zu erstellen, in denen sich unser Image befindet. Wir gehen die FAT-Kette durch und verwenden die Strukturinformationen aus dem MBS. Wir füllen eine Tabelle mit 256 LBA-Sektornummern aus.


Bei Erfolg startet PSIO und PS1 sieht die Karte als ihre übliche 15-Block-Karte. Wenn zu irgendeinem Zeitpunkt ein Fehler auftritt, wird der Betrieb unterbrochen, beide LEDs leuchten auf und alles bleibt in diesem Zustand, bis die Stromversorgung unterbrochen oder die SD-Karte ausgetauscht wird. Hier ist der Code für dieses Verfahren:



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;
}
      
      





Um ehrlich zu sein, da dies nur PoC ist, wird hier nur die FAT16-Suche implementiert. FAT12 muss wahrscheinlich nicht unterstützt werden - microSD mit so kleinen Volumes existiert nicht. Aber FAT32 oder vFAT können hinzugefügt werden, wenn jemand es in Zukunft benötigt.



Der Name des Bildes 'MEMCRD00.BIN' wurde nicht zufällig ausgewählt. Tatsache ist, dass ich in Zukunft die Bildauswahl über eine Standardkombination von Tasten auf dem Joypad für mehrseitige Speicherkarten hinzufügen möchte: Wenn SELECT gedrückt gehalten wird, folgt ein einziger Druck auf L1 / R1. Durch Ändern der letzten beiden Zeichen können Sie 100 Bilder im Stammverzeichnis von 'MEMCRD00.BIN' bis 'MEMCRD99.BIN' unterstützen. Hierfür gibt es im SCK- Interrupt-Handler eine Grundlage In der PSIO-Schnittstelle wird der Zweig analysiert, in dem der Anruf an das Joypad analysiert wird. Es ist kein Problem, einen Sniffer zu erstellen, aber die Peripheriegeräte des PS1-Controllers sind reichhaltig und Sie müssen fast alle unterstützen.



Infolgedessen erwies sich das Gerät als effizient und jeder kann es wiederholen, wenn er möchte. Der Link zum gesamten Projekt ist hier. Ich werde gerne allen helfen, die an den Kommentaren zum Artikel interessiert sind.



PS Ich möchte hier wirklich eine Liste aller Informationsquellen angeben, die ich bei der Erstellung dieses Projekts verwendet habe, aber leider ist dies sehr schwierig. Vieles wurde zufällig belauscht. Vor über 15 Jahren gab es TXT-Dateien mit allgemeinen Informationen zur PS1 für diejenigen, die ihren eigenen Emulator schreiben wollten. Und jetzt existiert alles als ein paar Textdateien auf meiner Festplatte. Wir können sagen, dass das gesamte Internet seit 15 Jahren die Informationsquelle ist.



All Articles