资讯动态

TMS320F28335程序从FLASH拷贝到RAM的两种实现与烧写流程详解

发布时间:2026/10/6 16:47:32 来源:尧图企业网站定制
做过DSP开发的工程师基本都绕不开“程序从FLASH拷贝到RAM”这个操作。我看到标题里写的是“DSP TMS320FF28335”先纠正一个细节市面上没有FF28335这个型号正确的是TMS320F28335搜索引擎里那个“FF”多半是手滑打出来的。不过问题本身倒是非常经典很多人刚接触这块芯片时都会卡在这明明在RAM里仿真跑得好好的烧进FLASH之后一上电就“死机”或者程序跑起来了但速度明显不对——这背后的关键就是FLASH和RAM的执行效率差异。这篇文章我把两类事情一次讲透F28335程序从FLASH拷贝到RAM的两种常用实现方式以及FLASH烧写的完整操作流程。内容适合刚入门C2000系列、正在调28335的小白也适合已经能跑通工程但想搞明白“为啥非要搬代码”的进阶开发者。所有方法都来自实际调试经验照着做基本能一次跑通。1. F28335为什么要把程序搬进RAM跑1.1 FLASH的等待周期问题先说根本原因。TMS320F28335最高主频150MHz片内的256K×16位FLASH虽然容量够大但它不是零等待存储。CPU访问FLASH时必须插入等待状态Wait States通常150MHz主频下要配到3个等待周期再加上FLASH本身没有高速缓存CPU取指令的时候就会“卡顿”。这个“卡顿”对一般的主循环影响不明显但对两类程序是致命的中断服务程序ISR尤其是ADC采样、PWM更新、通信接收这一类对实时性要求高的中断如果每条指令都多等3个周期中断延迟和抖动会翻好几倍。循环密集的数学计算比如FFT、FIR滤波、电机FOC解耦运算代码在FLASH里跑和搬进RAM里跑差距可能到3到5倍。我实测过一个256点FFTFLASH里跑大约要80多个微秒搬到RAM里之后压缩到20多微秒这个差距在控制环路里就直接决定了系统稳不稳。所以要记住一个原则F28335的FLASH适合“放程序、放常量”RAM才是真正用来“跑程序”的地方。1.2 RAM资源盘点动手之前先把手里的资源盘清楚。F28335片内SARAM总共是34K×16位拆成多块M0和M1各1K×16位地址范围0x000000到0x0007FF一般留给栈和变量。L0到L7共8块每块4K×16位地址范围0x008000到0x00FFFF这才是搬代码的主力区域。如果只搬一小部分关键函数34K×16位完全够用。但如果想把整个工程都塞进RAM跑那这个容量就紧张了通常需要靠外部扩展RAM——F28335带EMIF接口可以外接异步SRAM一般挂在Zone6区起始地址0x100000附近。这也是后面两种方法的核心差异部分搬移只需要片内SARAM全量搬移需要外部RAM。2. 方法一Copy Table方式把关键函数搬到RAM2.1 到底搬哪些内容第一种方法也是TI官方推荐、日常最常见的方式只把需要“快”的代码单独切出来放进RAM里执行其他普通代码留在FLASH。那哪些代码需要切出来我的习惯是这三类InitFlash()这类和FLASH控制寄存器相关的初始化函数必须放RAM因为初始化过程中FLASH本身状态不稳定继续从FLASH取指有风险。真正的时间敏感中断服务函数比如PWM中断里的电流环、速度环。调用频率极高的数学计算函数比如自定义的三角函数、滤波算法。具体实现靠TI编译器提供的#pragma CODE_SECTION指令它可以把指定函数放到你自定义的代码段里#pragma CODE_SECTION(MyIsr, ramfuncs) interrupt void MyIsr(void) { // 高实时性处理 }这里ramfuncs就是段名你用别的名字也行关键是CMD文件里要匹配。2.2 CMD文件的段配置接下来是关键中的关键——CMD文件里如何定义这个段。看起来很容易但很多人第一次写就踩坑。正确的写法是“两个地址”LOAD地址程序在FLASH中的存放位置和RUN地址程序在RAM中的运行位置同时导出三个符号供C代码引用。SECTIONS { ramfuncs : LOAD FLASHD, /* 程序烧写在FLASH扇区D */ RUN RAML0, /* 运行时搬到RAM的L0块 */ LOAD_START(_RamfuncsLoadStart), RUN_START(_RamfuncsRunStart), LOAD_SIZE(_RamfuncsLoadSize), PAGE 0 }这里的LOAD FLASHD表示代码作为段数据烧写时放在FLASH扇区DRUN RAML0表示上电后要放到RAML0这个地址空间执行。三个LOAD_START/RUN_START/LOAD_SIZE符号编译链接时会自动生成段在FLASH中的起始地址、在RAM中的运行地址、段的字节长度程序里就用这三个符号来执行拷贝。我给一个更保险的参考地址分配。F28335的FLASH扇区从0x300000开始每个扇区32K×16位扇区D大约是0x318000到0x31FFFFRAML0是0x008000到0x008FFF。如果你的工程里把RAML0已经分配给了别的用途那就换成RAML1或者L2前提是地址别重叠。2.3 启动时拷贝操作CMD配置好之后在main()开头执行一次memcpy就搞定了#include string.h extern Uint16 RamfuncsLoadStart; extern Uint16 RamfuncsRunStart; extern Uint16 RamfuncsLoadSize; void main(void) { // 上电后第一步把ramfuncs段从FLASH拷贝到RAM memcpy((void *)RamfuncsRunStart, (void *)RamfuncsLoadStart, (Uint16)RamfuncsLoadSize); // 之后随便调用ramfuncs里的函数已经是在RAM里执行了 InitFlash(); InitPieVectTable(); // ...其他初始化 }这段代码有两个细节要提醒。第一连接器导出的符号本身不是变量值而是地址所以必须写RamfuncsRunStart来取地址直接写变量名会把符号地址当数值拷完必跑飞。第二拷贝时机要足够早如果中断使能之前没拷中断一来直接去FLASH里取ISR的指令延迟又回去了。所以放main()最开始时间上是绝对来得及的。拷贝完成之后怎么确认成功看编译生成的.map文件。在里面搜ramfuncs你会看到类似这样的输出ramfuncs 0 008000 00000100 ......如果里面LOAD和RUN两个地址对上了数据长度也是你要搬的字节数那就说明段定义没问题。2.4 常见翻车点这个方法我用过很多次踩过的坑基本集中在三处第一CMD里只写了RUN RAML0忘了写LOAD。这样链接器会认为代码本来就在RAM里烧写时这段代码没被安排进FLASH上电后RAM是空的程序直接跳飞。第二段长度算错。LOAD_SIZE导出的是“字”数拷贝循环如果用memcpy按字节算可能差一倍。F28335是16位寻址一个“字”是16位换算时要留意。稳妥的做法是直接用LOAD_SIZE导出的值别自己根据函数估算大小。第三段跟中断向量表打架。很多人把ramfuncs放在RAML0但中断向量表也放在0x000000附近的PIE区或者用户自定义区如果RAM分配里重叠了拷代码会把向量表冲掉结果一进中断就死。编译后看map文件里各段的Start和Length确认没有交叠。提示如果你用了TI的DSP/BIOS或者SYS/BIOS拷贝逻辑可以交给操作系统的启动代码自动处理但底层原理仍然是这套LOAD/RUN分离机制。3. 方法二整体镜像搬运到外部RAM运行3.1 什么场景需要全量搬移方法一把“关键函数”搬进RAM已经能解决90%的问题。但有两个场景它搞不定整个工程都非常吃性能希望所有代码零等待跑而不是只优化加速度敏感的那几个函数。要做在线升级OTA升级过程中必须先擦掉FLASH里的旧程序但擦写FLASH时CPU引用的指令不能再从FLASH获取所以整个应用必须在RAM里跑才能放心擦写。这也解释了为什么FLASH API库程序都要求放在RAM中执行。这两个场景都需要“全量搬移”启动时把整个应用程序从FLASH搬到RAM然后完全在RAM里运行。由于F28335片内SARAM只有34K×16位全量搬移通常配合EMIF外扩SRAM把应用程序放到外部RAM的Zone6区域。3.2 两级引导结构全量搬移本质上是一套两级引导Bootloader App结构。最简单的做法是Bootloader工程烧在FLASH代码量很小负责初始化PLL、初始化EMIF时序、把FLASH里的App镜像拷到外部RAM、最后跳转。App工程整个链接地址都指向外部RAM编译出来的.out烧写到FLASH里作为镜像数据。Bootloader里的核心拷贝代码长这样#define APP_FLASH_BASE 0x320000 /* App镜像在FLASH中的存储地址 */ #define APP_RAM_BASE 0x100000 /* 外部RAM起始地址Zone6 */ #define APP_IMAGE_SIZE 0x20000 /* App镜像长度单位字 */ void CopyAppToRam(void) { Uint16 *src (Uint16 *)APP_FLASH_BASE; Uint16 *dest (Uint16 *)APP_RAM_BASE; Uint32 i; // 拷贝前先初始化外部RAM接口 InitEmif(); for (i 0; i APP_IMAGE_SIZE; i) { *dest *src; } } void JumpToApp(void) { void (*entry)(void); entry (void (*)(void))APP_RAM_BASE; entry(); // 跳转到App入口 } void main(void) { CopyAppToRam(); JumpToApp(); }这里有个关键点跳转地址填的是APP_RAM_BASE要求App工程编译时入口函数通常是_c_int00正好落在那个地址上。最简单的方法是在App工程的CMD文件里把.text段的RUN地址就设成0x100000同时Bootloader拿到App的入口地址后用函数指针跳过去。如果App使用的启动文件里带codestart段那个段的入口地址要跟0x100000对齐否则跳转后指令对不上。3.3 App工程的CMD配置思路App工程编译时所有段都“认为”自己直接运行在外部RAM里CMD大概是这种感觉SECTIONS { .text : RAM_EX0, PAGE 0 .cinit : RAM_EX0, PAGE 0 .bss : RAM_EX1, PAGE 1 .stack : RAM_EX1, PAGE 1 .sysmem : RAM_EX1, PAGE 1 }但注意App工程烧写时这些段的数据本身在FLASH里保存所以App编译链接时需要把LOAD地址指到FLASH、RUN地址指到外部RAM——包括.text逻辑和方法一完全一样只是搬移目标从内部RAML0变成了外部RAM。也就是说即便做全量搬移你依然要用COPY_TABLE或者自己的拷贝循环把App段从FLASH搬到外部RAM。很多新手在这里迷糊既然App全在RAM跑为啥烧写时还能把它写进FLASH因为编译器在链接时生成了“加载地址”烧写工具按加载地址把镜像放进FLASH运行时再由Bootloader按运行地址搬运。这个“双地址”思想是理解C2000启动流程的核心。3.4 两种方法怎么选对比维度方法一部分拷贝方法二整体搬移搬移内容仅关键函数段整个应用镜像运行RAM片内SARAM外部RAM或大容量RAM工程数量单个工程Bootloader App两个工程性能提升关键路径提升明显全部代码零等待运行在线升级能力需要FLASH API配合天然支持擦写FLASH实现复杂度低适合多数项目高适合量产/OTA方案以我的项目经验日常控制类应用选方法一就够了简单可靠如果做逆变器、通信网关、需要频繁升级的产品就果断上方法二。方法二的前期工作量多一些但后期升级维护会轻松很多。4. FLASH烧写完整流程4.1 准备阶段程序写好了RAM里仿真也没问题接下来就是把它烧进FLASH。F28335最常用的烧写方式是“CCS 仿真器 On-Chip Flash工具”我现在用的组合是CCS 8/9 XDS100v2仿真器低成本速度虽一般但烧写28335足够用了。硬件上要确认几样东西仿真器能正常连上目标板Debug模式下加载.out文件可以跑起来这一条先验证过再谈烧写。目标板的引导模式Boot Mode拨码能方便切换。烧写前先确认供电稳定FLASH编程过程中掉电轻则烧写失败重则把FLASH内容写坏需要重新擦除。软件上要准备一个专门给FLASH烧写用的工程版本。注意千万不要拿“RAM调试版”的CMD文件去烧因为那个版本所有段都链接到RAM地址烧进FLASH之后上电没有引导入口芯片起不来。烧写前一定要换成FLASH版CMD段里至少要有codestart和可以拷贝到RAM执行的代码段。提示codestart段非常重要。F28335复位后从0x3FFFC0开始执行Boot ROM跳转到FLASH入口时需要靠codestart里的跳转指令进入_c_int00。CMD里如果没有这个段的正确分配烧写后芯片永远是“黑屏”。4.2 CCS On-Chip Flash烧写步骤目前CCS的烧写流程已经比较傻瓜化了具体如下用FLASH版CMD编译工程生成.out文件。编译窗口里确保没有任何错误程序在RAM仿真时能跑通。在CCS的Debug配置里选择正确的仿真器和芯片型号F28335一般选XDS100v2或者XDS200芯片型号直接选TMS320F28335。启动Debug会话先把.out加载到芯片中如果上电连接一切正常此时程序可以运行点击运行能跑起来。保持Debug连接状态打开烧写工具菜单栏Tools - On-Chip Flash。这个工具会识别当前连接的芯片。在On-Chip Flash界面里选择要烧写的.out文件通常默认就是当前工程编译输出同时确认时钟设置。CCS会询问CPU主频填150MHz工具需要用它计算FLASH编程时序。点击Program或者Load Flash按钮CCS会自动加载FLASH编程算法到RAM然后擦除、编程、校验。烧写完成后退出Debug会话将引导模式拨到Flash Boot重新上电或按复位程序就会从FLASH启动运行。烧写过程中CCS日志会显示每一步状态比如擦除完成、校验通过等。看到“Flash programming complete”之类提示才算成功。4.3 通过串口SCI引导烧写除了仿真器F28335还支持串口烧写量产时特别有用毕竟产线上不可能每块板子都插仿真器。原理是芯片的Boot ROM里内置了SCI引导加载SCI Boot配合上位机软件把程序写入FLASH。大致流程将引导模式拨到SCI Boot模式。F28335的Boot模式由GPIO84到GPIO87决定具体组合需要查芯片的Boot ROM手册或者开发板的丝印不同开发板标注略有差异。用串口线连接SCI-A对应的引脚一般是GPIO28SCIRXDA和GPIO29SCITXDA波特率常用115200。用TI的串口烧写工具比如serial_flash_programmer或者第三方工具加载.out对应的HEX文件。板子上电工具和12835的SCI Boot握手发送数据。由于Boot ROM自身无法直接擦写FLASH工具会先加载一个小型的FLASH编程内核到RAM再由内核完成真正的擦写和写入。烧写完成后切回Flash Boot模式重新上电运行。串口烧写对电源稳定性和串口电平要求较高我曾经因为USB转串口质量和接触不良烧写烧到一半报错板子FLASH里的数据变得不完整最后只能重新擦除重烧。所以量产调试时串口线质量别省另外尽量用带隔离的USB转串口模块。4.4 烧写后的验证和密码问题烧写完别急着断电先把引导模式切到Flash Boot然后重新上电观察程序是否正常运行。F28335没有像单片机那样的串口打印必然条件我一般直接看外设状态比如LED闪烁频率、PWM输出波形、变量值的变换。还有一个必须提醒的坑F28335有Code Security ModuleCSM安全机制CMD文件里如果设置了安全密码0xFFFF以外的值并且你在烧写时把密码区域一起写进去那么芯片就被锁住了。之后再想连仿真器CCS会提示无法连接只有通过密码解锁或者换芯片解决。很多项目在调试阶段不应该烧任何密码保持密码区全为0xFFFF不锁定状态。量产阶段要烧密码的话也一定把密码单独保存最好双人备份。我碰到过不止一次因为密码丢失整个批次的板子全部变砖的情况这个教训很深刻。5. 常见问题与排查技巧实录5.1 典型故障速查表下面是这些年调试F28335时最常遇到的现象和解决方案整理成表格遇到问题直接对照排查故障现象可能原因解决方案烧写后上电程序不运行引导模式没切到Flash Boot检查GPIO84-GPIO87的拨码状态烧写后上电程序不运行CMD里缺codestart段或地址不对确认FLASH版CMD包含codestart并检查map文件程序烧完能启动但很卡ramfuncs段没有拷贝成功ISR还在FLASH里执行检查COPY_TABLE链接符号增加拷贝日志或断点验证拷贝执行时跑飞RUN地址和LOAD地址重叠或者拷贝长度错误对比map文件中的段地址使用LOAD_SIZE而不是估长度上电正常运行一进中断就死中断向量表被拷贝的代码覆盖检查向量表所在RAM段与其他段的地址重叠CCS报错Flash Download Failed目标DLL被取消或仿真器连接不稳定降低JTAG频率换USB口检查目标板供电重新插拔仿真器串口烧写没有响应没进SCI Boot模式或串口接线不对重新确认Boot模式测试GPIO28/29连接烧写器连不上芯片CSM被密码锁定尝试密码解锁无密码则换芯片烧写完程序能跑但掉电丢失烧写时校验未通过或根本没写进去检查日志确认编程成功擦除后重新烧写5.2 自己总结的三个调试习惯除了表格里的排查项我还想分享几个长期调试下来的好习惯。第一个习惯烧写前一定在RAM里先跑通。RAM版本程序和FLASH版本程序共用同一份C代码区别只在CMD文件。我发现很多“烧进去就死”的问题其实在RAM运行阶段已经埋了隐患比如数组越界、栈溢出只是RAM仿真时定位不明显一旦切换到FLASH真启动就暴露了。所以先把RAM版跑稳定再切FLASH版问题会少很多。第二个习惯善用map文件验证段分配。每次编译完打开.map文件检查三个点.text是否挂在FLASH地址ramfuncs的LOAD地址和RUN地址是否和预期一致总占用空间是否超出了RAM容量。这个检查30秒就能完成但能拦住一大半低级错误。第三个习惯烧写时开启LLVM/编译器优化要谨慎。有些时候你把优化等级开高编译器可能会内联掉你标记了ramfuncs的函数导致这个段看起来没被调用、链接器把它优化没。实际项目里我一般对关键代码保持-O2但对ramfuncs段里的函数必要时加__attribute__((noinline))保护防止被优化掉后产生“明明搬了RAM却没效果”的错觉。最后再分享一个小技巧。排查拷贝是否成功除了断点还可以直接在CCS的Memory Browser窗口里输入RUN地址比如0x008000看那块RAM的数据是否和FLASH里对应地址的数据完全一致。这个方法比我见过的很多调试方法都直观一次就能确认拷贝本身的正确性。如果在实际项目里你遇到了这里没写到的情况欢迎带着现象来聊大概率都是CMD和引导这些基础环节的组合问题理清楚思路就会豁然开朗。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑