feat(pack): v2 双固件 profile 打包 — fw_pack 公共库 + CLI 重构 + GUI 打包弹窗

This commit is contained in:
2026-10-22 00:53:52 +08:00
parent d776979a5a
commit 031ee4efdf
12 changed files with 2345 additions and 292 deletions
+127 -150
View File
@@ -1,34 +1,126 @@
# GD32E230 固件工具
本仓库提供 GD32E230 系列固件的处理与烧录工具:
本仓库提供 GD32E230 系列固件的打包与烧录工具:
- **DAPLink Flash GUI(推荐)**:图形界面烧录工具,位于 `FlashTool/`
- **命令行固件工具**:`gd32e230_firmware_tool.py`(合成 + 补齐二合一)
- **旧版脚本**:`merge_bin.py`、`fill_bin.py`(保留兼容)
- **v2 双固件打包(默认)**:`gd32e230_firmware_tool.py` + `fw_pack.py` 公共库——
布局 profile 化,输入编译好的 bin 自动识别 A/B 槽,产出 OTA 包 / B 底包 /
整片镜像 + MD5 清单,写盘后回读复核;
- **DAPLink Flash GUI(推荐)**:图形界面烧录工具(含 v2 打包弹窗),位于 `FlashTool/`;
- **legacy 单槽模式(过渡兼容)**:`-f` 纯 0xFF 补页 / `-m` 拼 BL+APP+flag;
- **旧版脚本**:`merge_bin.py`、`fill_bin.py`(保留兼容)。
## Flash 布局
## v2 双固件打包
### 布局(默认 profile `ceramixnano_mb_v51`,GD32E230F8/64K)
```text
0x08000000 ┌──────────────────┐
│ Bootloader │ 8KB
│ Bootloader │ 8K 页 0~7
0x08002000 ├──────────────────┤
│ │
│ Application │ 最大 54KB (F8) / 22KB (F6) / 6KB (F4)
│ │
0x0800F800 ├──────────────────┤
│ 校准数据 │ 1KB (预留)
│ FW_A 运行槽 │ 36K 页 8~43 OTA/Ymodem 唯一接收区
0x0800AFFC │ [槽尾锚点]──────│ ← 与 trailer 同值 {页数16|CRC16}
0x0800B000 ├──────────────────┤
│ FW_B 出厂保底槽 │ 19K 页 44~62 固定满槽,产线冻结
0x0800FBF8 │ [B 记录 8B]─────│ ← magic|页数|CRC|2B 保留
0x0800FC00 ├──────────────────┤
│ 0xEEEE 标志 │ 1KB
│ 0xEEEE 标志 │ 1K 页 63 @0x0800FFFC
0x08010000 └──────────────────┘
```
- 空白区域填充 `0xFF`(Flash 擦除默认值)
- 标志位 `0xEEEE` 告诉 Bootloader:APP 有效,直接跳转
- 校准页当前为预留空间
### 计算规则(与 BL dev_fact v5.1 逐字一致)
- **CRC-16/XMODEM**(poly 0x1021、init 0x0000、不反转、无 xorout),
启动自检向量 `"123456789" → 0x31C3`;
- **A 包**:补 `0xFF` 至整 N 页,末 4B 写 trailer `{页数N(低16)|CRC(高16)}`;
CRC 范围 = N 页内容去掉末 4B(含 0xFF 填充)。**整页扩展**:body 恰为整页
倍数、或余量 < 4B → 向后扩 1 页;扩展后须 ≤ 槽容量,否则报错;
- **B 包**:补 `0xFF` 恰好填满槽(19,456B),槽末 8B 写记录
`{magic16=0xC0DE, 页数16, CRC16, 2B 0xFF}`;CRC 范围 = 槽内容去掉末 8B;
**固件 > 槽-8B 直接报错**(固定槽不可扩展);
- **FULL**:BL + A 包 + 槽尾锚点 + 空隙 0xFF + B 满槽 + `0xEEEE` 标志。
### 快速开始
```bash
# 全产物: A 包 → *_UPDATE.bin, B 包 → *_B.bin, 整片 → *_FULL.bin + *.md5.txt
python gd32e230_firmware_tool.py app_a.bin app_b.bin -b bootloader.bin
# 只打 A 包(OTA 升级包)
python gd32e230_firmware_tool.py app_a.bin
# 仅 B 底包(产线备用,不拼 FULL)
python gd32e230_firmware_tool.py app_b.bin
```
输入 bin 从**向量表自动识别** A/B 包(初始 SP 须在 SRAM `[0x20000000,0x20002000]`、
复位向量 Thumb 且落在对应槽区间);也可 `--a`/`--b` 显式指定,与向量表判定冲突时报错。
写盘后回读复核(重算 trailer/记录/锚点一致才报成功),并输出布局摘要 + 19 项字节校验。
### 产物
| 产物 | 用途 |
|---|---|
| `*_UPDATE.bin` | A 包(补页+trailer),Ymodem/OTA 推送 |
| `*_B.bin` | B 底包(满槽+记录),产线单独烧 B 槽备用 |
| `*_FULL.bin` | 整片镜像,`@0x08000000` 整片烧录(底包) |
| `*.md5.txt` | 全部产物 MD5 清单 + 布局摘要 |
FULL 镜像自动命名 `<项目>_FULL_A-V<版本>_B-V<版本>[_日期].bin`(从输入文件名推导)。
### 布局 profile(可复用到任意工程)
内置 `ceramixnano_mb_v51`(默认)与 `f8_single_slot`(无保底槽)。其他工程写一份
JSON 即可(保底槽 8K/28K、无保底槽、BL 更大等都支持),工具做完整性校验
(分区不重叠、页对齐、总和=flash_size):
```json
{
"profile": "ceramixnano_mb_v51",
"chip": "GD32E230F8",
"flash_base": "0x08000000",
"flash_size": "64K",
"page_size": 1024,
"bl": { "addr": "0x08000000", "size": "8K" },
"slot_a": { "addr": "0x08002000", "size": "36K",
"anchor": "slot_end-4", "record": "{pages16@+0, crc16@+2}" },
"slot_b": { "addr": "0x0800B000", "size": "19K", "fixed_slot": true,
"record": "{magic16@+0=0xC0DE, pages16@+2, crc16@+4}",
"record_addr": "slot_end-8" },
"flag": { "page": "last", "addr": "0x0800FFFC", "value": "0xEEEE" },
"crc": "xmodem"
}
```
```bash
python gd32e230_firmware_tool.py app.bin --profile my_layout.json # 自定义布局
python gd32e230_firmware_tool.py app.bin --profile f8_single_slot # 无保底槽
```
### v2 参数
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `bins...` | (可选) | 输入 bin,A/B 包按向量表自动识别 |
| `--a BIN` / `--b BIN` | — | 显式指定 A/B 包(与自动识别冲突时报错) |
| `-b, --bootloader` | — | BL bin(参与 FULL 拼接;缺省仅警告) |
| `--profile` | `ceramixnano_mb_v51` | 内置名或 profile JSON 路径 |
| `-o, --output` | 输入所在目录 | **输出目录**(产物自动命名) |
| `--no-full` | — | 只出 UPDATE/B 包,不拼 FULL |
### 回归测试
```bash
python test_fw_pack.py # 49 项: 规格基准 + 边界 + 篡改检测 + CLI 冒烟
```
覆盖 UPGRADE_PLAN_v2.md 第五节全部基准(CeramixNano 1.0.17/1.0.18/V1.0.12
实测值逐字节复现、恰整页/余量<4 扩页等);真机固件不在本仓库时相关用例自动跳过。
## DAPLink Flash GUI(推荐)
日常烧录优先使用图形界面工具,不需要打开 VSCode,也不需要记忆 OpenOCD 命令。
v2.6.0 起顶栏新增**"打包 (v2)"**弹窗(profile 选择 + BL/A/B 输入 + 输出目录),
与命令行共用 `fw_pack.py`,结果与逐项校验流进主日志。
- **免安装版**:到 [Gitea Releases](https://gitea.hulk.wang/hulk/gd32e230f8_firmware_merge_tool/releases)
下载 `DAPLinkFlash-vX.Y.Z-win64.zip`,解压后进入 `DAPLinkFlash` 文件夹
@@ -36,165 +128,50 @@
(`toolchain/openocd/`),目标机器无需任何环境。
- **源码运行**:`python FlashTool/src/flash_gui.py` 或双击 `FlashTool\start_flash_gui.bat`
(需 Python 3.10+ 与 `pip install customtkinter`;OpenOCD 默认找 `D:/toolchain`)。
- **命令行打包**:`python FlashTool/src/flash_gui.py --pack a.bin b.bin --pack-bl bl.bin -o outdir`
详细说明见 [FlashTool/README.md](FlashTool/README.md)(GUI 截图说明、命令行模式、
构建与发布流程)。下面是旧单文件版说明保留的要点:
详细说明见 [FlashTool/README.md](FlashTool/README.md)。
### 功能
## legacy 单槽模式(过渡兼容)
- 烧录操作:检测设备、整片擦除、烧录固件、读取 Flash、复位芯片、停止
- 固件格式:BIN / HEX / ELF
- BIN:手动填写烧录基地址
- HEX / ELF:自动使用文件内地址
- 芯片型号:GD32E230F4 / F6 / F8 / C8,自动带出 Flash 大小和 target 配置
- 烧录器:DAPLink / CMSIS-DAP、GD-Link、ST-Link、J-Link
- 烧录参数:适配器速度、探针序列号、烧录前擦除、烧录后校验、烧录后复位运行
- 固件处理:补齐(1K / 2K / 4K)、拼接 Bootloader + APP + 有效标志
- 工具链自动定位:内置(exe 旁 `toolchain/openocd`)→ 本机 `D:/toolchain` → PATH
### 配置
- 配置文件:`flash_gui_settings.json`,保存在程序目录(exe 旁 / FlashTool 根),
自动保存与恢复
- 旧版 `config/flash_gui_<主机名>.json` 在新版首次启动时自动迁移
### 使用步骤
1. 连接烧录器(DAPLink / GD-Link 等)到目标板 SWD:SWDIO、SWCLK、GND
2. 启动 GUI,选择芯片型号和烧录器型号
3. 选择固件文件并确认地址:
- 完整镜像(bootloader + app):`0x08000000`
- 单独烧录 APP:`0x08002000`
4. 点击“检测设备”确认连接正常
5. 点击“烧录固件”
> Bootloader 的 Y-Modem 只按 1K 整包接收,OTA 升级包发送前请先用“补齐”功能处理。
## 命令行固件工具
### 快速开始
旧单槽方案(LDC1612 等,布局:BL 8K + APP 54K + 校准页 1K + 标志页 1K),
`-f`/`-m` 触发:
```bash
# 合成完整镜像(整片烧录用)
python gd32e230_firmware_tool.py -m app.bin
# 补齐升级包(Ymodem 升级用)
python gd32e230_firmware_tool.py -f app.bin
python gd32e230_firmware_tool.py -f app.bin # 纯 0xFF 补页, 无 trailer → *_UPDATE.bin
python gd32e230_firmware_tool.py -m app.bin # BL+APP+0xEEEE → *_BL.bin
python gd32e230_firmware_tool.py -m app.bin -s F6 # F6 芯片
python gd32e230_firmware_tool.py -m app.bin -a 0x0800B000 -b bl.bin # FW_B 槽落位
```
### 模式说明
#### `-m` / `--merge` — 合成完整镜像
将 Bootloader + APP + FLAG 合并为一个 Flash 镜像,直接整片烧录。**不补齐**(整片烧录无需考虑页对齐)。
```bash
python gd32e230_firmware_tool.py -m CeramixNano-MB_V1.0.9.bin
```
输出:`CeramixNano-MB_V1.0.9_BL.bin`
#### `-f` / `--fill` — 补齐升级包
将 APP 尾部补 `0xFF` 到 1KB 页边界,供 Bootloader 的 Ymodem 接收端使用。补齐后自动检查是否超过芯片 APP 容量上限。
```bash
python gd32e230_firmware_tool.py -f CeramixNano-MB_V1.0.9.bin
```
输出:`CeramixNano-MB_V1.0.9_UPDATE.bin`
### 参数
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `app` | (必填) | APP 的 .bin 文件 |
| `-m, --merge` | — | 合成完整 Flash 镜像 |
| `-f, --fill` | — | 补齐到整页边界 |
| `-m, --merge` | — | 合成完整 Flash 镜像(不补齐) |
| `-f, --fill` | — | 补齐到整页边界(纯 0xFF) |
| `-s, --flash-size` | `F8` | 芯片型号:`F4`(16K) `F6`(32K) `F8`(64K) |
| `-b, --bootloader` | `gd32e230f8_bootloader_hulk.bin` | Bootloader .bin 路径 |
| `-o, --output` | 自动生成 | 输出文件路径 |
| `-h, --help` | — | 查看帮助 |
| `-b, --bootloader` | 内置 `gd32e230f8_bootloader_hulk.bin` | Bootloader .bin 路径 |
| `-o, --output` | 自动生成 | **输出文件**路径 |
| `-a, --app-addr` | `0x08002000` | APP 落位地址(1K 页对齐) |
> **`-m` 和 `-f` 互斥**,每次只能选一种模式。
### 使用示例
```bash
# F8 合成镜像(默认)
python gd32e230_firmware_tool.py -m CeramixNano-MB_V1.0.9.bin
# F6 合成镜像
python gd32e230_firmware_tool.py -m app.bin -s F6
# F8 补齐升级包(默认)
python gd32e230_firmware_tool.py -f app.bin
# F4 补齐升级包
python gd32e230_firmware_tool.py -f app.bin -s F4
# 自定义 Bootloader 和输出路径
python gd32e230_firmware_tool.py -m app.bin -b my_bootloader.bin -o release.bin
```
### 输出示例
**merge 模式:**
```text
芯片: GD32E230F8 (64KB)
合成完成: CeramixNano-MB_V1.0.9_BL.bin
芯片 : 64KB
Bootloader : 3272B @ 0x08000000
Application: 25600B @ 0x08002000
Flag : 0xEEEE @ 0x0800FFFC
校准预留 : 1KB @ 0x0800F800
APP 剩余 : 29696B
总大小 : 65536B (64KB)
```
**fill 模式:**
```text
芯片: GD32E230F8 (64KB)
补齐完成: app_UPDATE.bin
原始大小: 25000B
页大小 : 1024B (1KB)
补齐字节: 600B
输出大小: 25600B
APP 上限: 55296B (54KB) → 剩余 29696B
```
**超限拦截:**
```text
错误:APP (60000B / 58.6KB) 超过最大容量 (55296B / 54KB)
芯片 64KB: 64KB - 8KB(BL) - 1KB(校准) - 1KB(Flag) = 54KB
```
> `-m` 与 `-f` 互斥;新工程请用 v2 模式(不带 `-f/-m`)。
### 旧版脚本
`merge_bin.py` / `fill_bin.py` 为旧版命令行脚本,功能已合并进 `gd32e230_firmware_tool.py`,历史说明见 [README_old.md](README_old.md)。
`merge_bin.py` / `fill_bin.py` 为旧版命令行脚本,功能已合并进 `gd32e230_firmware_tool.py`,
历史说明见 [README_old.md](README_old.md)。v5 格式临时脚本 `make_v5_update.py`
已于 v2 落地后删除(能力并入 `fw_pack.py`)。
### 烧录
```bash
# OpenOCD
openocd -f interface/cmsis-dap.cfg -f target/gd32e23x.cfg \
-c "init; reset halt; flash write_image erase merged.bin 0x08000000; reset; exit"
-c "init; reset halt; flash write_image erase FULL.bin 0x08000000; reset; exit"
# J-Link
JFlash -openprj project.jflash -open merged.bin,0x08000000 -auto -exit
JFlash -openprj project.jflash -open FULL.bin,0x08000000 -auto -exit
```
### 安全校验
- Bootloader 体积不超过 APP 起始偏移(8KB)
- APP 大小不超过芯片可用空间
- fill 模式补齐后再次校验容量上限
- 芯片不支持自动检测,需通过 `-s` 手动指定
## License
MIT