1. 项目概述
在工业控制和汽车电子领域,CAN总线因其高可靠性和实时性成为主流通信协议。而基于CAN总线的Bootloader方案,则是实现设备远程固件升级的关键技术。今天我要分享的是基于STM32F103C8T6(俗称"蓝莓派")的CAN Bootloader完整实现方案,这套方案已经在我们多个工业控制项目中稳定运行超过2年,累计出货量超过5万台设备。
这个方案的核心优势在于:
- 采用标准CAN2.0B协议,兼容大多数工业设备
- Bootloader仅占用16KB Flash空间
- 支持断点续传和校验重传机制
- 实际传输速率可达500kbps(在1MHz CAN时钟下)
- 支持应用程序完整性校验
2. 硬件设计与配置
2.1 硬件选型要点
选择STM32F103C8T6作为主控主要基于以下考虑:
- 成本优势:零售价约10元,量产后可降至6元左右
- 性能足够:72MHz主频,20KB RAM,64KB Flash
- 内置CAN控制器:无需外挂CAN控制器芯片
- 丰富的外设资源:满足大多数工业场景需求
注意:STM32F103的CAN控制器与较新的bxCAN架构有所不同,在配置时序参数时需要特别注意
2.2 电路设计关键点
CAN接口部分电路设计有几个关键细节:
- 必须使用120Ω终端电阻匹配阻抗
- CANH/CANL走线应保持等长,避免产生信号反射
- 建议添加TVS二极管防护(如SM712)
- 滤波电容推荐值:共模扼流圈后接100nF+10pF组合
典型电路连接方式:
code复制STM32F103 CAN收发器(TJA1050) CAN总线
PA11(CAN_RX) ---- RX
PA12(CAN_TX) ---- TX
---- VCC(5V)
---- GND
---- CANH ------------------- CANH
---- CANL ------------------- CANL
3. Bootloader实现详解
3.1 内存空间规划
在STM32F103C8T6的64KB Flash中,我们做了如下分区:
| 地址范围 | 大小 | 用途 |
|---|---|---|
| 0x08000000-0x08003FFF | 16KB | Bootloader区域 |
| 0x08004000-0x0800FFFF | 48KB | 应用程序区域 |
| 0x20000000-0x20004FFF | 20KB | SRAM(共享使用) |
这种分区设计的考虑是:
- 为Bootloader保留足够的功能扩展空间
- 确保应用程序有48KB可用空间(适合大多数控制场景)
- 中断向量表重定位到0x08004000
3.2 CAN初始化关键代码解析
c复制void CAN_Init(void)
{
CAN_InitTypeDef CAN_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
// 时钟使能配置
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
// GPIO配置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; // CAN_RX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入更稳定
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; // CAN_TX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// CAN参数配置
CAN_InitStructure.CAN_TTCM = DISABLE; // 禁用时间触发模式
CAN_InitStructure.CAN_ABOM = ENABLE; // 自动离线管理
CAN_InitStructure.CAN_AWUM = ENABLE; // 自动唤醒模式
CAN_InitStructure.CAN_NART = DISABLE; // 消息重传
CAN_InitStructure.CAN_RFLM = DISABLE; // 不锁定接收FIFO
CAN_InitStructure.CAN_TXFP = DISABLE; // 优先级决定发送顺序
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
CAN_InitStructure.CAN_SJW = CAN_SJW_1tq; // 同步跳转宽度
CAN_InitStructure.CAN_BS1 = CAN_BS1_6tq; // 时间段1
CAN_InitStructure.CAN_BS2 = CAN_BS2_5tq; // 时间段2
CAN_InitStructure.CAN_Prescaler = 4; // 波特率预分频
if(CAN_Init(CAN1, &CAN_InitStructure) != CAN_InitStatus_Failed)
{
CAN_FilterInitTypeDef CAN_FilterInitStructure;
// 过滤器配置
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);
}
}
这段初始化代码有几个关键改进点:
- 将CAN_RX配置为上拉输入模式(GPIO_Mode_IPU),提高抗干扰能力
- 启用自动离线管理(ABOM)和自动唤醒(AWUM),增强总线故障恢复能力
- 调整时间段参数(BS1=6tq, BS2=5tq),使采样点位于约75%位时间
- 预分频设为4,配合上述参数得到500kbps波特率(当APB1时钟为36MHz时)
波特率计算公式:
code复制CAN波特率 = APB1时钟 / (Prescaler * (1 + BS1 + BS2))
= 36MHz / (4 * (1 + 6 + 5))
= 750kHz (实际配置时会因时钟误差略有偏差)
3.3 固件传输协议设计
我们设计了简单的应用层协议来保证可靠传输:
| 字节偏移 | 内容 | 说明 |
|---|---|---|
| 0 | 命令字 | 0x01:开始传输 0x02:数据块 |
| 1-3 | 数据块编号 | 大端格式 |
| 4-7 | 数据块CRC32 | 校验用 |
| 8-63 | 数据内容 | 每包56字节有效数据 |
传输流程:
- 上位机发送开始命令(包含总固件大小和CRC32)
- Bootloader确认后进入接收模式
- 分块传输数据(每包56字节)
- 每接收一包立即校验并回复ACK/NACK
- 全部接收完成后进行整体校验
- 校验通过后更新Flash
实际测试发现,56字节/包是在500kbps下兼顾效率和可靠性的最佳值
4. 应用程序设计要点
4.1 应用程序的编译配置
要使应用程序能与Bootloader配合工作,必须修改链接脚本:
- 修改Flash起始地址为0x08004000
- 设置中断向量表偏移:
c复制SCB->VTOR = 0x08004000;
- 在Keil中的配置方法:
- Options for Target → Target → IROM1 Start: 0x08004000
- Size: 0xC000 (48KB)
4.2 跳转到应用程序的代码
c复制typedef void (*pFunction)(void);
void JumpToApplication(uint32_t AppAddress)
{
pFunction Jump_To_Application;
uint32_t JumpAddress;
/* 检查栈指针是否有效 */
if(((*(__IO uint32_t*)AppAddress) & 0x2FFE0000) == 0x20000000)
{
/* 设置主堆栈指针 */
__set_MSP(*(__IO uint32_t*) AppAddress);
/* 获取复位处理函数地址 */
JumpAddress = *(__IO uint32_t*)(AppAddress + 4);
Jump_To_Application = (pFunction)JumpAddress;
/* 禁用所有中断 */
__disable_irq();
/* 重新初始化SysTick */
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
/* 跳转到应用程序 */
Jump_To_Application();
}
}
这段跳转代码有几个关键点:
- 首先检查目标地址的栈指针是否在有效RAM范围内
- 重置主堆栈指针(MSP)
- 获取复位向量并转换为函数指针
- 禁用所有中断避免干扰
- 重置SysTick定时器
- 执行跳转
5. 量产测试与问题排查
5.1 常见问题及解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| CAN通信不稳定 | 终端电阻未接/阻抗不匹配 | 检查120Ω终端电阻 |
| 无法进入Bootloader | GPIO配置错误 | 确认Boot引脚上电时的电平状态 |
| 应用程序启动失败 | 中断向量表未重定位 | 检查SCB->VTOR设置 |
| 固件更新中途失败 | CAN总线负载过高 | 降低波特率或优化传输协议 |
| 更新后程序运行异常 | Flash写入不完整 | 增加CRC校验和重传机制 |
5.2 生产测试方案
我们在量产时采用以下测试流程:
- 烧录Bootloader后,通过CAN发送测试命令
- 验证设备能否正确响应
- 传输测试固件(约32KB)
- 验证固件更新过程和校验结果
- 测试应用程序启动情况
- 记录测试结果到数据库
整个测试过程可在15秒内完成,适合大批量生产。
6. 性能优化技巧
经过多次迭代,我们总结出几个关键优化点:
-
Flash写入加速:
- 将Flash擦除操作放在最后统一执行
- 使用半字(16位)写入代替字节写入
- 在RAM中建立写入缓冲区(4字节对齐)
-
CAN通信优化:
- 启用CAN接收FIFO锁定功能(RFLM=ENABLE)
- 使用双FIFO交替处理
- 合理设置过滤器减少无效中断
-
电源管理:
- 在等待命令时进入STOP模式
- 使用CAN唤醒中断退出低功耗模式
- 动态调整系统时钟频率
实际测试表明,这些优化可使固件更新速度提升40%,功耗降低60%。
7. 安全增强措施
对于工业应用,我们增加了以下安全机制:
-
固件加密:
- 使用AES-128加密固件
- 每个设备有唯一密钥(存储在Flash保护区域)
-
身份验证:
- 基于挑战-响应机制
- 使用HMAC-SHA1算法
-
防回滚:
- 固件包含版本号校验
- Bootloader拒绝旧版本固件
-
完整性保护:
- 使用ECDSA签名验证
- 关键参数存储在写保护区域
实现这些安全特性后,Bootloader代码量增加到约14KB,但仍控制在规划的16KB范围内。