LA LIBRERÍA SPI

Facebooktwittergoogle_pluslinkedinmailFacebooktwittergoogle_pluslinkedinmail

En este artículo vamos a hablar sobre una librería específicamente diseñada para gestionar la transmisión de datos en serie entre una placa Arduino y un dispositivo cualquiera, como pueda ser una pantalla TFT, una tarjeta shield Ethernet, una SD, o cualquier otro que admita datos en serie.

Sin embargo, para que, llegado el momento, podamos sacarle partido a esta librería, vamos a conocer, previamente, algunos detalles sobre las comunicaciones en serie y el protocolo SPI en general, y el puerto ICSP de Arduino en particular.

COMUNICACIONES SERIE Y SPI

SPI (Serial Peripheral Interface, Interface para Periféricos Serie) es un protocolo de transferencia de datos serie sincronizados, utilizado por microcontroladores para comunicaciones rápidas con uno o más dispositivos. También puede usarse para comunicar dos microcontroladoras entre sí.

En una conexión SPI siempre hay un dispositivo maestro (normalmente una microcontroladora) que controla los dispositivos periféricos (conocidos como esclavos). La configuración típica establece tres líneas comunes a todos los dispositivos:

  • MISO (Master In Slave Out, Entrada al Maestro, Salida del Esclavo). Es la línea por la que el dispositivo esclavo envía datos al maestro.
  • MOSI (Master Out Slave In, Salida del Maestro, Entrada al Esclavo). Es la línea por la que el dispositivo maestro envía datos al esclavo.
  • SCK (Serial Clock). Es la línea por la que se envían los pulsos de reloj con los que el maestro sincroniza las transmisiones.

También existe una línea específica para cada dispositivo esclavo:

  • SS (Slave Select, Selección de Esclavo). El pin de cada dispositivo esclavo que el maestro emplea para activar o desactivar ese dispositivo específico. Cuando este pin está a LOW el esclavo está en comunicación con el maestro. Si se pone en HIGH, no existe dicha comunicación. Esto permite que un mismo maestro se comunique con varios esclavos, usando las mismas líneas MISO, MOSI y SCK.

Para escribir código para un nuevo dispositivo SPI debes tener en cuenta algunos detalles:

¿Cúal es la máxima velocidad SPI que puede usar tu dispositivo? Esto se controla mediante el primer parámetro en SPISettings (ver más adelante, en la descripción de la librería). Si estás usando un chip a 15 MHz, establece 15000000. Arduino seleccionará, automáticamente, la mayor velocidad posible que sea igual o inferior al valor establecido.

¿Los datos se transmiten empezando por el Bit Más Significativo (MSB) o el Bit Menos Significativo (LSB)? Esto se controla en el segundo parámetro de SPISettings, que puede ser MSBFIRST o LSBFIRST. La mayoría de los chips SPI usan MSB.

¿El reloj está en reposo con nivel HIGH o LOW? ¿Hay muestras de flanco de subida o de bajada de los pulsos de reloj? Estos modos se controlan con el tercer parámetro de SPISettings.

El estándar SPI es flexible y cada dispositivo lo implementa con pequeñas diferencias. Esto significa que tienes que poner especial atención a la datasheet de tu dispositivo cuando programes el código.

En general, existen cuatro modos de transmisión. Estos modos controlan cuando se desplazan los datos de entrada y salida, en los flancos de subida o bajada de la señal de reloj (lo que se llama fase de reloj). Y si el reloj está activo en nivel alto o bajo (lo que se llama polaridad de reloj).

Los cuatro modos combinan la fase y la polaridad de acuerdo a la siguiente tabla:

Modo

Polaridad de reloj (CPOL)

Fase de reloj (CPHA)

SPI_MODE0 0 0
SPI_MODE1 0 1
SPI_MODE2 1 0
SPI_MODE4 1 1

Una vez establecidos los parámetros, usa SPI.beginTransaction() para empezar a usar el puerto SPI, que se configurará con los ajustes que hayas indicado. La forma más simple y eficiente para establecer los ajustes de SPI es en el propio método SPI.beginTransaction(), así:

SPI.beginTransaction(SPISettings(14000000, MSBFIRST, SPI_MODE0));

Si otras librerías están usando interrupciones SPI no podrán acceder a SPI hasta que llames a SPI.endTransaction(). Este método no cambia los ajustes SPI establecidos. Si tu código, u otra librería, vuelve a iniciar SPI (SPI.beginTransaction() de nuevo), se mantendrán los ajustes anteriores, a menos que, en la segunda inicialización se cambien explícitamente. Ten esto en cuenta si tu código emplea más de una librería que use SPI.

Con la mayoría de los dispositivos SPI, después de SPI.beginTransaction(), pondrás el pin SS en LOW, llamarás a SPI.transfer() las veces que sea necesario para enviar tus datos, pondrás el pin SS en HIGH y cerrarás la conexión con SPI.endTransaction().

CONEXIONES

La siguiente tabla muestra que pines de las distintas tarjetas Arduino se emplean como SPI. En las que la conexión se puede establecer en un pin digital, o bien en uno específico del puerto ICSP de la tarjeta, aparece así especificado.

Tarjeta Arduino / Genuino MOSI MISO SCK SS (slave) SS (master) Nivel
Uno o Duemilanove 11 o ICSP-4 12 o ICSP-1 13 o ICSP-3 10  5V
Mega1280 o Mega2560 51 o ICSP-4 50 o ICSP-1 52 o ICSP-3  53  5V
Leonardo ICSP-4 ICSP-1 ICSP-3  5V
Due ICSP-4 ICSP-1 ICSP-3 4, 10, 52  3.3V
Zero ICSP-4 ICSP-1 ICSP-3  3.3V
101 11 o ICSP-4 12 o ICSP-1 13 o ICSP-3 10 10  3.3V
MKR1000 8 10 9  3.3V
Conector ISCP de Arduino

Conector ISCP de Arduino

Observa que los pines MISO, MOSI y SCK están disponibles en un conector físico en ICSP. Esto es útil para el diseño de shields que se adapten a cualquier placa, o para usar estos pines para conectar los correspondientes de cualquier dispositivo SPI. En la imagen se ve la disposición de los pines en el conector ISCP.

 

EL PIN SS EN PLACAS BASADAS EN AVR

Todas las placas basadas en microcontroladores de la serie AVR (cómo, p.e. los de Arduino) tienen un pin SS que resulta útil cuando actúan como esclavos controlados por un maestro externo. Dado que la librería sólo soporta el modo maestro, este pin debería estar configurado siempre como de salida (OUTPUT). En otro caso la interface SPI podría configurar por hardware el maestro como esclavo, volviéndose inoperante.

Sin embargo, es posible usar cualquier pin como SS en los dispositivos. Por ejemplo, el shield Ethernet utiliza el pin 4 para controlar la conexión SPI del slot SD incorporado, y el pin 10 para controlar la conexión del controlador Ethernet.

LA LIBRERÍA SPI

Ahora que ya tenemos algunas ideas claras sobre la comunicación de datos en serie, vamoss a detallar la operativa de la librería SPI.

EL CONSTRUCTOR

El constructor de esta librería es SPISettings. Se emplea para crear un objeto con los ajustes de los que hablábamos anteriormente. La sintaxis genérica es:

SPISettings objetoSPI (VelMax, OrdenBits, ModoDeDatos);

Los argumentos, que ya hemos comentado antes, son:

  • VelMax. La velocidad máxima a la que puede transmitir o recibir datos tu dispositivo.
  • OrdenBits. Si en los bloques de bits que se envían o reciben se transmite primero el bit más significativo (MSB_FIRST) o el menos significativo (LSB_FIRST).
  • ModoDeDatos. Establece la fase de reloj y la polaridad de reloj, según la tabla que vimos más arriba.

Estos tres datos debes consultarlos en la datasheet de tu dispositivo.

EL MÉTODO begin()

Este método no recibe argumentos. Se emplea en la sección setup (normalmente) para inicializar el bus SPI de la tarjeta Arduino. Su sintaxis general es la siguiente:

SPI.begin();

EL MÉTODO beginTransaction()

Se emplea para iniciar una comunicación SPI. Si se empleó el constructor la sintaxis será pasándole cómo argumento el objeto de la clase SPISettings, así:

SPI.beginTransaction(objetoSPI);

Si no se usó SPISettings, o se desea iniciar la comunicación con otros parámetros, la sintaxis será:

SPI.beginTransaction(VelMax, OrdenBits, ModoDeDatos);

EL MÉTODO end()

No recibe ningún argumento. Desactiva el bus SPI. Se emplea cuando nuestro sketch no va a usar más este modo de comunicación. La sintaxis general es la siguiente:

SPI.end();

EL MÉTODO endTransaction()

Se emplea para indicar la finalización de una transmisión, tras poner a HIGH el pin correspondiente a SS (también llamado CS, en algunos textos), lo que desactiva la comunicación con el dispositivo esclavo. El uso de ambas operaciones permite que el bus SPI quede disponible para otras librerías que necesiten usarlo. El método no recibe argumentos y la sintaxis es:

SPI.endTransaction();

EL MÉTODO setBitOrder()

Este método se emplea para cambiar el orden de transferencia de los datos, permitiendo indicar que estos empiezan por el bit más significativo o el menos. Para ello, recibe como argumento una de las dos constantes Arduino que se emplean para este fin (MSB_FIRST o LSB_FIRST). La sintaxis genreal es la siguiente:

SPI.setBitOrder(MSB_FIRST);

o bien

SPI.setBitOrder(LSB_FIRST);

Dado que este dato puede establecerse, con carácter general, con SPISettings, o con carácter particular para una transacción concreta con SPI.beginTransaction(), el método SPI.setBitOrder() está cayendo en desuso, y la documentación oficial lo desaconseja, lo que induce a pensar que en el futuro quedará obsoleto y podría no ser aceptado por futuras versiones del IDE.

EL MÉTODO setClockDivider()

Este método es una alternativa para establecer la velocidad de transferencia de datos (la velocidad del reloj, que es el que marca los pulsos, en realidad). Su sintais general es la siguiente:

SPI.setClockDivider(CONSTANTE_DE_DIVISION);

En esta sintaxis, CONTANTE DE DIVISION es una constante de Arduino que establece un divisor sobre la velocidad estándar del reloj de las placas Arduino, que es de 16 MHz. Así, si le decimos que divida por 2, estaremos estableciendo la velocidad en 8 MHz. Las divisiones posibles son 2, 4, 8, 16, 32, 64 y 128. Si queremos, por ejemplo, establecer la velocidad en 8 MHz, usaremos la siguiente sintaxis específica:

SPI.setClockDivider(SPI_CLOCK_DIV2);

En el caso concreto de la placa Arduino Due, la velocidad estándar del reloj es de 84 MHz, en vez de los 16 MHz de otras placas, y el valor del divisor se puede establecer con un valor entre 1 y 255. Por ejemplo, un valor de 10 fijaría una velocidad de transmisión SPI de 8,4 MHz.

El uso de este método está desaconsejado por las mismas razones que SPI.setBitOrder().

EL MÉTODO setDataMode()

Establece la fase y polaridad del reloj, mediante una de las constantes que ya conocemos (SPI_MODE0, SPI_MODE1, SPI_MODE2 y SPI_MODE3). También está desaconsejado, ya que se puede establecer este modo en SPISettings o en SPI.beginTransaction().

EL MÉTODO shiftIn()

Este método es, realmente, una función independiente, es decir, no es parte de la librería SPI. Se usa para leer un byte disponible en un pin de la placa. Su uso está destinado a leer bit a bit una secuencia de bits, por lo que entre los argumentos de esta función se establece si esta secuencia empezará por el bit más significativo o el menos. La sintaxis general es la siguiente:

byte entrada = shiftIn(dataPin, clockPin, bitOrder);

Los argumentos son los siguientes:

  • dataPin. Es el pin por el que entran los datos.
  • clockPin. Es el pin en el que está la señal de reloj. Arduino lo pone a HIGH antes de recibir cada bit y a LOW inmediatamente después. No tenemos que hacer nada específico para que esto ocurra. La gunción shiftIn() se encarga de gestionar esto.
  • bitOrder. Es una de las dos constantes, MSB_FIRST o LSB_FIRST.

EL MÉTODO shiftOut()

Es la contraparte de la función anterior.  Al igual que esta, no es un método propio de la librería SPI. Su uso es el mismo que la función anterior, pero para enviar un byte, en lugar de recibirlo. La sintaxis es ligeramente diferente:

shiftOut(dataPin, clockPin, bitOrder, byte);

Los tres primeros argumentos ya los conocemos. El cuarto es el byte que vamos a enviar. La función se encarga de enviarlo bit a bit.

LOS MÉTODOS transfer() y transfer16()

Estos métodos se usan para recibir un byte (el primero) o dos bytes (el segundo). La sintaxis general es:

palabra = SPI.transfer(valor);

dónde palabra sería una variable con un byte de tamaño, o bien

palabra = SPI.transfer16(valor);

dónde palabra sería una variable con 16 bits de tamaño.

Para enviar datos, en lugar de recibirlos, usamos sólo el método SPI.transfer(). La sintaxis general, en este caso, es la siguiente:

SPI.transfer(matriz, tamaño);

Los argumentos son los siguientes:

  • matriz. Es una matriz de datos para transferir. Los datos son de un byte cada uno (pueden ser byte, o char, por ejemplo).
  • tamaño. Es el tamaño en bytes que tiene la matriz.

EL MÉTODO usingInterrupt()

Este método permite que, si se usa la librería SPI en un sketch que maneja interrupciones, evitemos conlictos al usar SPI.beginTransaction(). La sintaxis general es la siguiente:

SPI.usingInterrupt(interrupcion);

En breve publicaremos un artículo sobre interrupciones para entender como funcionan.

     

2 comentarios:

  1. Pingback: LA LIBRERÍA SD » eldesvandejose.com

  2. hola buenas! enhorabuena por los post y los tutoriales en general, personalmente son de gran ayuda.
    Estoy con un proyecto de enviar datos de 2 sensores de temperatura ( dht22 y sonda termopar k con amplificador max31856 ). Tengo arduino UNO con la shield Ethernet w5100( comunicación con UNO a través
    de SPI) el problema viene cuando intento ver los datos del amplificador max31856, que tambien tiene comunicación por SPI. No llego a entender muy bien la manera de hacer el código para que los la shield y el amplificador
    puedan enviar los datos, no me hace falta que sean a la vez. No se si alguien haya conseguido algún código de maestro con dos esclavos como es mi caso. Seguire el hilo de comentarios por si alguien se le ocurre algo.
    Muchas gracias de ante mano. Un saludo

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *