杰理 SDK 文档中心
首页
首页
  • 项目概览

    • AD23N SDK 概述与芯片平台
    • 工程结构与模块划分
  • 快速开始

    • 开发环境搭建与工具链
    • 编译构建指南
    • 烧录与固件升级工具
  • 应用框架与产品工作流

    • 应用入口与模式调度
    • 音乐播放应用
    • MIDI 解码与键盘演奏
    • 录音应用
    • LINEIN 与扩音应用
    • USB 从设备应用
    • 待机、软关机与空闲检测
    • 公共 UI 与 LED 显示
  • 音频子系统

    • 音频解码器框架
    • 音频编码器框架
    • 音效算法库
    • 音频管理与输出通路
  • 存储与文件系统

    • 文件系统层
    • NOR Flash 与虚拟机存储
    • 设备与设备管理
  • 系统服务与运行时

    • 消息机制与事件分发
    • 按键扫描与输入处理
    • 电源管理与低功耗控制
    • 定时器与系统任务
  • 外设驱动与平台

    • CPU 平台与启动流程
    • USB 协议栈与主机/设备驱动
    • SPI 与通用外设接口
  • 固件升级与构建工具

    • 固件升级机制
    • 编译后处理与镜像打包
    • 构建系统与命令行工具

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 寄存器窗口访问中断配置。

启动流程的职责划分非常清晰,分为三层:

  1. ROM 层(MaskROM):芯片出厂固化的引导程序,负责加载应用固件并把控制权交给 c_main(cfg_addr),同时提供 mask_init 回调注册机制(打印、延时、中断开关、异常分析钩子)。
  2. 平台初始化层(sdk/app/bsp/start/sh59/):main.c 中的 c_main() 串起整条初始化流水线;init.c 中的 early_system_init() / system_init() 分别完成「为时钟初始化提供 VM/Flash 能力」的早期初始化和「外设 + 音频」的完整初始化。
  3. 应用层: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

逐步说明:

  1. 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() 初始化调试通道。设计意图:异常处理必须在任何业务逻辑之前就位,保证后续初始化出错时可回溯。
  2. wdt_init(WDT_8S):启用 8 秒看门狗。从这一刻起,初始化总时长必须小于 8 秒,否则芯片自动复位——这是对初始化性能的硬约束。
  3. efuse_init() / efuse_dump():读取 eFuse(芯片一次性熔丝配置,含校准信息),后续时钟/电源初始化依赖其中数据。
  4. clk_voltage_init(CLOCK_MODE_ADAPTIVE, DVDD_VOL_123V):设置 DVDD 内核电压 1.23V,选择自适应时钟模式。
  5. clk_early_init(PLL_REF_LRC, 200000, 480000000):以 LRC(低频 RC 振荡器)为 PLL 参考源,参数为参考频率 200kHz、目标主频 48MHz。早期就用 PLL 把 CPU 提到 48MHz,缩短后续初始化耗时。
  6. log_init(1000000):日志波特率 1Mbps;随后 register_handle_printf_putchar(putchar) 统一 printf 与 putchar 输出通道,保证 log_info/xprintf 可用。
  7. power_early_flowing() / board_power_init():切换电源域、初始化板级电源轨。此时时钟已就绪,电源可以按最终负载配置。
  8. mem_init():堆初始化。此前的初始化尽量少用动态内存,之后 early_system_init/system_init 可以自由 malloc。
  9. critical_hook_init():注册临界区钩子 enter_critical_hook / exit_critical_hook,供中断保护模块使用。
  10. early_system_init() → power_later_flowing():先让文件系统/VM 可用,再做电源后段流程(详见下一节)。
  11. system_init():外设与音频全量初始化(详见下节)。
  12. 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/VMVFS 文件属性 vm_attrVM 配置分区,起始簇 sclust 作为 Flash 可写区域边界
/app_area_head/EEPROMVFS 文件属性 eeprom_attr模拟 EEPROM 的系统配置区,交给 syscfg_vm_init()
otp_cfg 顶层分区sydf_get_top_area_info()OTP 配置区地址/长度,存入 sydf_cfg
SFC 容量IOCTL_GET_CAPACITYFlash 总容量,作为可写区域上边界

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.23VDVDD 目标电压档位
PLL_REF_LRCPLL 参考源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 平台目录
  • 时钟系统、电源管理、内存管理、外设驱动等主题详见各自独立页面
Next
USB 协议栈与主机/设备驱动