编译后处理与语音资源打包
本文档介绍 AD24N SDK(杰理科技 GP-MCU 平台)中 sdk/app/post_build 目录下的编译后处理(post-build)流水线与语音资源打包机制:从 ELF 固件分段提取、反汇编/符号输出、多段 bin 合并,到调用 isd_download.exe 将应用与各语音资源目录烧入 NOR Flash,以及最终生成 UFW 升级固件的完整流程。
Purpose and Scope
本页覆盖以下内容:
download.bat后处理脚本的完整执行流程(ELF → 段提取 → 合并 → 下载 → UFW 生成)download_bat.c脚本源码模板及其对外置 Flash 烧写(-wflash)参数的注释约定- 语音资源目录(
dir_a、dir_song、dir_eng、dir_poetry、dir_story、dir_bin_f1x、dir_midi、dir_notice)的打包与烧录方式 - 配套工具(
isd_download.exe、ufw_maker.exe、flash_wp_tool.exe、fw_add.exe)的职责 - 提示音生成工具(
sdk/make_prompt.bat、sdk/tools/make_prompt.bat)与库合并工具(sdk/tools/utils/do_merge_libs.bat)的定位
本页不涉及:具体音频格式转换算法、uboot 引导流程(uboot.boot 的生成)、Flash 分区布局设计、以及各个语音目录内音频素材的采集/录制流程——这些属于独立话题,请参考对应目录下的工具与文档。
Overview
AD24N SDK 的构建产物是 sdk.elf(LLVM/pi32 工具链链接产物)。由于 MCU 固件需要按段(section)分别处理——代码段直接可执行、数据段需初始化、调试数据与低功耗覆盖层各有用途——SDK 提供了一组 llvm-* 工具与批处理脚本构成的"编译后处理"流水线,将 ELF 拆分为多个二进制段,再拼接为最终烧录镜像 app.bin。
语音资源的打包则采用"目录即分区"的约定:isd_download.exe 的 -res 参数接收一组目录名(dir_a dir_song dir_eng ...),工具会遍历这些目录,将其中文件组织成 Flash 上的资源分区。此外脚本还演示了三种典型的量产/升级路径:
- NOR Flash 直接烧录(
-tonorflash):通过 UART 将 uboot、app 与语音资源整体烧入 NOR Flash; - 外置 Flash 二次烧写(
-wflash,注释中说明):将单个资源文件烧到外部 SPI Flash 的指定地址; - UFW 升级固件生成(
ufw_maker.exe -fw_to_ufw):将jl_isd.fw转换为可 OTA/升级的update.ufw。
这套流程的核心设计意图是解耦:编译阶段只负责产出带完整调试信息的 ELF;后处理脚本负责"段提取 → 合并 → 烧录"等与目标硬件/量产强相关的操作,使得同一份 ELF 可以按不同量产需求(内嵌资源、外置 Flash、升级包)重复使用。
Architecture
下图展示编译后处理与语音资源打包的整体架构与数据流:
flowchart TD
subgraph sg_Build["编译阶段 (Toolchain)"]
LD["llvm-ld / 链接器"] --> ELF["sdk.elf"]
end
subgraph sg_PostBuild["post-build 后处理 (download.bat)"]
OBJDUMP["llvm-objdump.exe"]
OBJCOPY["llvm-objcopy.exe"]
OBJSIZE["llvm-objsizedump.exe"]
ELF --> OBJDUMP
ELF --> OBJCOPY
ELF --> OBJSIZE
OBJDUMP --> LST["sdk.lst 反汇编"]
OBJDUMP --> SYM["sdk.symbol.txt 符号表"]
OBJSIZE --> TXT["sdk.txt 段大小/内存占用"]
OBJCOPY -->|"段提取 .app_code/.data/.debug_data/.lowpower_overlay"| BINS["sdk.bin / data.bin / debug_data.bin / lowpower_overlay.bin"]
BINS -->|"copy /b 拼接"| APP["app.bin"]
end
subgraph sg_Voice["语音资源打包 (voice_enhanced)"]
APP --> APP2["voice_enhanced/app.bin"]
DIRS["dir_a dir_song dir_eng dir_poetry<br/>dir_story dir_bin_f1x dir_midi midi_cfg dir_notice"]
ISD["isd_download.exe"]
APP2 --> ISD
DIRS --> ISD
ISD -->|"-tonorflash 烧录"| FLASH[(NOR Flash)]
UFW["ufw_maker.exe"] -->|"-fw_to_ufw jl_isd.fw"| UFWOUT["update.ufw 升级固件"]
end
subgraph sg_ExtFlash["外置 Flash 烧写 (-wflash 注释约定)"]
WFLASH["-wflash dir_song 0 [PA05_1B_NULL]"]
WFLASH --> SPI["SPI1 端口 A/B/C/D"]
end
ISD -.->|"可选"| WFLASH
架构要点说明:
- 编译与后处理分离:
sdk.elf是唯一输入,后处理脚本只依赖C:\JL\pi32\bin下的 LLVM 工具,不依赖编译器的链接细节; - 段(section)粒度处理:
.app_code为可执行代码段,.data为需初始化的数据段,.debug_data为调试数据,.lowpower_overlay为低功耗覆盖层——四者通过copy /b按固定顺序拼接成app.bin,顺序即地址布局,因此不得随意调整; - 资源目录即分区:
-res后的目录顺序决定了语音资源在 NOR Flash 中的排列顺序,midi_cfg是 MIDI 配置文件目录; - 量产可选择性:
-wflash(外置 Flash)与 UFW 升级包是两条独立分支,与主烧录流程解耦,由注释中的示例给出用法。
编译后处理流水线详解
后处理入口是 download.bat。脚本首先 cd /d %~dp0 切换到自身所在目录,确保所有相对路径(ELF、工具、资源目录)都相对于脚本位置解析——这是批处理脚本最常见的路径陷阱,先切目录是避免"当前工作目录不确定"问题的标准做法。
工具链定位与环境探测
set OBJDUMP=C:\JL\pi32\bin\llvm-objdump.exe
set OBJCOPY=C:\JL\pi32\bin\llvm-objcopy.exe
set OBJSIZEDUMP=C:\JL\pi32\bin\llvm-objsizedump.exe
if exist %OBJDUMP% (
set NAME=sdk
)
Source: download.bat
三组 LLVM 工具路径被硬编码为 C:\JL\pi32\bin,这是杰理 pi32 工具链的标准安装位置。if exist %OBJDUMP% 探测工具是否存在,存在则将固件名设为 sdk(对应 sdk.elf)。这个探测的目的是:只有工具链可用时才执行后处理,避免在纯文档/配置环境下误触发烧录。
ELF 段提取与调试产物生成
if exist %NAME%.elf (
%OBJDUMP% -section-headers %NAME%.elf
%OBJDUMP% -d -print-imm-hex -print-dbg %NAME%.elf > %NAME%.lst
%OBJSIZEDUMP% -lite -skip-zero -enable-dbg-info %NAME%.elf > %NAME%.txt
%OBJCOPY% -O binary -j .app_code %NAME%.elf %NAME%.bin
%OBJCOPY% -O binary -j .data %NAME%.elf data.bin
%OBJCOPY% -O binary -j .debug_data %NAME%.elf debug_data.bin
%OBJCOPY% -O binary -j .lowpower_overlay %NAME%.elf lowpower_overlay.bin
%OBJDUMP% -section-headers %NAME%.elf
%OBJDUMP% -t %NAME%.elf > %NAME%.symbol.txt
Source: download.bat
各步骤职责:
| 命令 | 产物 | 用途 |
|---|---|---|
llvm-objdump -section-headers | 终端输出 | 打印段头,便于人工核对段布局 |
llvm-objdump -d -print-imm-hex -print-dbg | sdk.lst | 反汇编列表(立即数以十六进制显示、含调试信息),用于崩溃定位与代码审查 |
llvm-objsizedump -lite -skip-zero -enable-dbg-info | sdk.txt | 各段大小与内存占用统计,用于 RAM/ROM 预算检查 |
llvm-objcopy -O binary -j .app_code | sdk.bin | 可执行代码段二进制 |
llvm-objcopy -O binary -j .data | data.bin | 初始化数据段二进制 |
llvm-objcopy -O binary -j .debug_data | debug_data.bin | 调试数据段二进制 |
llvm-objcopy -O binary -j .lowpower_overlay | lowpower_overlay.bin | 低功耗覆盖层二进制 |
llvm-objdump -t | sdk.symbol.txt | 完整符号表,用于地址反查(函数/变量↔地址) |
注意 download_bat.c 模板中还有一行被注释掉的 cache_ram 段提取(/* %OBJCOPY% -O binary -j .cache_ram ... */),说明部分变体固件还会包含 .cache_ram 段,当前 SH58 工程未启用——模板与实机脚本的差异保留了可追溯性。
app.bin 多段合并
copy /b %NAME%.bin+data.bin+debug_data.bin+lowpower_overlay.bin app.bin
copy app.bin voice_enhanced/app.bin
Source: download.bat
copy /b 以二进制模式将四个段文件按固定顺序拼接为 app.bin。设计意图:
- 段顺序(
.app_code→.data→.debug_data→.lowpower_overlay)与链接脚本中的地址布局一致,isd_download.exe会按-app app.bin 0x40000指定的起始地址(0x40000)将整块写入 Flash; - 如果某个段在链接时为空,
objcopy仍会输出一个空文件,copy /b拼接后内容不变——这是该方案在"段可选"场景下仍能保持地址对齐的原因; - 紧接着
copy app.bin voice_enhanced/app.bin将合并结果复制到语音打包目录,供下一步烧录使用,保持工作目录整洁。
语音资源打包与烧录(isd_download.exe)
烧录命令解析
cd voice_enhanced
isd_download.exe -tonorflash -dev sh58 -boot 0x304000 -div8 -wait 300 -uboot uboot.boot -app app.bin 0x40000 -res dir_a dir_song dir_eng dir_poetry dir_story dir_bin_f1x dir_midi midi_cfg dir_notice
Source: download.bat
参数含义:
| 参数 | 值 | 说明 |
|---|---|---|
-tonorflash | — | 烧录目标为 NOR Flash(区别于外置 Flash / RAM 下载) |
-dev | sh58 | 目标芯片型号 SH58 |
-boot | 0x304000 | uboot 引导区在 Flash 中的地址 |
-div8 | — | 分频系数 8,UART 下载时序参数 |
-wait | 300 | 等待/超时参数(毫秒级) |
-uboot | uboot.boot | 引导程序镜像文件 |
-app | app.bin 0x40000 | 应用镜像及其烧录起始地址 |
-res | 目录列表 | 语音资源目录,按序打包为 Flash 资源分区 |
download_bat.c 模板中还保留了 -format vm / -format all(格式化虚拟区/整片)与 -reboot 500(烧录后延迟 500ms 复位)的注释示例,量产时可取消注释启用。
语音资源目录的组织约定
voice_enhanced/ 下每个 dir_* 目录对应一类语音资源(目录结构):
| 目录 | 资源类型 |
|---|---|
dir_a | 通用提示音(Alert/Tone) |
dir_song | 歌曲/旋律 |
dir_eng | 英文语音 |
dir_poetry | 诗词资源 |
dir_story | 故事资源 |
dir_bin_f1x | 二进制资源(F1x 系列) |
dir_midi + midi_cfg | MIDI 播放资源及其配置文件 |
dir_notice | 通知/公告语音 |
dir_ex_flash | 外置 Flash 相关资源(与 -wflash 配合) |
设计意图:目录名即分区标识。isd_download.exe 遍历 -res 列表时,按目录名生成资源表并写入 Flash 的资源区;应用固件通过资源名(目录名)在运行时索引这些分区。因此新增语音资源 = 新增目录或文件 + 更新 -res 参数,无需改动固件代码的 Flash 地址宏。
UFW 升级固件生成
ufw_maker.exe -fw_to_ufw jl_isd.fw
copy jl_isd.ufw update.ufw
del jl_isd.ufw
ping /n 2 127.1>null
IF EXIST null del null
Source: download.bat
ufw_maker.exe 将杰理固件格式 jl_isd.fw(由 isd_download.exe 在烧录过程中生成)转换为可分发升级的 UFW 格式,并统一命名为 update.ufw(OTA 升级约定的文件名)。末尾的 ping /n 2 127.1>null 是批处理中常见的延时技巧(约 1 秒),用于等待工具刷新文件句柄后清理临时文件。
Core Flow — 从构建产物到量产固件的完整时序
sequenceDiagram
participant Dev as 开发者
participant Elf as sdk.elf
participant Obj as llvm-objcopy/objdump
participant Bat as download.bat
participant Isd as isd_download.exe
participant Flash as NOR Flash
participant Ufw as ufw_maker.exe
Dev->>Bat: 双击/CI 执行 download.bat
Bat->>Elf: 探测工具链 & sdk.elf 存在?
alt 工具链或 ELF 缺失
Bat-->>Dev: 跳过后处理(静默退出)
else 正常路径
Bat->>Obj: 反汇编 + 段大小统计 + 符号表
Obj-->>Bat: sdk.lst / sdk.txt / sdk.symbol.txt
Bat->>Obj: 提取 .app_code/.data/.debug_data/.lowpower_overlay
Obj-->>Bat: 4 个段 bin
Bat->>Bat: copy /b 拼接为 app.bin
Bat->>Isd: 进入 voice_enhanced 并调用 -tonorflash 烧录
Isd->>Flash: 写 uboot@0x304000 / app@0x40000 / 语音资源分区
Isd-->>Bat: 生成 jl_isd.fw
Bat->>Ufw: -fw_to_ufw jl_isd.fw
Ufw-->>Bat: update.ufw(升级固件)
Bat-->>Dev: 流程结束(可选 -reboot 复位)
end
时序要点:后处理脚本是"顺序执行"的批处理,任何一步失败(工具缺失、ELF 不存在、烧录失败)都会中断后续步骤——这既是简单可靠的设计,也意味着量产环境必须保证工具路径与串口/芯片连接就绪。
外置 Flash 烧写(-wflash)约定
download_bat.c 源码模板中对 -wflash 命令有详细的注释约定(见 download_bat.c):
@rem -wflash dir_song 0 [PA05_1B_NULL]
@rem // dir_song : 要烧写的文件名(文件需在download.bat文件夹下)
@rem // 0 : 文件烧录到外置flash的起始地址
@rem // [PA05_1B_NULL]: PA05:外置flash片选引脚(注意:不能选USBDP/USBDM)
@rem // 1B :spi1 ,B端口
@rem // NULL: power_io & spi1_data_width,power_io连接到外置flash vcc引脚 可控制flash电源;spi1_data_width:0:单线;1:双向
Source: download_bat.c
该命令的三段式参数设计:
- 文件名:要烧写的资源文件(如
dir_song目录下的文件),须位于download.bat所在目录; - 起始地址:文件在外置 Flash 中的偏移;
- 引脚描述串
[PA05_1B_NULL]:由三部分组成——- 片选引脚(如
PA05,禁止使用 USBDP/USBDM,否则影响 USB 功能); - SPI 端口(
1B= SPI1 B 端口;1A= SPI1 A 端口); power_io与spi1_data_width:power_io接外置 Flash VCC 可控制供电;spi1_data_width为 0 表示单线模式、1 表示双向模式。
- 片选引脚(如
注释还给出了 SPI 端口与引脚映射(download_bat.c):
| SPI 端口 | CLK / DO / DI 引脚 |
|---|---|
| A | PB00 / PB01 / PB02 |
| B | PA14 / PA15 / PA13 |
| C | PA06 / PA07 / PA08 |
| D | PB08 / PB09 / PB07 |
设计意图:外置 Flash 烧写与 NOR Flash 主烧录走的是同一条 SPI 下载链路(isd_download.exe),因此引脚描述串必须精确匹配硬件连接。power_io 支持意味着可以完全控制外置 Flash 的电源时序,这是低功耗产品(语音玩具等)的常见需求——掉电时切断 Flash 电源以降低静态功耗。
配套工具与目录说明
voice_enhanced/ 目录还包含以下工具/文件:
| 文件 | 作用 |
|---|---|
fw_add.exe | 固件附加/合并工具(用于在固件中附加资源或校验信息) |
flash_wp_tool.exe | Flash 写保护工具(配合 flash_write_protect/ 使用) |
flash_write_protect/flash_list_to_bin_v3.bat | 将 Flash 列表(0xC8671A_v3.xlsx、0xEF4017_v3.xlsx)转换为写保护参数 bin 的脚本,输出 inside_flash/flash_params_v3.bin |
flash_write_protect/inside_flash/flash_params_v3.bin | 写保护参数文件,量产时烧入芯片用于保护关键 Flash 区域 |
app_ld.c | 应用链接配置(段布局相关) |
maskrom_stubs.ld | MaskROM 桩符号链接脚本 |
此外,SDK 根目录还提供提示音生成工具 sdk/make_prompt.bat(及 sdk/tools/make_prompt.bat)——用于将音频素材批量转换为固件可用的提示音资源,是 dir_notice/dir_a 等目录的素材来源;sdk/tools/utils/do_merge_libs.bat 用于合并静态库,属于构建期工具而非后处理范畴。
使用示例
示例 1:标准量产烧录流程(download.bat 核心命令)
以下为 AD24N SH58 工程的后处理主命令,展示了"段提取 → 合并 → 烧录 → 升级包"的完整链路:
%OBJCOPY% -O binary -j .app_code %NAME%.elf %NAME%.bin
%OBJCOPY% -O binary -j .data %NAME%.elf data.bin
%OBJCOPY% -O binary -j .debug_data %NAME%.elf debug_data.bin
%OBJCOPY% -O binary -j .lowpower_overlay %NAME%.elf lowpower_overlay.bin
copy /b %NAME%.bin+data.bin+debug_data.bin+lowpower_overlay.bin app.bin
copy app.bin voice_enhanced/app.bin
cd voice_enhanced
isd_download.exe -tonorflash -dev sh58 -boot 0x304000 -div8 -wait 300 -uboot uboot.boot -app app.bin 0x40000 -res dir_a dir_song dir_eng dir_poetry dir_story dir_bin_f1x dir_midi midi_cfg dir_notice
Source: download.bat
示例 2:外置 Flash 资源烧写(来自脚本模板注释)
当产品带外置 SPI Flash(如语音玩具扩展存储)时,取消注释并使用如下命令将单个资源烧到外置 Flash 指定地址:
@rem -wflash dir_song 0 [PA05_1B_NULL]
@rem 含义:将 dir_song 文件烧到外置 flash 起始地址 0,
@rem 片选 PA05(禁止 USBDP/USBDM),SPI1 B 端口,
@rem power_io 可控电源,spi 双向模式
Source: download_bat.c
示例 3:UFW 升级固件生成
ufw_maker.exe -fw_to_ufw jl_isd.fw
copy jl_isd.ufw update.ufw
del jl_isd.ufw
Source: download.bat
配置选项
后处理流程的"配置"通过脚本内参数与目录结构表达,汇总如下:
| 配置项 | 位置 | 类型/默认值 | 说明 |
|---|---|---|---|
OBJDUMP/OBJCOPY/OBJSIZEDUMP | download.bat 头部 set | 路径,C:\JL\pi32\bin\llvm-*.exe | LLVM 工具链路径,需与实际安装位置一致 |
NAME | download.bat | sdk | 目标 ELF 文件名(不含扩展名),由工具链探测决定 |
-dev | isd_download 参数 | sh58 | 目标芯片型号 |
-boot | isd_download 参数 | 0x304000 | uboot 区起始地址 |
-app ... 0x40000 | isd_download 参数 | app.bin @ 0x40000 | 应用镜像及烧录地址 |
-res 目录列表 | isd_download 参数 | 9 个目录 | 语音资源分区(顺序即 Flash 布局) |
-div8 / -wait 300 | isd_download 参数 | — | UART 下载时序参数,不同波特率/分频需调整 |
-format vm / -format all | 注释保留 | 默认关闭 | 烧录前格式化虚拟区/整片 Flash |
-reboot 500 | 注释保留 | 默认关闭 | 烧录完成后延时复位 |
-wflash 文件 地址 [引脚串] | 注释保留 | 默认关闭 | 外置 Flash 二次烧写 |
故障模式、边界情况与注意事项
以下故障模式与处理要点均从脚本源码与注释中可验证:
- 工具链路径错误:
set OBJDUMP=...为硬编码绝对路径。若 LLVM 工具未安装在C:\JL\pi32\bin,if exist探测失败,NAME不会被赋值,后续if exist %NAME%.elf判断失效——脚本静默跳过整个后处理,不会报错,需要人工检查输出产物是否存在。 - ELF 缺失:
if exist %NAME%.elf保护了不存在 ELF 时(如纯资源工程)的执行,避免误烧旧镜像。 - 烧录失败无重试:
isd_download.exe失败后脚本直接退出(批处理无错误码检查),量产环境需配合外部工具(CI/产测软件)轮询update.ufw/jl_isd.fw产物判断成功。 - 段拼接顺序敏感:
copy /b的四段顺序必须与链接脚本.app_code → .data → .debug_data → .lowpower_overlay的地址排布一致,任何顺序调整都会导致烧录后固件错位。 - 片选引脚冲突:
-wflash的片选引脚禁止使用 USBDP/USBDM(注释明确标注),否则 USB 下载功能与外置 Flash 争用引脚导致烧录失败。 - 资源目录必须存在:
-res列出的目录(如dir_song)在isd_download.exe执行时必须存在且可读,缺失会导致资源分区打包失败。 - 延时清理:
ping /n 2 127.1>null的 1 秒延时用于等待ufw_maker.exe关闭文件句柄,删除jl_isd.ufw时若文件仍被占用会残留临时文件——这是批处理无进程等待原语下的实用妥协。
并发与一致性说明
- 后处理脚本为单进程顺序执行,无并发逻辑;量产时多台机器并行烧录是各自独立的进程实例,互不影响;
app.bin与update.ufw是幂等产物:同一 ELF 多次运行脚本产出相同的烧录镜像,方便重复烧录与升级验证;- Flash 写保护参数(
flash_params_v3.bin)由flash_list_to_bin_v3.bat依据 Excel 列表生成,保护区域的变更必须重新生成参数并整体烧录,避免"部分更新"导致保护配置不一致。
扩展点
- 新增语音资源:在
voice_enhanced/下新建dir_xxx目录并加入-res列表即可,无需改动固件代码; - 新增段:仿照
.cache_ram注释示例,在download.bat中增加llvm-objcopy -j .xxx提取并在copy /b拼接链中按地址顺序插入; - 外置 Flash 资源:通过
-wflash与dir_ex_flash目录组合,可将资源与主固件分离部署,适配可插拔存储产品; - 升级渠道:UFW 产物
update.ufw可直接对接杰理 OTA 升级链路,量产/售后共用同一后处理脚本。