El bus SPI es un protocolo de comunicación síncrono pensado para transferir datos a alta velocidad.
Si comparamos el I2C con una carretera comarcal (fiable, dos carriles, velocidad moderada), el SPI es una autopista alemana sin límite de velocidad.
Mientras que I2C suele funcionar a 100kHz o 400kHz, el SPI puede alcanzar fácilmente los 10MHz, 40MHz o incluso 80MHz. Por eso es el estándar elegido para dispositivos que manejan grandes volúmenes de datos, como:
- Pantallas LCD/TFT/OLED a color.
- Lectores de tarjetas SD.
- Módulos Ethernet (como el ENC28J60).
- Lectores RFID/NFC (como el RC522).
En esta entrada aprenderemos a domar esta velocidad usando la clase SpiDevice.
Para este tutorial necesitas instalar el paquete NuGet: nanoFramework.System.Device.Spi
Las líneas del bus SPI
La velocidad tiene un precio: cables. El SPI necesita 4 líneas para funcionar (frente a las 2 del I2C).
- SCK (Serial Clock): El reloj que marca el ritmo (generado por el Maestro).
- MOSI (Master Out Slave In): Datos que salen del microcontrolador hacia el sensor.
- MISO (Master In Slave Out): Datos que vienen del sensor hacia el microcontrolador.
- CS / SS (Chip Select): ¡El más importante! Es un pin individual para cada dispositivo. Si conectas 3 sensores SPI, compartirán SCK, MOSI y MISO, pero necesitarás 3 pines CS distintos.
Comunicación full-duplex
Una característica única del SPI es que es Full-Duplex. Imagina dos personas pasándose tarjetas de visita al mismo tiempo. En el mismo ciclo de reloj, el Maestro envía un bit al Esclavo Y el Esclavo envía un bit al Maestro. Siempre se intercambian datos, aunque a veces uno de los dos envíe “basura” (dummy bytes) solo para empujar los datos del otro.
Configuración en nanoFramework
En el ESP32 tenemos dos buses SPI hardware disponibles para el usuario: VSPI (Bus 1) y HSPI (Bus 2).
Por defecto, en la mayoría de ESP32 DevKits, el VSPI (que solemos llamar SPI1) está en:
- MOSI: GPIO 23
- MISO: GPIO 19
- SCK: GPIO 18
- CS: un GPIO disponible, habitualmente el 5
La clase protagonista es SpiDevice. Primero configuramos la conexión con SpiConnectionSettings.
using System.Device.Spi;
// Configuración
// 1. Bus ID: consulta el mapeo del target
// 2. Chip Select Pin: GPIO 5
SpiConnectionSettings settings = new SpiConnectionSettings(1, 5);
// Parámetros avanzados (Cruciales leerlos del Datasheet del sensor)
settings.ClockFrequency = 1000000; // 1 MHz (Velocidad)
settings.Mode = SpiMode.Mode0; // Polaridad y Fase del reloj (Mode0 es el más común)
// Crear el dispositivo
SpiDevice dispositivo = SpiDevice.Create(settings);El Modo SPI (0, 1, 2, 3) Esto define cuándo se leen los datos (si cuando el reloj sube o baja). Si configuras mal el modo, leerás basura. El 90% de dispositivos usan Mode0, pero siempre verifica el datasheet.
Transferir datos: Write, Read y TransferFullDuplex
Tenemos tres formas de comunicarnos:
Solo escribir con Write
Usado para enviar comandos o configurar registros (ej. enviar píxeles a una pantalla).
byte[] comando = new byte[] { 0x01, 0xFF };
dispositivo.Write(comando);Solo leer con Read
Usado para leer datos cuando no necesitamos enviar nada específico (aunque internamente el SPI siempre envía ceros para poder leer).
byte[] buffer = new byte[4];
dispositivo.Read(buffer);Intercambiar con TransferFullDuplex
La forma “pura” del SPI. Envías un array y recibes otro del mismo tamaño simultáneamente.
byte[] enviar = new byte[] { 0xAA, 0xBB };
byte[] recibir = new byte[enviar.Length];
dispositivo.TransferFullDuplex(enviar, recibir);
// Ahora 'recibir' contiene lo que el sensor nos contestó MIENTRAS enviábamos AA y BB.Ejemplo: un byte dummy
Supongamos que queremos leer un valor de un sensor RFID. Normalmente funciona así:
- Bajamos la línea CS (automático por
SpiDevice). - Enviamos la dirección del registro que queremos leer (ej.
0x15). - El sensor necesita relojes para responder, así que enviamos un byte “vacío” (0x00) para “empujar” la respuesta hacia nosotros.
// Queremos leer el registro 0x15
// Byte 0: Dirección 0x15
// Byte 1: 0x00 (Dummy para dar tiempo a recibir el dato)
byte[] writeBuffer = new byte[] { 0x15, 0x00 };
byte[] readBuffer = new byte[2];
dispositivo.TransferFullDuplex(writeBuffer, readBuffer);
// readBuffer[0] -> Basura (lo que recibimos mientras enviábamos la dirección)
// readBuffer[1] -> ¡El dato real del registro 0x15!
byte datoValido = readBuffer[1];Este baile de bytes es lo que confunde al principio, pero es la base de la velocidad del SPI.
Drivers y dispositivos gráficos
Al igual que con I2C, la gran ventaja de nanoFramework es su ecosistema.
Si quieres usar una pantalla TFT ILI9341 o un lector RC522, no escribas el protocolo SPI a mano. Usa los drivers NuGet.
Por ejemplo, para un lector de tarjetas:
- Instala
nanoFramework.Iot.Device.Mfrc522. - Inicialízalo pasándole el
SpiDeviceya configurado.
El driver se encargará de toda la gestión de registros, comandos y tiempos, aprovechando la velocidad del bus SPI que hemos configurado.