STM32 USART Comunicazione Interrupt-Driven — Configurazione a Registri su STM32F4
La UART è la periferica più sottovalutata nei sistemi embedded — fino a quando non inizia a perdere byte, corrompere frame o bloccarsi con un errore di overrun. Quando serve una comunicazione interrupt-driven affidabile senza l'overhead dell'HAL, la USART a livello di registri su STM32F4 è sorprendentemente semplice, a patto di gestire correttamente quattro flag e un dettaglio di temporizzazione.
Ho debuggato più problemi di UART su progetti client che qualsiasi altra periferica: overrun silenzioso su un ricevitore GNSS che perdeva un byte ogni 30 secondi a 115200 baud, errori di framing su un bus RS-485 mal configurato, e una USART che sembrava funzionare ma emetteva il primo byte di ogni messaggio al baud rate sbagliato perché l'interrupt TXE scattava prima che il BRR fosse completamente stabile. Tutti questi problemi erano invisibili a livello HAL e trivialmente diagnosticabili leggendo il registro SR.
Questo articolo illustra l'inizializzazione a registri della USART su STM32F401/STM32F4, implementa TX interrupt-driven con un ring buffer, aggiunge RX robusto con recupero da overrun, e mostra il pattern su un modulo GPS NMEA reale.
Mappa dei registri USART su STM32F4
Lo STM32F401 ha fino a tre USART: USART1 su APB2 (fino a 84 MHz), USART2 e USART3 su APB1 (fino a 42 MHz). I registri che contano per la comunicazione asincrona standard:
| Offset | Registro | Scopo |
|---|---|---|
| 0x00 | SR | Stato — TXE, TC, RXNE, IDLE, ORE, NE, FE, PE |
| 0x04 | DR | Dato — 9 bit; scrivi per TX, leggi per RX |
| 0x08 | BRR | Baud rate — DIV_Mantissa [15:4] + DIV_Fraction [3:0] |
| 0x0C | CR1 | Controllo 1 — UE, TE, RE, TXEIE, TCIE, RXNEIE, IDLEIE, M, PCE, PS, OVER8 |
| 0x10 | CR2 | Controllo 2 — STOP bits, LINEN, CLKEN, CPOL/CPHA (modo sincrono) |
| 0x14 | CR3 | Controllo 3 — EIE, DMAR, DMAT, RTSE, CTSE (controllo flusso) |
| 0x1C | GTPR | Tempo di guardia e prescaler (solo IrDA / Smartcard) |
Generazione del baud rate: la trappola del BRR
La formula del baud rate dipende dal bit OVER8 in CR1:
OVER8 = 0 (default, 16× sovracampionamento): baud = f_CK / (16 × USARTDIV) OVER8 = 1 (8× sovracampionamento, baud massimo più alto): baud = f_CK / (8 × USARTDIV)
Per 115200 baud su USART1 (APB2 = 84 MHz) con sovracampionamento 16×:
USARTDIV = 84 MHz / (16 × 115200) = 45,5729... DIV_Mantissa = 45 (0x2D) DIV_Fraction = 0,5729 × 16 = 9,166 → 9 (0x9) BRR = (45 << 4) | 9 = 0x2D9 Baud effettivo = 84 MHz / (16 × (45 + 9/16)) = 84 MHz / 729 = 115.226 Errore = (115226 - 115200) / 115200 = +0,023% — ben entro il ±2%.
La trappola: se imposti OVER8=1 ma usi il calcolo BRR per sovracampionamento 16×, il baud rate sarà quasi esattamente il doppio di quanto ti aspetti. Il datasheet (RM0368 §19.3.4) è chiaro, ma è facile perderlo quando si porta codice tra famiglie STM32.
Funzione di inizializzazione a registri
#include "stm32f4xx.h"
#define USART1_BAUD_115200 0x2D9
void usart1_init(void) {
RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
__DSB();
/* PA9 (TX) come alternate function push-pull */
GPIOA->MODER &= ~(3U << 18);
GPIOA->MODER |= (2U << 18);
GPIOA->AFR[1] &= ~(0xF << 4);
GPIOA->AFR[1] |= (7U << 4); /* AF7 = USART1_TX */
GPIOA->OSPEEDR |= (3U << 18);
/* PA10 (RX) come alternate function input */
GPIOA->MODER &= ~(3U << 20);
GPIOA->MODER |= (2U << 20);
GPIOA->AFR[1] &= ~(0xF << 8);
GPIOA->AFR[1] |= (7U << 8); /* AF7 = USART1_RX */
GPIOA->PUPDR &= ~(3U << 20);
GPIOA->PUPDR |= (1U << 20); /* Pull-up su RX */
USART1->CR1 = 0;
USART1->BRR = USART1_BAUD_115200;
USART1->CR2 = 0;
USART1->CR3 = 0;
USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE
| USART_CR1_RXNEIE;
}
Mantengo RXNEIE sempre abilitato. TXEIE viene attivato solo quando ci sono dati da inviare e disattivato quando il ring buffer si svuota.
TX interrupt-driven con ring buffer
#define TX_BUF_SIZE 256
static volatile uint8_t tx_buf[TX_BUF_SIZE];
static volatile uint16_t tx_head = 0;
static volatile uint16_t tx_tail = 0;
static inline uint16_t tx_next(uint16_t i) {
return (i + 1) & (TX_BUF_SIZE - 1);
}
void usart1_send(const uint8_t *data, uint16_t len) {
for (uint16_t i = 0; i < len; i++) {
uint16_t next_head = tx_next(tx_head);
while (next_head == tx_tail);
tx_buf[tx_head] = data[i];
tx_head = next_head;
}
USART1->CR1 |= USART_CR1_TXEIE;
}
L'ISR invia il byte in tx_tail e avanza. Quando il ring si svuota, disabilita TXEIE:
void USART1_IRQHandler(void) {
if ((USART1->SR & USART_SR_TXE) && (USART1->CR1 & USART_CR1_TXEIE)) {
if (tx_head != tx_tail) {
USART1->DR = tx_buf[tx_tail] & 0xFF;
tx_tail = tx_next(tx_tail);
} else {
USART1->CR1 &= ~USART_CR1_TXEIE;
}
}
if (USART1->SR & USART_SR_RXNE) {
uint8_t byte = USART1->DR & 0xFF;
rx_put(byte);
}
if (USART1->SR & (USART_SR_ORE | USART_SR_FE | USART_SR_NE)) {
uint8_t dummy = USART1->DR;
(void)dummy;
error_counter++;
}
}
RX ring buffer e parsing line-based
#define RX_BUF_SIZE 512
static volatile uint8_t rx_buf[RX_BUF_SIZE];
static volatile uint16_t rx_head = 0;
static volatile uint16_t rx_tail = 0;
static void rx_put(uint8_t byte) {
uint16_t next = (rx_head + 1) & (RX_BUF_SIZE - 1);
if (next != rx_tail) {
rx_buf[rx_head] = byte;
rx_head = next;
} else {
rx_overflow++;
}
}
bool usart1_get_byte(uint8_t *byte) {
if (rx_tail == rx_head) return false;
*byte = rx_buf[rx_tail];
rx_tail = (rx_tail + 1) & (RX_BUF_SIZE - 1);
return true;
}
void process_nmea(void) {
uint8_t byte;
static char line[128];
static uint8_t idx = 0;
while (usart1_get_byte(&byte)) {
if (byte == '
' || idx >= sizeof(line) - 1) {
line[idx] = ' ';
parse_nmea_sentence(line);
idx = 0;
} else if (byte != '
') {
line[idx++] = byte;
}
}
}
Gestione errori: ORE, FE, NE
- ORE (Overrun Error, bit 3): un nuovo byte è arrivato prima che DR fosse stata letta. Il byte precedente viene sovrascritto e non è recuperabile.
- FE (Framing Error, bit 1): lo stop bit non è stato ricevuto correttamente. Di solito indica un disallineamento del baud rate.
- NE (Noise Error, bit 2): il campionamento a maggioranza ha rilevato campioni inconsistenti. Tipico in ambienti elettricamente rumorosi.
Esempio pratico: GPS NMEA su STM32F401
#define USART2_BRR_9600 ((273 << 4) | 7)
void usart2_init(void) {
RCC->APB1ENR |= RCC_APB1ENR_USART2EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
__DSB();
GPIOA->MODER = (GPIOA->MODER & ~(3U << 4) & ~(3U << 6))
| (2U << 4) | (2U << 6);
GPIOA->AFR[0] = (GPIOA->AFR[0] & ~(0xF << 8) & ~(0xF << 12))
| (7U << 8) | (7U << 12);
GPIOA->PUPDR = (GPIOA->PUPDR & ~(3U << 6)) | (1U << 6);
USART2->BRR = USART2_BRR_9600;
USART2->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE;
NVIC_EnableIRQ(USART2_IRQn);
}
Checklist pratica per progetti client
- Verifica OVER8 prima di debuggare problemi di baud rate. Un fattore di oversampling sbagliato raddoppia o dimezza il baud rate.
- Non stampare mai dentro l'ISR USART: printf, semihosting o toggle GPIO bloccanti causano ORE.
- Leggi DR una sola volta per evento RXNE: ogni lettura consuma un byte ricevuto.
- Gestisci ORE esplicitamente: la USART non si blocca in overrun — sovrascrive DR. Logga l'errore o passa a DMA.
- Tieni l'ISR RX sotto un byte-time: a 115200 baud, un byte (10 bit) richiede 86,8 µs.
- Metti una pull-up sul pin RX: un pin RX flottante capta rumore che causa interrupt RXNE spuri.
- Usa ring buffer di dimensione potenza-di-due: il trucco
& (SIZE - 1)è una singola AND su Cortex-M4.
Come lo affronterei su un progetto cliente
Ogni progetto embedded che inizio usa ora la stessa architettura UART a tre strati: inizializzazione a registri, ISR con ring buffer, parser line-based nell'applicazione. Questo pattern è stato usato su almeno una dozzina di progetti STM32F4 — ricevitori GNSS, gateway LoRaWAN, modem cellulari, slave MODBUS RS-485 e bootloader personalizzati.
La lezione chiave: non fidarti di un collegamento UART finché non lo hai stressato con un flusso continuo al baud rate massimo per almeno 60 secondi. Un adattatore USB-UART economico e cat /dev/urandom > /dev/ttyUSB0 lato PC, con un contatore errori lato STM32, ha individuato problemi di ORE su tre progetti separati prima che arrivassero in produzione.

Commenti
Hai commenti? Scrivimi un'email.