STM32 SPI Master Mode a Livello di Registri: Baud Rate, CPOL/CPHA, NSS e Trasferimenti DMA su STM32F4

2026-06-07 · Davide Carrese
STM32 · SPI · DMA · STM32F4 · Registri

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:

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:

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 è:

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

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 è:

Fonti e riferimenti

Comments

Have comments? Send me an email.