2026-07-06 · Davide Carrese

Periferica CRC su STM32: Checksum Hardware per Integrità Dati, Bootloader e Comunicazione

STM32 · CRC · Integrità Dati · Bootloader · Embedded

Ogni STM32 integra un'unità hardware per il calcolo del CRC, eppure molti sviluppatori continuano a implementarlo in software — sprecando cicli CPU e flash su qualcosa che il microcontrollore esegue in due cicli di bus. La periferica CRC gestisce la divisione polinomiale, l'inversione dei byte e l'XOR finale in hardware, ed è perfetta per la verifica dei bootloader, la validazione di protocolli di comunicazione e il controllo d'integrità della memoria flash. Ecco esattamente come configurarla e usarla a livello di registri su STM32F4, STM32G4 e STM32U5.

Ho perso il conto di quanti bootloader in produzione ho visto che calcolano un CRC32 sull'intera immagine firmware usando un loop software con lookup table. La flash è lì. La CPU è già accesa. Ma la periferica CRC è seduta sul bus, configurata al reset con il polinomio Ethernet CRC32 (0x04C11DB7), e pronta a produrre un risultato in meno cicli di una singola lookup table. Il problema è quasi sempre l'ignoranza dell'esistenza della periferica, non un vincolo tecnico.

Questo articolo copre la periferica CRC partendo dalla mappa dei registri: configurazione del polinomio, dimensione dei dati e inversione, il modello di calcolo one-shot, CRC incrementale (multi-blocco), verifica d'integrità del bootloader e CRC per comunicazioni CAN e SPI. Uso STM32F401 come riferimento, ma la mappa dei registri è identica su F4, G4, L4, L5, U5, H5 e H7. L'unica differenza è il valore iniziale del CRC (alcune famiglie resettano a 0xFFFFFFFF, altre a 0x00000000) — impostarlo sempre esplicitamente.

Mappa dei registri CRC (RM0368, RM0399)

La periferica CRC su STM32 ha esattamente quattro registri — né più, né meno:

OffsetRegistroScopo
0x00CRC_DRIngresso dati / Uscita CRC
0x04CRC_IDRRegistro dati indipendente (non parte del calcolo CRC)
0x08CRC_CRRegistro di controllo: reset + dimensione polinomio
0x10 (rev. V2)CRC_INITValore iniziale CRC (programmabile su serie recenti)

Il registro dati CRC_DR è il cavallo di battaglia. Ogni scrittura alimenta 8, 16 o 32 bit nel calcolo. Ogni lettura restituisce il resto CRC corrente. Scrivere 1 in CRC_CR resetta la periferica allo stato iniziale (default: 0xFFFFFFFF con il polinomio Ethernet).

CRC one-shot: verifica di una pagina flash

Questo è il caso d'uso più comune: generare un CRC su un blocco di memoria e confrontarlo con un riferimento memorizzato. Ecco la sequenza esatta a registri per STM32F4:

/* Reset della periferica CRC — imposta il resto a 0xFFFFFFFF */
CRC->CR = CRC_CR_RESET;

/* Alimenta un buffer flash di 256 word (1 KB) parola per parola */
uint32_t *src = (uint32_t *)0x08040000;
for (int i = 0; i < 256; i++) {
    CRC->DR = src[i];
}

/* Legge il CRC finale */
uint32_t crc_calcolato = CRC->DR;

/* Confronta con il valore memorizzato */
if (crc_calcolato != crc_atteso) {
    /* Pagina corrotta o modificata — attiva recovery */
    bootloader_jump_to_recovery();
}

La periferica assorbe una parola per ciclo di bus — due cicli CPU su un accesso flash a zero wait-state. Una pagina da 1 KB richiede circa 512 cicli. Lo stesso CRC in software con lookup table richiede circa 10–15 cicli per byte, ovvero 10.000–15.000 cicli per 1 KB. L'hardware lo fa nel 5–10% del tempo.

CRC streaming (multi-blocco)

Il modello one-shot resetta il CRC prima di iniziare. Ma si può anche calcolare un CRC incrementale: calcolare sul blocco A, catturare il resto intermedio, poi continuare con il blocco B. È utile quando:

/* Primo blocco */
CRC->CR = CRC_CR_RESET;
for (int i = 0; i < 128; i++) CRC->DR = blocco1[i];
uint32_t medio = CRC->DR;       /* snapshot — NON resettare */

/* Secondo blocco — continua dal resto precedente */
for (int i = 0; i < 128; i++) CRC->DR = blocco2[i];
uint32_t finale = CRC->DR;     /* CRC su entrambi i blocchi */

Il vincolo chiave: non scrivere CRC_CR_RESET tra un blocco e l'altro. La periferica CRC mantiene il resto corrente nel suo registro a scorrimento interno. Una lettura di CRC_DR restituisce una copia senza disturbare lo stato — la periferica ricarica trasparentemente il resto dopo che la lettura è completata.

Polinomio, valore iniziale e XOR finale

Sulle famiglie STM32 più recenti (G4, L4+, L5, U5, H5, H7), la periferica CRC ha una versione 2 (rev. V2) con polinomio programmabile, valore iniziale e configurazione dell'XOR finale attraverso i registri CRC_INIT e CRC_POL. Su F4 e serie precedenti, questi sono fissi: polinomio = 0x04C11DB7, iniziale = 0xFFFFFFFF, XOR finale = 0x00000000.

Se si usa il built-in __CRC32__ di GCC (__builtin_arm_crc32w) o il CRC hardware del Cortex-M4/M7, il polinomio corrisponde al default STM32. Ma il valore iniziale e l'XOR finale differiscono tra implementazioni. La periferica CRC degli STM32 implementa quello che comunemente viene chiamato CRC-32/MPEG2: iniziale = 0xFFFFFFFF, nessun XOR finale. Il CRC-32 Ethernet standard (PKZIP, CRC-32/ISO-HDLC) usa iniziale = 0xFFFFFFFF e XOR finale = 0xFFFFFFFF.

Per calcolare il CRC-32 standard su un STM32F4, fare XOR del risultato con 0xFFFFFFFF:

CRC->CR = CRC_CR_RESET;
CRC->DR = dati;
uint32_t std_crc32 = CRC->DR ^ 0xFFFFFFFF;

Su G4/U5 con il CRC configurabile, impostare CRC_INIT = 0xFFFFFFFF e configurare l'XOR finale in CRC_CFG (se disponibile sul modello specifico) per corrispondere esattamente.

Esempio pratico: verifica integrità firmware su STM32F401

Un bootloader di produzione deve verificare l'immagine applicativa prima di saltarci. La periferica CRC permette di farlo in microsecondi invece che millisecondi. Ecco un pattern completo:

#define APP_START  0x08010000
#define APP_SIZE   (128 * 1024)   /* 128 KB */
#define CRC_OFFSET (APP_SIZE - 4) /* CRC memorizzato negli ultimi 4 byte */

uint32_t calcola_crc_firmware(void) {
    uint32_t *p = (uint32_t *)APP_START;
    uint32_t words = (CRC_OFFSET) / 4;

    CRC->CR = CRC_CR_RESET;
    for (uint32_t i = 0; i < words; i++) {
        CRC->DR = p[i];
    }
    return CRC->DR;
}

uint32_t leggi_crc_memorizzato(void) {
    return *(uint32_t *)(APP_START + CRC_OFFSET);
}

int verifica_firmware(void) {
    uint32_t memorizzato = leggi_crc_memorizzato();
    uint32_t calcolato = calcola_crc_firmware();
    if ((calcolato ^ 0xFFFFFFFF) == memorizzato) {
        return 1; /* firmware valido */
    }
    return 0;
}

Metto la word CRC negli ultimi 4 byte dell'immagine firmware e la sostituisco durante il build con uno script post-link. Il bootloader calcola il CRC su tutto tranne l'ultima word e fa XOR con 0xFFFFFFFF per corrispondere al CRC-32 standard che la pipeline di build genera.

Esempio pratico: validazione CRC messaggi CAN

La periferica CRC non è limitata alla verifica della flash. Quando si implementa un livello di trasporto personalizzato su CAN o SPI che usa CRC-32 per l'integrità dei messaggi, l'unità hardware è più veloce di qualsiasi implementazione software e non aggiunge codice:

/* Alla ricezione di un messaggio CAN: calcola CRC sul payload */
void can_rx_valida(CAN_Msg *msg) {
    CRC->CR = CRC_CR_RESET;
    uint32_t *payload = (uint32_t *)msg->data;
    for (int i = 0; i < msg->len / 4; i++) {
        CRC->DR = payload[i];
    }
    /* Gestisci byte rimanenti se len % 4 != 0 */
    uint32_t crc_raw = CRC->DR;

    if ((crc_raw ^ 0xFFFFFFFF) != msg->crc_field) {
        error_handler("CRC CAN non corrispondente");
    }
}

Per l'offloading del CRC hardware durante traffico CAN attivo, assicurarsi che la periferica CRC sia clockata (RCC abilitato), ed evitare di resettarla a metà flusso se più messaggi consecutivi condividono un singolo contesto CRC.

Checklist pratica

Come lo affronterei su un progetto cliente

Il primo giorno di qualsiasi progetto embedded che necessita di verifica d'integrità — e sono quasi tutti quelli con un bootloader — aggiungo un modulo crc_hw.c con tre funzioni: crc_reset(), crc_feed_block(indirizzo, words) e crc_result(). L'implementazione è un sottile wrapper attorno ai quattro registri CRC. Lo aggiungo al bootloader e all'applicazione. Se il cliente passa successivamente a un Cortex-M0 (che non ha la periferica CRC), sostituisco il backend con un CRC software con la stessa API. Il codice applicativo non cambia mai — solo il livello di astrazione HAL-like. Questa separazione mi ha salvato mesi di rielaborazione firmware su due progetti dove l'MCU è stato cambiato a metà sviluppo.

La periferica CRC è anche il modo più veloce per validare un'immagine firmware attraverso una sonda di debug lenta. Invece di leggere l'intera flash via SWD (che richiede secondi), lascio che il target calcoli il CRC in hardware e rimandi indietro il singolo risultato a 32 bit. Una lettura di registro via SWD invece di 128 KB di letture bulk.

Fonti

Comments

Have comments? Send me an email.