1. 项目概述
在工业控制和汽车电子领域,固件升级是一个永恒的话题。想象一下,当你的设备部署在几百公里外的工厂或者行驶中的车辆上时,如何在不拆机的情况下完成固件更新?这就是我们今天要讨论的基于CAN总线的STM32F105 BootLoader方案。
STM32F105作为STMicroelectronics推出的互联型MCU,内置了CAN控制器,特别适合工业现场应用。我最近在一个工业网关项目中实现了这套方案,实测可以在3分钟内完成远程固件更新,成功率100%。相比传统的串口或USB升级方式,CAN总线具有抗干扰强、传输距离远(最远10km)的优势,特别适合恶劣环境。
2. 系统架构设计
2.1 存储空间规划
STM32F105VC这款512KB Flash的芯片,我们采用经典的BootLoader+APP双区设计:
code复制0x08000000 - 0x08007FFF (32KB) BootLoader区
0x08008000 - 0x0807FFFF (480KB) APP区
这种分区不是随意定的,而是经过精心计算:
- BootLoader需要包含CAN驱动、Flash操作、CRC校验等核心功能,实测编译后约18KB,预留32KB为后续功能扩展留足空间
- APP区从0x08008000开始,正好是32KB边界,符合STM32 Flash扇区划分(第5扇区开始)
重要提示:在Keil中配置APP程序时,务必修改Target选项中的IROM1起始地址为0x08008000,否则会导致程序运行异常。
2.2 功能模块划分
BootLoader需要实现以下关键功能链:
- 硬件初始化:时钟树配置→CAN初始化→GPIO配置
- 升级检测:通过CAN报文或GPIO电平判断是否需要进入升级模式
- 固件接收:采用分块传输机制,每包数据带序列号和CRC校验
- 固件写入:使用STM32标准库的FLASH_ProgramHalfWord函数
- 跳转执行:检查APP首地址有效性后执行向量表重定向
APP程序则需要:
- 修改中断向量表偏移量(SCB->VTOR = 0x08008000)
- 实现业务功能(如Modbus通信、数据采集等)
- 预留升级触发接口(如特定CAN指令)
3. 关键实现细节
3.1 BootLoader的CAN通信协议
我们设计了一套简单高效的传输协议:
| 字段 | 长度 | 说明 |
|---|---|---|
| 帧ID | 4字节 | 0x18FFA001(发送)0x18FFA002(接收) |
| 包类型 | 1字节 | 0x01:启动传输 0x02:数据包 0x03:结束传输 |
| 包序号 | 2字节 | 从0开始递增 |
| 数据长度 | 1字节 | 有效数据长度(最大8字节) |
| 数据内容 | N字节 | 固件数据 |
| CRC16 | 2字节 | 对整个数据包的校验 |
实际传输时采用分块机制,每包传输6字节有效数据(CAN FD可优化此效率)。上位机工具的工作流程:
c复制// 伪代码示例
void send_firmware() {
send_start_frame(file_size);
while(!file_end) {
data = read_6bytes();
send_data_frame(seq++, data);
}
send_end_frame();
}
3.2 Flash操作安全机制
Flash编程需要特别注意以下几点:
- 解锁顺序必须严格遵循:
c复制FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_BSY | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
- 擦除操作以扇区为单位:
c复制FLASH_ErasePage(0x08008000); // 擦除第5扇区
- 写入必须半字(16bit)对齐:
c复制for(int i=0; i<len; i+=2) {
uint16_t data = *(uint16_t*)(buf+i);
FLASH_ProgramHalfWord(addr+i, data);
}
血泪教训:我曾因忘记FLASH_Lock()导致Flash内容被意外修改,现在都会在关键操作后立即加锁。
3.3 跳转执行机制
从BootLoader跳转到APP需要完成以下步骤:
c复制typedef void (*pFunction)(void);
pFunction Jump_To_Application;
void jump_to_app(uint32_t app_addr) {
if(((*(__IO uint32_t*)app_addr) & 0x2FFE0000) == 0x20000000) { // 检查栈指针有效性
Jump_To_Application = (pFunction)(*(__IO uint32_t*)(app_addr + 4)); // 复位向量
__set_MSP(*(__IO uint32_t*)app_addr); // 设置主栈指针
Jump_To_Application(); // 跳转
}
}
4. 开发环境搭建
4.1 硬件连接示意图
code复制[PC] <---USB---> [CAN分析仪] <---CAN_H/CAN_L---> [STM32F105]
(如CANalyst-II) (120Ω终端电阻)
4.2 软件工具链配置
-
Keil工程配置要点:
- BootLoader工程:ROM地址0x08000000,Size 0x8000
- APP工程:ROM地址0x08008000,Size 0x78000
- 两个工程都需勾选"Use MicroLIB"以减小体积
-
生成bin文件的方法:
在Keil的User选项卡中添加以下post-build命令:code复制fromelf.exe --bin -o "$L@L.bin" "#L" -
上位机工具开发建议:
- 使用Python+python-can库快速原型开发
- 传输进度显示和断点续传功能很实用
- 建议加入超时重传机制(我设置的是500ms)
5. 常见问题排查
5.1 固件传输失败
现象:上位机显示发送完成,但设备未跳转到APP
排查步骤:
- 检查CAN总线终端电阻(需120Ω)
- 用逻辑分析仪抓取CAN波形,确认数据完整性
- 验证BootLoader中的CRC校验算法是否与上位机一致
- 检查Flash写入前后的数据对比
5.2 APP程序无法运行
现象:跳转后程序跑飞
解决方案:
- 确认APP工程的ROM地址配置正确
- 检查APP中是否设置了VTOR:SCB->VTOR = FLASH_BASE | 0x8000;
- 确保APP中使用的中断与BootLoader无冲突
- 检查链接脚本中的堆栈大小设置(建议至少1K)
5.3 升级后功能异常
现象:新固件运行后部分功能不正常
可能原因:
- Flash写入时电压不稳导致数据错误(建议升级时保证供电)
- 部分全局变量未初始化(在APP开头添加硬件初始化)
- 新旧固件的参数存储区地址冲突(建议使用单独的Flash扇区存参数)
6. 性能优化技巧
经过多个项目实践,我总结出以下优化经验:
-
传输速度优化:
- 将CAN波特率设置为1Mbps(需硬件支持)
- 采用CAN FD协议(STM32F105不支持,但FDCAN系列可以)
- 增加每包数据量(标准CAN最多8字节)
-
可靠性增强:
- 实现滑动窗口协议(我用的窗口大小为4)
- 添加重传计数限制(建议3次)
- 在Flash写入前先擦除整个APP区(避免部分更新导致的异常)
-
安全机制:
- 增加固件签名验证(ECDSA算法)
- 实现回滚机制(保留上一个有效版本)
- 添加看门狗监控(防止升级过程死机)
这套方案已经在工业现场稳定运行超过2年,累计完成超过10万次远程升级。最关键的是要确保BootLoader自身的健壮性,我的做法是:
- 在BootLoader中禁用所有中断
- 关键操作添加硬件CRC校验
- 实现简单的故障恢复机制(长按按键恢复出厂固件)
对于需要更高安全性的场景,可以考虑结合STM32的读保护功能(RDP)和选项字节编程,防止固件被非法读取或修改。