bootloader需硬编码flash分区地址与校验逻辑,链接脚本中用memory明确划分区域,跳转前须重映射vtor、初始化msp并关中断,校验应覆盖完整镜像且参数可配置,升级需分包处理与超时恢复。

Bootloader必须自己管理Flash分区,不能依赖操作系统
裸机环境下没有文件系统或分区表概念,Bootloader 需硬编码每个固件段的起始地址、大小和校验方式。常见错误是照搬Linux下的/dev/mtd0思维,结果烧写后跳转失败——因为MCU根本不知道“分区”是什么,只认绝对地址。
实操建议:
- 在链接脚本(
bootloader.ld)中用MEMORY明确划分区域,例如:MEMORY { bootloader (rx) : ORIGIN = 0x08000000, LENGTH = 32K app1 (rx) : ORIGIN = 0x08008000, LENGTH = 128K app2 (rx) : ORIGIN = 0x08028000, LENGTH = 128K } - 所有跳转目标地址必须与
app1入口点(通常是复位向量偏移0x00处的__vector_table)严格对齐,检查app1的VECT_TAB_OFFSET是否设为0x8000 - 避免用
memcpy直接拷贝整个bin到Flash;先擦除目标扇区(注意最小擦除单位,如STM32是1KB/2KB/16KB),再按页(通常256B)编程
校验逻辑必须覆盖整个应用镜像,且位置可配置
很多Bootloader只校验前几个字节(比如CRC32头),但实际出问题常在中间段——比如OTA升级时Flash写入中断导致某一页失败,而头部仍完好,Bootloader误判为有效固件。
实操建议:
- 校验范围应从应用入口向量开始,到镜像末尾(不含填充),长度由
app1.bin实际大小决定,不是固定值 - 把校验参数(算法、起始偏移、长度)存进Bootloader预留的配置区(如最后1KB Flash),而非硬编码在代码里,方便后续升级策略调整
- 推荐用
CRC32而非MD5:无依赖、计算快、适合资源受限场景;但注意初始化值(常用0xFFFFFFFF)和最终异或值(常再异或0xFFFFFFFF)需与生成工具一致
跳转前必须重映射中断向量表并关闭全局中断
直接typedef void (*func_ptr)(void)强转地址调用,大概率触发HardFault——因为新固件的中断向量表还在旧位置(0x08000000),而它期望在自己的基址(如0x08008000)。
实操建议:
- 跳转前执行:
SCB->VTOR = APP_BASE_ADDR; // 如0x08008000 __set_MSP(*((uint32_t*)APP_BASE_ADDR)); // 初始化主栈指针 __disable_irq();
- 确保
APP_BASE_ADDR与链接脚本中app1的ORIGIN完全一致,差一字节都会导致向量表错位 - 不要在跳转前调用任何C库函数(如
printf),它们可能依赖未初始化的运行时环境
串口/USB升级需处理分包边界和超时恢复
真实场景下,UART波特率低、线缆长、干扰大,read()返回的字节数往往小于请求长度,且可能卡死。直接循环read(1)效率低还易丢帧。
实操建议:
- 定义固定包头(如0x55AA)、包长字段、校验字节;接收端严格按此解析,不依赖单次
HAL_UART_Receive返回值 - 每收到一个完整包,立即擦除对应Flash扇区并写入,而非攒满整个镜像再操作——断电时只丢失最后几KB,而非整包
- 加入超时机制:连续2秒无新数据则进入空闲状态,等待命令(如'U'启动升级);避免因上位机异常导致Bootloader卡死
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











