1. 项目背景与核心需求
在工业控制、汽车电子等领域,设备固件升级是个硬需求。传统方式需要拆机烧录,费时费力还容易出错。基于CAN总线的远程升级方案能完美解决这个问题——设备连上线就能升级,不用开箱,不用停机。
DSP28335作为TI经典的C2000系列控制器,在电机控制、电源管理等领域应用广泛。但官方提供的CAN升级方案要么太简单(只有基础通信),要么太复杂(集成太多无关功能)。我们决定自己从头打造一套完整的解决方案,包括:
- 精简高效的Bootloader(小于8KB)
- 稳定可靠的上位机软件(支持CRC校验、断点续传)
- 详细的升级协议文档
- 完整的错误处理机制
这套方案已经在多个工业项目上稳定运行3年以上,最远支持过500米线长的CAN总线升级。下面就把关键实现细节和踩过的坑分享给大家。
2. 系统架构设计
2.1 整体通信流程
code复制上位机 → CAN报文 → DSP28335 Bootloader → 用户程序区
整个升级过程分为4个阶段:
- 握手阶段(确认设备在线且支持升级)
- 擦除阶段(清空目标Flash区域)
- 编程阶段(分块传输固件数据)
- 跳转阶段(校验成功后运行新程序)
2.2 Bootloader内存布局
c复制/* 关键地址定义 */
#define APP_START 0x3F8000 // 用户程序起始地址
#define APP_END 0x3FFFFF // 用户程序结束地址
#define BL_START 0x3F4000 // Bootloader起始地址
#define BL_SIZE 0x4000 // 预留16KB空间
注意:TI编译器默认链接文件需要修改,确保Bootloader和用户程序地址不重叠。我们遇到过因为地址冲突导致程序跳转后死机的问题。
3. Bootloader实现细节
3.1 CAN通信协议设计
采用标准CAN2.0B帧格式(29位标识符),定义如下报文结构:
| 字节偏移 | 内容说明 |
|---|---|
| 0 | 命令字(0x01握手) |
| 1-2 | 数据长度 |
| 3-7 | 自定义参数 |
常用命令字定义:
- 0x01: 握手请求/响应
- 0x02: 擦除Flash
- 0x03: 数据包传输
- 0x04: 校验请求
3.2 Flash操作关键代码
c复制// Flash擦除示例
void Erase_Application(void) {
Flash_Erase(APP_START, (APP_END-APP_START)/SECTOR_SIZE);
while(Flash_isBusy()); // 等待擦除完成
}
// 数据写入示例(每次写入64bit)
void Program_Flash(Uint32 addr, Uint64 data) {
EALLOW;
Flash_Program(&data, addr, 1);
EDIS;
while(Flash_isBusy());
}
实测发现:连续写入时,每次操作后需要至少100us延时,否则会出现校验错误。TI手册里没明确写这个要求。
3.3 看门狗处理技巧
Bootloader中必须妥善处理看门狗,否则升级过程中会意外复位。我们的方案:
- 初始化时禁用看门狗
- 在耗时操作(如Flash擦除)中定期喂狗
- 跳转用户程序前恢复看门狗设置
c复制// 跳转前环境准备
void Jump_To_Application(void) {
asm(" EALLOW");
SysCtrlRegs.WDCR = 0x0028; // 启用看门狗
asm(" EDIS");
// 其他寄存器恢复...
asm(" LB #0x3F8000"); // 跳转到用户程序
}
4. 上位机开发要点
4.1 开发环境选择
使用C# + VS2019开发,主要考虑:
- 方便调用PCAN-USB等CAN适配器API
- 界面开发效率高
- 支持多线程处理
4.2 关键功能实现
- 固件文件解析:将.out文件转换为纯二进制数据
- 分块传输算法:每包最多8字节有效数据
- 进度显示:实时显示传输进度和校验状态
- 日志记录:详细记录操作过程和错误信息
4.3 CRC校验优化
采用CRC16-CCITT算法,但做了两点改进:
- 预计算CRC表加速校验
- 在DSP端同样实现该算法,实现双向校验
csharp复制// C#端CRC计算
ushort CalculateCRC(byte[] data) {
ushort crc = 0xFFFF;
foreach (byte b in data) {
crc = (ushort)((crc << 8) ^ crcTable[((crc >> 8) ^ b) & 0xFF]);
}
return crc;
}
5. 现场问题排查实录
5.1 典型故障现象:握手失败
可能原因:
- CAN波特率不匹配(需确保与设备端一致)
- 终端电阻未接(120Ω电阻必须接)
- 报文ID过滤设置错误
5.2 数据校验错误处理
遇到校验错误时:
- 自动重传当前数据包(最多3次)
- 记录错误位置到日志
- 超过重试次数则终止升级
5.3 长距离传输优化
当总线长度超过100米时:
- 降低波特率到125kbps以下
- 增加报文间隔时间(建议≥5ms)
- 使用双绞屏蔽线并良好接地
6. 文件说明与版本管理
6.1 必备文档清单
- 通信协议文档:详细定义每个命令格式
- Bootloader使用说明:包含编译配置方法
- 上位机操作手册:图文说明操作步骤
- 版本变更记录:记录每次更新的修改内容
6.2 版本号规范
采用三段式版本号:主版本.次版本.修订号
- 主版本:重大功能变更
- 次版本:新增功能
- 修订号:问题修复
例如v2.1.3表示:第2个大版本,第1次功能更新,包含3个问题修复。
7. 实测性能数据
在以下环境测试:
- DSP28335 @90MHz
- CAN波特率500kbps
- 固件大小256KB
测试结果:
| 项目 | 耗时 |
|---|---|
| 握手建立 | 50ms |
| Flash全片擦除 | 1.8s |
| 数据传输 | 4.2s |
| 校验与跳转 | 0.3s |
| 总升级时间 | ≈6.5s |
8. 扩展功能建议
- 差分升级:只传输有变化的程序部分
- A/B分区:实现无缝回滚功能
- 加密传输:增加AES-128数据加密
- 无线扩展:通过CAN转4G模块实现远程升级
实际项目中我们实现了A/B分区方案,关键实现点:
- 在Flash中划分两个相同大小的程序区
- 通过标志位识别当前活动分区
- 升级时写入非活动分区
- 校验通过后更新标志位
c复制// 分区标志位结构体
typedef struct {
Uint32 magic; // 0x55AA55AA
Uint32 version;
Uint32 crc32;
Uint32 reserved;
} Partition_Header;
这套方案最大的优势是:即使升级失败,设备也能自动恢复到旧版本继续运行。