1. 从一次烧录事故说起:hex 文件详解 stm32 为什么绕不开
去年帮一个做工业控制的朋友排查产线问题,现象很典型:STM32F407 的板子,用 STM32CubeProgrammer 烧录.hex显示成功,但设备上电后跑的还是上一版固件。换 OpenOCD 烧同一份 hex,结果又正常。当时第一反应是工具链问题,后来把 hex 文件逐行拆开看,才发现是地址扩展记录被某个中间环节截断了——文件本身没问题,是烧录脚本里--start-address参数写错,把 0x08000000 之后的扩展线性地址记录跳过了。
这件事让我意识到,很多工程师对 hex 的理解停留在“编译产物、拖进工具就能烧”,但一旦涉及多工具协作、产线批量校验、IAP 升级,hex 的结构细节就会变成排障的关键线索。hex 文件详解 stm32 这个话题,本质上不是讲格式规范,而是讲“当烧录结果和预期不一致时,你手里有没有可验证的依据”。
Intel HEX 是一种用 ASCII 文本描述二进制数据的容器格式,最早由 Intel 为 8080 处理器定义,后来成为嵌入式领域的通用交换格式。STM32 的编译工具链(Keil MDK、STM32CubeIDE、IAR)默认都能输出 hex,它的核心价值在于:每一行都是自包含的记录,带地址、类型、数据和校验和,任何一行损坏都能被独立检测出来。这比裸 bin 文件强太多——bin 没有地址信息,烧到哪全靠工具参数,出错时你连“哪一段写错了”都定位不了。
适合读这篇的人:正在用 STM32CubeProgrammer 或 OpenOCD 做固件交付的嵌入式工程师;需要写自动化烧录校验脚本的测试人员;以及被“烧录成功但运行异常”折磨过、想搞清 hex 底层逻辑的开发者。下面我会从记录格式讲起,给出可复制的解析脚本、两条烧录路径的完整命令,以及读回比对的具体步骤。中间会穿插 TaoToken 统一 Key 管理多工具凭证的实践——当你同时用 CubeProgrammer、OpenOCD、以及自己写的 Python 校验脚本时,凭证散落各处是个真实痛点。
2. Intel HEX 记录格式拆解与地址扩展机制
2.1 一行 hex 记录的六个字段
先看一条真实记录,来自 STM32CubeIDE 编译出的工程:
:10000000B80B00207D250008850300088703000841冒号是行首标记,后面全是十六进制 ASCII 字符。拆开看:
| 字段 | 字节数 | 示例值 | 含义 |
|---|---|---|---|
| 数据长度 | 1 | 0x10 | 本行数据区有 16 字节 |
| 起始地址 | 2 | 0x0000 | 偏移地址(大端序) |
| 记录类型 | 1 | 0x00 | 数据记录 |
| 数据 | n | B80B...0008 | 实际固件内容 |
| 校验和 | 1 | 0x41 | 前面所有字节累加取反加一 |
校验和算法:把长度、地址高字节、地址低字节、类型、所有数据字节相加,取低 8 位,然后用 0x100 减去这个值。以上面为例,累加和是 0xBF,0x100 - 0xBF = 0x41,对上了。如果某一行校验和不对,说明文件在传输或存储过程中损坏了,烧录工具通常会直接报错。
2.2 记录类型 00 到 05 的分工
类型 0x00 是数据记录,占文件 95% 以上。类型 0x01 是文件结束记录,固定为:00000001FF,每个 hex 最后一行都是它。类型 0x02 和 0x04 都用来提供基地址,区别在于 0x02 是扩展段地址(左移 4 位),0x04 是扩展线性地址(左移 16 位)。STM32 的 Flash 起始地址是 0x08000000,所以你会看到:020000040800F2这样的记录——数据 0x0800 左移 16 位得到 0x08000000,后续数据记录的偏移地址都加在这个基地址上。
类型 0x03 和 0x05 是启动地址记录,0x03 用于 8086 实模式,0x05 用于 32 位线性地址。STM32 的 hex 里常见:0400000508000121CD,表示程序入口地址是 0x08000121,这个值通常对应 Reset_Handler 或 main 函数入口。IAP 场景下,这个记录能帮你确认向量表偏移是否正确。
2.3 地址计算的实际推演
拿两条记录串起来看:
:020000040800F2 :10000000B80B00207D250008850300088703000841第一条:类型 0x04,数据 0x0800,基地址 = 0x0800 << 16 = 0x08000000。 第二条:类型 0x00,偏移 0x0000,实际地址 = 0x08000000 + 0x0000 = 0x08000000,16 字节数据写入此处。
如果后面出现:020000040801F1,基地址变成 0x08010000,后续数据就落在 0x08010000 开始的区域。STM32F4 的 Flash 扇区划分不均匀,前几个扇区 16KB、后面 64KB、再后面 128KB,hex 里的地址跳变往往对应扇区边界。理解这一点,你在做扇区擦除或 IAP 分区时就不会算错偏移。
2.4 为什么 bin 不够用
bin 文件是纯二进制流,没有地址、没有校验、没有结束标记。烧录时必须由工具指定起始地址,一旦指定错误,固件就写到错误位置。hex 自带地址和校验,工具可以逐行验证,产线批量烧录时能自动发现文件损坏。代价是 hex 体积约为 bin 的 2.5 倍(ASCII 编码膨胀),但对现代 Flash 容量来说可以忽略。
3. 可复制配置:解析脚本与 TaoToken 统一 Key 接入
3.1 用 Python 解析 hex 并输出地址映射
下面这个脚本我实测过,能打印每条数据记录的起始地址、长度和校验结果,方便你快速定位异常行:
import sys def parse_hex(path): base = 0 records = [] with open(path, 'r') as f: for lineno, line in enumerate(f, 1): line = line.strip() if not line or not line.startswith(':'): continue raw = bytes.fromhex(line[1:]) length = raw[0] offset = (raw[1] << 8) | raw[2] rtype = raw[3] data = raw[4:4+length] checksum = raw[4+length] calc = (0x100 - (sum(raw[:-1]) & 0xFF)) & 0xFF ok = (calc == checksum) if rtype == 0x04: base = ((data[0] << 8) | data[1]) << 16 elif rtype == 0x00: addr = base + offset records.append((addr, len(data), ok)) elif rtype == 0x01: break if not ok: print(f"line {lineno}: checksum FAIL") for addr, ln, ok in records[:10]: print(f"0x{addr:08X} {ln:3d} bytes {'OK' if ok else 'BAD'}") print(f"total data records: {len(records)}") if __name__ == '__main__': parse_hex(sys.argv[1])运行python parse_hex.py firmware.hex,输出前 10 条记录的地址和校验状态。如果某条显示 BAD,说明文件损坏,别急着烧。
3.2 TaoToken 统一 Key 管理多工具凭证
当你同时用 CubeProgrammer CLI、OpenOCD、以及自研校验脚本时,凭证管理会变乱。TaoToken 的做法是提供一个统一的 API 通道,把模型调用、编码辅助、脚本生成等能力收敛到一个 Key 上。配置方式是在项目根目录建一个taotoken.json:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-your-unified-key", "model_id": "claude-sonnet-4-20250514", "tools": { "hex_parser": { "enabled": true }, "openocd_helper": { "enabled": true } } }这个文件里的base_url和api_key就是三件套的核心。Model ID 按你实际使用的模型填。如果你用 Claude Code 做固件脚本润色,可以在 settings 里指向同一个 base_url:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-your-unified-key", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }这样 CubeProgrammer 的脚本生成、OpenOCD 的配置检查、hex 解析脚本的调试,都走同一个 Key,不用在多个工具间来回切换凭证。API Keys 管理页面在https://taotoken.net/api-keys,可以按项目建不同 Key 做隔离。
3.3 STM32CubeProgrammer CLI 烧录命令
假设 hex 文件是firmware.hex,ST-Link 连接正常:
STM32_Programmer_CLI -c port=SWD -w firmware.hex -v -rst-c port=SWD指定 SWD 接口,-w写入,-v烧录后校验,-rst复位运行。如果只想校验不烧录:
STM32_Programmer_CLI -c port=SWD -v firmware.hex输出里会显示每个扇区的写入和校验结果。注意-v是 verify,不是 verbose,别搞混。
3.4 OpenOCD 烧录命令
OpenOCD 需要配置文件,以 STM32F4 为例:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \ -c "program firmware.hex verify reset exit"program后面跟文件,verify触发读回比对,reset复位,exit退出。如果 hex 里包含多个地址段,OpenOCD 会自动处理扩展线性地址记录。踩过的坑是:某些旧版 OpenOCD 对 0x04 记录支持不完整,遇到:020000040800F2会报invalid record type,升级到 0.12 以上即可。
4. 验证请求与成功结果:读回比对实操
4.1 用 CubeProgrammer 读回 Flash 并比对
烧录完成后,读回指定地址范围:
STM32_Programmer_CLI -c port=SWD -u 0x08000000 0x08010000 -r readback.bin-u指定起始地址和结束地址,-r输出到文件。然后用 Python 把原始 hex 转成 bin,再逐字节比对:
def hex_to_bin(hex_path, bin_path, start=0x08000000, size=0x10000): base = 0 buf = bytearray(b'\xFF' * size) with open(hex_path) as f: for line in f: line = line.strip() if not line.startswith(':'): continue raw = bytes.fromhex(line[1:]) length, offset, rtype = raw[0], (raw[1]<<8)|raw[2], raw[3] data = raw[4:4+length] if rtype == 0x04: base = ((data[0]<<8)|data[1]) << 16 elif rtype == 0x00: addr = base + offset if start <= addr < start + size: idx = addr - start buf[idx:idx+length] = data with open(bin_path, 'wb') as f: f.write(buf) hex_to_bin('firmware.hex', 'expected.bin')然后cmp expected.bin readback.bin,无输出即完全一致。如果有差异,cmp -l会列出具体偏移和字节值。
4.2 OpenOCD 的 verify 输出解读
OpenOCD 执行program ... verify后,成功时输出:
** Verified OK **失败时会显示Verify Failed并给出地址。常见原因是 Flash 未擦除干净、写保护未解除、或 hex 文件本身校验和错误。如果 verify 失败但烧录显示成功,优先检查 hex 文件的校验和——用 3.1 的脚本跑一遍。
4.3 用 TaoToken 模型对话辅助排障
当比对出现差异、但你不确定是 hex 解析问题还是 Flash 读取问题,可以把差异片段贴到模型对话里,让它帮你分析地址对齐和字节序。模型对话入口在https://taotoken.net/chat,走的是同一个统一 Key。我试过把cmp -l的输出和 hex 对应行一起贴进去,模型能快速指出是扩展地址记录被跳过导致的偏移错位。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
5.1 401 Unauthorized
现象:调用 TaoToken API 或 CubeProgrammer 脚本时报 401。原因通常是 Key 没带对或过期。检查taotoken.json里的api_key是否以sk-开头,以及请求头是否带了Authorization: Bearer sk-xxx。如果用的是 Claude Code,检查 settings 里ANTHROPIC_API_KEY是否和ANTHROPIC_BASE_URL匹配。重新生成 Key 的入口在https://taotoken.net/api-keys。
5.2 local proxy failed
现象:OpenOCD 或 CubeProgrammer 连接 ST-Link 时报local proxy failed或open failed。这通常和 API 无关,是调试器驱动问题。检查 ST-Link 驱动是否安装、USB 线是否接触良好、目标板是否供电。如果同时开了多个调试会话(比如 CubeIDE 和 OpenOCD 抢同一个 ST-Link),关掉多余会话即可。
5.3 reading choices 报错
现象:用模型对话生成 hex 解析脚本时,返回error reading choices或类似字段缺失。这多半是请求体里model字段和实际可用模型不匹配。确认model_id填的是当前账号可用的模型标识,别照抄文档里的示例值。如果用的是 Coding Plan 通道,检查 plan 是否覆盖该模型。
5.4 OAuth 回调失败
现象:Claude Code 或某些 CLI 工具走 OAuth 流程时卡在回调。如果你用的是 API Key 模式(推荐),根本不需要 OAuth,直接在 settings 里配ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY即可。OAuth 适合交互式登录场景,但固件交付这种自动化流程,Key 模式更稳定。
5.5 hex 校验和错误但工具不报
有些旧版烧录工具对校验和不敏感,直接跳过。用 3.1 的脚本先跑一遍,确认所有行 checksum OK。如果某行 BAD,重新编译生成 hex,别试图手动改——手动改校验和容易引入新错误。
6. 把校验动作固化进交付流程
固件交付的核心不是“烧进去”,而是“证明烧进去的和编译出来的一致”。hex 文件详解 stm32 的落脚点就在这里:理解记录格式,你才能写出可靠的解析脚本;理解地址扩展,你才能在 IAP 和多分区场景下算对偏移;理解校验和,你才能在文件损坏时第一时间发现。
把 3.1 的解析脚本、4.1 的读回比对、以及 TaoToken 的统一 Key 配置串起来,就形成了一条可重复的校验链路:编译产出 hex → 解析脚本验证文件完整性 → CubeProgrammer 或 OpenOCD 烧录 → 读回 bin → 逐字节比对。任何一环失败,都有明确的报错和定位手段。
长期做编码和 Agent 辅助的,可以走 Coding Plan 通道,把脚本生成、配置检查、排障对话都收敛到同一个凭证体系下。接入文档在https://taotoken.net/doc,里面有各工具的完整配置示例。最后留一个实用技巧:产线批量烧录时,把读回比对做成自动化脚本,每片板子烧完自动跑一次cmp,比人工抽检可靠得多。