网站首页 > 技术文章 正文
嵌入式设备固件一旦被写入Flash,若出现写入失败、掉电中断、存储器损坏等问题,极有可能导致系统无法启动或执行异常。为此,在Bootloader中加入固件完整性校验机制,成为实现系统可靠启动的重要手段之一。
STM32系列芯片提供了硬件CRC模块,配合Bootloader可以实现高效、低资源消耗的校验机制。本文将通过实例演示如何设计启动前的CRC完整性校验流程,并使用面向接口设计、回调机制、弱函数重载等工程手法构建一套通用可靠的架构。
一、整体设计思路
启动前流程:
- Bootloader启动后,读取APP区预设的CRC校验值;
- 对APP区代码(通常为固定地址段)进行CRC计算;
- 与预设CRC值对比,若一致则跳转到APP,否则进入异常处理流程(如进入待机、等待OTA等)。
二、STM32 CRC模块初始化与使用
STM32硬件CRC计算基于标准多项式 0x04C11DB7(32位CRC-32/IEEE标准)。使用流程非常简单,不需中断,也无DMA依赖。
1. 初始化
void CRC_Init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_CRCEN; // 使能CRC时钟
}
2. 清除与计算流程
uint32_t CRC_Calculate(uint32_t* data, uint32_t length)
{
CRC->CR = CRC_CR_RESET; // 清除寄存器
for (uint32_t i = 0; i < length; ++i) {
CRC->DR = data[i];
}
return CRC->DR;
}
说明:STM32 CRC模块按32位为单位写入,因此需确保待校验区域按32位对齐。
三、Bootloader中固件校验实现
1. 固件末尾添加CRC值(由上位机写入或编译后工具生成)
假设APP空间为 0x08010000 - 0x0807FFFF,最后4字节写入CRC结果。
#define APP_START_ADDR 0x08010000
#define APP_END_ADDR 0x0807FFFC // 最后4字节是CRC值
#define APP_CRC_ADDR 0x0807FFFC
2. 校验函数
#include "crc32.h" // 模块化接口
static uint8_t check_app_crc(void)
{
uint32_t length = (APP_END_ADDR - APP_START_ADDR) / 4;
uint32_t* data_ptr = (uint32_t*)APP_START_ADDR;
uint32_t calc_crc = CRC_Calculate(data_ptr, length);
uint32_t stored_crc = *(uint32_t*)APP_CRC_ADDR;
return (calc_crc == stored_crc) ? 1 : 0;
}
四、模块接口封装与扩展设计
为支持可扩展设计,将CRC封装为模块接口,方便日后替换为软件CRC、支持不同多项式等。
1. CRC模块接口定义
typedef struct {
void (*init)(void);
uint32_t (*calculate)(uint32_t* data, uint32_t length);
} crc_ops_t;
2. CRC实现结构体
static void hw_crc_init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_CRCEN;
}
static uint32_t hw_crc_calculate(uint32_t* data, uint32_t length)
{
CRC->CR = CRC_CR_RESET;
for (uint32_t i = 0; i < length; ++i) {
CRC->DR = data[i];
}
return CRC->DR;
}
const crc_ops_t crc_hw = {
.init = hw_crc_init,
.calculate = hw_crc_calculate
};
3. 使用统一接口进行调用
static const crc_ops_t* crc_driver = &crc_hw;
uint8_t check_app_image(void)
{
crc_driver->init();
uint32_t len = (APP_END_ADDR - APP_START_ADDR) / 4;
uint32_t calc = crc_driver->calculate((uint32_t*)APP_START_ADDR, len);
uint32_t stored = *(uint32_t*)APP_CRC_ADDR;
return (calc == stored);
}
五、弱函数回调设计:自定义异常处理
为提升灵活性,异常处理不应固化在Bootloader内部,而是留给项目方进行自定义处理。可使用 __weak 机制进行默认实现,并允许重载。
__weak void on_firmware_check_fail(void)
{
while (1) {
// 默认处理:红灯报警
}
}
用户在项目中自定义处理:
void on_firmware_check_fail(void)
{
log_error("固件CRC校验失败");
enter_ota_wait_mode(); // 等待远程升级
}
启动流程中调用:
void boot_process(void)
{
if (!check_app_image()) {
on_firmware_check_fail(); // 回调处理
} else {
jump_to_app();
}
}
六、调试技巧与注意事项
- 编译器对齐问题:使用 uint8_t* 进行校验时注意按4字节对齐。
- 尾部CRC写入问题:确保上位机或烧录工具能正确写入固件CRC末尾。
- 调试建议:保留中间变量 calc_crc 和 stored_crc,方便通过串口打印比对。
七、总结与拓展
通过STM32硬件CRC模块,可以非常高效地实现固件完整性校验机制。整个流程在Bootloader执行前阶段完成,适用于:
- 上电初始化校验;
- OTA前后完整性确认;
- 恢复机制判断依据。
本方案还支持以下扩展方向:
- 软件CRC(如CRC-16、XMODEM)适配;
- FLASH扇区校验(分段更新验证);
- CRC值和固件版本号绑定,实现版本回滚限制;
- 配合MD5、SHA256形成多重校验(通过软件实现)。
最终目标,是构建一个可扩展、可插拔、具备灵活处理机制的Bootloader校验架构,为系统可靠运行提供稳定支撑。
猜你喜欢
- 2025-08-05 42张图,带你真正搞懂redis数据类型的底层
- 2025-08-05 深度解密epoll 如何工作的?
- 2025-08-05 5分钟看懂的WebAssembly入门指南
- 2025-08-05 万字详文:Golang 汇编入门知识总结,看这一篇就够了
- 2025-08-05 Sliero VAD:高精度、轻量级的语音活动检测模型
- 2025-08-05 Go 语言 + aardio 快速开发图形化桌面软件,简单生成独立 EXE
- 2025-08-05 STM32+A3P125 图形控制器方案,多参数监护仪数据采集模块深度解析
- 2025-08-05 Go要点新解(二)map小解
- 2025-08-05 linux网络编程epoll模型
- 2025-08-05 盘点10个让你直呼“卧槽”的Go语言小技巧
- 08-06中等生如何学好初二数学函数篇
- 08-06C#构造函数
- 08-06初中数学:一次函数学习要点和方法
- 08-06仓颉编程语言基础-数据类型—结构类型
- 08-06C++实现委托机制
- 08-06初中VS高中三角函数:从"固定镜头"到"360°全景",数学视野升级
- 08-06一文讲透PLC中Static和Temp变量的区别
- 08-06类三剑客:一招修改所有对象!类方法与静态方法的核心区别!
- 1524℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 657℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 528℃MySQL service启动脚本浅析(r12笔记第59天)
- 494℃启用MySQL查询缓存(mysql8.0查询缓存)
- 493℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 480℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 462℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 461℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- windowsscripthost (69)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (70)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)