第4章:Bootloader核心功能实现
好,咱们今天聊点实在的。Bootloader说白了就是一段上电先跑的代码,它的任务只有一个:把真正的固件安全地写进Flash,然后跳过去执行。我做了这么多年嵌入式,见过太多因为Bootloader写得糙导致产品变砖的案例。这一章,咱们就把串口Ymodem接收、Flash擦写、CRC校验这三个核心功能彻底讲透。
- 用Ymodem协议从串口把固件包收进来
- 把收到的数据可靠地写入Flash
- 用CRC32确保固件没被改过
4.1 Ymodem协议接收——别小看这个老协议
Ymodem是Xmodem的升级版,支持128字节和1024字节两种包长,还能传文件名和大小。我最早接触它是在一个卫星接收机项目里,当时觉得这协议太老了,结果发现工业设备里遍地都是。为什么?因为它简单、可靠、有重传机制。
Ymodem的传输流程其实不复杂:
- 发送方先发一个文件名包(SOH + 序号0 + 文件名 + 文件大小)
- 接收方回应ACK
- 发送方开始发数据包(STX或SOH开头)
- 每包收完,接收方回ACK或NAK
- 发完所有数据,发EOT结束
这里有个坑——超时处理。我曾经在一个项目里没做超时,结果发送方卡住了,Bootloader就一直死等,连看门狗都救不回来。后来我加了个10秒超时,收不到包就复位重来,稳得很。
typedef enum {
YM_STATE_IDLE,
YM_STATE_WAIT_SOH,
YM_STATE_RECV_HEADER,
YM_STATE_RECV_DATA,
YM_STATE_RECV_CRC,
YM_STATE_WAIT_EOT,
YM_STATE_COMPLETE,
YM_STATE_ERROR
} YmodemState_t;
接收缓冲区我建议用双缓冲——一个收当前包,一个存上一包。这样在Flash写入期间,串口还能继续收,不会丢数据。嗯,这个技巧是我从一次OTA升级失败里学来的。
4.2 Flash擦除与写入——手把手教你操作寄存器
Flash操作是Bootloader的命门。写错了,轻则程序跑飞,重则芯片锁死。STM32的Flash控制器有几个关键寄存器:FLASH_CR(控制)、FLASH_SR(状态)、FLASH_AR(地址)。
操作流程其实就三步:
- 解锁:向FLASH_KEYR写入两个密钥字
- 擦除:按扇区擦除,每次擦除前要检查BSY位
- 编程:按字或半字写入,同样要等BSY清空
我一般把Flash驱动封装成三个函数:
HAL_StatusTypeDef Flash_EraseSector(uint32_t sector);
HAL_StatusTypeDef Flash_WriteWord(uint32_t addr, uint32_t data);
HAL_StatusTypeDef Flash_WriteBuffer(uint32_t addr, uint8_t *buf, uint32_t len);
这里有个性能问题——逐字写入太慢。1024字节的包,一个字一个字写要写256次。我建议用Flash_WriteBuffer做批量写入,内部用循环展开,速度能快3-5倍。你想想看,OTA升级时用户可没耐心等。
| 操作 | 典型耗时 | 注意事项 |
|---|---|---|
| 扇区擦除(1KB) | ~20ms | 必须关中断 |
| 逐字写入(1KB) | ~15ms | CPU占用高 |
| 批量写入(1KB) | ~5ms | 推荐使用 |
4.3 CRC32校验——别让坏固件跑起来
固件完整性校验是Bootloader的最后一道防线。CRC32虽然不如SHA256安全,但用在Bootloader里足够了——计算快、硬件支持好、碰撞概率极低。
STM32有个硬件CRC计算单元,用起来很简单:
uint32_t CRC32_Calculate(uint8_t *data, uint32_t len) {
__HAL_RCC_CRC_CLK_ENABLE();
CRC->CR = CRC_CR_RESET;
for (uint32_t i = 0; i < len; i++) {
CRC->DR = data[i];
}
return CRC->DR;
}
但要注意——硬件CRC的初始值和多项式是固定的(0xFFFFFFFF和0x04C11DB7)。如果你的上位机用的软件CRC算法不一样,算出来的值就对不上。我踩过这个坑,后来统一用0xEDB88320查表法,两边一致。
- 每包校验:收到一包就算一次CRC,和包尾的CRC比较
- 整体校验:全部收完后,对整个固件区算一次CRC
- 我建议两个都做——包校验防传输错误,整体校验防Flash写入错误
4.4 整体流程——把三件事串起来
下面这张图展示了Bootloader接收固件的完整流程。我画这张图时特意把状态和关键判断都标出来了,你调试时对着看,能省不少时间。
4.5 避坑指南——我踩过的那些雷
做Bootloader这么多年,有些坑我几乎每个项目都见一次。列出来,你遇到了直接翻这页:
- Flash写入未对齐:STM32要求写入地址必须是半字或字对齐,不对齐直接HardFault。我习惯在写入前做地址对齐检查。
- 中断向量表没重映射:跳转前要把SCB->VTOR设成新固件的起始地址,否则中断一个都进不去。
- 栈指针没恢复:跳转前要从新固件开头读栈顶指针并写入MSP,不然函数调用就崩。
- Ymodem包序号乱序:发送方可能重传旧包,接收方要检查序号,别把旧数据当新的写进去。
好了,这一章的内容就是这些。Ymodem接收、Flash擦写、CRC校验,这三个功能你吃透了,Bootloader的核心骨架就有了。剩下的就是往上加功能——比如双备份、回滚、加密——这些后面章节再聊。