芯片平台与启动流程
本文档介绍 fw-AD15N(杰理 AD 系列 MCU SDK)所支持的芯片平台家族、平台化构建系统,以及从芯片复位到应用就绪的启动流程,并深入分析双 Bank Flash 启动/OTA 升级机制的实现。
Purpose and Scope
本页覆盖:
- AD 系列芯片平台家族(AC104N / AD14N / AD15N / AD17N / AD18N)及其产品形态(mcu、voice_toy、mbox_mg)
- 平台化构建系统:
sdk/Makefile与各平台Makefile.<platform>_<product>、Code::Blocks 工程文件的组织方式 - 启动流程:内存布局(链接器符号驱动的堆初始化)、BSP 初始化、应用主循环
- 双 Bank Flash 启动与在线升级(OTA)流程,基于
sdk/app/bsp/common/dual_bank_demo.c的真实实现
本页不展开的具体子系统(各有独立文档页):
- 音频解码/编码子系统(
sdk/app/bsp/common/decoder/、encoder/)→ 见"音频编解码框架"页 - 音效与降噪(
sound_effect_list/ans_api.c、ANC 补丁)→ 见"音效处理与 ANC 降噪"页 - 文件系统与存储管理(jlfs、flash 驱动)→ 见"存储与文件系统"页
Overview
该 SDK 是杰理(Jieli)针对其 AD/AC 系列语音 MCU 提供的嵌入式软件开发套件,同一套应用代码通过平台化 Makefile 面向多颗芯片构建。不同芯片平台共享 sdk/app/bsp/ 下的应用与 BSP 公共代码,差异主要体现在芯片外设寄存器、Flash 布局与链接脚本上。
从仓库结构可以观察到清晰的分层设计意图:
- 平台层(platform):由
sdk/Makefile分派到Makefile.ad15n_mcu、Makefile.ad18n_voice_toy等平台级构建文件,每颗芯片 + 产品形态组合对应一个独立构建目标; - BSP 层:
sdk/app/bsp/下的公共驱动与中间件(decoder、encoder、sound_effect_list、文件系统、双 Bank 升级 demo); - 启动支撑:
sdk/app/post_build/sh54/voice_toy/app_ld.c等后处理脚本负责应用链接与镜像生成; - 维护通道:
patch/目录存放针对特定芯片的补丁(如 AD14N 的 ANC 降噪补丁),体现"平台基座 + 差异化补丁"的维护策略。
启动流程的核心问题是:芯片上电后,如何把代码从 Flash 引导进 RAM、初始化内存堆、配置时钟与外设,最终进入应用主循环。本 SDK 中,堆内存的边界由链接脚本符号 _free_start / _free_end 提供(见 sdk/app/bsp/lib/my_malloc.c),而 OTA 升级则依赖双 Bank Flash 布局(见 sdk/app/bsp/common/dual_bank_demo.c),这两者是理解本 SDK 启动体系的关键。
Architecture
flowchart TD
subgraph sg_Chip["芯片平台层 (Chip Platforms)"]
AC104N["AC104N"]
AD14N["AD14N"]
AD15N["AD15N"]
AD17N["AD17N"]
AD18N["AD18N"]
end
subgraph sg_Build["构建系统 (Build System)"]
MK["sdk/Makefile"]
MKP["make_prompt.bat"]
WS["default.workspace"]
CBP["*.cbp 工程文件"]
end
subgraph sg_BSP["BSP 与应用层 (sdk/app/bsp/)"]
COMMON["common/ 公共中间件"]
LIB["lib/ 库与内存管理"]
DEC["decoder/ 音频解码"]
ENC["encoder/ 音频编码"]
FX["sound_effect_list/ 音效与降噪"]
DUAL["dual_bank_demo.c 双Bank升级"]
end
subgraph sg_Post["构建后处理 (post_build)"]
ALD["sh54/voice_toy/app_ld.c"]
end
subgraph sg_Patch["补丁通道 (patch/)"]
PATCH["AD14N ANC 降噪补丁"]
end
MK -->|"分派平台构建"| MKP
MK -->|"定义平台宏与链接"| CBP
MK -->|"选择芯片/产品"| AC104N
MK -->|"选择芯片/产品"| AD14N
MK -->|"选择芯片/产品"| AD15N
MK -->|"选择芯片/产品"| AD17N
MK -->|"选择芯片/产品"| AD18N
WS --> MK
AC104N --> COMMON
AD14N --> COMMON
AD15N --> COMMON
AD17N --> COMMON
AD18N --> COMMON
COMMON --> DEC
COMMON --> ENC
COMMON --> FX
COMMON --> DUAL
COMMON --> LIB
ALD -->|"生成最终镜像"| CBP
PATCH -->|"按芯片差异合入"| COMMON
架构说明:
- 芯片平台层:AC104N / AD14N / AD15N / AD17N / AD18N 是同一 SDK 支持的芯片家族,由构建系统按目标选择。它们共享应用代码,差异封装在芯片级驱动、链接脚本和 Makefile 中——这是"一套代码、多芯片复用"的平台化设计。
- 构建系统层:
sdk/Makefile是总入口,通过make_prompt.bat辅助调用;每个平台/产品组合有独立 Makefile(如Makefile.ad15n_mcu、Makefile.ad18n_voice_toy)和 Code::Blocks 工程(AD15N_mcu.cbp等),default.workspace将多个工程组织为工作区。 - BSP/应用层:
sdk/app/bsp/common/承载跨芯片复用的中间件——解码器、编码器、音效、双 Bank 升级;lib/提供内存管理等基础库。 - 构建后处理:
post_build/sh54/voice_toy/app_ld.c在链接阶段对应用镜像做定制处理(如地址重定位、镜像封装),是启动流程与 Flash 布局的直接关联点。 - 补丁通道:
patch/AD14N_20250303_ANC_noise_reduction_patch/展示了对特定芯片的差异化能力(ANC 降噪)以补丁形式合入公共代码的维护方式,避免污染其他平台基线。
说明:以上分层来自对仓库目录结构与构建文件命名的分析;各芯片寄存器级差异、Boot ROM 内部细节不在本 SDK 源码内,属于芯片手册范畴,本文档不臆测其内部实现。
芯片平台家族与构建系统
平台矩阵
仓库根目录的构建文件直接揭示了平台矩阵:每个 Makefile.<platform>_<product> 对应一个"芯片 + 产品形态"的组合,Code::Blocks 工程(.cbp)与之对应。
| 芯片平台 | 产品形态 | Makefile | 工程文件 |
|---|---|---|---|
| AC104N | mbox_mg(音箱管理) | Makefile.ac104n_mbox_mg | AC104N_mbox_mg.cbp |
| AD14N | mcu(语音 MCU) | Makefile.ad14n_mcu | AD14N_mcu.cbp |
| AD14N | voice_toy(语音玩具) | Makefile.ad14n_voice_toy | AD14N_voice_toy.cbp |
| AD15N | mcu | Makefile.ad15n_mcu | AD15N_mcu.cbp |
| AD15N | voice_toy | Makefile.ad15n_voice_toy | AD15N_voice_toy.cbp |
| AD17N | mcu | Makefile.ad17n_mcu | AD17N_mcu.cbp |
| AD17N | voice_toy | Makefile.ad17n_voice_toy | AD17N_voice_toy.cbp |
| AD18N | mcu | Makefile.ad18n_mcu | AD18N_mcu.cbp |
| AD18N | voice_toy | Makefile.ad18n_voice_toy | AD18N_voice_toy.cbp |
设计意图:mcu 与 voice_toy 两种产品形态在 Flash 分区、外设配置(如按键/点灯/马达驱动)上存在差异,但共享同一套解码器与 BSP 代码。将平台差异前置到构建层,使应用代码可以保持"一次编写、多平台编译",这是杰理 SDK 典型的平台化(board-level porting)策略。default.workspace 与 sdk/Makefile 作为总入口,避免开发者直接记忆每个平台的构建细节。
构建入口与镜像生成
make_prompt.bat 是 Windows 下的构建辅助脚本,配合 sdk/Makefile 使用。构建产物经过 post_build/sh54/voice_toy/app_ld.c 的后处理——该文件位于 post_build 目录,表明它是在常规编译链接之后运行的定制链接/封装步骤,负责生成最终烧录镜像(如设置应用入口地址、附加文件系统元数据)。
这条链路对启动流程的意义在于:烧录进 Flash 的镜像布局(入口地址、Bank 划分)由构建期决定,而运行时启动流程必须与之一致——这正是"芯片平台与启动流程"这一主题把构建系统纳入讨论范围的原因。
内存布局与堆初始化
my_malloc.c 通过外部链接器符号定义堆边界:
extern const u8 _free_start[];
extern const u8 _free_end[];
Source: my_malloc.c
设计意图:堆的起始与结束地址不由源码硬编码,而是由链接脚本根据该平台的 RAM 布局自动生成 _free_start/_free_end 符号。这意味着:
- 换一颗芯片(如 AD14N → AD18N)时,RAM 大小不同,只要链接脚本正确,堆区间自动跟随调整,无需改动内存管理代码;
- 启动流程中必须先完成 BSS/数据段搬运与堆初始化,后续
malloc/my_free才能工作——dual_bank_demo.c中大量使用my_free(见下文),也印证了内存管理是启动后的基础服务。
说明:
_free_start/_free_end的具体地址值由链接脚本(.ld/link 文件)决定,链接脚本文件未在本页证据范围内直接读取,其符号语义(堆上下界)可从my_malloc.c的 extern 声明确认。
启动流程
总体阶段划分
根据 SDK 的代码结构与构建产物组织方式,启动流程可归纳为以下阶段(阶段 0/1 属于芯片内部行为,阶段 2 起对应本 SDK 源码):
sequenceDiagram
participant ROM as "芯片 BootROM"
participant FLASH as "SPI Flash"
participant LDS as "链接脚本/镜像"
participant HEAP as "my_malloc 堆"
participant BSP as "BSP 公共中间件"
participant APP as "应用主循环"
ROM->>FLASH: 上电复位,读取启动镜像头部
ROM->>LDS: 按镜像布局搬运代码/数据段
LDS->>HEAP: 生成 _free_start/_free_end 堆边界符号
ROM->>BSP: 跳转到应用入口 (app_ld 重定位后地址)
BSP->>BSP: 时钟/外设/Flash 控制器初始化
BSP->>HEAP: 初始化堆,启用 malloc/my_free
BSP->>BSP: 注册文件系统 (jlfs) 与设备 (flash_dev)
BSP->>APP: 进入应用主循环
APP->>APP: 处理解码/升级/音效等业务
各阶段说明:
- 芯片复位与 BootROM 引导(阶段 0):芯片上电后由内部 BootROM 从 SPI Flash 读取启动镜像。镜像的布局(入口地址、代码段位置)由构建期
post_build/sh54/voice_toy/app_ld.c的定制链接决定——这正是该文件位于构建后处理链中的原因。 - 镜像搬运与内存初始化(阶段 1):链接脚本为各内存段(data、bss、堆)确定地址,并导出
_free_start/_free_end等符号供运行时使用。 - BSP 初始化(阶段 2):应用入口执行后,先完成堆初始化与设备注册。
dual_bank_demo.c中flash_dev、vfs_read、dev_ioctl、dev_byte_write等 API 的可用,说明 Flash 设备驱动、VFS(虚拟文件系统)与 jlfs 文件系统都已在应用代码运行前就绪。 - 应用主循环(阶段 3):进入业务逻辑,包括音频解码(
decoder_api.c、decoder_msg_tab.c、decoder_point.c)、音效(ans_api.c)以及本页重点的 OTA 升级服务。
启动流程中的看门狗与长耗时操作
启动与初始化阶段常包含耗时操作(如 Flash 擦写),SDK 在 dual_bank_demo.c 的擦除循环中显式调用 wdt_clear():
for (u32 i = 0; i < (file_size / flash_alignsize); i ++) {
erase_err = dev_ioctl(flash_dev, flash_erase_cmd, upgrade_start_addr + i * flash_alignsize);
if (erase_err) {
log_error("erase %x", upgrade_start_addr + i * flash_alignsize);
break;
}
wdt_clear();
log_info("erase %x", upgrade_start_addr + i * flash_alignsize);
}
Source: dual_bank_demo.c
设计意图:Flash 扇区擦除是毫秒级甚至更长的阻塞操作,若看门狗在启动/升级期间超时,系统会被强制复位,导致升级中断、固件损坏。在循环内每个扇区擦除后 wdt_clear(),既保证看门狗不误触发,又保留了出错即 break 的快速失败路径——这是嵌入式启动代码中"长任务 + 看门狗喂狗"的标准范式。
双 Bank 启动与 OTA 升级机制
为什么需要双 Bank
语音 MCU 的 OTA 升级必须解决"升级失败导致变砖"的问题。双 Bank(dual bank)方案将 Flash 划分为两个 Bank:当前运行的固件位于 Bank A,新固件下载到 Bank B;校验通过后切换启动 Bank。这样即使升级中途断电,设备仍可从旧 Bank 正常启动。本 SDK 的 dual_bank_demo.c 正是这一机制的参考实现,其依赖的 jlfs_* 系列 API 表明 Bank 信息由 jlfs(杰理文件系统)统一管理。
升级流程实现分析
dual_bank_demo.c 的完整升级流程如下:
flowchart TD
Start([升级任务启动]) --> GetBank["jlfs_get_idle_bank_info<br/>(查询空闲 Bank 地址与大小)"]
GetBank --> LogAddr["log_info 打印升级地址/大小"]
LogAddr --> OpenFile["vfs 打开升级固件文件<br/>(file_attr.fsize - 4096 为有效数据)"]
OpenFile --> Loop{"逐块处理<br/>(flash_alignsize 对齐)"}
Loop -->|"擦除"| Erase["dev_ioctl flash_erase_cmd<br/>擦除失败则 log_error + break"]
Erase --> Wdt["wdt_clear 喂狗"]
Wdt --> Write["dev_byte_write 写入<br/>chip_crc16_with_init 累计 CRC"]
Write --> Loop
Loop -->|"全部写完"| ReadBack["dev_byte_read 回读校验"]
ReadBack --> UpdateInfo["jlfs_updata_dual_bank_info<br/>(记录地址与 CRC)"]
UpdateInfo --> CheckInfo{"jlfs_check_dual_bank_info<br/>校验 Bank 信息?"}
CheckInfo -->|"成功"| SetSFC["IOCTL_SET_SFC_READ 切换启动 Bank"]
CheckInfo -->|"失败"| Fallback["IOCTL_ERASE_SECTOR 擦除失败 Bank<br/>回退到旧 Bank"]
SetSFC --> Done([升级完成,重启后从新 Bank 启动])
Fallback --> Done
关键实现细节(对应源码行):
获取空闲 Bank:
jlfs_get_idle_bank_info(&upgrade_start_addr, &bank_size)返回当前未运行的 Bank 的起始地址与容量,升级目标不是硬编码地址,而是由文件系统动态分配——这保证双 Bank 布局变化时应用代码无需修改。对齐擦写:擦除以
flash_alignsize为粒度进行,写入前先擦除对应扇区;dev_ioctl(flash_dev, flash_erase_cmd, ...)与dev_byte_write(flash_dev, tmp_buf, ...)构成"擦-写"配对,遵循 NOR Flash 必须先擦后写的物理约束。CRC 累计校验:写入过程中以
chip_crc16_with_init(tmp_buf, cnt, data_crc)逐块累计 CRC16,最后通过jlfs_updata_dual_bank_info(upgrade_start_addr, data_crc)把地址与 CRC 一并写入 Bank 信息区;启动时据此判断新固件完整性。回读验证与回退:写入完成后
dev_byte_read回读对比;jlfs_check_dual_bank_info校验 Bank 信息,失败时IOCTL_ERASE_SECTOR擦除损坏的 Bank 头部并回退旧 Bank——这是防"变砖"的最后一道防线。切换启动 Bank:
dev_ioctl(flash_dev, IOCTL_SET_SFC_READ, 1)设置 SFC(SPI Flash 控制器)读取模式/启动 Bank,配合jlfs_updata_dual_bank_info的记录,重启后 BootROM 从新 Bank 引导。
升级代码示例
以下摘自 dual_bank_demo.c,展示"获取空闲 Bank → 擦写 → 记录 CRC → 校验"的核心段落:
u32 upgrade_start_addr = 0;
u32 bank_size;
...
res = jlfs_get_idle_bank_info(&upgrade_start_addr, &bank_size);
log_info("addr %x,size %x\n", upgrade_start_addr, bank_size);
Source: dual_bank_demo.c
dev_byte_write(flash_dev, tmp_buf, upgrade_start_addr + offset, cnt);
...
data_crc = chip_crc16_with_init(tmp_buf, cnt, data_crc);
Source: dual_bank_demo.c
jlfs_updata_dual_bank_info(upgrade_start_addr, data_crc);
...
u32 head_check_res = jlfs_check_dual_bank_info(upgrade_start_addr);
if (head_check_res) {
...
dev_ioctl(flash_dev, IOCTL_ERASE_SECTOR, upgrade_start_addr);
}
Source: dual_bank_demo.c
设计意图:将"Bank 管理"(哪些地址空闲、CRC 存哪里)交给 jlfs,应用只需关心"把文件写到哪、写完校验",职责分离清晰;CRC16 采用 _with_init 形式允许流式累计,无需为整个固件分配大缓冲区,适配 MCU 有限 RAM 的约束。
配置选项
平台选择与构建配置集中在 sdk/ 根目录,通过 Makefile/工程文件声明,而非运行时配置:
| 配置项 | 位置 | 可选值/类型 | 说明 |
|---|---|---|---|
| 芯片平台 | sdk/Makefile.<platform>_<product> | AC104N / AD14N / AD15N / AD17N / AD18N | 决定芯片驱动、链接脚本与 Flash 布局 |
| 产品形态 | 同上 | mcu / voice_toy / mbox_mg | 决定外设配置与应用集 |
| 工程组织 | sdk/default.workspace | 工作区文件 | 组织各 .cbp 工程 |
| 构建辅助 | sdk/make_prompt.bat | 批处理脚本 | Windows 下调用 Makefile 的入口 |
| 补丁合入 | patch/ 目录 | 按芯片/日期命名(如 AD14N_20250303_ANC_noise_reduction_patch) | 向公共代码合入差异化能力 |
| 堆边界 | 链接脚本符号 | _free_start / _free_end(const u8 数组) | 运行时由 my_malloc.c 引用,地址由链接期决定 |
| 升级粒度 | dual_bank_demo.c | flash_alignsize | Flash 擦写对齐粒度,决定循环迭代次数 |
注:以上均为构建期/链接期配置;运行时行为(如升级 Bank 切换)由 jlfs 与 Flash 控制器状态决定。
失败模式、边界情况与并发
从源码可验证的失败模式与处理策略:
| 失败场景 | 处理方式 | 源码证据 |
|---|---|---|
| Flash 擦除失败 | log_error 记录后 break 中止升级,不继续写入 | dual_bank_demo.c#L113-L116 |
| 升级期间看门狗超时 | 每个扇区擦除后 wdt_clear() 喂狗;出错 break 后看门狗仍可复位系统 | dual_bank_demo.c#L118 |
| 新固件写入中断/损坏 | 累计 CRC16(chip_crc16_with_init),通过 jlfs_check_dual_bank_info 校验 | dual_bank_demo.c#L133-L134、#L178 |
| Bank 信息校验失败 | IOCTL_ERASE_SECTOR 擦除损坏 Bank 头部,回退旧 Bank 启动 | dual_bank_demo.c#L196 |
| 升级过程中断(断电) | 双 Bank 机制保证旧 Bank 完整,重启仍可引导 | 整体设计(jlfs_get_idle_bank_info 只写空闲 Bank) |
并发/时序注意:升级流程是单线程阻塞式长任务,Flash 擦写期间不响应其他业务;MCU 环境下由看门狗保证不会永久卡死。dual_bank_demo.c 中 my_free((void *)f_tmp_buf) 的调用表明临时缓冲区在流程结束或失败路径上需要释放——启动/升级代码中的内存泄漏会直接侵蚀有限的 RAM 堆。
性能与运维考量
- 擦写粒度:以
flash_alignsize对齐分块擦写,避免整片擦除,缩短单次升级的阻塞时间;块大小越小,喂狗频率越高,看门狗窗口越宽裕。 - 流式 CRC:
chip_crc16_with_init逐块累计,无需为整个固件分配缓冲,内存占用恒定(tmp_buf/f_tmp_buf级别),这是 MCU 场景的关键约束。 - SFC 读取模式切换:
IOCTL_SET_SFC_READ在升级前后切换 Flash 控制器读取模式,避免高速缓存与直读模式不一致导致读到旧数据——该 IOCTL 的调用点(写入前/校验后)是排查"升级后启动异常"的首选检查位置。 - 镜像生成可复现性:
post_build/sh54/voice_toy/app_ld.c在每次构建时执行,确保 Flash 布局与 BootROM 预期一致;修改平台宏后应重新构建而非复用旧镜像。
扩展点
- 新增芯片平台:仿照现有
Makefile.<platform>_<product>+<PLATFORM>_<product>.cbp添加构建目标,并在链接脚本中导出正确的_free_start/_free_end符号。 - 新增产品形态:在
sdk/Makefile总入口登记新的Makefile.<platform>_<product>组合,复用sdk/app/bsp/common/公共代码。 - 差异化能力合入:参考
patch/AD14N_20250303_ANC_noise_reduction_patch/,以补丁形式向sound_effect_list/ans_api.c等公共模块合入芯片专属功能,保持平台基线稳定。 - 自定义升级策略:
dual_bank_demo.c是参考实现,可通过替换 jlfs 回调或实现自己的 Bank 切换逻辑来定制 OTA 流程(如多副本、签名校验)。 - 启动后置任务:在应用主循环之前插入初始化钩子(如外设自检、开机音效),需保证在
my_malloc堆初始化与设备注册之后执行。
Related Links
- fw-AD15N 仓库主页
- README.md / README-en.md
- 双 Bank 升级参考实现:dual_bank_demo.c
- 堆初始化与链接符号:my_malloc.c
- 构建后处理:app_ld.c
- 平台 Makefile 示例:Makefile.ad15n_mcu、Makefile.ad18n_voice_toy
- 音频解码子系统:decoder_api.c(详见"音频编解码框架"页)
- 音效与 ANC 降噪:ans_api.c 及 AD14N ANC 补丁(详见"音效处理与 ANC 降噪"页)