STM32 SPI Master Mode a Livello di Registri: Baud Rate, CPOL/CPHA, NSS e Trasferimenti DMA su STM32F4
SPI è il bus periferico di fatto per interfacciare sensori, ADC, DAC, display, schede SD e moduli wireless nei sistemi embedded. Su STM32F4, il periferico SPI è abbastanza flessibile da gestire qualsiasi cosa, da un sensore di temperatura a bassa frequenza (125 kHz) a un controller per display in streaming a 21 MHz — ma solo se si conoscono le impostazioni a livello di registri. Questo articolo copre la configurazione master SPI, polarità/fase del clock (CPOL/CPHA), selezione del prescaler del baud rate, gestione software NSS, modalità half-duplex bidirezionale, CRC e trasferimenti full-duplex basati su DMA su STM32F401.
Architettura SPI su STM32F4
Lo STM32F401 ha fino a tre interfacce SPI. SPI1 è su APB2 (max 84 MHz), mentre SPI2 e SPI3 sono su APB1 (max 42 MHz). Ogni SPI può operare in modalità master o slave, supporta comunicazione full-duplex, half-duplex e simplex, e ha un formato dati indipendente a 8 o 16 bit.
I registri principali da conoscere:
SPI_CR1— selezione master/slave (MSTR), baud rate (BR[2:0]), polarità clock (CPOL), fase clock (CPHA), formato dati (DFF), NSS software (SSM+SSI), half-duplex (BIDIMODE/BIDIOE) e abilitazione (SPE).SPI_CR2— abilitazione DMA (TXDMAEN,RXDMAEN), abilitazione interrupt (TXEIE,RXNEIE,ERRIE), uscita SS (SSOE) e soglia FIFO (FRXTH).SPI_SR— flag di stato: TXE (buffer di trasmissione vuoto), RXNE (buffer di ricezione non vuoto), BSY (occupato), MODF (errore di modo), CRCERR, OVR (overrun).SPI_DR— registro dati, largo 16 bit. Scrivere trasmette; leggere riceve.SPI_CRCPR— registro del polinomio CRC (se CRC abilitato).
Su F401, SPI1, SPI2 e SPI3 non hanno una FIFO profonda — usano un buffer dati a parola singola con i flag TXE/RXNE. Famiglie STM32 più recenti come G4 o H7 hanno aggiunto FIFO a 32 bit, ma l'SPI dell'F4 è puramente a livello di registri con un buffer a parola singola.
Selezione del prescaler del baud rate
Il clock SPI si genera dividendo il clock periferico (PCLK) per un prescaler potenza di due configurato in SPI_CR1[BR]:
SPI_CR1_BR_0 → fPCLK / 2 SPI_CR1_BR_1 → fPCLK / 4 SPI_CR1_BR_0 | SPI_CR1_BR_1 → fPCLK / 8 SPI_CR1_BR_2 → fPCLK / 16 SPI_CR1_BR_2 | SPI_CR1_BR_0 → fPCLK / 32 SPI_CR1_BR_2 | SPI_CR1_BR_1 → fPCLK / 64 SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0 → fPCLK / 256
Se SPI1 è su APB2 a 84 MHz, il clock SPI massimo è 84/2 = 42 MHz, ma l'SPI dell'F401 è limitato a fPCLK/2 in modalità master. In pratica, il clock SPI massimo affidabile per F401 è circa 21 MHz (prescaler /4) a 3,3 V. La velocità effettiva dipende anche dal layout della scheda, dalla lunghezza delle tracce e dalla capacità del dispositivo slave — controllare sempre il datasheet dello slave.
Esempio pratico: scelta del baud rate per un ADC SPI a 10 MHz
/* SPI1 su APB2 = 84 MHz. Obiettivo: clock SPI 10 MHz */ /* 84 MHz / 8 = 10,5 MHz (il più vicino a 10 MHz) */ #define SPI_BAUD_10MHZ (SPI_CR1_BR_0 | SPI_CR1_BR_1) /* /8 */ /* Obiettivo: clock SPI 2 MHz per una scheda SD */ /* 84 MHz / 64 = 1,3125 MHz — troppo lento */ /* Usare SPI2 su APB1 = 42 MHz: 42 / 16 = 2,625 MHz */ #define SPI_BAUD_2MHZ_APB1 (SPI_CR1_BR_2) /* /16 su APB1 */
Misurare sempre la frequenza SCK effettiva con un oscilloscopio o un analizzatore logico nella propria configurazione. La capacità parassita della scheda e la forza del driver possono ridurre la frequenza effettiva rispetto al calcolo del prescaler.
CPOL e CPHA: polarità e fase del clock
Questi due bit definiscono lo stato di idle del clock SPI e il fronte di campionamento dei dati. Esistono quattro modalità, e il dispositivo slave deve usare la stessa modalità del master:
- Modo 0 (CPOL=0, CPHA=0): SCK idle basso, dati campionati sul fronte di salita, trasferiti sul fronte di discesa.
- Modo 1 (CPOL=0, CPHA=1): SCK idle basso, dati campionati sul fronte di discesa, trasferiti sul fronte di salita.
- Modo 2 (CPOL=1, CPHA=0): SCK idle alto, dati campionati sul fronte di discesa, trasferiti sul fronte di salita.
- Modo 3 (CPOL=1, CPHA=1): SCK idle alto, dati campionati sul fronte di salita, trasferiti sul fronte di discesa.
I modi 0 e 3 sono i più comuni in pratica. La maggior parte dei sensori di temperatura, ADC e MEMS usa il Modo 0. Alcuni controller per display e moduli RF usano il Modo 3. Controllare sempre il datasheet dello slave.
/* SPI Modo 0 (CPOL=0, CPHA=0) — il più comune per sensori */ #define SPI_MODE_0 (0) /* SPI Modo 3 (CPOL=1, CPHA=1) — comune per display, RF */ #define SPI_MODE_3 (SPI_CR1_CPOL | SPI_CR1_CPHA)
Gestione NSS: hardware vs software
Il pin NSS (Slave Select) può essere gestito via hardware o software. In modalità master con NSS software, si impostano SSM=1 e SSI=1 in SPI_CR1. Il periferico SPI ignora il pin NSS fisico e genera internamente il segnale di selezione slave. Questo è l'approccio più semplice e comune per un bus master singolo: si guida un GPIO separato come chip select per ogni dispositivo slave.
/* Abilitazione NSS software per modalità master */
SPI1->CR1 |= SPI_CR1_SSM | SPI_CR1_SSI;
/* CS pilotato come GPIO normale */
#define CS_PIN GPIO_PIN_4
#define CS_PORT GPIOA
void cs_select(void) { HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET); }
void cs_deselect(void) { HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET); }
Con NSS hardware (SSOE=1 in SPI_CR2), il periferico SPI porta il pin NSS basso automaticamente all'inizio di un trasferimento. Funziona solo per un singolo dispositivo slave e richiede il mapping corretto dei pin. Per più slave, usare NSS software + chip select GPIO manuali.
Full-duplex master in polling
Un trasferimento full-duplex base in polling scrive in SPI_DR e attende RXNE. Poiché SPI è un protocollo a registro a scorrimento, ogni byte trasmesso riceve simultaneamente un byte dallo slave:
static uint8_t spi_transfer_byte(SPI_TypeDef *spi, uint8_t tx)
{
/* Attesa TXE (buffer di trasmissione vuoto) */
while (!(spi->SR & SPI_SR_TXE));
spi->DR = tx;
/* Attesa RXNE (buffer di ricezione non vuoto) */
while (!(spi->SR & SPI_SR_RXNE));
return (uint8_t)spi->DR;
}
Per trasferimenti multi-byte, non usare mai polling per buffer grandi — blocca la CPU per l'intera durata del trasferimento. A 10 MHz, un trasferimento da 1 KB richiede ~820 µs, un'eternità in un sistema real-time.
Trasferimento full-duplex con DMA
L'SPI dello STM32F4 può usare il DMA sia per TX che per RX simultaneamente. L'SPI genera una richiesta DMA ad ogni evento TXE e RXNE. Servono due stream DMA — uno per TX e uno per RX — configurati in modalità circolare o normale a seconda del caso d'uso.
Per SPI1 su STM32F401, il mapping DMA è:
- SPI1_RX: DMA2 Stream 0, Canale 3
- SPI1_TX: DMA2 Stream 3, Canale 3
Configurazione DMA per un trasferimento full-duplex one-shot
void spi1_dma_fullduplex(uint8_t *txbuf, uint8_t *rxbuf, uint16_t len)
{
/* 1. Abilitazione richieste DMA SPI1 */
SPI1->CR2 |= SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN;
/* 2. Configurazione stream RX (DMA2 Stream 0, Canale 3) */
DMA2_Stream0->CR = 0; /* reset */
DMA2_Stream0->NDTR = len;
DMA2_Stream0->PAR = (uint32_t)&SPI1->DR;
DMA2_Stream0->M0AR = (uint32_t)rxbuf;
DMA2_Stream0->CR = DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 /* ch 3 */
| DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 /* 8-bit */
| DMA_SxCR_MINC | DMA_SxCR_TCIE; /* inc + irq */
/* 3. Configurazione stream TX (DMA2 Stream 3, Canale 3) */
DMA2_Stream3->CR = 0;
DMA2_Stream3->NDTR = len;
DMA2_Stream3->PAR = (uint32_t)&SPI1->DR;
DMA2_Stream3->M0AR = (uint32_t)txbuf;
DMA2_Stream3->CR = DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 /* ch 3 */
| DMA_SxCR_MSIZE_0 | DMA_SxCR_PSIZE_0 /* 8-bit */
| DMA_SxCR_MINC | DMA_SxCR_DIR_0; /* mem-to-per */
/* 4. Abilitazione stream */
DMA2_Stream0->CR |= DMA_SxCR_EN;
DMA2_Stream3->CR |= DMA_SxCR_EN;
}
Lo stream RX dovrebbe avere priorità più alta o essere avviato per primo, perché il primo evento TXE attiva lo stream TX e l'SPI inizia a trasferire immediatamente. Se lo stream RX non è pronto, il primo byte ricevuto va in overrun.
Per l'interrupt di completamento DMA, abilitare TCIE (Transfer Complete Interrupt Enable) sullo stream RX. Quando il contatore NDTR dello stream RX arriva a zero, scatta il flag TCIF. A quel punto si possono disabilitare le richieste DMA dell'SPI e deselezionare il pin CS:
void DMA2_Stream0_IRQHandler(void)
{
if (DMA2->LISR & DMA_LISR_TCIF0) {
DMA2->LIFCR |= DMA_LIFCR_CTCIF0;
SPI1->CR2 &= ~(SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN);
DMA2_Stream0->CR &= ~DMA_SxCR_EN;
DMA2_Stream3->CR &= ~DMA_SxCR_EN;
cs_deselect();
/* Segnala completamento al main loop o al task RTOS */
}
}
Modalità half-duplex bidirezionale
Alcuni slave sono solo trasmissione (es. sensore di temperatura digitale) o solo ricezione (es. DAC). Per questi casi, si può usare l'SPI in modalità half-duplex (BIDIMODE=1) bidirezionale con una sola linea dati (MISO/MOSI) condivisa. Impostare BIDIOE=1 per emettere dati, o BIDIOE=0 per ricevere.
Questo è utile quando si vuole liberare il GPIO normalmente usato dal pin dati inutilizzato. Il baud rate e il protocollo sono gli stessi, ma è necessario commutare BIDIOE tra le fasi di scrittura e lettura se il protocollo slave alterna le direzioni.
CRC su SPI
L'SPI dello STM32F4 supporta la generazione e verifica hardware CRC quando CRCEN=1 in SPI_CR1. Il CRC viene calcolato sui dati TX e aggiunto dopo l'ultimo byte. Il lato RX genera il proprio CRC e lo confronta. In caso di mancata corrispondenza, CRCERR viene impostato in SPI_SR.
In pratica, il CRC hardware SPI è raramente usato in modalità master su bus multi-slave perché aumenta l'overhead del protocollo e richiede che entrambi i lati concordino sul polinomio. Per trasferimenti affidabili su bus rumorosi, la maggior parte dei progettisti preferisce un CRC o checksum a più alto livello nel protocollo applicativo, o un bus differenziale come RS-485 con SPI esteso su brevi distanze.
Esempio pratico: lettura di un sensore di temperatura SPI (MAX31855 o MCP3564)
Ecco un'inizializzazione master completa a livello di registri per SPI1 su STM32F401 che comunica con un sensore SPI generico a 5,25 MHz, Modo 0, 8 bit, con trasferimento DMA one-shot:
void spi1_master_init(void)
{
/* Abilitazione clock: SPI1, GPIOA (SCK, MOSI, MISO, CS), DMA2 */
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
/* GPIO: PA5=SCK, PA7=MOSI, PA6=MISO, PA4=CS */
GPIOA->MODER &= ~(0xFu << 8); /* PA5,PA6,PA7: AF */
GPIOA->MODER |= (0x2u << 8) | (0x2u << 12) | (0x2u << 14);
GPIOA->MODER &= ~(0x3u << 8); /* PA4: output */
GPIOA->MODER |= (0x1u << 8);
GPIOA->AFR[0] |= (0x5u << 20) | (0x5u << 24) | (0x5u << 28); /* AF5 */
GPIOA->OSPEEDR |= 0x3F00; /* alta velocità */
cs_deselect();
/* SPI1: master, /16 → 84/16 = 5,25 MHz, Modo 0, 8 bit, SSM */
SPI1->CR1 = SPI_CR1_MSTR
| SPI_CR1_BR_2 /* /16 */
| SPI_CR1_SSM | SPI_CR1_SSI /* NSS software */
| SPI_CR1_SPE; /* abilitazione */
SPI1->CR2 = 0; /* nessun interrupt, nessun DMA ancora */
}
Lettura del sensore con trasferimento DMA one-shot:
uint8_t tx_buf[4] = {0x00, 0x00, 0x00, 0x00}; /* byte dummy per generare clock */
uint8_t rx_buf[4] = {0};
cs_select();
spi1_dma_fullduplex(tx_buf, rx_buf, 4);
/* Attesa interrupt DMA completamento */
/* rx_buf ora contiene i 4 byte di lettura del sensore */
Checklist pratica per SPI master mode
- Clock periferico: confermare su quale APB si trova l'istanza SPI e che il PCLK sia quello atteso. SPI1 su APB2 può clockare molto più velocemente di SPI2/SPI3 su APB1.
- Alternate function GPIO: verificare il mapping AF nel datasheet. STM32F401 usa AF5 per SPI1 e AF5 per SPI2/SPI3. AF mal mappate sono la causa #1 di SPI non funzionante.
- Velocità di uscita: impostare OSPEEDR ad alta velocità su SCK, MOSI e MISO. Impostazioni a bassa velocità causano fronti SCK distorti sopra ~5 MHz.
- Pull-up: MISO non necessita di pull-up (pilotato dallo slave). MOSI e SCK dovrebbero usare pull-down deboli o essere configurati dall'applicazione.
- Priorità stream DMA: impostare lo stream RX a priorità più alta del TX per evitare overrun RX sul primo byte.
- Temporizzazione CS: alcuni slave richiedono un ritardo minimo tra CS basso e primo fronte SCK. Inserire qualche NOP o un
udelay()dopo aver assertato CS. - Flag BSY: dopo l'ultimo byte, attendere BSY=0 prima di de-assertare CS. Questo garantisce che l'ultimo bit sia stato trasmesso.
- Flag OVR: dopo ogni trasferimento, cancellare il bit OVR leggendo
SPI_SRpoiSPI_DR. Un OVR non cancellato blocca l'SPI.
Come lo affronterei su un progetto cliente
Su un progetto cliente, non uso mai SPI in polling per più di 4 byte. Il tempo CPU è troppo prezioso. L'approccio standard è:
- Scrivere un'abstrazione SPI basata su DMA che accetta una coppia di buffer, una lunghezza e una callback. Il periferico SPI + DMA lavora in background mentre la CPU gestisce altri task o entra in modalità low-power.
- Per bus multi-slave, mantenere un context struct per pin CS che memorizzi l'istanza SPI, gli handle degli stream DMA e un semaforo di completamento (o flag).
- Usare NSS hardware + SSOE solo per progetti a chip singolo. Per qualsiasi scheda con più slave SPI, usare NSS software con CS via GPIO — questo dà controllo totale sulla temporizzazione CS e permette di inserire ritardi tra slave diversi.
- Validare il bus SPI con un dispositivo noto o un test di loopback (collegare MOSI a MISO esternamente) prima di connettere lo slave reale. Un loopback a 8 bit, Modo 0, scrivendo 0xAA dovrebbe restituire 0xAA.
- Documentare la frequenza SCK massima dopo averla misurata sul PCB reale — non fidarsi solo del calcolo del prescaler. La capacità della scheda e la forza del driver contano.
Fonti e riferimenti
- STM32F401 Reference Manual (RM0368) — capitolo SPI
- STM32F40x/41x Reference Manual (RM0090) — capitoli SPI e DMA
- Pacchetto firmware STM32CubeF4 — esempi HAL SPI in
Projects/STM32F401RE-Nucleo/Examples/SPI/ - AN4031: STM32™ SPI communication application note
- AN4488: Getting started with STM32F4xxxx MCU hardware development

Comments
Have comments? Send me an email.