-
Notifications
You must be signed in to change notification settings - Fork 13
CT.6: SPI maestro
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
- 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
- Fecha: 2019/Julio/6
Haz click en la imagen para ver el vídeo en Youtube
Haz click en la imagen para ver el vídeo en Youtube
- Colection-Jedi-v1.7.0.zip: Colección para este cuaderno técnico. Descargar e instalar esta versión o superior (>= 1.7.0)
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
- Introducción
- Bloques esclavos de pruebas: SPI-test
- El bloque Master SPI
- Midiendo el maestro SPI
- Puerto serie y SPI
- Memoria flash SPI
- Sensor capacitivo SPI CAP1188
- Funcionamiento interno del maestro SPI
- Conclusiones
- Descargas
- Autor
- Licencia
- Créditos y agradecimientos
- Enlaces
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
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
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
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
#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
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
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:
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
#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
Esto es lo que vemos en la consola serie del entorno de Arduino:
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
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
#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
En la consola serie del arduino vemos el estado de los pulsadores
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
Y esto es lo que sale por la consola serie del entorno de Arduino
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)
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 |
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:
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:
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
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!
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 |
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
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
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)
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
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
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)
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
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:
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
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
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)
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í:
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:
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:
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
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
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
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
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
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:
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
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:
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
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
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)
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
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 |
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
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
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
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
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
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 |
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
En la FPGA cargamos el siguiente circuito:
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
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
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
#-- 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:
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
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
#-- 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
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
#------- 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
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? :-)
- 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!!!
Todos los ejemplos se pueden descargar de este repositorio
- Juan González-Gómez (Obijuan)
-
Dibujo bloques del SPI: en:User:Cburnett - Trabajo propio. Este gráfico vectorial, sin especificar según el W3C, fue creado con Inkscape., CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=1476502
-
Dibujo Maestro SPI con varios esclavos: en:User:Cburnett - Own workThis W3C-unspecified vector image was created with Inkscape., CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=1476503
-
El bloque Máster está inspirado en el CORE SPI desarrollado por Salvador Tropea, del INTI. ¡Muchas gracias!
- SPI Máster core. Salvador Tropea. INTI
- sensor capacitivo CAP1188
- CAP1188 en BricoGeek
- Hola de dato de la memoria Flash N25Q032A
- Icestudio, Nigthly builds
- Tutorial de Electrónica digital para makers con FPGAs libres
- VideoBlog: Píldoras de conocimiento
- Jedi Collection
- SPI Transfer Modes
- Arduino SPI Library
- Unidad de PWM de frecuencia aproximada
- VGA Retro: Puesta en marcha. MonsterLED
- Pines de Entrada/Salida
- Control de LEDs
- SPI esclavo
- SPI Maestro
- Display SPI de 4 dígitos de 7 segmentos
- Entrada y Salida de Bits con Componentes Virtuales
- Memorias
- Entradas y pulsadores
- Señales del sistema. Medición con el LEDOscopio
- Controlador LCD 16x2
- Señales periódicas y temporización
- Buses: Medio compartido
- Memoria Flash SPI
- Conexión de LEDs en la Alhambra II. Placa AP‐LED8‐THT
- Periféricos PMOD
- Fundamentos. Sistema Unario
- Autómatas
- Pantallas de vídeo. Fundamentos. Display de 1x4 LEDs