1. 项目背景与核心价值
在工业控制领域,设备固件升级一直是个让人头疼的问题。传统方式需要拆解设备外壳连接串口,不仅效率低下,在产线环境或设备安装在复杂位置时更是雪上加霜。基于CAN总线的bootloader方案彻底改变了这一局面,让远程固件升级成为可能。
这个方案已经在STM32F103平台上经过量产验证,累计完成超过5万次安全升级。相比传统方式,它具有三个显著优势:
- 无需物理接触设备,通过CAN总线即可完成升级
- 支持断点续传,即使升级过程中断电也不会导致设备变砖
- 内置多重安全校验机制,确保固件完整性和兼容性
2. 系统架构设计
2.1 整体框架
系统采用经典的bootloader+APP双区设计:
- Bootloader区(16KB):负责固件接收、校验和跳转
- APP区(112KB):用户应用程序存储区
- 共享内存区(256B):用于bootloader与APP之间的参数传递
这种设计保证了即使APP区固件损坏,设备仍然可以通过bootloader恢复,大大提高了系统可靠性。
2.2 关键设计决策
选择CAN总线而非其他通信方式主要基于以下考虑:
- 工业环境适应性:CAN天生抗干扰能力强
- 广播特性:支持同时升级多个设备
- 距离优势:最远可达1km(波特率5kbps时)
- 带宽足够:对于固件升级场景,50kbps的速率已经足够
3. CAN总线配置精要
3.1 初始化参数详解
c复制void CAN1_Mode_Init(u16 brp, u8 ts1, u8 ts2, u8 mode)
{
CAN_FilterInitTypeDef filter;
hcan.Instance = CAN1;
hcan.Init.Prescaler = brp; // 波特率分频系数
hcan.Init.Mode = mode; // 工作模式
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = ts1; // 时间段1
hcan.Init.TimeSeg2 = ts2; // 时间段2
hcan.Init.AutoRetransmission = ENABLE; // 关键配置!
HAL_CAN_Init(&hcan);
// 过滤器配置
filter.FilterIdHigh = 0x123 << 5; // 设备ID
filter.FilterMode = CAN_FILTERMODE_IDMASK;
HAL_CAN_ConfigFilter(&hcan, &filter);
}
波特率计算公式:
code复制CAN波特率 = APB1时钟 / (Prescaler * (1 + TimeSeg1 + TimeSeg2))
对于36MHz的APB1时钟,典型配置:
- 500kbps:brp=6, ts1=5, ts2=4
- 250kbps:brp=12, ts1=5, ts2=4
- 125kbps:brp=24, ts1=5, ts2=4
3.2 抗干扰实战技巧
- 自动重传必须开启:我们曾因关闭此功能导致10%的升级失败率
- 总线电压监测:升级前检查CAN_H和CAN_L间电压,低于2.5V拒绝操作
- 双CRC校验:传输CRC(校验数据完整性)和存储CRC(校验Flash写入正确性)
- 错误帧统计:连续收到3个错误帧即暂停升级,等待总线恢复
4. Bootloader核心实现
4.1 固件接收与存储
采用分块接收机制,每块256字节,带序号标识:
- 接收固件头信息(包含固件大小、版本号等)
- 按序号接收数据块
- 每块写入前擦除对应Flash扇区
- 支持断点续传,记录最后成功接收的块号
c复制typedef struct {
uint32_t fw_size;
uint16_t fw_ver;
uint32_t crc;
uint8_t reserved[58]; // 补齐64字节
} FirmwareHeader;
4.2 安全跳转实现
c复制void jump_to_app(uint32_t app_addr)
{
// 1. 关闭所有外设中断
__disable_irq();
HAL_CAN_Stop(&hcan);
// 2. 重设中断向量表
SCB->VTOR = app_addr & 0x1FFFFF;
// 3. 设置堆栈指针并跳转
__set_MSP(*(volatile uint32_t*)app_addr);
((void (*)(void))(*(volatile uint32_t*)(app_addr + 4)))();
}
关键注意事项:
- 必须按顺序执行:关中断→停外设→重设VTOR→跳转
- STM32F103的VTOR偏移需要特殊处理
- 堆栈指针必须校验(检查是否指向RAM有效地址)
5. APP工程配置要点
5.1 链接脚本修改
ld复制MEMORY {
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
FLASH (rx) : ORIGIN = 0x8004000, LENGTH = 112K
}
5.2 中断向量表偏移
c复制// system_stm32f1xx.c
#define VECT_TAB_OFFSET 0x4000
5.3 启动文件调整
修改startup_stm32f103xe.s中的Flash起始地址:
asm复制.word 0x8004000
6. 量产验证与问题排查
6.1 典型问题汇总
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 升级后CAN异常 | 跳转前未关闭CAN外设 | 添加HAL_CAN_Stop() |
| 偶发校验失败 | Flash写入不完整 | 增加存储CRC校验 |
| 设备无响应 | 堆栈指针被篡改 | 加强地址合法性检查 |
| 升级过程卡死 | 电动工具干扰 | 增加总线电压监测 |
6.2 可靠性提升措施
-
双看门狗机制:
- 独立看门狗(IWDG):防止代码跑飞
- 窗口看门狗(WWDG):防止进程卡死
-
电源监测:
c复制if (__HAL_PWR_GET_FLAG(PWR_FLAG_PVDO)) { // 电压低于2.0V,拒绝升级 } -
固件回滚:
保留上一版本固件,当新固件校验失败时自动回退
7. 开发工具链优化
7.1 自动化构建脚本
makefile复制$(OBJCOPY) -O binary $@.elf $@.bin
truncate -s %4 $@.bin
crc32 $@.bin >> $@.bin
7.2 波特率参数生成工具
使用Excel表格自动计算CAN参数组合,避免手工计算错误:
| 目标波特率 | APB1时钟 | Prescaler | TS1 | TS2 | 实际波特率 | 误差 |
|---|---|---|---|---|---|---|
| 500kbps | 36MHz | 6 | 5 | 4 | 500kbps | 0% |
| 1Mbps | 36MHz | 3 | 5 | 4 | 1Mbps | 0% |
8. 性能优化技巧
-
Flash写入加速:
- 使用半字写入(HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,...))
- 批量写入前统一擦除整个扇区
-
CAN接收优化:
c复制// 使用FIFO0接收中断 HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING); -
内存管理:
- 使用静态分配替代动态内存
- 关键缓冲区添加__attribute__((aligned(4)))
这个方案经过两年多的现场验证,升级成功率从最初的97.3%提升到99.98%。最关键的经验是:工业级产品必须考虑最恶劣的环境条件,任何小概率事件在量产基数下都会变成必然事件。