资讯详情

TMS320F28377D基于CAN总线的远程固件升级方案

📅 2026/10/20 18:41:34 | 华诺云谱 👁 阅读
TMS320F28377D基于CAN总线的远程固件升级方案
做嵌入式的人应该都体会过这种感觉设备已经批量铺到客户现场突然发现固件有bug要修或者客户提了新功能要加。拆机刷程序客户一般不愿意产线也停了整机返厂物流加周期来回一个月就没了。如果设备上恰好有CAN口这件事其实有更体面的解法——直接通过CAN总线把固件刷进去。这篇文章要聊的就是我在一个电机驱动控制项目上落地的一套TMS320F28377D以下简称28377DCAN升级方案。这个方案解决了产品现场固件更新的核心痛点同时保证了升级过程足够稳即使中途断电、通信中断也不会把设备变成砖。这套做法适合谁手上有28377D做电机控制、数字电源、工业控制类产品的开发者尤其是产品已经小批量出货、需要在现场维护固件的人。如果你是学生或者刚接触C2000平台这篇也能帮你把Bootloader、CAN通信协议、Flash编程这些概念串起来少走弯路。1. 为什么非要用CAN来升级28377D1.1 现场固件升级的真实痛点项目做久了你会发现固件升级这件事产品在研发阶段根本不是问题——仿真器一插CCS里点一下烧录就完事了。真正的麻烦全在出货之后。工业设备普遍分布在车间、户外、配电柜这些地方拆开外壳刷程序既危险又不现实。更麻烦的是很多设备是连续运行的停机半小时意味着产线损失几十万的产出客户不可能为了一个固件小版本更新等你慢慢折腾。所以现场升级方案的第一诉求不是“能不能升”而是“升得快、升得稳、失败了还能自己恢复”。除了这些还有版本管理的问题。产品卖出去几十台每台固件版本可能都不一样总不能派人带着电脑一台一台拆机查版本。有了通信升级通道远程读取版本号、校验CRC、批量推送固件这些问题就都顺带解决了。1.2 CAN、串口、以太网为什么最终选了CAN28377D片上外设非常丰富串口、CAN、USB、以太网需要外接PHY都有。选哪种做升级通道我当时的判断标准其实很简单通信方式现场可用性布线成本传输速度抗干扰能力结论UART串口很多设备没有引出低中等较差不优先CAN工业设备几乎标配低两根双绞线中等1Mbps上限强差分信号首选以太网工业现场不一定有网线较高快中等看场景USB基本只能近场使用低快一般不适配现场当时项目里的设备本身就有CAN总线在跑用于多台设备之间的实时通信和上位机监控。这意味着升级通道不需要额外增加任何硬件成本直接用现有总线就行。CAN是差分信号抗共模干扰能强200米以内的现场总线传输距离完全够用再加上CAN协议本身有CRC校验、位填充和错误帧机制通信可靠性远高于普通串口。另外一个现实理由是28377D的CAN控制器内部有多个邮箱Mailbox可以比较方便地做发送、接收和过滤配置。升级数据流是单向大流量下发用邮箱中断就够了不用像TCP/IP协议栈那样占用大量CPU资源。1.3 28377D做升级主控的硬件底子28377D是C2000系列里的双核DSP/MCU主频最高到200MHz内部集成浮点运算单元、三角函数加速器和协处理器在电机控制、数字电源这些实时控制场合用得非常多。做升级方案时最需要关注的是它的这几块资源第一个是Flash容量。28377D内部Flash空间足够放下一套精简Bootloader和两套应用固件这也是做双区备份升级的基础。第二个是CAN控制器28377D有两个CAN模块CAN-A和CAN-B完全支持CAN 2.0B协议标准帧扩展帧都能收发。第三个是片上RAM升级过程要跑Flash擦写API这部分代码必须放到RAM里执行28377D的RAM空间足够承载这些临时程序和数据缓冲。一个容易被忽略的点是28377D的双核架构。CPU1负责主控制逻辑CPU2可以用来做额外的算法任务。在升级方案里CPU2的固件通常由CPU1负责引导。这带来一个额外问题升级包不只要管CPU1的固件还得把CPU2的镜像一起管理好后面我会专门说这个问题。2. 升级架构与Flash分区规划2.1 把Bootloader和App彻底分开很多人第一次做升级方案喜欢把升级逻辑堆在App主程序里检测到升级命令就直接擦Flash写Flash。这种设计在小规模实验里看起来没问题但产品化之后隐患很大App本身可能已经跑飞了、死机了你怎么还能指望它来处理升级所以做产品级升级方案的第一原则是Bootloader必须是独立于App的一段程序放在单独的Flash区上电优先执行。Bootloader的职责非常单一——检测升级请求如果有就执行升级流程没有就跳转到App去运行。它不依赖App的任何代码和数据这样即使App彻底崩溃了只要Bootloader还在设备就能重新刷固件。另外要特别提醒的是Bootloader一旦写好功能最好保持稳定不要经常改动。它是整个升级链路里的最后保险改坏了就真的只能拆机JTAG恢复了。2.2 Flash空间划分与扇区规划28377D的Flash按Bank和Sector两级管理不同扇区大小可能不同设计分区之前一定要打开芯片手册把扇区边界看清楚。我当时的分区策略大致如下以实际容量为准这里是示意图Bootloader区存放上电引导和升级主逻辑编译后约十几KB固定放在Flash起始地址段。参数区存放升级状态标志、当前版本号、升级包CRC、恢复标记等。App区存放当前运行的应用程序。备份区存放上一版可用的固件用于升级失败时的回滚。CPU2镜像区单独存放CPU2的固件镜像。这个布局的好处在于Bootloader和参数区独立App升级时被覆盖也是整个分区整体更新不会破坏关键引导信息。备份区保留了上一版“确定能跑”的固件一旦新固件启动异常Bootloader可以从备份区拉回旧版本。分区时要特别注意扇区边界对齐如果一个扇区里既放了参数又放了代码擦除时就会把不相干的数据一并干掉。我当时就把参数区单独占用了一个小扇区而不是图省事把参数塞在App区的末尾这是经验教训。2.3 升级状态机跳转、运行、回滚升级不只涉及“把数据写进Flash”这一个动作它是一整个状态流转过程。我把升级状态机按下面几步来设计App正常运行收到升级请求或用户在特定时间段内拉高某个升级标志引脚。App在参数区写入“请求升级”标志然后软复位。Bootloader启动读取参数区的标志发现升级请求进入升级模式。Bootloader初始化CAN向上位机发送就绪帧。上位机下发升级数据Bootloader逐帧接收、校验、擦写Flash。全部数据接收完成并校验通过后Bootloader把“请求升级”标志改为“新固件待验证”跳转至新App。新App启动后跑一个自检流程确认系统正常后写入“应用运行正常”标志。如果新App在指定时间内没有写入正常标志例如设备反复重启Bootloader再次上电后自动从备份区恢复旧固件。这套状态机把升级从“一次性烧写”变成了“可验证、可回滚的闭环过程”从根源上解决了升级把设备弄死的风险。2.4 CPU2固件怎么同步升级28377D双核的结构让很多第一次做升级的人栽了跟头Bootloader跑在CPU1上但CPU2的固件也得更新怎么处理工程上常见的做法是CPU1和CPU2的固件打成一个升级包。Bootloader先升级CPU1的App区数据再升级CPU2的镜像区数据。全部的升级数据写完后CPU1的App开始运行由App在启动流程里把新版本的CPU2镜像从约定地址加载到CPU2并启动CPU2。这里有几个细节需要特别注意。第一CPU2固件版本必须和CPU1固件版本配套否则可能出现CPU1是新算法、CPU2是旧算法两边接口对不上整个系统行为异常。第二CPU2镜像在升级过程中不能掉电一半所以Bootloader里要有严格的完成标志位只有CPU2镜像完整写入了才把整体升级状态置为成功。第三升级包描述信息里要同时包含CPU1和CPU2的版本号上位机在握手阶段就要比对免得升完CPU1才发现CPU2需要单独再传一次。3. CAN升级协议设计帧怎么定才不容易翻车3.1 一帧CAN数据能带多少信息CAN总线一帧标准数据帧最多携带8字节数据2.0B扩展帧虽然ID位数变多了但数据场还是8字节。这意味着升级协议必须把8字节利用到极致同时还要考虑校验和序号占用的空间。我们当时的设计约束有三个每帧必须有帧序号否则接收方没法判断丢帧每帧必须有独立的校验否则中间任何一位翻转都可能写入错误数据应用层协议还要尽量简单方便在上位机和Bootloader两端同时实现。实际用下来的结果是8字节里分配成——帧类型1字节帧序号2字节数据净荷4字节帧CRC 1字节。虽然一帧只能带4字节有效数据看起来效率不高但换来了协议极简、可靠性高、任何丢帧都能立刻发现并重传。3.2 CAN ID分配与关键帧格式CAN ID不仅仅是标识它还决定了报文优先级ID数值越小优先级越高。升级数据流属于大流量数据我把它的优先级设得比普通应用报文高但不能高到把实时控制报文堵住。项目里升级通道用的是扩展帧ID分配大致长这样报文方向CAN ID含义Bootloader - 上位机0x10就绪/应答帧Bootloader - 上位机0x11状态报告帧上位机 - Bootloader0x20握手/握手应答上位机 - Bootloader0x21升级数据帧上位机 - Bootloader0x22升级结束帧最重要的一条规则是任何帧的异常ID不匹配、长度错误、CRC错都必须被当作错误处理不能静默丢弃。我见过很多协议设计者只做“收到对的帧就处理”却忽略了异常帧的统计和上报结果设备在工作现场偶尔卡死排查起来非常痛苦。3.3 应答、重传与断点续传CAN总线本身有硬件层的错误重发机制但那是针对物理层错误的。应用层仍然需要自己的确认机制否则一包数据丢了Bootloader不知道自己少了几帧。我们的方案是Bootloader对握手帧做立即应答对数据帧做“桶式确认”——每收到N帧比如32帧回复一次确认帧确认帧里带上最后成功写入的帧序号。上位机只有在收到确认后才继续发下一批超时未确认则从最后一个确认帧序号开始重传。断点续传是现场升级的隐藏刚需。一次升级几百KB如果中间通信断了全部从头开始体验非常差。Bootloader把“当前已成功写入的帧序号”保存在参数区设备意外断电重启后Bootloader读取这个序号告诉上位机“我上次写到了第几帧”上位机直接从断点续传而不是重新开始。这个功能对动辄两三分钟的升级过程来说体验差别太大了。3.4 CRC校验两头都要算CAN协议自带的CRC只能保证一帧数据在线路上传输没出错。对于文件级完整性必须额外做整包校验。我用的方案是双层次校验逐帧校验每帧带1字节CRC或简单累加和Bootloader写入Flash前先验帧帧错则NAK重传。整包校验上位机在发送完所有数据后额外发送一个结束帧里面带上整个固件文件的CRC32值。Bootloader在接收完数据后从Flash里把刚写入的内容重新读出来对全部数据计算CRC32和结束帧里的值对比一致才把状态置为“待验证”。整包CRC建议用CRC32而不是CRC16。固件包动辄几十上百KBCRC16碰撞概率并不算低万一校验碰巧过了但数据实际错了后果是设备运行一个未知行为的新固件那比升级失败还难排查。3.5 升级要花多长时间很多人关心升级耗时。我们可以简单做一个估算。假设CAN波特率500kbps这是工业CAN的常见配置。一帧标准CAN数据帧最长约130位含SOF、仲裁场、数据场、CRC、ACK、EOF和位填充实际按每帧发送加帧间隔约0.5ms来算。如果每帧携带4字节有效数据那么100KB固件需要约25600帧纯传输时间约13秒。加上Flash擦写时间、应答等待和重传时间200KB的固件实际全程约1到2分钟属于可接受范围。如果把每帧的有效数据从4字节增到6字节去掉逐帧CRC传输时间会更短但丢帧检测能力会下降。两者之间怎么权衡取决于现场总线质量。当时我们的现场总线比较长环境电磁干扰也多我宁愿多花一点时间也要保证每帧都能独立验证。4. 核心代码与落地实现4.1 CAN初始化与过滤配置28377D的CAN模块用原厂外设库配置起来不算复杂。初始化时最关键的是设置波特率以及配置收邮箱的过滤规则。// 初始化CAN-A模块假设外设时钟为200MHz目标波特率500kbps CAN_initModule(CANA_BASE); // 设置位速率 CAN_setBitRate(CANA_BASE, 200000000, 500000); // 配置发送邮箱发送握手应答、状态等报文 CAN_setupMessageObject(CANA_BASE, TX_MAILBOX, 0x10, CAN_MSG_FRAME_EXT, CAN_MSG_OBJ_TYPE_TX, 0, CAN_MSG_OBJ_NO_FLAGS); // 配置接收邮箱接收上位机下发的数据帧 CAN_setupMessageObject(CANA_BASE, RX_MAILBOX, 0x21, CAN_MSG_FRAME_EXT, CAN_MSG_OBJ_TYPE_RX, 0, CAN_MSG_OBJ_NO_FLAGS); // 使能接收中断 CAN_enableInterrupt(CANA_BASE, CAN_INT_IE0);过滤规则这里有个小坑如果用一个接收邮箱收多种ID的报文需要把CAN ID掩码设成查部分位。但如果协议里报文类型本身就不多更省事的做法是给每种报文单独建接收邮箱过滤条件写死中断回调里按邮箱号区分处理。这样逻辑简单也不容易误收别人的报文。升级期间Bootloader只关心升级协议里的那三个上行ID其他无关报文一概不收避免干扰升级流程。4.2 跳转引导逻辑Bootloader上电后的第一件事是读状态、做判断然后决定是进入升级流程还是跳转App。跳转的代码看着简单但细节很多。#define APP_START_ADDR 0x08020000U // 以实际分区为准 void jump_to_application(void) { uint32_t app_reset_vector; void (*app_entry)(void); // 关闭全局中断避免跳转过程中断嵌套 DINT; // 关闭Bootloader用到的外设恢复默认状态 CAN_disableModule(CANA_BASE); // 从App向量表起始位置取出复位向量 app_reset_vector *(volatile uint32_t *)APP_START_ADDR; app_entry (void (*)(void))app_reset_vector; // 切换栈指针并跳转 __asm( MOV SP, #0x400); app_entry(); }跳转前关闭外设、关中断这两步很多人会忽略。如果Bootloader里CAN的中断还开着跳到App的瞬间来了一个CAN中断App的向量表还没准备好程序直接跑飞。另一个容易忽略的点是跳转前最好把用到的外设时钟和GPIO复用到默认状态防止App启动时检查外设状态出现异常。4.3 Flash擦写的正确姿势28377D的Flash编程不像单片机那样直接往地址写数据就行必须调用芯片原厂提供的Flash API库而且这套API要求在执行期间不能被中断打断、不能在Flash里执行自己的代码。// Flash API使用步骤 // 1. 拷贝Flash API到RAM memcpy((void *)RAM_APP_API_ADDR, (void *)FLASH_API_ADDR, FLASH_API_SIZE); // 2. 初始化Flash模块包括等待状态、时钟分频等 Flash_initModule(); // 3. 擦除目标扇区 Flash_eraseSector(BANK0, SECTOR_A); // 4. 按16字节对齐方式写入数据 Flash_program(BANK0, (uint16_t *)src_buf, (uint32_t *)dst_addr, data_len);这里有几个必须死记的注意事项。第一Flash API执行期间必须关中断如果开中断一个控制周期中断打进来Flash状态机就被破坏了轻则写失败重则Flash控制器锁死需要复位。第二写入地址和数据长度必须对齐到芯片规定的编程单位28377D通常是16字节对齐不对齐直接报错。第三擦除动作很耗时一个扇区动辄几十上百毫秒这段时间内不能响应CAN报文所以协议层面必须设计好上位机发完一批数据后等确认Bootloader在擦除期间不接收新数据避免CAN接收缓冲溢出。Flash API的调用代码执行位置必须在RAM里不能放在Flash里这是芯片的硬件限制。我在项目里是把Flash API相关代码统一放在一个RAM段链接脚本里单独分配地址链接时确保所有Flash操作函数都落到这个段。4.4 升级标志与参数存储升级状态、帧序号、版本号这些关键信息不能放在普通RAM里因为掉电就丢。我专门在Flash参数区开辟了一块区域存储升级信息并在RAM里保留一份镜像方便快速读写。typedef struct { uint32_t magic; // 固定魔数 0x5A5A5A5A uint16_t upgrade_status; // 0无升级, 1请求升级, 2升级完成待验证 uint16_t app_version; // 当前App版本 uint32_t total_frames; // 总帧数 uint32_t received_frames;// 已接收帧数 uint32_t file_crc32; // 整包CRC32 } UpgradeInfo_t;这类参数区的写入寿命是有限的Flash擦写次数有限如果每收一帧就更新一次Flash状态几千次升级后参数区就废了。所以我把“当前接收帧序号”只在特定节点写入每确认32帧更新一次、掉电启动时读取其他时候都只在RAM里维护。这样一次升级Flash写入次数只有几十次寿命完全不是问题。5. 可靠性设计怎么保证升级不把设备升成砖5.1 双分区备份与回滚真正产品级的升级方案默认前提是“升级可能会失败但我有后路”。这里说的后路就是备份分区。升级前Bootloader把当前App区的内容完整复制到备份区。这个过程在CAN升级中往往被忽略因为现场第一版固件是出厂烧好的备份区内容可以在出厂环节就写好。当设备需要从V1.0升到V1.1时Bootloader其实不需要再复制当前App备份区保留的出厂V1.0就是兜底版本。回滚触发条件有两种一是新App上电后没有在约定时间内写入“运行正常”标志说明它运行不起来或反复崩溃二是App自检发现关键外设异常主动写入“回滚请求”标志。Bootloader在下次上电时检测到这两种情况直接把备份区固件搬回App区然后跳转。回滚策略的细节还有一个小技巧新App写入“运行正常”标志前建议加一个延时窗口例如30秒避免“上电能跑但一跑任务就挂”的情况没有被兜底住。5.2 掉电保护和看门狗兜底升级到一半断电是现场大概率事件。CAN升级方案对掉电的核心防护策略其实就是断点续传加完整标志位。Bootloader设计的写入顺序是先写数据区所有数据写完后才更新状态标志。如果中途掉电状态标志仍然是“升级中”下次上电Bootloader从参数区读到最后确认的帧序号从断点继续。如果掉电发生在Flash擦写过程中当前扇区可能是不完整的但这个扇区本来就属于待覆盖区域重新擦写一次即可不影响已有固件。看门狗在这里是第二层防线。Bootloader主循环里喂狗但如果某个环节卡死例如Flash API死锁、CAN中断风暴看门狗会强制复位。复位后Bootloader重新从参数区读取状态要么继续升级要么回滚。5.3 升级档案管理产品维护到一定阶段最麻烦的事就是搞不清现场设备跑的是什么版本。所以我的升级协议里始终保留了版本管理字段握手帧里有当前固件版本结束帧里有新固件版本Bootloader在升级完成后把新版本号写入参数区。版本不匹配的情况也要在握手阶段拦截掉。比如上位机要推送V2.0固件但Bootloader检查升级包头里的目标硬件ID和当前设备不匹配直接拒绝。这个防护对大批量维护特别重要不然几十台设备现场你根本分不清哪台该升哪个包。6. 实战中踩过的坑6.1 擦除Flash失败问题出在安全配置第一版测试时我频繁遇到擦除失败的问题。查了一圈才发现28377D有一个安全模块DCSM它管理着Flash区域的保护权限。芯片上电默认状态下某些Flash区域是被锁的需要先把安全配置正确设置好或者通过API解锁对应区域才能正常擦写。DCSM配置需要在工程里单独做烧录芯片时一次性写入。如果出厂程序没做好这一步后续Bootloader在任何保护区的擦写都会失败而且没有直观的错误提示只能反复看API返回码。这个配置建议在产品生产烧录流程里就固化统一下来不要依赖运行期动态修改。6.2 CAN通信时好时坏但明明配置都对有段时间升级经常传到一半就停了Bootloader端显示接收超时但用CAN卡抓包发现报文一直在发。排查到最后是波特率偏差CAN控制器的时钟源和上位机CAN卡的时钟源存在细微偏差虽然都在标称500kbps附近但长期跑大数据量时相位误差累积导致采样点偏移到错误的位。解决办法有两个一是用示波器或CAN分析仪实测两边实际波特率把28377D的波特率预分频值微调二是尽量让CAN控制器的时钟源用精度更高的外部晶振不要用芯片内部的PLL分频出来就完事了。另外总线两端务必接终端电阻。CAN总线标准要求两端各接120欧终端电阻少了一端信号反射在长线上会直接导致误码率上升这不是软件能修好的问题。6.3 升级完成后程序跑飞复位都不好使这个问题我印象很深升级明明全部完成了CRC也对了跳转App就是跑不起来。反复复位像是卡死在某个外设访问上。最后定位到原因是——App启动早期用到了CAN外设但Bootloader跳转前没有把CAN控制器完全复位到默认状态App的CAN初始化代码没有重新初始化模块的某些状态位导致收发异常。所以跳转前的清理动作要做得足够彻底关闭并复位用到的外设模块、恢复GPIO复用、关闭中断、清中断标志。不能用完直接跳App侧宁可多初始化一遍也不要依赖Bootloader留的现场。6.4 上位机一直报CRC错帧序号也乱一遇到报CRC错首先要区分是本帧CRC错还是整包CRC错。如果是本帧错大概率是波特率偏差或者CAN收发器电平有问题需要检查物理层。如果只是偶尔零星几帧错重传机制能兜住不用太紧张如果连续错或者错帧密集优先查终端电阻和线缆质量。如果是整包CRC错问题多半出在“校验范围不一致”。上位机对原始文件算CRC而Bootloader是对Flash读出来的数据算CRC两者应该在同一个固件内容上算但如果升级包里有跳转向量、校验和、地址重定位等字段每一帧数据写入的位置与原始文件地址偏移不对就会导致整包校验不一致。排查时把两边计算的CRC和字节数都打出来一看便知。6.5 新手常问的几个问题有人问Bootloader放哪里比较安全。我的建议是放在Flash最靠前的独立扇区并且这个区域的扇区不要让App链接脚本占用。还有人问能不能直接用仿真器把App烧到备份区完全可以生产预烧录时就可以把出厂固件同时写进App区和备份区省去现场首版备份的过程。也经常有人问App自己能不能触发升级。可以常见方式是App在收到上位机升级命令后写升级标志然后软复位让Bootloader接管。这个交互逻辑要写在App里但要注意一个细节升级标志的写入必须放在Flash参数区并且写完后要等待一段时间让Flash完成写入再复位否则刚硬复位就没有标志Bootloader不会进入升级流程。最后说点实际体会做完这套方案我个人最大的感受是升级方案做得好不好不取决于你用了多高深的技术而取决于你把边界条件想得多周全。CAN协议、Flash编程、状态机这些内容任何一本参考手册都有真正拉开差距的是故障场景的覆盖——掉电了怎么办、发错版本了怎么办、新固件起不来怎么办、总线被干扰了怎么办。这些大概率不会出现在开发文档里的问题才是现场运维里真正决定口碑的东西。另一个经验是上位机和Bootloader要同时开发、同时测试不要把Bootloader先写完扔一边。升级是两端配合的事情握手、应答、重传、断点续传任何一个环节只要有一端理解不一致现场就多一次故障。我当时就是先把上位机协议文档写好再让Bootloader和上位机照着同一份文档实现联调阶段的问题数量比预期少了很多。如果你也在做类似的项目建议第一批先做小规模现场验证确认升级流程在真实线缆长度、真实干扰环境下的成功率再全面铺开。这套方案虽然是在电机驱动项目上落地的但思路在几乎所有带CAN总线的28377D产品上都能复用区别只是具体Flash分区的调整和协议指令的增删。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑