DSP28335 CAN在线升级方案:Bootloader与上位机从零到落地

发布时间:2026/10/19 4:34:32
DSP28335 CAN在线升级方案:Bootloader与上位机从零到落地
前阵子整理了一套基于TMS320F28335下面直接叫DSP28335的CAN在线升级方案bootloader、上位机、通信协议和辅助脚本全部自主开发。起因是手头一个电机控制器项目设备装到现场之后要升级固件拆壳插仿真器根本不现实客户提的要求很直白通过现成的CAN口把新程序刷进去而且刷写工具要能对接他们的产线流程。这套方案从协议定义、Flash分区、Bootloader代码到上位机界面前后改了四五个版本中间还经历过升级过程中掉电导致板子起不来的险情。我把它整理成这篇文字重点讲方案是怎么搭起来的、协议为什么这么定、Flash资源怎么分配、工程文件怎么组织以及实测下来遇到的那些坑。给正在折腾DSP28335或者其他C2000系列CAN升级的朋友一个完整可参考的范本。1. 为什么放弃仿真器烧写坚持自己搭CAN升级1.1 现场升级的真实场景我手头的项目是车载电机控制器设备装到整车或者设备机柜里之后外壳是密封的电气接口只引出电源、CAN、旋变等接插件。以前改程序要拆盖子、找调试口、接仿真器一次至少半小时而且生产装机后基本留不出调试口。客户的要求是能不能不做任何拆解通过现成的CAN口把程序换了。这就是CAN在线升级方案的核心动力。这种情况在工业设备里特别常见。设备已经交付到客户现场固件有问题或者要加功能不可能让客户把设备寄回来也不可能派工程师带着仿真器出差。CAN口是控制器最常见的通信接口只要协议和引导程序设计得当它就是一个天然的升级通道。1.2 全自主开发到底图什么一开始我也考虑过用芯片原厂的ROM引导或者买第三方的CAN刷写工具。后来发现都不理想。原厂ROM的CAN引导功能虽然存在但帧格式固定、校验简单、没有应用层应答重传机制对接产线自动化流程很吃力第三方刷写工具则是黑盒协议不开放固件格式受限还涉及授权费用。自主开发虽然前期投入不小但协议自己定、上位机自己写想要什么功能都能往上加。我后面陆续加了批量升级、掉线续刷、和诊断服务集成这些在项目验收和售后阶段都是加分项。对需要长期迭代的控制器产品来说这个投入非常划算。1.3 方案适用范围这套方案适合这几类场景DSP28335/C2000系列芯片的板子产品已经预留CAN接口需要频繁升级固件或者希望在产线和售后都用同一套刷写工具。只要Bootloader和上位机是同一套代码体系后期移植到其他C2000型号也只是改改Flash地址、调调寄存器配置。2. 升级链路全景一次CAN刷写的完整流程2.1 系统组成和角色分工整套系统由四个部分组成上位机PC端程序负责解析固件文件、组帧、发送命令、显示进度。USB-CAN适配器把PC的USB数据转成CAN帧市面常见适配器SDK接口大同小异。目标板上的Bootloader常驻Flash负责擦写App区、接收固件、校验并跳转。目标板上的App业务程序收到特定命令可软复位进入Bootloader。四个角色里最重要的是上位机和Bootloader这对关系它们共同遵守一套通信协议。适配器只是透明的转发层协议设计时不需要为它做特殊考虑。2.2 一次升级的时序步骤一次完整升级大致走下面这几步上位机打开固件文件解析出连续数据计算CRC32和总帧数。上位机通过适配器发送握手命令带一个随机数作为会话标识。Bootloader应答回报自己的版本号、Flash剩余空间、最大支持载荷长度。上位机发送擦除命令Bootloader擦除App区擦完回报成功。上位机按帧序号连续发送数据帧每帧带5字节有效载荷。Bootloader每收一帧回一个ACK上位机超时未收到ACK则重传该帧。全部数据发完后上位机发校验命令Bootloader对写入区重新读回并计算CRC32与上位机比对。校验通过则上位机发送跳转命令Bootloader恢复现场并跳进App。App启动后跑一个自检确认运行正常后再回报上位机“升级成功”。这个流程看着不复杂但每一步的细节都决定了升级的可靠性。后面我会把Bootloader和协议的部分单独拆开讲。2.3 Flash分区规划F28335片内Flash一共256K×16bit分成8个Sector每个32K地址范围0x300000到0x33FFFF。我的分区是这样的区域地址范围大小用途Sector H~C0x300000~0x31FFFF128KApp主程序Sector B0x320000~0x32FFFF32KApp扩展/参数备份区Sector A0x330000~0x33FFFF32KBootloader程序Sector A的末尾0x33FFF6处放置一条跳转指令这是芯片上电从Flash启动时的入口。Bootloader启动后先运行一小段自检然后决定继续待在Bootloader还是跳去App。提示不要把Bootloader和App混在同一个Sector里。一旦擦错Bootloader也会被一起抹掉板子就只能拆壳上仿真器恢复了。2.4 进入Bootloader的三种方式我在实际项目中同时保留了三种进入方式上电主动进入按下升级按钮后上位机在目标板上电前就持续发送握手命令Bootloader启动后发现CAN口有合法握手命令就待命升级。App软命令进入App收到特定诊断命令后把一个标志位写入RAM然后软复位。Bootloader检测到这个标志就不跳App。启动保护进入Bootloader先检查App头部魔数和CRC不合格就直接停在Bootloader等待升级。三种方式互相配合既能主动刷写又能防止板子变砖。3. Bootloader端实现入口、驱动与Flash操作3.1 从0x33FFF6说起的程序入口F28335的Boot ROM在上电后根据GPIO状态选择启动方式。配置为“Boot to Flash”时芯片会跳到0x33FFF6读取一条分支指令。所以链接脚本里必须保证0x33FFF6这个地址放的是跳转到Bootloader主函数的指令。我通常用一个位于Flash Sector A末尾的跳转函数来实现// 放置在Flash Sector A的末尾区域 __interrupt void BootEntry(void) { asm( LB _mainBoot); // 跳转到Bootloader主函数 }链接脚本里要给这个函数安排固定地址同时保证0x33FFF6就是这条分支指令的存放位置。这个位置错了Bootloader根本跑不起来上电就进非法指令。3.2 eCAN初始化和500kbps波特率计算Bootloader里的CAN驱动要尽量精简不需要像App那样跑完整的协议栈能用标准帧收发即可。波特率我用的是500kbps这是工业CAN最常用的速率。F28335的eCAN模块时钟来自系统时钟SYSCLKOUT。如果SYSCLK是150MHz要让最终位时间等于500kbps需要满足位时间 (BRP 1) × (1 TSEG1 TSEG2) BRP 1 151 TSEG1 TSEG2 20 位时间 15 × 20 300个时钟周期 150MHz / 300 500kHz对应寄存器设置ECanaRegs.CANBTC.bit.BRP 14; // 分频系数15 ECanaRegs.CANBTC.bit.TSEG1 12; // 时间段1 ECanaRegs.CANBTC.bit.TSEG2 6; // 时间段2 ECanaRegs.CANBTC.bit.SJW 2;如果你的板子系统时钟不是150MHz把公式代进去重算即可。收发双方波特率必须一致差一点都会导致大量错误帧最典型的现象就是握手命令发出去完全没回应。3.3 Flash擦写为什么必须搬到RAM里执行这是初学者最容易踩的坑。F28335的Flash不能一边擦写一边取指执行芯片手册明确要求Flash编程和擦除操作必须在RAM中执行。我一开始图省事直接把Flash API函数放在Flash里调用结果每次擦写到这里程序就飞了。正确做法是把Flash操作函数放到RAM段里运行。在链接脚本里单独建一个RAM运行段比如放在L0~L4这块RAM里ramfuncs : LOAD FLASH_BOOT, RUN RAM_L4, LOAD_START(_RamfuncsLoadStart), RUN_START(_RamfuncsRunStart), SIZE(_RamfuncsLoadSize)配合启动代码把这几个函数从Flash复制到RAM并在调用Flash API之前把时钟和等待状态配好。F28335在150MHz下必须配置对应数量的Flash等待周期否则读Flash也会出错。3.4 升级过程中的保护策略Bootloader里必须处理三个问题看门狗、掉电、非法写入。看门狗的问题很现实擦除和写入Flash需要几秒如果看门狗一直在跑烧到一半就会复位。我的做法是Bootloader启动后直接关闭看门狗整个升级期间不喂狗升级完成跳转前再重新初始化。前提是Bootloader本身的逻辑足够可靠不能有死等分支。掉电保护靠分区和标志位配合。升级前先把App区开头若干字写成“升级中”的标志只有收到完整固件并校验通过后才把“升级完成”的标志写进去。这样即使中途掉电下次上电Bootloader也能识别出App不完整不会盲目跳转。非法写入防护主要面向地址越界。Bootloader必须对每个数据帧的目标地址做范围判断超出App区直接回错误应答并中止升级。4. 通信协议设计帧格式、序号管理和容错机制4.1 消息ID与数据域布局CAN标准帧一次最多8个字节数据必须精打细算。我用了两个CAN ID0x101作为上位机发给目标的命令帧0x102作为目标回报给上位机的应答帧。数据域第一个字节是命令字后续字节按命令不同解释。命令字定义如下表命令字含义数据域剩余字节说明0x01握手请求会话种子、上位机版本号0x02握手应答Bootloader版本、最大载荷、Flash容量0x03擦除请求起始扇区索引0x04擦除应答擦除结果0x05数据写入帧序号(1) 有效长度(1) 数据(5)0x06写入应答帧序号 写入结果0x07校验请求无0x08校验应答本机CRC32(4字节)0x09跳转请求App起始地址0x0A跳转应答跳转结果应答帧的数据域里固定包含“命令回声”和“状态码”。状态码0表示成功1表示校验错误2表示地址非法3表示序号不连续4表示Flash操作失败。上位机拿到状态码就知道下一步该怎么处理。4.2 数据分帧有效载荷一帧CAN数据最多8字节扣掉命令字、序号、长度留给实际固件数据的空间就是5字节。我一开始也纠结过要不要省掉长度字段多塞两字节数据后来还是保留了。长度字段很有用最后一帧往往不满5字节Bootloader必须知道哪些字节是有效的。而且以后协议要扩展有了长度字段兼容性会好很多。按5字节一帧算100KB固件大概是20480帧在500kbps下数据传送阶段约20秒这个时间完全够用。地址不用在帧里传因为数据是顺序发送的。Bootloader内部按“基地址 帧序号×5”计算写入地址上一帧校验通过后这个地址就是确定的不会乱。4.3 ACK/NACK与超时重传CAN本身只有硬件层的错误检测和重发不提供应用层确认。所以应用层必须自己做应答。我的策略是握手时选择随机数作为会话ID之后所有数据帧都带会话ID防止把总线上的其他消息误收进来。上位机每发一帧数据等待ACK最多200ms超时未收到就重传最多重传3次3次都失败就停止并发提示。Bootloader收到数据帧后先判断序号是否等于期望序号不等就回状态码3的NACK上位机收到NACK主动重发。发送过程中连续10秒没收到任何命令Bootloader判定上位机已掉线放弃升级并进入等待或跳转App。重传逻辑一定要用状态机实现不要用阻塞延时否则上位机界面卡死轮询式等待还会丢引用帧。4.4 固件完整性校验完整性的最后一道闸门是CRC32。固件bin在上位机侧预先算出CRC32随校验命令下传。Bootloader写完所有数据后从Flash读回全部App区重新计算CRC32两个值相等才允许跳转。读回校验比写完直接跳多花一点时间但能挡住绝大多数写入异常。实测100KB固件读回校验大约1秒这个成本完全值得。5. 上位机自主开发从HEX解析到进度管理5.1 技术选型与模块划分上位机我最终用的是C# WinForms开发快、调试方便、对接适配器SDK也简单。如果你更熟悉Python用带CAN支持的库也可以但在产线部署和代码保护上不如C#方便。无论用哪种语言模块划分是通用的固件解析模块读入Intel HEX或纯bin输出按地址排好的二进制数据。协议层负责组帧、解帧、维护序号、超时重传。适配器驱动封装USB-CAN适配器的DLL调用。界面层进度条、日志、按钮。协议层和界面层严格分离这样以后换个适配器或者想改成串口升级只需要改驱动模块。5.2 Intel HEX解析编译环境生成的一般是COFF格式的.out文件通过转换工具可以输出Intel HEX。上位机要做的就是把HEX解析成连续的地址加数据序列。HEX记录的关键处理逻辑记录类型00是数据记录把地址和数据拼接起来。记录类型04是扩展线性地址高16位地址以它为准。记录类型01是文件结束停止解析。解析后按地址从低到高排序检查连续区间的空洞。补0x00还是补0xFF要看硬件要求DSP的Flash擦除后是0xFFFF所以补0xFF更稳妥。我在上位机里还做了一件事解析结束后立即和工程导出的bin做一次字节级对比防止HEX与实际烧录内容不一致。5.3 发送状态机设计上位机的发送逻辑不要写成顺序执行的循环而是设计一个状态机空闲态选择文件、计算CRC、显示固件信息。握手态发送0x01收到0x02后校验会话ID进入就绪态。擦除态发送0x03等待0x04应答超时重发。传送态按帧序号循环发送数据帧每帧等待ACK出现NACK按错误码处理。校验态发送0x07等待0x08并比对CRC32。跳转态发送0x09收到0x0A后提示“升级完成目标机正在启动”。每个状态都要有超时处理也要允许用户手动取消。测试阶段这个功能特别有用。5.4 日志与界面日志是升级方案里最容易被低估的部分。我要求每次升级都生成一个带时间戳的日志文件记录每条命令的收发时间、状态码、重传次数。产线上如果有一块板子刷不进去拿着日志排查比现场猜快得多。界面进度条不要按帧序号做线性因为后面还有校验和跳转阶段。我的做法是把整个升级过程按工作量拆成三段擦除占10%数据传送占80%校验跳转占10%每一段内部再按帧粒度更新。6. 工程文件结构与说明6.1 目录总体布局这套方案我一直用“按角色分目录、文档和代码同库”的方式组织整套工程结构大致如下DSP28335_CAN_Upgrade/ ├── 01_Bootloader/ │ ├── src/ │ │ ├── main_boot.c │ │ ├── can_boot.c │ │ ├── flash_boot.c │ │ ├── protocol_boot.c │ │ ├── jump_boot.c │ │ └── BootEntry.asm │ ├── include/ │ │ └── boot.h │ ├── cmd/ │ │ ├── Flash_SectorA_lnk.cmd │ │ └── RAM_lnk.cmd │ └── proj/ ├── 02_App/ │ ├── src/ │ ├── include/ │ └── cmd/ ├── 03_Host/ │ ├── HostApp/ # 上位机源码 │ ├── HostTool/ # 固件打包工具 │ └── output/ ├── 04_Docs/ │ ├── 通信协议_V1.0.md │ ├── CAN升级总体设计.md │ └── 升级操作手册.md └── 05_Tools/ ├── hex2bin.py ├── crc32_tool.py └── batch_upgrade.bat6.2 Bootloader工程文件Bootloader源码按职责拆成几个文件职责划分一定要清楚main_boot.c上电流程、升级标志判断、状态机主循环。can_boot.ceCAN初始化、邮箱收发、波特率配置。flash_boot.c封装Flash擦除、写入、读回校验内部调用原厂Flash API。protocol_boot.c协议帧解析、命令分发、应答生成。jump_boot.c处理全局变量恢复、设置App入口并跳转。BootEntry.asm启动入口跳转表保证0x33FFF6正确。cmd目录下最值得注意的是Flash_SectorA_lnk.cmd。Bootloader工程必须限制自己的代码只落在Sector AApp的入口和中断向量表由App工程自己管理。两块地址千万不要有交集否则App写进去就把Bootloader覆盖了。6.3 上位机与脚本03_Host目录下的HostApp是完整的上位机工程HostTool里是我写的辅助工具把编译产出的固件打包成带版本头和CRC的升级包。升级包格式是自己定的[固件头 16字节] 魔数(2) 版本号(2) 目标地址(4) 总长度(4) CRC32(4) [固件体] 对齐后的bin数据05_Tools目录下的脚本主要用于产线hex2bin.py负责格式转换crc32_tool.py负责单独计算校验值batch_upgrade.bat可以循环对一批设备执行升级。6.4 固件生成流程一个App从源码到可以CAN升级走的是这个流程在App工程里编译生成.out。用编译链自带的HEX转换工具把.out转成Intel HEX。上位机加载HEX后自行转成bin并计算CRC32填进升级包头。库内固化“加载HEX→解析→计算CRC→待发”流程产线操作人员只需要选文件和点开始。建议把“生成升级包”做成脚本固化不要在产线上临时手工操作减少人为出错。7. 实测数据与踩坑记录7.1 实测性能以我这个项目为例App固件大约96KBBootloader代码约6KB运行在150MHz系统时钟、500kbps CAN波特率下实测升级耗时如下阶段耗时握手擦除约2~3秒数据传送约19700帧约20秒读回校验约1秒跳转启动小于1秒整包刷完大约25秒。这个时间对产线批量升级和售后现场升级都能接受。如果想更快可以把波特率提到1Mbps或者把数据帧的有效载荷从5字节加到7字节代价是协议要跟着改。7.2 坑点清单这套方案从头到尾踩过的坑不少挑几个最有代表性的Flash API放在Flash里执行一调用就飞。解决办法前面说了必须把Flash操作搬到RAM。擦除时看门狗复位。升级期间关狗但不是关掉就不管了主状态机的每个超时都要有明确出口不然板子会静默死掉。中途掉电变砖。最严重的一次现场刷到一半断电重新上电后App和Bootloader都起不来。这就是分区和启动保护没做好的教训。适配器DLL线程模型问题。某些适配器的接收回调在独立线程里直接往界面控件写数据会闪退。处理办法是回调只进队列UI线程定时取。CRC算错但程序没报错。这个问题最隐蔽原因是HEX解析时补位策略不对导致bin中间有空洞被补成了0x00而Bootloader校验收到的是上位机直接发送的CRC自洽但和实际固件不一致。所以一定要在上位机里冗余核对源文件。7.3 几个提高成功率的细节最后分享几个我实测下来对稳定性帮助很大的细节升级开始前让Bootloader上报Flash最大可写地址上位机发送前先校验固件总长度避免写一半才发现放不下。数据帧序号用8位循环计数完全够用因为重传机制保证了不乱序序号只是为了检测丢帧不是为了寻址。CAN收发器一定选带唤醒功能的型号板子上120欧终端电阻尽量靠近连接器。升级完成后让Bootloader先软复位再跳App比直接跳转更干净能把外设寄存器彻底复位干净。这套方案从协议到代码都是自己磨出来的过程中交了不少学费但换来的是产线和现场升级都握在自己手里。如果你也正在做DSP28335的CAN升级先把协议和Flash分区想清楚再动手写代码方向上少走弯路后面会省很多事。