1. DSP28335远程升级实战全解析
作为一名在工业控制领域摸爬滚打多年的工程师,我最近刚完成一个基于TI DSP28335的远程固件升级项目。这个看似基础的功能,在实际落地时却让我踩遍了你能想到的所有坑——从Bootloader设计到通信协议,从内存分配到校验机制,每个环节都暗藏杀机。今天我就把这次实战中积累的经验和代码干货全盘托出,帮你避开那些教科书不会告诉你的"暗礁"。
2. 项目背景与核心挑战
2.1 为什么需要远程升级?
在工业现场,设备往往部署在难以物理接触的位置(如风电变流器、光伏逆变器)。传统方式需要工程师携带烧录器到现场,不仅效率低下,在疫情等特殊时期更是难以实施。我们采用的方案是通过CAN总线实现固件空中升级(OTA),这也是汽车电子ECU升级的常规做法。
2.2 DSP28335的特殊性
与通用MCU不同,这款C2000系列DSP有几个关键特性直接影响升级设计:
- 哈佛架构(程序/数据空间分离)
- 片内Flash分扇区管理(16×4K + 6×8K + 1×64K)
- 需要特定的Flash编程算法
- 时钟配置复杂(PLL、DIV等)
关键提示:28335的Flash写入前必须将相关代码搬移到RAM运行,这是第一个容易翻车的点
3. 系统架构设计
3.1 双区备份方案
我们采用经典的A/B双备份设计:
- Bank A:运行当前固件(APP)
- Bank B:存储新固件(UPDATE)
- 预留8KB作为升级缓冲区(防止CAN通信丢包)
内存分配示例(CMD文件片段):
c复制MEMORY
{
FLASH_A : origin = 0x3F8000, length = 0x020000
FLASH_B : origin = 0x3FA000, length = 0x020000
UPDATE_BUF : origin = 0x3FC000, length = 0x002000
}
3.2 通信协议栈
采用CAN2.0B扩展帧格式,自定义应用层协议:
code复制| 0x18FFA001 | [0]包序号 | [1-7]数据 |
| 0x18FFA002 | [0]总包数 | [1]校验和 | ... |
每包包含7字节有效数据,最后一个包发送MD5校验值。实测在500kbps波特率下,升级100KB固件约需30秒。
4. Bootloader实现细节
4.1 启动流程优化
默认的DSP2833x_CodeStartBranch.asm需要修改:
assembly复制 LB _c_int00 ; 原启动代码
; 改为:
MOV AL, #0x55AA
CMP @0x3FFFFE, AL ; 检查升级标志
JNZ NormalBoot
LB JumpToUpdater
4.2 Flash驱动关键代码
Flash擦除必须按扇区操作,这是最容易卡死的地方:
c复制void Flash_Erase(uint32_t addr) {
EALLOW;
FlashRegs.FPWR.bit.PWR = 3; // 最高性能模式
FlashRegs.FBANKWAIT.bit.PAGEWAIT = 5;
FlashRegs.FSTDBYWAIT.bit.STDBYWAIT = 0x01FF;
// 必须运行在RAM中的函数
memcpy(&RamfuncsRunStart, &RamfuncsLoadStart, (size_t)&RamfuncsLoadSize);
Flash_Erase((Uint32 *)addr);
}
4.3 看门狗管理
升级过程中必须谨慎处理看门狗:
c复制#define WD_ENABLE 0x0055
#define WD_DISABLE 0x00AA
void DisableDog() {
EALLOW;
SysCtrlRegs.WDCR = WD_DISABLE | 0x0080;
EDIS;
}
5. 应用层协议实现
5.1 数据包处理
使用状态机管理升级流程:
c复制typedef enum {
WAIT_HEADER,
RECEIVING_DATA,
VERIFY_MD5,
PROGRAM_FLASH
} UpgradeState;
// CAN中断服务例程
interrupt void canISR(void) {
switch(state) {
case WAIT_HEADER:
if(CanaRegs.CANRMP.bit.RMP31) {
Uint16 totalPackets = CanMsgBox31.MDRL.all;
state = RECEIVING_DATA;
}
break;
// ...其他状态处理
}
CanaRegs.CANRMP.all = 0xFFFFFFFF; // 清除中断标志
}
5.2 断点续传设计
每个数据包都带有序号,实现原理:
c复制typedef struct {
Uint32 baseAddr;
Uint16 receivedPackets[2048]; // 位图记录已收包
} UpgradeContext;
void handlePacket(Uint16 seq) {
ctx.receivedPackets[seq/16] |= (1 << (seq%16));
if(seq == lastSeq) {
checkCompletion();
}
}
6. 致命陷阱与解决方案
6.1 时钟配置坑
升级后新程序无法运行?大概率是PLL配置问题:
c复制// 新旧固件PLL配置必须一致
#define CPU_RATE 6.667L // 150MHz SYSCLK
#define PLLCR_VALUE 0xA
InitSysCtrl() {
EALLOW;
SysCtrlRegs.PLLCR = PLLCR_VALUE;
while(SysCtrlRegs.PLLSTS.bit.PLLLOCKS != 1);
EDIS;
}
6.2 中断向量表重映射
APP中必须重定向中断向量:
c复制// 在APP的main()开头添加
MemCopy(&RamfuncsLoadStart, &RamfuncsLoadEnd, &RamfuncsRunStart);
InitPieCtrl();
InitPieVectTable();
6.3 校验失败处理
我们采用双重校验机制:
- 每包CRC16校验
- 整体MD5校验
c复制Bool VerifyFirmware() {
MD5_CTX ctx;
MD5_Init(&ctx);
for(int i=0; i<FW_SIZE; i+=512) {
MD5_Update(&ctx, (void*)(UPDATE_BUF+i), 512);
}
return memcmp(ctx.digest, expectedMD5, 16) == 0;
}
7. 实测性能优化
7.1 Flash编程加速
通过调整等待周期提升写入速度:
c复制FlashRegs.FBANKWAIT.bit.RANDWAIT = 5;
FlashRegs.FBANKWAIT.bit.PAGEWAIT = 10;
// 150MHz下典型值,需根据温度调整
7.2 CAN总线优化
提升吞吐量的关键配置:
c复制CanaRegs.CANMC.bit.ABO = 1; // 自动总线恢复
CanaRegs.CANMC.bit.STM = 0; // 标准模式
CanaRegs.CANBTC.bit.BRP = 9; // 500kbps @150MHz
8. 生产测试方案
8.1 自动化测试脚本
使用Python-can库模拟上位机:
python复制import can
bus = can.interface.Bus(channel='can0', bustype='socketcan')
msg = can.Message(
arbitration_id=0x18FFA001,
data=[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06],
is_extended_id=True
)
bus.send(msg)
8.2 异常注入测试
必须验证的异常场景:
- 随机丢包(模拟工业现场干扰)
- 故意发送错误校验码
- 断电恢复测试(在50%、90%进度时断电)
9. 完整代码结构
最终项目目录如下:
code复制/bootloader
├── source
│ ├── main.c // 主流程控制
│ ├── can_upgrade.c // CAN协议处理
│ └── flash_driver.c // Flash操作封装
├── include
│ └── upgrade_defs.h // 协议定义
└── cmd
├── F28335.cmd // 内存分配
└── F28335_ram.cmd // RAM调试配置
在真实项目中验证的这套方案,已经稳定运行在超过200台光伏逆变器上。最关键的体会是:DSP的远程升级不能简单套用STM32的经验,必须充分考虑其独特的架构特性。特别是Flash操作和中断管理这两个部分,稍有疏忽就会导致升级后系统"变砖"。
最后分享一个救命技巧:在Bootloader中预留串口调试接口,当检测到连续3次升级失败后,自动回滚到上一个版本并输出错误日志到UART。这个设计至少挽救了我们现场5台设备,避免了几十万的返厂损失。