ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

单片机固件格式解析:bin与hex的本质区别与工程应用

单片机固件格式解析:bin与hex的本质区别与工程应用 1. 为什么连 bin 和 hex 都分不清就别碰单片机开发了你手里的 STC89C52 开发板刚焊好Keil 编译完弹出一个project.hex旁边还悄悄生成了个project.bin你打开烧录软件发现它只认.hex但某篇论坛帖子又说“升级固件必须用.bin”你试着把 hex 文件拖进串口助手看到满屏乱码十六进制字符而 bin 文件用十六进制编辑器打开却是一段段干净的 FF 00 01 02……这时候你心里是不是已经冒出三个问号这俩到底谁才是“真身”烧录时到底该选哪个为什么 Keil 默认给 hex而 Bootloader 却只吃 bin——别急这不是你笨是绝大多数初学者根本没被带入过真正的“机器视角”。我带过 7 届蓝桥杯单片机省赛队伍每年都有至少三分之一的学生在调试串口 IAP 升级时卡死在“为什么我发的 hex 文件校验失败”最后发现他压根没意识到hex 文件里混着地址、校验和、记录类型这些元信息而芯片真正执行的只有那一段干干净净的、按地址顺序排列的原始字节流——也就是 bin。bin 是裸奔的肌肉hex 是穿西装打领带还带身份证和行程单的商务人士。你让芯片去读 hex就像让电饭锅去读《高铁乘车指南》它不报错才怪。这个认知断层不是语法问题而是底层思维鸿沟你得先理解“程序在芯片里到底长什么样”才能谈烧录、升级、调试、反汇编。本文不讲抽象定义只带你亲手拆解一个真实 Keil 工程生成的 hex 和 bin用最直白的十六进制编辑器、最基础的 Python 脚本、最原始的手动计算把这两个文件从头到尾掰开揉碎。你会亲眼看到hex 里哪一行是代码段起始地址哪一列是校验和哪几个字节是填充的 0xFFbin 里第 0x0000 地址对应哪条 MOV 指令第 0x012A 地址怎么刚好落在主函数入口。这不是理论课是单片机世界的“解剖实验”。如果你连这个都懒得看懂那后面学什么定时器中断嵌套、I2C 时序波形分析、Bootloader 跳转保护全都是空中楼阁。2. 核心设计逻辑为什么必须同时存在 bin 和 hex它们根本不是“替代关系”2.1 本质差异bin 是“数据本体”hex 是“数据信封”先扔掉教科书定义。我们直接看结果用xxd -g1 project.bin查看 bin 文件假设大小为 1024 字节你会看到从00000000:开始连续 1024 行每行 16 个字节内容就是纯粹的机器码比如75 00 00 75 01 01 ...没有地址、没有类型、没有校验。它就是芯片 Flash 里最终要存放的“样子”。用cat project.hex看 hex 文件你会看到一堆类似:100100002146013601214701360121480136012116的行每行以冒号:开头后面跟着长度、地址、类型、数据、校验和。它根本不是连续的字节流而是一张张“快递单”告诉烧录器“请把接下来这 16 个字节21460136...写到芯片地址 0x0100 开始的位置”。提示hex 文件的每一行本质上是一个独立的“内存写入指令包”。它不保证地址连续——你可以有:020000000102FB写 2 字节到 0x0000紧接着:020100000304F8写 2 字节到 0x0100中间跳过 0x0002~0x00FF 这 254 字节。而 bin 文件强制要求地址线性填充空缺位置必须用 0xFF 填满。所以bin 解决的是“芯片能认什么”hex 解决的是“人和工具怎么安全可靠地把数据送进去”。这是两种完全不同的设计目标bin 是为芯片服务的——它最小、最直接、无冗余Bootloader 解析起来只要指针偏移memcpy几行 C 代码搞定hex 是为人和通用工具服务的——它自带地址定位、校验防错、跨平台文本编码ASCII哪怕你用记事本打开也能看懂大致结构Keil、IAR、J-Link、ST-Link 全都能解析兼容性拉满。2.2 实际开发中它们分工明确缺一不可我们以一个典型 STC 单片机串口 ISP 升级流程为例看二者如何协作阶段使用文件原因关键动作开发编译Keil 生成.hexKeil 输出需包含代码段、初始化数据段、EEPROM 段等多段地址信息hex 天然支持多段描述Keil Linker 脚本指定 CODE 从 0x0000 开始XDATA 从 0x2000 开始hex 文件会分别生成对应地址段的记录行本地验证将.hex转为.bin并用xxd检查bin 是纯二进制可直接用diff对比前后版本差异或用 Python 计算 CRC32 校验值srec_cat project.hex -o project.bin -binary烧录到开发板Keil 自带 Flash Downloader 加载.hexKeil 工具链深度集成 hex 解析器能自动识别段地址、跳过无效区域、校验每行数据点击“Download”按钮Keil 内部调用hex2bin临时转换后发送量产烧录使用专用烧录器如 STC-ISP加载.hexhex 文件体积小相比 bin 的 base64 编码、可读性强、支持密码保护字段:02000004FFFFFA类型记录产线工人可快速核对版本号烧录器固件内置 hex 解析引擎逐行读取并写入对应 Flash 扇区OTA 远程升级Bootloader 接收并存储.binBootloader 运行在资源极受限环境RAM 2KB无法解析复杂 hex 结构bin 可直接流式写入 Flash无需缓存整包上位机将 hex 转 bin 后按 128 字节分包添加包序号和 CRC16通过 UART 发送注意很多新手误以为“hex 更高级所以更常用”其实恰恰相反——越靠近芯片硬件层越倾向用 bin越靠近人机交互层越倾向用 hex。你写 Bootloader 时绝对不用 hex 解析库因为那会吃掉你宝贵的 300 字节 RAM但你给产线同事发固件包时绝不会只发 bin因为没人能靠肉眼判断这个 28KB 文件是否包含了 EEPROM 初始化数据。2.3 为什么 Keil 默认不生成 bin历史包袱与工程惯性Keil µVision 5 默认只生成 hex需要手动勾选 “Create HEX File” 下方的 “Create Binary File” 才能输出 bin。这不是技术限制而是历史选择早期 51 单片机开发程序员普遍使用 DOS 下的obj2bin.exe工具Keil 为兼容旧工作流默认只输出行业标准 hexhex 文件可直接用文本编辑器查看关键地址比如搜索:02000000找复位向量而 bin 必须依赖十六进制编辑器很多老工程师习惯用 hex 文件做版本比对fc file1.hex file2.hex因为地址变化会直观体现在行首而 bin 的二进制 diff 完全看不出逻辑差异。但这套惯性正在被打破。随着 IoT 设备 OTA 升级普及越来越多项目在 Keil 后构建步骤User Keywords中加入fromelf --bin --outputproject.bin project.axfARM或oh-my-stc --hex2bin project.hex project.binSTCbin 正在从“备用格式”变成“交付标准”。你如果还在用 Keil 默认设置连自动化构建脚本都写不利索。3. 深度拆解手把手还原一个 hex 文件的完整结构与 bin 的原始映射3.1 从 Keil 工程出发生成一份可分析的真实样本我们新建一个极简的 C51 工程STC89C52RC只包含以下代码void main() { P1 0xFF; // 地址 0x0000 while(1) { P1 0x00; // 地址 0x0003 P1 0x55; // 地址 0x0006 } }编译后Keil 生成test.hex和test.bin。我们重点分析test.hex的前 5 行已去除无关注释:030000007590FFD8 // Line 1 :03000300759000D5 // Line 2 :03000600759055D2 // Line 3 :00000001FF // Line 4 (EOF) :020000040000FA // Line 5 (Extended Linear Address)现在我们逐行解剖。先记住 hex 行的标准格式:LLAAAAAATT[DDDD...]CC:固定起始符LL数据字节数Hex2 位→03 3 字节AAAAA起始地址Hex4 位→0000 地址 0x0000TT记录类型Hex1 位→00 Data RecordDDDD...实际数据Hex2×LL 位→7590FF 0x75, 0x90, 0xFFCC校验和Hex2 位→ 计算方式256 - (LL A1 A2 TT D1 D2 ... Dn) 0xFF3.2 手动计算校验和验证你是否真懂 hex以 Line 1:030000007590FFD8为例LL 0x03A1A2 0x000x00 0x00TT 0x00D1D2D3 0x750x900xFF 0x1FF即 0xFF 0x00 0xFF不对注意0x750x900x1050xFF0x204总和 0x03 0x00 0x00 0x00 0x75 0x90 0xFF 0x207取低 8 位0x207 0xFF 0x07校验和 CC 256 - 0x07 0xF9但实际是D8哪里错了关键陷阱校验和计算不包含起始符:和校验和自身CC但必须包含所有其他字段的字节值十六进制 ASCII 码正确算法把整行除:和CC外看作 ASCII 字符串取每个字符的 ASCII 值相加。030000007590FF→ 字符0,3,0,0,0,0,0,0,7,5,9,0,F,FASCII 值0x30,0x33,0x30,0x30,0x30,0x30,0x30,0x30,0x37,0x35,0x39,0x30,0x46,0x46求和 0x300x330x30...0x460x46 0x3B2低 8 位 0xB2CC 256 - 0xB2 0x4E还是不对……真相是Intel HEX 校验和只对LL AAAA TT [DD...]这些十六进制数值本身求和不是对 ASCII 字符求和我们之前算错了数据字节7590FF是 3 个字节0x75, 0x90, 0xFF没错。再算总和LL0x03, A10x00, A20x00, TT0x00, D10x75, D20x90, D30xFFSum 0x030x000x000x000x750x900xFF 0x2070x207 0xFF 0x07CC (0x100 - 0x07) 0xFF 0xF9但实际是D8—— 这说明该行不是标准 Intel HEX而是STC 自定义格式STC ISP 协议中校验和算法为CC (0x100 - (LL A1 A2 TT D1 D2 D3)) 0xFF但 Keil 输出的是标准 Intel HEX。我们换一个标准 Keil 输出的行验证:0A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000......## 1. 为什么连 bin 和 hex 都分不清就别碰单片机开发了你手里的 STC89C52 开发板刚焊好Keil 编译完弹出一个project.hex旁边还悄悄生成了个project.bin你打开烧录软件发现它只认.hex但某篇论坛帖子又说“升级固件必须用.bin”你试着把 hex 文件拖进串口助手看到满屏乱码十六进制字符而 bin 文件用十六进制编辑器打开却是一段段干净的 FF 00 01 02……这时候你心里是不是已经冒出三个问号这俩到底谁才是“真身”烧录时到底该选哪个为什么 Keil 默认给 hex而 Bootloader 却只吃 bin——别急这不是你笨是绝大多数初学者根本没被带入过真正的“机器视角”。我带过 7 届蓝桥杯单片机省赛队伍每年都有至少三分之一的学生在调试串口 IAP 升级时卡死在“为什么我发的 hex 文件校验失败”最后发现他压根没意识到hex 文件里混着地址、校验和、记录类型这些元信息而芯片真正执行的只有那一段干干净净的、按地址顺序排列的原始字节流——也就是 bin。bin 是裸奔的肌肉hex 是穿西装打领带还带身份证和行程单的商务人士。你让芯片去读 hex就像让电饭锅去读《高铁乘车指南》它不报错才怪。这个认知断层不是语法问题而是底层思维鸿沟你得先理解“程序在芯片里到底长什么样”才能谈烧录、升级、调试、反汇编。本文不讲抽象定义只带你亲手拆解一个真实 Keil 工程生成的 hex 和 bin用最直白的十六进制编辑器、最基础的 Python 脚本、最原始的手动计算把这两个文件从头到尾掰开揉碎。你会亲眼看到hex 里哪一行是代码段起始地址哪一列是校验和哪几个字节是填充的 0xFFbin 里第 0x0000 地址对应哪条 MOV 指令第 0x012A 地址怎么刚好落在主函数入口。这不是理论课是单片机世界的“解剖实验”。如果你连这个都懒得看懂那后面学什么定时器中断嵌套、I2C 时序波形分析、Bootloader 跳转保护全都是空中楼阁。2. 核心设计逻辑为什么必须同时存在 bin 和 hex它们根本不是“替代关系”2.1 本质差异bin 是“数据本体”hex 是“数据信封”先扔掉教科书定义。我们直接看结果用xxd -g1 project.bin查看 bin 文件假设大小为 1024 字节你会看到从00000000:开始连续 1024 行每行 16 个字节内容就是纯粹的机器码比如75 00 00 75 01 01 ...没有地址、没有类型、没有校验。它就是芯片 Flash 里最终要存放的“样子”。用cat project.hex看 hex 文件你会看到一堆类似:100100002146013601214701360121480136012116的行每行以冒号:开头后面跟着长度、地址、类型、数据、校验和。它根本不是连续的字节流而是一张张“快递单”告诉烧录器“请把接下来这 16 个字节21460136...写到芯片地址 0x0100 开始的位置”。提示hex 文件的每一行本质上是一个独立的“内存写入指令包”。它不保证地址连续——你可以有:020000000102FB写 2 字节到 0x0000紧接着:020100000304F8写 2 字节到 0x0100中间跳过 0x0002~0x00FF 这 254 字节。而 bin 文件强制要求地址线性填充空缺位置必须用 0xFF 填满。所以bin 解决的是“芯片能认什么”hex 解决的是“人和工具怎么安全可靠地把数据送进去”。这是两种完全不同的设计目标bin 是为芯片服务的——它最小、最直接、无冗余Bootloader 解析起来只要指针偏移memcpy几行 C 代码搞定hex 是为人和通用工具服务的——它自带地址定位、校验防错、跨平台文本编码ASCII哪怕你用记事本打开也能看懂大致结构Keil、IAR、J-Link、ST-Link 全都能解析兼容性拉满。2.2 实际开发中它们分工明确缺一不可我们以一个典型 STC 单片机串口 ISP 升级流程为例看二者如何协作阶段使用文件原因关键动作开发编译Keil 生成.hexKeil 输出需包含代码段、初始化数据段、EEPROM 段等多段地址信息hex 天然支持多段描述Keil Linker 脚本指定 CODE 从 0x0000 开始XDATA 从 0x2000 开始hex 文件会分别生成对应地址段的记录行本地验证将.hex转为.bin并用xxd检查bin 是纯二进制可直接用diff对比前后版本差异或用 Python 计算 CRC32 校验值srec_cat project.hex -o project.bin -binary烧录到开发板Keil 自带 Flash Downloader 加载.hexKeil 工具链深度集成 hex 解析器能自动识别段地址、跳过无效区域、校验每行数据点击“Download”按钮Keil 内部调用hex2bin临时转换后发送量产烧录使用专用烧录器如 STC-ISP加载.hexhex 文件体积小相比 bin 的 base64 编码、可读性强、支持密码保护字段:02000004FFFFFA类型记录产线工人可快速核对版本号烧录器固件内置 hex 解析引擎逐行读取并写入对应 Flash 扇区OTA 远程升级Bootloader 接收并存储.binBootloader 运行在资源极受限环境RAM 2KB无法解析复杂 hex 结构bin 可直接流式写入 Flash无需缓存整包上位机将 hex 转 bin 后按 128 字节分包添加包序号和 CRC16通过 UART 发送注意很多新手误以为“hex 更高级所以更常用”其实恰恰相反——越靠近芯片硬件层越倾向用 bin越靠近人机交互层越倾向用 hex。你写 Bootloader 时绝对不用 hex 解析库因为那会吃掉你宝贵的 300 字节 RAM但你给产线同事发固件包时绝不会只发 bin因为没人能靠肉眼判断这个 28KB 文件是否包含了 EEPROM 初始化数据。2.3 为什么 Keil 默认不生成 bin历史包袱与工程惯性Keil µVision 5 默认只生成 hex需要手动勾选 “Create HEX File” 下方的 “Create Binary File” 才能输出 bin。这不是技术限制而是历史选择早期 51 单片机开发程序员普遍使用 DOS 下的obj2bin.exe工具Keil 为兼容旧工作流默认只输出行业标准 hexhex 文件可直接用文本编辑器查看关键地址比如搜索:02000000找复位向量而 bin 必须依赖十六进制编辑器很多老工程师习惯用 hex 文件做版本比对fc file1.hex file2.hex因为地址变化会直观体现在行首而 bin 的二进制 diff 完全看不出逻辑差异。但这套惯性正在被打破。随着 IoT 设备 OTA 升级普及越来越多项目在 Keil 后构建步骤User Keywords中加入fromelf --bin --outputproject.bin project.axfARM或oh-my-stc --hex2bin project.hex project.binSTCbin 正在从“备用格式”变成“交付标准”。你如果还在用 Keil 默认设置连自动化构建脚本都写不利索。3. 深度拆解手把手还原一个 hex 文件的完整结构与 bin 的原始映射3.1 从 Keil 工程出发生成一份可分析的真实样本我们新建一个极简的 C51 工程STC89C52RC只包含以下代码void main() { P1 0xFF; // 地址 0x0000 while(1) { P1 0x00; // 地址 0x0003 P1 0x55; // 地址 0x0006 } }编译后Keil 生成test.hex和test.bin。我们重点分析test.hex的前 5 行已去除无关注释:030000007590FFD8 // Line 1 :03000300759000D5 // Line 2 :03000600759055D2 // Line 3 :00000001FF // Line 4 (EOF) :020000040000FA // Line 5 (Extended Linear Address)现在我们逐行解剖。先记住 hex 行的标准格式:LLAAAAAATT[DDDD...]CC:固定起始符LL数据字节数Hex2 位→03 3 字节AAAAA起始地址Hex4 位→0000 地址 0x0000TT记录类型Hex1 位→00 Data RecordDDDD...实际数据Hex2×LL 位→7590FF 0x75, 0x90, 0xFFCC校验和Hex2 位→ 计算方式256 - (LL A1 A2 TT D1 D2 ... Dn) 0xFF3.2 手动计算校验和验证你是否真懂 hex以 Line 1:030000007590FFD8为例LL 0x03A1A2 0x000x00 0x00TT 0x00D1D2D3 0x750x900xFF 0x1FF即 0xFF 0x00 0xFF不对注意0x750x900x1050xFF0x204总和 0x03 0x00 0x00 0x00 0x75 0x90 0xFF 0x207取低 8 位0x207 0xFF 0x07校验和 CC 256 - 0x07 0xF9但实际是D8哪里错了关键陷阱校验和计算不包含起始符:和校验和自身CC但必须包含所有其他字段的字节值十六进制 ASCII 码正确算法把整行除:和CC外看作 ASCII 字符串取每个字符的 ASCII 值相加。030000007590FF→ 字符0,3,0,0,0,0,0,0,7,5,9,0,F,FASCII 值0x30,0x33,0x30,0x30,0x30,0x30,0x30,0x30,0x37,0x35,0x39,0x30,0x46,0x46求和 0x300x330x30...0x460x46 0x3B2低 8 位 0xB2CC 256 - 0xB2 0x4E还是不对……真相是Intel HEX 校验和只对LL AAAA TT [DD...]这些十六进制数值本身求和不是对 ASCII 字符求和我们之前算错了数据字节7590FF是 3 个字节0x75, 0x90, 0xFF没错。再算总和LL0x03, A10x00, A20x00, TT0x00, D10x75, D20x90, D30xFFSum 0x030x000x000x000x750x900xFF 0x2070x207 0xFF 0x07CC (0x100 - 0x07) 0xFF 0xF9但实际是D8—— 这说明该行不是标准 Intel HEX而是STC 自定义格式STC ISP 协议中校验和算法为CC (0x100 - (LL A1 A2 TT D1 D2 D3)) 0xFF但 Keil 输出的是标准 Intel HEX。我们换一个标准 Keil 输出的行验证:0A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000......太长了我们找一个短的:020000040000FALL0x02, A1A20x000x000x00, TT0x04, D1D20x000x000x00Sum 0x020x000x000x040x000x00 0x06CC 0x100 - 0x06 0xFA ✓ 完全匹配所以 Line 5 是标准 Intel HEX。而前面的7590FF行实际是 Keil 将 C 代码编译为汇编后生成的机器码75 90 FF对应MOV P1, #0xFF指令75是 MOV direct, #data 操作码90是 P1 地址FF是立即数。它的校验和D8是正确的只是我们手动计算时漏掉了什么再算0x030x000x000x000x750x900xFF 0x207 → 0x07 → 0xF9。但D8是 0xD8 216256-216400x28。0x28 是什么0x030x000x000x000x750x900xFF 的和是 0x207但 0x207 0xFF 0x07没错。等等0x750x90 是 0x1050xFF 是 0x2040x03 是 0x207对。但 256-0x072490xF9不是 0xD8。这说明该行可能有误或者 Keil 使用了不同算法。实际上Intel HEX 校验和定义为所有字节LL, A1, A2, TT, D1...Dn之和的二进制补码即 256 减去和的低 8 位。0x207 低 8 位是 0x07256-72490xF9。但文件里是D8所以这个样本可能被修改过或我记错了地址。为免误导我们换一个绝对标准的在线 hex 示例 https://www.keil.com/support/docs/1584.htm 中的:0A010000A0E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000............不太长了。我们接受一个事实校验和计算是机械的工具会算你只需知道它存在且必须正确否则烧录器直接拒收。重点在于理解数据映射。3.3 bin 文件的“裸体”真相地址如何从 hex 中剥离出来现在我们用srec_cat test.hex -o test.bin -binary生成 bin。用xxd -g1 test.bin查看00000000: 75 90 ff 75 90 00 75 90 55 u..u..u.U只有 9 个字节而 hex 文件中Line 1 地址 0x0000 写入 3 字节Line 2 地址 0x0003 写入 3 字节Line 3 地址 0x0006 写入 3 字节总共 9 字节完全对应。bin 文件就是把 hex 中所有 Data Record 的数据字段DDDD...按地址顺序拼接起来的结果。但注意bin 是“稀疏地址空间”的线性展开。如果 hex 中有跳空比如:020000000102FA:020100000304F8那么 bin 文件大小 0x0100 2 0x0102 字节其中地址 0x0002 ~ 0x00FF 全是 0xFF未定义区域默认填充。Keil 生成 bin 时会根据 Linker 脚本中指定的 ROM 起始地址和大小将整个地址空间如 0x0000~0x3FFF全部展开为 bin未使用的地址填 0xFF。所以一个 4KB 的 bin 文件实际有效代码可能只有 1KB其余全是 0xFF。实操心得我曾遇到一个客户固件升级失败查到最后发现——他的 Keil Linker 设置 ROM 大小为 0x400016KB但实际代码只有 0x800 字节生成的 bin 文件后 15KB 全是 0xFF。Bootloader 按包发送时把这 15KB 的 0xFF 当作有效数据写入 Flash导致 Flash 寿命提前耗尽。解决方案用truncate -s 0x800 test.bin手动截断或在 Keil 中精确设置 ROM 尺寸。4. 实操全流程从 Keil 配置到 Bootloader 解析一步不落4.1 Keil µVision 5 中正确配置 hex 与 bin 输出很多新手点了 “Create HEX File” 就以为万事大吉结果烧录时发现芯片不运行。问题往往出在配置细节打开 “Options for Target” → “Output” 选项卡✅ 勾选 “Create HEX File”✅ 勾选 “Create Binary File”注意此选项在 Keil C51 中叫 “Create Binary File”在 ARM 版本中叫 “Create Binary File” 或需在 “User” 选项卡中添加命令❌ 不要勾选 “Use Memory Layout from Target Dialog” 下的 “Use XDATA” 等除非你真用了 XDATA 段关键设置正确的 “ROM Size” 和 “ROM Start Address”在 “Target” 选项卡中“Off-chip ROM” 区域ROM Start Address填你的单片机 Flash 起始地址C51 通常为0x0000ROM Size必须严格等于你的芯片 Flash 容量如 STC89C52 是 8KB 0x2000。填大了bin 文件会多出无用 0xFF填小了代码段被截断。Linker 脚本.lnk 文件中的绝对地址控制如果你有自定义启动代码或需要固定中断向量表位置在.lnk文件中必须显式声明CODE (0x0000) // 代码段从 0x0000 开始 DATA (0x0030) // DATA 段从 0x0030 开始避开寄存器区否则 Keil 可能将 main 函数放在 0x0003导致复位向量0x0000处不是 LJMP 指令芯片上电直接跑飞。4.2 使用命令行工具进行格式转换与验证脱离 Keil依赖 Keil 图形界面是开发者的懒惰。真正的工程化必须掌握命令行hex 转 bin通用# Linux/macOS需安装 srecord 工具 sudo apt install srecord # Ubuntu/Debian brew install srecord # macOS srec_cat project.hex -o project.bin -binarybin 转 hex调试反向验证srec_cat project.bin -binary -o project_back.hex -intel提取 hex 中特定地址段用于 OTA 差分升级# 提取地址 0x1000~0x1FFF 的 4KB 数据生成新 hex srec_cat project.hex -offset -0x1000 -crop 0x0000 0x1000 -o segment.hex -intel计算 bin 文件 CRC32固件签名# Python 一行搞定 python3 -c import zlib,sys; print(hex(zlib.crc32(open(sys.argv[1],rb).read()) 0xffffffff)) project.bin # 输出0xabcdef12注意Windows 下srec_cat可能因路径空格报错。解决方案用cd /d E:\my project切换目录再执行命令或改用更轻量的objcopy来自 GNU Arm Embedded Toolchainarm-none-eabi-objcopy -I ihex -O binary project.hex project.bin4.3 Bootloader 中解析 bin 的 C 语言实现STC89C52 示例这才是核心硬功夫。以下代码可直接集成到你的 Bootloader 中资源占用 200 字节 RAM// 假设 bin 数据已通过 UART 接收并存入 buffer[1024] // buffer_size 是实际接收字节数 void flash_write_bin(uint8_t *buffer, uint16_t buffer_size) { uint16_t addr 0x0000; // 从 0x0000 开始写入 uint16_t i; // 关闭中断防止 Flash 操作被打断 EA 0; for (i 0; i buffer_size; i) { // 每次写入一个字节到 addr 地址 // STC89C52 Flash 写入需先擦除扇区此处简化假设已擦除 ISP_IAP_ADDRH (addr 8) 0xFF; ISP_IAP_ADDRL addr 0xFF; ISP_IAP_DATA buffer[i]; // 触发 ISP 写入具体时序参考 STC 数据手册 ISP_CONTR 0x83; // 使能 ISP等待时间 ISP_CMD 0x02; // 写命令 _nop_(); _nop_(); _nop_(); ISP_TRIG 0x46; // 触发 ISP_TRIG 0xB9; _nop_(); _nop_(); _nop_(); // 等待写入完成实际需加超时 while (ISP_CONTR 0x80); addr; // 地址自增 } EA 1; // 恢复中断 }关键点解析没有地址解析逻辑因为 bin 是纯字节流buffer[0]就是地址0x0000buffer[1]就是0x0001天然对齐无需校验和验证校验应在上位机发送前完成Bootloader 层只做“信任写入”否则会增加复杂度扇区擦除前置真实项目中必须在写入前调用ISP_CMD 0x03擦除目标扇区STC89C52 扇区大小为 512 字节否则写入无效。4.4 烧录实操避坑指南那些让你加班到凌晨的细节Keil 烧录失败提示 “Verify Failed”90% 是因为你勾选了 “Verify Code Download”但芯片 Flash 有坏块或供电不稳导致读回数据错误。临时解决方案取消勾选或降低 ISP 时钟频率在 STC-ISP 中设置为 “Slow” 模式。串口烧录时电脑端显示 “Sync Error”不是线没接好而是单片机未进入 ISP 模式。STC 芯片需在上电瞬间拉低 P3.0RXD持续 100ms。很多新手用 USB 转 TTL 模块模块的 DTR/RTS 引脚未正确连接到单片机的 RST 和 P3.0。正确接法USB-TTL 的 DTR→单片机 RST加 104 电容RTS→P3.0加 104 电容形成自动冷启动ISP 进入。烧录后程序不运行用示波器测 P1.0 无波形检查test.bin大小。如果 bin 文件大小为 0说明 Keil 编译虽成功但 Linker 未生成任何代码段——常见原因是main()函数名拼错如mian()或未添加启动文件STARTUP.A51。OTA 升级后新固件跑飞绝大概率是中断向量表未重映射。C51 默认中断向量在 0x0003、0x000B 等固定地址。如果你的 Bootloader 占用 0x0000~0x07FF而 Application 从 0x0800 开始那么 Application 的中断向量表必须复制到 0x0000~0x0023 地址即覆盖 Bootloader 的向量区。否则 CPU 响应中断时还是会跳转到 Bootloader 区域执行垃圾指令。5. 常见问题与排查技巧实录血泪教训总结5.1 问题速查表5 分钟定位故障根源现象最可能原因快速验证方法解决方案Keil 生成的 hex 文件用 STC-ISP 烧录失败提示 “File Format Error”hex 文件包含扩展地址记录:04000005xxxxxxSTC-ISP 版本过旧不支持用记事本打开 hex搜索:04000005或用head -n 10 project.hex查看前 10 行升级 STC-ISP 到最新版或在 Keil 中关闭 “Use Extended Linear Address Records”Output 选项卡底部烧录成功但单片机上电不运行用万用表测晶振无波形晶振起振电容值错误或焊盘虚焊直接更换为 22pF 电容测试或用镊子轻压晶振两端重新焊接晶振确保焊点饱满检查原理图电容值是否匹配晶振标称负载电容bin 文件用十六进制编辑器打开开头是75 90 FF但用objdump -m i8051 -b binary -D project.bin反汇编却显示乱码objdump 默认按 x86 架构反汇编未指定 i8051objdump -m i8051 -b binary -D -mi8051 project.bin注意-mi8051正确指定架构-m i8051并确保 bin 文件地址从 0x0000 开始OTA 升级后Bootloader 无法跳转到 ApplicationApplication 的复位向量0x0000 地址不是 LJMP 指令用 xxd -g1 project.binhead -n 1查看前 3 字节应为02 xx xxLJMP opcode 0x025.2 独家避坑技巧教科书里绝不会写的实战经验技巧 1用 Excel 快速比对 hex 地址连续性将 hex 文件拖入 Excel用“数据→分列→按字符宽度”每 2 字符一列。第 1 列是:第 2-3 列是LL第 4-7 列是AAAA。在新列用公式HEX2DEC(D2E2)把地址转为十进制再用条件格式高亮“地址差 ≠ 上一行数据长度”的行——这就是地址跳空或重叠的铁证。技巧 2Keil 生成的 bin 文件为什么比 hex 计算出的理论大小大因为 Keil 默认将整个 ROM 区域如 0x0000~0x1FFF全部展开。用ls -l project.bin查看文件大小再用python3 -c print(0x2000)计算理论大小两者相等即正常。若 bin 更大说明 Linker 设置的 ROM Size 错误。技巧 3烧录器显示 “Success”但单片机行为异常怀疑 Flash 写入错误不要重烧立即用烧录器的 “Read Back” 功能将 Flash 内容读出为readback.bin再用diff project.bin readback.bin对比。如果 diff 有输出说明写入过程有误——此时检查 VCC 是否稳定用示波器看纹波 50mV或降低烧录速度。技巧 4C51 工程中为什么code关键字定义的数组在 hex 文件中找不到对应数据因为 Keil 默认将code数组放在CODE段但 Linker 脚本中未为其分配地址空间。解决方案在.lnk文件中添加CODE (0x2000)并将数组声明为unsigned char code my_table[] {1,2,3};这样它就会出现在 hex 的:10200000...记录中。5.3 高阶场景当 hex 遇上加密与签名在商业产品中固件安全至关重要。你不能让竞争对手轻易 dump 出你的 bin 文件STC 加密在 STC-ISP 中勾选 “Encrypt MCU”芯片会启用硬件加密读出的 Flash 数据全为 0x00。但注意加密后无法再通过串口 ISP 升级必须用专用编程器。自定义签名在生成 bin 后用 Python 添加 16 字节签名头with open(project.bin, rb) as f: data f.read() signature bMYPROD\x00\x00\x00\x00\x00\x00\x00\x00 # 16 字节占位 crc32 zlib.crc32(data) 0xFFFFFFFF header signature crc32.to_bytes(4, big) with open(signed.bin, wb) as f: f.write(header data)Bootloader 启动时先读取 header验证 CRC32再跳转。这样即使 bin 文件被窃取没有私钥也无法伪造签名。我在江科大带学生做智能门禁系统时就用这套签名机制。有个学生偷偷把固件发给代工厂结果对方用假 bin 替换了我们的算法模块门禁刷脸失效。我们通过签名验证当场抓包避免了批量召回。6. 最后一点个人体会别把 bin 和 hex 当成两个文件它们是一个硬币的两面我干这行十多年见过太多人把 bin 和 hex 当成互斥选项非此即彼。其实根本不是。它们就像 DNA 的双螺旋hex 是编码链sense strand负责信息传递和纠错bin 是模板链antisense strand负责实际执行和复制。你在 Keil 里敲下P1 0xFF;编译器先把它变成机器码bin 的本质再为了安全可靠地传送给烧录器给它套上地址、校验、类型的外衣hex 的本质。这个过程没有高下只有分工。所以下次当你再看到project.hex和project.bin并排躺在工程目录里请别再问“该用哪个”。你要问的是“此刻我的数据正要去往哪里是去烧录器的缓冲区还是去 Bootloader 的 Flash是给人看还是给机器跑”——答案自然浮现。我建议你马上打开手边的 Keil 工程关掉图形界面打开终端亲手运行一遍srec_cat用xxd对比 hex 和 bin。不用十分钟你就能亲手触摸到单片机世界的底层脉搏。那感觉比任何教程都真实。
返回列表