Anteriormente ya se ha hablado de varios protocolos de comunicación como I2C, RS-485 y Serial. Hoy es el turno de hablar de SPI con Arduino, uno de los protocolos de comunicación más difundidos y utilizados en el mundo de los microcontroladores.
De hecho, es tan común y popular, que es posible que ya lo hayas empleado sin siquiera saberlo, ya que es utilizado por muchos sensores y Shields Arduino. Quizás los más conocidos sean el Ethernet Shield y el sensor BME280, muy comunes ambos en proyectos relacionados con el IoT.
Para comprender a fondo cómo utilizar SPI con Arduino vamos a ver:
- ¿Qué significa SPI y cómo surge?
- En qué casos es mejor usar SPI y en cuales no.
- Las características técnicas del protocolo SPI.
- Pines que se utilizan en SPI con Arduino.
- Librería para utilizar el protocolo SPI con Arduino.
- Ejemplo práctico de uso de SPI con Arduino.
- Solución de problemas del protocolo SPI.
Todo esto lo vas a poder ver a continuación en el artículo donde hablaré de SPI con Arduino a fondo. Ponte cómodo que empezamos.
Indice de contenidos
- 1 ¿Qué es y cómo funciona SPI?
- 2 Comunicación SPI con Arduino
- 3 Parámetros de SPI Arduino
- 4 Comparación I2C vs SPI con Arduino
- 5 Identificar pines SPI con Arduino
- 6 Librería SPI con Arduino
- 7 Ejemplo comunicación SPI con Arduino y AD5206
- 8 Problemas frecuentes con la comunicación SPI con Arduino
- 9 Ejemplo comunicación SPI con Arduino y sensor BMP280
- 10 Conclusiones sobre SPI con Arduino y sensor BMP280
¿Qué es y cómo funciona SPI?
SPI son las siglas por las que se conoce el Serial Peripheral Interface (en español, Interfaz de Comunicación Serie). Este protocolo de comunicación es utilizado para comunicar varios circuitos integrados entre sí. Eso sí, permite distancias cortas y altas velocidades. El protocolo surgió en la década de 1970, desarrollado por Motorola, en busca de reducir el número de pines que se necesitaba para conectar dos o más circuitos integrados.
Antes de SPI la mayoría de los circuitos utilizaban una interfaz paralela para comunicarse. Este tipo de interfaces necesitan cerca de diez pines (algunas incluso más). Imagina el sufrimiento del que tenía que diseñar el PCB para conectar tantos pines.
SPI es un protocolo síncrono que funciona como una arquitectura maestro-esclavo.
En esta arquitectura existen dos tipos de dispositivos:
- Maestro (Master) o Controlador (Controller): son los que inician y coordinan la comunicación. Usualmente, cuando utilizas un Arduino en un bus SPI esta es su función.
- Esclavos (Slave) o Periféricos (Peripheral): son los dispositivos que están a la espera de que algún maestro se comunique con ellos. Casos comunes son los sensores y actuadores que soportan este protocolo, aunque también es posible, y en ocasiones necesario, que un microcontrolador se comporte como un esclavo.
El siguiente ejemplo te ayudará a comprender mejor el funcionamiento de SPI con Arduino.
Analogía funcionamiento SPI con Arduino
Piensa en una clase, donde hay un profesor y varios estudiantes. Como es lógico, el profesor es el que coordina la clase lo que significa que los estudiantes tienen que estar atentos a él.
Cuando el profesor va a hacer una pregunta se dirige a un estudiante (ya sea por su nombre o señalándole con el dedo), luego realiza la pregunta y espera por la respuesta del alumno.
Una vez el alumno ha respondido, el profesor da por terminada la conversación y puede interactuar con otro estudiante.
En este caso, el profesor es el dispositivo maestro o controlador (cuidado no es un juego de palabras) y los estudiantes juegan el papel de periféricos o esclavos. La diferencia entre la comunicación del profesor y sus alumnos y entre el esclavo y el maestro es que no utilizan el mismo lenguaje. Luego veremos cuál es el que utilizan los microcontroladores.
En la siguiente figura puedes apreciar la arquitectura necesaria para conectar varios dispositivos utilizando SPI.
Como puedes ver son necesarios tres pines comunes para todos los dispositivos, estos son:
- MOSI (Master-Out Slave-In) o COPI (Controller-Out Peripheral-In): este pin es utilizado por el maestro para enviar información a los esclavos.
- MISO (Master-In Slave-Out) o CIPO (Controller-In Peripheral-Out): este pin es utilizado por los esclavos para enviar información al maestro.
- SCK (Serial Clock): este pin es utilizado por el maestro para generar los pulsos (la señal del reloj) que sincronizan la comunicación (de ahí que sea un protocolo síncrono).
Teniendo en cuenta que todos los pines anteriores son comunes a todos los dispositivos, es necesario que exista un mecanismo mediante el cual el maestro indique con qué esclavo o periférico se desea comunicar. Para esto es necesario utilizar un pin específico para cada esclavo:
- SS (Slave Select): mediante estos pines el maestro indica con qué esclavo se desea comunicar.
A diferencia de I2C, en SPI solo puede existir un dispositivo maestro y no existe límite para el número de esclavos.
Comunicación SPI con Arduino
Más o menos tenemos claro la lógica que utiliza un protocolo maestro-esclavo y de cómo se conectan los componentes a un bus SPI. Ahora vamos a ver cómo funciona en realidad la comunicación.
Etapas de la comunicación SPI Arduino
En SPI es posible resumir el proceso de comunicación entre el maestro y un esclavo en tres etapas:
- Etapa 1: activación de esclavo.
- Etapa 2: transferencia y/o recepción de información.
- Etapa 3: desactivación de esclavo.
En la siguiente imagen se muestra un ejemplo de comunicación SPI donde se han señalado las tres etapas.
Etapa 1: Activación de esclavo
El primer paso para comunicarse con un esclavo es poner el pin SS correspondiente en estado bajo. Una vez realizado esto el esclavo puede leer los datos enviados por el pin MOSI y escribir datos en el pin MISO.
Solo un esclavo puede tener su pin SS en estado bajo al mismo tiempo, de lo contrario es posible que existan errores de comunicación o incluso que alguno de los esclavos resulte dañado. De esta gestión se encarga el maestro.
Etapa 2: Transferencia y/o recepción de información
Esta etapa es donde se intercambia información entre los dispositivos. Las transferencias se realizan mediante los pines MOSI y MISO en unidades de 8 bits, es decir, 1 byte. Ojo, que se pueden transmitir varios bytes, pero siempre en unidades completas. En otras palabras, no se puede enviar 1 byte y medio, por ejemplo.
Si necesitas enviar 1 byte y medio tendrás que hacerlo en 2 bytes.
Cabe destacar que, tal y como se puede ver en la imagen, un estado alto (HIGH) en los pines de datos representan un 1 lógico; mientras un estado bajo (LOW) representa un 0 lógico.
La señal de reloj juega un papel fundamental en esta etapa. En cada pulso de reloj el dispositivo maestro lee el estado del pin MISO; y además, pone la línea MOSI en estado alto (HIGH) o bajo (LOW), en dependencia del bit a transmitir. De igual forma el esclavo obtiene el estado del pin MOSI (lee el bit enviado por el maestro) y modifica el estado del pin MISO (envía información al maestro).
Como puedes ver ambos dispositivos pueden transmitir información al mismo tiempo sin ningún inconveniente. Es por esto que SPI también es una comunicación full-duplex.
Durante toda esta etapa el pin SS debe permanecer en estado bajo (LOW).
Etapa 3: Desactivación de esclavo
Una vez se ha intercambiado toda la información necesaria es preciso indicarle al esclavo que ya no se va a continuar interactuando con él. Para esto es necesario poner el pin SS nuevamente en estado alto (HIGH).
Parámetros de SPI Arduino
Como has podido apreciar la comunicación es muy sencilla, sin tramas complejas ni otros mecanismos. Sin embargo, al ser SPI un estándar libre, cada dispositivo lo implementa de manera un poco diferente. Esto significa que se debe prestar especial atención a la hoja de características técnicas del dispositivo con el que se desea establecer la comunicación.
A la hora de establecer una comunicación utilizando SPI es necesario tener en cuenta tres parámetros:
- La frecuencia de la señal de reloj (pin SCK)
- El orden de transmisión de los bits
- El modo de operación.
Frecuencia de reloj
Lo primero que hay que revisar a la hora de utilizar un dispositivo que emplea SPI es su máxima frecuencia de comunicación, es decir, cuál es la máxima frecuencia a la que el maestro puede manejar el pin SCK para enviar o recibir información desde el esclavo. Este parámetro usualmente aparece en las hojas de datos de los componentes o en los manuales de usuario.
En caso de que se emplee una frecuencia de operación mayor a la máxima soportada por el dispositivo no será posible establecer una comunicación con el esclavo.
Orden de transferencia de bits
Otro parámetro a tener en cuenta es el orden en que se deben enviar y/o leer los bits del esclavo.
Esto se debe a que algunos dispositivos comienzan transmitiendo los bytes desde el bit más significativo (MSB), mientras que otros lo realizan desde el bit menos significativo (LSB). A continuación, puedes ver un ejemplo de cómo varía la etapa de transferencia.
En la figura se muestra como quedaría la transmisión de un byte con valor 182 utilizando ambos métodos. Es importante destacar que si un dispositivo no recibe los bits en el orden correcto puede interpretar de forma errónea dicha información.
Modos de transmisión
De modo general se definen 4 modos de transmisión, que dependen de la polaridad y la fase utilizada para la señal de reloj.
La polaridad se refiere al estado en que se debe mantener la señal reloj cuando está inactiva, esta puede ser alta o baja.
La fase, por otra parte, define el momento en que la información es escrita en el pin de salida. Esto puede ser en el flanco de bajada (cuando va de alto a bajo) o en el flanco de subida (cuando va de bajo a alto) de la señal de reloj.
En la siguiente tabla puedes ver un resumen de los cuatro modos.
Modo | Polaridad | Fase |
---|---|---|
Modo 0 | Estado bajo | Flanco de bajada |
Modo 1 | Estado bajo | Flanco de subida |
Modo 2 | Estado alto | Flanco de subida |
Modo 3 | Estado alto | Flanco de bajada |
En la siguiente figura puedes ver las señales de reloj y datos para cada uno de estos modos.
Los flancos en que se actualizan los pines han sido marcados en azul y en rojo se han marcado los flancos en los que el esclavo lee el valor del pin.
Comparación I2C vs SPI con Arduino
Una de las dudas más comunes a la hora de usar SPI con Arduino es saber si es mejor este protocolo o usar otro como I2C. Esto depende del proyecto en cuestión pero para que te hagas una idea vamos a ver una comparativa.
En la siguiente tabla se muestran, a modo de comparación, las principales propiedades de los protocolos I2C y SPI en las placas Arduino.
Características | SPI | I2C |
---|---|---|
Tipo de comunicación | Sincrona full-duplex | Síncrona semi-duplex |
Dispositivos maestros soportados | Solo uno | Ilimitado (teóricamente) |
Dispositivos esclavos soportados* | Ilimitado (teóricamente) | 112 |
Número de cables | 3 comunes a todos los dispositivos + 1 por cada esclavo | 2 comunes a todos los dispositivos |
Velocidad | 20 MHz | 100 KHz |
Distancia | Cortas (típicamente menos de 20 cm) | Cortas (típicamente menos de 20 cm) |
* La cantidad máxima de esclavos en un bus SPI está determinado por la cantidad de pines disponibles en el microcontrolador para direccionar los esclavos.
Pros y contras de usar SPI con Arduino
Teniendo en cuenta todo lo visto hasta ahora se pueden destacar las siguientes ventajas:
- Permite velocidades mucho mayores que I2C.
- Comunicación full-duplex (se puede enviar y recibir información al mismo tiempo).
- Protocolo flexible, es decir, no existe una trama específica como en I2C.
- Consume menos energía que otros buses como I2C, por ejemplo.
Hasta aquí todo parece bien, sin embargo también hay algunas desventajas que caben destacar:
- Consume más pines que I2C.
- Es necesario utilizar un pin adicional para cada esclavo.
- En un bus solo un maestro puede estar presente.
- Está diseñado para funcionar a distancias cortas.
Y una vez tienes claro utilizar SPI con Arduino lo primero que debes hacer es identificar los pines.
Identificar pines SPI con Arduino
Debido a la gran variedad de placas de Arduino, la posición de los pines del SPI puede variar bastante de un modelo a otro.
En la siguiente tabla puedes encontrar un resumen donde se muestra los pines digitales correspondientes a los pines SPI en la mayoría de las placas Arduino.
Placa Arduino | MOSI | MISO | SCK | SS | Nivel de voltaje |
---|---|---|---|---|---|
Arduino UNO (o compatible) | 11 | 12 | 13 | 10 | 5V |
Arduino Nano | 11 | 12 | 13 | 10 | 5V |
Arduino Mega 2560 | 51 | 50 | 52 | 53 | 5V |
Arduino 101 | 11 | 12 | 13 | 10 | 3.3V |
Arduino MKR1000 | 8 | 10 | 9 | – | 3.3V |
Adicionalmente, los pines SPI se encuentran en el cabezal ICSP (presente en la mayoría de las placas Arduino), esto ha permitido el diseño de Shields que utilicen este protocolo y sean compatibles con los distintos modelos de placas Arduino. Uno de los ejemplos más representativos es el Ethernet Shield.
En la siguiente imagen, obtenida del esquema eléctrico de la placa Arduino UNO se puede observar la correspondencia entre las señales SPI y los pines de la cabecera ICSP.
La conexión de los pines SPI en el cabezal ICSP es igual para todas las placas Arduino.
En caso de que planees utilizar una placa basada en ESP8266 es necesario ser cuidadosos al determinar los pines SPI, ya que la mayoría de estas no poseen conector ICSP.
En estas placas los pines de SPI tienen la siguiente asignación:
- SCK: D5 (GPIO14)
- MISO: D6 (GPIO12)
- MOSI D7 (GPIO13)
- SS: D8 (GPIO15)
Cabe la posibilidad de utilizar SPI por software utilizando otros pines. Ten en cuenta que al hacerlo así estás perdiendo velocidad en la conexión con el BUS SPI con Arduino.
Librería SPI con Arduino
Para controlar el bus SPI con Arduino existe la librería SPI, que permite enviar y recibir datos utilizando dicho bus. Siempre y cuando Arduino se comporte como maestro. En otras palabras, la librería solo permite programar la placa para que funcione como maestro, y no como esclavo.
Esta librería cuenta con dos clases:
- SPISettings: permite establecer los parámetros de las transferencias SPI a realizar.
- SPI: permite controlar el bus SPI para enviar y recibir información.
Inicio analizando la clase SPISettings, ya que es la más simple.
Clase SPISettings
Un objeto de esta clase puede ser utilizado para configurar el bus SPI. Esto implica especificar:
- Velocidad de transferencia máxima.
- Orden de transmisión de bits
- Modo de transmisión
La sintaxis para crear un objeto de este tipo es la siguiente:
1 |
SPISettings mySettting(speedMaximum, dataOrder, dataMode); |
Donde:
- mySettting: es el objeto a crear.
- speedMaximum: es la máxima frecuencia a utilizar especificada en herz.
- dataOrder: es el orden en que se transfieren los bits. Su valor puede ser:
- MSBFIRST: para comenzar por el bit más significativo.
- LSBFIRST: para comenzar por el bit menos significativo.
- dataMode: especifica el modo de transmisión. Puede tomar uno de los siguientes valores: SPI_MODE0, SPI_MODE1, SPI_MODE2 o SPI_MODE3.
Funciones de la Clase SPI
La clase SPI es estática, lo que quiere decir que no hace falta crear una instancia u objeto de la clase. Es como las librerías Serial o Wire.
Directamente escribes la clase (SPI) y luego llamas a la función correspondiente. Aquí alguna de estas funciones.
SPI.begin()
Esta función inicializa el bus SPI y configura los pines SCK, MOSI y SS como salida.
Su sintaxis sería:
1 |
SPI.begin(); |
Como puedes ver es muy simple y ni siquiera requiere parámetros.
Después de ejecutar esta función no se deben utilizar las funciones digitalWrite() o analogWrite() en los pines de SPI.
SPI.end()
Esta función realiza lo contrario a SPI.begin(), es decir, que des-inicializa el bus SPI. De esta forma es posible utilizar las funciones digitalWrite() o analogWrite() para controlar los pines de SPI nuevamente.
Su sintaxis es la siguiente:
1 |
SPI.end(); |
Como puedes ver, al igual que SPI.begin(), es muy simple y no requiere parámetros.
SPI.beginTransaction()
Esta función es utilizada para preparar el bus SPI al comienzo de una transferencia y tiene dos propósitos:
- Establecer los parámetros necesarios para la transferencia a realizar.
- Indicar que el bus SPI está siendo utilizado. De lo contrario otra librería que emplee el bus SPI podría intentar utilizarlo, generando errores en la comunicación.
Su sintaxis es:
1 |
SPI.beginTransaction(mySettings); |
Donde:
- mySettings: es un objeto de tipo SPISettings con los parámetros para la transferencia.
En dependencia de la naturaleza de los parámetros es posible utilizar esta función de dos formas diferentes.
Cuando los parámetros a utilizar son constantes, es decir que siempre serán los mismos, es posible declarar el objeto SPISettings en la propia llamada a la función. Tal y como se muestra en el siguiente ejemplo.
1 |
SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE1)); |
Esta sintaxis provoca que el código obtenido se ejecute más rápido.
En caso de que los parámetros no sean constantes es necesario declarar el objeto SPISettings y después utilizarlo como parámetro al ejecutar la función. En el siguiente ejemplo lo puedes ver.
1 2 |
SPISettings spi_sett(speedMax, datOrd, datMod); SPI.beginTransaction(spi_sett); |
Esta función debe ejecutarse antes de poner el pin SS del esclavo a nivel bajo.
SPI.transfer()
Esta función envía y recibe datos con un esclavo previamente seleccionado. Se puede ejecutar de 2 formas diferentes en dependencia de la cantidad de parámetros.
Cuando una función puede ser llamada de diferentes maneras dependiendo del número de parámetros o el tipo de estos, se dice que es una función o un método sobrecargado.
La primera sobrecarga de la función admite un parámetro.
1 |
byte ret = SPI.transfer(byte2send); |
Donde:
- ret: es el valor leído desde el pin MISO en esa transacción.
- byte2send: es el valor a enviar al esclavo (mediante el pin MOSI).
La segunda sobrecarga de la función admite dos parámetros.
1 |
SPI.transfer(buffer, bufferSize); |
Donde:
- buffer: es un arreglo de bytes donde están almacenados los bytes a enviar. Además, es utilizado para almacenar los bytes recibidos en la transferencia.
- bufferSize: representa la cantidad de elementos a enviar y recibir por el bus SPI.
SPI.endTransaction()
La función SPI.endTransaction() es la encargada de liberar el bus SPI una vez se han terminado las transferencias a realizar. En otras palabras, permite que otras librerías puedan utilizar nuevamente el bus.
Su sintaxis es:
1 |
SPI.endTransaction(); |
Esta función debe ser ejecutada antes de establecer el pin SS del esclavo en estado alto.
Ejemplo comunicación SPI con Arduino y AD5206
Ya está bien de tanta teoría, ahora toca la parte práctica. Ahora verás cómo controlar la intensidad de 6 LEDs utilizando un potenciómetro digital mediante el protocolo SPI con Arduino.
Para este proyecto necesitarás:
- 1x placa Arduino UNO*
- 6x LEDs
- 6x resistencias de 330 Ω
- 1x potenciómetro digital AD5206
- 1x placa de prototipos para el montaje del circuito
- Cables para conexiones
* Es posible utilizar cualquier otra placa como Arduino Nano o Arduino MEGA 2560.
Potenciómetro digital AD5206
Si llevas un tiempo en esto de la electrónica de seguro que sabes qué es un potenciómetro o resistencia variable y has trabajado con ellos.
De modo muy simple, se puede decir que un potenciómetro no es más que un resistencia al que le puedes variar su valor. Existen una increíble variedad: rotativos, deslizantes, de tornillo, etcétera…
Si tienes dudas y quieres aprender más, puedes revisar el artículo sobre potenciómetros con Arduino.
El AD5206 es un potenciómetro digital de 6 canales. Esto significa que tiene seis resistencias variables (potenciómetros) incorporadas para ser controladas de forma individual. Hay tres pines en el chip para cada una de las seis resistencias variables internas, y se pueden conectar con ellas de la misma manera que usaría un potenciómetro mecánico.
Esto lo convierte en un componente ideal para reemplazar potenciómetros mecánicos, en la implementación de filtros programables, ajuste de ganancia automática en sistemas analógicos, entre otras cosas.
Inspeccionando la primera página de su hoja de datos puedes comprobar que:
- Su voltaje de operación puede estar en el rango de 2.7 a 5 voltios, eso lo hace ideal para utilizar con cualquier placa Arduino, incluso las basadas en ESP8266 o ESP32, sin importar su voltaje de operación.
- Es compatible con SPI.
- Sirve como reemplazo para potenciómetros de 10 kΩ, 50 kΩ y 100 kΩ.
En la siguiente figura, se puede ver la distribución de pines presente en dicho circuito integrado.
Como se puede apreciar, cuenta con 6 potenciómetros digitales, una interfaz SPI y dos pines de alimentación. En la siguiente tabla puedes ver un resumen de los pines y sus funcionalidades.
Pines | Descripción |
---|---|
VDD | Alimentación positiva |
GND | Tierra |
CS | Chip Select (equivalente a SS) |
SDI | Serial Data Input (equivalente a MOSI) |
CLK | Clock (equivalente a SCK) |
Ax | Terminal positivo del potenciómetro (la x indica el número del potenciómetro) |
Wx | Terminal central del potenciómetro (la x indica el número del potenciómetro) |
Bx | Terminal negativo del potenciómetro (la x indica el número del potenciómetro) |
En este caso la interfaz SPI tiene una particularidad, y es que no existe el pin MISO. Eso se debe a que el potenciómetro (que actúa como esclavo) nunca envía información al microcontrolador.
En este ejemplo se usa cada resistencia variable como divisor de voltaje, conectando un pin lateral (pin Ax) a 5 V, el otro pin lateral (pin Bx) a tierra y tomando la salida de voltaje variable del pin central (Wx) . En este caso, el AD5206 proporciona una resistencia máxima de 10k ohmios, entregados en 255 pasos (255 es el máximo, 0 es el mínimo).
En otras palabras, que trabaja similar a la función analogWrite(), pero eso se hace más adelante.
Determinar parámetros para el bus SPI con Arduino
Si sigues indagando en la hoja de datos puedes encontrar las especificaciones necesarias para la comunicación. Observa la forma de onda ofrecida en la página 4.
Ahí se muestran los parámetros tCH y tCL que especifican el tiempo mínimo que la señal de reloj puede estar en estado alto y bajo respectivamente.
En la tabla de características dinámicas se encuentran los valores correspondientes a dichos parámetros.
Teniendo en cuenta que para enviar un bit es necesario esperar un ciclo de reloj completo (tCH+tCL) se puede determinar que un bit tarda 40 ns en enviarse. Calculando el recíproco obtienes la máxima frecuencia admisible para comunicarse con el AD5206:
Lo siguiente es determinar con cuál modo SPI es compatible el AD5206. Teniendo en cuenta que no se dice explícitamente es necesario prestar atención al siguiente diagrama de tiempo obtenido de la hoja de características.
Como se puede apreciar el pin datos es modificado en los flancos de bajada de la señal de reloj. Además, el estado inactivo de la señal de reloj es en estado bajo. Esto coincide con el modo de operación SPI 0.
Por último, en la sección OPERATION de la hoja de datos se indica que los bits se tienen que enviar comenzando por el más significativo (MSB).
Hasta aquí ya tienes los parámetros necesarios para configurar el bus SPI. Sin embargo, aún no se ha especificado la estructura de la trama a enviar.
Trama de comunicación con AD5206
En la sección OPERATION también se muestra la estructura de la trama que se debe enviar al dispositivo. En la siguiente imagen la puedes observar.
Como puedes ver está compuesta por dos secciones:
- ADDR: esta sección consta de 3 bits y es utilizada para indicar a qué potenciómetro va dirigida la trama.
- DATA: esta sección consta de 8 bits y es utilizada para especificar la posición del potenciómetro.
Esto significa que para establecer la posición de un potenciómetro es necesario enviar dos bytes. El primero con el número del potenciómetro y el segundo con la posición. Para comprender mejor el significado de esta trama es mejor montar el circuito y verlo en funcionamiento.
Circuito SPI con Arduino y AD5206
En la siguiente imagen puedes ver el esquema del circuito a montar.
Con el fin de lograr variar la intensidad de los LEDs, estos se han conectado a los pines centrales de cada potenciómetro. Los pines de los extremos de los potenciómetros se han conectado a tierra (GND) y 5V. De esta forma cuando el potenciómetro esté en su menor posición (código 0) el LED se encontrará apagado y a medida que se vaya incrementando la posición el LED comenzará a incrementar su intensidad.
En la siguiente imagen puedes ver como debería quedar el circuito una vez montado en la placa de prototipos.
Si el circuito te parece demasiado complejo puedes probar utilizando un solo potenciómetro. En ese caso solo necesitarías un LED y una resistencia.
Código SPI con Arduino
Ya tienes todo el circuito montado. Ahora es momento de darle vida a esos LEDs.
Lo primero es añadir la librería SPI y definir el pin a utilizar para SS, en este caso el 10.
1 2 3 |
#include <SPI.h> const int slaveSelectPin = 10; |
En la función setup() se configura el pin SS como salida digital y se pone en estado alto, de esta forma se garantiza que el esclavo no se active. Luego, se inicializa el bus SPI.
1 2 3 4 5 6 7 |
void setup() { // poner el pin SS como salida y en estado alto pinMode(slaveSelectPin, OUTPUT); digitalWrite(slaveSelectPin, HIGH); // Inicializar bus SPI SPI.begin(); } |
La función loop() contiene un ciclo que itera por cada uno de los potenciómetros. Para cada potenciómetro se incrementa la salida desde su valor mínimo (0) hasta el máximo (255) a intervalos de 10 ms.
Una vez alcanzado ese nivel se realiza un pequeño retardo de 100 ms para comenzar el proceso inverso, es decir, para decrementar la salida desde su máximo valor hasta el mínimo (0).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void loop() { // ir por cada canal for (int channel = 0; channel < 6; channel++) { // se incrementa la intensidad hasta el maximo a intervalos de 10 ms for (int level = 0; level < 255; level++) { digitalPotWrite(channel, level); delay(10); } // esperar un tiempo con todos al maximo delay(100); //decrementar la intensidad hasta el minimo a intervalos de 10 ms for (int level = 255; level > 0; level--) { digitalPotWrite(channel, level); delay(10); } } } |
Para establecer la posición del potenciómetro, se ejecuta la rutina digitalPotWrite(). Aquí ves la implementación de esta función.
La función recibe dos parámetros:
- address: que indica sobre qué potenciómetro debe actuar.
- value: indica la posición a establecer en ese potenciómetro.
1 |
void digitalPotWrite(int address, int value) { |
En su primera línea se ejecuta la función SPI.beginTransaction() con la configuración obtenida de la hoja de datos.
Cabe destacar que en este caso se ha definido una frecuencia máxima de 20 Mhz para dejar un margen con el máximo permitido por el chip.
1 |
SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0)); |
Una vez establecida la configuración del bus se activa el esclavo poniendo el pin SS en estado bajo (LOW). Se ha adicionado un pequeño retardo para garantizar estabilidad, pero este puede omitirse sin inconvenientes.
1 2 |
digitalWrite(slaveSelectPin, LOW); delay(1); |
Con el esclavo activado es hora de enviarle la trama. Para eso se ejecuta dos veces la función SPI.transfer(). En la primera ejecución se envía la dirección y en la segunda la posición a establecer.
1 2 |
SPI.transfer(address); SPI.transfer(value); |
Por último, cuando ambos bytes se han enviado se pone el pin SS nuevamente en estado alto para desactivar al esclavo y se ejecuta la función SPI.endTransaction() para liberar el bus SPI.
1 2 3 4 |
digitalWrite(slaveSelectPin, HIGH); SPI.endTransaction(); } |
Pues sí, así de simple es establecer una comunicación utilizando SPI Arduino. Aquí te dejo todo el código, listo para cargar a tu Arduino.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
#include <SPI.h> const int slaveSelectPin = 10; void setup() { // poner el pin SS como salida y en estado alto pinMode(slaveSelectPin, OUTPUT); digitalWrite(slaveSelectPin, HIGH); // Inicializar bus SPI SPI.begin(); } void loop() { // ir por cada canal for (int channel = 0; channel < 6; channel++) { // se incrementa la intensidad hasta el ma'ximo a intervalos de 10 ms for (int level = 0; level < 255; level++) { digitalPotWrite(channel, level); delay(10); } // esperar un tiempo al ma'ximo delay(100); //decrementar la intensidad hasta el minimo a intervalos de 10 ms for (int level = 255; level > 0; level--) { digitalPotWrite(channel, level); delay(10); } } } void digitalPotWrite(int address, int value) { SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0)); // poner pin CS en estado bajo para seleccionar el esclavo digitalWrite(slaveSelectPin, LOW); delay(1); // enviar la direccion y el valor SPI.transfer(address); SPI.transfer(value); delay(1); // poner pin CS en estado alto para deshabilitar el esclavo digitalWrite(slaveSelectPin, HIGH); SPI.endTransaction(); } |
Si cargas el código y todo funciona bien ¡Felicidades!; si no, a continuación, te dejo un listado de los problemas más frecuentes con los que te puedes topar al utilizar SPI Arduino y sus posibles soluciones.
Problemas frecuentes con la comunicación SPI con Arduino
Es probable que cuando estés utilizando el bus SPI con Arduino los componentes no funcionen adecuadamente, es decir, que la comunicación no vaya del todo bien.
Esto puede ser debido a varias causas algunas de ellas las puedes ver a continuación.
Pin SS en placas Arduino basadas en AVR
Todas las placas basadas en microcontroladores AVR (como Arduino UNO, Arduino MEGA 2560 Arduino Nano, etc.) tienen un pin SS que es útil cuando actúan como esclavos controlados por un maestro externo. Teniendo en cuenta que la librería SPI solo admite el modo maestro, este pin debe establecerse siempre como salida; de lo contrario, el hardware podría configurar automáticamente el bus SPI en modo esclavo, haciendo que la librería deje de funcionar.
Una buena práctica para evitar ese inconveniente es utilizar el propio pin SS como selector para uno de los dispositivos esclavos. De esa forma se garantiza que el pin siempre esté configurado como salida.
Cables largos y ruido electromagnético en SPI con Arduino
Si estás utilizando cables relativamente largos (más de 25 cm) para conectar un dispositivo utilizando SPI con Arduino, es posible que existan problemas en la comunicación. Esos problemas se pueden deber a dos factores principalmente: la velocidad de transmisión y el ruido electromagnético.
Si esto te ocurre, lo primero que debes hacer es intentar utilizar cables más cortos. En caso de que no te sea posible debes comprobar que el sistema no esté cerca de ninguna fuente de ruido electromagnético, como pueden ser fuentes de alimentación conmutadas, radios, antenas, o incluso tu ordenador.
Si aún con todas estas medidas el problema persiste debes reducir la velocidad de comunicación. De esta forma el ruido afecta mucho menos a la señal.
Ejemplo comunicación SPI con Arduino y sensor BMP280
En este segundo ejemplo práctico vamos a ver cómo comunicar el sensor de temperatura y presión BMP280 utilizando el bus SPI con Arduino.
Este sensor es bastante común y lo puedes encontrar tanto Amazon como en Aliexpress por un precio muy asequible.
Asegúrate que el sensor BMP280 que compres tenga 6 pines ya que hay sensores con solo 4 pines que solo permiten comunicación por I2C.
Permite medir tanto la temperatura como la presión atmosférica y gracias a la librería de Adafruit, Adafruit_BMP280, vas a poder trabajar con él mediante el protocolo I2C como con el bus SPI.
En este caso voy a utilizar 2 sensores para poder probar el funcionamiento del bus SPI con Arduino.
Si quieres saber más sobre el sensor te recomiendo que eches un vistazo a la hoja de características técnicas del BMP280.
Conexión BMP280 con Arduino utilizando SPI
El siguiente esquema eléctrico te muestra cómo conectar dos sensores BMP280 utilizando el bus SPI con Arduino.
Cada sensor BMP280 tiene 6 pines.
- VCC: pin de alimentación. Admite una tensión de 1,8V a 3,3V
- GND: toma de tierra.
- SCL (SCK): pin de señal de reloj en protocolo I2C y señal de reloj bus SPI.
- SDA (MOSI): pin de datos en protocolo I2C y pin MOSI bus SPI (salida maestro).
- CSB (SS): pin SS para seleccionar el esclavo en el bus SPI.
- SDO (MISO): pin MISO bus SPI (entrada maestro).
La conexión de los pines es la siguiente
Pin | Sensor 1 | Sensor 2 |
---|---|---|
VCC | 3,3V | 3,3V |
GND | GND | GND |
SCL (SCK) | 13 | 13 |
SDA (MOSI) | 11 | 11 |
CSB (SS) | 9 | 10 |
SDO (MISO) | 12 | 12 |
Fíjate que en las conexiones los pines SCK, MOSI y MISO de los dos sensores van a los mismos pines de Arduino, es decir, comparte conexión. Sin embargo, para el pin de SS de selección de esclavo cada sensor se conecta con un pin diferente.
El sensor 1 se conecta al pin 9 y el sensor 2 al pin 10.
Y una vez conectado pasamos a ver el código.
Programación BMP280 SPI con Arduino
Para poder programar este sensor vamos a utilizar la librería de Adafruit Adafruit_BMP280. En el gestor de librerías busca la librería y la instalas.
Cuando la intentas instalar te pregunta si quieres instalar otras librerías que son necesarias para que la librería de Adafruit funcione. Haz clic en Install all.
Ahora ya está todo listo. El código que voy a utilizar es el siguiente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#include <Wire.h> #include <SPI.h> #include <Adafruit_BMP280.h> #define BMP_SCK (13) #define BMP_MISO (12) #define BMP_MOSI (11) #define BMP_CS_2 (9) #define BMP_CS_1 (10) // Instancia de los dos sensores Adafruit_BMP280 sensor1(BMP_CS_1); Adafruit_BMP280 sensor2(BMP_CS_2); //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // Cuando se configura por software void setup() { Serial.begin(9600); Serial.println(F("Iniciando ejemplo BMP280 SPI")); Serial.println("----------------------------"); if (!sensor1.begin()) { Serial.println(F("No se ha encontrado el primer sensor (9)")); while (1); } if (!sensor2.begin()) { Serial.println(F("No se ha encontrado el segundo sensor (10)")); while (1); } } void loop() { // Mostrar información sensor 1 Serial.println("Sensor 1"); mostrarInfoSensor(sensor1); Serial.println("Sensor 2"); mostrarInfoSensor(sensor2); Serial.println("-------------------------------------------"); Serial.println(); delay(5000); } void mostrarInfoSensor(Adafruit_BMP280 sensor){ Serial.print(F("Temperatura = ")); Serial.print(sensor.readTemperature()); Serial.println(" *C"); Serial.print(F("Presion = ")); Serial.print(sensor.readPressure()); Serial.println(" Pa"); Serial.println(); } |
Lo primero es importar todas las librerías necesarias. Luego crea 5 constantes para almacenar los pines SCD, MISO, MOSI y los dos pines SS de selección de esclavo.
Luego hay que crear dos objetos de la clase Adafruit_BMP280 que se llaman sensor1 y sensor2. Este constructor está sobrecargado y puede admitir 0, 1 o 4 parámetros.
Cuando no admite parámetros estás indicando que vas a trabajar con I2C, cuando indicas un único parámetro le estás diciendo que vas a utilizar el bus SPI con Arduino por hardware. El parámetro indica el pin donde has conectado el SS (selector de esclavo).
Por último, cuando indicas todos los pines del bus SPI con Arduino estás diciendo a la librería que vas a trabajar a través de SPI por software.
En nuestro caso utilizamos la sobrecarga que admite un único parámetro y pasamos el pin SS de cada sensor.
En la función setup() llamamos a la función begin() para iniciar cada sensor. En caso de no poder iniciar el sensor muestra un mensaje por el monitor serie y no continúa.
En la función es donde vamos a consultar la temperatura y la presión atmosférica. He creado una función que se llama mostrarInfoSensor(sensor) que admite un parámetro que es el objeto de la clase Adafruti_BMP280.
Dentro de esta función se consulta la temperatura con la función readTemperature() y la presión con readPressure(). Luego se muestra la información por el monitor serie.
Por último se hace una espera de 5 segundos entre cada medida.
Solo queda una cosa, comprobar que todo funciona correctamente cargando el código a Arduino y abriendo el monitor serie.
Aparecerá algo parecido a la anterior imagen.
Con esto damos por finalizado este tutorial del bus SPI con Arduino y el sensor BMP280.
Conclusiones sobre SPI con Arduino y sensor BMP280
Este tutorial has podido comprobar cómo utilizar el protocolo de comunicaciones SPI con Arduino y has visto un ejemplo de comunicación con el sensor de temperatura y presión BMP280.. Se trata de otra funcionalidad que incluyen los microcontroladores como los que llevan integrados los Arduino.
Esto no quiere decir que siempre tengas que utilizar SPI con Arduino o I2C. Cada proyecto va a requerir utilizar una tecnología y un protocolo concreto.
Lo importante es conocer cada uno de ellos y poder elegir consecuentemente.
Si lo que necesitas es una comunicación bidireccional full-duplex a través de un protocolo flexible y robusto con un consumo de energía bajo y transmisión de datos a gran velocidad, SPI con Arduino puede ser una buena opción.
Cualquier duda o sugerencia en los comentarios de aquí abajo. Gracias :)
Gracias a Shutterstock por la cesión de las imágenes.