☰
CCS下TMS320F28335生成hex与bin文件的完整教程及避坑指南
2026/9/25 9:01:11 网站建设 项目流程

前阵子帮同事处理量产固件,发现很多人卡在同一个地方:CCS里编译只出.out,对着仿真器烧没问题,一到产线要用离线编程器、要用串口Bootloader升级,立刻抓瞎。所以把我在CCS 12.2下、TMS320F28335工程里生成.bin和.hex文件的完整过程整理出来,重点是那些不实际踩一遍根本不知道的坑。

这篇文章适合正在做28335开发的工程师,尤其是要接触量产烧录、Bootloader远程升级、或者需要把固件交给第三方工具处理的人。如果你是刚开始学C2000,还没搞清楚.out和烧录文件的关系,这篇也能帮你把工具链的完整链路理清楚。

1. 先搞清楚:.out、.hex、.bin 到底差在哪

1.1 三种文件的本质:从“调试产物”到“烧录产物”

我见过不少新手,把.out文件当万能固件,直接发给产线,然后产线反馈“打不开”。这不是产线工具的问题,而是文件格式压根不对。

.out是CCS编译器直接生成的链接产物。它内部是COFF格式(新版本也可能用ELF),除了代码数据,还带着完整的符号表、重定位信息、调试信息。仿真器加载.out的时候,就是靠这些信息把变量名对应到具体地址,这就是为什么CCS调试时能看变量、能打断点。但离线烧录器、产线工具不需要这些,它们只关心“哪个地址写什么数据”。

.hex是Intel HEX格式,本质上是一个纯文本文件。把任意一个hex文件用记事本打开,你会看到大量冒号开头的行,例如:20000000开头的一串ASCII字符。每一行都包含长度、地址、记录类型、数据、校验和,所以它自带地址映射和校验信息,烧录器照着地址逐个写Flash就行。

.bin是最纯粹的二进制镜像,一段连续的数据,从某个起始地址开始排列,不包含地址、不包含校验、不包含任何元信息。烧录.bin前,你必须明确知道它的起始地址。这也是很多人在用.bin时栽跟头的地方——文件本身不告诉你它该放在哪。

打个比方:.out是带着施工图纸的全套工程资料,.hex是一份带门牌号的货物清单,.bin就是货物本身。你给快递员货物清单他能准确投递,你直接把货物堆门口,他根本不知道往哪送。

1.2 哪些场景必须用 .hex 或 .bin

如果你只用XDS100/XDS110仿真器,在CCS里调试和烧录,那么一直用.out没有任何问题。但只要是下面几个场景,就必须把固件转成.hex或.bin:

我实际接触最多的,就是量产烧录。产线上的离线烧录器、自动烧录工装,绝大多数只支持Intel HEX或纯bin,没人会去解析COFF。还有一个高频场景是Bootloader升级,28335的串口引导模式(SCI Boot)需要的是特定的镜像,通过Bootloader搬运固件时,普通.out完全无法使用。再就是跨工具协作,到了客户现场,对方经常只收“固件文件”,你扔一个.out过去,他连打开都费劲,更别说二次处理。

1.3 转换工具为什么是 hex2000 而不是别的

CCS的C2000编译器自带一个工具叫hex2000,位于编译器安装目录的bin文件夹下。这个工具专门负责把COFF/ELF格式的输出文件转换成各种烧录格式:Intel HEX、TI-TXT、二进制、引导表等。为什么主力是它?因为它是TI官方工具,与编译器版本完全匹配,能正确处理C2000系列16位存储器的数据宽度和字节序,别的第三方工具还真不一定靠谱。

2. 在 CCS 12.2 里自动生成 .hex 文件

2.1 最容易上手的方案:Post-build Steps 一条命令搞定

如果你不想每次编译完都手动敲命令行,最稳妥的做法是在CCS工程属性里配置构建后步骤,让编译完成后自动执行转换命令。

具体操作路径:右键工程 -> Properties -> Build -> Steps,在 Post-build steps 的文本框中填入转换命令。

这里先给一个最稳、全路径版本的命令,不会受到变量展开问题影响:

"C:\ti\ccs12xx\ccs\tools\compiler\ti-cgt-c2000_22.6.x\bin\hex2000.exe" --intel --memwidth 16 --romwidth 16 -o "D:\work\28335_app\Debug\28335_app.hex" "D:\work\28335_app\Debug\28335_app.out"

注意,我上面写的是一个示意路径,实际目录要根据你的CCS安装位置和工程路径来替换。通常情况下,CCS 12.2的安装路径会在C:\ti\ccs12xx下,编译器文件夹以ti-cgt-c2000_开头,版本不同文件夹名也不同,建议你在文件管理器里直接搜一下hex2000.exe,把实际路径填进来。

如果你更在意可移植性,比如代码要交给同事、换电脑也要能一键构建,可以用CCS的构建变量来替代:

"${CG_TOOL_HEX}" --intel --memwidth 16 --romwidth 16 -o "${PROJECT_LOC}/Debug/${ProjName}.hex" "${PROJECT_LOC}/Debug/${ProjName}.out"

使用变量版有两个前提:一是确认你当前CCS版本支持${CG_TOOL_HEX}这个变量,如果命令执行时提示找不到程序,就老老实实写绝对路径;二是确认工程的构建输出目录确实是Debug,如果你用的是Release配置,要把路径里配置名一起换掉。

配置完成后,正常点击Build(锤子图标)。编译结束后,CCS会自动执行Post-build步骤,控制台会打印hex2000的执行信息。没有报错的话,在Debug目录下就能看到.hex文件。

2.2 命令行手工转换与完整参数对照

有些时候我不想配置工程属性,或者想批量转换一批.out文件,就会直接用命令行操作。手动转换的思路和自动配置完全一致,只是省去在CCS界面里点来点去。

进入编译器bin目录后,执行:

hex2000.exe --intel --memwidth 16 --romwidth 16 -o output.hex input.out

这几个参数我逐个说一下,因为它们决定了生成结果是否正确:

--intel指定输出Intel HEX格式。这个格式通用性最好,UniFlash、C2Prog、各种离线烧录器都认它。--memwidth 16表示目标系统的存储器宽度是16位。TMS320F28335是32位CPU,但外部Flash是16位宽,这里必须指定为16,让工具按16位数据宽度来处理地址和数据。--romwidth 16表示ROM的物理宽度是16位,C2000的片上Flash基本就是16位,跟memwidth保持一致就对了。-o后面跟输出文件名,一般把输入文件的.out后缀改成.hex即可。

你可能看到网上有些教程会加上--boot和--bootorg参数,这个我在后面生成bin的章节单独展开。这里先明确一个概念:如果你的目标是把程序烧进Flash并正常启动,用--intel生成普通hex就够了,别去加boot参数。加了反而会让hex变成一个Boot Table格式,直接烧进Flash以后程序大概率跑不起来。

2.3 如何确认生成的 HEX 是可用的

生成hex只是第一步,关键是确认它真的能被正确烧录。我每次转完文件都会做三个快速检查。

第一个检查是看文件大小是否合理。用文件管理器看一眼hex大小,如果只有几十字节,那肯定不对,多半是链接时没把段放进去,或者CMD文件定位错误。第二个检查是打开hex文件,看首行的地址跟CMD里Flash起始地址是否一致。比如你的CMD把FLASH段定义在0x003F8000,hex里第一条数据记录的处理地址应该与此对应。第三个检查是针对会用脚本的上位机开发人员:Intel HEX每行开头是冒号,第二个字节是数据长度,紧接着是16位地址,再往后是记录类型,00代表数据记录,01代表文件结束,04代表扩展线性地址记录。如果你想写脚本把hex转成十进制数组,记得每两个十六进制字符转成一个字节,不要整行直接转。

我在实际项目里做过一个简单的自动校验工具,用Python逐行读取hex文件,提取所有00类型记录,按地址拼接成一个字节映射表,再比对我预期的固件版本号。对量产来说,这种自动化检查比人工看靠谱得多。

3. 生成 .bin 文件的三种路径与各自的大坑

3.1 最直接但最容易翻车:hex2000 的 --binary

生成.bin最直接的方式,就是给hex2000加--binary参数:

hex2000.exe --binary --fill=0xFF --memwidth 16 --romwidth 16 -o app.bin app.out

这个命令背后的逻辑是:把.out中所有具有加载地址(load address)的段提取出来,按地址从低到高连续排列,形成一个二进制流。如果地址中间有空洞,--fill=0xFF会把这些空洞填上0xFF。28335的Flash空白区域本来就是0xFF,所以这样填充被认为是安全的。

但这里藏着一个巨坑:bin文件的长度取决于.out中所有可加载段的最小地址和最大地址之差。如果你的CMD文件把程序入口放在0x003F8000,同时又有中断向量段放在0x003F8000附近,那么bin的起始地址就是整个可加载区域的最低地址,不是0x003F8000。如果工程里某个数据段被放到了0x00338000(Flash区域的较低地址),那么生成的bin就会从0x00338000开始,中间几千字节全部是0xFF填充,文件体积瞬间膨胀。

我在一个工程里就碰到过这种情况,一个实际只有20KB代码量的程序,转出来的bin有近200KB。产线同事还以为是程序写错了,检查下来完全没问题,就是地址空洞闹的。后续的规避方案我放在3.4小节详细说。

3.2 Bootloader远程升级时:用 --boot 生成引导表

如果你在做Bootloader相关的工作,场景就完全不一样了。28335复位后,内部Boot ROM会根据GPIO引脚状态决定进入哪种引导模式。如果进入SCI Boot模式,Boot ROM会通过串口接收一段特殊格式的数据——Boot Table。这个Boot Table包含引导关键字、数据块起始地址、数据块长度、数据内容、校验和。它和普通hex完全是两回事。

此时就需要用hex2000的Boot Table生成模式。常见命令形式:

hex2000.exe --boot --serial8 --binary --memwidth 16 --romwidth 16 -o app_boot.bin app.out

--boot告诉工具按Bootloader可识别的格式输出,--serial8表示面向SCI引导,按8位串行方式打包数据。生成的app_boot.bin可以直接通过串口发送给Boot ROM。

网上的老教程里经常出现--bootorg 0x003F7FF6这种写法,我提醒一句:这个地址不是硬性规定,它表示Boot Table在内存中的存放位置,具体值要和你实际使用的Bootloader约定一致。不同CMD文件、不同Flash起始地址,需要的--bootorg都不一样。直接抄网上的命令很可能翻车。更稳妥的做法是:先看你的Bootloader源代码,确认它期望从哪个地址读取Boot Table,再据此设置--bootorg。

3.3 用脚本做裁剪和校验:一个简单的Python方案

当hex2000的--binary因为地址空洞导致bin文件过大,或者你需要对hex内容做定制化处理时,可以写一段小脚本来完成。这里分享一个我常用的Intel HEX解析思路,足够覆盖大多数场景:

import sys def parse_hex(path): data = {} base = 0 for line in open(path, 'r'): line = line.strip() if not line.startswith(':'): continue length = int(line[1:3], 16) addr = int(line[3:7], 16) rectype = int(line[7:9], 16) payload = bytes.fromhex(line[9:9 + length * 2]) if rectype == 0x04: # 扩展线性地址 base = int.from_bytes(payload, 'big') << 16 elif rectype == 0x00: # 数据记录 for i, b in enumerate(payload): data[base + addr + i] = b elif rectype == 0x01: # 文件结束 break return data def to_bin(data, start_addr, end_addr, fill=0xff): result = bytearray() for addr in range(start_addr, end_addr + 1): result.append(data.get(addr, fill)) return bytes(result) if __name__ == '__main__': data_map = parse_hex('app.hex') bin_data = to_bin(data_map, 0x003F8000, 0x003FBFFF) open('app_cut.bin', 'wb').write(bin_data)

这段代码的逻辑很清晰:先把hex文件解析成一个地址到字节值的映射,然后按你需要的地址区间提取数据,空洞部分填入fill指定的默认值。通过这种方式,产线使用的小体积bin文件就出来了,而且起始地址完全可控。如果你要做固件合并、版本标识写入、CRC校验追加,也是在这种映射基础上操作最方便。

3.4 关于文件大小的隐藏问题:地址空洞与CMD布局

我在3.1节提到的bin文件膨胀问题,本质上不是hex2000的锅,而是CMD文件布局造成的。所以要让生成的bin体积可控,最根本的办法是调整CMD文件的段定位。

一种常见的做法是把应用程序的Flash段统一放到较高地址区域,比如从0x003F8000开始,和Bootloader程序占用的低地址区域隔离开。这样应用程序单独生成bin时,起始地址就是0x003F8000,结束地址就是代码区最大地址,中间不会有低地址段和它混在一起。前提是你需要清楚哪些段是可加载段(loadable),哪些只是符号定义。如果CMD文件里有多个PAGE0的Flash区域被标记成可加载,那么hex2000都会把它们纳入输出,哪怕这些段之间隔了几十KB的空洞。

另外提醒一个细节:不要指望单纯用--fill选项把空洞填成0,除非你有明确理由。对Flash而言,空白区域保持0xFF是天然状态,填0会改变未使用区间的电平,某些场景下可能引发误判。所以我在默认命令里写的是--fill=0xFF,这是跟Flash特性对齐的选择。

4. 我实际踩过的坑与排查实录

4.1 常见报错速查表

下面这张表是我用CCS 12.2处理28335工程时遇到过的典型报错,以及排查思路:

报错信息实际原因处理办法
createprocess failed或 系统找不到指定的文件hex2000.exe路径错误,或路径含空格且没有加引号核对hex2000实际路径,用双引号包住完整路径
Error: Can't find input file xxx.outPost-build步骤的工作目录和实际构建输出目录不一致使用${PROJECT_LOC}/Debug/xxx.out这样的绝对化路径
生成的hex文件是空的CMD文件里没有正确分配可加载段,或链接阶段没有代码段输出检查Build控制台是否有warning,确认FLASH段是否分配
生成的bin巨大(几MB).out里包含低地址段,导致地址空洞被填充调整CMD布局,或用脚本裁剪地址区间
用C2Prog烧写hex后程序不跑使用了带--boot的Boot Table格式替换了普通烧录hex烧Flash用--intel,Boot Table只用于Bootloader搬运场景
hex2000不是内部或外部命令当前终端PATH里没有编译器bin目录,或工具名写错在命令中写全路径,不要依赖PATH

我提一句,CCS 12.2的Post-build步骤报错时,控制台输出的信息可能比较模糊,经常是“Build Failed”加上一行状态码。这时候最好的方法是把Post-build里的命令复制到CMD终端里手动执行,逐字核对。命令行能跑通,多半是变量没有正确展开;命令行也报错,就是命令本身的问题。

4.2 烧录阶段的隐性坑:地址、密码区、字节序

hex和bin生成出来了,烧录阶段还有几个坑不会立刻暴露,但出了问题会让你排查到崩溃。

第一个是密码区。TMS320F28335的Flash有一个CSM密码区域,位于0x003F7FF8到0x003F7FFF。如果这个区域写入全0,芯片的Flash就会被锁死,之后连CCS都无法通过JTAG访问。所以在量产前,一定要检查生成的hex或bin文件在这个地址区间内的数据,确认不是全0。如果工程里没有专门处理密码区逻辑,最安全的做法是让这段保持0xFF,也就是不写入任何密码。

第二个是字节序。虽然28335是小端处理器,但Flash的16位数据在文件中的字节排列方式,不同烧录器可能有不同预期。生成hex时使用--memwidth 16 --romwidth 16能保证绝大多数情况下的正确排列。如果你遇到烧录成功但程序运行结果错乱,先怀疑字节序,查看hex文件中一个16位常量的字节顺序,和反汇编结果做对比。

第三个是烧录工具的地址解析方式。UniFlash这类TI官方工具对hex文件处理得很规范,但有些第三方产线工具在解析涉及Flash起始地址偏移时可能不够严谨,导致写错位置。遇到这种情况,我的经验是先烧一个只有LED翻转的测试程序,确认烧录工具出的地址完全正确,再烧正式固件。

4.3 工具选型实测:官方工具、UniFlash、脚本、第三方

围绕bin和hex的生成,市面上可选的工具不少,但我实测下来各有取舍。

首选的仍然是TI官方工具链。hex2000已经在大量生产项目中验证过,和编译器同源,对COFF/ELF支持最完善,参数虽然多但逻辑清晰。缺点是纯命令行,新手需要一点学习成本。

UniFlash的CLI模式也很实用,它能直接导入.out,再导出bin或hex。对不喜欢命令行的人来说,UniFlash图形界面操作更直观。它的缺点是自动化集成不如Post-build步骤方便,适合单次操作或小批量生产。

基于Python的自研脚本则是我的兜底方案,特别是需要裁剪地址空洞、拼接多段固件、校验CRC时,脚本的控制粒度是前两者做不到的。缺点是需要维护,测试覆盖不足时容易引入新问题。

至于各种在线转换网站,我不是特别推荐。自己能本地生成的情况下,没必要把固件传到第三方平台,而且在线工具对TI的COFF格式支持参差不齐,很容易出现段信息丢失、地址漂移这类问题,量产环节承担不起这种风险。

在我当前的工程实践里,标准流程是:CCS的Post-build步骤同时输出普通Intel Hex和裁剪后的bin,再用一个本地Python脚本做CRC校验和版本号注入,最后把成品hex/bin归档到量产目录。整个链路全部自动化,人为干预越少,产线出错的概率就越低。这个流程跑了一年多,基本没再为固件格式问题返过工。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询