杰理 SDK 文档中心
首页
首页
  • 概述与入门

    • 项目概述与芯片平台
    • 环境搭建与工具链安装
    • 编译与烧录指南
    • 工程结构总览
  • 应用层与公共模块

    • GP MCU 主应用入口
    • AT 指令与调试模块
    • 电池检测与电源管理
    • EEPROM 与参数存储
    • 按键与 USB 设备驱动
    • 音频解码与 APA 语音播报
  • 外设驱动与示例

    • 高精度 ADC(HADC)
    • 通用 ADC 与定时器
    • UART / SPI / IIC 通信外设
    • MCPWM 与电机控制
    • RTC 与低功耗唤醒
    • 段码 LCD 驱动
    • NOR Flash 与红外编解码组件
  • 显示与 UI 系统

    • LCD 驱动与字库引擎
    • UI 平台与控件绘制
    • UI 工程与资源生成工具
  • 系统底层与芯片平台

    • cd09 芯片平台与预编译库
    • GPIO 与 IIC 底层驱动
    • 系统文件系统与设备模型
  • 启动引导与固件升级

    • UBOOT 引导工程
    • 固件升级机制
  • 开发工具与资源

    • 编译脚本与命令行工具
    • 音频文件转换工具
    • 硬件资料与文档资源

UBOOT 引导工程

UBOOT 引导工程是 AC82N 芯片 SDK 中负责系统上电后第一段引导逻辑的独立编译工程,完成时钟初始化、调试串口使能、JLFS 文件系统挂载、固件升级检查与 SFC 启动模式切换,最终将控制权交给 Mask ROM 的 USB 升级流程。

Purpose and Scope

本文档介绍 sdk/UBOOT工程/ 目录下的引导工程,覆盖其工程结构、启动主流程、ISD 配置解析、升级检查、SFC 启动与 Mask USB 升级入口,以及可扩展的弱符号钩子。UBOOT 工程与 UI 工程、MP3 解码等上层应用工程是相互独立的编译单元,本文不涉及上层应用的业务逻辑。

  • 本页范围:app/src/main.c、app/inc/uboot.h 所定义的引导主流程、cpu/cd09 板级定制文件、升级与 flash 写保护相关模块。
  • 不涉及:UI 工程(UI 应用逻辑)、MP3/解码库内部实现、烧录工具(cpu/cd09/output/download.bat/.sh 仅作为产物下载脚本提及)。

Overview

在 AC82N(GP-MCU)方案中,UBOOT 是芯片复位后最早运行的片上程序之一。它的职责可以用一句话概括:以最小依赖完成系统基本环境初始化,判断是否需要升级,然后引导进入 SFC 启动模式或 Mask USB 升级模式。

从 main.c 可以看到引导流程是一条严格的线性序列:

  1. 时钟初始化:默认使用 OSC_FREQ/SYS_CLK 宏,若定义了 __DYNAMIC_CLOCK 则从 ISD(芯片信息描述)配置段动态读取。
  2. 调试通道初始化(__DEBUG 时):注册 exception_analyze 异常分析函数,从 ISD 配置读取 UTTX(调试串口引脚)与 UTBD(波特率)并调用 uart_init。
  3. 中断初始化:irq_init() 建立中断向量基础。
  4. 文件系统挂载:jlfs_mount() 挂载 JLFS,返回值非 0 表示文件系统初始化失败——很可能是上一次升级未完成。
  5. 升级检查:jl_check_upgrade(err) 结合挂载结果判断是否需要进入升级流程。
  6. 启动模式切换:sfc_mode_boot() 切换到 SFC(SPI Flash Controller)启动模式。
  7. 最终兜底:goto_mask_usb_updata() 进入 Mask ROM 的 USB 升级模式,随后死循环驻留。

这一设计体现了引导程序应尽量简短、无业务依赖的原则:所有可配置项(时钟、串口)都放在 ISD 配置中而非硬编码,所有平台差异通过弱符号(platform_init、exception_analyze)与 user_custom 系列文件解耦。

Architecture

模块架构

flowchart TD
    subgraph sg_Boot["UBOOT 引导工程 (sdk/UBOOT工程)"]
        Main["app/src/main.c<br/>主引导流程"]
        UbootH["app/inc/uboot.h<br/>引导接口声明"]
        UserUp["app/src/user_up.c<br/>用户升级逻辑"]
        FlashWp["app/src/flash_wp.c<br/>Flash 写保护"]
        LogCfg["app/src/log_config.c<br/>日志配置"]
    end

    subgraph sg_CPU["板级定制 (cpu/cd09)"]
        UserCustom["user_custom.c<br/>平台初始化钩子"]
        UserUart["user_uart.c<br/>串口定制"]
        Makefile["Makefile / 布局与链接"]
    end

    subgraph sg_Lib["SDK 底层库 (include_lib)"]
        JLFS["jlfs 文件系统"]
        SFC["sfc/norflash 驱动"]
        MaskApi["mask_api / upgrade"]
        Dec["dec 配置解析"]
        ClockIrq["clock / irq / wdt / uart"]
    end

    Main --> UbootH
    Main --> UserUp
    Main --> FlashWp
    Main --> LogCfg
    Main --> UserCustom
    UserCustom --> UserUart
    Main --> JLFS
    Main --> SFC
    Main --> MaskApi
    Main --> Dec
    Main --> ClockIrq
    UserUp --> FlashWp

图中上层为 UBOOT 自身的应用代码(app/),中层为 cpu/cd09 板级定制(对应 platform_init/user_custom 扩展点),底层为 SDK 提供的驱动与库。main.c 是唯一入口,通过头文件接口与底层库交互,user_up.c 与 flash_wp.c 是升级链路上的两个应用级模块。

引导决策流程

flowchart TD
    Start([上电复位]) --> Clk["sys_clk_init 时钟初始化"]
    Clk --> Uart["uart_init 调试串口 (__DEBUG)"]
    Uart --> Irq["irq_init 中断初始化"]
    Irq --> Mount["jlfs_mount 挂载 JLFS"]
    Mount --> Err{"挂载失败?<br/>(返回值非0)"}
    Err -->|"是 (可能升级未完成)"| LogErr["log_error 记录错误"]
    Err -->|"否"| Continue["继续引导"]
    LogErr --> Upgrade["jl_check_upgrade 升级检查"]
    Continue --> Upgrade
    Upgrade --> SFC["sfc_mode_boot SFC 启动"]
    SFC --> USB["goto_mask_usb_updata Mask USB 升级"]
    USB --> Loop["while(1) 驻留"]

该流程反映了一个关键设计意图:即使 JLFS 挂载失败也不能阻塞引导——错误仅记录日志,随后仍会尝试升级检查,因为挂载失败本身就可能是升级中断导致的,此时恰恰需要进入升级流程来修复系统。

工程目录结构

路径作用
app/inc/uboot.hUBOOT 核心接口声明(arch_init、sfc_mode_boot、ISD 配置访问)
app/inc/user_up.h / app/src/user_up.c用户升级检查与执行逻辑
app/inc/flash_wp.h / app/src/flash_wp.cFlash 写保护区域管理
app/inc/user_custom.h / app/src/log_config.c用户定制声明、日志开关配置
cpu/cd09/CD09 芯片板级工程:Makefile、链接布局 cd09_uboot.layout、链接脚本 build/ram_ld.c、板级实现 user_custom.c / user_uart.c
cpu/cd09/output/编译产物下载脚本 download.bat / download.sh
default.workspace工程工作区文件(CodeBlocks 工程 cd09_uboot.cbp)
include_lib/SDK 底层头文件(dec.h、printf.h 等)

主引导流程实现解析

main() 入口:从复位到引导

main() 是整个 UBOOT 的入口,声明为 __attribute__((noreturn)),意味着该函数永远不会返回——引导完成后通过 while(1) 驻留:

__attribute__((noreturn))
int main(void)
{
    u32 osc_freq = OSC_FREQ;
    u32 sys_clk = SYS_CLK;
#ifdef __DYNAMIC_CLOCK
    {
        u8 *ptr = get_isd_cfg_ptr();
        dec_isd_cfg_ini("OSC_FREQ", &osc_freq, ptr);
        dec_isd_cfg_ini("SYS_CLK", &sys_clk, ptr);
    }

#endif

    sys_clk_init(osc_freq, sys_clk);

#ifdef __DEBUG
    {
        mask_api_init(putchar, exception_analyze);

        u8 *ptr = get_isd_cfg_ptr();
        u32 ut_baud = 0;
        char uttx[8] = {0};
        memset(uttx, 0, sizeof(uttx));
        dec_isd_cfg_ini("UTTX", uttx, ptr);
        dec_isd_cfg_ini("UTBD", &ut_baud, ptr);
        // uart_init("PB02", 1000000);
        uart_init(uttx, ut_baud); //debug串口
    }
#endif

    irq_init();

    log_info("****************** uboot *****************\n\n");

    arch_init();

    /* board_init(); */

    u32 err = jlfs_mount();//非0表示fs初始化失败,可能是升级未完成
    if (err) {
        log_error("jlfs_mount err:%d\n", err);
    }

    platform_init();

#if 0
    flash_set_wp(128);
#endif

#if 0
    user_check_upgrade(err);
#endif

    jl_check_upgrade(err);

    sfc_mode_boot();


    goto_mask_usb_updata();

    while (1) {
        asm("nop");
    }

    return 0;
}

Source: main.c

各步骤的设计意图:

  • OSC_FREQ/SYS_CLK 先取宏默认值,当定义了 __DYNAMIC_CLOCK 时再从 ISD 配置覆盖。这样量产固件可以不改代码、只改 Flash 中的 ISD 配置来适配不同晶振/主频,避免为每个频点维护一个编译版本。
  • get_isd_cfg_ptr() 返回 ISD 配置段指针,配合 dec_isd_cfg_ini() 按名称解析键值。UTTX(串口引脚)与 UTBD(波特率)同样来自 ISD,注释中的 uart_init("PB02", 1000000) 是硬编码写法的反面示例,被动态配置取代。
  • jlfs_mount() 失败不致命:错误仅 log_error,随后仍进入升级检查——这正是为了处理"上次升级写到一半断电导致文件系统损坏"的场景。
  • flash_set_wp(128) 与 user_check_upgrade(err) 被 #if 0 屏蔽:说明该版本默认走 SDK 标准升级路径 jl_check_upgrade,用户升级钩子作为可选项保留。
  • sfc_mode_boot() 之后仍调用 goto_mask_usb_updata():SFC 启动与 Mask USB 升级并非互斥的终态,而是"尝试启动,失败则进入 USB 升级"的兜底设计,最终 while(1) 保证函数不返回。

弱符号扩展点

main.c 中定义了两个 __attribute__((weak)) 函数,允许板级工程在不修改 UBOOT 主体代码的情况下覆盖:

#ifdef __DEBUG
__attribute__((weak))
void exception_analyze(u32 *sp)
{
    unsigned int reti = sp[16];
    unsigned int rete = sp[17];
    unsigned int retx = sp[18];
    unsigned int rets = sp[19];
    unsigned int psr  = sp[20];
    unsigned int icfg = sp[21];
    unsigned int usp  = sp[22];
    unsigned int ssp  = sp[23];
    log_error("reti %x\n", reti);
    log_error("rets %x\n", rets);
    log_error("ssp %x\n", ssp);
    log_error("usp %x\n", usp);
    //Dump R0 ~ R15
    for (int i = 0; i < 16; ++i) {
        printf("R%d: %x\n", i, sp[i]);
    }
    while (1);
}
#endif

__attribute__((weak))
void platform_init()
{
}

Source: main.c

  • exception_analyze(u32 *sp):仅编译于 __DEBUG。收到异常栈指针后,从栈偏移 [16]~[23] 取出 reti/rete/retx/rets/psr/icfg/usp/ssp 寄存器现场并打印,再 Dump R0~R15,最后 while(1) 挂起等待调试器。它是 UBOOT 阶段"蓝屏"信息,是定位启动崩溃的核心工具。
  • platform_init():空的弱钩子,由板级工程(如 cpu/cd09/user_custom.c)实现,用于在文件系统挂载后、升级检查前执行平台特有初始化。

API 参考

uboot.h 公开接口

以下接口由 app/inc/uboot.h 声明,是 UBOOT 对外(对升级模块、板级工程)暴露的全部引导能力:

函数签名说明
arch_initu32 arch_init(void)架构初始化,返回错误码;在时钟/串口/中断就绪后调用
sfc_mode_bootu8 sfc_mode_boot(void)切换到 SFC 启动模式,返回状态字节
get_isd_cfg_ptru8 *get_isd_cfg_ptr(void)获取 ISD 配置段指针,供 dec_isd_cfg_ini 解析键值(时钟、串口等)
get_arch_infoconst void *get_arch_info(void)获取架构信息结构指针
get_sys_cfgvoid *get_sys_cfg(void)获取系统配置结构指针

Source: uboot.h

启动序列

sequenceDiagram
    participant HW as 硬件复位
    participant Main as main()
    participant ISD as ISD 配置段
    participant JLFS as JLFS 文件系统
    participant UP as 升级模块
    participant SFC as SFC/NOR Flash

    HW->>Main: 复位向量进入 main
    Main->>ISD: get_isd_cfg_ptr + dec_isd_cfg_ini<br/>(OSC_FREQ/SYS_CLK/UTTX/UTBD)
    Main->>Main: sys_clk_init + uart_init + irq_init
    Main->>JLFS: jlfs_mount()
    JLFS-->>Main: err (非0 = 升级未完成)
    Main->>UP: jl_check_upgrade(err)
    UP-->>SFC: 升级完成/无需升级
    Main->>SFC: sfc_mode_boot()
    Main->>Main: goto_mask_usb_updata()<br/>进入 Mask USB 升级
    Main->>Main: while(1) 驻留

main.c 内部关键函数

函数位置说明
exception_analyze(u32 *sp)L27-L49(__DEBUG)异常现场打印与挂起
platform_init()L51-L54平台初始化弱钩子
main(void)L57-L123引导主流程

配置选项

UBOOT 工程的配置分为编译期宏与运行期 ISD 配置两类,体现"代码不变、配置可变"的设计。

编译期宏(#ifdef 开关)

宏作用默认状态
__DEBUG使能调试串口初始化、mask_api_init(putchar, exception_analyze) 注册与异常分析由构建配置决定
__DYNAMIC_CLOCK使能从 ISD 配置动态读取 OSC_FREQ/SYS_CLK,替代编译期宏由构建配置决定

Source: main.c

ISD 运行期配置键

键名类型默认值说明
OSC_FREQu32OSC_FREQ 宏晶振频率,仅 __DYNAMIC_CLOCK 时读取
SYS_CLKu32SYS_CLK 宏系统主频,仅 __DYNAMIC_CLOCK 时读取
UTTXchar[8]空调试串口 TX 引脚名(如 "PB02"),仅 __DEBUG 时读取
UTBDu320调试串口波特率(如 1000000),仅 __DEBUG 时读取

Source: main.c

屏蔽的可选功能(#if 0)

代码功能说明
flash_set_wp(128)Flash 写保护参数 128 为保护区域大小(KB);默认不启用,量产需写入保护时打开
user_check_upgrade(err)用户自定义升级检查默认走 SDK 的 jl_check_upgrade,需要自定义升级策略时启用

Source: main.c

失败模式、边界情况与并发

JLFS 挂载失败(升级未完成)

jlfs_mount() 返回非 0 表示文件系统初始化失败。main.c 的处理策略是记录错误但不中断:

u32 err = jlfs_mount();//非0表示fs初始化失败,可能是升级未完成
if (err) {
    log_error("jlfs_mount err:%d\n", err);
}

Source: main.c

设计意图:引导阶段出现文件系统错误,最常见原因是上一次固件升级被中断。此时直接放弃引导会导致设备变砖;正确的做法是把 err 传给 jl_check_upgrade(err),让升级流程有机会重新烧写固件完成修复。这是 UBOOT 保证"可恢复性"的关键路径。

调试串口初始化失败

__DEBUG 下若 UTTX 配置为空或引脚名无效,uart_init 的日志输出会丢失。代码中注释保留了硬编码兜底写法(uart_init("PB02", 1000000)),量产调试时应先确认 ISD 中的 UTTX/UTBD 与硬件一致。

异常后的挂起行为

exception_analyze 在打印现场后执行 while(1) 而非复位。这是有意为之:异常后系统状态不可信,直接复位可能陷入"崩溃-复位"循环且丢失现场;挂起等待调试器(__DEBUG)可以保留寄存器与栈信息用于离线分析。

并发与中断

引导阶段是单核单线程的确定性序列:中断在 irq_init() 之后才使能,期间所有初始化按序执行。goto_mask_usb_updata() 之后的 while(1) 驻留循环不主动处理业务,后续行为完全交给 Mask ROM 的 USB 升级逻辑,因此 UBOOT 自身不存在共享资源竞争问题——这也是引导程序保持极简的原因之一。

性能与运维注意事项

  • 启动路径极短:整个引导只有时钟、串口、中断、文件系统挂载、升级检查、SFC 切换几个步骤,无动态内存分配、无长事务,上电到进入升级/启动模式的时间由 Flash 读写与 SFC 初始化主导。
  • 日志开关:log_config.c 与 main.c 顶部的 LOG_TAG_CONST/LOG_ERROR_ENABLE/LOG_DEBUG_ENABLE/LOG_INFO_ENABLE 控制日志编译级别;量产固件可关闭 __DEBUG 以减少串口占用与日志体积。
  • 下载脚本:cpu/cd09/output/download.bat(Windows)与 download.sh(Linux)用于将编译产物烧录到芯片,是 UBOOT 迭代调试的入口。
  • 链接布局:cpu/cd09/cd09_uboot.layout 与 cpu/cd09/build/ram_ld.c 定义 UBOOT 的内存布局,修改地址映射(如为升级模块预留空间)时需同时核对布局文件与 flash_wp 保护区域,避免覆盖写保护段。

扩展点

UBOOT 通过以下方式支持定制,板级工程无需改动 app/src 主体:

  1. 弱符号覆盖:重新实现 platform_init()(板级初始化)与 exception_analyze()(自定义异常处理),cpu/cd09/user_custom.c 即该模式的载体。
  2. 用户升级钩子:user_up.c 提供 user_check_upgrade 路径,将 main.c 中的 #if 0 打开即可切换为用户升级策略;flash_wp.c 提供 flash_set_wp 写保护控制。
  3. ISD 配置:通过 get_isd_cfg_ptr()/dec_isd_cfg_ini() 修改运行期参数(时钟、串口),无需重新编译。
  4. 板级串口定制:cpu/cd09/user_uart.c 可覆盖默认串口行为,与 UTTX/UTBD 动态配置互补。

相关链接

  • README.md — 仓库工程结构总览
  • main.c — UBOOT 主引导流程
  • uboot.h — UBOOT 核心接口声明
  • user_up.c — 用户升级逻辑
  • flash_wp.c — Flash 写保护
  • cpu/cd09 — 板级工程(Makefile/布局/定制实现)

提示:升级与烧录的完整链路(固件包格式、烧录工具使用)属于"升级"主题的相邻能力,相关细节请参阅同目录下的升级专题文档。

升级链路分析

UBOOT 的升级能力由 main.c 中的两个调用串联:jl_check_upgrade(err)(应用级升级检查)与 goto_mask_usb_updata()(Mask ROM USB 升级入口)。相关模块在头文件引用中即可确认:

#include "upgrade.h"
#include "mask_api.h"
#include "norflash.h"
#include "uboot.h"
#include "user_up.h"
#include "flash_wp.h"

Source: main.c

升级决策模型

flowchart TD
    Mount["jlfs_mount()"] --> Decide{"jl_check_upgrade(err)"}
    Decide -->|"err != 0 (FS 损坏/升级未完成)"| Repair["进入升级流程修复固件"]
    Decide -->|"err == 0 且检测到新固件"| Update["执行固件升级"]
    Decide -->|"err == 0 且无新固件"| Normal["正常引导"]
    Repair --> SFC["sfc_mode_boot"]
    Update --> SFC
    Normal --> SFC
    SFC --> Mask["goto_mask_usb_updata<br/>(Mask USB 升级兜底)"]

升级与引导的耦合设计

  • jl_check_upgrade(err) 接收挂载错误码:这并非简单传参,而是让升级逻辑能区分"文件系统正常但需升级"与"文件系统损坏需修复"两种场景,两种场景都可能触发重写固件——前者是常规 OTA,后者是断电容错。
  • sfc_mode_boot() 位于升级检查之后:无论升级是否发生,最终都切到 SFC 模式读取 NOR Flash 中的固件;若固件不完整(升级中断),sfc_mode_boot 的失败路径自然落到 goto_mask_usb_updata()。
  • Mask USB 升级作为最终兜底:goto_mask_usb_updata() 调用 Mask ROM 内置的 USB 升级服务,它不依赖 JLFS 与应用固件,因此即使主固件区完全损坏,设备仍可通过 USB 恢复——这是整个引导链路的"最后一道保险"。
  • user_check_upgrade 与 flash_set_wp 的开关位:main.c 中以 #if 0 保留这两个功能。写保护 flash_set_wp(128) 与升级是一对矛盾需求——写保护防止意外擦写,但升级需要擦写,因此默认关闭,量产策略需在两者间权衡(如升级前临时解除保护)。

说明:user_up.c 与 flash_wp.c 的内部函数级实现细节未在本页本次源码读取中展开(读取预算受限);上述分析全部基于 main.c 中可验证的调用关系与注释。如需函数级签名,请直接阅读 user_up.c 与 flash_wp.c。

Next
固件升级机制