1. 项目概述:STM32F103的CAN bootloader设计初衷
在嵌入式产品量产和维护过程中,固件升级是个高频刚需。传统方式需要拆机连接SWD调试器,既增加人力成本又影响设备密封性。基于CAN总线的bootloader方案完美解决了这个痛点——通过车辆或工业设备原有的CAN网络即可完成远程固件更新。
我最近为某工业控制器设计的CAN bootloader,在STM32F103C8T6上实现了以下核心功能:
- 通过CAN2.0B协议接收并校验固件包
- 支持APP程序跳转前的完整性验证
- 自动回滚机制确保升级失败时的系统可靠性
- 仅占用16KB Flash空间(Bootloader区)
2. 硬件设计关键点
2.1 最小系统搭建
使用STM32F103C8T6作为主控时需注意:
- 复位电路:10kΩ上拉电阻+100nF电容组合(避免电源波动误触发)
- 启动模式:BOOT0引脚通过10kΩ电阻接地(从主闪存启动)
- CAN接口:推荐使用TJA1050收发器,终端电阻120Ω
重要提示:CAN_H和CAN_L必须使用双绞线,线距保持恒定以减少共模干扰
2.2 电源设计要点
- 添加LC滤波电路(22μH电感+10μF电容)
- 调试阶段建议预留电流测量点
- 使用TVS二极管防护CAN总线(如SMBJ6.5CA)
3. 软件架构解析
3.1 内存空间划分
c复制/* 内存映射配置 */
#define FLASH_BASE 0x08000000
#define BOOTLOADER_SIZE 0x4000 // 16KB
#define APP_ADDRESS (FLASH_BASE + BOOTLOADER_SIZE)
3.2 启动流程设计
- 上电后运行Bootloader
- 检测GPIO触发引脚(如PA0持续低电平)
- 若未触发则跳转到APP区
- 若触发则进入固件接收模式
3.3 CAN协议帧设计
| 帧类型 | 帧ID | 数据长度 | 数据内容 |
|---|---|---|---|
| 握手 | 0x701 | 1 | 0x55 |
| 数据 | 0x702 | 8 | 分包固件数据 |
| 校验 | 0x703 | 4 | CRC32值 |
| 执行 | 0x704 | 1 | 0xAA(跳转APP) |
4. 核心代码实现
4.1 Flash编程关键函数
c复制void FLASH_WritePage(uint32_t addr, uint8_t *data) {
FLASH_Unlock();
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
if(FLASH_ErasePage(addr) != FLASH_COMPLETE) {
// 擦除异常处理
}
for(int i=0; i<1024; i+=2) {
uint16_t word = data[i+1]<<8 | data[i];
FLASH_ProgramHalfWord(addr+i, word);
}
FLASH_Lock();
}
4.2 跳转到APP的汇编代码
assembly复制__asm void JumpToApplication(uint32_t addr) {
LDR SP, [R0] ; 加载APP堆栈指针
LDR PC, [R0, #4] ; 加载复位向量
}
5. 实战调试经验
5.1 常见问题排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| CAN接收不到数据 | 波特率不匹配 | 用示波器测量实际波特率 |
| 跳转后死机 | 中断向量表未重映射 | 在APP中调用SCB->VTOR=APP_ADDRESS |
| Flash写入失败 | 未解锁FLASH | 检查FLASH_Unlock()返回值 |
| 校验通不过 | 内存对齐问题 | 确保CRC计算使用__packed指针 |
5.2 性能优化技巧
- 采用双缓冲机制:在接收第N包时处理第N-1包数据
- 使用DMA接收CAN数据(节省CPU资源)
- 对FLASH写入进行流水线优化(实测速度提升40%)
6. 量产测试方案
6.1 自动化测试脚本示例
python复制import can
bus = can.interface.Bus(channel='can0', bustype='socketcan')
def send_firmware(file):
with open(file, 'rb') as f:
data = f.read()
# 发送握手帧
bus.send(can.Message(arbitration_id=0x701, data=[0x55]))
# 分包发送固件
for i in range(0, len(data), 8):
bus.send(can.Message(arbitration_id=0x702, data=data[i:i+8]))
# 发送CRC
crc = calculate_crc(data)
bus.send(can.Message(arbitration_id=0x703, data=crc.to_bytes(4, 'little')))
6.2 压力测试参数
- 连续升级测试:≥1000次
- 异常断电测试:随机在50%-90%进度时断电
- 兼容性测试:不同CAN分析仪(周立功/PCAN等)
我在实际项目中发现,当升级包超过128KB时,建议在APP中实现分段校验机制。同时推荐使用开源工具CanOpen协议栈中的EDS文件管理方式,可以很方便地实现版本控制和升级记录功能。
