Haciendo una tarjeta de memoria sin fin para PS1



PS1 (también conocida como PSX, también conocida como PS One) es la primera generación de consolas de juegos PlayStation de Sony y pertenece a la quinta generación de consolas de juegos en general. Utiliza una unidad de velocidad 2x para leer CD. Una cantidad tan grande de datos, según los estándares de la época actual para la consola, permitió a los desarrolladores de juegos no mirar hacia atrás en particular a las restricciones al crear contenido para juegos, lo que hizo que este último fuera de mayor calidad en comparación con los juegos de la generación anterior. de consolas. Además, los juegos ahora pueden ser largos. Y si cualquier juego, con raras excepciones, en consolas de generaciones anteriores bien podría completarse en una sesión de juego, entonces con los juegos de PS1, todo fue diferente. Para guardar el progreso, PlayStation tiene tarjetas de memoria: pequeños módulos de memoria no volátiles extraíbles.



Si se pregunta exactamente cómo funciona la tarjeta de memoria de PlayStation 1, cómo funciona y cómo puede crear la suya propia, bienvenido a cat.



Entonces, la tarjeta de memoria PS1 es un dispositivo periférico estándar, como todo el zoológico de joypads, joysticks y otros accesorios. Para comprender exactamente cómo funciona, primero debe mirar lo que tiene adentro.





Foto de la placa de circuito impreso de una tarjeta de memoria estándar de 15 bloques



Como puede ver en la foto, el dispositivo de la tarjeta es muy simple: un controlador que atiende las solicitudes del sistema y, de hecho, una memoria no volátil, que está representada por el estándar NOR FLASH. Lógicamente, la tarjeta de memoria está dividida en 15 bloques que pueden usar los juegos. Puede parecer que 15 no es lógico para un sistema binario, pero no hay ninguna contradicción aquí: se da un bloque para el sistema de archivos, los nombres de los archivos e incluso los iconos animados se almacenan allí, al igual que los flujos NTFS. Cada bloque tiene un tamaño de 8 KiB, 16 bloques en total son 128 KiB, lo que se puede ver en la marca de la memoria FLASH en la foto de arriba.



Al principio, esto fue suficiente para todos, pero luego comenzaron a aparecer juegos que usaban más de un bloque a la vez. Por ejemplo, algunos simuladores, como Sega GT, usan 4-5 bloques, mientras que Constructorpor lo que, en general, toda la tarjeta de memoria es de 15 bloques. Esto obligó a comprar más tarjetas y la situación amenazaba con convertirse en disquetes o cartuchos. Pero luego los piratas se detuvieron y comenzaron a emitir tarjetas de 2, 4 u 8 páginas a la vez. Y las páginas se cambiaron mediante una combinación inteligente en el joypad o mediante un botón explícito en la propia tarjeta de memoria. Sin embargo, en tarjetas con más de 2 páginas se utilizó compresión, y el número real de páginas fue mucho menor, y algunas tarjetas podrían bloquearse estúpidamente. Y fue muy difícil sacarlos de este estado, pero a lo que no acudieron los jugadores por el bien de sus atajadas. Estos son los representantes típicos de las tarjetas de memoria de varias páginas:





A la izquierda hay una tarjeta de memoria con 2 páginas, a la derecha con 8. La derecha tiene un botón de paso de página de hardware y un indicador que muestra el número del 1 al 8, que es escondido detrás de un cristal oscuro



Pequeña digresión lírica



Todo comenzó en 2001, cuando compré un disco milagroso para PC llamado "Todos los emuladores", en el que había emuladores de PS1, entre ellos: ¡era Bleem! y ePSXe temprano. ¡Y mi computadora de entonces incluso podía reproducir mis discos de PS1 de manera jugable! Y un poco más tarde obtuve un módem y aprendí sobre DirectPad Pro . Conectar un joystick nativo a una computadora (aunque a través de LPT) cuesta mucho. ¡Y este sistema funcionó tanto en 9x como en XP! Y un poco más tarde, ya en 2002, me enteré de la captura de tarjetas de memoria Sakura.! Este programa permitió trabajar con tarjetas de memoria reales utilizando el mismo esquema de conexión DirectPad Pro. Fue entonces cuando se me ocurrió la idea de hacer una tarjeta de memoria "infinita" que me permitiera intercambiar información con una computadora sin necesidad de dispositivos adicionales. Pero en ese momento no tenía suficiente información y base de elementos disponible, y la idea seguía siendo solo una idea, brillando en algún lugar del patio trasero de mi conciencia.



Han pasado casi 9 años desde que me di cuenta de que ya sé lo suficiente y tengo la oportunidad de implementar al menos alguna versión de una tarjeta de memoria sin fin. Sin embargo, otro factor entró en juego aquí: la edad y todo lo relacionado con ella. Hay menos tiempo para pasatiempos, cada vez más preocupaciones. Y solo ahora puedo proporcionar al público al menos algún resultado, una prueba de concepto completa.



Interfaz física



Entonces, la tarjeta de memoria y los joypads funcionan a través de una interfaz común. El número de señales en él es 6, aquí están sus nombres y propósitos:



  • SEL0 - Señal de selección del primer puerto, nivel activo bajo
  • SEL1 - Segunda señal de selección de puerto, nivel activo bajo;
  • CLK : señal de reloj de la interfaz, estado pasivo de alto nivel, en el borde descendente, enclavamiento en el borde;
  • CMD - Señal de datos de la consola a la periferia;
  • DAT - Señal de datos desde la periferia a la consola;
  • ACK - Apretón de manos de hardware, activo bajo.


También hay dos voltajes de suministro diferentes en la interfaz: 3.3v y 7.6v. Todas las señales excepto SEL0 y SEL1 son comunes a todos los dispositivos conectados. Es por eso que una tarjeta de memoria que no funcionaba o un joypad en la segunda ranura afectaba a los trabajadores en la primera, aunque después de las consolas de 16 bits parecía extraño. Creo que muchos ya han reconocido el SPI estándar en la interfaz ; todo es correcto, lo es. Solo se agregó una señal ACK para confirmar la operación de E / S. Aquí están las asignaciones de las señales en los contactos de la tarjeta de memoria:





Tarjeta de memoria reparada con FLASH de 5 voltios Las



características técnicas de la interfaz son las siguientes:



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

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

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

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


La frecuencia medida de la señal CLK es 250 kHz, que es 4 µs por ciclo. Con los parámetros físicos de la interfaz resueltos, ahora la capa de transporte. Un ingeniero experimentado ya ha notado que el joypad y la tarjeta de memoria están conectados completamente en paralelo y pueden entrar en conflicto entre sí. De hecho, existe un arbitraje de software para esto. Después de que se activa la señal SELn, la periferia permanece en silencio, pero escucha el primer byte enviado. Si este byte es igual a 0x01, entonces se activa el joypad y la tarjeta de memoria permanece en silencio hasta que se desactive la señal de selección. Y si el byte era 0x81, entonces ocurre lo contrario: la tarjeta de memoria está activada y el joypad está en silencio. Naturalmente, el host está esperando una señal ACK.en este byte de arbitraje y no espera mucho. Esto es necesario para tener tiempo de interrogar al resto de la periferia, si parte de esta periferia está ausente. El hecho es que el sistema operativo sondea los controladores y las tarjetas de memoria estrictamente de acuerdo con la señal del camino inverso del haz, o mejor conocido como VBlank . Está tan aceptado que los juegos en consolas hasta la quinta generación están vinculados a este tiempo, que es igual a la velocidad de fotogramas. Y la velocidad de fotogramas es estrictamente estable y normalizada: 50 Hz para PAL y 60 Hz para NTSC. Es decir, el período de sondeo para joysticks y tarjetas de memoria es de 20 ms para PAL o 16 ms para NTSC.



Entonces, calculamos el arbitraje, ahora el nivel superior real. ¿Qué comandos comprende la tarjeta de memoria estándar PS1? Sí, de hecho, solo hay 3 de ellos.



  • R - 0x52 o Leer . Leer un sector de una tarjeta de memoria;
  • W - 0x57 o escritura . Grabación del sector de la tarjeta de memoria;
  • S - 0x53 o Estado . Leyendo el estado de la tarjeta de memoria.


Toda la tarjeta de memoria está dividida en sectores. Un sector de 128 bytes. Por lo tanto, 128 KB se ajusta a 0x400 o 1024 sectores. En este caso, no es necesario borrar el sector antes de grabar. Pero se garantiza que el sistema dará tiempo para el siguiente cuadro completo al grabar. Es decir, puede leer la tarjeta de memoria en cada fotograma, pero lo escribe después de uno. Por cierto, todo tipo de "Codebreakers" no se adhieren a estos tiempos para acelerar. Analicemos cada comando con más detalle.



Protocolo de tarjeta de memoria



El orden de los datos transmitidos en cada comando se ve así:

Leer:

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


Leyenda:



CMD : datos que el host envía a la tarjeta.

DAT : datos que la tarjeta envía al host.

BANDERA : banderas actuales del estado del mapa y el resultado del comando anterior.

PRV : datos recibidos anteriormente, el resultado de simplificar el circuito en el mapa.

MSB : byte alto del número de sector.

LSB : byte menos significativo del número de sector.

DATOS - Carga útil.

CHK : suma de comprobación del bloque.

ACK - Bandera de acuse de recibo.



El byte FLAG utiliza los siguientes bits:



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


Después del encendido, FLAG es 0x08. Y después del primer registro, se restablece a cero. El sistema operativo PS1 siempre escribe en el sector 0x003F para esto, lo que provoca un desgaste en ese sector. Pero en el marco de marcar la tarjeta de memoria por parte del sistema, no hay información útil en este sector. Número de sector MSB : LSB 10 bits y va desde 0x0000 a 0x03FF. La suma de comprobación CHK es el XOR habitual de los 128 bytes de datos + MSB y LSB . La confirmación ACK solo puede tomar 3 valores: G 0x47, E 0x43 y 0xFF. G = Bien o bien. E = Error . En realidad, cuando se lee de la tarjeta, ACK siempre es igual a G , y cuando se escribe G = OK, E = error de suma de verificación y 0xFF significa un número de sector incorrecto. Es cierto que la mayoría de las tarjetas simplemente descartan los bits no utilizados en el byte alto del número de sector y, por lo tanto, nunca responden con 0xFF. Los números 0x0400 y 0x0080 en el comando de estado sugieren ciertos pensamientos de que este es el número de sectores y el tamaño del sector en bytes, pero esto no se sabe con certeza. Bueno, aquí estamos y llegamos a lo principal:



Dándose cuenta de su tarjeta de memoria



Entonces, esta es toda la información que necesita para crear su tarjeta de memoria PS1. Los posibles cuellos de botella son los siguientes:



  1. Al leer, se necesita tiempo para actualizar los datos. Entre el número de sector y la transferencia de datos real, tenemos 4 bytes de los cuales podemos estirar un poco el ACK . Por cierto, para la tarjeta de memoria original en NOR FLASH, todos los ACK van de manera uniforme, para las tarjetas de memoria con SPI FLASH, después de la transmisión LSB , hay un retraso ACK , durante el cual el controlador establece el comando en SPI FLASH y lee el primer byte y lee el resto durante el intercambio.
  2. Al grabar, después de la transferencia de todo el paquete y el comienzo de la grabación a la matriz, lleva tiempo, pero aquí el sistema en sí da el retraso necesario.


En cuanto a la fuente de alimentación, los joypads de 3,3 V se utilizan para la lógica y los 7,6 V para alimentar los motores. Las tarjetas de memoria suelen utilizar solo una fuente de alimentación. Si hay un FLASH de 5v en el interior, entonces se utilizan 7.6v y un estabilizador. Si hay 3.3v FLASH, entonces 3.3v se usa inmediatamente.



La primera versión que construí en STM32F407VG, que funciona con 3.3V, tiene SPI para PSIO, SDIO rápido y suficiente memoria para almacenar la imagen completa en su interior, resolviendo los problemas antes mencionados. Foto del dispositivo terminado:





La primera versión de mi tarjeta de memoria en STM32F407



Resultó rápida, confiable, pero costosa. ¿Puedes hacerlo más barato? Bueno, bueno, el desafío está aceptado. Teniendo en cuenta los detalles de la tarea, elegí STM32F042F6. Esto es lo que sucedió:





Segunda versión de mi tarjeta de memoria en STM32F042



Nuestra tarjeta está impulsada, por lo que no se necesita estabilización de frecuencia con un resonador de cuarzo externo, un oscilador interno es suficiente. Este controlador tiene un SPI de hardware, así que se lo di a la tarjeta SD para reducir los retrasos en el transporte. PSIO será software aquí.



Implementación de software



Lo primero que debe hacer es trabajar con una tarjeta SD en modo SPI. No me extenderé demasiado en esto, durante mucho tiempo ha sido masticado y esparcido por Internet. El código de inicio, lectura y escritura del sector se proporciona a continuación.



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





La tarjeta se inicializa a 375 kHz (PCLK / 128) y funciona a 24 MHz (PCLK / 2). A tales velocidades, las mediciones mostraron que SDv1 y SDHC dan un sector dentro de 2.8ms para toda la transacción. Esto debe recordarse porque importante para la operación de lectura de PSIO.



Ahora echemos un vistazo a PSIO. Como se mencionó anteriormente, lo tenemos en software de todos modos. Solo hay dos señales para rastrear: SEL y CLK . Realizaremos un seguimiento del primero en ambos frentes y haremos los preparativos para el intercambio de datos:



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





Solo captaremos la señal CLK a lo largo del frente. El hecho es que STM32F042 opera solo a 48MHz y su rendimiento es demasiado pequeño para nuestra tarea. Y si hace una interrupción en ambos frentes, durante la transferencia de un byte, prácticamente no sale del controlador de interrupciones y todo funciona al borde de la posibilidad, a veces fallando. Y si reacciona solo al frente, y el trabajo que debe hacerse en el declive se realiza al final de la interrupción, entonces todo está bien en menos del 55% del período CLK , porque se pueden descartar varios controles. . Estoy seguro de que si este controlador está escrito en ensamblador de la manera más óptima posible, podría funcionar incluso en ambos saltos. Aquí está el código del controlador:



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





El temporizador TIM3 se encargará de generar el ACK . Esto es necesario para que el kernel pueda funcionar libremente con la tarjeta SD durante este retraso. El controlador de interrupciones del temporizador es así:

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

      
      







El código está bastante comentado y creo que no necesita mucho análisis. Solo notaré que después de recibir el segundo byte del número de sector en el comando de lectura, configuramos la bandera para la operación de lectura desde la tarjeta SD para el código que gira en el ciclo eterno de la función main (). E inmediatamente después de eso, los siguientes 4 ACK se emiten con un tiempo adicional. En la interfaz se ve así:





Captura de pantalla del programa del analizador lógico, se resaltan 4 grandes retrasos en la transacción



En total, se escriben aproximadamente 3,5 ms, y esto es más que suficiente para que el algoritmo del código principal lea el sector. Además, este código solo puede funcionar cuando no hay interrupción, es decir, solo en esas grandes pausas. Durante la grabación, la bandera se establece al final y debido al hecho de que el sistema permite que la tarjeta de memoria complete la grabación, el código principal funciona sin interferencias de interrupciones. Ahora echemos un vistazo al código del bucle principal.

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





En un bucle eterno, la señal de inserción de la tarjeta SD se analiza constantemente. Si lo saca sobre la marcha, el código desactivará el PSIO y el PS1 "perderá" la tarjeta. Si la tarjeta se inserta nuevamente (o simplemente se enciende con la tarjeta insertada), al principio habrá un intento de inicializar la tarjeta con la función Card_Init (), que devolverá el tipo de tarjeta detectada. Esto es importante porque SDv1 y otros métodos de direccionamiento SDHC / SDXC son diferentes. El código de inicialización en sí mismo no contiene ningún secreto y fue espiado en un montón de ejemplos disponibles en Internet sobre FatFS y proyectos similares.



Después de la inicialización de la tarjeta, se llama a la complicada función Card_FSInit (). Esta es la característica más importante de este proyecto. El hecho es que STM32F042 tiene capacidades modestas y no podrá obtener soporte completo de FatFS a la velocidad requerida. Por lo tanto, se me ocurrió este método: el archivo de imagen siempre es de 128 KB, por lo tanto, necesita conocer solo 256 sectores de 512 bytes, cada uno de los cuales tendrá exactamente 4 sectores de nuestra tarjeta de memoria PS1. Por lo tanto, hacemos lo siguiente:



  1. Analizamos el sector LBA = # 0 para MBR. Si esto es realmente MBR, entonces obtenemos un nuevo sector donde se encuentra el MBS.
  2. Habiendo recibido la dirección del supuesto MBS (puede ser # 0, si no hay MBR, o algún número, si hay MBR), comenzamos a analizarlo por pertenecer a alguna de las FATs: FAT12, FAT16, FAT32 o vFAT .
  3. Si el sector ha pasado la verificación, tomamos información sobre la estructura y buscamos un elemento con el nombre del archivo en el directorio raíz. En este caso, es 'MEMCRD00.BIN'.
  4. Si se encuentra un archivo de este tipo, verificamos su tamaño; debe fijarse estrictamente en 0x20000 bytes. Si todo es así, obtenemos el número del primer grupo.
  5. Si hemos llegado a este punto, entonces ya tenemos toda la información necesaria para construir una lista de sectores físicos de LBA donde se ubica nuestra imagen. Pasando por la cadena FAT y utilizando la información de estructura del MBS, completamos una tabla de 256 números de sector LBA.


Si tiene éxito, se inicia PSIO y PS1 verá la tarjeta como su tarjeta habitual de 15 bloques. Si ocurre un error en cualquier etapa, la operación se interrumpe, ambos LED se encienden y todo permanece en este estado hasta que se desconecta la energía o se reemplaza la tarjeta SD. Aquí está el código para este procedimiento:



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





Para ser honesto, dado que esto es solo PoC, aquí solo se implementa la búsqueda FAT16. FAT12, probablemente, no necesita ser compatible; no existe microSD de volúmenes tan pequeños. Pero se pueden agregar FAT32 o vFAT si alguien lo necesita en el futuro.



El nombre de la imagen 'MEMCRD00.BIN' no fue elegido por casualidad. El hecho es que en el futuro planeo agregar la selección de imágenes a través de una combinación estándar de botones en el joypad para tarjetas de memoria de varias páginas: cuando SELECT se mantiene presionado, sigue una sola presión en L1 / R1. Y al cambiar los últimos 2 caracteres, puede admitir 100 imágenes en el directorio raíz, desde 'MEMCRD00.BIN' a 'MEMCRD99.BIN'. Hay una base para esto en el manejador de interrupciones SCKen la interfaz PSIO, la rama donde se analiza la llamada al joypad. No hay problema para hacer un sniffer, pero los periféricos del controlador de PS1 son ricos y tendrás que admitirlos casi todos.



Como resultado, el dispositivo resultó ser eficiente y todos pueden repetirlo si lo desean. El enlace a todo el proyecto está aquí. Estaré encantado de ayudar a todos los interesados ​​en los comentarios al artículo.



PD: Realmente me gustaría indicar aquí una lista de todas las fuentes de información que utilicé para crear este proyecto, pero, lamentablemente, esto es muy difícil. Mucho se escuchó por casualidad. Algo vino en forma de archivos TXT con información general sobre la PS1 hace más de 15 años, para aquellos que querían escribir su propio emulador. Y ahora todo existe como unos pocos archivos de texto en mi disco duro. Podemos decir que toda Internet ha sido la fuente de información durante los últimos 15 años.



All Articles