CPU 平台与启动流程
AD23N(sh59 内核)平台的 CPU 架构、上电复位后的完整启动链路与系统初始化流程,覆盖从 MaskROM 引导、c_main 入口到 system_init 外设初始化的全部阶段。
Purpose and Scope
本页面向 AD23N 平台的 CPU 内核特性 与 启动(Boot)流程,说明固件从复位向量进入 C 语言主入口 c_main() 后,如何依次完成中断向量、看门狗、时钟、电源、内存、文件系统/VM、外设与音频子系统的初始化,最终进入应用主循环。
本页不展开以下属于兄弟页面的主题,仅在启动流程中提及调用点:
- 时钟/PLL 的详细配置(
clk_early_init内部算法)→ 见「时钟系统」页面 - 电源管理状态机(
power_early_flowing/power_later_flowing)→ 见「电源管理」页面 - 内存堆管理与 MMU 映射(
mem_init)→ 见「内存管理」页面 - 单个外设驱动的实现(GPADC、按键、DAC 等)→ 见各外设页面
Overview
AD23N 是杰理(Jieli)面向音频/蓝牙应用的 MCU 平台,其 CPU 核为 sh59(SDK 中所有平台相关目录均以 sh59 命名,例如 sdk/app/bsp/cpu/sh59/、sdk/app/bsp/start/sh59/、sdk/app/src/mbox_flash/sh59/)。该内核为双核 DSP 架构,代码中通过 core_num() 获取当前核编号,并通过 q32DSP(core_num())->ICFGxx 寄存器窗口访问中断配置。
启动流程的职责划分非常清晰,分为三层:
- ROM 层(MaskROM):芯片出厂固化的引导程序,负责加载应用固件并把控制权交给
c_main(cfg_addr),同时提供mask_init回调注册机制(打印、延时、中断开关、异常分析钩子)。 - 平台初始化层(
sdk/app/bsp/start/sh59/):main.c中的c_main()串起整条初始化流水线;init.c中的early_system_init()/system_init()分别完成「为时钟初始化提供 VM/Flash 能力」的早期初始化和「外设 + 音频」的完整初始化。 - 应用层:
app()进入业务逻辑,随后进入喂狗主循环。
关键设计意图:
- 分阶段初始化:把「读 Flash 分区/VM 配置」提前到
early_system_init(),因为时钟初始化需要读取 VM 中的校准/配置数据;init.c中的注释明确写道「该部分提前初始化为时钟初始化使用VM功能」。 - 异常可见性:启动早期就通过
maskrom_init()注册exception_analyze与exception_irq_handler,保证任何初始化阶段的崩溃都能被定位到具体异常。 - 看门狗兜底:
c_main一进入就设置WDT_8S看门狗,且主循环持续wdt_clear(),任何阶段死循环都会触发复位,避免系统挂死。
Architecture
下图展示了从 ROM 引导到应用主循环的完整启动架构及各阶段调用关系:
flowchart TD
subgraph sg_ROM["ROM 层 (MaskROM)"]
BOOT["BootROM 加载应用固件"]
MASK["maskrom_init()<br/>mask_init + interrupt_init + request_irq"]
end
subgraph sg_START["平台启动层 (bsp/start/sh59)"]
CMAIN["c_main(cfg_addr)"]
WDT["wdt_init(WDT_8S)"]
EFUSE["efuse_init()"]
CLK["clk_voltage_init + clk_early_init"]
LOG["log_init + printf 注册"]
PWR["power_early_flowing<br/>board_power_init"]
MEM["mem_init() + critical_hook_init()"]
EARLY["early_system_init()<br/>VFS / Flash分区 / VM / tzflash"]
PWR2["power_later_flowing()"]
SYS["system_init()<br/>消息 / GPADC / 电源 / 定时器 / 按键<br/>设备 / NORFS / 音频 / DAC"]
APP["app() 应用逻辑"]
IDLE["while(1): wdt_clear + mdelay(500)"]
end
BOOT --> CMAIN
CMAIN --> MASK
MASK --> WDT
WDT --> EFUSE
EFUSE --> CLK
CLK --> LOG
LOG --> PWR
PWR --> MEM
MEM --> EARLY
EARLY --> PWR2
PWR2 --> SYS
SYS --> APP
APP --> IDLE
各阶段职责:
| 阶段 | 函数 | 职责与设计意图 |
|---|---|---|
| 引导 | BootROM | 出厂固件,加载应用并跳转 c_main(cfg_addr),cfg_addr 携带配置地址 |
| 中断/异常 | maskrom_init() | 注册 MaskROM 回调(putchar/udelay/local_irq_*/exception_analyze),清零 ICFG 寄存器,注册异常 IRQ,初始化调试输出 |
| 看门狗 | wdt_init(WDT_8S) | 启动早期即启用 8 秒看门狗,防止初始化死锁 |
| 时钟 | clk_voltage_init() / clk_early_init() | 自适应时钟模式、设置 DVDD 电压、以 LRC 为参考初始化 PLL 到 48MHz 主频 |
| 电源 | power_early_flowing() / board_power_init() | 早期电源域切换与板级电源初始化 |
| 内存 | mem_init() | 堆内存初始化,为后续所有动态分配做准备 |
| 早期系统 | early_system_init() | VFS 挂载、Flash 分区解析(VM/EEPROM/OTP)、VM 初始化、tzflash 模式切换——为时钟/后续初始化提供非易失配置 |
| 完整系统 | system_init() | 消息队列、GPADC、电源管理、tick 定时器、按键、设备、NORFS、音频/DAC 初始化 |
| 运行 | app() + 主循环 | 进入业务应用,循环喂狗并打印心跳字符 h |
启动主入口:c_main 逐步解析
c_main(int cfg_addr) 是整个应用固件的 C 语言入口,BootROM 完成硬件引导后跳转至此。它是一条严格的初始化流水线——顺序不可随意调整,因为每一步都为下一步提供前提条件。
int c_main(int cfg_addr)
{
maskrom_init();
wdt_init(WDT_8S);
efuse_init();
clk_voltage_init(CLOCK_MODE_ADAPTIVE, DVDD_VOL_123V);
clk_early_init(PLL_REF_LRC, 200000, 480000000);
log_init(1000000);
register_handle_printf_putchar(putchar);
log_info("--------sh59-apps-------------\n");
power_early_flowing();
board_power_init();
clock_dump();
efuse_dump();
mem_init();
/* mem_stats(); */
critical_hook_init();
early_system_init();
power_later_flowing();
xprintf("sh59 main!\n");
system_init();
app();
while (1) {
wdt_clear();
mdelay(500);
putchar('h');
}
return 0;
}
Source: main.c
逐步说明:
maskrom_init():建立与 MaskROM 的协作。填充struct maskrom_argv,把应用侧的putchar(打印)、exception_analyze(异常分析)、udelay(微秒延时)、local_irq_enable/disable(中断开关)注册给 ROM,随后mask_init(&argv)让 ROM 驱动接管这些能力;接着interrupt_init()清零中断配置,request_irq(IRQ_EXCEPTION_IDX, 7, exception_irq_handler, 0)注册异常中断,debug_init()初始化调试通道。设计意图:异常处理必须在任何业务逻辑之前就位,保证后续初始化出错时可回溯。wdt_init(WDT_8S):启用 8 秒看门狗。从这一刻起,初始化总时长必须小于 8 秒,否则芯片自动复位——这是对初始化性能的硬约束。efuse_init()/efuse_dump():读取 eFuse(芯片一次性熔丝配置,含校准信息),后续时钟/电源初始化依赖其中数据。clk_voltage_init(CLOCK_MODE_ADAPTIVE, DVDD_VOL_123V):设置 DVDD 内核电压 1.23V,选择自适应时钟模式。clk_early_init(PLL_REF_LRC, 200000, 480000000):以 LRC(低频 RC 振荡器)为 PLL 参考源,参数为参考频率 200kHz、目标主频 48MHz。早期就用 PLL 把 CPU 提到 48MHz,缩短后续初始化耗时。log_init(1000000):日志波特率 1Mbps;随后register_handle_printf_putchar(putchar)统一 printf 与 putchar 输出通道,保证log_info/xprintf可用。power_early_flowing()/board_power_init():切换电源域、初始化板级电源轨。此时时钟已就绪,电源可以按最终负载配置。mem_init():堆初始化。此前的初始化尽量少用动态内存,之后early_system_init/system_init可以自由 malloc。critical_hook_init():注册临界区钩子enter_critical_hook/exit_critical_hook,供中断保护模块使用。early_system_init()→power_later_flowing():先让文件系统/VM 可用,再做电源后段流程(详见下一节)。system_init():外设与音频全量初始化(详见下节)。app():进入应用。正常应用不应返回;若返回,则落入while (1)心跳循环:每 500ms 清一次看门狗并输出h,防止复位同时提供存活指示。
early_system_init:为时钟准备 VM 能力
void early_system_init(void)
{
/* 该部分提前初始化为时钟初始化使用VM功能 */
vfs_resource_init();
vfs_init();
flash_info_init();
vm_init_api();
tzflash_change_mode();
}
Source: init.c
这段注释直接点明了设计意图:时钟初始化需要读取 VM(虚拟存储/配置区)中的数据,因此必须先把 VFS(虚拟文件系统)和 Flash 分区挂载起来。flash_info_init() 通过 VFS 打开 /app_area_head/VM 获取 VM 分区的文件属性(sclust/fsize),据此设置 SFC(SPI Flash 控制器)的未加密分区(IOCTL_SET_PART_INFO),并查询 otp_cfg 顶层分区地址写入 sydf_cfg;vm_init_api() 再打开 /app_area_head/EEPROM 分区,把起始簇与大小交给 syscfg_vm_init();最后 tzflash_change_mode() 切换 TrustZone Flash 工作模式。
system_init:外设与音频全量初始化
void system_init(void)
{
/* clock_2_64m(); */
d_key_voice_init();
message_init();
gpadc_init();
app_power_init();
tick_timer_init();
key_init();
devices_init();
norfs_init_api();
audio_init();
dac_mode_init(31, NULL);
dac_init_api(SR_DEFAULT, 0);
//升级初始化
#if TFG_DEV_UPGRADE_SUPPORT
/* 暂不支持 */
/* int app_update_init(void); */
/* app_update_init(); */
#endif
}
Source: init.c
初始化顺序遵循依赖关系:先建消息机制(message_init)与按键语音(d_key_voice_init),再初始化模拟采样(GPADC,按键扫描依赖它)与电源管理、tick 定时器;之后才是按键、设备框架、NORFS 文件系统;最后是音频链路——audio_init() 建立音频框架,dac_mode_init(31, NULL) 配置 DAC 工作模式,dac_init_api(SR_DEFAULT, 0) 以默认采样率启动 DAC。升级模块由 TFG_DEV_UPGRADE_SUPPORT 宏控制,当前 SDK 中暂未启用。
启动时序
sequenceDiagram
participant ROM as BootROM / MaskROM
participant M as main.c c_main
participant I as init.c
participant HW as 硬件 (Clock/Power/SFC)
ROM->>M: 跳转 c_main(cfg_addr)
M->>M: maskrom_init(): 注册回调 + 异常IRQ + debug_init
M->>M: wdt_init(WDT_8S) / efuse_init()
M->>HW: clk_voltage_init(CLOCK_MODE_ADAPTIVE, 1.23V)
M->>HW: clk_early_init(PLL_REF_LRC, 48MHz)
M->>HW: power_early_flowing() / board_power_init()
M->>M: mem_init() / critical_hook_init()
M->>I: early_system_init()
I->>I: vfs_resource_init / vfs_init
I->>HW: flash_info_init(): 解析 VM 分区 + OTP 配置
I->>I: vm_init_api(): EEPROM 分区 -> syscfg_vm_init
I->>HW: tzflash_change_mode()
M->>M: power_later_flowing()
M->>I: system_init()
I->>I: 消息 / GPADC / 电源 / 定时器 / 按键 / 设备 / NORFS / 音频 / DAC
M->>M: app() 进入应用
M->>M: while(1): wdt_clear() + mdelay(500) + putchar('h')
中断与异常处理
CPU 的中断初始化在启动早期完成,由 main.c 中的 interrupt_init() 与 init.c 中的 all_init_isr() 分工:
void interrupt_init(void)
{
int i ;
local_irq_disable();
volatile unsigned int *icfg_ptr = &(q32DSP(core_num())->ICFG00);
for (i = 0 ; i < 32 ; i++) {
icfg_ptr[i] = 0;
}
local_irq_enable();
}
Source: main.c
interrupt_init() 在关中断状态下,通过 q32DSP(core_num()) 拿到当前核的 DSP 寄存器映射,把 32 个中断配置寄存器 ICFG00~ICFG31 全部清零——即关闭所有中断源,构建一个干净的中断起点。
void all_init_isr(void)
{
u32 i;
unsigned int *israddr = (unsigned int *)IRQ_MEM_ADDR;
for (i = 0; i < 32; i++) {
israddr[i] = (u32)exception_irq_handler;
}
}
Source: init.c
all_init_isr() 把 IRQ_MEM_ADDR 处的 32 个中断向量入口全部指向 exception_irq_handler,保证任何未单独注册的中断都会落到统一的异常处理逻辑(配合 exception_analyze 输出异常现场),而不是执行到未定义地址。maskrom_init() 中的 request_irq(IRQ_EXCEPTION_IDX, 7, exception_irq_handler, 0) 则显式注册了异常中断源,优先级 7。
flowchart TD
subgraph sg_IRQ["中断子系统初始化"]
INT_INIT["interrupt_init()<br/>q32DSP(core_num())->ICFG00~31 清零"]
ALL_ISR["all_init_isr()<br/>IRQ_MEM_ADDR 32项填 exception_irq_handler"]
REQ_IRQ["request_irq(IRQ_EXCEPTION_IDX, 7,<br/>exception_irq_handler, 0)"]
EXC["exception_irq_handler / exception_analyze<br/>输出异常现场"]
end
INT_INIT --> ALL_ISR
ALL_ISR --> REQ_IRQ
REQ_IRQ --> EXC
EXC -->|"未处理异常"| WDT["看门狗复位兜底"]
Flash 分区与写入保护机制
启动流程早期需要解析 Flash 布局,init.c 中的 struct sydf_cfg_info 与 VFS 属性结构承载了这些数据:
struct sydf_cfg_info {
u32 votp_cfg_addr;
u32 votp_cfg_len;
u32 ini_cfg_addr;
u32 ini_cfg_len;
};
struct sydf_cfg_info sydf_cfg;
Source: init.c
启动阶段解析出的关键区域包括:
| 区域 | 来源 | 用途 |
|---|---|---|
/app_area_head/VM | VFS 文件属性 vm_attr | VM 配置分区,起始簇 sclust 作为 Flash 可写区域边界 |
/app_area_head/EEPROM | VFS 文件属性 eeprom_attr | 模拟 EEPROM 的系统配置区,交给 syscfg_vm_init() |
otp_cfg 顶层分区 | sydf_get_top_area_info() | OTP 配置区地址/长度,存入 sydf_cfg |
| SFC 容量 | IOCTL_GET_CAPACITY | Flash 总容量,作为可写区域上边界 |
Flash 写入保护由 flash_code_protect_callback 实现:
AT_SPI_CODE/*该函数放置段不可更改*/
u32 flash_code_protect_callback(u32 offset, u32 len)
{
u32 limit_addr = vm_attr.sclust;
/* log_info("0x%x 0x%x", limit_addr, offset); */
if ((offset < limit_addr) || ((offset + len) > boot_info.flash_size)) {
/* 超过正常擦写区域,不进行擦写操作 */
return 1;
} else {
/* 进行擦写操作 */
return 0;
}
}
Source: init.c
该回调是写保护策略:vm_attr.sclust 是 VM 分区的起始簇,也是「应用代码区」与「可写数据区」的分界线。任何擦写请求若落在 [sclust, flash_size) 之外(即越过边界进入固件/引导区),直接拒绝(返回 1);只有位于 VM 与数据区之内的操作才被放行(返回 0)。函数用 AT_SPI_CODE 段属性强制驻留在 SPI 可执行区域,避免保护逻辑本身不可访问。设计意图:防止上层误擦固件导致变砖,同时允许 VM/数据区正常读写。
版本与启动标识
固件在 .version 段放置升级版本标识:
sec_used(.version)
u8 const lib_update_version[] = "\x7c\x4f\x94\x0aUPDATE-@20210816-$9c89ae0";
Source: init.c
sec_used(.version) 将该数组固定放置到 .version 段,BootROM/升级工具可通过固定地址读取版本字符串(包含日期 20210816 与校验后缀)以判断固件是否需要升级。
配置选项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
WDT_8S | 看门狗超时 | 8 秒 | wdt_init 设置的看门狗周期,主循环每 500ms 喂狗 |
CLOCK_MODE_ADAPTIVE | 时钟模式 | 自适应 | clk_voltage_init 的时钟模式,按负载自动调频调压 |
DVDD_VOL_123V | 内核电压 | 1.23V | DVDD 目标电压档位 |
PLL_REF_LRC | PLL 参考源 | LRC | 以低频 RC 振荡器作为 PLL 参考 |
clk_early_init 参考频率 | 数值 | 200000 (Hz) | PLL 参考频率参数 |
clk_early_init 目标频率 | 数值 | 480000000 (Hz) | 早期主频目标 48MHz |
log_init 波特率 | 数值 | 1000000 (bps) | 日志串口波特率 1Mbps |
TFG_DEV_UPGRADE_SUPPORT | 编译宏 | 关闭 | 是否启用升级模块,当前 SDK 暂不支持 |
IRQ_MEM_ADDR | 地址常量 | 平台定义 | 中断向量表内存地址,32 项 × 4 字节 |
SR_DEFAULT | 采样率枚举 | 平台默认 | DAC 初始化采样率 |
失败模式、边界情况与并发
- 初始化超时 → 看门狗复位:
c_main启动即启用WDT_8S,若early_system_init/system_init任一环节阻塞超过 8 秒(例如 Flash 器件无响应、VFS 挂载失败死等),芯片会被硬件复位。这是启动流程最核心的失败兜底,调试时可通过putchar('h')心跳判断系统是否仍存活。 - 异常中断兜底:
interrupt_init清零全部 ICFG +all_init_isr填充 32 项异常向量 +request_irq(IRQ_EXCEPTION_IDX,...)注册,构成三层防护——即使驱动注册了错误的中断号,也会进入exception_irq_handler打印现场而非跑飞。 - Flash 越界擦写拒绝:
flash_code_protect_callback对任何越过vm_attr.sclust下界或超出boot_info.flash_size上界的擦写返回 1 拒绝;边界条件offset == limit_addr或offset + len == flash_size恰好等于边界时放行,属于闭区间策略。 - 临界区一致性:
interrupt_init在local_irq_disable()下修改 ICFG,防止与已使能的中断竞争;critical_hook_init注册的 enter/exit 钩子为后续临界区保护提供统一入口。 - 双核寄存器访问:ICFG 操作通过
q32DSP(core_num())访问当前核的窗口,不同核互不干扰;跨核共享资源(如mbox_flash目录下的cpu_config.c)的初始化由各自核的启动流程分别完成。 cfg_addr参数:c_main的入参来自 BootROM,若引导配置损坏,可能传入非法地址;SDK 未在c_main内校验该参数,依赖 BootROM 侧的完整性校验。
扩展点
- 板级电源:
board_power_init()是板级电源的注入点,不同硬件板卡可在此挂接自定义电源轨。 - 升级能力:
system_init中#if TFG_DEV_UPGRADE_SUPPORT预留给升级模块(app_update_init),当前关闭,开启后可扩展 OTA 初始化。 - 异常分析钩子:
maskrom_init将exception_analyze注册给 ROM,可通过替换该回调定制异常现场输出格式。 - printf 通道:
register_handle_printf_putchar(putchar)统一了日志输出,可替换putchar实现以重定向日志到不同物理通道。
相关链接
- main.c — c_main 启动流水线
- init.c — early_system_init / system_init
- init.c — Flash 写保护回调
- sh59 CPU 平台目录
- 时钟系统、电源管理、内存管理、外设驱动等主题详见各自独立页面