
简介面向TI DSP TMS320F28335的嵌入式开发者这份资源提供一套完整的串口Bootloader升级方案内容涵盖bootloader引导固件、应用测试固件以及配套的上位机升级软件可解决量产设备后期固件更新不便的问题。包内共140个文件主要包含C语言工程源码.c/.h、汇编启动文件.asm、编译生成的中间文件.obj、可执行映像.out/.bin以及上位机程序.exe/.dll工程配置文件如.pjt、.ccxml等也一应俱全压缩包整体仅2MB结构紧凑便于快速部署参考。目前已有717人学习下载。借助这套资源开发者可以深入理解28335的Flash分区与跳转机制、串口通信协议定义以及升级失败时的回滚处理思路并可直接使用上位机软件配合测试固件完成联调验证从而大幅缩短Bootloader开发周期适合正在做远程升级、现场设备维护或需要掌握引导加载机制的工程师参考。 搞过DSP开发的朋友应该都有过这种体会产品已经量产发货了现场突然反馈需要改个参数、加个功能甚至是修个Bug这时候要么派人带着仿真器出差要么让客户把板子寄回来来回折腾不说时间成本和人力成本都让人头疼。我自己在TMS320F28335上就踩过这个坑后来下决心把Bootloader串口升级这套方案完整做了一遍才算真正把这个问题解决掉。这篇文章就基于我实际跑通的完整工程把F28335的Bootloader串口升级从原理到代码、从分区规划到协议设计、从调试踩坑到最终验证一步一步拆开讲清楚。先说结论这个方案做完之后现场升级只需要一根USB转串口线配合一个简单的上位机几分钟就能完成固件更新再也不用拆机、不用仿真器。整个过程涉及Flash分区规划、SCI串口通信、中断向量重映射、Flash擦写操作、在线跳转等多个环节每个环节都有不少需要注意的细节。我会把这些细节全部整理出来希望能帮你少走一些弯路。1. 整体设计与思路拆解1.1 为什么不用TI官方的SCI引导模式TMS320F28335内部Boot ROM其实自带了一套SCI引导程序硬件上把GPIO84拉低就能进入。我一开始也想过直接白嫖官方方案省时省力但实际深入一研究就发现问题不少。首先是它的通信协议不公开TI只提供了AN012这种应用笔记里的汇编例程没有完整的上位机协议文档你想自己写一个升级工具就得逆向分析它的时序太折腾。其次是它不支持断点续传也不支持固件加密和校验万一传输中途断电板子直接变砖只能拿仿真器救。还有就是官方SCI引导程序放在Boot ROM里Flash的划分、跳转逻辑这些你完全没法定制灵活性基本为零。自研Bootloader的好处就很明显了通信协议自己定义想加校验加校验想加加密加加密Flash分区自己规划可以预留参数存储区、日志区升级过程的安全策略也能自己做比如升级失败自动回滚到旧版本。说白了官方方案是别人给你定死的规矩自研方案是规则自己定出问题也心里有数。1.2 Flash分区规划Boot区、APP区、参数区的博弈F28335的Flash总共有256KB分8个扇区每个扇区32KB。我在实际项目中把Flash规划成了四个区域这是整个方案的地基建议先画一张内存映射图再动手写代码。Bootloader程序放在Sector A和Sector B总共64KB实际编译出来Bootloader只有不到20KB留这么大空间主要为了以后Bootloader本身升级迭代。APP程序放在Sector C到Sector H共192KB我们的实际固件大约120KB空间够用。最后在Sector H末尾单独划出2KB作为升级标志位和版本信息存储区这个区域在APP运行的时候也会被访问用来记录当前固件版本、升级状态、校验结果等关键信息。这样划分的好处是Boot区、APP区、参数区互不干扰。APP升级的时候只擦除APP区不动Boot区即使APP写坏了Bootloader还在板子就还有救。参数区独立出来升级固件不会把校准参数、设备序列号这些重要数据弄丢。1.3 升级流程设计从复位到跳转的完整链路整个升级流程我把它总结成四个阶段每个阶段都有明确的进入条件和退出条件。第一阶段是启动判断。芯片上电复位后Bootloader首先执行读取参数区的升级标志位。如果标志位是0xA5A5说明上位机请求升级进入升级模式如果标志位是0x0000说明正常启动直接校验APP区的有效性合法就跳转执行APP不合法就停留在Bootloader等待升级。第二阶段是握手协商。Bootloader向上位机发送设备信息包括设备ID、Bootloader版本、APP版本、Flash容量等。上位机根据这些信息决定是否允许升级以及用多大的数据包进行传输。第三阶段是数据下载。上位机把固件按帧发送给BootloaderBootloader每收到一帧就写入Flash对应地址然后返回应答帧。这里要注意F28335的Flash写入有最小单位限制一次编程操作至少写16位我实际采用一帧256字节的数据长度连续写入。每帧都带CRC16校验出错就重发保证数据完整性。第四阶段是升级验证。所有数据发送完毕后上位机发送结束帧Bootloader对APP区做一次整体CRC校验和上位机计算的CRC比对。一致则清除升级标志写入新版本号复位重启进入APP不一致则保留升级标志等待重新发送防止升级失败变砖。2. 核心细节解析与实操要点2.1 串口通信协议帧格式设计和波特率选择串口通信协议是Bootloader和上位机之间的语言协议设计得好不好直接决定升级过程的稳定性和可调试性。这是我最终定下来的帧格式帧头(2字节) 帧类型(1字节) 数据长度(2字节) 数据域(N字节) CRC16(2字节) 帧尾(1字节)帧头固定为0xAA 0x55帧尾固定为0x0D 0x0A。帧类型有0x01握手请求、0x02握手应答、0x03数据帧、0x04结束帧、0x05错误帧等。数据长度指示数据域的字节数最大256字节。CRC16的校验范围是从帧类型到数据域末尾多项式用0x8005这是Modbus标准用的那个。波特率我最终选的是115200。F28335的SCI模块时钟来自LSPCLK默认是37.5MHzBRR寄存器计算方法是BRR LSPCLK / (波特率 * 8) - 1代入115200就得到BRR 37500000 / (115200 * 8) - 1 ≈ 39.7取整后实际波特率和理论值有约0.2%的误差完全在可接受范围内。之前试过460800虽然速度快了4倍但遇到干扰大的工业现场误码率明显上升重传反而浪费时间可靠性优先的话115200是甜点值。2.2 中断与查询模式的选择为什么不用中断接收Bootloader的串口接收我用了查询模式没有开中断。可能有人会觉得奇怪嵌入式开发不都崇尚中断驱动吗这里有个关键原因Bootloader运行期间中断向量表还没有重映射到APP区此时如果有中断触发CPU会跳到Boot ROM里的默认向量而不是我们的中断服务函数可能导致不可预知的行为。另外查询模式配合超时机制逻辑更简单直观。主循环里不断查询SCI接收标志位收到一帧数据就处理一帧处理完继续查询。没有中断嵌套的烦恼也没有中断优先级要操心。对于115200波特率、每帧256字节数据的情况一帧的接收时间大约22毫秒查询模式完全来得及处理不会丢数据。要注意的是查询模式下一定要加超时判断。每帧数据接收过程中如果两个字节之间的间隔超过100ms就认为传输异常丢弃当前帧重新等待帧头。这样即使用户中途拔线、上位机崩溃Bootloader也不会卡死。2.3 Flash操作的关键参数与注意事项F28335的Flash操作有几个硬性参数必须记住。Flash编程电压由芯片内部电荷泵产生编程一个16位字需要约20微秒但这20微秒是Flash状态机自动完成的CPU只需要等待PGS标志位清零即可。Flash擦除一个扇区需要约20毫秒这段时间内不能对Flash做任何操作。操作Flash的步骤是固定的先解锁Flash配置寄存器然后设置等待状态再执行擦除或编程命令最后检查状态寄存器确认操作成功。等待状态的设置非常关键F28335在150MHz主频下Flash的等待状态必须设置为5个周期设小了会导致Flash读取数据错误程序跑飞设大了影响性能。我还遇到一个坑Flash编程必须按16位字对齐不能像操作普通RAM那样按字节访问。如果固件数据是奇数长度最后一字节要补0xFF凑成偶数。这个细节在拼装数据帧的时候就要处理好不然后面写Flash会报错。3. 实操过程与核心环节实现3.1 工程搭建Bootloader工程和APP工程的配置要点Bootloader和APP虽然是两个独立的工程但两者之间有不少需要配合的地方最核心的是中断向量表的处理。F28335的中断向量表默认放在Boot ROM里上电后CPU从Boot ROM启动执行完Bootloader后需要把PIE向量表重定向到APP的中断向量表。我在Bootloader的跳转代码里在跳转前关闭全局中断然后把PIE控制寄存器恢复默认值再跳转到APP的入口地址。APP工程在启动代码里会重新初始化PIE向量表这样两者就不会冲突。两个工程的CMD文件都要自己核对。Bootloader的CMD文件要把所有段都限定在Boot区APP的CMD文件要把所有段限定在APP区绝对不能交叉。我在APP的CMD文件里专门定义了codestart段放在APP区的起始地址用来存放跳转入口。跳转时直接调用的就是codestart段的起始地址。3.2 核心代码实现跳转、Flash写入、CRC校验跳转代码是Bootloader和APP的交接口我把它单独写成一个函数逻辑很清晰void jump_to_app(void) { void (*app_entry)(void); // 关闭全局中断避免跳转过程中被打断 DINT; // 复位PIE控制器恢复默认状态 PieCtrlRegs.PIECTRL.bit.ENPIE 0; // 从APP区起始地址取出复位向量 app_entry (void (*)(void))0x30000; // 跳转前初始化栈指针指向APP的栈顶 // 这个地址在APP的CMD文件里定义需要在Bootloader里硬编码对应值 app_entry(); }这里的0x30000是APP区的起始地址对应Sector C的地址。跳转前为什么要关闭中断前面说过了还有一个细节是跳转前要把看门狗关闭不然跳转过程中看门狗超时触发复位又会回到Bootloader形成死循环。Flash写入的代码框架如下关键点是每写一个16位字都要等待Flash状态机完成void flash_write(Uint16 *dest, Uint16 *src, Uint32 len) { Uint32 i; // 解锁Flash配置寄存器 EALLOW; SysCtrlRegs.PCLKCR0.bit.ENCLK 1; FlashRegs.FWRITE.bit.ENPIPE 0; // 循环写入每个16位字 for (i 0; i len; i) { // 启动编程命令 dest[i] src[i]; // 等待编程完成标志 while (FlashRegs.FSTATUS.bit.PGS 0) { // 如果写失败PGS会置1但BUSY标志为0需要检查 } } EDIS; }CRC16校验函数用查表法实现速度比逐位计算快很多。查表法本质上是把常见的CRC计算过程预先算好存在数组里运行时直接查表256条记录的表项每条16位总共512字节Bootloader和上位机都用同一套表保证计算结果一致。3.3 上位机设计用C#快速实现一个升级工具上位机我用C#写了个简单的串口升级工具界面就三个按钮连接设备、开始升级、显示日志。核心逻辑是协议封装和文件解析。协议封装就是把之前定义的帧格式用代码实现把bin文件按256字节拆包逐个包发送等待应答超时重发。文件解析要处理两种格式纯bin文件直接发给Bootloaderhex文件要先解析地址和数据按地址写入对应Flash位置。实际使用中bin文件更省事因为Bootloader已经把APP固定到0x30000地址了bin文件不需要地址信息。C#端串口操作很简单用System.IO.Ports.SerialPort类设置好波特率、数据位、停止位、校验位就能收发。这里要注意的是串口接收数据是异步的上位机要在DataReceived事件里做协议解析不能在主线程里死等。我遇到过一个典型问题上位机发送数据帧太快Bootloader处理不过来导致丢帧。后来在发送每帧后加入20ms延时问题就解决了其实这不是Bootloader速度不够而是SCI模块发送缓冲有限上位机要照顾下位机的处理节奏。4. 常见问题与排查技巧实录4.1 APP跳转后跑飞中断向量表和栈指针的坑这个问题几乎每个人都遇到过我自己也折腾了整整一天才定位到根因。APP程序跳转后跑飞或者跑起来但不正常通常有两个原因。第一个原因是中断向量表没有重映射。Bootloader运行的时候PIE向量表指向Bootloader自己的中断服务函数跳转到APP后如果APP没有重新初始化PIE向量表中断来了之后会跳转到Bootloader的中断服务函数而Bootloader的函数里用的变量、Flash地址映射都不一样程序肯定跑飞。解决办法是在APP的启动代码里确保在main函数之前就完成PIE向量表的初始化这个TI的DSP库已经做好的关键是别在Bootloader里把PIE禁用后忘了恢复。第二个原因是栈指针初始化不正确。F28335上电后SP指针默认指向Boot ROM里的一个临时栈跳转到APP后要用APP自己的栈。我在Bootloader的跳转函数里手动设置了SP但当时写错了地址指向了Bootloader的RAM区APP运行一段时间后栈溢出把APP的代码段覆盖了程序才跑飞。排查方法是把APP区起始地址的前几个字打印出来检查是不是0x0000开头然后再检查SP指向的RAM地址是不是APP工程里定义的栈空间。4.2 串口收不到数据或数据乱码波特率误差和电平问题串口通信出问题优先级最高的是检查波特率。前面算过BRR寄存器取整带来的误差正常情况下0.2%的误差不会导致通信失败。但如果你的系统时钟不是标准的150MHz比如换了外部晶振或者PLL配置不对波特率误差会放大就可能出现乱码。排查时要做的第一件事是用示波器测量SCITXDA引脚上的波形数一数一个字节的位宽是不是和理论值一致。没有示波器的话可以先用串口助手发一个固定字符0x55看回显是不是0xAA如果不对优先检查时钟配置。还有一个常见问题是USB转串口模块的电平不匹配。F28335的SCI引脚是3.3V TTL电平直接用5V的USB转串口模块虽然多数时候能通信但长期使用有损坏芯片的风险。建议用支持3.3V电平的模块比如CP2102或者用SP3232做RS232电平转换。市面上有些便宜的CH340模块默认是5V电平需要确认有没有跳线切换到3.3V。4.3 Flash擦写失败等待状态和擦除时间要设对Flash编程失败的典型表现是写完后读回来的数据和预期不一致或者Flash状态寄存器的错误标志位置1。F28335的Flash有几组与时间相关的寄存器要配置包括等待状态寄存器、编程脉冲宽度寄存器、擦除脉冲宽度寄存器。等待状态设置前面说过150MHz下必须设为5这个值写在FlashRegs.FBANKWAIT里。编程脉冲宽度和擦除脉冲宽度寄存器设的是Flash操作的最小时钟周期数如果设置得太小Flash状态机在操作完成前就认为完成了数据就没写进去。我当时的做法是在TI的FlashAPI库基础上做封装把标准库里的初始化函数调用一遍再自己封装写入和擦除接口这样参数配置就交给库来处理比自己裸写寄存器可靠得多。擦除时间还有个容易忽略的地方擦除一个扇区大约20毫秒这段时间内如果来了串口中断而中断服务函数里又访问了Flash就会导致擦除失败或数据损坏。所以擦除Flash前一定要屏蔽串口中断或者把串口数据先缓存到RAM里等擦除完成再处理。我实际是把升级流程设计成收到数据帧、写入RAM缓冲区、凑够一整个扇区的数据量再一次性擦除写入既提升了效率也避免了擦除和写Flash期间被中断打扰。4.4 升级一半断电变砖备份区和回滚机制升级过程中断电是最可怕的情况轻则固件损坏重则Bootloader也损坏。我设计的方案里做了一层保护APP区有两份一份是当前运行版本一份是备份版本升级时先往备份区写入新的固件写入成功后交换映射关系下次启动跑新版本如果新版本跑不起来Bootloader检测到启动失败会回退到旧版本。这个机制的实现涉及到Flash映射切换F28335的Flash区不是动态映射的所以我实际用的是固定地址方案备份区放在Sector H升级时先写备份区写完校验通过后把升级标志改为指向新版本APP启动后运行正常则把备份区内容复制到运行区或者直接把APP区的有效标志位更新。这个方案在原理上是可行的但会占用额外的Flash空间如果你追求简单Flash空间又紧张那至少要做到升级失败时保留旧固件的有效标志让Bootloader能跳回旧版本。4.5 关于CMD文件和.s文件Bootloader和APP为什么可以不一样热搜词里有“bootloader与app的ld文件和.s文件是一致的吗”这个问题对DSP和ARM是不一样的。在F28335上Bootloader和APP是两个独立的CCS工程每个工程都有自己的CMD文件、启动文件和中断向量表它们完全可以不一致。Bootloader的启动文件把运行环境初始化好然后跳转APP的启动文件再做一次自己的初始化两者互不影响。关键是跳转后CPU执行的是APP的启动代码所以APP的启动代码在跳转后自然会执行一遍不需要Bootloader操心。唯一需要注意的是APP的启动代码里对系统寄存器有一些初始化操作比如PLL重配、外设时钟使能等如果Bootloader已经做过了APP重复做也不算错只是浪费一点时间。更重要的是APP启动代码里对栈指针、全局变量的初始化要正确这些工作由编译器生成的启动代码完成不需要手动干预。这在实际排查问题时的意义是不要纠结两个工程的启动文件是否一致而是把注意力放在跳转入口地址、中断向量表、栈指针这三个地方它们才是跳转成功的关键。5. 版本管理和升级策略一些额外的心得做完Bootloader串口升级之后我又补了一些配套工作这里一并分享出来。版本信息管理方面我在参数区存了四个字段Bootloader版本号、APP版本号、编译日期、升级次数。上位机在握手阶段就能读到这些信息显示在界面上现场工程师一眼就能确认当前设备跑的什么版本。这个设计在售后排查问题时非常实用经常能帮你快速定位是不是版本不对导致的故障。固件加密方面如果产品对安全要求比较高可以在上位机发送前对固件做AES128加密Bootloader收到后先解密再写入Flash。F28335的CPU主频150MHz软件AES解密256字节一帧耗时大约几毫秒不会影响升级速度。我的实际项目因为保密要求不高暂时没做加密但预留了解密接口后面要加也方便。超时重传策略方面我在协议里加了超时重传机制但做了个限制每帧最多重传3次超过3次就终止升级不再死循环重试。这样可以避免通信链路故障时无休止地卡在升级状态。实际生产中遇到信号干扰强的环境3次重传成功率高很多你可以根据自己的场景调整这个参数。最后再分享一个小技巧调试Bootloader的时候建议先在RAM里调试不要急着烧Flash。F28335支持从RAM启动把编译生成的.out文件加载到RAM里运行配合CCS的断点调试可以单步跟踪Bootloader的每一个流程比直接烧Flash后看现象快十倍。等RAM调试完全通过再烧Flash验证最终效果。这个习惯帮我节省了大量时间尤其是排查跳转跑飞这种难缠的问题时断点一打变量一看真相就出来了。本文还有配套的精品资源点击获取