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

    • AD16N 系列芯片与 SDK 能力总览
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建指南
    • 烧录与固件升级
  • SDK 工程架构

    • SDK 目录结构与模块分层
    • 构建系统与批处理工具
    • BSP 板级支持包
  • mbox_flash 小音箱应用

    • 应用初始化与启动流程
    • 应用配置系统
    • 按键、UI 与用户交互
  • 音频子系统

    • 音频解码框架与调度
    • 音频格式解码器实现
    • MIDI 合成与播放
    • 音频编码与录音
    • EQ/DRC 与音效处理
    • DAC/ADC 音频接口与采样
  • 存储与文件系统

    • 媒体 IO 抽象层 MIO
    • 存储设备驱动
    • 文件系统支持
  • 平台系统库

    • 系统基础服务
    • CPU 平台与运行库
    • 固件升级与更新机制
    • 蓝牙与扩展连接接口
  • 电源与低功耗管理

    • 电源管理与低功耗设计
    • 锂电池充电管理
  • 硬件与文档参考

    • SDK 文档中心与版本发布记录
    • 芯片数据手册与硬件设计参考

应用初始化与启动流程

本文档解析 fw-AD16N_GP-MCU_SDK 应用固件从上电复位到应用入口的完整启动链路,核心入口为 c_main()(位于 sdk/apps/app/bsp/start/uc03/main.c),涵盖时钟、中断、日志、看门狗、内存、定时器、电源管理及系统级初始化的顺序与设计意图。

Purpose and Scope

本页面向嵌入式开发与固件移植工程师,说明 GP-MCU(杰理 AD16N 系列)应用侧启动流程的每一步做什么、为什么按此顺序执行、各步骤之间如何衔接。内容包括:

  • c_main() 启动入口的完整执行序列
  • 启动期的内存段布局约束(#pragma 段定位)
  • 掩膜 ROM 初始化(mask_init / sd_mask_init)的钩子机制
  • 时钟、日志、看门狗、电源等子系统的初始化时机
  • 关键配置宏(TCFG_*、WDT_8S、SYS_TIMER_EN 等)对启动行为的影响

以下内容属于兄弟页面,不在本页展开:具体的驱动外设配置(GPIO、UART 收发)、电源策略与低功耗状态机、SD 卡/文件系统挂载细节、各应用(mbox/flash 等)的业务逻辑。如需了解,请参见对应目录的专门页面。

Overview

AD16N 是一颗面向音频/MCU 应用的嵌入式 SoC。其上电启动链路分两段:

  1. ROM 引导段:芯片内部 MaskROM 运行,完成最基本的环境准备后跳转至用户固件。
  2. 用户固件段:由 c_main(int cfg_addr) 接管,依次完成看门狗、掩膜接口、中断、GPIO、串口日志、PLL 时钟、调试器、内存堆、系统定时器、电源、系统总初始化,最后调用 app() 进入应用主循环。

main.c 是这段链路的"总指挥",其设计意图非常明确:把可独立验证的底层资源(时钟/中断/内存/日志/看门狗)先建立起来,再叠加需要依赖它们的子系统(定时器、电源、系统服务),最后才进入业务应用——任何一步失败都能通过 log_init 之后的串口日志快速定位,而看门狗在 wdt_init(WDT_8S) 之后全程兜底防止死循环。

关键术语:

术语含义
c_main用户固件 C 入口,由启动汇编跳入
mask_init掩膜 ROM 环境初始化接口,注册异常/打印/时钟/设备状态钩子
sd_mask_initSD 卡掩膜初始化(TFG_SD_EN 使能时)
TCFG_*工程级配置宏,定义于 app_config.h / config.h
system_init系统级总初始化(挂接 app() 之前的最后一步)

Architecture

下图展示 c_main() 启动链路的层次结构:每个节点对应 main.c 中真实调用的函数,箭头为实际调用顺序。

flowchart TD
    subgraph sg_Boot["启动入口 (main.c)"]
        C_MAIN["c_main(cfg_addr)"]
        WDT_CLOSE["wdt_close()"]
        MASK_INIT["mask_init_for_app()"]
        IRQ_INIT["irq_init()"]
        PORT_INIT["port_init()"]
        LOG_INIT["log_init(TCFG_UART_BAUDRATE)"]
        PLL["pll_sel(TCFG_PLL_SEL, ...)"]
        DEBUG_INIT["debug_init()"]
        WDT_INIT["wdt_init(WDT_8S)"]
        MALLOC["my_malloc_init()"]
        SYS_TIMER["sys_timer_init()"]
        POWER["sys_power_init()"]
        SYS_INIT["system_init()"]
        APP["app()"]
        LOOP["while(1) wdt_clear()"]
    end

    subgraph sg_ROM["掩膜 ROM 环境"]
        MASK["mask_init(exception_analyze, putchar, clk_get, device_status_emit)"]
        SD_MASK["sd_mask_init(...)"]
    end

    C_MAIN --> WDT_CLOSE
    C_MAIN --> MASK_INIT
    MASK_INIT --> MASK
    MASK_INIT --> SD_MASK
    C_MAIN --> IRQ_INIT
    C_MAIN --> PORT_INIT
    C_MAIN --> LOG_INIT
    C_MAIN --> PLL
    C_MAIN --> DEBUG_INIT
    C_MAIN --> WDT_INIT
    C_MAIN --> MALLOC
    C_MAIN --> SYS_TIMER
    C_MAIN --> POWER
    C_MAIN --> SYS_INIT
    C_MAIN --> APP
    APP --> LOOP

架构解读:

  • 入口节点:c_main 被声明为 __attribute__((noreturn)),说明启动成功后永不返回,最后进入 while(1){ wdt_clear(); } 的空循环——应用的所有行为都由 app() 内部创建的任务/消息机制驱动,主循环仅负责喂狗。
  • 掩膜 ROM 钩子:mask_init_for_app() 依据 config_exception_enable 决定是否注册异常分析钩子 exception_analyze;同时把 putchar、clk_get、device_status_emit 传给 ROM,使 ROM 层能复用应用侧打印、时钟与设备状态回调。
  • 依赖分层:中断(irq_init)→ GPIO(port_init)→ 串口日志(log_init)→ 时钟(pll_sel)→ 调试(debug_init)→ 看门狗(wdt_init)→ 内存(my_malloc_init)→ 定时器(sys_timer_init)→ 电源(sys_power_init)→ 系统(system_init)→ 应用(app)。下层服务建立后,上层服务才能安全使用。

启动流程详解

以下按 c_main() 的实际执行顺序逐段分析(对应源码 main.c)。

1. 段定位与启动期内存布局

main.c 顶部通过一组 #pragma 将启动代码固定在专用内存段:

#pragma bss_seg(".main.data.bss")
#pragma data_seg(".main.data")
#pragma const_seg(".main.text.const")
#pragma code_seg(".main.text")
#pragma str_literal_override(".main.text.const")

Source: main.c

设计意图:启动代码必须在链接脚本中放在固定、优先加载的位置(.main.text 段),确保上电后第一条 C 代码可用;str_literal_override 让字符串字面量也落入只读段,避免启动早期因数据段尚未搬移而访问到非法地址。

2. 关闭看门狗与掩膜初始化

__attribute__((noreturn))
void c_main(int cfg_addr)
{
    wdt_close();

    mask_init_for_app();
    irq_init();
    local_irq_disable();
    local_irq_enable();

Source: main.c

  • wdt_close():ROM 阶段可能已开启看门狗,进入用户固件先关闭,避免在后续初始化(可能耗时较长)中被意外复位。
  • mask_init_for_app():向掩膜 ROM 注册回调钩子,具体实现如下:
void mask_init_for_app(void)
{
    if (config_exception_enable) {
        mask_init(exception_analyze, putchar, clk_get, device_status_emit);
    } else {
        mask_init(NULL, putchar, clk_get, device_status_emit);
    }

//--------------------------
#if TFG_SD_EN
    sd_mask_init(5, NULL, NULL, NULL, NULL, sd_debug);
#endif
}

Source: main.c

  • exception_analyze 仅在 config_exception_enable 为真时传入(该符号为 extern const u8,由工程配置生成);为 NULL 时 ROM 使用默认异常处理。
  • sd_debug 钩子中特殊处理了 SD 卡超时事件 E_SD_RECEIVE_DATA_TIMEOUT / E_SD_SEND_ACMD41_TIMEOUT:此时主动 wdt_clear(),防止 SD 初始化慢速阶段触发看门狗复位——这是典型的慢速外设与看门狗协同设计。
  • TFG_SD_EN 使能时,sd_mask_init 以 idle_cnt_max=5 配置 SD 掩膜轮询。

3. GPIO、日志与时钟

    //上电初始化所有IO
    port_init();

    log_init(TCFG_UART_BAUDRATE);

    pll_sel(TCFG_PLL_SEL, TCFG_PLL_DIV, TCFG_HSB_DIV);

    dump_clock_info();

    debug_init();

Source: main.c

  • port_init():上电统一初始化所有 IO 引脚(默认状态/上下拉),防止悬空引脚造成异常功耗或干扰。
  • log_init(TCFG_UART_BAUDRATE):以工程配置的波特率初始化串口日志。从这里开始,后续任何步骤失败都能打印定位,因此它被安排在中断/GPIO 之后、复杂子系统之前。
  • pll_sel(...):根据 TCFG_PLL_SEL / TCFG_PLL_DIV / TCFG_HSB_DIV 完成 PLL 与高速总线分频配置,随后 dump_clock_info() 打印当前时钟树,便于核对频率是否符合预期。
  • debug_init():初始化调试器接口(如 SWD/JTAG 相关),供在线调试使用。

4. 看门狗、复位源与电源

    wdt_init(WDT_8S);

    P3_PINR_CON &= ~BIT(0); //关闭长按复位

    log_info("time & date %s %s \n  OTP c_main\n", __TIME__, __DATE__);

    my_malloc_init();
#if SYS_TIMER_EN
    sys_timer_init();
#endif
    power_reset_source_dump();
    power_wakeup_reason_dump();
    sys_power_init();

Source: main.c

  • wdt_init(WDT_8S):重新使能看门狗,超时窗口 8 秒。此后所有耗时操作都必须在该窗口内完成或被喂狗打断,否则系统复位——这也是为什么 app() 之后的主循环里持续 wdt_clear()。
  • P3_PINR_CON &= ~BIT(0):关闭长按复位功能,避免用户长按按键误触发硬件复位。
  • my_malloc_init():初始化应用堆管理器,后续所有动态内存分配依赖它。
  • sys_timer_init()(由 SYS_TIMER_EN 控制):初始化系统软定时器服务。
  • power_reset_source_dump() / power_wakeup_reason_dump():打印本次是上电复位、看门狗复位还是异常复位,以及唤醒源,是现场问题定位的第一手信息。
  • sys_power_init():初始化电源管理子系统(稳压器、低功耗模式参数等)。

5. 系统总初始化与应用入口

    system_init();
    app();
    while (1) {
        wdt_clear();
    }
}

Source: main.c

  • system_init():挂接全部系统服务(设备管理器、消息队列、文件系统等),是业务可运行前的最后一道初始化。
  • app():业务应用入口(extern void app(void); 声明,实现在各 app 目录),app() 内部通常创建消息循环/任务后返回,或自身成为任务主体。
  • 主循环 while(1){ wdt_clear(); }:c_main 声明为 noreturn,CPU 永不退出;喂狗循环保证即使业务任务崩溃,系统也不会因看门狗反复复位。

6. 启动期自实现的 C 库函数

main.c 还内嵌了 strchr、memcmp、strncmp、strlen 四个标准库函数的自实现:

char *strchr(const char *s, int c)
{
    const char ch = c;
    for (; *s != ch; ++s) {
        if (*s == '\0') {
            return (NULL);
        }
    }
    return ((char *) s);
}

Source: main.c

设计意图:在 .main.text 段被链接、且标准库可能尚未就绪的启动早期,这些轻量实现保证字符串/内存操作可用且不依赖 libc 初始化;io_debug() 则通过 JL_PORTC->OUT |= BIT(0) 提供一个极简的 GPIO 调试观察点,用于无串口环境下的启动状态指示。

Core Flow

下图以时序图展示 c_main() 与各子系统之间的交互顺序:

sequenceDiagram
    participant ROM as MaskROM
    participant CM as c_main (main.c)
    participant CLK as 时钟子系统
    participant DBG as 调试/日志
    participant WDT as 看门狗
    participant MEM as 内存管理
    participant PWR as 电源管理
    participant SYS as system_init
    participant APP as app()

    ROM->>CM: 跳转 c_main(cfg_addr)
    CM->>CM: wdt_close()
    CM->>ROM: mask_init(exception/putchar/clk_get/device_status)
    CM->>CM: irq_init + 局部中断开关
    CM->>CM: port_init() 上电初始化IO
    CM->>DBG: log_init(TCFG_UART_BAUDRATE)
    CM->>CLK: pll_sel(PLL_SEL/DIV/HSB_DIV)
    CLK-->>CM: dump_clock_info()
    CM->>WDT: wdt_init(WDT_8S)
    CM->>MEM: my_malloc_init()
    CM->>CM: sys_timer_init() (SYS_TIMER_EN)
    CM->>PWR: power_reset_source_dump / power_wakeup_reason_dump
    PWR-->>CM: sys_power_init()
    CM->>SYS: system_init()
    SYS-->>CM: 完成
    CM->>APP: app() 进入业务
    APP-->>CM: 返回/挂起
    CM->>CM: while(1) wdt_clear() 兜底

流程要点:

  1. 中断使能在 log_init 之前,保证串口驱动可中断收发;
  2. 时钟配置在日志初始化之后,因为 log_init 使用默认/低速时钟即可工作,而 PLL 切换可能引入短暂不稳定,日志先行可打印切换前后状态;
  3. 看门狗 8 秒窗口覆盖 system_init 与 app() 的全部剩余初始化;
  4. app() 之后 CPU 不再执行其他代码,仅喂狗,体现"初始化即主循环"的单线程事件驱动模型。

Usage Examples

完整启动入口(标准模板)

以下是从 main.c 提取的完整 c_main() 实现,是任何基于该 SDK 的工程启动模板:

__attribute__((noreturn))
void c_main(int cfg_addr)
{
    wdt_close();

    mask_init_for_app();
    irq_init();
    local_irq_disable();
    local_irq_enable();

    //上电初始化所有IO
    port_init();

    log_init(TCFG_UART_BAUDRATE);

    pll_sel(TCFG_PLL_SEL, TCFG_PLL_DIV, TCFG_HSB_DIV);

    dump_clock_info();

    debug_init();

    wdt_init(WDT_8S);

    P3_PINR_CON &= ~BIT(0); //关闭长按复位

    log_info("time & date %s %s \n  OTP c_main\n", __TIME__, __DATE__);

    my_malloc_init();
#if SYS_TIMER_EN
    sys_timer_init();
#endif
    power_reset_source_dump();
    power_wakeup_reason_dump();
    sys_power_init();

    system_init();
    app();
    while (1) {
        wdt_clear();
    }
}

Source: main.c

掩膜钩子注册(条件编译范例)

演示如何按配置开关差异化注册掩膜回调,并叠加 SD 卡掩膜初始化:

void mask_init_for_app(void)
{
    if (config_exception_enable) {
        mask_init(exception_analyze, putchar, clk_get, device_status_emit);
    } else {
        mask_init(NULL, putchar, clk_get, device_status_emit);
    }

//--------------------------
#if TFG_SD_EN
    sd_mask_init(5, NULL, NULL, NULL, NULL, sd_debug);
#endif
}

Source: main.c

配合使用的 SD 调试钩子(对慢速 SD 初始化做喂狗保护):

static void sd_debug(u32 idx)
{
    if (idx == E_SD_RECEIVE_DATA_TIMEOUT || idx == E_SD_SEND_ACMD41_TIMEOUT) {
        wdt_clear();
    }
}

Source: main.c

Configuration Options

启动流程行为由以下宏控制(定义于 app_config.h / config.h 等工程配置头文件,main.c 通过 #include "config.h" 与 #include "app_config.h" 引入):

配置宏类型典型值/默认影响
TCFG_UART_BAUDRATE整数如 115200/921600log_init 的串口日志波特率,决定启动日志输出速率
TCFG_PLL_SEL整数平台相关pll_sel 的 PLL 时钟源选择
TCFG_PLL_DIV整数平台相关PLL 分频系数,决定主频
TCFG_HSB_DIV整数平台相关高速总线(HSB)分频
WDT_8S常量8 秒wdt_init 的看门狗超时窗口
SYS_TIMER_EN0/11是否启用 sys_timer_init() 系统软定时器
TFG_SD_EN0/1按工程是否调用 sd_mask_init 初始化 SD 掩膜
config_exception_enableu8 常量按工程是否向 mask_init 注册 exception_analyze 异常分析钩子

说明:以上宏的具体取值以各工程 app_config.h 为准;main.c 仅消费这些宏,不在本文件内定义。

API Reference

void c_main(int cfg_addr)

应用固件 C 入口,由启动汇编/ROM 跳转进入,不返回。

Parameters:

  • cfg_addr (int): 配置区地址,由引导环境传入(本实现中未直接使用,预留给配置段读取)。

Returns: 无(__attribute__((noreturn)))。

行为: 依次执行看门狗关闭 → 掩膜初始化 → 中断/GPIO/日志/时钟/调试 → 看门狗/内存/定时器/电源 → system_init() → app() → 永久喂狗循环。

void mask_init_for_app(void)

封装掩膜 ROM 环境初始化。根据 config_exception_enable 决定异常钩子是否传入;TFG_SD_EN 使能时追加 sd_mask_init。

void mask_init(void *exp_hook, void *pchar, void *clk_hook, void *emit_hook)

(外部符号,声明于 main.c)向掩膜 ROM 注册钩子:

  • exp_hook:异常分析回调(可为 NULL)
  • pchar:字符打印回调(传入 putchar)
  • clk_hook:时钟查询回调(传入 clk_get)
  • emit_hook:设备状态上报回调(传入 device_status_emit)

void sd_mask_init(u32 idle_cnt_max, void *notify_hook, void *get_buf_hook, void *deal_event_hook, void *user_hookfun_hook, void *debug_hook)

(外部符号)SD 卡掩膜初始化,main.c 以 sd_mask_init(5, NULL, NULL, NULL, NULL, sd_debug) 调用,传入空闲计数上限 5 与调试钩子 sd_debug。

static void sd_debug(u32 idx)

SD 事件调试回调。对 E_SD_RECEIVE_DATA_TIMEOUT、E_SD_SEND_ACMD41_TIMEOUT 两类超时事件调用 wdt_clear() 防止慢速初始化触发复位。

启动期自实现库函数

strchr(const char *s, int c)、memcmp(const void *s1, const void *s2, size_t n)、strncmp(const char *s1, const char *s2, size_t n)、strlen(const char *s):与标准 C 库语义一致的精简实现,供启动早期无 libc 环境使用。

Failure Modes & Edge Cases

看门狗复位风险(启动慢速阶段)

  • 场景:wdt_init(WDT_8S) 使能后,若 system_init() / app() 初始化耗时超过 8 秒且无喂狗,系统将被复位。
  • 源码应对:sd_debug 中对 E_SD_RECEIVE_DATA_TIMEOUT 与 E_SD_SEND_ACMD41_TIMEOUT 调用 wdt_clear()(main.c),这是"慢速外设超时即喂狗"的标准处理。
  • 调试建议:若出现启动反复复位,优先看 power_reset_source_dump() 打印的复位源是否为 WDT 复位。

异常钩子使能分支

  • config_exception_enable 为 0 时,mask_init 第一个参数为 NULL,异常信息不会经过 exception_analyze 分析,排查 hardfault 时需要先确认该宏的值。
  • exception_analyze 在 main.c 中被注释掉(第 51-54 行),实际分析逻辑需由工程按需实现或依赖 ROM 默认处理。

段定位错误导致启动失败

  • c_main 及其依赖被 #pragma code_seg(".main.text") 固定在专用段。若链接脚本未正确装载 .main.text,上电后第一条 C 语句即跑飞。
  • str_literal_override(".main.text.const") 保证日志字符串也在该段,避免数据段未搬移时访问非法字符串地址。

IO 初始化顺序依赖

  • port_init() 在 log_init() 之前执行,若 UART 引脚状态配置错误(复用/上下拉),日志将无输出,启动定位只能依赖 io_debug() 的 GPIO 电平或调试器。

Concurrency & Consistency

  • 启动阶段为单线程顺序执行,local_irq_disable() / local_irq_enable() 对用于短暂临界区保护;进入 app() 后系统切换为中断 + 消息驱动的并发模型。
  • c_main 的 noreturn 语义保证启动代码不会与业务任务并发复用栈空间——启动完成后 CPU 永远停留在喂狗循环,业务逻辑完全交给 app() 创建的任务/中断。

Performance & Operational Notes

  • 启动耗时敏感点:log_init(波特率越高打印越快)、pll_sel(PLL 锁定时间)、SD 掩膜初始化(受卡速影响,故有超时喂狗保护)。
  • 内存占用:my_malloc_init() 建立的堆是所有动态分配的来源,堆大小由链接脚本决定,启动日志可观测剩余堆。
  • 现场信息:power_reset_source_dump() 与 power_wakeup_reason_dump() 打印复位源与唤醒源,是售后/产线问题分析的第一手证据,不建议裁剪。
  • 喂狗循环:主循环 wdt_clear() 是最后一道防线,业务任务必须自行保证不会长时间阻塞中断/任务调度,否则系统表现为周期性复位。

Extension Points

  • 自定义 app():业务入口按工程替换(mbox/flash 等不同 app 各有实现),c_main 无需改动。
  • 掩膜钩子替换:mask_init_for_app() 是 ROM 与应用交互的唯一窗口,可替换 putchar/clk_get/device_status_emit 以适配自定义外设或时钟方案。
  • 异常分析钩子:当 config_exception_enable 为真时,可实现自定义 exception_analyze(当前为注释状态)以捕获崩溃现场。
  • 配置宏覆盖:通过修改 TCFG_* 宏即可在不改 main.c 的前提下调整时钟树、串口波特率、看门狗窗口与功能开关(SYS_TIMER_EN、TFG_SD_EN)。

Tests

仓库中启动流程本身为硬件烧录验证路径(无独立单元测试框架)。工程层面的验证手段包括:

  • 上电后 log_info("time & date %s %s \n OTP c_main\n", ...) 打印出现 → 证明已进入 c_main 且日志链路正常;
  • dump_clock_info() 输出与预期频率比对 → 验证 PLL/分频配置;
  • power_reset_source_dump() 的复位源打印 → 验证复位路径(上电/WDT/异常);
  • 长按按键不复位 → 验证 P3_PINR_CON &= ~BIT(0) 生效。

Related Links

  • main.c(启动入口源码)
  • app_config.h(配置宏定义)
  • config.h(基础配置)
  • 电源管理与低功耗:参见电源子系统相关页面
  • 业务应用(mbox/flash)入口 app():参见 4-mbox-flash-app 目录下各应用页面
Next
应用配置系统