
先说个背景这篇是《TMS320C6678 开发笔记》系列的第六篇。前面几篇我陆续整理了 CCS 环境搭建、SYS/BIOS 工程怎么建、IPC 核间通信的基础写法基本上都是围绕着“让程序在仿真器下面跑起来”。但很多朋友做到这一步之后很快就卡在了一个非常现实的问题程序确实能在 CCS 里跑板子一断电就没了怎么样才能让它上电自己跑起来答案就是烧写。从项目标题就能看出来用户要的是 TMS320C6678 单核烧写程序。单核烧写说白了就是把编译出来的程序固化到板载 SPI NOR Flash 里让 C6678 上电后能自动从 Flash 加载并运行。这个过程不复杂但中间的坑特别多。我见过不少人算法调得挺好最后在烧写这个环节卡了一周原因往往不是烧写本身而是对 C6678 的启动链路理解不透。这篇笔记我就把单核烧写的完整流程、原理和常见问题一次性说清楚。先给出一个结论单核烧写的本质是让 Core0 在脱离仿真器的情况下能够通过芯片内部 ROM 引导程序RBL从 SPI Flash 加载并跳转到用户代码。其他核可以不启动也可以后续由 Core0 手动唤醒。理解了这个本质后面所有操作和排查思路都会清晰很多。1. 为什么要烧写以及单核烧写到底在烧什么1.1 C6678 的启动流程RBL 是怎么一步步把程序拉起来的TMS320C6678 是 TI KeyStone 架构的八核 DSP主频最高 1GHz常用于雷达信号处理、软件无线电、高性能工业控制这些场景。它的启动流程和普通 MCU 有一些相似之处但细节差别很大。芯片上电复位后Core0 会自动执行片内固化的 ROM Boot LoaderRBL。RBL 会先采样 DEVSTAT 引脚上的电平也就是板卡上 Boot Mode 拨码开关配置的电平根据电平组合决定从哪个外设加载程序比如 SPI NOR Flash、EMIF NAND Flash、UART、PCIe、SRIO 等等。如果我们把拨码拨到 SPI Boot 模式RBL 就会从外部 SPI 器件通常是 SPI NOR Flash的 0 地址开始读取数据。这里有个关键点RBL 从 Flash 读回来的不是一份裸的 .bin 文件而是一个带特定结构的引导表Boot Table。引导表里记录了每一段代码要搬运到的目标内存地址、段长度、段数据以及最终的入口地址。RBL 会按照引导表把各段数据搬运到对应地址最后跳转到入口函数执行。所以如果直接把 CCS 编译生成的 .out 文件改名成 .bin 扔进 Flash是启动不起来的。必须先进行格式转换生成符合 RBL 引导表格式的镜像文件。这是整个烧写过程中最容易出错的一步。1.2 单核烧写和多核烧写的边界在哪里C6678 虽然有 8 个核但并不是每次都必须 8 核一起跑。单核烧写就是只让 Core0 运行一个程序Core1 到 Core7 保持复位状态或者由 Core0 在后续程序里自行唤醒。从镜像生成角度看单核烧写比多核烧写简单得多。多核烧写通常要把多个核的 .out 合并成一个多核镜像还要考虑每个核的入口地址以及 IPC 同步时用到的共享内存要不要提前初始化。单核烧写只需要关注一个程序、一个入口、一个运行地址。但“简单”是相对的。单核程序如果链接时把很多数据段定义到了 DDR3那么启动时就必须有一段代码先把 DDR3 初始化好否则程序跳转过去直接跑飞。而 DDR3 的初始化并不是 RBL 的职责RBL 只负责从 Flash 读引导表、搬数据、跳转外部存储器控制器DDR3的 PLL 和控制器配置都需要我们自己来做。这就引出了 C6678 烧写中一个非常重要的角色二次引导程序Secondary Boot LoaderSBL。对于比较大的单核程序通常启动链路是这样的上电复位后RBL 从 SPI Flash 的 0 地址读取第一段引导表第一段引导表将 SBL 加载到 L2 SRAM 或 MSMC SRAMRBL 跳转到 SBL 入口SBL 初始化 PLL、DDR3 等外设SBL 再从 SPI Flash 的某个偏移地址比如 0x10000 以后把真正的应用程序读取到 DDR3SBL 跳转到应用程序入口。如果应用程序很小能全部放进 L2 SRAMC6678 每个核有独立的 1MB L2理论上可以省掉 SBL直接让 RBL 把程序一次性加载上来。但实际工程中很少有这种情况所以单核烧写也基本绕不开 SBL。1.3 开发环境与硬件准备清单烧写之前建议先把环境固定下来避免不同版本工具链之间的差异干扰问题定位。以下是我这边验证过的一套常见组合硬件TI TMDSEVM6678L 评估板或者自研的 C6678 板卡仿真器XDS100V2 或 XDS200V3都能满足单核烧写的调试需求Flash 芯片板上 SPI NOR Flash常见型号有 N25Q128A、W25Q128JV 等容量通常 16MB 甚至更大CCS 版本CCS 5.5 或 CCS 6.x 都可以新版 CCS 也可以但 GEL 文件和旧工程需要适配MCSDKmcsdk_2_01_02_06 或相近版本里面有 Starterware、PDK 和 boot 工具串口工具用于观察打印信息推荐用串口助手或 SecureCRT提示自研板卡的 Boot Mode 拨码、JTAG 复位时序、SPI Flash 片选接线都和 EVM 不一样排查问题时优先查板卡的原理图不要完全照搬 EVM 的经验。2. 单核烧写全流程实操从编译到固化一次走通2.1 第一步让程序先在 RAM 里跑通在生成烧写镜像之前务必先在 CCS 里把程序在线跑一遍。这不是多此一举而是为了验证“程序本身没问题”。如果程序在仿真模式下都跑不通烧写后基本也是白搭。在线调试时通常的做法是在 CCS 里新建 Target Configuration选择 C6678 设备加载对应的 GEL 文件EVM 板卡一般用 evm6678l.gel。连接仿真器GEL 会自动初始化 PLL 和 DDR3。所以在线调试时程序放在 DDR3 里也能跑因为 GEL 已经帮你把 DDR3 初始化好了。加载应用程序的 .out 文件运行确认功能正常。这里要注意GEL 做的事烧写后没人帮你做。GEL 文件本质上是仿真器连接时执行的一段脚本它不属于你的程序脱机之后根本不会执行。所以在线跑通只是第一步你得清楚你的程序依赖了哪些初始化这些初始化最终要由 SBL 在脱机环境下完成。如果你用的工程是 SYS/BIOS 系统在线调试时还要注意启动流程里的main()之前SYS/BIOS 的启动模块会做不少初始化。这些初始化是包含在程序内部的烧写后同样会被执行不会因为脱机就丢失可以放心。2.2 第二步把 .out 变成可烧写的镜像这是整个烧写流程里最容易踩坑的环节。很多初学者直接把 .out 文件拖到烧写工具里或者把 .out 简单改个后缀名就写入 Flash结果上电毫无反应反复查硬件查了好久最后才发现是镜像格式不对。CCS 编译出来的 .out 是 ELF 格式包含了调试符号、段信息、重定位信息等非常“肥大”而且它不是按运行地址连续排列的。Flash 里需要的是一份去掉调试信息、按目标地址排列的二进制镜像同时还要带上 RBL 需要的引导表头。在 TI 工具链里这个转换工作一般用 hex6x 工具完成或者使用 MCSDK 里自带的 Boot Image 生成脚本。hex6x 是一个命令行工具位于 CCS 安装目录下它需要一个 .cmd 文件来指定输入、输出、引导选项等信息。举一个典型的 boot_image.cmd 片段具体参数以你使用的 SDK 版本和工程为准/* 输入文件 */ app.out /* 输出为带引导表的 hex/BIN 镜像 */ -boot /* 告诉工具这是 C6678 SPI 启动 */ -serial8 /* 指定引导表加载的基地址 */ -bootorg 0x003C /* 输出格式 */ -o app.bin然后执行hex6x boot_image.cmd生成 app.bin 后可以通过十六进制编辑器查看文件头部。正常的 SPI Boot 镜像开头会有引导表标识接着是一段段地址和长度信息。如果你看到的文件开头就是程序代码本身那说明引导表没生成成功烧进去大概率起不来。另外也可以用 MCSDK 里配套的脚本比如 create_boot_image 脚本来做输入还是 .out输出 .bin原理和 hex6x 一样。不管是哪种方式转换完成后都要检查文件大小。如果 .bin 的大小和 .out 里可执行代码段大小完全对不上十有八九是 cmd 文件里的段映射配置少了内容。注意生成镜像前先确认链接 cmd 文件里用的是 RAM 运行模型还是 ROM 运行模型。C6678 的 SPI Boot 是从外部存储加载到内存执行通常按 RAM 模型处理。如果你的程序里有初始化数据段和未初始化数据段要确保这些段在生成的镜像里都被正确包含否则程序可能能启动但全局变量初始值不对跑起来又是另一个坑。2.3 第三步通过仿真器把镜像写进 SPI NOR Flash镜像生成好后接下来就是把 app.bin 写入 SPI NOR Flash。实际操作中主要有三种方式各有适用场景。方式一通过仿真器加载一个烧写工程来写。TI 的 Starterware 里带了一个 SPI programmer 示例在 MCSDK 的 starterware/examples/evm6678l/spi 目录下。这个工程本身也是一个跑在 DSP 上的程序通过仿真器加载到板子上运行然后通过 CCS 的脚本或其他工具把 app.bin 传给这个烧写程序烧写程序负责驱动 SPI 控制器把数据写入 Flash。这种方式的好处是不依赖板子上已有的系统只要仿真器能连上就能烧比较适合空板或变砖后的恢复。方式二通过 U-Boot 烧写。如果板卡上已经移植好了 U-Boot用 U-Boot 烧写是最快的。先把 app.bin 通过 TFTP 或串口传输到 DDR3 的某个内存地址然后执行 SPI 相关的命令写入 Flash。这里给出一个 tftp 方式的典型命令流程setenv ipaddr 192.168.1.10 setenv serverip 192.168.1.100 tftp 0x80000000 app.bin sf probe 0 sf erase 0x0 0x800000 sf write 0x80000000 0x0 0x800000这里有几个细节要特别说。第一sf probe 0是探测并初始化 SPI Flash如果返回错误信息先检查 Flash 型号和驱动支持情况。第二sf erase的地址和长度必须按 Flash 的擦除粒度对齐一般 SPI NOR Flash 的扇区是 4KB块是 64KB擦除的最小单位是扇区所以擦除长度最好按块对齐。第三sf write后面的第三个参数是实际写入的字节数最好是实际 bin 文件的大小而不是上面示例里的 0x800000写多了会把后面地址空间的内容也覆盖掉。方式三用专门的离线烧录器烧写 SPI Flash。这种方式适合量产阶段先通过烧录器把 app.bin 写入裸片 SPI Flash再贴板。工具和操作手册各不一样这里不多展开。三种方式的简单对比烧写方式前置条件优点缺点仿真器Starterware 烧写工程仿真器、CCS不依赖板卡已有系统适合变砖恢复操作繁琐速度慢U-Boot 烧写板卡已有可用的 U-Boot速度快命令直观适合调试需要先移植好 U-Boot死机时介入困难离线烧录器烧录器、适配座适合量产效率高设备成本高需要拆片/预烧我个人调试阶段最喜欢用 U-Boot因为它能直接读 Flash 内容、比较数据排查问题方便。但如果是第一次给一块还没跑过 U-Boot 的板子烧程序那就只能用仿真器加烧写工程的方式先把 U-Boot 或者 SBL 烧进去再谈后面的事。烧写完成后建议再用sf read把 Flash 起始地址的数据读回 DDR3和原始 app.bin 做一次比较确认数据完全一致。这一步能帮你排除写入过程的偶发错误。2.4 修改 Boot Mode 拨码并验证上电启动程序写进 Flash 后拔掉仿真器把板卡的 Boot Mode 拨码调到 SPI Boot 模式重新上电。如果程序正常运行串口里就应该能看到自己应用程序的打印输出或者通过 LED、示波器测量 GPIO 判断程序是否起来了。C6678 的 Boot Mode 是由多个拨码开关组合确定的不同板卡的具体拨码值不太一样。比如 EVM6678L 上通常是通过几组 DIP 开关设置 BOOTMODE[12:0] 的电平SPI Boot 对应的组合在板卡手册里都有明确说明。我这里给不出一个放之四海而皆准的拨码表因为不同板卡布线不同。但排查时有个通用思路先确认芯片 DEVSTAT 寄存器读到的值手册里会写明每个组合对应的启动外设再和板卡拨码实际状态比对。这一步一定要有耐心。第一次上电失败非常常见不要急着怀疑芯片坏了先按后面的排查清单一项项确认。3. 常见问题与排查思路速查3.1 上电后完全没有任何反应“程序烧进去了拨码也改了上电之后串口什么打印都没有板子像死了一样。” 这是我在论坛和私信里遇到最多的问题。遇到这种情况先别慌按顺序检查。第一确认 Boot Mode 拨码真的拨到了 SPI Boot。这个听着可笑但真有不少人改的是板子上另一个区域的功能拨码或者拨码开关接触不良。用万用表量一下 DEVSTAT 相关引脚的电压对照手册确认。第二确认 Flash 里确实有内容。连上仿真器先不要加载程序手工执行 GEL 初始化然后查看 SPI Flash 控制器或直接用 U-Boot 的sf read把 Flash 内容读出来检查起始几个字节。如果发现 Flash 全 0xFF 或者写入的数据不在 0 地址那就说明写入过程就没做好重新烧。第三确认镜像文件格式正确。把读回来的 Flash 数据和电脑里的 app.bin 做二进制对比如果外观一致但仍然启动不了重点怀疑引导表格式或者入口地址的问题。此时可以加载 RBL 的跟踪工具或者用仿真器检查 Core0 的 PC 指针看 RBL 是否成功跳转。3.2 程序启动到一半就跑飞有反应但跑飞比完全没反应好排查但坑也不少。典型的症状是串口打印了一两行 SBL 的信息然后程序就没有后续了或者程序看起来启动了但某个功能表现异常和在线调试时的行为不一样。第一个常见原因是 DDR3 初始化问题。在线调试时 GEL 帮你做了 DDR3 初始化但脱机启动时如果 SBL 里没有做同样的初始化或者初始化顺序不对程序一旦运行到 DDR3 里的代码或数据段就会访问到未初始化或不稳定的内存行为完全随机。解决办法是在 SBL 里完整地初始化 PLL、DDR3 控制器确保稳定后再跳到应用。第二个常见原因是链接地址和实际加载地址不一致。比如你在链接 cmd 里把代码放在了 DDR3 的 0x80000000但 SBL 从 Flash 读取应用时却加载到了别的地址或者引导表生成时指定的段地址和链接地址不匹配。程序搬运到错误地址后的表现就是“能跳转但执行的是乱码”。第三个常见原因是 cache 一致性问题。C6678 的 L2 Cache 在使能后会和 DDR3 产生一致性问题脱机启动时 SBL 通常会先关 cache但跳到应用之前有没有把已经缓存的数据回写或使 cache 无效会影响程序运行。在线调试时仿真器的一些操作恰好规避了这个问题脱机后就会暴露。3.3 仿真器连接失败或烧写过程中断烧写过程中 CCS 突然连不上仿真器或者烧写到一半报错退出也是常见事故。这类问题通常集中在几个方面。第一JTAG 连接不稳定。XDS100V2 这类低端仿真器对线材和接口电平敏感尽量使用短线避免带电插拔。烧写过程中如果目标板复位或掉电仿真器会立刻掉线所以先确保电源稳定。第二SPI Flash 处于写保护状态。很多 SPI NOR Flash 出厂时状态寄存器的 BP 位是默认值有些板子的硬件电路还把 WP 引脚拉到了保护状态。擦除和写入都会失败。在 U-Boot 里可以先用sf protect off解锁或者通过读写状态寄存器的命令把 BP 位清零。排查时也可以用一条最简单的命令验证只擦除一个扇区看是否报错。第三CCS 的 Target Configuration 里仿真器型号或目标器件选错也会导致连接失败。重新建立 Target Configuration 时注意核对仿真器型号、接口模式14-pin 还是 20-pin和 C6678 的 device 型号。一个比较常见的现象是之前在线调试一切正常某天突然连不上最后发现是目标板 Flash 里烧了一个 SBL而 SBL 里改了 PLL 配置导致 JTAG 时钟频率过高或系统时钟异常仿真器被“带崩”。这种情况可以在连接目标板时把 JTAG 时钟降到 5MHz 或更低通常能连回来。3.4 常见问题速查表现象可能原因排查建议上电无任何输出Boot Mode 拨码不对核对 DEVSTAT 引脚电平和板卡手册上电无任何输出Flash 0 地址没有引导表用仿真器读 Flash 内容检查文件头上电无任何输出镜像生成时没加 -boot 选项重新生成镜像确认引导表结构存在启动后跑飞DDR3 未初始化SBL 中增加 DDR3 初始化比对 GEL 初始化流程启动后跑飞链接地址和加载地址不一致检查 cmd 文件和引导表段地址启动后行为异常Cache 一致性问题在跳转前 invalidate 相关 cache烧写时擦除失败Flash 写保护使用 sf protect off 或清状态寄存器烧写时擦除失败SPI 时钟频率过高降低 SPI 时钟尤其高温度环境下CCS 连不上仿真器JTAG 线缆/接口问题换短线降低 JTAG 频率CCS 连不上仿真器目标板 PLL 配置异常用低速 JTAG 连接尝试复位后立刻连接这张表不是万能药但覆盖了八成以上的启动问题。多数情况下问题不是出在最后那一步烧写动作而是出在镜像格式和初始化链路上。4. 个人实用心得几个绕开就会很省事的细节4.1 烧写之前先把启动链路画一遍我踩过的最大一个坑是自己以为“单核烧写就是把程序扔进 Flash 就行”。那时候我还不太理解 SBL 的作用结果在线调试明明好好的烧进去之后 DDR3 里的数据全是乱的程序跑飞得一塌糊涂。后来静下心把启动链路画了一遍才发现自己的想法太天真。这里给新手一个非常实用的建议在你点烧写按钮之前拿出一张纸把以下问题写清楚应用程序的入口地址是多少放在哪个内存区域程序里最关键的初始化PLL、DDR3、UART目前是在哪里做的GEL 里有没有SBL 里有没有你的 Flash 里要放几个镜像如果放了 SBL 又会引导应用那么 SBL 放在哪个偏移地址应用放在哪个偏移地址应用程序编译时的链接地址和目标加载地址是否一致这些问题不需要全部背下来但一定要有个清晰的答案。只要能清楚回答这几点烧写成功率会提升一大截。另外一个经验是不要一上来就搞一个功能特别复杂的程序来烧写。第一次跑烧写流程时建议先用一个最简单的程序GPIO 翻转或者 LED 闪烁就行。先打通链路再去做复杂应用排查起来会容易得多。4.2 推荐保留一个“硬恢复”方案单片机玩久了的人都知道Flash 里代码万一写坏板子很可能变砖连仿真器都难连。C6678 因为是 DSP恢复手段比普通 MCU 多一些但也要提前准备好方案。我的做法是在板卡上保留一组 Jumper 或拨码可以把 Boot Mode 临时改成别的模式比如 No Boot / 仿真器等待模式方便在 Flash 异常时让 RBL 不执行 Flash 引导从而保证仿真器能连上。准备一个最简单的 Flash 擦除程序SPI programmer 工程在 Flash 被写坏时通过仿真器加载该程序把整个 Flash 擦干净再重新烧写。烧写完成后把当前能正常工作的 app.bin 备份一份放到网盘或 Git 仓库里标记好日期和板卡版本。这些准备看起来很基础但真遇到变砖时能救命的往往就是这些“笨办法”。4.3 单核烧写之后还可以往哪些方向扩展把单核烧写跑通并不代表 C6678 的学习就结束了相反这只是一个新的开始。后续值得关注的方向有几个一是多核启动。单核跑通后你会自然地想用上其它七个核。C6678 多核启动通常由一个总入口一般是 Core0负责加载和唤醒其他核每个核跑独立程序段核间通过 IPC、共享内存、硬件信号量来同步。多核镜像的生成、链接脚本的划分、内存的保护策略都比单核复杂很多但基础还是依赖熟悉启动过程。二是 SBL 的深度定制。很多工业项目需要在上电后快速初始化 DDR3、网口、PCIe 等外设甚至需要做镜像校验、加密解密这些逻辑都可以放进 SBL。理解了 SBL 的加载流程之后你会发现它其实就是一个独立的裸机程序完全可以按项目需求改造成自己的 bootloader。三是镜像可靠性和固件升级。产品化之后还要考虑 Flash 分区、镜像回滚、远程升级等功能。比如把应用分成两区一个运行区一个备份区启动时校验失败自动回滚。C6678 的 SPI Flash 空间通常足够大做双镜像方案并不牵强。我现在自己再写烧写相关的程序时习惯上会先把 SBL 里每一行初始化都过一遍确认它和在线调试时 GEL 执行的操作一致然后再点烧写按钮。这套方法帮我在后续几个项目里省掉了大量瞎猜的时间。希望这篇笔记也能让你的 C6678 单核烧写之路顺利一些。