资讯动态

STM32F103 CAN Bootloader开发实战:工业现场固件在线升级方案

发布时间:2026/9/16 16:32:43 来源:尧图企业网站定制
简介本资源是一套基于STM32F103的CAN总线在线升级Bootloader完整实现方案面向嵌入式开发工程师、高校电子类专业学生及固件升级功能开发者解决工业与汽车电子场景下无需拆机即可安全更新MCU固件的核心需求。压缩包含187个文件以66个C源文件如stm32f10x_flash.c、stm32f10x_tim.c实现底层驱动与Bootloader逻辑72个头文件.h提供接口定义与配置宏36个汇编文件.s保障启动与中断处理可靠性另有bin固件镜像、Keil工程文件uvproj/uvopt及调试备份文件整体包体仅674KB轻量易集成。已有405人学习下载资源结构清晰覆盖CAN通信配置、CRC校验、Flash页擦写编程、双区备份跳转、异常回滚等关键模块附带多版本固件ProjectV3.0.bin/ProjectV4.0.bin便于对比验证是深入理解STM32 Bootloader设计与CAN OTA机制的高价值实践素材。1. 基于 CAN 总线的 STM32F103 Bootloader为什么不用 UART 或 USB 升级而选 CAN在工业现场、汽车电子或分布式传感器节点中你常会遇到这样的场景设备已部署在配电柜深处、电机控制箱内或户外防爆壳体中物理接触困难但 CAN 总线早已铺好——它连着主控 PLC、HMI 或上位机具备强抗干扰、多主仲裁、远距离500m125kbps和天然拓扑鲁棒性。此时用 UART 接线刷固件等于要求运维人员带 USB 转串口线钻进机柜用 USB 则需额外供电与物理接口支持且无法复用现有布线。而一个能通过 CAN 总线接收新固件、校验、擦写 Flash 并安全跳转的 bootloader就能让整条产线设备在不停机状态下完成批量固件更新。本方案聚焦 STM32F103 系列主流 Cortex-M3Flash 64–256KB内置 CAN 控制器不依赖 J-Link 或 ST-Link 硬件调试器仅靠标准 CAN 收发器如 TJA1050和上位机 CAN 卡即可完成在线升级。它不是通用 Bootloader 框架而是针对 F103 的寄存器级实现从 CAN 初始化时钟分频与位定时参数BS1/BS2/SJW配置到 Flash 写保护解除、扇区擦除时序控制再到 APP 跳转前的 MSP 和 PC 恢复——每一步都直面芯片手册第 27 章CAN、第 3 章系统架构和第 2 章存储器映射的硬约束。2. 从零构建 CAN Bootloader初始化、协议解析与 Flash 操作三步闭环2.1 配置 CAN 外设以满足工业现场通信鲁棒性要求STM32F103 的 bxCAN 控制器必须工作在精确的位定时参数下才能与 PLC 或 CAN 分析仪稳定通信。常见错误是直接套用 1Mbps 标准值却忽略晶振偏差与总线长度影响。以 8MHz 外部晶振为例若目标波特率为 500kbps需手动计算 BS1、BS2 和 SJW// CAN 波特率计算核心Tq (CAN_Prescaler) / PCLK1 // 500kbps → Tbit 2000ns → 需 16 Tq/bit → Tq 125ns → Prescaler 1 (PCLK18MHz) // 同步段固定为 1TqBS1 取 8Tq传播相位缓冲1BS2 取 7Tq相位缓冲2SJW1Tq CAN_InitTypeDef CAN_InitStructure; CAN_DeInit(CAN1); CAN_InitStructure.CAN_TTCM DISABLE; CAN_InitStructure.CAN_ABOM DISABLE; // 自动离线恢复关闭由应用层控制 CAN_InitStructure.CAN_AWUM DISABLE; // 自动唤醒关闭 CAN_InitStructure.CAN_NART ENABLE; // 禁止自动重传确保单帧指令原子性 CAN_InitStructure.CAN_RFLM DISABLE; // FIFO 锁定模式关闭使用 FIFO0 接收 CAN_InitStructure.CAN_TXFP ENABLE; // 发送优先级由 ID 决定非 FIFO 顺序 CAN_InitStructure.CAN_Mode CAN_Mode_Normal; CAN_InitStructure.CAN_SJW CAN_SJW_1tq; // 同步跳跃宽度1Tq抗晶振抖动 CAN_InitStructure.CAN_BS1 CAN_BS1_8tq; // 时间段18Tq覆盖传播延迟与相位缓冲1 CAN_InitStructure.CAN_BS2 CAN_BS2_7tq; // 时间段27Tq相位缓冲2留足采样点调整余量 CAN_InitStructure.CAN_Prescaler 1; // 分频系数1 → Tq 125ns CAN_Init(CAN1, CAN_InitStructure);提示CAN_BS1和CAN_BS2的分配直接影响采样点位置。F103 手册规定采样点应在 Tbit 的 75% 左右。此处 BS18、BS27 → 采样点位于第 189 Tq即 9/1656.25%虽略低于 75%但因工业现场总线反射小、终端匹配良好实测误码率 1e-9若现场存在长分支线可将 BS1 减至 6、BS2 增至 9使采样点后移至 7/1643.75%配合 SJW2tq 动态补偿。2.2 定义轻量级 CAN 固件升级协议并实现帧解析状态机UART 升级常用 XMODEM但 CAN 帧最大仅 8 字节数据需设计紧凑协议。本方案采用4 字节命令头 4 字节负载的固定格式避免长度字段开销字节含义示例值说明0命令类型0x55CMD_GET_VER获取 Bootloader 版本1子命令/标志0x00CMD_FLASH_ERASE时为扇区号0–1272数据低字节0x00CMD_FLASH_WRITE时为地址低字节3数据高字节0x08地址高字节组合为 0x08004–7有效载荷0x00000000命令无参数时全 0写数据时为 32 位字接收端使用环形缓冲区 状态机处理typedef enum { RX_STATE_IDLE, RX_STATE_HEADER, RX_STATE_PAYLOAD } rx_state_t; static rx_state_t rx_state RX_STATE_IDLE; static uint8_t rx_buffer[12]; // 4字节头 8字节数据 static uint8_t rx_index 0; void CAN_RX_IRQHandler(void) { CanRxMsg RxMessage; CAN_Receive(CAN1, CAN_FIFO0, RxMessage); if (rx_state RX_STATE_IDLE RxMessage.StdId 0x100) { // 固定指令ID rx_state RX_STATE_HEADER; rx_index 0; memcpy(rx_buffer, RxMessage.Data, 4); // 先存头 rx_index 4; } else if (rx_state RX_STATE_HEADER RxMessage.StdId 0x101) { memcpy(rx_buffer[4], RxMessage.Data, 4); // 再存后4字节 process_can_command(); // 解析并执行 rx_state RX_STATE_IDLE; } }注意CAN_FIFO0必须配置为按 ID 过滤非掩码模式只接收 ID0x100头帧和 0x101数据帧。若用掩码过滤可能漏帧若未启用 FIFOCAN 中断可能被高优先级任务阻塞导致丢帧。2.3 安全擦写 Flash解除写保护、扇区擦除与编程验证全流程F103 的 Flash 编程有严格时序必须先解锁再按扇区擦除最小单位 1KB最后按 32 位字编程。关键陷阱在于——APP 区域0x0800 4000 起的扇区擦除会锁死整个 Flash直到操作完成// 1. 解锁 Flash需连续写入两密钥 FLASH_Unlock(); FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR); // 2. 计算目标地址所属扇区F103C8: 0x08000000–0x08003FFF → 扇区0 uint32_t addr 0x08004000 offset; // APP 起始地址 偏移 uint16_t sector GetSector(addr); // 查表得扇区号0–127 // 3. 擦除扇区阻塞式约 20ms/扇区 FLASH_Status status FLASH_ErasePage(addr); if (status ! FLASH_COMPLETE) { // 记录错误码0x01PGERR编程错误0x02WRPRTERR写保护 error_code status; return; } // 4. 编程 32 位字每次写 4 字节需对齐 for (uint32_t i 0; i len; i 4) { uint32_t word *(uint32_t*)data[i]; status FLASH_ProgramWord(addr i, word); if (status ! FLASH_COMPLETE) break; } // 5. 验证写入读回比对 for (uint32_t i 0; i len; i 4) { if (*(uint32_t*)(addr i) ! *(uint32_t*)data[i]) { error_code 0x03; // VERIFY_FAIL break; } } FLASH_Lock(); // 操作完毕立即上锁提示GetSector()函数需根据 F103 子型号查表。例如 F103C864KB Flash有 64 个 1KB 扇区F103ZE512KB有 128 个 4KB 扇区。务必确认addr不越界否则FLASH_ErasePage()返回FLASH_ERROR_PG。3. 实现 APP 与 Bootloader 的安全跳转向量表偏移与栈指针重置3.1 将 APP 的中断向量表重定位到指定地址Bootloader 占用 Flash 前 16KB0x0800 0000–0x0800 3FFFAPP 从 0x0800 4000 开始。但 Cortex-M3 复位后默认从 0x0800 0000 取 MSP 和复位向量因此 APP 必须将向量表起始地址设为 0x0800 4000并在启动文件中修改; startup_stm32f10x_md.s 中修改 ; 将原 Vector_Table 中的地址从 0x08000000 改为 0x08004000 .section .isr_vector,a,%progbits .align 2 .word 0x20002000 /* Top of Stack */ .word Reset_Handler /* Reset Handler */ .word NMI_Handler /* NMI Handler */ ...同时在 APP 的main()开头强制重定位// APP main.c void main(void) { // 1. 设置向量表偏移必须在启用中断前 SCB-VTOR 0x08004000; // APP 向量表基址 // 2. 初始化外设不调用 SysTick_InitBootloader 已停用 SysTick RCC_Configuration(); GPIO_Configuration(); while(1) { ... } }3.2 Bootloader 跳转前的硬件状态清理与双栈切换跳转不是简单((void (*)(void))app_addr)()必须确保关闭所有外设时钟尤其 CAN、USART、ADC避免 APP 初始化冲突清空 NVIC 中待处理中断NVIC-ICPR[0] 0xFFFFFFFF从 APP 首地址读取 MSP主栈指针和复位向量PCtypedef void (*pFunction)(void); pFunction Jump_To_Application; uint32_t JumpAddress; // 1. 关闭 Bootloader 使用的外设 CAN_DeInit(CAN1); RCC_APB1PeriphClockCmd(RCC_APB1ENR_CAN1EN, DISABLE); RCC_APB2PeriphClockCmd(RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN, DISABLE); // 2. 清空 NVIC 待处理中断 NVIC-ICPR[0] 0xFFFFFFFF; NVIC-ICPR[1] 0xFFFFFFFF; // 3. 读取 APP 的 MSP 和复位向量 if (((*(__IO uint32_t*)0x08004000) 0x2FFE0000) 0x20000000) { // 栈顶地址有效性检查 JumpAddress *(__IO uint32_t*)(0x08004004); // 复位向量地址 Jump_To_Application (pFunction)JumpAddress; // 4. 设置 MSP主栈指针 __set_MSP(*(__IO uint32_t*)0x08004000); // 5. 执行跳转 Jump_To_Application(); }注意0x08004000处必须是有效的栈顶地址0x2000xxxx否则跳转后立即 HardFault。可在 APP 的startup.s中显式定义.equ STACK_TOP, 0x20002000并链接到该地址。4. 上位机 CAN 升级工具开发基于 Python-CAN 的指令组装与进度反馈4.1 使用 python-can 构建跨平台升级脚本无需商业 CAN 分析仪软件用python-can USB-CAN 适配器如 PCAN-USB、USB2CAN即可实现。关键在于按协议组装帧并处理超时import can import time def send_can_frame(bus, cmd_id, header, payloadb\x00\x00\x00\x00): 发送标准帧cmd_id0x100头0x101数据 msg_header can.Message(arbitration_idcmd_id, dataheader, is_extended_idFalse) bus.send(msg_header) if payload ! b\x00\x00\x00\x00: msg_payload can.Message(arbitration_idcmd_id1, datapayload, is_extended_idFalse) bus.send(msg_payload) # 连接 CAN 总线Linux SocketCAN 示例 bus can.interface.Bus(bustypesocketcan, channelcan0, bitrate500000) # 步骤1获取 Bootloader 版本 send_can_frame(bus, 0x100, b\x55\x00\x00\x00) time.sleep(0.1) # 监听响应帧需另启接收线程 # 步骤2擦除 APP 扇区扇区号0 → 地址0x08004000 send_can_frame(bus, 0x100, b\x56\x00\x00\x08) # CMD_FLASH_ERASE, sector0, addr_hi0x084.2 实时进度反馈与 CRC 校验机制为防止传输错误每 128 字节数据块后发送校验帧帧ID数据内容说明0x1020xAA block_num crc16_low crc16_high主机发送校验请求0x1030xBB block_num result(0OK/1FAIL)Bootloader 返回校验结果def verify_block(bus, block_num, data): crc calculate_crc16(data) # 标准 CRC-16-IBM send_can_frame(bus, 0x102, bytes([0xAA, block_num]) crc.to_bytes(2, little)) # 等待响应超时 500ms start_time time.time() while time.time() - start_time 0.5: msg bus.recv(timeout0.1) if msg and msg.arbitration_id 0x103 and msg.data[0] 0xBB and msg.data[1] block_num: return msg.data[2] 0 # result byte return False提示calculate_crc16()必须与 Bootloader 端完全一致。推荐使用查表法256项表避免运行时计算开销。若 Bootloader 用CRC-16-IBMpoly0x8005, init0x0000则 Python 端不可用crcmod.predefined.mkCrcFun(crc-16)其 init 默认为 0xFFFF需显式指定参数。5. 现场部署必调参数与典型故障排查表5.1 五类高频问题对应的关键参数检查清单故障现象涉及模块必查参数验证方法典型值CAN 接收无中断CAN 初始化CAN_Prescaler,CAN_BS1/BS2,CAN_SJW用示波器测 CAN_H/CAN_L 差分波形看位宽是否为 2μs500kbpsPrescaler1,BS18,BS27,SJW1Flash 擦除失败WRPRTERRFlash 控制FLASH_OBR.WRPRT寄存器、FLASH_WRPR寄存器用 ST-Link Utility 读取 Option Bytes确认写保护区域未覆盖 APP 区WRP00xFFFF无保护跳转后 HardFaultAPP 启动SCB-VTOR, 栈顶地址有效性在跳转前打印*(__IO uint32_t*)0x08004000确认为 0x2000xxxx0x20002000若 RAM20KB升级中途卡死协议层帧 ID 过滤配置、FIFO 溢出检查CAN_FMR.FINIT1后是否正确设置CAN_FA1R.FA01FIFO0 使能CAN_FA1R0x00000001上位机收不到响应电气层终端电阻120Ω、CAN 收发器供电用万用表测 CAN_H–CAN_L 电压正常应为 2.5V±0.5VCAN_H3.2V,CAN_L1.8V差分 1.4V5.2 使用 ST-Link Utility 快速验证 Bootloader 功能边界当 CAN 升级失败时绕过 CAN 直接用 ST-Link 验证 Bootloader 逻辑是否完好擦除 Bootloader 区域在 ST-Link Utility 中选择Target → Erase仅擦除0x08000000–0x08003FFF烧录 Bootloader binFile → Program Download加载编译好的bootloader.bin起始地址0x08000000手动触发跳转测试连接 ST-Link点击Target → Connect在Memory Browser中输入0x08000000查看前 4 字节是否为有效栈顶如0x20002000输入0x08000004查看复位向量是否指向0x08000xxxBootloader 入口若一切正常点击Target → RunBootloader 应进入 CAN 等待循环可用逻辑分析仪抓取 CAN 引脚电平变化。提示若0x08000000处为0xFFFFFFFF说明 Flash 擦除失败或写入异常需检查FLASH_Status返回值并确认FLASH_Unlock()是否成功执行。5.3 基于 CAN 总线波形判断通信质量的实操技巧仅靠“能通”不够工业现场需量化评估。用示波器捕获 CAN_H 波形后重点测量三项上升/下降时间应 ≤ 100nsISO 11898-2 标准若 200ns检查终端电阻或收发器驱动能力隐性电平噪声CAN_H–CAN_L 差分电压在隐性态逻辑1应稳定在 0V±0.1V若波动 0.3V说明共模干扰严重需加共模电感位宽度一致性连续 10 个位的宽度标准差应 5%若某位突变如 1.8μs→2.5μs表明总线存在反射或节点失步需缩短分支线或增加终端电阻。这些参数不依赖上位机软件是判断 CAN 物理层是否达标的黄金指标。当升级失败时优先做此项检查——往往比查代码更快定位根因。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价