F28335 Flash代码固化:从RAM调试到独立运行的全流程解析

📅 2026/8/17 9:34:39
F28335 Flash代码固化:从RAM调试到独立运行的全流程解析
1. 从RAM到Flash为什么F28335的代码下载是个技术活如果你刚开始玩TI的DSP尤其是像F28335这种经典的C2000系列大概率会经历这么一个阶段在CCS里写代码、编译、点一下调试按钮程序跑起来了一切都很美好。但当你拔掉仿真器重新给板子上电发现程序“失忆”了一切又回到了原点。这时候你才会真正意识到把代码下载到Flash里运行和直接在RAM里调试完全是两码事。这不仅仅是点个“下载”按钮那么简单它涉及到芯片的启动流程、存储器的物理特性、链接器命令文件的配置以及一堆容易让人踩坑的细节。我刚开始用F28335做项目时也在这个环节卡了很久。网上资料零散官方文档又过于庞杂很多关键步骤都是一步步试错试出来的。今天我就把自己从RAM调试转向Flash固化过程中所有核心的技术点、配置步骤以及那些文档里不会写的“坑”系统地梳理一遍。无论你是学生做毕设还是工程师做产品开发搞懂F28335的Flash下载都是让代码从实验室走向实际应用的关键一步。2. 核心原理F28335的存储器架构与启动机制要理解如何下载到Flash首先得明白F28335芯片内部是怎么“布置房间”的以及它上电后是怎么“找活干”的。2.1 存储器地图RAM、Flash与OTP的分工F28335的存储器空间是统一编址的我们可以把它想象成一个巨大的线性地址表。对于代码和数据存放我们主要关心以下几块M0和M1 SARAM这是两块高速RAM各1K x 16位。它们最大的特点是零等待周期代码在其中运行速度最快。所以在调试阶段我们通常把程序加载到这里执行。但它们是易失性的断电后数据就没了。L0-L7 SARAM这是8块更大的RAM每块4K x 16位同样零等待。它们通常用来存放全局变量、堆栈和实时性要求高的数据。部分也可配置为程序空间。Flash存储器这是我们的主角。F28335片上集成了256K x 16位的Flash地址范围是0x33 0000到0x33 7FFF。Flash是非易失性的代码烧录进去断电后再上电依然存在。但是从Flash直接取指执行有等待周期速度比RAM慢。因此高性能应用中常采用“从Flash启动拷贝到RAM运行”的策略。OTPOne-Time Programmable一次可编程存储器通常用来存放工厂校准数据、加密密钥或最核心的引导代码用户一般很少直接操作。关键点在于CPU执行指令时它只认地址。编译器和链接器的工作就是决定把你的每一段代码、每一个变量放到这个线性地址表的哪个位置。2.2 上电引导流程芯片如何找到第一条指令这是理解Flash下载的另一个基石。F28335上电或复位后会执行一段固化在芯片内部的ROM引导加载程序Bootloader。这个Bootloader会检查几个特定的GPIO引脚如GPIO34/GPIO37等的状态根据这些状态决定从哪里开始执行用户程序。常见的引导模式有跳转到Flash这是最常用的模式。Bootloader直接跳转到Flash的起始地址通常是0x33 0000开始执行。跳转到SARAM跳转到M0 SARAM的起始地址用于RAM调试。SCI/SPI引导通过串口或SPI从外部设备下载代码用于工厂生产或系统升级。我们做Flash下载目标就是让芯片在“跳转到Flash”这个模式下能从Flash的起始地址找到我们正确的、可执行的代码。这要求我们的程序镜像必须被正确地放置在Flash对应的地址区域并且开头部分必须包含一个合法的中断向量表其中第一条就是复位向量Reset Vector指向我们的main函数或c_int00启动例程。3. 工程配置实战从CCS工程到可烧录的.out文件知道了原理我们开始在Code Composer Studio (CCS)里动手配置。一个针对Flash的工程和纯RAM调试的工程配置上有几个关键区别。3.1 链接器命令文件.cmd的深度解析这是整个配置的核心也是最容易出错的地方。.cmd文件告诉链接器把编译生成的各个段Section放到存储器的哪个位置。我们需要准备两个版本的.cmd文件一个用于RAM调试如F28335_RAM_lnk.cmd一个用于Flash固化如F28335_FLASH_lnk.cmd。这里我们重点看Flash版本。一个典型的Flash.cmd文件主要包含两部分MEMORY指令和SECTIONS指令。MEMORY指令定义存储区块MEMORY { PAGE 0: /* 程序空间 */ FLASH : origin 0x33 0000, length 0x7F80 /* 256K - 保留空间 */ FLASH_RSVD : origin 0x33 7F80, length 0x0080 /* 保留给TI的Flash API或安全模块 */ BEGIN : origin 0x33 FFF6, length 0x0002 /* 用于安全模块 */ PAGE 1: /* 数据空间 */ RAMM0 : origin 0x000000, length 0x0400 RAMM1 : origin 0x000400, length 0x0400 RAML0 : origin 0x008000, length 0x1000 /* ... 其他RAM区域 ... */ }这里的关键是PAGE 0程序空间里的FLASH区域我们程序的代码段.text、常量段.cinit,.const等都要链接到这里。SECTIONS指令分配段到存储区SECTIONS { /* 中断向量表必须放在Flash的开始 */ .intvecs : FLASH_BEGIN_VECS, PAGE 0 /* 代码段 */ .text : FLASH, PAGE 0 /* C编译器使用的初始化表 */ .cinit : FLASH, PAGE 0 /* 常量数据 */ .const : FLASH, PAGE 0 /* 已初始化的全局和静态变量初始值在Flash运行时要拷贝到RAM */ .econst : FLASH, PAGE 0 /* 运行时实际存放数据的RAM区域 */ .stack : RAMM1, PAGE 1 .ebss : RAML0, PAGE 1 /* 未初始化的全局/静态变量 */ .esysmem : RAML0, PAGE 1 /* 动态内存堆 */ }这里有一个极其重要的概念.econst和.ebss。.econst段存放的是已经初始化的全局变量和静态变量的初始值。这些初始值在编译时就被确定并和代码一起存放在Flash里 FLASH。但是变量本身在运行时是需要被读写的所以它们必须位于RAM中。因此在程序启动时在main()函数之前启动代码c_int00会负责将.econst段的内容从Flash拷贝到对应的RAM地址即.ebss段或其它指定地址。.ebss段则存放未初始化的变量启动时只需清零。实操心得1.pinit段别漏了除了常见的段还有一个容易忽略的段叫.pinit它存放C全局对象的构造函数地址表。如果你的工程用了C或者链接了某些库必须确保.pinit也被正确分配到Flash并包含在拷贝表中否则全局对象构造可能失败导致程序行为异常。3.2 编译器与链接器选项设置在CCS工程属性中需要检查以下关键设置Device选型确保选择的是TMS320F28335。Compiler Version选择与你安装的C2000编译器版本一致如TI v22.6.x.LTS。Runtime Model通常选择--rom_model。这个选项告诉链接器生成代码的初始化表和拷贝表在.cinit段里以便启动时能正确初始化变量。Linker Command File在Build C2000 Linker File Search Path中将Command File的路径指向我们修改好的Flash版本.cmd文件如${ProjDirPath}/F28335_FLASH_lnk.cmd。输出文件格式在Build C2000 Linker Basic Options中Output Format通常选择--output_filexxx.out这是CCS调试器加载的标准格式。如果需要生成二进制文件.bin用于其他烧录器需要额外配置--bin_file选项或使用hex2000工具进行转换。实操心得2优化等级与Flash等待状态在Build C2000 Compiler Optimization中你可以设置优化等级。高等级优化如-O2或-O3可以显著减少代码体积这对于空间有限的Flash很有用。但要注意高优化可能会改变代码行为调试时建议先用-O0无优化。另外在最终Flash运行时需要根据系统时钟频率配置Flash的等待状态通过FlashRegs.FBANKWAIT寄存器以确保CPU能正确读取Flash。这个配置通常放在系统初始化函数里如InitSysCtrl()。4. 代码迁移与启动代码适配仅仅配置好链接器还不够你的C代码本身可能也需要为Flash运行做一些调整。4.1 关键宏定义与头文件在F28335的标准外设库如DSP2833x_Device.h中通常会有一些控制存储器模式的宏定义。你需要确保在编译Flash版本时正确的宏被定义。最常见的是_FLASH宏。在工程预定义符号Build C2000 Compiler Predefined Symbols中添加_FLASH。这个宏会影响到一些底层头文件比如DSP2833x_SysCtrl.c中的MemCopy函数实现以及中断向量表的重映射。例如在DSP2833x_GlobalPrototypes.h或相关启动文件中你可能会看到#ifdef _FLASH #define RUN_FROM_FLASH #endif然后在系统初始化代码中会根据RUN_FROM_FLASH来决定是否初始化Flash控制寄存器以及是否执行代码从Flash到RAM的搬移操作。4.2 中断向量表的重定位在RAM调试时中断向量表通常被链接到RAM地址如0x00 0000因为这样修改和调试向量非常方便。但在Flash运行时向量表必须放在Flash的起始区域0x33 0000附近因为芯片上电后是从这里开始取指的。标准外设库提供了DSP2833x_CodeStartBranch.asm和DSP2833x_usDelay.asm等文件来处理启动分支。更重要的是DSP2833x_PieVect.c和.h文件。你需要确保在.cmd文件中.intvecs段被分配到了Flash的起始地址例如origin 0x33 0000。在main()函数一开始调用InitPieVectTable()函数来初始化PIE向量表。这个函数内部通常会检查是否运行在Flash模式如果是它会将Flash中的向量表内容拷贝到RAM中的PIE向量表寄存器区域因为中断响应时需要快速访问向量表放在RAM中效率更高。踩坑记录1忘记初始化PIE向量表这是我早期犯的一个错误。程序在Flash中能启动但一使能中断就跑飞。排查了半天发现是InitPieVectTable()函数在Flash模式下没有正确执行向量拷贝操作。检查该函数源码发现它依赖于某个全局标志位如RunMode而这个标志位又依赖于_FLASH宏是否正确定义。所以确保宏定义和函数调用逻辑匹配至关重要。4.3 初始化函数的调用顺序一个典型的F28335主函数结构如下void main(void) { // 1. 初始化系统控制锁相环、看门狗、时钟 InitSysCtrl(); // 2. 关闭CPU中断初始化PIE控制寄存器 DINT; InitPieCtrl(); IER 0x0000; IFR 0x0000; // 3. 初始化PIE向量表关键从Flash拷贝到RAM InitPieVectTable(); // 4. 初始化所有外设GPIO, ADC, ePWM等 InitPeripheralClocks(); InitGpio(); // ... 其他外设初始化 // 5. 用户代码使能中断 EINT; // 使能全局中断 ERTM; // 使能实时中断 for(;;) { // 主循环 } }这个顺序不能乱。特别是InitSysCtrl()它里面可能会配置Flash等待状态必须在访问任何Flash中的代码或数据之前完成。5. 烧录与调试CCS操作指南及常见问题工程编译成功生成了.out文件接下来就是把它烧写到芯片的Flash里。5.1 使用CCS进行Flash烧录连接硬件确保仿真器XDS100v2, XDS200, XDS560等正确连接F28335开发板并给板上电。建立目标配置在CCS中新建一个Target Configuration File.ccxml选择正确的仿真器和器件型号F28335。进入调试视图点击Debug按钮CCS会连接目标板加载程序默认加载到RAM。此时程序可能停在main函数入口或c_int00。切换至Flash烧录在CCS菜单栏选择Tools On-Chip Flash。这会打开Flash插件工具。不同版本的CCS和芯片支持包这个工具的位置和名称可能略有不同有时也叫F28xx On-Chip Flash Programmer。在Flash工具界面你需要配置几个关键参数Flash Settings选择正确的芯片型号F28335。Erase Options选择Erase Sectors擦除需要烧写的扇区或Erase Entire Flash全擦。第一次烧录或程序改动较大时建议全擦。Program File选择你编译生成的.out文件。其他选项如Verify after programming烧写后校验建议勾选。执行烧录点击Program或Execute按钮。工具会先擦除Flash然后烧写程序最后校验。整个过程会在CCS的控制台输出日志。复位与运行烧录完成后点击CCS调试工具栏的Reset CPU然后Restart最后Run。此时程序应该从Flash开始运行。关键一步拔掉仿真器接口或给开发板完全断电再上电程序应能独立启动运行。5.2 生成独立的二进制文件.bin/.hex有时我们需要用第三方烧录器进行量产这就需要生成二进制文件。使用CCS内置工具在工程属性中Build Steps可以在Post-build steps里添加命令行调用hex2000.exe工具将.out转换为.hexIntel Hex格式或.bin。${CCS_INSTALL_ROOT}/utils/tiobj2bin/tiobj2bin.bat ${BuildArtifactFileName} ${BuildArtifactFileBaseName}.bin ${CG_TOOL_ROOT}/bin/ofd2000.exe ${CG_TOOL_ROOT}/bin/hex2000.exe ${CCS_INSTALL_ROOT}/utils/tiobj2bin/mkhex4bin.exe这条命令比较复杂它调用了一个批处理文件来完成转换。你需要将${CCS_INSTALL_ROOT}和${CG_TOOL_ROOT}替换成你电脑上的实际路径。使用独立工具TI也提供独立的hex2000工具可以在命令行下使用。你需要编写一个.cmd格式的配置文件指定输入.out文件、输出格式、存储器宽度等参数。踩坑记录2bin文件烧录后不运行生成.bin文件后用烧录器写到Flash的0x33 0000起始地址但芯片不运行。除了前面提到的启动代码问题还要检查地址偏移有些烧录软件或烧录器需要你指定.bin文件在Flash中的起始地址。确保你设置的是0x33 0000而不是默认的0x00。数据宽度F28335是16位器件Flash也是16位宽。确保烧录器配置为16位或字模式而不是8位字节模式。用8位模式烧录会导致高低字节顺序错乱。文件内容用二进制查看工具如Hex Editor打开生成的.bin文件看看开头几个字节是不是有效的指令码例如跳转指令0x2B0xFC对应的汇编可能是SB 0, UNCF这通常是启动分支。如果全是FF或00说明转换可能出了问题。5.3 常见错误与排查Error: Flash Programmer: Error erasing sector擦除扇区错误原因Flash被锁定了可能是由于安全模块、不正确的操作或芯片损坏。排查尝试执行Unlock操作Flash工具里可能有该选项。如果不行尝试全片擦除。最坏情况是Flash物理损坏。Error: Flash Download Failed - Target DLL has been cancelled目标DLL被取消原因仿真器连接不稳定或CCS/驱动有问题。排查重新插拔仿真器和USB线重启CCS检查设备管理器中仿真器驱动是否正常尝试降低JTAG时钟频率在Target Configuration里设置。程序在Flash中运行异常跑飞、数据错误原因1等待状态未配置。系统时钟频率很高如150MHz但Flash默认等待状态不足导致CPU读Flash出错。解决在InitSysCtrl()函数中找到配置Flash等待状态的代码例如FlashRegs.FBANKWAIT.bit.RANDWAIT和PAGEWAIT根据数据手册的推荐值进行设置。例如150MHz主频可能需要设置等待状态为5或更高。原因2中断向量表未正确初始化。如前所述检查InitPieVectTable()函数和.intvecs段的链接地址。原因3代码中有直接针对RAM地址的绝对地址访问。在RAM调试时某些指针或数组地址是固定的RAM地址。当代码段被链接到Flash后这些绝对地址访问可能指向错误的位置。解决避免在代码中使用绝对地址。使用链接器分配的符号或相对地址。检查所有的指针操作和内存映射外设访问这些是固定的没问题。程序在RAM调试正常烧到Flash后大小不对或校验失败原因链接器命令文件.cmd中Flash区域的长度定义可能小于程序实际大小。排查编译完成后查看CCS的Console窗口链接器会输出各段的大小和占用地址。检查.text、.cinit、.const等段的总和是否超过了你在MEMORY中定义的FLASH区域的length。记得为TI的保留区域FLASH_RSVD留出空间。6. 进阶话题从Flash引导并搬运到RAM运行对于实时性要求极高的应用如高频PWM控制、快速ADC采样中断服务程序即使配置了等待状态从Flash取指的速度也可能成为瓶颈。这时就需要采用“Flash引导RAM运行”的策略。6.1 原理与实现思路基本思路是上电后Bootloader跳转到Flash起始地址执行一段引导加载程序Bootloader Code。这段小程序用汇编或C写的任务是把存储在Flash中特定位置的主程序代码段通常是.text段和关键的中断服务程序拷贝到高速RAM如L0 SARAM中然后跳转到RAM中的地址开始执行。这样后续的代码执行就全在零等待的RAM中了速度得到极大提升。6.2 具体步骤修改链接器命令文件你需要将需要快速运行的代码段比如一个名为.fastcode的段或者你指定的某些函数链接到RAM地址但同时也要在Flash中分配一个区域存放它们的副本。SECTIONS { /* 在Flash中开辟一个区域存放需要搬运的代码的镜像 */ .flashCopy : FLASH, PAGE 0 /* 在RAM中开辟区域存放运行时代码 */ .fastrun : RAML0, PAGE 0 LOAD FLASH /* LOADFLASH 表示加载地址在Flash */ .text : FLASH, PAGE 0 /* 其他不关键的代码仍放Flash */ }LOAD FLASH这个属性是关键它告诉链接器.fastrun段的内容加载地址在Flash里但运行时它应该被放到RAML0运行地址。编写搬运代码在Flash的启动代码中通常是c_int00之前你需要添加一段汇编或C代码来完成从.flashCopy或LOAD地址到.fastrunRUN地址的内存拷贝。这需要你知道这两个段的确切起始地址和长度。链接器会生成一些符号如_flashCopy_start,_fastrun_start,_fastrun_size你可以在C代码中声明这些外部变量并使用它们。extern uint32_t flashCopyStart; extern uint32_t fastrunStart; extern uint32_t fastrunSize; void copyFastCodeToRAM(void) { uint32_t *src (uint32_t *)flashCopyStart; uint32_t *dst (uint32_t *)fastrunStart; uint32_t size fastrunSize / 4; // 假设按32位字拷贝 for(uint32_t i0; isize; i) { dst[i] src[i]; } }然后在main()函数最开始调用这个拷贝函数。函数重定向对于那些被搬运到RAM的函数你需要确保所有对它们的调用包括中断向量都指向RAM中的地址而不是Flash中的原始地址。这通常通过编译器的#pragma CODE_SECTION指令将特定函数分配到.fastrun段来实现。#pragma CODE_SECTION(myFastISR, .fastrun); interrupt void myFastISR(void) { // 高速中断服务程序 }实操心得3注意拷贝过程中的中断在代码搬运期间如果系统中断已经使能可能会发生不可预知的问题。因此搬运操作最好在main()函数最开始、全局中断使能EINT之前完成。或者在搬运代码前先关闭中断搬运完成后再恢复。7. 调试技巧与工程管理建议7.1 如何调试Flash中的程序即使程序烧录到FlashCCS仍然可以进行源代码级调试但有些许不同加载符号当你连接调试器时CCS会加载.out文件中的调试符号信息。你需要确保加载的是针对Flash编译的、带有调试信息的.out文件。设置断点在Flash中设置的断点是硬件断点。F28335的硬件断点数量有限通常6-8个要节省使用。在RAM中则可以设置几乎无限的软件断点。单步执行可以单步执行但由于Flash读取速度慢单步可能会感觉比在RAM中卡顿。查看变量可以查看全局变量和静态变量。但如果变量被优化掉了在高优化等级下可能无法查看。一个高效的调试流程是先在RAM中完成大部分功能调试和逻辑验证然后将工程配置切换到Flash模式解决Flash特有的问题如等待状态、启动代码最后再进行Flash版本的集成测试。7.2 工程管理维护多个构建配置一个良好的习惯是为同一个工程创建多个构建配置Build Configuration例如F28335_FLASH针对Flash发布版本的配置优化等级高-O2定义_FLASH宏使用Flash链接文件。F28335_RAM_DEBUG针对RAM调试的配置优化等级低-O0或-O1无_FLASH宏使用RAM链接文件便于设置断点和观察变量。在CCS中你可以通过右键点击工程 Build ConfigurationsManage...来创建和管理这些配置。在开发时可以轻松切换而不需要手动修改一大堆工程设置。7.3 版本控制与文档将正确的、经过验证的Flash链接命令文件.cmd、关键的启动文件如修改过的DSP2833x_CodeStartBranch.asm以及Flash等待状态配置代码纳入你的版本控制系统如Git。同时在工程根目录下建立一个README.md或Flash_Programming_Guide.txt文档记录下本工程使用的具体芯片型号和Flash大小。所使用的CCS和编译器版本。Flash链接命令文件的特殊配置说明。烧录步骤和注意事项。已知的问题和解决方法。这对于团队协作和项目维护来说价值巨大。