Skip to content

CT.6: SPI maestro

Juan Gonzalez-Gomez edited this page Apr 9, 2024 · 294 revisions

Descripción

Ya sabemos crear esclavos SPI. Ahora aprenderemos a hacer maestros, aunque de momento sólo a nivel físico, ya que cada periféricos tiene sus propios comandos y requiere por tanto de un controlador específico. Gracias al bloque serial-SPI podremos hacer pruebas fácilmente

Historial

  • 2019-Julio-03: Version inicial del cuaderno técnico
  • 2024-Abril-09: Ejemplos adaptados a la nueva toolchain: apio-0.9.3. Eliminado el error en la verificación. Probados con icestudio 0.11.3wip (futura versión 0.12). Los pantallazos de los ejemplos no se han actualizado todavía

Vídeos

  • Fecha: 2019/Julio/6

Haz click en la imagen para ver el vídeo en Youtube

Click to see the youtube video

Haz click en la imagen para ver el vídeo en Youtube

Click to see the youtube video

Colección

Ejemplos

Todos los ejemplos de este cuaderno técnico están en este repositorio: Ejemplos CT6: SPI maestro. También encontrarás una selección de ellos en el menú Archivo/Ejemplos/SPI/Spi-master de Icestudio, una vez instalada y seleccionada la colección Jedi

Contenido

Introducción

En el cuaderno técnico 5 aprendimos a realizar comunicaciones por el bus spi, usando como maestro un Arduino uno y como esclavos nuestro propios periféricos en la FPGA. Ahora usaremos como maestro la FPGA, para intercambiar datos con otros periféricos con bus SPI

Esto nos permitirá crear circuitos en la placa Alhambra II para acceder a periféricos como memorias flash, cámaras, displays, etc. La FPGA será la maestra, por lo que deberá generar las señales SCLK (reloj), MOSI (envío de datos al esclavo) y SS (Selección de esclavo)

Nos centraremos en el nivel físico del SPI: envío y sincronización de los bits. De momento nos restringiremos al modo 0 del SPI (CPOL=0, CPHA=0) y velocidad de 2MHZ. Cada periférico es un mundo en sí mismo: tiene sus propios comandos y registros, pero el nivel físico es común a todos ellos

En futuros cuadernos técnicos usaremos este nivel físico para implementar controladores específicos para periféricos concretos. Pero el primer paso es manejar bien este nivel. ¿Cómo vamos a hacer las pruebas? Comunicándonos con nuestros propios periféricos esclavos en la FPGA

Bloques esclavos de Pruebas: spi-test

Para hacer pruebas genéricas usaremos cuatro bloques esclavos en la FPGA: spi-test-id, spi-test-echo, spi-test-cmd y spi-test-regs. Nos comunicaremos con ellos desde nuestro maestro en la FPGA, para comprobar que todo está funcionando bien. Primero los pondremos en marcha con Arduino

Cada bloque implementa un periférico spi completo, que nos ayudará a probar y depurar. Son configurables mediante parámetros. Todos ellos se conecta a las 4 señales del SPI: sclk, mosi, miso y ss. Tienen conexión a los leds y a los pulsadores

Bloque spi de test Parámetros Descripción
spi-slave-test-id Identificador. Valor por defecto: 0xA5 Todo lo recibido lo saca por los LEDs. En cada transacción devuelve un valor constante: el identificador
spi-slave-test-echo No tiene parámetros Todo lo recibido lo saca por lo leds y lo almacena para devolverlo en la siguiente transacción (eco)
spi-slave-test-cmd Código de los 2 comandos Implementa dos comandos: write_LED para escribir un valor en los leds, y read_buttons para leer el estado de los dos pulsadores
spi-slave-test-regs Código de los 3 comandos, direcciones de los 3 registros mapeados y valor del identificador Implementa 3 registros mapeados en memoria: Leds, pulsadores e identificador. Son accesibles mediante los comandoss SAP, WR y RD

Estos bloques están accesibles desde el menú Varios/SPI/SPI-slave/Test de la colección Jedi 1.7.0 ó superior. Primero haremos circuitos de pruebas para comprobar su funcionamiento usando el Arduino como maestro, y luego lo reemplazaremos por nuestro propio maestro en la FPGA

El escenario para probar los bloques esclavos es el mismo que utilizamos en el cuaderno técnico 5: un Arduino uno que hace de maestro, conectado a los periféricos esclavos en la FPGA. Colocamos 2 LEDs en el arduino para las pruebas

Bloque spi-slave-test-id

El periférico implementado con este bloque saca los valores recibidos por los LEDs, y devuelve siempre un identificador constante, en cada transacción, salvo en la primera después del reset, que devuelve 0. Este es el circuito que se carga en la FPGA esclava

(00-1-spi-slave-test-id.ice)

En el Arduino cargamos este programa, que genera una secuencia de dos estatos en los LEDs, enviando alternativamete los valores 0xAA y 0x55. El valor recibido (0xA5) se muestra en la consola serie

(00-1-spi-slave-test-id.ino)

#include <SPI.h>

//-- Pin usado para la seleccion del esclavo
#define SS 10

void setup() {

  //-- Inicializar SPI
  SPI.begin();
  SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));

  //-- Debug
  Serial.begin(9600);

}

uint8_t transaction(uint8_t value)
{
  digitalWrite(SS, LOW);
  uint8_t ret = SPI.transfer(value); 
  digitalWrite(SS, HIGH);

  return ret;
}

uint8_t id;

void loop() {

  //-- Sacar valor 0xAA por los LEDs
  id = transaction(0xAA);
  delay(500);

  Serial.print("ID: ");
  Serial.println(id, HEX);

  //-- Sacar valor 0x55 por los LEDs
  transaction(0x55);
  delay(500);
}

En este vídeo se muestra el resultado. Si quitamos la señal de reloj, la secuencia se detiene. Es igual que lo que hicimos en el cuaderno técnico 5. La diferencia es que lo tenemos todo encapsulado en el bloque spi-slave-test-id

Click to see the youtube video

En la consola seria de arduino se muestra el identificador 0xA5, cada segundo. Como es constante, siempre aparece el mismo valor. Este identificador lo establecemos en el parámetro del bloque spi-slave-test-id

Bloque spi-slave-test-echo

El bloque spi-slave-test-echo saca por los LEDs el byte que recibe del maestro, y lo devuelve en la siguiente transacción. Es decir, hace eco de todo lo recibido. Este es el circuito que se carga en el esclavo:

(00-2-spi-slave-test-eco.ice)

En programa de prueba que cargamos en el Arduino generamos otra secuencia de dos estados, enviando los valores 0x0F y 0xF0. Por la consola serie se muestra el eco recibido

00-2-spi-slave-test-eco.ino

#include <SPI.h>

//-- Pin usado para la seleccion del esclavo
#define SS 10

void setup() {

  //-- Inicializar SPI
  SPI.begin();
  SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));

  //-- Debug
  Serial.begin(9600);

}

uint8_t transaction(uint8_t value)
{
  digitalWrite(SS, LOW);
  uint8_t ret = SPI.transfer(value); 
  digitalWrite(SS, HIGH);

  return ret;
}

uint8_t eco;
uint8_t val;

//-- Hacer una transacción e imprimir lo recibio
void transaction_print(uint8_t val) 
{
  eco = transaction(val);
  Serial.print("Enviado: ");
  Serial.print(val,HEX);
  Serial.print(". Eco: ");
  Serial.println(eco, HEX);
}

void loop() {

  //-- Enviar 0xF0
  transaction_print(0xF0);

  delay(300);

  //-- Enviar 0x0F
  transaction_print(0x0F);
  delay(300);
}

Y en este vídeo se muestra la secuencia en acción

Click to see the youtube video

Esto es lo que vemos en la consola serie del entorno de Arduino:

Bloque spi-slave-test-cmd

El bloque spi-slave-test-cmd implementa dos comandos: WRITE_LEDS para enviar un valor a los LEDs y READ_BUTTONs para leer los dos pulsadores de la Alhambra II. Los códigos de los comandos se pueden establecer mediante los parámetros. Los valores por defecto se muestran en esta tabla

Comando Código comando Descripción
WRITE_LEDS val 0x40 Sacar el número val por los LEDs
READ_BUTTONS 0x60 Lectura de los pulsadores

Este es el circuito de prueba en el esclavo. El bloque tiene las entradas button1 y button2 que se conectan directamente a los pines donde están los pulsadores SW1 y SW2 de la Alhambra II

(00-3-spi-slave-test-cmd.ice)

El programa de pruebas de arduino está enviando una secuencia de dos estados a los LEDs y leyendo el estado de los pulsadores. Si alguno está apretado, se enciene el LED correspondiente, de lo contrario se apaga

(00-3-spi-slave-test-cmd.ino)

#include <SPI.h>

//-- Pin usado para la seleccion del esclavo
#define SS 10

//-- Comando WRITE_LEDS
#define WLEDS 0x40

//-- Comando READ_BUTTONS
#define RBUTT 0x60


//-- Pin de los LEDs
#define LED1 7
#define LED2 6


void setup() {

  //-- Inicializar SPI
  SPI.begin();
  SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));

  //-- Configurar los LEDs
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);

  //-- Debug
  Serial.begin(9600);

}

//-- Enviar un valor por el SPI para
//-- sacarlo por los LEDs de la FPGA
void write_LEDs(uint8_t value)
{
  digitalWrite(SS, LOW);

  //-- Enviar el codigo de comando
  SPI.transfer(WLEDS);

  //-- Enviar el valor para los LEDs
  SPI.transfer(value);
   
  digitalWrite(SS, HIGH);
}

uint8_t read_buttons()
{
  digitalWrite(SS, LOW);

  //-- Enviar el codigo de comando
  SPI.transfer(RBUTT);

  //-- Leer el estado de los pulsadores
  //-- Se envía el byte "basura" 0x00
  uint8_t value = SPI.transfer(0x00);
   
  digitalWrite(SS, HIGH);

  return value;
}


void loop() 
{
  
  uint8_t buttons;
  uint8_t brillo = 100;

  unsigned long tiempo1 = millis();
  unsigned long tiempo2;
  uint8_t valor_leds = 0x55;

  //-- Valor inicial a mostrar en los LEDs
  write_LEDs(valor_leds);

  //-- Bucle principal
  while(1) {

    //-- Leer botones
    buttons = read_buttons();

    //-- Pulsador SW1 apretado
    if (buttons & 0x01) {

      //-- Encender LED1 (de arduino)
      digitalWrite(LED1, HIGH);
    }
    else
      //-- Apagar LED1
      digitalWrite(LED1, LOW);

    //-- Pulsador SW2 apretado
    if (buttons & 0x02) {

       //-- Encender LED2 (de arduino)
       digitalWrite(LED2, HIGH);
    }
    else
      //-- Apagar LED2
      digitalWrite(LED2, LOW);

    //-- Cada 300ms actualizar la secuencia
    //-- Imprimir el estado de los pulsadores en la consola
    tiempo2 = millis();
    if (tiempo2 > tiempo1 + 300) {
      valor_leds = ~valor_leds;
      write_LEDs(valor_leds);
      tiempo1 = tiempo2;

      Serial.print("Pulsadores: ");
      Serial.println(buttons, HEX);
    }
  
    delay(5);
  }
  
}

En este vídeo se muestra el funcionamiento en acción

Click to see the youtube video

En la consola serie del arduino vemos el estado de los pulsadores

Bloque spi-slave-test-regs

El bloque spi-slave-test-regs incorpora tres registros mapeados en su memoria: uno para acceder a los LEDs, otro para los botones y un tercero con el identificador

Dir. R/W Nombre Función Valor por defecto
10h R/W LEDs Valor mostrado en los LEDs 00h
12h R PULSADORES Estado de los pulsadores SW1 y SW2 00h
FDh R ID Código de identificación del periférico 50h

También implemente los 3 comandos necesarios para su acceso: SAP, WR y RD. Tanto los comandos como las direcciones y el identificador se pasan como parámetros del bloque

Comando Abrev. Código Descripción
SET ADDRES POINTER val SAP 0x7D Establecer el valor del registro de dirección
WRITE REGISTER val WR 0x7E Escribir en el registro apuntado por el registro de dirección
READ REGISTER RD 0x7F Leer el registro apuntado por el registro de dirección

Este es el circuito de ejemplo cargado en el esclavo. Se utilizan los parámetros por defecto. Las dos entradas butt1 y butt2 están conectadas a los pines de los pulsadores SW1 y SW2 respectivamente

(00-4-spi-slave-test-regs.ice)

El programa de pruebas de Arduino genera una secuencia de dos estados, que se actualiza cada medio segundo. Además se leen los pulsadores y se actualizan los **LEDs **de arduino. Y también se lee el identificador. Toda la información se saca por la consola serie

(00-4-spi-slave-test-regs.ino)

#include <SPI.h>

//-- Pin usado para la seleccion del esclavo
#define SS 10

//-- Codigo de los comandos
#define SAP 0x7D  //-- Comando SET ADDRESS POINTER
#define WR  0x7E  //-- Comando de escritura en registro
#define RD  0x7F  //-- Comando de lectura en registro

//-- Pin de los LEDs
#define LED1 7
#define LED2 6

//-- Direcciones de los registros
#define LEDS_REG    0x10  //-- Registro de LEDs
#define BUTTONS_REG 0x12  //-- Registro de pulsadores
#define ID_REG      0xFD  //-- Registro de Identificacion

void setup() {

  //-- Inicializar SPI
  SPI.begin();
  SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));

  //-- Puerto serie
  Serial.begin(9600);

  //-- Configurar los LEDs
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
}

//-- Comando generico
uint8_t cmd(uint8_t cod, uint8_t val)
{
  digitalWrite(SS, LOW);

  //-- Enviar el codigo de comando
  SPI.transfer(cod);

  //-- Enviar el valor de su parametro
  uint8_t ret = SPI.transfer(val);
   
  digitalWrite(SS, HIGH);

  return ret;
}


void SAP_cmd(uint8_t value)
{
  cmd(SAP, value);
}

void WR_cmd(uint8_t value)
{
  cmd(WR, value);
}

uint8_t RD_cmd()
{
  return cmd(RD, 0x00);
}

//-- Escritura en un registro mapeado
void write_reg(uint8_t reg, uint8_t val)
{
  SAP_cmd(reg);
  WR_cmd(val);
}

//-- Lectura de un registro mapeado
uint8_t read_reg(uint8_t reg)
{
  SAP_cmd(reg);
  return RD_cmd();
}

uint8_t butt;
uint8_t id;
uint8_t leds;
uint8_t valor_leds = 0xC3;
uint8_t i = 0;

void loop() 
{

  //-- Escribir un valor en los LEDs
  write_reg(LEDS_REG, valor_leds);

  //-- Leer los valores escritos
  leds = read_reg(LEDS_REG);

  //-- Leer el identificador
  id = read_reg(ID_REG);

  //-- Leer los pulsaddores
  butt = read_reg(BUTTONS_REG);

  //-- Encender los leds de arduino
  if (butt & 0x01) digitalWrite(LED1, HIGH);
  else digitalWrite(LED1, LOW);
  
  if (butt & 0x02) digitalWrite(LED2, HIGH);
  else digitalWrite(LED2, LOW);


  //-- Mostrar las lecturas en la consola
  Serial.print("ID: ");
  Serial.print(id, HEX);
  Serial.print(", LEDs: ");
  Serial.print(leds, HEX);
  Serial.print(", Botones: ");
  Serial.println(butt, HEX);
  delay(500);

  //-- Cambiar la secuencia de los LEDs
  valor_leds = ~valor_leds; 

}

En este vídeo se muestra en acción

Click to see the youtube video

Y esto es lo que sale por la consola serie del entorno de Arduino

El bloque Master SPI

Para intercambiar datos con un periférico esclavo a través del bus SPI utilizamos el bloque maestro: spi-master-mode0-2mhz disponible en el menú Varios/SPI/Master/spi-master-mode0-2Mhz. Nos ceñiremos al modo 0 y una velocidad de reloj de 2Mhz

Cada vez que se activa el tic de transacción se inicia el intercambio de 1 byte con el esclavo. El dato situado en la entrada del bloque se envía hacia el esclavo, por MOSI, y el dato recibido del esclavo por MOSI se obtiene por la salida de datos al finalizar la transacción. Emite un tic por done cuando ha terminado

La señal SS la debe gestionar el controlador superior, ya que depende del comando que se esté enviando. El bloque master incorpora una salida ss opcional, para enviar datos de 1 byte. Pone ss a 0, envía el dato y vuelve a llevar ss a su estado de reposo (ss a 1)

Escenario de pruebas con 2 FPGAs: Maestra y esclava

Para hacer las pruebas necesitamos tener un maestro y un esclavo. Este es uno de los escenarios, en el que se usan dos FPGAs, una para el maestro y otra para el esclavo. Usamos dos placas Alhambra II. La esclava se alimenta mediante un cable macho-hembra que une los pines de 5v

Se están usando los mismos pines tanto en el maestro como en el esclavo, para que sea más sencillo el cableado, pero podrían estar en otros. Estos pines, además, coinciden con los de Arduino

Señal SPI Pin Maestra Pin esclava Descripción
SCLK D13 D13 Reloj
MISO D12 D12 Datos del esclavo hacia el maestro
MOSI D11 D11 Datos del maestro hacia el esclavo
SS D10 D10 Selección del esclavo

Ejemplo 1: Enviando un valor al esclavo

Vamos a hacer nuestro primer ejemplo de envío de un dato del maestro al esclavo. Al apretar el pulsador, el maestro enviará la constante 0xAA al esclavo, y éste la mostrará por los LEDs. El circuito es el siguiente:

(01-spi-master-constant.ice)

En la FPGA esclava cargamos el ejemplo 00-1-spi-slave-test-id.ice que muestra por los LEDs todo lo recibido desde el maestro. El circuito es este:

(00-1-spi-slave-test-id.ice)

Cargamos los circuitos en las FPGAs maestra y esclava respectivamente y lo probamos. Al apretar el pulsador SW1 en el Maestro, veremos cómo aparece el número 0xAA en los LEDs de la esclava. Si volvemos a apretarlo se envía de nuevo, pero como ya estaba en los LEDs, no lo apreciamos

Click to see the youtube video

Para repetirlo, tenemos que hacer reset de la esclava y pulsar SW1 en la Maestra nuevamente. ¡Hemos enviado nuestro primer byte a través del SPI, de una FPGA a otra!

Escenario de pruebas con una FPGA: Maestro y esclavo juntos

Utilizar dos FPGAs independientes es más tedioso, y puede ser que no las tengamos a mano. ¿Por qué no meter el maestro y el esclavo en la misma FPGA, como circuitos independientes? Esto es hardware, lo podemos hacer sin problemas ;-) El escenario es el mostrado en este dibujo

Los circuitos maestro y esclavos son totalmente independientes, como si estuviesen en chips diferentes, pero se cargan en la misma FPGA. Usamos cables externos para unir sus pines del SPI. Ahora sólo hay que cargar un único diseño desde Icestudio por lo que las pruebas se simplifican

En esta tabla se resumen los pines utilizamos, que deben ser unidos mediante los cables externos

Señal SPI Pin Maestro Pin Esclavo Descripción
SCLK D13 D3 Reloj
MISO D12 D2 Datos Esclavo --> Maestro
MOSI D11 D1 Datos Maestro --> Esclavo
SS D0 D0 Selección de esclavo

Ejemplo 1-2: Enviando un valor al esclavo. Escenario 2

El ejemplo 1 anterior lo remodelamos para probarlo en este nuevo escenario. Sólo hay que hacer copy & paste del circuito del esclavo en el maestro. Queda así:

(01-2-spi-master-constant.ice)

En este vídeo se muestra en funcionamiento. Es igual que el ejemplo anterior, pero con todo en la misma FPGA. Al apretar el pulsador, el maestro envía el valor 0xAA al esclavo, que lo muestra en los LEDs. Si se quita el reloj, no se transmite nada

Click to see the youtube video

Ejemplo 2: Leyendo un valor del esclavo

Para realizar la lectura de un dato primero generamos una transacción activando start, y luego capturamos el dato obtenido al recibirse el tic de done. En este ejemplo leemos el esclavo cada 100ms, y guardamos el dato recibido en un registro conectado a los LEDs

En el esclavo tenemos el bloque spi-slave-test-id que nos devuelve el valor constante 0xA5 en cada transacción. Usamos el escenario con una única FPGA con el maestro y esclavos en su interior (escenario 2). Este es el circuito completo:

(02-spi-master-lectura-id.ice)

En este vídeo lo probamos. El maestro muestra en los LEDs lo recibido del esclavo: 0xA5. Se actualiza cada 100ms pero al ser un valor constante no lo apreciamos. Si hacemos un reset, vemos cómo el valor vuelve a aparecer. Si quitamos un cable dejamos de verlo

Click to see the youtube video

Ejemplo 3: Lectura/escritura

Para hacer una lectura y escritura simultáneas basta con combinar los dos ejemplos anteriores. Al tener el SPI un cable de datos para cada sentido (MOSI y MISO) la lectura y escritura se hacen de forma simultánea (transacción). En el maestro activamos la transacción con start

Por la entrada de datos de la izquierda colocamos los datos que queremos enviar y por la de la derecha tendremos lo que recibimos. En el ejemplo 3 incrementamos un contador cada 100ms y enviamos sus valores al esclavo a la vez que leemos el indentificador de este esclavo

En el circuito del esclavo tenemos lo mismo que en el ejemplo 2: el bloque spi-slave-test-id. Los datos recibidos no se llevan directamente a los LEDs, sino al bus slave[7:0]. Lo mismo el maestro: lo recibido lo lleva al bus master[7:0]

Al apretar el pulsador 1, se muestra por los leds lo recibido en el maestro: el identificador constante 0xA5. Al pulsar el botón 2, se muestra lo recibido por el esclavo: el valor del contador. Se selecciona mediante un multiplexor activado por un biestable RS

Este es el circuito completo, con todas las partes, pensado para cargarse en una única FPGA (escenario 2). Pero por supuesto se podría modificar muy fácilmente para probarlo en dos FPGAs. Pero es mucho más fácil para las pruebas hacerlo sólo con una

(03-spi-master-lectura-escritura.ice)

Lo cargamos y lo probamos. En el vídeo lo vemos en funcionamiento. Por defecto está seleccionado el esclavo, y vemos el contador. Al apretar el pulsador 1 cambia al maestro y se muestra el identificador, que es constante (y no lo vemos variar)

Click to see the youtube video

Midiendo el maestro SPI

El bloque maestro spi utiliza el modo 0, con una frecuencia de reloj de 2Mhz. Vamos a realizar mediciones usando un analizador lógico compatible Saleae y la herramienta libre Pulseview. Este es el escenario

Lo probaremos con este circuito, que envía consecutivamente los valores 0xAA y 0x55 al esclavo, cada 300ms, aunque es este tiempo es un parámetro que cambiaremos. En el esclavo tenemos el bloque spi-slave-test-echo que devuelve todo lo recibido, en la siguiente transacción

(04-spi-master-medidas.ice)

Este es el resultado al cargarlo en la FPGA. En los LEDs vemos la secuencia de datos recibida en el esclavo. Como estamos usando un tiempo de 300ms se aprecia el cambio a simple vista. Sin embargo, para verlo mejor en las mediciones lo cambiaremos a 10 micro-segundos

Click to see the youtube video

Cambiamos a 10µs y medimos. Primero observamos el comportamiento global. En esta medición vemos 4 transacciones, que empiezan en los tiempos 0, 10µs, 20µs y 30µs. Sabemos que empieza una transacción porque hay un flanco de bajada en la señal SS

Los valos que le llegan al esclavo (MOSI) son 0xAA, 0x55, 0xAA, 0x55... Y el maestro recibe (por MISO) lo que había en la transacción anterior. Los valores son correctos. También observamos que busy se activa durante toda la duración de la transacción, y hay un tic en done al acabar

Hacemos zoom en la primera transacción. Como es el modo 0, los datos se capturan en el flanco de subida de SCLK. Y los cambios en los datos serie (Miso y mosi) sólo se pueden producir cuando SCLK es 0. Mientras que SCLK=1, deben permanecer con valores estables

Y por último medimos la frecuencia del reloj. Comprobamos que efectivamente SCLK tiene una frecuencia de 2Mhz (periodo de 500ns)

Puerto serie y SPI

Con el bloque spi-máster ya tenemos cubierto el SPI a nivel físico: transmsión de bits y su sincronización. Sin embargo, los periféricos SPI requieren del envío de comandos, que tienen una sintáxis. Esto ya dependen de periférico en cuestión

Una forma rápida de probar periféricos SPI es usar una pasarela Serie-SPI. Consiste en utilizar el ordenador para enviar datos/comandos al periférico, usando un circuito en la FPGA que pasa del puerto serie al SPI. Este es el esquema:

Desde el ordenador bien podemos enviar comandos manualmente al periférico SPI, usando un terminal serie, como por ejemplo el ScriptCommunicator, o bien podemos crear nuestros propios programas. Por ejemplo un script para configurar ciertos registros del periférico, o lo que sea

Ejemplo 5-1: Prueba de eco y LEDs

Comenzamos las pruebas comunicándonos desde el PC con el SPI esclavo de eco (bloque spi-slave-test-eco) que muestra en los LEDs todo lo que recibe y hace eco en la siguiente transacción. Al máster SPI le llegan los datos procedentes del receptor serie

Cuando llega un dato desde el PC (115200 baudios por defecto) por el pin RX, el receptor serie saca el dato en paralelo (8 bits) y un tic por rcv. Esto hace que el SPI máster inicie una transacción, enviando el dato al esclavo y recibiendo otro de él

Cuando la transacción finaliza, hay un dato disposible en su salida data y se emite un tic por done que activa el transmisor serie, enviándose el dato recibido hacia el PC. La velocidad del SPI es mucho mayor que el serie (2Mbps vs 115200 bps) por lo siempre que llega un dato serie el SPI está disponible

Esto simplifica mucho las cosas, ya que no hay que añadir ningún buffer para adaptar las velocidades. El único inconveniente es que la velocidad queda limita al más lento: los 115200 baudios del PC (o la que hayamos configurado)

La gestión de la señal SS la hacemos por separado. Se activa cuando llega una ráfaga de datos (datos seguidos, uno a continuación del otro, sin pausas). Si transcurren 200µs desde que llegó el último dato, la señal SS se desactiva (se pone a 1)

Se implementa muy fácilmente con un biestable RS y un temporizador. El biestable está inicialmente a 1. Cuando llega un dato serie se pone a 0 (y se queda a 0 si ya lo estaba antes). Cada carácter que llega inicializa el temporizador. Al pasar 200µs sin llegar nada se activa el biestable

Para probarlo usamos el escenario con una FPGA, por lo que también incluimos el esclavo (spi-slave-test-echo). El circuito completo es el siguiente:

(05-1-serial-spi-echo.ice)

Cargamos el circuito en la FPGA y abrimos el Script Communicator para enviar datos serie. Primero enviamos los valores 0xaa y 0x55 seguidos (ráfaga) y luego se envían caracteres sueltos tecleando en la consola. En este vídeo se muestra el funcionamiento

Click to see the youtube video

Midiendo

Usando el mismo escenario que en las mediciones anteriores, realizamos una medición una ráfaga de 2 bytes: 0xAA y 0x55. En esta captura nos fijamos en la señal SS. Está a 1. Cuando llega el primer carácter se pone a 0. Al cabo de 90µs aprox. llega el siguiente carácter. Transcurridos 200µs SS se pone a 1

Ahora nos acercamos un poco para ver mejor los datos. En el MOSI vemos que efectivamente están saliendo los valores 0xAA y 0x55, hacia el esclavo. Del esclavo recibimos 0x00 y 0xAA. El 0x00 es el valor inicial (el envío se hace después del reset) y el 0xAA es el eco del primer byte enviado

Bloque Serial-SPI

La pasarela serie-SPI es tan útil para preparar protitos que la encapsulamos en su propio bloque: serial-spi-mode0-2Mhz, accesible desde el menú Varios/SPI/Master/serial-spi-mode0-2Mhz. Se conecta directamente a los pines del SPI maestro y del puerto serie

Como parámetros se le pasa la velocidad del puerto serie (115200 baudios por defecto) y el tiempo sin actividad que debe transcurrir para desactivar la señal de selección del esclavo (Por defecto 200µs)

Ejemplo 5-2: Prueba de eco y LEDs con bloque Serial-SPI

Rehacemos el ejemplo 5-1 pero usando este nuevo bloque serial-SPI. El comportamiento es exactamente el mismo, pero ahora es más fácil crear circuitos de prueba. El ejemplo queda así:

(05-2-serial-spi-echo.ice)

Programa en python

Lo interesante de usar el puerto serie es que podemos crear muy fácilmente programas para enviar la información desde el PC. Este script en python genera una secuencia de dos estados en los LEDs. Envía constantemente los valores 0xAA y 0x55:

(sequence-01.py)

import time
import serial

#-- Poner el nombre del puerto serie aquí
#-- En windows será COMx
SERIAL_PORT = "/dev/ttyUSB1"

#-- Abrir el puerto serie
with serial.Serial(SERIAL_PORT, 115200) as sp:

    #-- Imprimir la información del pueto serie
    print("Puerto serie: {}".format(sp.portstr))

    #-- Valores para la secuencia de los LEDs
    sec = [0xaa, 0x55]

    #-- Bucle infinito. Acabar pulsando ctrl-C
    while True:
        for value in sec:
            #-- Enviar dato por puerto serie
            sp.write(bytes([value]))

            #-- Leer un dato del puerto serie
            eco = sp.read()

            #-- Imprimir lo enviado y lo recibido
            print("Sent: {:02X}, Read: {:02X}".format(value, ord(eco)))

            #-- Esperar medio segundo
            time.sleep(0.5)

Lo ejecutamos desde la línea de comandos y vemos cómo se genera la sencuencia en los LEDs. ¡Estamos enviando datos a nuestro periférico SPI desde el PC!. Terminamos pulsando Ctrl-C. En este vídeo lo vemos en acción:

Click to see the youtube video

Ejemplo 6: Probando los comandos WRITE_LEDs y READ_BUTTONs

Probaremos el envío de comandos a través del puerto serie. Como cada periférico SPI tiene sus propios comandos, haremos las pruebas con el bloque spi-slave-test-cmd que implementa los comandos WRITE_LEDS para sacar por los LEDs y el READ_BUTTONs para leer los botones

(06-serial-spi-cmd.ice)

Cargamos el circuito y lo probamos desde el Script Comunicator. Sólo hace caso a los comandos 0x40 y 0x60. Si enviamos cualquier otra cosa se ignora. Con el comando 0x40 (Write_LEDs) saca por los LEDs el valor que se le pasa como segundo parámetro. En el vídeo enviamos primero 0x40 0xF0 y luego 0x40 0x0F

Click to see the youtube video

Con el comando 0x60 (READ_BUTTONs) leemos los pulsadores. El segundo parámetro se ignora, es para que el esclavo envíe la respuesta. Según los botones que estén apretados recibiremos 0x00, 0x01, 0x02 ó 0x03

Este es un ejemplo de programa python que envía al SPI esclavo una secuencia por los LEDs, y muestra el estado de los pulsadores cada medio segundo

(cmds-01.py)

import time
import serial

#-- Poner el nombre del puerto serie aquí
#-- En windows será COMx
SERIAL_PORT = "/dev/ttyUSB1"

#-- Códigos de comando
CMD_WRITE_LEDS = 0x40
CMD_READ_BUTTONS = 0x60


#-- Implementacion del comando WRITE_LEDS
def write_leds(value):

    ##-- Crear el comando a enviar
    cmd = [CMD_WRITE_LEDS, value];

    #-- Enviar el comando: código comando + valor
    sp.write(bytes(cmd))

    #-- Leer los 2 bytes recibidos e ignorarlos
    ret = sp.read(2)

    #-- Esperar un tiempo sin enviar, para que SS
    #-- se ponga a 1 de nuevo
    time.sleep(0.001)


def read_buttons():
    ##-- Crear el comando a enviar
    cmd = [CMD_READ_BUTTONS, 0x00];

    #-- Enviar el comando: código comando + valor
    sp.write(bytes(cmd))

    #-- Leer los 2 bytes recibidos e ignorarlos
    ret = sp.read(2)

    #-- Esperar un tiempo sin enviar, para que SS
    #-- se ponga a 1 de nuevo
    time.sleep(0.001)

    #-- Devolver el valor recibido
    return ret[1]


#-- Abrir el puerto serie
with serial.Serial(SERIAL_PORT, 115200) as sp:

    #-- Imprimir la información del pueto serie
    print("Puerto serie: {}".format(sp.portstr))

    #-- Valor inicial para los LEDs
    value = 0x0F

    #-- Bucle infinito. Acabar pulsando ctrl-C
    while True:

        #-- Sacar por los LEDs
        write_leds(value)

        #-- Leer los pulsadores
        butt = read_buttons()
        print("Botones: {:2X}".format(butt))

        #-- Cambiar el valor de los leds por su negado
        value = (~value) & 0xFF

        #-- Esperar medio segundo
        time.sleep(0.5)

En este vídeo se muestra en funcionamiento

Click to see the youtube video

Ejemplo 7: Acceso a registros desde el PC

Otros periféricos SPI tienen registros mapeados. Como cada uno tiene los suyos, usaremos el bloque de prueba spi-slave-test-regs que implementa 3 registros mapeados: acceso a los leds, lectura de botones y lectura del identificador. Lo probamos con la pasarela serie-spi

(07-serial-spi-regs.ice)

Desde el terminal serie enviamos los siguientes comandos para hacer pruebas:

  • Selección del registro ID: 0x7D 0xFD
  • Lectura del registro ID: 0x7F 0x00
  • Selección del registro LEDs: 0x7D 0x10
  • Escritura en los LEDs: 0x7E 0xFF (encender todos los leds)
  • Selección del registro Buttons: 0x7D 0x12
  • Lectura de los pulsadores: 0x7F 00

En este vídeo se muestra en funcionamiento:

Click to see the youtube video

Mediante este ejemplo en python se accede desde el PC a los diferentes registros. Cada medio segundo se imprime la identificación y el estado de los pulsadores. Además se escribe en los LEDs para generar una secuencia de dos estados

(regs-01.py)

import time
import serial

#-- Poner el nombre del puerto serie aquí
#-- En windows será COMx
SERIAL_PORT = "/dev/ttyUSB1"

##-- Codigos de comando
CMD_SAP = 0x7D
CMD_RD = 0x7F
CMD_WR = 0x7E

##-- Registros
REG_ID = 0xFD
REG_LEDS = 0x10
REG_BUTTS = 0x12

##-- Comando: Set Address Pointer
def sap(sp, addr):

    #-- Construir el comando
    sec = [CMD_SAP, addr]

    #-- Enviar el comando
    sp.write(bytes(sec))

    #-- Leer los dos bytes recibios. Se ignoran
    eco = sp.read(2)

    #-- Esperar a que la señal ss se ponga a 1
    time.sleep(0.0002)


##-- Comando de escritura
def write(sp, value):
    #-- Construir el comando
    sec = [CMD_WR, value]

    #-- Enviar el comando
    sp.write(bytes(sec))

    #-- Leer los dos bytes recibios. Se ignoran
    eco = sp.read(2)

    #-- Esperar a que la señal ss se ponga a 1
    time.sleep(0.0002)


#--- Comando de lectura
def read(sp):
    #-- Construir el comando
    sec = [CMD_RD, 0x00]

    #-- Enviar el comando
    sp.write(bytes(sec))

    #-- Leer los dos bytes recibios. Se ignoran
    eco = sp.read(2)

    #-- Esperar a que la señal ss se ponga a 1
    time.sleep(0.0002)

    #-- Devolver el valor de la lectura
    return eco[1]


##-- Escritura en un registro
def write_reg(sp, reg, value):

    #-- Seleccionar registro
    sap(sp, reg)

    #-- Escribir valor
    write(sp, value)


#-- Lectura de un registro
def read_reg(sp, reg):
    #-- Seleccionar el registro
    sap(sp, reg)

    #-- Devolver la lectura
    return read(sp)


#-- Abrir el puerto serie
with serial.Serial(SERIAL_PORT, 115200) as sp:

    #-- Imprimir la información del pueto serie
    print("Puerto serie: {}".format(sp.portstr))

    value = 0x0F
    butt = 0

    while True:
        #-- Leer registro de identificacion
        id = read_reg(sp, REG_ID)
        print("Id: {:02X}".format(id))

        #-- Escribir valor
        write_reg(sp, REG_LEDS, value)

        #-- Leer botones
        butt = read_reg(sp, REG_BUTTS)
        print("Botones: {:X}".format(butt))

        #-- Alterar valor e la secuencia
        value = (~value) & 0xFF

        #--- Esperar
        time.sleep(0.5)

En este vídeo lo vemos en acción:

Click to see the youtube video

Memoria flash SPI

A través del bus SPI podemos acceder a memorias flash. Muchas de las placas basadas en las FPGAs ice40, como las Alhambra I, Alhambra II, icestick, ... incorporan una memoria flash SPI en la propia placa, a la que tenemos acceso desde la FPGA usando unos pines dedicados

La Alhambra II incorpora la memoria flash N25Q032A (Hoja de datos), de 4MB. Su función es almacenar el bitstream con el circuito a cargar. Pero queda mucho espacio libre para usarlo como almacenamiento de datos de propósito general

En cuadernos técnicos futuros crearemos un controlador SPI específico para este memoria, pero de momento vamos a hacer pruebas con el que ya conocemos, y usaremos el conversor serie-SPI para probar algunos comandos de esta memoria desde el PC

Conexionado

Usaremos la placa Alhambra II como ejemplo. En ella hay un bus spi que une el chip FTDI, la memoria flash y la FPGA. El chip FTDI es un conversor USB-SPI, que permite que desde el PC se pueda acceder a la memoria flash para cargar los circuitos

Pero la memoria flash también está accesible como un periférico SPI esclavo desde la FPGA, a través de los pines MISO, MOSI, SCK y SS, accesibles desde Icestudio

Circuito de pruebas

Nuestro circuito para las pruebas será el bloque serial-spi, conectado a los pines SPI de la memoria flash. Placas como la icestick o la icezum Alhambra tiene también esos mismos pines: el circuito de pruebas es el mismo:

(08-1-serial-spi-flash-memory-Alhambra-II.ice)

Mapa de memoria

La memoria flash es de 4Mbytes. Sus direcciones van desde la 0x000000 hasta la 0x3FFFFF. El bitstream está almacenado a partir de la dirección 0, y tiene un tamaño de 32KB para la Icezum Alhambra y Icestick, y de 136KB para la Alhambra II. Eso nos da una idea del espacio disponible para nuestras aplicaciones

Comandos para la memoria

La memoria SPI se controla mediante comandos. En la hoja de datos están los detalles de todos. Aquí sólo usaremos 3 para las pruebas: Activar (WAKEUP), leer el identificador (READID) y leer de una dirección(READ). Cada comando tiene su código, sus parámetros y los bytes para las respuestas

Nombre Código comando Parámetros Bytes de respuesta Descripción
WAKEUP 0xAB Ninguno Ninguno Activar la flash. Es necesario ejecutarlo después de haber hecho un reset o el encendido de la placa. De lo contrario no se podrá leer nada
READID 0x9F Ninugno 4 ó más Devolver el identificador del chip de la flash
READ 0x03 Dirección (3 bytes): A2 A1 A0 1 ó más Devolver los bytes que se encuentran en la dirección A2_A1_A0

Para que los comandos tengan efecto hay que activar SS, enviar el comando completo y desactivar SS. En esta tabla se muestra un ejemplo de uso de cada comando. Son los que usaremos en las pruebas

Comando Ejemplo Descripción
WAKEUP 0xAB Sólo con enviarlo la memoria se activa
READID 0x9F 0x00 0x00 0x00 0x00 Se reciben 5 bytes. El primero es basura. El resto contienen la identificación. Ej: ID válido: EF 40 16 00
READ 0x03 0x3F 0xFF 0xF0 0x00 Lectura del byte que se encuentra en la dirección 0x3FFFF0

Probando la memoria

Cargamos el circuito y abrimos el script communicator para enviar comandos. Primero escribirmos el comando AB. Después leemos el identificador con 9F 0 0 0 0 y por último leemos 4 bytes de la posición 0x04AABB: 03 04 AA BB 00 00 00 00

Lo que está en rojo es lo que hemos enviado nosotros, y lo negro es lo que devuelve la memoria por el spi. Vemos que el identificador devuelto es: EF 40 16 00, y que los 4 bytes leídos de la memoría están a FF (es el valor por defecto cuando está en blanco)

En este vídeo lo vemos en funcionamiento. En otras placas se obtendrán otros identificadores y el contenido de la memoria puede variar también

Click to see the youtube video

Este es un script en python para leer el identificador de la memoria flash y realizar un volcado de 256 bytes a partir de la dirección 0x04AABB

(memory-01.py)

import time
import serial

#-- Poner el nombre del puerto serie aquí
#-- En windows será COMx
SERIAL_PORT = "/dev/ttyUSB1"

##-- Codigos de comando
CMD_WAKE = 0xAB
CMD_FLASHID = 0x9F
CMD_READ = 0x03


def wakeup(sp):
    sp.write(bytes([CMD_WAKE]))
    eco = sp.read(1)
    time.sleep(0.0002)

#-- Returns a list wit the identification bytes
def read_id(sp):
    cmd = [CMD_FLASHID, 0, 0, 0, 0]
    sp.write(bytes(cmd))
    id = sp.read(5)
    return [id[i+1] for i in range(4)]

#-- Read one byte
def read_byte(sp, addr):

    addr0 = addr & 0xFF
    addr1 = (addr >> 8) & 0xFF
    addr2 = (addr >> 16) & 0xFF
    cmd = [CMD_READ, addr2, addr1, addr0, 0x00]
    sp.write(bytes(cmd))
    res = sp.read(5)
    time.sleep(0.0002)
    return res[4]

#-- Read N bytes
def read(sp, addr, n):
    addr0 = addr & 0xFF
    addr1 = (addr >> 8) & 0xFF
    addr2 = (addr >> 16) & 0xFF
    cmd = [CMD_READ, addr2, addr1, addr0] + [0] * n
    sp.write(bytes(cmd))
    res = sp.read(n + 4)
    time.sleep(0.0002)
    return [res[i+4] for i in range(n)]

#-- Abrir el puerto serie
with serial.Serial(SERIAL_PORT, 115200) as sp:

    #-- Imprimir la información del pueto serie
    print("Puerto serie: {}".format(sp.portstr))

    #-- Despertar la memoria flash
    wakeup(sp)

    #-- Leer la identificacion de la flash (4 bytes)
    id = read_id(sp)
    id_str = ''.join("{:02X} ".format(id[i]) for i in range(4))
    print("Identificacion: {}\n".format(id_str))
    addr = 0x04aabb
    values = read(sp, addr, 16)
    for j in range(16):
        values = read(sp, addr, 16)
        mem_str = ''.join("{:02X} ".format(values[i]) for i in range(16))
        print("{:06X}: {}".format(addr, mem_str))
        addr += 16

    print()

Ejecutamos el script para obtener el volcado de memoria. Vemos que todos los valores están a 0xFF. Es normal, ya que en esas posiciones de memoria no se guarda el bitstream y nosotros no hemos metido nada todavía. Al comienzo del volcamos vemos el identificador del chip

Vamos a grabar algunos valores diferentes para leerlos con el script. Para ello creamos un fichero binario con los bytes a grabar. En linux podemos usar el programa ghex. El fichero que usamos es test.bin

Ahora lo grabamos usando el programa iceprog. Si tenemos instalado apio, basta con ejecutar el siguiente comando (en cualquier sistema operativo):

apio raw "iceprog -o 0x04AABB test.bin"

Ahí podemos comprobar también el identificador de la flash, que debe ser el mismo que nos ha devuelto nuestro script. Repetimos el volcado anterior:

Efectivamente aparecen los nuevos valores que habíamos definido en el fichero test.bin. Ya sabemos cómo grabar datos en la flash desde el PC, y cómo leerlos desde la FPGA. Esto nos será muy útil en el futuro

Sensor capacitivo SPI CAP1188

Como ejemplo de manejo de un periférico SPI externo, vamos a hacer pruebas con el sensor capacitivo CAP1188. Más adelante diseñaremos un controlador específico, pero de momento usaremos el bloque serial-spi para comprobar que el nivel físico de nuestro maestro spi funciona correctamente

Con este sensor podemos leer hasta 8 entradas capacitividas y usar 8 LEDs, a través del SPI. Es un sensor de Adafruit. El mío lo compré en Bricogeek. Implementa los tres comandos que ya conocemos: SAP, WR y RD y utiliza registros mapeados

Conexión a la Alhambra II

Este es el esquema de conexioando entre la Alhambra II y el sensor CAP1188. Además de conectar las señales del spi que ya conocemos: SCk, Mosi, Miso y SS, hay que alimentar el sensor con 5v, conectar GND y colocar sus entradas AD y RST a GND

Los cables al aire, que están conectados a C1, C5 y C8, son las tres entradas capacitivas que usaremos de prueba. Hay disponibles 8 en total, pero sólo usaremos 3 en los ejemplos. Este es el conexionado real

Comandos y registros

El CAP1188 implementa los 3 comandos que ya conocemos para aceder a los registros mapeados. De hecho, esos son los comandos que he usado de referencia para implementar el bloque esclavo spi-test-regs, con sus mismos códigos de comando y todo

Comando Abrev. Código Descripción
SET ADDRES POINTER val SAP 0x7D Establecer el valor del registro de dirección
WRITE REGISTER val WR 0x7E Escribir en el registro apuntado por el registro de dirección
READ REGISTER RD 0x7F Leer el registro apuntado por el registro de dirección

Esos 3 comandos nos permite acceder a cualquiera de los registros del sensor, para leerlos o escribirlos. Sólo hay que conocer su dirección. El sensor CAP1188 tiene muchos registros, pero en los ejemplos sólo utilizaremos los mostrados en esta tabla

Dir. R/W Nombre Función Valor por defecto
00h R/W Main Control Registro de control. El bit 0 se usa para limpiar el flag de interrupción 00h
03h R Sensor Input Status Estado de las entradas capacitivas 00h
2Ah R/W Multiple touch configuration Determina el número de pulsaciones simultaneas para disparar la condición de pulsación múltiple 80h
41h R/W Standby configuration Controla la actividad de los pulsadores en el estado de reposo 39h
74h R/W LED output control Establecer el estado de los LEDs 00h
FDh R Product ID Código del sensor 50h
FEh R Manufacturer ID Código del fabricante 5Dh
FFh R Revision Código de la versión 83h

Probando el sensor

Para probar el sensor CAP1188 usaremos en la FPGA el bloque serial-spi y enviaremos los comandos desde el PC, tanto desde la consola serie como desde programas en python

Circuito en FPGA

En la FPGA cargamos el siguiente circuito:

(09-serial-spi-cap1188.ice)

Funciones python

Para acceder a los registros desde los programas python primero implementamos las funciones sap, write y read que envíen los correspondientes comandos

##-- Comando: Set Address Pointer
def sap(sp, addr):

    #-- Construir el comando
    sec = [CMD_SAP, addr]

    #-- Enviar el comando
    sp.write(bytes(sec))

    #-- Leer los dos bytes recibios. Se ignoran
    eco = sp.read(2)

    #-- Esperar a que la señal ss se ponga a 1
    time.sleep(0.0002)


##-- Comando de escritura
def write(sp, value):
    #-- Construir el comando
    sec = [CMD_WR, value]

    #-- Enviar el comando
    sp.write(bytes(sec))

    #-- Leer los dos bytes recibios. Se ignoran
    eco = sp.read(2)

    #-- Esperar a que la señal ss se ponga a 1
    time.sleep(0.0002)


#--- Comando de lectura
def read(sp):
    #-- Construir el comando
    sec = [CMD_RD, 0x00]

    #-- Enviar el comando
    sp.write(bytes(sec))

    #-- Leer los dos bytes recibios. Se ignoran
    eco = sp.read(2)

    #-- Esperar a que la señal ss se ponga a 1
    time.sleep(0.0002)

    #-- Devolver el valor de la lectura
    return eco[1]

Con ellas implementamos las dos funciones principales de acceso a los registros: write_reg() para escribir un valor en un registro y read_reg() para leerlo

##-- Escritura en un registro
def write_reg(sp, reg, value):

    #-- Seleccionar registro
    sap(sp, reg)

    #-- Escribir valor
    write(sp, value)


#-- Lectura de un registro
def read_reg(sp, reg):
    #-- Seleccionar el registro
    sap(sp, reg)

    #-- Devolver la lectura
    return read(sp)

Para hacer nuestros programas de ejemplo sólo nos queda definir los códigos de los comandos y las direcciones de los registros

##-- Codigos de comando
CMD_SAP = 0x7D
CMD_RD = 0x7F
CMD_WR = 0x7E

##-- Registros
CAP1188_MAIN = 0x00
CAP1188_SENINPUTSTATUS = 0x03
CAP1188_MTBLK = 0x2A
CAP1188_STANDBYCFG = 0x41
CAP1188_LED_OUTPUT_CTRL = 0X74
CAP1188_PROID = 0xFD
CAP1188_MANUID = 0xFE
CAP1188_REV = 0xFF

Ejemplo 9: Lectura de registros

Para probar empezaremos leyendo los registros de identificación, en las direcciones 0xFD (ProductID), 0xFE (Manufacturer id) y 0xFF (REvision). Primero desde el terminal serie escribiendo los comandos a mano:

En este vídeo se muestra el proceso. Obtenemos los valores correctos: 0x50, 0x5D y 0x83

Click to see the youtube video

Esto mismo es lo que hace el siguiente programa python (sólo se muestra el bucle principal). Se accede a los tres registros anteriores y se muestran sus valores

(cap1188-01-lectura-ids.py)

#-- Abrir el puerto serie
with serial.Serial(SERIAL_PORT, 115200) as sp:

    #-- Imprimir la información del pueto serie
    print("Puerto serie: {}".format(sp.portstr))

    #-- Leer registros de identificacion
    proid = read_reg(sp, CAP1188_PROID)   #-- Producto
    manuid = read_reg(sp, CAP1188_MANUID) #-- Fabricante
    revid = read_reg(sp, CAP1188_REV)     #-- Revision
    print("PRODId: {:02X}".format(proid))
    print("MANUID: {:02X}".format(manuid))
    print("REVID: {:02X}".format(revid))

Este es el resultado al ejecutarlo:

Ejemplo 10: Accediendo a los LEDs

El CAP1188 tiene 8 LEDs mapeados en el registro de control de LEDs (dirección 0x74). Al escribir un valor en él se encenderán los LEDs. Esto es muy útil para hacer pruebas. Basta con enviar el comandos 0x7D 0x74 para seleccionar el registro y 0x7E 0xAA para escribir el valor 0xAA

Click to see the youtube video

Desde el bucle principal de este programa en python generamos una secuencia de dos estados en los LEDs, enviando cada medio segundo los valores 0xAA y 0x55

(cap1188-02-leds.py)

#-- Valor inicial para los LEDs
    value = 0xAA

    #-- Bucle principal. Se termina pulsando ctrl-c
    while True:

        #-- Escribir valor en los LEDs
        write_reg(sp, CAP1188_LED_OUTPUT_CTRL, value)

        #--- Esperar
        time.sleep(0.5)

        #-- Invertir el numero de los LEDs
        value = ~value & 0xFF;

En este vídeo se muestra el resultado

Click to see the youtube video

Ejemplo 11: Leyendo los pulsadores capacitivos

Para saber el estado de los pulsadores capacitivos hay que leer el registro de la dirección 0x03. Si hay alguno activado, el flag de interrupción se elimina escribiendo un 0 en el bit 0 del registro principal. Este ejemplo lee los pulsadores y muestra su estado en la consola y los LEDs

(cap1188-03-input-leds.py)

#------- Configuracion
    #-- Permitir el funcionamiento todas las entradas a la vez
    write_reg(sp, CAP1188_MTBLK, 0x00)
    #-- Aumentar la velocidad de muestreo
    write_reg(sp,CAP1188_STANDBYCFG, 0x30);

    #-- Bucle principal. Se termina pulsando ctrl-c
    while True:

        #-- Leer pulsadores capacitivos
        sens = read_reg(sp, CAP1188_SENINPUTSTATUS)

        #-- Mostrar su estado en la consola
        print("Sensores: {:02X}".format(sens))

        #-- Sacar por los leds su estado
        write_reg(sp, CAP1188_LED_OUTPUT_CTRL, sens)

        #-- Si hay alguno activo
        if sens:

            ##-- Leer el registro principal y escribir un 0
            ##-- en el bit 0, para borrar el flag de interrupcion
            main = read_reg(sp, CAP1188_MAIN)
            write_reg(sp, CAP1188_MAIN, (main & ~0x01) & 0xFF)

Antes de entrar en el bucle principal se configuran dos registros para permitir el funcionamiento de varios pulsadores a la vez y aumentar la velocidad (tomado del ejemplo de adafruit). En este vídeo se muestra el resultado de su ejecución

Click to see the youtube video

Funcionamiento interno del maestro SPI

La comunicación por SPI es serie síncrona. En los tutoriales 28 y 29 estudiamos los principios de su funcionamiento mediante ejemplos. Primero con biestables y luego usando registros. Veremos brevemente cómo funciona el módulo SPI-master por dentro

La señal de reloj la genera el componete SPI-Heart-2Mhz, disponible en Varios/SPI/Máster/parts/. Cada vez que llega un tic por start, se generan 8 pulsos, que permiten realizar las transaciones con datos de 8 bits

Su salida SCLK es la que se envía directamente al esclavo. Además, genera los tics up y down, para señalizar los flancos de subida y bajada respectivamente, ya que en ellos el maestro realiza actividades diferentes. Cuando se ha generado el pulso número 8, se emite un tic por done

El envío de los datos hacia el esclavo se hace en los flancos de bajada (tics-down) para que luego el esclavo los capture en los flancos de subida (modo 0). Esto se realiza mediante un registro de desplazamiento cuya señal de shift está activada por tics-down. Su salida serie se saca por MOSI

En el comienzo de la transacción, este registro se carga con el dato a enviar, al recibir el tic de start por su entrada load. Los bits recibidos del esclavo (miso) entran por la entrada serie y se van desplazando en cada flanco de bajada. Cuando llega el último bit, done se activa y el dato recibido se captura y se saca por data

Los bits que llegan del esclavo por MISO, se captura en un biestable en flanco de subida (porque así lo especifica el modo 0). La salida de este biestable es la que entra por la entrada serie del registro anterior, encargado de la transmisión y la recepción

Por último, la señal SS (opcional) se genera mediante un biestable RS inicializado a 1. Cuando comienza la transacción, se pone a 0 porque el tic de start entra por rst. Al terminar de enviar el último bit, se activa done que llega a set y lo pone a 1 de nuevo

El circuito completo del bloque spi-master es el mostrado en esta figura. Su funcionamiento es sencillo, y bastante intuitivo, ¿No? :-)

Conclusiones

  • Ya tenemos controlado el nivel físico del SPI, tanto a nivel de esclavo como de Maestro
  • Esto nos da acceso a usar sensores y periféricos SPI, para integrarlos en nuestros diseños hardware
  • Y por supuesto para crear nuestros propios periféricos y utilizarlos desde nuestros circuitos, modularizando los diseños
  • Para hacer pruebas de acceso a nuevos periféricos SPI es muy útil usar el bloque serial-SPI, y enviar comandos desde el PC, como paso previo a realizar específicos
  • Hemos aprendido un poco sobre la memoria flash SPI integrada en las placas Alhambra, Icestick y otras
  • Hemos hecho pruebas con el sensor capacitivo SPI CAP1188
  • Nuestro ecosistema de FPGAs libres ha evolucionado un poquito. Ahora podemos hacer algunas cositas más :-)
  • Todavía nos queda un largo camino por recorrer. ¡¡¡Vamos!!!

Descargas

Todos los ejemplos se pueden descargar de este repositorio

Autor

Licencia

Créditos y agradecimientos

Enlaces

Clone this wiki locally