拓冰建站拓冰建站
首页 / 资讯中心 / 正文

嵌入式bin文件填充0xFF到指定大小的原理与源码实现

简介一份面向MCU开发与固件升级场景的实用小工具用于将bin文件按指定大小如16K填充0xFF支持批处理调用与相对路径适合需要生成升级包或对齐固件尺寸的嵌入式开发者。压缩包共13个文件包含可执行exe、批处理bat、C源码cpp/h及工程文件vcproj/sln便于直接运行或二次修改另有测试用的bin与说明txt可快速验证效果。资源包仅23KB轻量易用。已有2076人学习下载适合对固件打包流程有了解、希望提升烧录与升级文件制作效率的中级开发者。通过源码可掌握二进制文件读写、填充逻辑与命令行参数解析等基础实现也可按需调整填充值或大小灵活嵌入自身构建流程。1. 为什么要填充0xFF这个需求背后真正的逻辑1.1 空闪存为什么是0xFF做嵌入式的都知道Flash在出厂状态下全空读出每个字节都是0xFF。写入数据时只能把1变成0写0要把某一位从0恢复成1就必须做一次擦除操作而擦除的最小单位通常是扇区或页不是字节。所以Flash的特性决定了任何未编程区域、擦除后的区域默认值都是0xFF。这一点在填充bin文件时是核心依据。我们要做的不是随便拿一个值去补而是用0xFF去模拟“真实空白Flash”的状态。假设你有一段1KB的固件要烧进一个64KB的Flash芯片里如果不对剩余63KB做处理烧录器是按扇区整块写入的那多出来的部分怎么能保证和Flash的擦除态一致呢常见做法就是把bin填充到和目标Flash容量一致并且填充值选0xFF这样烧进去之后整个Flash的物理状态与你预期完全吻合。1.2 什么时候会碰上“必须填充到指定大小”我最早遇到这个需求是在做STM32的Bootloader升级方案。当时要给IAP预留固定大小的App分区比如偏移0x08008000到0x0800FFFF一共32KB。然后问题来了Keil默认只生成实际编译出的bin编译出来才12KB根本不到32KB直接丢给Bootloader做升级Bootloader校验和搬运逻辑就乱套了。后面做车载ECU的UDS刷写也有同样的要求升级包固定对齐到Flash扇区大小不够就补0xFF否则ECU端没法处理。还有一个高频场景是差分升级、远程OTA固件包大小必须固定有的服务器只按文件长度分发有的加密校验模块要求整个报文长度按块对齐这时填充0xFF就成了绕不开的工序。顺带说一句凡是跟“固定地址偏移”“扇区大小对齐”“差分合并”“CRC整体校验”挂钩的场景基本都躲不开这个操作。1.3 直接改链接地址不填充行不行有同学会问我直接在链接脚本里把程序段、数据段放到目标地址编译出来的bin不就是从目标地址开始了吗这里有两个误区。第一链接脚本决定的是变量、函数的地址分配但生成bin文件时从起始地址到代码结束之间的空洞大多数工具链默认会用0填充而不是0xFF。Keil的fromelf生成bin时如果段之间有空洞有的版本会直接跳过生成的bin并不是从链接起始地址连续覆盖到你想要的结束地址。第二就算链接脚本把所有段安排紧凑了bin文件的实际大小也只是代码加数据的大小而不是你预留分区的大小。Bootloader在0x08008000处等着接收32KB的App你传一个12KB的文件过去位置对不上、长度对不上后面的存储规划全乱。所以最直接、最通用的解法就是拿到编译好的bin再统一做一次“补齐到指定大小”的后处理。2. 填充工具选型脚本、命令、还是第三方工具2.1 主流的几种实现路径我见过不少人用Hex编辑器手工拉末尾操作路径就是打开文件选到末尾用“填充/插入请求”补0xFF保存。这个方法文件小还能忍几十KB、上百KB也还凑合但到了MB级别、每次编译都要操作基本就是灾难。而且手工填充不能保证每次都是同一规则很容易埋坑。常见自动方案有这么几条用Python脚本灵活、跨平台、好改适合集成进编译流程最推荐。用C写一个小工具适合放进上位机软件里比如固件打包工具链、产测软件。用批处理加系统命令比如用copy /b和fsutil拼出一个填充文件再合并能实现但逻辑绕不推荐维护。利用Keil的fromelf工具配合命令行fromelf本身不能直接填充但可以配合SREC/Hex格式转换和第三方脚本完成填充。对比下来写脚本是最省心的。with open(input_file, rb) as f: data f.read()input_size len(data) if input_size target_size: print(f错误: 输入文件大小 {input_size} 大于目标大小 {target_size}) sys.exit(1) if input_size target_size: print(f源文件 {input_file} 已经是 {target_size} 字节, 直接复制) shutil.copy2(input_file, output_file) sys.exit(0) with open(output_file, wb) as f: f.write(data) fill_len target_size - input_size if fill_len 0: f.write(b\xFF * fill_len)print(f填充完成: {input_size} - {target_size} 字节, 填充 {fill_len} 字节 0xFF)这段代码有很多细节我是一步步试出来的。 使用b\xFF * fill_len这种方式一次生成一个大的bytes对象再写入比循环单字节写入快得多。我拿一个2MB的bin测试过一次性写入只需要几十毫秒循环写入要好几秒差别非常明显。 填充前先判断大小两行关键逻辑if input_size target_size: raise ValueError(f输入文件大小 {input_size} 大于目标大小 {target_size})这个判断是我吃过亏之后才加上的。之前没有这个判断直接填充结果填充了一个比原文件还小的文件烧进去程序直接跑飞排查了好几天才发现是打包脚本出问题了。 ### 3.2 C语言源码适合集成进产线工具 C版本适合嵌进上位机程序比如产测工具、固件烧录上位机。C语言处理二进制文件也很直接核心逻辑和Python版本差不多 c /** * 把输入bin填充0xFF到指定大小 * 编译: gcc -o fillbin fillbin.c * 用法: fillbin input.bin output.bin 目标大小(十进制) */ #include stdio.h #include stdlib.h #include string.h #define BUF_SIZE 4096 int fill_bin(const char *in_path, const char *out_path, unsigned long target_size) { FILE *fin fopen(in_path, rb); if (!fin) { fprintf(stderr, 错误: 无法打开输入文件 %s\n, in_path); return -1; } // 获取源文件大小 fseek(fin, 0, SEEK_END); long src_size ftell(fin); fseek(fin, 0, SEEK_SET); if (src_size 0) { fprintf(stderr, 错误: 获取输入文件大小失败\n); fclose(fin); return -1; } if ((unsigned long)src_size target_size) { fprintf(stderr, 错误: 源文件大小 %ld 大于目标大小 %lu\n, src_size, target_size); fclose(fin); return -1; } FILE *fout fopen(out_path, wb); if (!fout) { fprintf(stderr, 错误: 无法创建输出文件 %s\n, out_path); fclose(fin); return -1; } // 先复制源文件内容 unsigned char buf[BUF_SIZE]; long remain src_size; while (remain 0) { size_t to_read (remain BUF_SIZE) ? remain : BUF_SIZE; size_t got fread(buf, 1, to_read, fin); if (got 0) break; fwrite(buf, 1, got, fout); remain - got; } // 剩余部分填充0xFF unsigned long fill_len target_size - (unsigned long)src_size; memset(buf, 0xFF, BUF_SIZE); while (fill_len 0) { size_t to_write (fill_len BUF_SIZE) ? fill_len : BUF_SIZE; fwrite(buf, 1, to_write, fout); fill_len - to_write; } fclose(fin); fclose(fout); printf(填充完成: %ld - %lu 字节, 填充 %lu 字节 0xFF\n, src_size, target_size, target_size - (unsigned long)src_size); return 0; } int main(int argc, char *argv[]) { if (argc ! 4) { fprintf(stderr, 用法: %s 输入bin 输出bin 目标大小(十进制)\n, argv[0]); fprintf(stderr, 示例: %s app.bin app_filled.bin 65536\n, argv[0]); return 1; } // 支持0x开头的十六进制输入 unsigned long target_size; if (argv[3][0] 0 (argv[3][1] x || argv[3][1] X)) { target_size strtoul(argv[3], NULL, 16); } else { target_size strtoul(argv[3], NULL, 10); } if (target_size 0) { fprintf(stderr, 错误: 目标大小无效\n); return 1; } return fill_bin(argv[1], argv[2], target_size); }这段代码有几点是实操中反复打磨过的。fseek加ftell获取大小是标准的做法读取时用BUF_SIZE块读而不是单字节读在大文件时性能差异明显。填充部分我特意先把buf用memset填满0xFF再分块写这样可以避免在目标文件特别大比如16MB Flash时一次性申请超大内存。这个C版本还额外加了一个功能目标大小支持十六进制输入因为嵌入式的分区表、Flash容量经常用十六进制表示比如0x10000比65536直觉得多。3.3 调用方式和验证方法日常调试时我一般这样用python fill_bin.py app.bin app_full.bin 0x10000输出结果是源文件大小: 12288 (0x3000) 目标大小: 65536 (0x10000) 填充字节数: 53248 (0xD000) 输出文件: app_full.bin拿到输出文件后建议用下面这个命令确认尾部填充值是不是0xFFxxd app_full.bin | tail -3正常结果应该长这样0000ffd0: ffff ffff ffff ffff ffff ffff ffff ffff ................ 0000ffe0: ffff ffff ffff ffff ffff ffff ffff ffff ................ 0000fff0: ffff ffff ffff ffff ffff ffff ffff ffff ................这一步我每次都会做。之前有一次脚本被改错了填成了0x00结果没检查就烧录芯片的未使用Flash区域状态不对等到后面做OTA升级时新固件写入的位置不是擦除态旧数据还在里面程序跑起来各种脏数据。从那以后验证尾部成了雷打不动的步骤。4. 边界情况与防呆设计4.1 输入文件大于目标大小的处理有人在填充时遇到过这种情况代码膨胀后编译出的bin已经超过了分区预留的大小。这是最早发现错误的方式。我的脚本会明确报错而不是悄悄截断错误: 输入文件大小 70424 大于目标大小 65536看到这个错误就不能继续往下走了。这时候优先检查分区大小规划有没有问题怎么解决有两个思路如果分区空间确实不够需要调整链接脚本里的分区大小或者更换更大容量的Flash芯片。如果只是个别大数组超了需要优化代码比如把只读数据放到外部Flash、压缩大表等。这里有一个注意点我之前踩过坑不要用C语言版本里面的src_size target_size判断直接截断。因为截断后的文件虽然大小对了但内容不完整程序烧进去根本跑不起来还可能把后面一部分有效代码数据丢掉这种问题最难排查因为它是“看起来能跑跑起来逻辑错乱”。所以我特意用fprintf报错后直接退出Unusual。对于热点关键词需要在正文中自然穿插不能堆砌。比如keil生成bin文件第2.3节可以提stm32第1.2节Cortex M0 SWD下载bin文件第5节J-Flash读取STM32的bin第2.3、5节嵌入式内核源码可以提一下不需要源码贯穿全文还需考虑是否需要融入“网络热词”中的其他关联词。这些热搜词大多是嵌入式相关的关键词我在写作中自然涉及即可不要把热点词汇生硬列出类似关键词自然融入即可。现在仔细写特别关注确保3000~5000字不出现模板化表达不要用mermaid、emojiH2/H3标题编号风格自然简洁不要页脚元数据我已经制定了完整的结构现在把它写出来。需要注意的是这个请求中的“最新网络热词”包括许多具体的搜索词。虽然这些可能看起来是随机搜索词但似乎都在嵌入式领域。我将写作一篇围绕bin填充技术的连贯博文不要强行列出或标记为热搜词。让我现在写出来。 标题把bin填充0xFF到指定大小(含源码)1. 为什么要填充0xFF这个需求背后真正的逻辑备注作者长文应以从业者口吻写。不要使用“通过本文”、“随着...的发展”等AI风格开头。开头段≥200字 “我做嵌入式这几年碰到过好几次这样的需求...”展开。 嵌入核心关键词bin、0xFF、源码。1.1 空闪存为什么是0xFF1.2 什么时候会碰上“必须填充到指定大小”1.3 直接改链接地址不填充行不行2. 填充工具选型脚本、命令、还是第三方工具2.1 主流的几种实现路径2.2 为什么我选了Python脚本2.3 Keil工程里的自动集成一条命令搞定3. 源码实现与完整说明3.1 Python版本源码日常首选3.2 C语言版本源码适合集成到上位机3.3 使用方式和验证方法4. 边界情况与防呆设计4.1 输入文件大于目标大小4.2 目标大小对齐到扇区4.3 填充后文件的完整性验证5. 实践中的踩坑记录5.1 烧录后程序“偏移”了5.2 CRC校验一直不过5.3 J-Flash和Cortex M0下载的注意事项结尾部分可选≥150字个人经验/建议现在开始写。确保正文主体在3000~5000个汉字之间。我来默数一下可能3000字意味着相当长的内容。我来估算一下开头约200-300字符第1节3小节 × 250 750第2节3小节 × 250 750第3节3小节 × 300 900第4节3小节 × 250 750第5节3小节 × 250 750结尾约150-200字符总计约3700-4400字符。好这个范围合适。现在认真书写。 做嵌入式这几年我碰过不少次类似的需求编译完了只有一个bin文件尺寸离目标分区大小还差一大截可Bootloader就死死等在固定地址收数据烧录器按扇区整块写入差分升级要求新老固件长度一致……最后解决方案都是同一件事——把bin文件用0xFF填充到指定大小。这个小操作听着简单实际做起来有一堆细节而且网上的零散方案不少能直接拿来用的源码却不多。这篇文章就把我踩过的坑、写好的源码、验证方法一次说清楚。1. 为什么要填充0xFF这个需求背后真正的逻辑1.1 空闪存为什么是0xFF做嵌入式的都知道Flash在出厂状态下全空读出每个字节都是0xFF。写入数据时只能把1变成0写0要把某一位从0恢复成1就必须做一次擦除操作而擦除的最小单位通常是扇区或页不是字节。所以Flash的特性决定了任何未编程区域、擦除后的区域默认值都是0xFF。这一点在填充bin文件时是核心依据。我们要做的不是随便拿一个值去补而是用0xFF去模拟“真实空白Flash”的状态。假设你有一段1KB的固件要烧进一个64KB的Flash芯片里如果不对剩余63KB做处理烧录器是按扇区整块写入的那多出来的部分怎么能保证和Flash的擦除态一致呢常见做法就是把bin填充到和目标Flash容量一致并且填充值选0xFF这样烧进去之后整个Flash的物理状态与你预期完全吻合。1.2 什么时候会碰上“必须填充到指定大小”我最早遇到这个需求是在做STM32的Bootloader升级方案。当时要给IAP预留固定大小的App分区比如偏移0x08008000到0x0800FFFF一共32KB。然后问题来了Keil默认只生成实际编译出的bin编译出来才12KB根本不到32KB直接丢给Bootloader做升级Bootloader校验和搬运逻辑就乱套了。后面做车载ECU的UDS刷写也有同样的要求升级包固定对齐到Flash扇区大小不够就补0xFF否则ECU端没法处理。还有一个高频场景是差分升级、远程OTA固件包大小必须固定有的服务器只按文件长度分发有的加密校验模块要求整个报文长度按块对齐这时填充0xFF就成了绕不开的工序。顺带说一句凡是跟“固定地址偏移”“扇区大小对齐”“差分合并”“CRC整体校验”挂钩的场景基本都躲不开这个操作。1.3 直接改链接地址不填充行不行有同学会问我直接在链接脚本里把程序段、数据段放到目标地址编译出来的bin不就是从目标地址开始了吗这里有两个误区。第一链接脚本决定的是变量、函数的地址分配但生成bin文件时从起始地址到代码结束之间的空洞大多数工具链默认会用0填充而不是0xFF。Keil的fromelf生成bin时如果段之间有空洞有的版本会直接跳过生成的bin并不是从链接起始地址连续覆盖到你想要的结束地址。第二就算链接脚本把所有段安排紧凑了bin文件的实际大小也只是代码加数据的大小而不是你预留分区的大小。Bootloader在0x08008000处等着接收32KB的App你传一个12KB的文件过去位置对不上、长度对不上后面的存储规划全乱。所以最直接、最通用的解法就是拿到编译好的bin再统一做一次“补齐到指定大小”的后处理。2. 填充工具选型脚本、命令、还是第三方工具2.1 主流的几种实现路径我见过不少人用Hex编辑器手工拉末尾操作路径就是打开文件选到末尾用“填充/插入请求”补0xFF保存。这个方法文件小还能忍几十KB、上百KB也还凑合但到了MB级别、每次编译都要操作基本就是灾难。而且手工填充不能保证每次都是同一规则很容易埋坑。常见自动方案有这么几条用Python脚本灵活、跨平台、好改适合集成进编译流程最推荐。用C写一个小工具适合放进上位机软件里比如固件打包工具链、产测软件。用批处理加系统命令比如用copy /b和fsutil拼出一个填充文件再合并能实现但逻辑绕不推荐维护。利用Keil的fromelf工具配合命令行fromelf本身不能直接填充但可以配合SREC/Hex格式转换和第三方脚本完成填充。对比下来写脚本是最省心的。2.2 为什么我选了Python脚本在Windows开发机上Python几乎不用额外装依赖标准库就能搞定文件读写在Linux编译服务器上Python更是标配。而且Python处理二进制文件非常直观读进来是bytes后面追加bytes不需要手动管理缓冲区、内存释放代码量比C少一半还多。有人说Python性能不行、大文件会不会卡。实测下来几十MB的bin一秒钟内都能处理完嵌入式的固件撑死几MB到十几MB这个量级Python完全不是瓶颈。真到了几百MB、几GB的场景再考虑C版本也不迟。后面我也会给出C语言版本因为产测环境不一定有Python解释器尤其是部署到工控机、产线一体机上的时候一个绿色exe反而更省事。2.3 Keil工程里的自动集成一条命令搞定项目用Keil的话大家通常都是用fromelf来生成bin文件User选项卡After Build/Rebuild里填一句fromelf --bin --output.\Output\app.bin .\Output\app.axf问题在于这句话只能生成原始大小的bin没法直接“填充”。我的做法是再跟一条Python调用命令python .\Tools\fill_bin.py .\Output\app.bin .\Output\app_full.bin 0x10000这样编译完自动生成填充后的文件产线直接拿app_full.bin去烧录或打包一条龙。如果编译机没有Python也能把C版本编译成exe放在Tools目录下命令行用法完全一致替换命令就行。关键点在于把填充脚本放进版本管理目录里跟代码一起走不然换了电脑或同事拉代码编译流程就断了。3. 源码实现与完整说明3.1 Python版本源码日常首选#!/usr/bin/env python3 把bin文件填充0xFF到指定大小 用法: python fill_bin.py 输入bin 输出bin 目标大小 目标大小支持十进制(65536)或十六进制(0x10000) import os import sys import shutil def parse_size(s): s s.strip().lower() if s.startswith(0x): return int(s, 16) return int(s) def main(): if len(sys.argv) ! 4: print(用法: python fill_bin.py 输入bin 输出bin 目标大小) print(示例: python fill_bin.py app.bin app_full.bin 0x10000) sys.exit(1) input_file sys.argv[1] output_file sys.argv[2] try: target_size parse_size(sys.argv[3]) except ValueError: print(错误: 目标大小格式不正确) sys.exit(1) if not os.path.isfile(input_file): print(f错误: 输入文件 {input_file} 不存在) sys.exit(1) with open(input_file, rb) as f: data f.read() src_size len(data) if src_size target_size: print(f错误: 输入文件大小 {src_size} 大于目标大小 {target_size}) sys.exit(1) if src_size target_size: print(f源文件大小已等于目标大小, 直接复制) shutil.copy2(input_file, output_file) sys.exit(0) with open(output_file, wb) as f: f.write(data) fill_len target_size - src_size f.write(b\xFF * fill_len) print(f填充完成: {src_size} - {target_size} 字节, 填充 {fill_len} 字节 0xFF) if __name__ __main__: main()这段代码我一步一步说细节。用parse_size统一处理十进制和十六进制是因为实际工作里分区地址、扇区大小大家习惯用十六进制表述比如0x10000比65536直觉得多。用b\xFF * fill_len一次性生成大bytes对象再写入比循环单字节写入快很多2MB的文件前者是毫秒级后者能到秒级。还有一点如果src_size刚好等于target_size我直接用shutil.copy2复制文件避免重新读写文件带来的时间损耗虽然差别细微但这是个好习惯也避免后面处理出错。3.2 C语言版本源码适合集成到上位机C版本适合嵌进产测工具、上位机程序。核心逻辑和Python类似但C需要自己管理内存和缓冲区/* * fillbin.c - 把bin文件填充0xFF到指定大小 * 编译: gcc -o fillbin.exe fillbin.c * 用法: fillbin 输入bin 输出bin 目标大小 * 目标大小支持十进制(65536)或十六进制(0x10000) */ #include stdio.h #include stdlib.h #include string.h #define BUF_SIZE 4096 static unsigned long parse_size(const char *s) { if (s[0] 0 (s[1] x || s[1] X)) { return strtoul(s, NULL, 16); } return strtoul(s, NULL, 10); } int main(int argc, char *argv[]) { if (argc ! 4) { fprintf(stderr, 用法: fillbin 输入bin 输出bin 目标大小\n); fprintf(stderr, 示例: fillbin app.bin app_full.bin 0x10000\n); return 1; } unsigned long target_size parse_size(argv[3]); if (target_size 0) { fprintf(stderr, 错误: 目标大小无效\n); return 1; } FILE *fin fopen(argv[1], rb); if (!fin) { fprintf(stderr, 错误: 无法打开输入文件 %s\n, argv[1]); return 1; } fseek(fin, 0, SEEK_END); long src_size ftell(fin); fseek(fin, 0, SEEK_SET); if (src_size 0) { fclose(fin); fprintf(stderr, 错误: 获取输入文件大小失败\n); return 1; } if ((unsigned long)src_size target_size) { fclose(fin); fprintf(stderr, 错误: 输入文件大小 %ld 大于目标大小 %lu\n, src_size, target_size); return 1; } FILE *fout fopen(argv[2], wb); if (!fout) { fclose(fin); fprintf(stderr, 错误: 无法创建输出文件 %s\n, argv[2]); return 1; } unsigned char buf[BUF_SIZE]; long remain src_size; while (remain 0) { size_t to_read (remain (long)BUF_SIZE) ? (size_t)remain : BUF_SIZE; size_t got fread(buf, 1, to_read, fin); if (got 0) break; fwrite(buf, 1, got, fout); remain - (long)got; } memset(buf, 0xFF, BUF_SIZE); unsigned long fill_len target_size - (unsigned long)src_size; while (fill_len 0) { size_t to_write (fill_len BUF_SIZE) ? fill_len : BUF_SIZE; fwrite(buf, 1, to_write, fout); fill_len - to_write; } fclose(fin); fclose(fout); printf(填充完成: %ld - %lu 字节, 填充 %lu 字节 0xFF\n, src_size, target_size, target_size - (unsigned long)src_size); return 0; }C版本里我用了固定大小的静态缓冲区而不是根据文件大小动态分配。这样不管目标文件多大内存占用都只有4KB不会因为一次性申请大块内存出问题。还有一个细节src_size是longfill_len是unsigned long比较时直接强转(unsigned long)src_size target_size避免符号位带来的坑。3.3 使用方式和验证方法日常调试时我最常用的命令是python fill_bin.py app.bin app_full.bin 0x10000输出结果类似填充完成: 12288 - 65536 字节, 填充 53248 字节 0xFF意思是源文件12KB目标是64KB后面补了52KB的0xFF。拿到输出后一定要验证尾部的填充值。我习惯用xxdxxd app_full.bin | tail -3正常结果应该长这样0000ffd0: ffff ffff ffff ffff ffff ffff ffff ffff ................ 0000ffe0: ffff ffff ffff ffff ffff ffff ffff ffff ................ 0000fff0: ffff ffff ffff ffff ffff ffff ffff ffff ................这一步我每次都会做。有一次脚本被人改错填成了0x00没检查就烧录后面做OTA时程序跑起来各种脏数据排查了半天才发现是填充值错了。从那以后验证尾部成了雷打不动的步骤。4. 边界情况与防呆设计4.1 输入文件大于目标大小代码膨胀后编译出的bin超过了分区预留大小这种情况脚本会直接报错错误: 输入文件大小 70424 大于目标大小 65536看到这个错误就不能继续往下走了。这时候优先检查分区大小规划有没有问题。一般有两个解决思路一是调整链接脚本里的分区大小换更大容量的Flash芯片二是优化代码把只读数据放到外部Flash、压缩大表等。这里有一个很大的坑绝对不能用“截断”来处理。截断后的文件大小虽然对了但内容不完整有效代码被砍掉程序烧进去不是跑不起来就是“看起来能跑、跑起来逻辑错乱”这种问题最难排查。所以我特意让程序报错后直接退出而不是自己裁一刀。4.2 目标大小对齐到扇区很多Flash的扇区是4KB被擦除时最少擦一个扇区。所以填充目标大小一般取扇区的整数倍这样烧录时扇区边界清晰不会出现一个扇区里一半是旧数据一半是新数据的情况。比如STM32F103系列的Flash扇区是1KB起跳有些系列是4KB具体看手册。通用做法是目标大小 扇区大小 × 向上取整(实际代码大小 / 扇区大小)。写链接脚本或者打包脚本时建议先算好这个值再传给fill工具。4.3 填充后文件的完整性验证填充完不是就万事大吉了。我习惯再用Python做一次快速复查确保输出文件大小正确、尾部确实全是0xFFwith open(app_full.bin, rb) as f: data f.read() print(文件大小:, len(data)) # 检查尾部256字节 tail data[-256:] print(尾部全FF:, all(b 0xFF for b in tail))把这个脚本也放进构建流程或者写进CI脚本里能省很多后面肉眼排查的功夫。填充操作本身不难难的是保证每次流程都万无一失。5. 实践中的踩坑记录5.1 烧录后程序“偏移”了有一回同事反馈程序烧进去后中断进不去主流程也是乱的。我原以为是代码问题最后发现是他把bin填充到了错误的目标大小——理论上应该填充到0x08008000之后32KB也就是0x10000他填成了0x8000结果整个bin直接“短了一截”程序被烧到了错误的位置Flash里内容全部移位。这个经历给我的教训是目标大小不是随手拍脑袋填的一定要严格按照链接脚本里的分区定义来。最好在填充脚本里加一个校验跟链接脚本里的常量做比对或者至少写进注释里。5.2 CRC校验一直不过做OTA时固件包头部通常有CRC32、MD5等校验字段。如果bin填充操作放在“计算CRC之前”和“之后”会得出不同结果这个一定要心里有数。我的习惯是先填充0xFF到目标大小再计算CRC最后把CRC字段更新到文件里。顺序反了或者对原始bin做校验都会在接收端报错。这里还要注意有些Bootloader校验时会跳过填充区域有些则会把整个分区都算进去两种方案都得验证一下再做决定。5.3 J-Flash和Cortex M0下载的注意事项用J-Flash烧录填充后的bin下载地址一定要对应上分区起始地址。比如填充是为了在0x08008000放App那J-Flash里的Program Address也得设成0x08008000不然文件内容是连续了位置却偏了。Cortex M0系列芯片只能用SWD接口下载SWD比JTAG引脚少速度通常也慢一些。填充之后文件变大下载时间会变长这是一个正常的代价但要注意下载超时的设置。还有SWD连接不稳定时bin文件越大越容易在中途掉线这时候优先检查接线质量、降低SWD时钟频率而不是怀疑文件有问题。再分享一个J-Flash的小技巧烧录前先读一次Flash内容确认整片都是0xFF这样可以排除芯片里旧数据造成的干扰。如果哪个扇区不是全FF先执行擦除再下载问题往往就消失了。最后说点使用经验这个填充工具虽然小但在我手里的项目里几乎没有一天不用。编译完跑一遍产线拿到手直接烧录省去了所有‘文件大小不对’的扯皮。后来我还把它扩展了一下同一套思路可以填充自定义字节、可以在头部加固定头、也可以在尾部追加CRC一个小小的脚本慢慢变成了我们固件发布流程里最实用的工具之一。如果你在做OTA、Bootloader或者量产烧录建议直接把填充这一步固化到构建脚本里不要每次手动操作这样才算真正省心。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门