从编辑 JSON 到 PBP 运行时解析 的顺序,把 pbp_cfg.bin 逐步说清楚(工具链以 tools/scripts/mk_private_resource.py 为准)。
总览:pbp_cfg.bin 是什么
pbp_cfg.bin 不是可执行代码,而是给 闭源 d13x.pbp 读的 私有配置容器 :一串按类型拼接的 数据段(section),每段格式统一为:
text
+0x00 u32 data_type // 段类型魔数,如 0x41490006 = PSRAM
+0x04 u32 data_len // 本段 payload 长度(不含这 8 字节头)
+0x08 ... payload // 具体结构,依类型而定
...
最后一段: data_type = 0x4149FFFF, data_len = 0 (结束标记)
PBP 启动时由 BROM/镜像头得知 private 数据在 RAM 中的地址 (常通过寄存器 a1 传给后续阶段),再按 data_type 依次解析各段。
Step 0:涉及哪些文件
| 文件 | 角色 |
|---|---|
target/<chip>/<board>/pack/pbp_cfg.json |
人工维护:PSRAM 参数、UART、JTAG、升级脚等 |
output/.../partition.json |
编译时自动生成 (来自 image_cfg.json) |
tools/scripts/mk_private_resource.py |
生成 pbp_cfg.bin |
tools/scripts/mk_prebootprog.py |
可选:给 private2 包 PBP2 头(多数板子用 private 不会改 bin) |
tools/scripts/mk_image.py |
把 pbp_cfg.bin 打进 pbp_ext.aic / loader.aic |
d13x.pbp |
PBP 程序本体 (与 pbp_cfg.bin 分开) |
以你打开的 demo68-nor 为例:image_cfg.json 里 "private": "pbp_cfg.bin" 与 "pbp": "d13x.pbp" 成对出现。
Step 1:何时、谁触发编译
编 RT-Thread 应用 (非 baremetal bootloader)时,aic_build.py 在打包镜像前执行:
text
1) gen_partition_table.py
image_cfg.json → output/.../partition.json
+ partition_table.h
2) mk_private_resource.py
-l pbp_cfg.json,partition.json → pbp_cfg.bin
3) (若 image_cfg 含 private2)mk_prebootprog.py -m PDAT ...
多数 d13x 板只用 private,此步跳过
4) mk_image.py
生成 pbp_ext.aic、loader.aic、bootloader.aic ...
对应命令等价于:
bash
python3 tools/scripts/mk_private_resource.py \
-v -l target/d13x/demo68-nor/pack/pbp_cfg.json,output/.../partition.json \
-o output/.../pbp_cfg.bin
-l 表示 多个 JSON 合并(后写的 key 会覆盖先前的同名 key)。
Step 2:解析 pbp_cfg.json
parse_private_data_cfg() 做三件事:
- 读文件,去掉
//注释行或行尾注释 - 去掉 JSON 尾逗号(
,}→}) json.loads(..., object_pairs_hook=OrderedDict)保持字段顺序
合并后顶层 key 典型顺序(demo68-nor / demo88-nand):
text
"psram" ← 来自 pbp_cfg.json
"system" ← 来自 pbp_cfg.json
"partitions" ← 来自 partition.json(编译生成)
(若还有 dram 段,则是 D21x 等 DDR 方案,d13x 用 psram。)
Step 3:partition.json 如何进 bin
gen_partition_table.py 根据 image_cfg.json 里 spi-nor / spi-nand 的 partitions 生成,例如 demo68-nor:
json
{
"partitions": {
"mtd": "spi0:512k(spl),128k(env),...(os),...(rodata),...(data)",
"type": ["mtd"]
}
}
gen_partition_table() → gen_bytes_of_part_str() 把分区表压成 ASCII 字符串:
text
spi0:512k(spl),128k(env),2m(os),12m(rodata),1m(data)
再写入 PARTITION 段 (见 Step 4.6)。
这样 PBP / Bootloader 早期就能按 mtd 字符串 访问 Flash,而不依赖 OS 已启动。
Step 4:按顺序生成各数据段(核心)
gen_private_data(cfg) 按 顶层 key 顺序 调用各 gen_*,最后 gen_end_flag()。
段类型常量(小端 u32):
| 宏 | 值 | JSON 顶层/子项 |
|---|---|---|
DATA_SECT_TYPE_DRAM |
0x41490001 |
"dram"(D21x DDR) |
DATA_SECT_TYPE_SYS_UART |
0x41490002 |
system.uart |
DATA_SECT_TYPE_SYS_JTAG |
0x41490003 |
system.jtag |
DATA_SECT_TYPE_SYS_UPGMODE |
0x41490004 |
system.upgmode |
DATA_SECT_TYPE_PARTITION |
0x41490005 |
"partitions" |
DATA_SECT_TYPE_PSRAM |
0x41490006 |
"psram" |
DATA_SECT_TYPE_SYS_REGCFG |
0x41490007 |
system.regcfg |
DATA_SECT_TYPE_END |
0x4149FFFF |
固定结尾 |
下面按 d13x 常见顺序 说明(psram → system → partitions)。
Step 4.1 --- PSRAM 段(0x41490006)
JSON :pbp_cfg.json → "psram" → "cfg0", "cfg1", ...(多套颗粒,PBP 用 getid 匹配)
二进制布局:
text
u32 0x41490006 // type
u32 payload_len // 下面 payload 字节数
u32 entry_cnt // cfg 个数(如 4)
重复 entry_cnt 次 {
struct psram { // 每套 42 个 u32 = 168 字节
common[15] // clock, cs0/1_pins, xspi_*, psram_cfg*, iocfg*
reset[2] // proto, buf
getid[3] // proto, id, buf
init[8] // proto0..3, buf0..3
xip_cfg[4] // wr/rd proto/buf
backup[10] // buf0..9(training 等)
}
}
以 cfg0(APS3208K 8M)为例,JSON 字段如何进 bin:
| JSON 路径 | 含义 | 写入字段 |
|---|---|---|
common.clock |
XSPI/PSRAM 时钟 Hz | 198000000 |
common.xspi_ctl/tcr/cfg/ldo |
控制器寄存器初值 | 十六进制串 → u32 |
common.psram_cfg0/1 |
PSRAM 控制器配置 | u32 |
common.xspi_cs*_iocfg* |
引脚驱动/时序 | u32 |
reset.proto/buf |
复位序列 | SPI 协议字 + 数据 |
getid.proto/id/buf |
读 ID,与硬件颗粒比对 | id: 0x80c980c9 等 |
init.proto0..3 |
初始化命令序列 | 注释里 cmd/dummy/addr/len |
xip_cfg.* |
XIP 读写协议 | 可选执行态 |
backup.buf0..9 |
Training 图案/ hold 参数 | 如 0xAA55AA55 |
缺省字段在工具里多为 0xFFFFFFFF(见 get_bytes_by_str(..., "0xFFFFFFFF"))。
PBP 运行时逻辑(概念):
是
否
读 PSRAM 段
entry_cnt = N
对 cfg0..cfgN-1 发 getid
ID 匹配?
reset → init → xip_cfg → backup training
PSRAM 可用 @ 0x40000000 一带
Step 4.2 --- SYSTEM:upgmode(0x41490004)
JSON :system.upgmode
Payload(6×u32,共 24 字节 + 8 字节头):
| 字段 | demo88-nand 示例 | 作用 |
|---|---|---|
upgmode_pin_cfg_reg/val |
0x18700080 / 0x10321 |
配置 PA0 等为升级检测脚 |
upgmode_pin_input_reg |
0x18700000 |
GPIO 输入寄存器 |
upgmode_pin_input_msk/val |
0x1 / 0x0 |
检测电平 |
upgmode_pin_pullup_dly |
500 |
上拉稳定延时 µs |
upgmode_pin_cfg_reg = "0" → 关闭 PBP 内 bootpin 检测(JSON 注释)。
Step 4.3 --- SYSTEM:uart(0x41490002)
JSON :system.uart → 可多个子项(通常只有 "main")
每个 UART 配置 5×u32:
| 字段 | 含义 |
|---|---|
uart_id |
0=UART0, 1=UART1, ... |
uart_tx_pin_cfg_reg/val |
Pinmux 寄存器地址与写入值 |
uart_rx_pin_cfg_reg/val |
RX 引脚 |
- demo88-nand :UART0,PA0/PA1(
0x335)→ PBP 阶段就能打 log - demo68-nor (你打开的板级):UART1 ,PA2/PA3(
0x325),UART0 整段被注释掉
删掉整个 system.uart 块 → PBP 内无串口 log(JSON 注释说明)。
Step 4.4 --- SYSTEM:jtag(0x41490003)
JSON :system.jtag
Payload 结构:
text
u32 jtag_only // 1 = PSRAM/init 后在 PBP 内停住等 JTAG
对每个子项 "main" 等(OrderedDict):
u32 jtag_id
u32 jtag_do/di/ms/ck 的 pin_cfg_reg + pin_cfg_val (各 2 个 u32)
- demo88-nand:PA8--PA11 四线 JTAG
- demo68-nor :只配了 ms + ck(PA10/PA11),do/di 被注释------与硬件走线一致即可
jtag_only: "1" 用于极早期调试:init 完成后 不继续跳 loader,先接调试器。
Step 4.5 --- SYSTEM:regcfg(0x41490007,可选)
JSON :system.regcfg(很多板子没有)
text
u32 entry_cnt
每条约:
u32 reg // 寄存器地址
u32 val // 写入值
u32 dly // 延时
用于 PBP 里 批量写 SoC 寄存器(上电时序、电源域等)。
Step 4.6 --- PARTITION 段(0x41490005)
JSON :合并进来的 partition.json
Payload:
text
ASCII 字符串: "spi0:512k(spl),128k(env),...(os),..."
+ 0~3 字节 padding 到 4 字节对齐
gen_bytes_of_part_str() 规则:
text
对 type 数组里每种类型(如 mtd、nftl):
"{type}={layout};"
例如:
mtd=spi0:512k(spl),128k(env),2m(os),12m(rodata),1m(data);
nftl=data:40m(data); // 若有 NFTL 分区
PBP / tinySPL 用该字符串解析 spl/os/rodata 等偏移,与 image_cfg.json 分区定义一致。
Step 4.7 --- 结束段(0x4149FFFF)
text
u32 0x4149FFFF
u32 0x00000000
d13x.pbp 解析到此停止。
Step 5:写出 pbp_cfg.bin
python
data = gen_private_data(cfg)
open("pbp_cfg.bin", "wb").write(data)
无额外文件头、无对齐填充(整文件就是各段首尾相接)。
加 -v 可在构建 log 里看详细过程。
Step 6:可选后处理(private2 / PDAT)
若 image_cfg.json 某处使用 "private2"(安全启动场景),会再跑:
bash
mk_prebootprog.py -m PDAT -i pbp_cfg.bin -o pbp_cfg.bin
给 整份 private 数据 加 PBP2 魔数头、校验和、可选 AES 加密。
demo68-nor / demo88-nand 通常只有 "private": "pbp_cfg.bin" ,因此 Step 5 的输出即为最终 bin。
Step 7:打进启动镜像(与 d13x.pbp 的关系)
mk_image.py 制作 pbp_ext.aic 时资源区大致为:
text
[AIC 镜像头 256B]
[resource: d13x.pbp] ← 可执行 PBP,带 PBP2 头
[resource: pbp_cfg.bin] ← 本文件,原始 section 流
[可选: loader / signature ...]
bootloader.aic = pbp_ext.aic + loader.aic(concatenate)。
运行时:
- BROM 加载
bootloader.aic到 RAM - 执行
d13x.pbp,根据镜像头里的 private 偏移/长度 找到pbp_cfg.bin - 按 Step 4 顺序解析:PSRAM → upgmode/uart/jtag → partitions → END
- PBP 完成后跳转到
bootloader.bin(tinySPL) - SPL 的
save_boot_params仍可通过 a0/a1 使用 BROM 传下来的参数(boot_param.c)
Step 8:整文件逻辑布局示意(d13x demo68-nor 典型)
text
偏移 内容
------ ------------------------------------------
0x0000 [PSRAM] type=0x41490006, len=..., cnt=4, cfg0..cfg3 × 168B
0x???? [UPGMODE] type=0x41490004, len=24, 6×u32
0x???? [UART] type=0x41490002, len=20, uart1@PA2/PA3
0x???? [JTAG] type=0x41490003, jtag_only + partial pins
0x???? [PARTITION] type=0x41490005, "spi0:512k(spl),..."
0x???? [END] type=0x4149FFFF, len=0
精确偏移需对生成的 pbp_cfg.bin 做 hexdump;段长由 entry_cnt 和字符串长度决定。
Step 9:改配置时改哪里(实操清单)
| 目的 | 改什么 |
|---|---|
| 换 PSRAM 颗粒 | pbp_cfg.json → 新增/改 getid.id + init 序列 |
| PBP 串口无输出 | 打开 system.uart,对齐硬件 UART 与引脚 |
| 换调试串口引脚 | 改 uart_*_pin_cfg_reg/val(寄存器地址查 TRM / pinmux) |
| 误进 USB 升级 | 查 upgmode 或设 upgmode_pin_cfg_reg: "0" |
| 早期 JTAG | jtag_only: "1" + system.jtag 引脚 |
| 分区与烧录不一致 | 改 image_cfg.json 分区 → 重编自动生成 partition.json 段 |
| 仅改 Boot 不改编应用 | 改 pack/pbp_cfg.json 后需 重编应用工程 才会重新生成 bin 并打包 |
Step 10:与 ddr_init.bin 的对比(避免混用)
| 项目 | DDR 方案 (D21x) | PSRAM 方案 (d13x) |
|---|---|---|
| JSON | ddr_init.json |
pbp_cfg.json |
| 输出 bin | ddr_init.bin |
pbp_cfg.bin |
| 段类型 | 0x41490001 DRAM |
0x41490006 PSRAM |
| 工具 | 同一 mk_private_resource.py |
同上 |
小结
pbp_cfg.bin 的生成可以记成 10 步:
- 维护
pbp_cfg.json(PSRAM + system) - 编应用时由
image_cfg.json生成partition.json mk_private_resource.py合并两个 JSON- 按序序列化各 DATA_SECT_TYPE_* 段(小端 u32)
- 写裸 bin
- (多数情况)不做 PDAT 封装
mk_image与d13x.pbp一起打进bootloader.aic- 上电后 PBP 解析 bin,完成 PSRAM/引脚/分区表
- 再进 tinySPL
- 调试 PBP 阶段主要看 uart 段 和 psram getid