La interfaz de periféricos en serie (SPI) es un bus de interfaz utilizado habitualmente para enviar datos entre microcontroladores y pequeños periféricos como registros de desplazamiento, sensores y tarjetas SD. Utiliza líneas de reloj y datos separadas, junto con una línea de selección para elegir el dispositivo con el que se desea hablar.
¿Qué tienen de malo los puertos serie?
Un puerto serie común, del tipo con líneas TX y RX, se llama “asíncrono” (no síncrono) porque no hay control sobre cuándo se envían los datos ni ninguna garantía de que ambos lados estén funcionando precisamente a la misma velocidad. Dado que los ordenadores se basan normalmente en que todo está sincronizado con un único “reloj” (el cristal principal conectado a un ordenador que lo controla todo), esto puede ser un problema cuando dos sistemas con relojes ligeramente diferentes intentan comunicarse entre sí.
Para solucionar este problema, las conexiones seriales asíncronas añaden bits de inicio y parada adicionales a cada byte, lo que ayuda al receptor a sincronizarse con los datos a medida que llegan. Ambas partes deben acordar de antemano la velocidad de transmisión (por ejemplo, 9600 bits por segundo). Las ligeras diferencias en la velocidad de transmisión no suponen un problema porque el receptor vuelve a sincronizar al principio de cada byte.

(Por cierto, si te has dado cuenta de que “11001010
” no es igual a 0x53
en el diagrama anterior, felicitaciones por tu atención al detalle. Los protocolos en serie suelen enviar primero los bits menos significativos, por lo que el bit más pequeño está en el extremo izquierdo. La parte inferior es en realidad 0011 = 0x3, y la superior es 0101 = 0x5).
La comunicación serial asíncrona funciona bien, pero agrega una gran sobrecarga tanto en los bits de inicio y parada adicionales enviados con cada byte, como en el complejo hardware necesario para enviar y recibir datos. Y como probablemente has notado en tus propios proyectos, si ambos lados no están configurados a la misma velocidad, los datos recibidos serán basura. Esto se debe a que el receptor está muestreando los bits en momentos muy específicos (las flechas en el diagrama anterior). Si el receptor está mirando en los momentos equivocados, verá los bits equivocados.
Una solución síncrona
SPI funciona de una manera ligeramente diferente. Es un bus de datos “síncrono”, lo que significa que utiliza líneas separadas para los datos y un “reloj” que mantiene ambos lados en perfecta sincronía. El reloj es una señal oscilante que indica al receptor exactamente cuándo debe muestrear los bits de la línea de datos. Puede ser el flanco ascendente (de bajo a alto) o descendente (de alto a bajo) de la señal de reloj; la hoja de datos especificará cuál de ellos debe utilizarse. Cuando el receptor detecte ese flanco, mirará inmediatamente a la línea de datos para leer el siguiente bit (ver las flechas en el siguiente diagrama). Dado que el reloj se envía junto con los datos, no es importante especificar la velocidad, aunque los dispositivos tendrán una velocidad máxima a la que pueden funcionar (dentro de un rato hablaremos de la elección del flanco de reloj y la velocidad adecuados).

Una de las razones por las que SPI es tan popular es que el hardware receptor puede ser un simple registro de desplazamiento. Se trata de una pieza de hardware mucho más sencilla (¡y más barata!) que el UART (receptor/transmisor asíncrono universal) que requiere la serie asíncrona.
Recibiendo datos
Nota: Puede que no reconozcas las etiquetas COPI/CIPO para los pines SPI. Los miembros de la OSHWA resolvieron dejar de usar “Maestro” y “Esclavo” para describir las señales entre el controlador y el periférico. Consulta esta página para saber más sobre los motivos de este cambio. También puede ver la resolución de la OSHWA aquí.
Puede que estés pensando, “eso suena muy bien para las comunicaciones unidireccionales, pero ¿cómo se envían los datos en la dirección opuesta? Aquí es donde las cosas se complican un poco más.
En SPI, sólo un lado genera la señal de reloj (normalmente llamada CLK o SCK para Serial ClocK). El lado que genera el reloj se llama “controlador”, y el otro lado se llama “periférico”. Siempre hay un solo controlador (que casi siempre es tu microcontrolador), pero puede haber múltiples periféricos (más sobre esto en un momento).
Cuando se envían datos desde el controlador a un periférico, se envían por una línea de datos llamada COPI, por “Controller Out / Peripheral In”. Si el periférico necesita enviar una respuesta al controlador, el controlador continuará generando un número preestablecido de ciclos de reloj, y el periférico pondrá los datos en una tercera línea de datos llamada CIPO, para “Controller In / Peripheral Out”.

Fíjate en que hemos dicho “preestablecido” en la descripción anterior. Como el controlador siempre genera la señal de reloj, debe saber de antemano cuándo un periférico necesita devolver datos y cuántos datos serán devueltos. Esto es muy diferente a la serie asíncrona, donde se pueden enviar cantidades aleatorias de datos en cualquier dirección y en cualquier momento. En la práctica esto no es un problema, ya que SPI se usa generalmente para hablar con sensores que tienen una estructura de comandos muy específica. Por ejemplo, si envías el comando para “leer datos” a un dispositivo, sabes que el dispositivo siempre te enviará, por ejemplo, dos bytes a cambio. (En los casos en los que quieras devolver una cantidad variable de datos, siempre podrías devolver uno o dos bytes especificando la longitud de los datos y luego hacer que el controlador recupere la cantidad completa).
Ten en cuenta que SPI es “full duplex” (tiene líneas de envío y recepción separadas), y, por lo tanto, en ciertas situaciones, puede transmitir y recibir datos al mismo tiempo (por ejemplo, solicitar una nueva lectura del sensor mientras se recuperan los datos de la anterior). La hoja de datos de tu dispositivo te indicará si esto es posible.
Selección del chip (CS)
Hay una última línea que debes tener en cuenta, llamada CS por Chip Select. Esto le dice al periférico que debe despertarse y recibir/enviar datos y también se utiliza cuando hay varios periféricos presentes para seleccionar el que le gustaría hablar.

La línea CS normalmente se mantiene alta, lo que desconecta el periférico del bus SPI. (Este tipo de lógica se conoce como “activa baja”, y a menudo la verás utilizada para las líneas de habilitación y reset). Justo antes de que se envíen los datos al periférico, la línea se pone a bajo, lo que activa el periférico. Cuando se termina de usar el periférico, la línea se vuelve a poner en alto. En un registro de desplazamiento, esto corresponde a la entrada “latch”, que transfiere los datos recibidos a las líneas de salida.
Múltiples periféricos
Hay dos formas de conectar múltiples periféricos a un bus SPI:
- En general, cada periférico necesitará una línea CS independiente. Para hablar con un periférico en particular, harás que la línea CS de ese periférico sea baja y mantendrás el resto alta (no querrás que dos periféricos se activen al mismo tiempo, o ambos podrían intentar hablar en la misma línea CIPO resultando en datos confusos). Muchos periféricos requerirán muchas líneas CS; si te quedas sin salidas, hay chips decodificadores binarios que pueden multiplicar tus salidas CS.

- Por otro lado, algunas partes prefieren estar conectadas en cadena, con la CIPO (salida) de una que va a la COPI (entrada) de la siguiente. En este caso, una sola línea CS va a todos los periféricos. Una vez enviados todos los datos, la línea CS se eleva, lo que hace que todos los chips se activen simultáneamente. Esto se utiliza a menudo para los registros de desplazamiento en cadena y los controladores de LED direccionables.

Ten en cuenta que, para esta disposición, los datos se desbordan de un periférico a otro, por lo que, para enviar datos a cualquier periférico, tendrás que transmitir suficientes datos para llegar a todos ellos. Además, ten en cuenta que el primer dato que transmitas acabará en el último periférico.
Este tipo de diseño se utiliza normalmente en situaciones de sólo salida, como la conducción de LEDs donde no necesitas recibir ningún dato de vuelta. En estos casos puedes dejar la línea CIPO del controlador desconectada. Sin embargo, si los datos necesitan ser devueltos al controlador, puedes hacerlo cerrando el bucle de la cadena (cable azul en el diagrama anterior). Ten en cuenta que, si haces esto, los datos de retorno del periférico 1 tendrán que pasar por todos los periféricos antes de volver al controlador, así que asegúrate de enviar suficientes comandos de recepción para obtener los datos que necesitas.
Programación para SPI
Muchos microcontroladores tienen periféricos SPI incorporados que manejan todos los detalles del envío y la recepción de datos, y pueden hacerlo a velocidades muy altas. El protocolo SPI es también lo suficientemente simple como para que tú (¡sí, tú!) puedas escribir tus propias rutinas para manipular las líneas de E/S en la secuencia adecuada para transferir datos. (Un buen ejemplo está en la página SPI de Wikipedia).
Si estás usando un Arduino, hay dos maneras de comunicarte con los dispositivos SPI:
- Puedes usar los comandos shiftIn() y shiftOut(). Estos son comandos basados en software que funcionarán en cualquier grupo de pines, pero serán algo lentos.
- O puedes usar la librería SPI, que aprovecha el hardware SPI integrado en el microcontrolador. Esto es mucho más rápido que los comandos anteriores, pero sólo funcionará en ciertos pines.
Tendrás que seleccionar algunas opciones cuando configures tu interfaz. Estas opciones deben coincidir con las del dispositivo con el que estás hablando; comprueba la hoja de datos del dispositivo para ver lo que requiere.
- La interfaz puede enviar datos con el bit más significativo (MSB) primero, o con el bit menos significativo (LSB) primero. En la biblioteca SPI de Arduino, esto se controla con la función setBitOrder().
- El periférico leerá los datos en el flanco ascendente o en el flanco descendente del pulso de reloj. Adicionalmente, el reloj puede ser considerado “ocioso” cuando está alto o bajo. En la librería SPI de Arduino, ambas opciones son controladas por la función setDataMode().
- SPI puede operar a velocidades extremadamente altas (millones de bytes por segundo), que pueden ser demasiado rápidas para algunos dispositivos. Para acomodar tales dispositivos, puedes ajustar la velocidad de datos. En la librería SPI de Arduino, la velocidad se establece mediante la función setClockDivider(), que divide el reloj del controlador (16MHz en la mayoría de los Arduinos) a una frecuencia entre 8MHz (/2) y 125kHz (/128).
- Si estás usando la librería SPI, debes usar los pines SCK, COPI y CIPO proporcionados, ya que el hardware está conectado a esos pines. También hay un pin CS dedicado que puedes utilizar (que debe, al menos, estar ajustado a una salida para que el hardware SPI funcione), pero ten en cuenta que también puedes utilizar cualquier otro pin(s) de salida disponible para CS a tu(s) dispositivo(s) periférico(s).
- En los Arduinos más antiguos, tenías que controlar los pines CS tú mismo, haciendo que uno de ellos esté bajo antes de la transferencia de datos y alto después. Los Arduinos más nuevos, como el Due, pueden controlar cada pin CS automáticamente como parte de la transferencia de datos; consulta la página de documentación de Due SPI para más información.
Recursos y donde continuar
Consejos y trucos
Debido a la alta velocidad de las señales, SPI sólo debería utilizarse para enviar datos a distancias cortas (hasta unos pocos metros). Si necesitas enviar datos más allá de esa distancia, reduce la velocidad del reloj y considera el uso de chips controladores especializados.
Si las cosas no funcionan como crees que deberían, un analizador lógico es una herramienta muy útil. Los analizadores inteligentes como el Saleae USB Logic Analyzer pueden incluso decodificar los bytes de datos para su visualización o registro.

Ventajas de SPI:
- Es más rápido que el serial asíncrono
- El hardware de recepción puede ser un simple registro de desplazamiento
- Soporta múltiples periféricos
Desventajas de SPI:
- Requiere más líneas de señal (cables) que otros métodos de comunicación
- Las comunicaciones deben estar bien definidas de antemano (no se pueden enviar cantidades aleatorias de datos cuando se quiera)
- El controlador debe controlar todas las comunicaciones (los periféricos no pueden hablar directamente entre sí)
- Suele requerir líneas CS separadas para cada periférico, lo que puede ser problemático si se necesitan numerosos periféricos.
Donde continuar
Consulta la página de Wikipedia sobre SPI, que incluye mucha información útil sobre SPI y otras interfaces sincrónicas.
Esta página presenta una forma más correcta de configurar una red SPI entre sus dispositivos integrados, particularmente para usar con un microcontrolador Arduino.
Te recomendamos leer:
Mikegrusin. Serial Peripheral Interface (SPI). Sparkfun.