在嵌入式开发领域,产品固件升级一直是个绕不开的痛点。传统方式要么需要拆机连接调试器,要么依赖串口这种低速接口,效率低下且操作繁琐。特别是在工业现场,设备往往安装在难以触及的位置,频繁拆装既不现实也不安全。
基于CAN总线的bootloader方案完美解决了这些痛点。CAN总线天生具备抗干扰能力强、传输距离远(最远可达10km)、多节点通信等优势,特别适合工业环境。通过这个方案,工程师可以在不拆机的情况下,通过CAN总线将新的hex文件直接烧录到STM32的Flash中,实现远程固件更新。
我在汽车电子行业第一次接触这个需求时,设备安装在车体内部,每次升级都需要拆解中控台,耗时又费力。后来我们团队开发了这套CAN bootloader方案,将固件升级时间从原来的2小时缩短到5分钟,大大提升了售后效率。
这个bootloader系统包含两个主要部分:上位机程序(负责发送hex文件)和下位机bootloader(负责接收并烧录)。它们通过CAN总线进行通信,具体流程如下:
CAN ID分配方案:
数据分片策略:
由于CAN帧最多只能携带8字节数据,我们需要将hex文件的每行数据进行拆分。实际采用每包传输64字节有效数据,分8个CAN帧发送,包含以下字段:
提示:CAN总线建议使用250kbps或500kbps速率,既能保证传输速度又具备良好的抗干扰性。实际测试中,500kbps下传输1MB固件约需3分钟。
典型的STM32F103内存分配如下:
code复制0x08000000-0x08003FFF Bootloader (16KB)
0x08004000-0x0801FFFF Application (112KB)
0x08020000-0x0803FFFF Configuration (128KB)
关键点在于链接脚本的修改。我们需要确保:
跳转应用程序的实现:
c复制void JumpToApplication(uint32_t appAddress)
{
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t StackPointer = *(volatile uint32_t*)appAddress;
uint32_t ResetHandler = *(volatile uint32_t*)(appAddress + 4);
__disable_irq();
SCB->VTOR = appAddress; // 重设向量表
__set_MSP(StackPointer);
Jump_To_Application = (pFunction)ResetHandler;
Jump_To_Application();
}
CAN接收处理:
c复制void CAN_RX_Handler(CAN_HandleTypeDef *hcan)
{
CAN_RxHeaderTypeDef rxHeader;
uint8_t rxData[8];
HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxHeader, rxData);
switch(rxHeader.StdId)
{
case CMD_ENTER_BOOT:
EnterBootloaderMode();
break;
case CMD_DATA_PACKET:
ProcessDataPacket(rxData);
break;
// 其他命令处理...
}
}
Hex文件每行格式示例:
`:1000000000040020D10B0008D90B0008E10B0008C8'
解析流程:
由于CAN传输速度限制,我们需要实现双缓冲机制:
当编程缓冲区满1KB时,触发Flash写入操作。STM32的Flash编程需要注意:
三级校验机制确保数据可靠:
关键保护措施:
推荐使用Python+PCAN开发上位机工具,主要流程:
python复制import can
from intelhex import IntelHex
def send_hex_file(can_bus, hex_file):
ih = IntelHex(hex_file)
# 发送进入bootloader命令
msg = can.Message(arbitration_id=0x100, data=[0xAA], is_extended_id=False)
can_bus.send(msg)
# 分段发送hex数据
for seg in ih.segments():
data = ih.tobinarray(start=seg[0], end=seg[1])
for i in range(0, len(data), 64):
chunk = data[i:i+64]
send_data_packet(can_bus, i//64, chunk)
在STM32F103C8T6平台测试结果:
| 测试项 | 结果 |
|---|---|
| CAN波特率 | 500kbps |
| 传输1MB固件时间 | 182秒 |
| Flash写入速度 | 约5.5KB/s |
| 成功率 | 1000次测试零失败 |
问题1:无法进入bootloader模式
问题2:数据传输中途失败
问题3:写入后程序不运行
我在实际项目中发现,增加简单的压缩算法(如LZ77)可以将传输时间减少40%。一个技巧是在hex文件解析阶段就进行压缩,而不是传输整个文件。例如,连续相同的0xFF(擦除后的值)可以用计数方式压缩表示。
另一个实用技巧是在Flash中预留一个配置区,存储设备序列号、硬件版本等信息。bootloader可以读取这些信息并与固件匹配,避免错误的升级操作。我们曾经因此避免了一次大规模的错误升级事故。