on 2026-04-02 5:39 AM
This article explains how to add a robust firmware integrity check to STM32 projects configured with STM32CubeMX2 (MX2) and developed using either IAR or VS Code with CMake and GCC. It shows how to enable the STM32 CRC peripheral, use a post-build step to compute a 32-bit CRC over the application image, inject that CRC into the final .hex file with srec_cat, and verify it at startup using both hardware and software CRC calculations, while keeping the original ELF/OUT file unchanged for debugging.
Many STM32 applications need to ensure that the firmware image in flash has not been corrupted, especially when using bootloaders, in-field updates, or safety and security mechanisms. A common solution is to compute a CRC over the application image and store the resulting 32-bit value inside the same image so that the code can verify its own integrity at every reset.
This article describes a concrete way to implement this pattern on the NUCLEO-C562RE board, using MX2 configuration, the STM32 CRC peripheral, and an external post-build flow based on objcopy or ielftool together with srec_cat. You will see how to:
By following these steps, you can add a portable and repeatable image-integrity check to your STM32C562RE projects with minimal changes to your build and debug setup.
For the UART output used in this article:
For the GCC/VS Code/CMake flow, you do not need a batch file or a .cat script: the post-build logic is implemented directly inside CMakeLists.txt using CMAKE_OBJCOPY and srec_cat.exe, as described later.
You only need to adapt paths (for ielftool.exe, srec_cat.exe, objcopy) and project names to match your local environment.
Many STM32 applications validate their firmware at boot using a 32-bit CRC stored inside the image. The firmware recomputes the CRC at runtime and compares it with the stored value to decide whether the image is valid.
This approach provides:
Replace the auto-generated main.c with the code below. This file already contains the CRC test logic and UART retargeting. The code defines the CRC calculation range and storage address as follows:
/**
******************************************************************************
* file : main.c
* brief : Main program body
* Calls target system initialization then loop in main.
******************************************************************************
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32c5xx_hal_gpio.h"
#include <stdio.h>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* CRC calculation length */
#define BUFFER_SIZE (0x4FFC - 0x0000)/4 // CRC over 0x08000000..0x08004FFB
/* Address where the CRC value are stored */
#define STD_CRC_ADDR (FLASH_BASE + 0x4FFC) // 0x08004FFC
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
__IO uint32_t u32CRCValueHw = 0;
__IO uint32_t u32CRCValueSoft= 0xFFFFFFFF;
const uint32_t *myDataBuffer;
/* Private functions prototype -----------------------------------------------*/
uint32_t Crc32(uint32_t Crc, uint32_t Data);
void Error_Handler(void);
/**
* brief: The application entry point.
* retval: none but we specify int to comply with C99 standard
*/
int main(void)
{
uint32_t crc_result = 0;
uint16_t u16myDataBufferCount = BUFFER_SIZE;
myDataBuffer = (uint32_t*) 0x08000000;
if (mx_system_init() != SYSTEM_OK)
{
return (-1);
}
else
{
printf("\n\r================================ CRC TEST ================================\n\r");
/* 1 - Compute the CRC by STM32 CRC Peripheral */
/* CRC calculation by Hardware (peripheral) */
/* BUFFER_SIZE is words; HAL expects bytes → BUFFER_SIZE * 4 */
HAL_CRC_Accumulate(mx_crc_gethandle(), (uint32_t *)FLASH_BASE, BUFFER_SIZE * 4U, &crc_result);
u32CRCValueHw = crc_result;
/* CRC calculation by software */
while (u16myDataBufferCount--)
{
u32CRCValueSoft= Crc32(u32CRCValueSoft, *myDataBuffer++);
}
/* 2 - Print the calculated checksum */
printf("\tChecksum generated by STM32 hardware CRC Unit = 0x%08X\n\r", (unsigned int)u32CRCValueHw);
printf("\tChecksum generated by STM32 software CRC = 0x%08X\n\r", (unsigned int)u32CRCValueSoft);
/* 3 - Print the stored CRC */
printf("\tChecksum stored at 0x%08X = 0x%08X\n\r", (unsigned int)STD_CRC_ADDR, *(unsigned int *)STD_CRC_ADDR);
/* 4 - Check if the calculated CRC is equal the stored value */
if(u32CRCValueHw == *(uint32_t *)STD_CRC_ADDR)
{
printf("CRC Check: OK...\n\r");
printf("==========================================================================\n\r");
}
else
{
printf("CRC Check: Fail...\n\r");
printf("==========================================================================\n\r");
Error_Handler();
}
while (1) {
/* Blink USER LED to indicates the running application */
printf("Application running... \n\r");
HAL_GPIO_TogglePin(HAL_GPIOA, HAL_GPIO_PIN_5);
HAL_Delay(1000);
}
}
} /* end main */
/* CRC Calculation by software function */
uint32_t Crc32(uint32_t Crc, uint32_t Data)
{
uint8_t index;
Crc ^= (Data);
for(index=0; index<32; index++)
{
if (Crc & 0x80000000)
Crc = (Crc << 1) ^ 0x04C11DB7; // Polynomial used in STM32
else
Crc = (Crc << 1);
}
return(Crc);
}
int __io_putchar(int ch)
{
HAL_UART_Transmit(mx_usart2_uart_gethandle(), (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
void Error_Handler(void)
{
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
}
This means:
Resuming, in main.c we will:
After that, build the code.
For the GCC toolchain, the post-build CRC insertion is handled directly in CMakeLists.txt. No extra batch file is required.
Add the following code in the final of your main CMakeLists.txt:
add_custom_command(
TARGET ${CMAKE_PROJECT_NAME}
POST_BUILD
# 1) Convert ELF to Intel HEX
COMMAND ${CMAKE_OBJCOPY} -O ihex
$<TARGET_FILE:${CMAKE_PROJECT_NAME}>
${CMAKE_PROJECT_NAME}.hex
# 2) Compute STM32 CRC and inject it into a new HEX
COMMAND "C:/srecord/bin/srec_cat.exe"
${CMAKE_PROJECT_NAME}.hex -Intel
-fill 0xFF 0x08000000 0x08004FFC
-STM32 0x08004FFC
-o ${CMAKE_PROJECT_NAME}_SRECORD.hex -Intel
# 3) Convert CRC-injected HEX back to ELF
COMMAND ${CMAKE_OBJCOPY} -I ihex -O elf32-littlearm
${CMAKE_PROJECT_NAME}_SRECORD.hex
${CMAKE_PROJECT_NAME}_SRECORD.elf
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating HEX and ${CMAKE_PROJECT_NAME}_SRECORD.hex with STM32 CRC at end of flash"
)
This sequence:
These addresses and range exactly match what main.c uses:
After each build, you have:
With the STM32 VS Code extension and your launch.json, the same debug configuration takes care of:
launch.json:
Note: Make sure to adjust the *.elf name and path based on your own project.
{
"version": "0.2.0",
"configurations": [
{
"type": "stlinkgdbtarget",
"request": "launch",
"name": "CRC_Debug",
"origin": "snippet",
"cwd": "${workspaceFolder}",
"preBuild": "${command:st-stm32-ide-debug-launch.build}",
"runEntry": "main",
"imagesAndSymbols": [
{
"imageFileName": "${workspaceFolder}/build/debug_GCC_NUCLEO-C562RE/NUCLEO-C562RE_CRC_SRECORD.elf",
"symbolFileName": "${workspaceFolder}/build/debug_GCC_NUCLEO-C562RE/NUCLEO-C562RE_CRC.elf"
}
]
}
]
}
This configuration does the following:
The complete programming and debug flow is:
Once the post-build CRC insertion and debug flows are in place, you can validate that the firmware correctly detects a valid and an invalid image.
Expected UART Output
If everything is correctly configured, you should see an output similar to:
Where:
This confirms:
Negative test (corrupted image)
To verify that the mechanism detects corrupted firmware:
In this case:
This article showed how to implement a build-time CRC mechanism for STM32 firmware on NUCLEO-C562RE using MX2, VS Code with CMake and GCC, and IAR EWARM. On the build side, you configured a post-build step that converts the ELF/OUT to HEX, computes a STM32-style CRC over the flash range, and stores the result at a given position via srec_cat in the GCC flow. On the run-time side, the attached main.c recomputes the CRC using both the STM32 CRC peripheral and a software Crc32() function and compares the result with the stored value, reporting the status over USART2.
This pattern gives you a self-checking firmware image that can detect corruption at boot, is independent of a specific toolchain, and still keeps the original ELF/OUT clean for debugging. By only adjusting the flash range and CRC placement address for each device, you can reuse the same approach across other STM32 projects and integrate robust image integrity checks into your standard development flow.