Módulo 2.5

Protocolo SPI

Comunicación síncrona de alta velocidad con periféricos externos

Santi Scagliusi, PhD

Comenzar arrow_downward
sync_alt
4 hilos
SCLK, MOSI, MISO, CS
speed
Alta velocidad
Hasta 10+ MHz
swap_horiz
Full-duplex
TX y RX simultáneo
code
Zephyr API
spi_transceive_dt()

¿Qué es SPI?

Serial Peripheral Interface: comunicación síncrona de corta distancia a alta velocidad.

help ¿Por qué usar SPI?

SPI es ideal cuando necesitas velocidad y simplicidad. A diferencia de I2C, no hay direccionamiento por software - cada esclavo tiene su propia línea de selección.

  • check_circle Velocidades de 1-10+ MHz (vs 400 kHz de I2C)
  • check_circle Comunicación full-duplex simultánea
  • check_circle Sin overhead de direccionamiento
lightbulb
Analogía: SPI es como una conversación telefónica donde ambas partes pueden hablar al mismo tiempo (full-duplex), mientras que I2C es como un walkie-talkie donde solo uno habla a la vez (half-duplex).

Interfaz de 4 hilos

schedule
SCLK
Serial Clock - Reloj del master
arrow_forward
MOSI
Master Out, Slave In - Datos hacia esclavo
arrow_back
MISO
Master In, Slave Out - Datos desde esclavo
toggle_on
CS/SS
Chip Select - Selecciona esclavo (activo bajo)
warning
Activo bajo: CS = 0 selecciona el esclavo, CS = 1 lo desactiva. Cada esclavo necesita su propia línea CS.

Arquitectura Master/Slave

Múltiples esclavos en el mismo bus, cada uno con línea CS dedicada.

Topología del bus SPI

SCLK MOSI MISO MASTER (nRF52840) CS0 CS1 CS2 Slave 0 Flash Slave 1 Sensor Slave 2 Display Leyenda: Bus compartido CS individual
hub

Bus compartido

SCLK, MOSI y MISO son compartidos por todos los esclavos. Solo el seleccionado responde.

toggle_off

CS dedicado

Cada esclavo tiene su propia línea CS. Necesitas N pines GPIO para N esclavos.

swap_horiz

Full-duplex

MOSI y MISO permiten enviar y recibir datos simultáneamente en cada ciclo de reloj.

Transacción SPI

Diagrama de tiempos durante una transferencia de datos.

Transferencia de 8 bits (Modo 0)

CS SCLK MOSI MISO 1 0 1 0 0 1 0 1 0xA5 0x3C = Punto de muestreo
Secuencia de la transacción
1
CS baja
El master activa el esclavo
2
Transferencia de bits
8 ciclos de reloj = 1 byte
3
CS sube
Fin de la transacción
Orden de bits

Por defecto, SPI transmite el bit más significativo (MSB) primero:

7
6
5
4
3
2
1
0
Primero (MSB) Último (LSB)

Simulador SPI Interactivo

Recorre paso a paso una transferencia SPI completa. Observa cómo MOSI y MISO transmiten datos simultáneamente (full-duplex).

MOSI: 0xA5 (10100101)
MISO: 0x3C (00111100)
swap_horiz Full-Duplex
play_circle Simulador interactivo: Transferencia SPI (Modo 0)
Paso 0/11
CS
SCLK
MOSI
MISO
HIGH (1)
LOW (0)
Transición
Presiona "Siguiente paso" para comenzar
Recorrerás cada fase de una transferencia SPI completa: el Master envía 0xA5 por MOSI mientras el Slave responde 0x3C por MISO, simultáneamente.
school
Punto clave: A diferencia de I2C, SPI es full-duplex: por cada bit que el Master envía por MOSI, simultáneamente recibe un bit del Slave por MISO. La línea CS (Chip Select) activa (LOW) selecciona el esclavo antes de iniciar la transferencia.

Modos SPI (0-3)

CPOL y CPHA definen cuándo se muestrean y cambian los datos.

CPOL - Clock Polarity

CPOL=0 Reloj en reposo = LOW
CPOL=1 Reloj en reposo = HIGH

CPHA - Clock Phase

CPHA=0 Muestreo en primer flanco
CPHA=1 Muestreo en segundo flanco
0
CPOL=0, CPHA=0
Idle LOW
Sample: flanco subida
Más común
1
CPOL=0, CPHA=1
Idle LOW
Sample: flanco bajada
2
CPOL=1, CPHA=0
Idle HIGH
Sample: flanco bajada
3
CPOL=1, CPHA=1
Idle HIGH
Sample: flanco subida

Comparación visual de modos

Modo 0 Sample en flanco subida Modo 1 Sample en flanco bajada Modo 2 Sample en flanco bajada (idle HIGH) Modo 3 Sample en flanco subida (idle HIGH)
warning
Importante: El modo SPI debe coincidir entre master y slave. Consulta siempre el datasheet del periférico para saber qué modo requiere.

SPI vs I2C

¿Cuándo usar cada protocolo?

Característica SPI I2C
Velocidad máxima 10+ MHz 400 kHz (FM) / 3.4 MHz (HS)
Número de hilos 4 (+ 1 CS por esclavo) 2 (SDA + SCL)
Direccionamiento Por línea CS (hardware) 7/10 bits (software)
Comunicación Full-duplex Half-duplex
Acknowledgment No Sí (ACK/NACK)
Multi-master Complejo Soportado
Distancia máxima Corta (~cm) Corta (~m con buffers)
Uso típico Flash, displays, ADCs rápidos Sensores, EEPROMs, RTCs

bolt Usa SPI cuando necesites:

  • check Alta velocidad de transferencia
  • check Comunicación full-duplex
  • check Pocos esclavos y pines disponibles
  • check Memorias Flash, displays LCD/OLED

hub Usa I2C cuando necesites:

  • check Muchos dispositivos con pocos pines
  • check Confirmación de recepción (ACK)
  • check Hot-swapping de dispositivos
  • check Sensores, RTCs, EEPROMs pequeñas

Implementaciones SPI en Nordic

El nRF52840 ofrece tres variantes del periférico SPI.

memory

SPI

Básico

Periférico SPI básico con CPU polling o interrupciones.

  • - Transferencia byte a byte
  • - Mayor uso de CPU
  • - Menor complejidad
speed

SPIM

EasyDMA

SPI Master con EasyDMA para transferencias eficientes.

  • - Transferencia automática
  • - CPU libre durante TX/RX
  • - Recomendado para Zephyr
input

SPIS

Slave

SPI Slave para cuando el nRF actúa como periférico.

  • - nRF como esclavo
  • - Responde a master externo
  • - Uso especial (bridges)
Especificaciones SPI nRF52840
Instancias SPIM 4 (SPIM0-SPIM3)
Frecuencia máxima 8 MHz (SPIM3: 32 MHz)
Tamaño buffer DMA Hasta 255 bytes
Modos soportados 0, 1, 2, 3
check_circle
Recomendación: Usa siempre SPIM (con EasyDMA) en Zephyr. La API de Zephyr lo configura automáticamente con compatible = "nordic,nrf-spim".

API SPI de Zephyr

Configuración y estructuras básicas para comunicación SPI.

1. Configuración en prj.conf

prj.conf
CONFIG_SPI=y
CONFIG_LOG=y

2. Estructura spi_dt_spec

Definición de la estructura
struct spi_dt_spec {
const struct device *bus; // Controlador SPI
struct spi_config config; // Configuración
};
/* spi_config incluye: */
// - frequency (velocidad)
// - operation (modo, word size)
// - cs (chip select GPIO)

3. Estructuras de buffer

spi_buf - Buffer individual
struct spi_buf {
void *buf; // Puntero a datos
size_t len; // Tamaño en bytes
};
spi_buf_set - Conjunto de buffers
struct spi_buf_set {
const struct spi_buf *buffers;
size_t count; // Número de buffers
};
settings
Flags de operación comunes:
SPI_WORD_SET(8) - 8 bits por palabra
SPI_TRANSFER_MSB - MSB primero
SPI_OP_MODE_MASTER - Modo master

Funciones SPI de Zephyr

Funciones principales para operaciones de lectura, escritura y transferencia.

Funciones principales

SPI_DT_SPEC_GET(node, op, delay)
Obtiene spec desde devicetree con flags de operación.
spi_is_ready_dt(spec)
Verifica si el dispositivo SPI está listo.
spi_read_dt(spec, rx_bufs)
Lee datos del esclavo SPI.
spi_write_dt(spec, tx_bufs)
Escribe datos al esclavo SPI.
spi_transceive_dt(spec, tx, rx)
Transmite y recibe simultáneamente (full-duplex).

Ejemplo de uso basico

Patron tipico
/* 1. Definir operación */
#define SPI_OP (SPI_WORD_SET(8) | \
SPI_TRANSFER_MSB)
/* 2. Obtener spec */
static const struct spi_dt_spec dev =
SPI_DT_SPEC_GET(node, SPI_OP, 0);
/* 3. Verificar */
if (!spi_is_ready_dt(&dev)) {
return -ENODEV;
}
/* 4. Transferir */
spi_transceive_dt(&dev, &tx, &rx);
check_circle
Tip: spi_transceive_dt es la funcion mas versatil - permite enviar y recibir datos en una sola llamada.

Devicetree para SPI

Configuración del hardware SPI mediante overlays.

Configuración del controlador SPI

app.overlay
&spi1 {
compatible = "nordic,nrf-spim";
status = "okay";
/* GPIO para Chip Select */
cs-gpios = <&gpio0 17 GPIO_ACTIVE_LOW>;
/* Configuración de pines */
pinctrl-0 = <&spi1_default>;
pinctrl-1 = <&spi1_sleep>;
pinctrl-names = "default", "sleep";
/* Nodo del dispositivo esclavo */
bme280: bme280@0 {
compatible = "bosch,bme280";
reg = <0>; // Índice del CS
spi-max-frequency = <1000000>;
};
};

Propiedades importantes

cs-gpios
Lista de GPIOs para Chip Select. El índice corresponde a reg del esclavo.
reg
Índice del CS en la lista cs-gpios (0, 1, 2...).
spi-max-frequency
Velocidad máxima en Hz que soporta el esclavo.
lightbulb
Múltiples esclavos: Añade más entradas en cs-gpios y más nodos hijos con diferentes valores de reg.
warning
GPIO_ACTIVE_LOW: La mayoría de dispositivos SPI usan CS activo bajo. Verifica el datasheet de tu periférico.

Ejemplo: Lectura BME280 (Setup)

Configuracion inicial y funcion de lectura de registros SPI.

Includes y configuracion

src/main.c
#include <zephyr/drivers/spi.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(spi_demo, LOG_LEVEL_INF);
/* Flags de operación SPI */
#define SPI_OP (SPI_WORD_SET(8) | \
SPI_TRANSFER_MSB | \
SPI_OP_MODE_MASTER)
/* Obtener spec desde DT */
static const struct spi_dt_spec spi_dev =
SPI_DT_SPEC_GET(DT_NODELABEL(bme280),
SPI_OP, 0);

Desglose de la configuracion

1
Headers
spi.h para la API, log.h para debug
2
SPI_OP flags
8 bits, MSB first, modo master
3
SPI_DT_SPEC_GET
Obtiene config desde devicetree
lightbulb
DT_NODELABEL: Referencia al nodo bme280 definido en el overlay del devicetree.

Funcion read_register()

Implementacion de lectura de registros via SPI.

src/main.c - read_register()
int read_register(uint8_t reg,
uint8_t *data, size_t len) {
// MSB=1 indica lectura
uint8_t tx_data = reg | 0x80;
struct spi_buf tx_buf = {
.buf = &tx_data,
.len = 1
};
struct spi_buf_set tx_bufs = {
.buffers = &tx_buf,
.count = 1
};
/* Buffer de recepcion */
struct spi_buf rx_buf = {
.buf = data, .len = len
};
struct spi_buf_set rx_bufs = {
.buffers = &rx_buf, .count = 1
};
return spi_transceive_dt(&spi_dev,
&tx_bufs, &rx_bufs);
}

Flujo de la transaccion

1
reg | 0x80
Bit 7 = 1 indica operacion de lectura
2
spi_buf TX
Buffer con direccion del registro
3
spi_buf RX
Buffer para datos recibidos
4
spi_transceive_dt
Envia TX, recibe RX simultaneamente
warning
Protocolo BME280: El bit 7 del primer byte determina R/W. Otros sensores pueden usar convencion diferente.

Funcion main()

Uso de la funcion read_register para leer el ID del chip.

src/main.c - main()
int main(void) {
uint8_t chip_id;
int ret;
/* Verificar dispositivo */
if (!spi_is_ready_dt(&spi_dev)) {
LOG_ERR("SPI not ready");
return -1;
}
/* Leer ID del chip (reg 0xD0) */
ret = read_register(0xD0, &chip_id, 1);
if (ret == 0) {
LOG_INF("BME280 ID: 0x%02x", chip_id);
// Esperado: 0x60
} else {
LOG_ERR("Read failed: %d", ret);
}
return 0;
}

Verificacion del sensor

Registro ID (0xD0)
BME280 0x60
BMP280 0x58
check_circle
Burst read: Para leer multiples registros consecutivos, aumenta len en el buffer RX. El BME280 auto-incrementa la direccion.
terminal
Output esperado:
*** Booting Zephyr OS ***
[00:00:00.001,000] <inf> spi_demo: BME280 ID: 0x60

Resumen del Módulo

Conceptos clave sobre el protocolo SPI en sistemas embebidos NRF.

sync_alt

4 señales

SCLK (reloj), MOSI (master to slave), MISO (slave to master), CS (chip select activo bajo).

tune

Modos 0-3

CPOL define polaridad del reloj, CPHA define flanco de muestreo. Modo 0 (CPOL=0, CPHA=0) es el más común.

code

Zephyr API

SPI_DT_SPEC_GET para config, spi_is_ready_dt para validar, spi_transceive_dt para comunicación full-duplex.

Referencia rápida

Diferencias clave SPI vs I2C
Velocidad SPI: 10+ MHz vs I2C: 400 kHz
Hilos SPI: 4+ vs I2C: 2
Duplex SPI: Full vs I2C: Half
Funciones Zephyr
Obtener spec SPI_DT_SPEC_GET()
Verificar spi_is_ready_dt()
Transferir spi_transceive_dt()
tips_and_updates

"SPI es el bus preferido cuando la velocidad importa y los pines no son un recurso escaso."

Memorias Flash, displays, ADCs de alta velocidad, y cualquier periférico que requiera transferencias rápidas de datos se beneficia de SPI.

arrow_forward Siguiente módulo

2.6 Modelo de Drivers en Zephyr

Arquitectura de drivers y cómo extender el sistema operativo.