STM32 CRC Peripheral: Hardware Checksum for Data Integrity, Bootloader Verification, and Communication

2026-07-06 · Davide Carrese
STM32 · CRC · Data Integrity · Bootloader · Embedded

Every STM32 ships with a hardware CRC calculation unit, yet most firmware engineers compute CRC in software — wasting CPU cycles and flash on something the MCU does in two bus cycles. The CRC peripheral handles the polynomial division, byte reversal, and output XOR in hardware, and it feeds directly into bootloader integrity checks, communication protocol validation, and flash verification. Here is exactly how to configure and use it at register level on STM32F4, STM32G4, and STM32U5.

I have lost count of how many production bootloaders I have seen that compute a CRC32 over the entire firmware image using a software loop with a lookup table. The flash is already there. The CPU is running anyway. But the CRC peripheral is sitting on the bus, configured at reset to the Ethernet CRC32 polynomial (0x04C11DB7), and ready to produce a result in fewer cycles than a single table lookup. The gap is almost always ignorance of the peripheral's existence, not a technical constraint.

This article covers the CRC peripheral from the register map up: polynomial configuration, data size and reversal, the one-shot calculation model, streaming (multi-block) CRC, bootloader image integrity, and communication CRC with CAN and SPI. I use the STM32F401 as the reference, but the register layout is identical across F4, G4, L4, L5, U5, H5, and H7 series. The only difference is the initial CRC value default (some families reset to 0xFFFFFFFF, others to 0x00000000) — always set it explicitly.

CRC register map (RM0368, RM0399)

The CRC peripheral on STM32 has exactly four registers — no more, no less:

OffsetRegisterPurpose
0x00CRC_DRData input / CRC output
0x04CRC_IDRIndependent data register (not part of CRC calculation)
0x08CRC_CRControl register: reset + polynomial size
0x10 (rev. V2)CRC_INITInitial CRC value (programmable on newer series)

The data register CRC_DR is the workhorse. Every write feeds 8, 16, or 32 bits into the calculation. Every read returns the current CRC remainder. Writing 1 to CRC_CR resets the peripheral to its initial state (default: 0xFFFFFFFF with the Ethernet polynomial).

One-shot CRC: verifying a flash page

This is the most common use case: generate a CRC over a block of memory and compare it against a stored reference. Here is the exact register sequence for STM32F4:

/* Reset the CRC peripheral — sets remainder to 0xFFFFFFFF */
CRC->CR = CRC_CR_RESET;

/* Feed a 256-word (1 KB) flash buffer word by word */
uint32_t *src = (uint32_t *)0x08040000;
for (int i = 0; i < 256; i++) {
    CRC->DR = src[i];
}

/* Read the final CRC */
uint32_t computed_crc = CRC->DR;

/* Compare against stored value */
if (computed_crc != expected_crc) {
    /* Page is corrupt or modified — trigger recovery */
    bootloader_jump_to_recovery();
}

The peripheral absorbs one word per bus cycle — two CPU cycles on a zero-wait-state Flash access. A 1 KB page takes about 512 cycles. The same CRC in software with a table lookup takes about 10–15 cycles per byte, which is 10,000–15,000 cycles for 1 KB. The hardware does it in 5–10% of the time.

Streaming (multi-block) CRC

The one-shot model resets the CRC before starting. But you can also compute a CRC incrementally: calculate over block A, capture the intermediate remainder, then continue with block B. This is useful when:

/* First block */
CRC->CR = CRC_CR_RESET;
for (int i = 0; i < 128; i++) CRC->DR = block1[i];
uint32_t mid = CRC->DR;       /* snapshot — do NOT reset */

/* Second block — continue from previous remainder */
for (int i = 0; i < 128; i++) CRC->DR = block2[i];
uint32_t final = CRC->DR;     /* CRC over both blocks */

The key constraint: do not write CRC_CR_RESET between blocks. The CRC peripheral keeps the running remainder in its internal shift register. A read of CRC_DR gives you a copy without disturbing the state — the peripheral transparently reloads the remainder after the read completes.

Polynomial, initial value, and output XOR

On newer STM32 families (G4, L4+, L5, U5, H5, H7), the CRC peripheral has a version 2 (rev. V2) block with programmable polynomial, initial value, and output XOR configuration through the CRC_INIT and CRC_POL registers. On F4 and earlier series, these are fixed: polynomial = 0x04C11DB7, initial = 0xFFFFFFFF, output XOR = 0x00000000.

If you use GCC's __CRC32__ built-in (__builtin_arm_crc32w) or hardware CRC on the Cortex-M4/M7, the polynomial matches the STM32 default. But the initial value and output XOR differ between implementations. The STM32 CRC peripheral implements what is commonly called CRC-32/MPEG2: initial = 0xFFFFFFFF, no output XOR. Standard Ethernet CRC-32 (PKZIP, CRC-32/ISO-HDLC) uses initial = 0xFFFFFFFF and output XOR = 0xFFFFFFFF.

To compute the standard CRC-32 on an STM32F4, XOR the result with 0xFFFFFFFF:

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

On G4/U5 with the configurable CRC, set CRC_INIT = 0xFFFFFFFF and configure the output XOR reversal in CRC_CFG (if available in your specific part) to match exactly.

Practical example: bootloader firmware integrity check on STM32F401

A production bootloader must verify the application image before jumping to it. The CRC peripheral lets you do this in microseconds rather than milliseconds. Here is a complete pattern:

#define APP_START  0x08010000
#define APP_SIZE   (128 * 1024)   /* 128 KB */
#define CRC_OFFSET (APP_SIZE - 4) /* CRC stored in last 4 bytes */

uint32_t compute_firmware_crc(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 get_stored_crc(void) {
    return *(uint32_t *)(APP_START + CRC_OFFSET);
}

int verify_firmware(void) {
    uint32_t stored = get_stored_crc();
    uint32_t computed = compute_firmware_crc();
    if ((computed ^ 0xFFFFFFFF) == stored) {
        return 1; /* firmware is valid */
    }
    return 0;
}

I put the CRC word at the last 4 bytes of the firmware image and patch it during the build step with a post-link script. The bootloader computes the CRC over everything except the last word and XORs with 0xFFFFFFFF to match the standard CRC-32 that the build pipeline generates.

Practical example: CAN message CRC validation

The CRC peripheral is not limited to flash verification. When implementing a custom transport layer over CAN or SPI that uses CRC-32 for message integrity, the hardware unit is faster than any software implementation and adds zero code footprint:

/* On CAN message reception: compute CRC over payload */
void can_rx_validate(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];
    }
    /* Handle remaining bytes at byte level if len % 4 != 0 */
    uint32_t crc_raw = CRC->DR;

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

For hardware CRC offloading during active CAN traffic, ensure the CRC peripheral is clocked (RCC enables it), and avoid resetting it mid-stream if multiple consecutive messages share a single CRC context.

Practical checklist

How I would approach this on a client project

On the first day of any embedded project that needs data integrity verification — and that is nearly every project with a bootloader — I add a crc_hw.c module with three functions: crc_reset(), crc_feed_block(addr, words), and crc_result(). The implementation is a thin wrapper around the four CRC registers. I add it to the bootloader and the application. If the client later switches to a Cortex-M0 part (which lacks the CRC peripheral), I swap the backend to a software CRC with the same API. The application code never changes — only the HAL-like abstraction layer does. This separation has saved me months of firmware rework on two different projects where the MCU was changed mid-development.

The CRC peripheral is also the fastest way to validate a firmware image over a slow debug probe. Instead of reading the entire flash over SWD (which takes seconds), I let the target compute the CRC in hardware and send back the single 32-bit result. One register read over SWD instead of 128 KB of bulk reads.

Sources

Comments

Have comments? Send me an email.