
简介围绕TI DSP TMS320F28335的串口Bootloader升级需求该资料集成了从底层引导到上位机操作的一整套实践内容适合嵌入式工程师、DSP开发者及需要实现固件在线升级的研发人员参考。整个压缩包共140个文件约2MB包含C/汇编源程序、头文件、编译生成的bin/out固件、CCS工程配置文件以及上位机升级软件的exe/dll等覆盖开发、编译、烧录与升级操作各环节。目前已有717人学习下载说明该方案在TMS320F28335用户中具备一定的参考热度。资料内含Bootloader引导固件、应用测试固件及多种LED测试bin可直接配合上位机软件验证串口升级全流程同时提供启动引导汇编、系统控制与外部接口等底层源码便于深入理解启动引导、Flash编程和串口通信细节方便二次开发与项目移植。 上个月给一台装了铝壳的变频器升级固件JTAG排针被外壳挡住客户在现场又不让拆机最后是靠设备屁股后面那对RS485线把新固件灌进去的。从那次之后我彻底想明白一个事TMS320F28335的Bootloader串口升级方案不是有空才做的选修课而是产品落地后的必修课。做过STM32的朋友都知道串口IAP已经是标配但TI C2000系的资料比STM32零散得多F28335既没有USB DFU也没有出厂固化的系统存储器Bootloader一切都要自己动手。这篇就把我从内存规划到Flash API、再到跳转和上位机的完整过程以及那几个差点让我怀疑人生的坑一次说清楚。1. 为什么F28335项目迟早需要一套串口Bootloader1.1 先弄清楚Bootloader到底解决什么问题开发阶段用JTAG下载器烧程序又快又方便但产品一出厂问题就来了机箱装好了、螺丝拧死了、现场可能还在几十公里外。这时候客户说“参数要调一下”“控制逻辑要改一版”你不可能派人背着仿真器去现场拆壳子。烧录口本来就是开发工具不是为运维设计的。Bootloader要解决的核心矛盾就是“程序写好了之后还能不能在不接触JTAG的情况下更新”。对F28335来说最常见的升级介质就是串口。工业设备里RS485几乎是标配很多老设备没有CAN引出、没有以太网口但哪怕只有一对双绞线也能把升级通信跑起来。这是串口方案在工业场景里最现实的价值。1.2 SCI串口方案为什么在F28335上行得通F28335自带3个SCI模块SCI-A/B/C硬件上天生支持串口通信。更关键的是TI在F28335的Boot ROM里做了SCI引导流程上电时可以通过特定引脚组合让芯片从SCI-A口接收引导代码。这说明原厂设计时就给串口引导留了一条路我们做的Bootloader只是把这条路做成正式的产品功能。传输速度方面F28335的SCI配置成115200或者460800都很稳几十K words的固件包在115200波特率下大概十几秒传完460800下几秒就完事。如果算上擦除和校验整包升级控制在半分钟以内现场完全可以接受。对有更高速度要求的场景还可以把波特率拉到921600但要额外关注线材和隔离器的带宽。成本上是零增加不用加任何芯片复用设备本来就有的SCI引脚和RS485收发器就行。这一点对硬件已经定型的存量设备尤其关键哪怕板子已经量产了只要引出过串口后续加Bootloader也能补救。2. 架构先行Flash分区、内存布局与启动流程设计2.1 Flash八个扇区怎么分给Bootloader和App写Bootloader之前先把F28335的Flash扇区表记牢。F28335片上Flash共256K words注意是16位word为单位的地址范围从0x300000到0x33FFFF一共8个扇区。常见划分如下扇区地址范围容量A0x300000 - 0x307FFF32K wordsB0x308000 - 0x30FFFF32K wordsC0x310000 - 0x31FFFF64K wordsD0x320000 - 0x32FFFF64K wordsE0x330000 - 0x333FFF16K wordsF0x334000 - 0x337FFF16K wordsG0x338000 - 0x33BFFF16K wordsH0x33C000 - 0x33FFFF16K words我建议Bootloader放在A扇区32K words对于Bootloader来说绰绰有余。Bootloader编译出来通常不到10K words剩下的空间可以放升级标志、版本号、跳转参数这些运行时数据。App放在B扇区开始如果App比较大可以连续占用B、C、D等扇区。这里有一条红线Bootloader自己所在的扇区在升级流程里绝对不能擦除否则升级到一半断个电Bootloader也没了芯片就彻底变砖。2.2 上电先跑谁引导标志与启动跳转判断Bootloader和App是两套独立的工程不放在一起编译。上电顺序是芯片复位 - Boot ROM根据引脚电平决定引导方式 - 跳到Flash起始地址执行Bootloader - Bootloader判断是否需要升级 - 不需要则跳到App入口。这里需要设计一个“升级标志”。常见做法是在Bootloader区内部固定地址存一个特殊值比如0xA5A5。上位机发来升级请求时Bootloader把这个值写入标志位然后软复位上电后Bootloader读到这个标志就进入升级流程而不跳App。升级完成后再把标志位清掉。还有一种做法是上电后延时等待上位机握手命令比如等200ms收到升级命令就留在Bootloader等不到就跳App。实际项目里两种方法可以结合先检查标志再开一个短暂握手窗口兼顾可靠性和现场操作自由度。2.3 中断向量表Bootloader方案里最容易忽略的坑F28335的PIE中断向量表放在RAM里地址在0x000D00附近不是像有些MCU那样固化在ROM里。这带来一个关键问题Bootloader和App各自要维护自己的向量表。Bootloader用串口中断接收数据时要把自己的SCI中断服务函数挂到PIE向量表上。App启动后必须重新初始化PIE并装载自己的向量表。如果App代码里忘了这步中断来了之后CPU会跑到Bootloader的中断函数里去执行表现就是“功能偶尔异常但又不完全死机”这类问题排查起来非常头大。网上经常会问“bootloader与app的ld文件和.s文件是一致的吗”答案很明确完全不一致cmd文件不同中断向量段也各自独立App工程必须有自己的PIE初始化逻辑。3. Flash API烧写为什么必须在RAM里跑怎么跑3.1 F28x Flash API的RAM运行约束与初始化F28335烧写Flash不能直接对Flash地址写数据必须调用TI提供的Flash API库函数就是Flash28335_API_V210.lib这类东西。这个库有一个硬性约束擦除和编程操作期间CPU不能从Flash取指令必须把API函数复制到RAM里执行。原因是Flash控制器在擦写Flash时同一时刻Flash无法响应取指请求如果此时CPU还从Flash里取指令就会卡死或者执行出错。所以Bootloader的链接脚本里要单独划一块RAM段给Flash API函数在main函数里用memcpy把整个库从Flash复制到RAM之后所有擦写操作都调用RAM里的副本。F28335的SRAM总量是34K words扣掉运行栈和缓冲区划出8K words给Flash API通常够用。调用Flash操作前还要做初始化配置Flash等待状态、使能流水线模式减少CPU访问Flash的等待周期。这个Init函数也必须从RAM执行如果忘了初始化直接擦大概率返回错误状态字甚至整片Flash锁死。3.2 擦除、编程、校验的完整时序Flash操作流程不能乱我的实际经验是先擦后写、边写边验最后整体校验。基本时序如下// 伪代码非完整实现 Flash_Init(); // 初始化Flash控制器 // 擦除App区所有扇区 Flash_Erase(SECTOR_B); Flash_Erase(SECTOR_C); // 分帧编程每帧4K words写入指定Flash地址 while (recv_frame(buf)) { Flash_Program(flash_addr, buf, len); flash_addr len; } // 读回校验 Flash_Verify(flash_addr, buf, len);擦除和编程都是耗时操作擦一个扇区往往要几十到几百毫秒编程时每写一个word也要微秒级。所以上位机一定不能连发必须等每帧的ACK再发下一帧。我在协议里把超时时间设成了2秒主要就是给擦除操作留出余量。3.3 串口中断和Flash操作的并发冲突处理这是串口Bootloader最容易翻车的地方。F28335的SCI有16级FIFO看着不小但Flash擦除一个扇区要几百毫秒这段时间内如果上位机还在发数据哪怕SCI中断开着只要CPU在执行Flash_Erase中断根本进不去FIFO瞬间就被塞满后续字节直接丢弃。解决办法是协议层做流控擦除命令发出后上位机必须等擦除完成的ACK期间不允许发数据帧。所有数据帧的发送都采用“发一帧等一帧”的交互模式ACK是对后续帧的授权。我在第一版里图省事用了连续发送结果擦完扇区回来发现丢了几十个字节排查半天才意识到是流控缺失。4. 跳转App那一下做对了才不算白写Bootloader4.1 跳转条件与函数指针跳转只有全部数据接收完成、CRC校验和读回校验都通过之后Bootloader才允许跳转。跳转代码本身不复杂本质就是函数指针调用void jump_to_app(void) { DINT; // 关闭全局中断 SysCtrlRegs.WDCR 0x0068; // 禁止看门狗 void (*app_entry)(void); app_entry (void (*)(void))0x308000; // App入口地址 app_entry(); }App入口地址0x308000对应B扇区的起始地址这是我在App工程链接文件里指定的必须是App工程中codestart段所在的地址。这个地址不是拍脑袋写的而是通过.map文件确认的每个工程的入口地址都不同。如果入口地址取错跳过去就是执行一坨随机数据系统直接跑飞。4.2 App侧cmd文件和中断向量的配合修改App工程不是复制Bootloader改个名就完事链接文件必须专门调整。Bootloader占了0x300000起始的A扇区App的codestart和入口代码就不能再放这里要搬到0x308000。我在App的cmd文件里把BEGIN段、codestart段分配到B扇区RAM区也要重新划分避免和Bootloader用的RAM重叠。App启动后的第一件事是重新初始化PIE中断向量表。通常包含InitPieCtrl()、InitPieVectTable()然后把App自己的中断服务函数填充进去。同时要把IER和IFR清零防止Bootloader期间挂起的中断在App里突然触发。这一步做错最常见的现象就是升级后串口一收数据就进异常板子重启看起来像“升级把程序写坏了”其实向量表根本没换过来。4.3 看门狗、外设与引脚状态的跳转前清理跳转前只关中断是不够的。Bootloader初始化过的SCI、GPIO、ADC这些外设状态可能和App期望的不一样尤其要注意ePWM这类功率相关外设。如果Bootloader测试串口时把某个引脚配置成了强上拉输出跳转后App还没来得及重新初始化这个引脚电平就可能在功率级上造成瞬时误动作。我的处理方式是跳转前把所有用过的外设关掉GPIO恢复到默认输入状态关键输出先放安全电平然后延时几个毫秒等电平稳定再跳转。看门狗那行代码放在了最靠近跳转的位置确保跳转瞬间不会被狗咬复位。App的启动代码里还要尽早重新喂狗两边要衔接好。5. 通信协议与上位机把升级整条链路串起来5.1 帧格式与命令定义通信协议是整个升级系统里最容易返工的部分前期设计得简单一点后面省很多事。我用的帧格式是这样的字节偏移内容说明0~10xAA 0x55帧头2命令字0x01握手 0x02擦除 0x03写数据 0x04整体校验 0x05跳转3~6地址32位Flash地址7~8长度16位单位是word9~N数据载荷区末尾2字节CRC16从命令字到载荷区结束CRC16用多项式0x1021初值0xFFFF这个算法很通用网上现成的查表实现直接拿去用。帧头为什么要两个字节是为了尽量避开噪声干扰。单字节帧头在工业现场偶尔会被RS485总线上的毛刺数据骗到两个字节连续匹配的误判概率低很多。命令字定义上握手、擦除、写数据、校验、跳转五条命令基本覆盖全流程。有些实现里会把写数据和擦除合成一条命令我试过看起来省了几行代码实际调试时问题定位麻烦不推荐。5.2 流控、超时重传与RS485方向控制协议层流控是串口Bootloader的生命线。我在上位机侧规定每个命令发出后必须等待ACK超时500ms重发连续3次无响应则报错退出。Flash擦除期间这个等待时间要放宽到2秒因为擦除本身就要几百毫秒上位机等500ms就急着重发反而会把正在做擦除的单片机逼到崩溃。RS485方向切换是一个容易忽略的细节。DSP侧通过GPIO控制DE引脚接收状态时DE拉低发送状态时DE拉高。方向切换之后总线上的电平稳定需要一点时间通常几十微秒到上百微秒。我在每次发完响应帧后都要Delay一下再切回接收否则最后一个字节可能被自己的DE拉低动作截掉。上位机用USB转RS485时也有同样问题发完一帧要等总线静默完成再切接收。5.3 上位机三选一Python脚本、C#小工具还是LabVIEW上位机实现没有标准答案取决于使用场景。我自己调试阶段用Python的pyserial库写一个几十行的脚本就能跑通握手和数据传输快速验证DSP侧协议逻辑有没有问题。脚本里用xmodem库或者自己按帧拼包都行关键是CRC和重传逻辑要跟上位机UI解耦。交付给产线使用时Python脚本对操作员来说不够友好我一般会编译一个C# WinForms小工具界面放一个COM口选择、一个固件文件选择、一个进度条、一个日志框。产线工人只需要点“开始升级”就行不需要知道底层协议。搜索热词里还有LabVIEW方案如果实验室或者产线测试系统本来基于LabVIEW用VISA串口节点做收发也完全可以只是协议解析比文本语言啰嗦一些胜在能直接整合进已有测试流程。6. 实测翻车现场那些“下载完没反应”的根因排查6.1 “固化后必须接JTAG才能启动”到底哪来的网上搜“TMS320F28335固化程序后必须接JTAG才能启动”是高频问题我自己也被这个问题折磨过。程序明明烧进去了断电重上电就是跑不起来可是CCS里连上JTAG再复位一次程序就正常了看起来就像“必须接JTAG才能启动”。这个问题通常不是Debug模式导致的而是Boot模式引脚配置不对。F28335上电时Boot ROM会读取GPIO84到GPIO87的电平组合决定从Flash启动、SCI启动、SPI启动还是并行启动。如果板子上这几个引脚被其他功能复用了JTAG连接时调试器会强制引导到Flash所以能跑脱离JTAG后Boot ROM按引脚电平走走到了别的启动分支自然跑不起来。排查方法是断开JTAG用万用表量GPIO84~87的上电电平对照TRM的启动模式表逐个确认。另一个高频元凶是CSM代码安全模块锁死。F28335有个密码区如果Flash编程时不小心把密码区的值写成了非0xFFFFCSM就会锁定。锁定后JTAG还能连上但无法读取Flash内容程序看似烧进去了一上电Boot ROM检测CSM状态异常就直接卡住。密码区地址在0x3F7FF8附近操作Flash时必须避开。6.2 Flash擦除期间丢字节和波特率误差丢字节的问题在第3章提过根因基本都是流控缺失或者FIFO溢出。这里再补一个波特率相关的坑F28335的SCI波特率由LSPCLK分频而来而LSPCLK又由SYSCLKOUT分频得到。如果Bootloader里PLL配置和App不一致Bootloader跑得起来但波特率不准上位机能收到数据却是乱码。排查波特率问题时不要只看公式要拿示波器量DSP的SCI TX引脚数一下一个字节的时间宽度是否和设定波特率吻合。我遇到过一块板子Bootloader里把PLL倍频系数写错系统时钟只有预期的一半115200实际跑成了57600上位机能收到但全是错码折腾了很久才定位到是时钟配置问题。USB转串口芯片CH340、FTDI这类带来的影响主要是驱动安装和枚举方面驱动没装好时设备管理器里看不到COM口这个检查顺序要放在最前面。6.3 升级掉电变砖以及A/B分区兜底方案只要能保证Bootloader扇区不被擦除升级过程中掉电通常不会变砖——最多App区写了一半上电后Bootloader发现App校验失败重新进入升级流程等待新固件就行。真正会变砖的是代码里把擦除范围写大连Bootloader自己也擦掉了那才是叫天天不应。搜索热词里有“bootloader双分区ab分区”这个思路在F28335上完全可行。如果App控制在100K words以内可以把Flash分成两个应用区比如App1放BC扇区App2放D扇区加E/F/G/H的一部分。Bootloader维护一个当前启动分区标志每次升级只擦写非当前分区成功后把标志指向新分区。万一新版本跑不起来Bootloader还能自动回滚到旧分区。这套方案在F28335上做出来之后现场升级的安全性会高一个档次掉电、传错包、断电重启都有兜底。个人体会是串口Bootloader这个功能调试时每一步都要在板级验证过再进下一步。先确认串口能通再测Flash单扇区擦写最后才是完整升级链路。任何一步没验证就往下走出了bug会同时涉及通信、存储、跳转多个环节排查成本会成倍增加。这套思路在F28335上跑通之后换到其他C2000平台比如F28004x、F28069原理也是相通的只是Flash扇区地址和API库不同而已。本文还有配套的精品资源点击获取