Protocolo SPI
Por ahí leí algo así: El protocolo SPI es aquel que uno diseñaría si le dieran solo 10 minutos para ello. Y sinceramente, sin desmerecer el protocolo, concuerdo en que es bastante sencillo.
Es un protocolo serial y sincrónico en el que un maestro puede comunicarse con uno o mas esclavos. Posee 3 lineas para la comunicación (MOSI, MISO, CLK) más 1 para la selección de la tarjeta con la que se comunicará (CS). Por último existen 4 modos en los que cada uno especifica solamente en que polaridad y flanco se leen o envían los datos.
Detalle de las lineas:
MOSI: Siglas que significan Salida del Maestro - Entrada del esclavo (Master Output - Slave Input). Por esta linea el Maestro (en mi caso el microcontrolador) envía los datos hacia el esclavo (tarjeta SD).
MISO: Entrada del Maestro - Salida del Esclavo (Master Input - Slave Output). Es la linea por donde el maestro recibe los datos que le envía el esclavo.
CLK: Linea de reloj. Es la encargada de generar los pulsos de reloj que indicaran cuando un dato (bit) es valido. Osea cuando tiene que ser leído un bit o cuando hay que preparar otro para su lectura. Esta linea es manejada exclusivamente por el maestro.
CS: Selección del Esclavo (Chip Select). Por medio de esta linea el maestro habilita uno de los esclavos presentes (Si es que hay mas de 1). Es esencial para evitar que dos esclavos, por ejemplo, transmitan los datos al mismo tiempo creando colisiones (errores en la linea) con lo que se corromperían los datos.
Tanto las lineas MISO y MOSI son llamadas también DO (Data Out) y DI(Data In). En lo personal prefiero MISO y MOSI ya que a mi me crea menos confusiones.
La comunicación se establece cuando la linea CS se hace baja (!CS). A partir de acá, como dijimos, hay 4 modos que son los siguientes:
Modo 0: (Y el usado por las tarjetas SD).

Datos se leen en el flanco de subida del pulso de reloj.
El micro mantiene el CLK en bajo. Pone el bit a transmitir, sube la linea CLK en alto y luego lee los datos.
Modo 1:

Datos se leen en el flanco de bajada del pulso de reloj.
El micro mantiene el CLK en bajo. Sube la linea de CLK, pone el bit a transmitir, baja la linea CLK y luego lee los datos.
Modo 2:

Datos se leen en el flanco de bajada del pulso de reloj.
El micro mantiene el CLK en alto. Pone el bit a transmitir, baja la linea CLK y luego lee los datos.
Modo 3:

Datos se leen en el flanco de subida del pulso de reloj.
El micro mantiene el CLK en alto. Baja la linea de CLK, pone el bit a transmitir, sube la linea CLK y luego lee los datos.
Básicamente el modo 0 y el 2, como así también el 1 y el 3, son similares. La única diferencia es en que nivel está la linea de CLK al mandar el primer bit para hacer el desplazamiento de los datos. Por ejemplo, en el modo 0, el primer bit a transmitir se carga antes de subir la linea de CLK.
Como se puede apreciar en las imágenes los datos se envían empezando por el bit mas significativo (MSB).
La función en C seria así (para el Modo 0):
C:
unsigned char spi_write(unsigned char byte)
{
unsigned char i, SPI_Valor = 0;
for (i = 0; i < 8; i++) { // 8 bit a transmitir
SCK = 0;
MOSI = (byte & 128) >> 7;
SCK = 1;
SPI_Valor = SPI_Valor << 1;
SPI_Valor = SPI_Valor | MISO;
byte = byte << 1;
}
MOSI = 1;
return SPI_Valor;
}
1) Linea de CLK en bajo. Recuerden que el primer bit se lee en el pulso de subida.
Código:
SCK = 0;
2) Preparo el bit MSB a ser transmitido y lo pongo en la linea de salida.
Código:
MOSI = (byte & 128) >> 7;
3) Subo la linea de CLK para validar los datos.
Código:
SCK = 1;
4) Las dos siguientes lineas leen el bit transmitido por la tarjeta SD y lo desplazan.
Código:
SPI_Valor = SPI_Valor << 1;
SPI_Valor = SPI_Valor | MISO;
5) Desplazo el byte a transmitir para comenzar otro ciclo en el paso 1.
Código:
byte = byte << 1;
Ojala que con las imágenes quede mas claro.
Como se aprecia, la función de escritura también lee el dato enviado por la SD. Con lo que la función debería llamarse spi_RW(). A mi me quedo mas claro llamarla como spi_write por lo que explicare a continuación.
Una cosa importante es que la linea MOSI nunca debe quedar en bajo cuando se lee los datos de la tarjeta. Cuando se leen datos de la tarjeta se le envía continuamente 1´s.
Código:
dato = spi_write(0xFF);
Para no confundirse, en C uno puede decirle que la función spi_read() sea igual que spi_write(0xFF) de la siguiente forma:
Código:
#define spi_read() spi_write(0xFF)
Con lo que el compilador al encontrarse con spi_read() lo reemplazara con spi_write(0xFF).
Otro punto a aclarar es que si bien parece que el protocolo SPI es full duplex (se transmite y recibe al mismo tiempo) es mas bien half duplex (los datos van en solo sentido a la vez, osea o se transmite o se recibe, pero no los dos a la vez). Por eso hay que enviarle el valor 0xFF para que la memoria nos devuelva el dato que necesitamos.
Espero que con esto me haya podido explicar bien como es la transmisión entre el micro y la SD en el protocolo SPI emulado en software. Tengan presente que si el micro a usar tiene ya un modulo SPI integrado la cosa se simplifica bastante.
Nota: Todo esto lo había escrito hace tiempo cuando estaba jugando con las tarjetas SD y usaba un microcontrolador (PIC12F6XX) que no posee SPI por hardware. Hay que tener presente que no todos los dispositivos SPI funcionaran igual pudiendo haber algunas variaciones en los datos que devuelve cuando se le envía uno como así también hay, por ejemplo, memorias que usan datos de 16 bits y no de 8. Por lo tanto deberán adecuar al programa a sus necesidades.
Saludos.