芯片平台与启动流程
本文档介绍 AD24N(SH58 系列)MCU SDK 的芯片平台构成与从芯片上电复位到应用程序运行之间的完整启动链路:MaskROM → UBoot → App 三级引导,以及各级之间通过 boot_info 传递的平台信息(Flash 布局、SFC 映射、芯片 ID、Trim 值等)。
Purpose and Scope
本页覆盖以下内容:
- SH58 芯片平台的分级引导架构(MaskROM / UBoot / App)与各阶段职责
- UBoot 与 App 之间的握手数据结构:
BOOT_INFO、vm_info、sfc_info、flash_head、BOOT_DEVICE_INFO - App 侧启动入口
c_main()的完整执行顺序:maskrom_init→ 看门狗 → 日志 → 电源/时钟不可变初始化 →system_init→app() - 电源与时钟初始化流水线
immutable_initialize_clock_power()的顺序约束 - Flash 物理地址与 CPU 逻辑地址之间的换算规则,以及 VM 参数区的初始化流程
以下主题属于同平台的其他页面,本页只做必要的交叉引用,不展开:Flash 文件系统与 VM 存储(sdfile/vm 读写细节)、SFC 控制器驱动、电源管理(power_early_flowing/power_later_flowing 内部实现)、时钟树配置(clk_early_init 内部实现)、OTA 升级流程。
Overview
AD24N 是杰理(Jieli)科技 SH58 系列单芯片 MCU,片上集成 CPU、SPI Flash 控制器(SFC)、电源管理单元(PMU)、时钟发生器(PLL/LRC)以及丰富的音频外设。SDK 中 sdk/app/bsp/start/sh58/ 目录承载了该平台的启动代码。
芯片的启动是一个"接力"过程:
- MaskROM:芯片出厂固化的一级引导,负责最基础的初始化,并通过
struct maskrom_argv回调(putchar、异常分析、中断开关)把控制权交给 UBoot。 - UBoot:二级引导,完成 Flash 探测、SFC 映射建立、芯片 ID/Trim 值读取,并把结果打包成
BOOT_INFO结构传递给 App。 - App(用户固件):从
c_main(int cfg_addr)进入,先做不可变电源/时钟初始化,再初始化系统服务,最后调用业务入口app()。
这种分层设计的意图在于:把硬件相关的、对时序极其敏感的初始化(电源、时钟、SFC 映射)固化为不可变流程,而把业务逻辑隔离在 app() 之后,从而降低应用开发者的出错面。代码中明确注释"此函数涉及电源和时钟的初始化,有严格的顺序要求,严禁对此函数的修改",体现了平台对初始化顺序的强约束。
Architecture
下图展示了 SH58 平台的分级引导架构与各级之间的数据交接关系:
flowchart TD
subgraph sg_Stage1["一级引导 MaskROM"]
MaskROM["MaskROM<br/>(芯片出厂固化)"]
end
subgraph sg_Stage2["二级引导 UBoot"]
UBoot["UBoot<br/>探测 Flash / SFC 映射<br/>读取 chip_id / trim_value"]
end
subgraph sg_Stage3["三级引导 App 固件"]
CMain["c_main(cfg_addr)"]
MaskromInit["maskrom_init()<br/>注册异常/打印回调"]
Wdt["wdt_init(WDT_8S)"]
Log["log_init(1000000)"]
Immutable["immutable_initialize_clock_power()<br/>电源+时钟顺序初始化"]
SysInit["system_init()"]
App["app() 业务入口"]
MainLoop["while(1){ wdt_clear(); }"]
end
subgraph sg_Data["阶段间数据交接"]
BootInfo["BOOT_INFO<br/>vm / sfc / flash_size<br/>chip_id / trim_value"]
FlashHead["flash_head<br/>crc / FlashSize / pid"]
end
MaskROM -->|"maskrom_argv 回调<br/>putchar/exp_hook"| UBoot
UBoot -->|"BOOT_INFO / BOOT_DEVICE_INFO"| CMain
CMain --> MaskromInit
MaskromInit --> Wdt
Wdt --> Log
Log --> Immutable
Immutable --> SysInit
SysInit --> App
App --> MainLoop
BootInfo --> CMain
FlashHead --> UBoot
各环节职责说明:
- MaskROM:
maskrom_init()填充struct maskrom_argv(打印函数、异常分析钩子、本地中断开关、软浮点支持),并注册 IRQ1/7 的异常中断处理,再调用mask_init(&argv)与emu_init()完成仿真/调试环境初始化(见 main.c)。 - UBoot:负责探测 SPI Flash 容量、建立 SFC 基地址到 CPU 逻辑地址的映射,读取芯片
chip_id、trim_value(Trim 校准值),并将这些信息写入BOOT_INFO供 App 使用。 - App 启动:
c_main()先做最少的运行时准备(MaskROM 回调、看门狗、串口日志),随后立即执行电源/时钟不可变初始化,最后进入系统与业务初始化。 - 数据交接:
boot_info是全局结构体(extern BOOT_INFO boot_info;),App 启动后可直接读取其中的 Flash 布局与芯片参数;flash_head描述 Flash 文件系统的头部信息(CRC、容量、PID 等),供 UBoot 校验与定位。
核心数据结构:UBoot 与 App 的握手协议
启动链路中最关键的部分是阶段之间共享的数据结构,全部定义在 sdk/include_lib/common/boot.h。
vm_info:VM 参数区描述
struct vm_info {
#if (USE_SYDFILE_NEW == 1)
u8 align; //from uboot, 按 n * 256 对齐
#endif
u32 vm_saddr; //from sdfile, flash addr
u32 vm_res; //reverse_bytes
u32 vm_size; //from sdfile
};
Source: boot.h
vm_info 描述 VM(掉电保持参数)区在 Flash 中的位置:vm_saddr 是起始 Flash 地址(来自 sdfile 文件系统解析结果),vm_size 是区域大小,align 表示按 n * 256 字节对齐。VM 区由 UBoot 定位、由 App 侧文件系统解析后回填,是"先有文件系统、后有 VM 参数"这一设计顺序的体现。
sfc_info:SFC 地址映射
struct sfc_info {
u32 sfc_base_addr; //flash memory addr, from uboot
u32 app_addr; //cpu logic addr, from uboot
};
Source: boot.h
sfc_info 记录 SFC 控制器的地址映射关系:sfc_base_addr 是 Flash 的 memory 映射基地址,app_addr 是对应的 CPU 逻辑地址。两者之差就是 Flash 物理地址与 CPU 逻辑地址的换算偏移,App 通过该结构把 Flash 地址翻译成可直接寻址的内存地址。
BOOT_INFO:App 侧的全局启动信息
typedef struct _boot_info {
struct vm_info vm;
struct sfc_info sfc;
u32 flash_size; //from uboot
u16 chip_id; //from uboot
u16 trim_value; //from uboot
// struct bt_mac_addr mac;
u32 up_suc_flag;
} BOOT_INFO;
Source: boot.h
BOOT_INFO 聚合了 App 启动所需的所有平台参数:VM 区描述、SFC 映射、总 Flash 容量、芯片 ID、Trim 校准值,以及 up_suc_flag(升级成功标志,供升级流程判断上次升级是否完成)。bt_mac_addr 结构被注释掉,说明该平台蓝牙 MAC 不再经由此处传递,而由独立路径管理。
flash_head 与 BOOT_DEVICE_INFO:UBoot 侧的文件系统头部
struct flash_head {
u16 crc;
u16 size4burner;
u8 vid[4]; //u32 vm_eaddr;
u32 FlashSize;
u8 FsVersion; //flash文件结构
u8 align; //对齐 n * 256
u8 res;
u8 SpecialOptFlag;
u8 pid[16];
};
typedef struct boot_device_info {
struct flash_head *fs_info;
struct sfc_info sfc;
u16 chip_id; //from uboot
u16 trim_value; //from uboot
u8 bt_mac_addr[8];
} BOOT_DEVICE_INFO;
Source: boot.h
flash_head 是 Flash 文件系统的头部描述:crc 用于校验、FlashSize 标记容量、FsVersion 标记文件结构版本、pid[16] 是产品标识、SpecialOptFlag 携带特殊烧录选项。BOOT_DEVICE_INFO 是 UBoot 视角的"设备信息包",包含文件系统头指针、SFC 映射、芯片 ID、Trim 值以及蓝牙 MAC 地址——相比 BOOT_INFO,它还携带 MAC,说明 UBoot 阶段就已获取 MAC 供早期使用。
数据结构选择上,USE_SYDFILE_NEW == 1 编译开关控制新旧文件系统的兼容:开启新文件系统时 vm_info 增加 align 字段,并启用 struct bt_mac_addr 的独立定义。这种宏隔离让同一份 boot.h 同时服务于新旧两代 Flash 文件系统的产品。
启动入口 c_main() 的完整执行流程
App 固件的 C 入口是 c_main(int cfg_addr),位于 sdk/app/bsp/start/sh58/main.c。它的执行顺序经过精心设计,每一阶段都有明确的约束:
int c_main(int cfg_addr)
{
maskrom_init();
wdt_init(WDT_8S);
register_handle_printf_putchar(putchar);
log_init(1000000);
log_info("--------sh58 apps-------------\n");
immutable_initialize_clock_power();
log_info("hello world\n");
system_init();
app();
while (1) {
wdt_clear();
}
return 0;
}
Source: main.c
逐步拆解:
maskrom_init():与 MaskROM 建立回调契约。关键点在于argv.pchar = (void *)putchar——注释明确指出 "sh58 maskrom 没有包含 print 驱动",因此必须由 App 提供打印函数;同时注册exception_analyze作为异常分析钩子,并在 IRQ1/7 上挂接exception_irq_handler,保证早期异常可被捕获分析。之后调用mask_init(&argv)完成 MaskROM 交接、emu_init()初始化仿真/调试环境(见 main.c L39-L54)。wdt_init(WDT_8S):在进入任何耗时初始化之前就打开 8 秒看门狗,避免初始化死循环导致系统挂死不可恢复——这是"先保命、后干活"的健壮性设计。register_handle_printf_putchar(putchar)+log_init(1000000):注册打印通道(波特率 1M)并初始化日志系统,使后续所有阶段可输出调试信息。immutable_initialize_clock_power():电源与时钟的不可变初始化(详见下一节),是整个启动流程中时序最敏感的部分。system_init():初始化系统服务(内存管理、中断管理、设备驱动注册等),为业务层提供运行时环境。app():进入业务入口。返回后进入while(1) { wdt_clear(); }主循环持续喂狗。app()设计为不返回,主循环只是兜底保护。
电源与时钟不可变初始化
immutable_initialize_clock_power() 是整个平台启动的核心,函数注释以"此函数严禁修改!!!!!!!"强调其不可变性,因为电源与时钟的初始化存在严格的先后依赖:
void immutable_initialize_clock_power()
{
//此函数涉及电源和时钟的初始化,有严格的顺序要求,严禁对此函数的修改。
efuse_init();
critical_hook_init();
early_system_init();
clock_set_sfc_max_freq(SPI_MAX_CLK);
clk_voltage_init(CLOCK_MODE_ADAPTIVE, DVDD_VOL_123V);
//----clk_early_init函数运行期间禁止打印,否则有死机风险!!!!!!!!
//----clk_early_init运行启动会停时钟,所有有参考时钟的外设全部会受影响,影响不限于程序卡死及外设运行失常。
register_handle_printf_putchar(NULL);
clk_early_init(PLL_REF_LRC, 200000, PLL_MAX_LIMIT);
register_handle_printf_putchar(putchar);
power_early_flowing();
//----sys_pll_ldo_trim_check函数运行期间禁止打印,否则有死机风险!!!!!!!!
//----sys_pll_ldo_trim_check运行启动会停时钟,所有有参考时钟的外设全部会受影响,影响不限于程序卡死及外设运行失常。
sys_pll_ldo_trim_check();//该函数会短暂关闭pll,std
board_power_init();
clock_dump();
efuse_dump();
power_later_flowing();
}
Source: main.c
顺序背后的设计意图:
efuse_init():最先读取 eFuse 中的芯片校准信息(Trim 值、PLL 参数等),后续所有时钟/电压设置都依赖这些出厂数据。critical_hook_init():注册临界区钩子(enter_critical_hook/exit_critical_hook),确保后续初始化期间中断保护机制可用(见 main.c L33-L37)。early_system_init():极早期的系统级初始化(中断向量、基础外设时钟门控)。clock_set_sfc_max_freq(SPI_MAX_CLK)+clk_voltage_init(CLOCK_MODE_ADAPTIVE, DVDD_VOL_123V):先设定 SFC 最高时钟频率上限,再以自适应模式配置核心电压(DVDD 1.23V)——电压必须先于高频时钟建立,否则高频切换会因电压不足而不稳定。clk_early_init(PLL_REF_LRC, 200000, PLL_MAX_LIMIT):以 LRC 为 PLL 参考时钟建立主时钟树。该函数运行期间禁止打印,因为运行时会停时钟,打印(依赖时钟的 UART)会导致死机——所以前后用register_handle_printf_putchar(NULL)/(putchar)临时屏蔽打印。power_early_flowing()/power_later_flowing():电源管理的"早期流水"与"后期流水"两阶段,分别在时钟建立前后配置电源域与 LDO。sys_pll_ldo_trim_check():会短暂关闭 PLL 与 STD 电源做 Trim 校验,同样禁止打印,随后恢复。board_power_init():初始化板级电源(外设供电)。clock_dump()/efuse_dump():输出最终时钟树与 eFuse 校准结果,便于产线/调试确认初始化正确性。
异常与断言钩子
void cpu_assert_debug(void)
{
/* 用户自行决定断言操作 */
/* 默认不操作 */
}
Source: main.c
cpu_assert_debug() 是断言处理钩子,默认空实现,留给用户自定义(如点亮 LED、进入低功耗或打印调用栈)。这体现了 SDK 在"平台强约束"与"用户可扩展"之间的平衡:初始化主流程不可改,但故障处理策略可定制。
VM 参数区与 SFC 地址映射的初始化
App 启动后,VM 参数区与 SFC 映射信息由 sdk/app/bsp/start/sh58/init.c 中的初始化逻辑完成回填与下发。该文件通过全局 boot_info 读取 UBoot 传入的平台信息,再与 Flash 文件系统(sdfile/VFS)协同工作:
/* log_info("file size : 0x%x\nfile sclust : 0x%x\n", vm_attr.fsize, vm_attr.sclust); */
vfs_file_close(&pvfile);
boot_info.vm.vm_saddr = vm_attr.sclust;
boot_info.vm.vm_size = vm_attr.fsize;
/* sfc_encryption_init(boot_info.sfc.app_addr, boot_info.sfc.sfc_base_addr, boot_info.vm.vm_saddr); */
dev_ioctl(device, IOCTL_GET_CAPACITY, (u32)&capacity);
boot_info.flash_size = capacity;
log_info("boot info 0x%x\n", boot_info.flash_size);
struct tzflash_ndec_cfg_data ndec_cfg;
ndec_cfg.saddr = boot_info.vm.vm_saddr;
ndec_cfg.eaddr = tzflash_cpu2flash_addr((void *)(0xfffffffUL));
memcpy(&ndec_cfg.sfc, &boot_info.sfc, sizeof(struct sfc_info));
dev_ioctl(device, IOCTL_SET_VM_INFO, (u32)&ndec_cfg);
Source: init.c
这段代码揭示了三件事:
- VM 区来自文件系统解析:通过 VFS 打开 VM 文件并读取属性(
vm_attr.sclust= 起始簇地址、vm_attr.fsize= 文件大小),回填到boot_info.vm。也就是说 VM 参数区不是固定地址,而是由 Flash 文件系统动态定位的,这要求文件系统必须先于 VM 使用初始化完成。 - Flash 容量实时查询:通过
IOCTL_GET_CAPACITY向 Flash 设备查询真实容量并回填boot_info.flash_size,确保上层始终使用实际容量而非编译期常量。 - 安全(TZFlash)解密配置下发:构造
tzflash_ndec_cfg_data,把 VM 起始地址(saddr)、解密终点(eaddr = 0xfffffff经tzflash_cpu2flash_addr换算)以及 SFC 映射信息一并写入设备,使 Flash 控制器对 VM 区启用硬件解密。
Flash 地址与 CPU 逻辑地址换算
reserved_area.c 中定义了地址换算宏,是平台地址模型的核心约定:
#define FLASH_ADDR2CPU_ADDR(flash_addr) \
(boot_info.sfc.app_addr + flash_addr - boot_info.sfc.sfc_base_addr)
Source: reserved_area.c
该宏把 Flash 物理地址翻译为 CPU 可寻址的逻辑地址:以 UBoot 建立的映射为基准(app_addr - sfc_base_addr 即映射偏移),任意 Flash 地址加上该偏移即得到内存映射地址。配套的 tzflash_cpu2flash_addr 则是反向换算。整个平台的存储访问(保留区、VM、升级区)都统一走这套换算,避免各处硬编码地址产生漂移。
同文件中还包含 Flash 擦写保护逻辑,限制擦写范围不越过保留区边界:
if ((offset < limit_addr) || ((offset + len) > boot_info.flash_size)) {
/* 超过正常擦写区域,不进行擦写操作 */
Source: init.c
擦写保护以 boot_info.flash_size 为上限,防止越界擦写破坏引导区或文件系统元数据。
核心启动时序
下图展示从复位到业务运行的完整时序,标注了各阶段的关键动作与数据依赖:
sequenceDiagram
participant ROM as MaskROM
participant UBOOT as UBoot
participant APP as c_main()
participant PWR as 电源/时钟(Power/Clock)
participant FS as Flash 文件系统(VFS/sdfile)
participant SFC as SFC 控制器
ROM->>UBOOT: 基础初始化 + 跳转
UBOOT->>UBOOT: 探测 Flash 容量
UBOOT->>UBOOT: 建立 SFC 映射(app_addr/sfc_base_addr)
UBOOT->>UBOOT: 读取 chip_id / trim_value
UBOOT->>APP: 传递 BOOT_INFO / cfg_addr
APP->>APP: maskrom_init() 注册打印/异常回调
APP->>APP: wdt_init(WDT_8S) 开看门狗
APP->>APP: log_init(1000000) 串口日志
APP->>PWR: immutable_initialize_clock_power()
PWR->>PWR: efuse_init → clk_voltage_init → clk_early_init
PWR->>PWR: power_early_flowing → sys_pll_ldo_trim_check → power_later_flowing
PWR-->>APP: 时钟/电源就绪
APP->>FS: 解析 VM 文件 (vm_attr.sclust/fsize)
FS-->>APP: 回填 boot_info.vm
APP->>SFC: IOCTL_GET_CAPACITY → flash_size
APP->>SFC: IOCTL_SET_VM_INFO → TZFlash 解密配置
APP->>APP: system_init() 系统服务
APP->>APP: app() 业务入口
APP->>APP: while(1) { wdt_clear(); }
时序中的关键约束:
- 看门狗先于耗时初始化:
wdt_init在电源/时钟初始化之前打开,任何阶段的死循环都会被 8 秒看门狗复位兜底。 - 打印屏蔽窗口:
clk_early_init与sys_pll_ldo_trim_check执行期间时钟停止,必须临时卸载打印函数,否则 UART 依赖时钟导致死机。 - 文件系统先于 VM 使用:
boot_info.vm由文件系统解析结果回填,因此 VM 初始化必然发生在 Flash 文件系统挂载之后。
配置选项
编译期配置宏
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
USE_SYDFILE_NEW | 宏 | 1 | 是否启用新版 Flash 文件系统结构;为 1 时 vm_info 增加 align 字段,启用独立 bt_mac_addr 结构 |
SPI_MAX_CLK | 宏 | 平台相关 | SFC 最大时钟频率,经 clock_set_sfc_max_freq() 在时钟树建立前设定上限 |
CLOCK_MODE_ADAPTIVE | 枚举 | 自适应 | 时钟-电压联动模式,传入 clk_voltage_init() |
DVDD_VOL_123V | 枚举 | 1.23V | 核心 DVDD 电压档位,先于高频时钟建立 |
PLL_REF_LRC | 枚举 | LRC | PLL 参考时钟源选择,传入 clk_early_init() |
PLL_MAX_LIMIT | 宏 | 平台相关 | PLL 频率上限约束 |
WDT_8S | 枚举 | 8 秒 | 看门狗超时,wdt_init() 在启动早期启用 |
运行期结构体字段
| 字段 | 来源 | 说明 |
|---|---|---|
boot_info.sfc.sfc_base_addr | UBoot | Flash memory 映射基地址 |
boot_info.sfc.app_addr | UBoot | CPU 逻辑地址,两者差值即映射偏移 |
boot_info.flash_size | App 查询(IOCTL_GET_CAPACITY) | 实际 Flash 容量,同时用作擦写保护上限 |
boot_info.chip_id | UBoot | 芯片 ID,用于平台差异化处理 |
boot_info.trim_value | UBoot | Trim 校准值,用于 PLL/LDO 校准 |
boot_info.vm.vm_saddr | App 文件系统解析 | VM 参数区起始 Flash 地址 |
boot_info.vm.vm_size | App 文件系统解析 | VM 参数区大小 |
boot_info.up_suc_flag | 升级流程 | 上次升级成功标志 |
失败模式、边界情况与并发注意点
- 初始化期间打印导致死机:
clk_early_init与sys_pll_ldo_trim_check运行期间时钟会停止,任何依赖时钟的打印(UART)都会造成程序卡死或外设运行失常。平台通过临时卸载打印回调规避,用户不得在这些窗口内自行插入打印或其他依赖时钟的操作。 - 看门狗兜底:
wdt_init(WDT_8S)在早期打开,若immutable_initialize_clock_power或system_init卡死,8 秒后复位。业务主循环必须持续wdt_clear(),长时间阻塞的耗时操作(如大块 Flash 擦写)需考虑分段喂狗。 - 异常早期捕获:
maskrom_init在 IRQ1/7 挂接exception_irq_handler,配合exception_analyze钩子把异常现场(栈指针等)交给分析器;cpu_assert_debug()是用户可自定义的断言处理点,默认空操作。 - Flash 擦写越界保护:擦写逻辑以
limit_addr与boot_info.flash_size为边界,越界擦写请求被直接拒绝,保护引导区与文件系统元数据不被破坏。 - 地址换算一致性:所有 Flash 地址访问必须统一经过
FLASH_ADDR2CPU_ADDR换算,直接硬编码地址会导致与 UBoot 映射不一致的寻址错误;tzflash_cpu2flash_addr负责反向换算(如 TZFlash 解密终点)。 - 并发/临界区:启动阶段为单线程顺序执行,但
critical_hook_init()提前注册了临界区钩子,确保早期初始化中的中断保护可用;后续驱动层(SFC、Flash)的多任务访问依赖于系统启动完成后中断与信号量机制的建立。
扩展点与可定制位置
| 扩展点 | 位置 | 说明 |
|---|---|---|
cpu_assert_debug() | main.c | 断言处理钩子,默认空实现,可自定义故障响应(LED、复位、日志) |
exception_analyze | maskrom_argv | 异常分析钩子,可扩展为打印调用栈或进入调试模式 |
app() | 业务层 | 系统初始化完成后进入的业务入口,所有应用逻辑从这里开始 |
board_power_init() | 电源流水线 | 板级电源定制点,在核心电源/时钟就绪后执行 |
boot_info 全局结构 | boot.h | 各子系统共享的平台信息总线,新增平台参数在此扩展(注意 UBoot 侧同步) |
性能与运维注意点
- 启动路径中耗时最长的部分是 Flash 探测与电源/时钟收敛(PLL 锁定、LDO 稳定),这些阶段属于不可并行化硬件时序,优化空间有限;
log_init(1000000)的 1M 波特率日志用于加快调试输出。 clock_dump()/efuse_dump()输出时钟树与校准参数,产线不良或时钟异常排查时优先核对这两段日志。- 发布固件需保证 UBoot 与 App 的
boot.h结构定义一致(尤其USE_SYDFILE_NEW开关与字段顺序),否则BOOT_INFO解析错位将导致 Flash 布局错误。
测试与验证建议
- 启动顺序验证:在
immutable_initialize_clock_power各步骤之间加入时间戳打印,确认电源/时钟流水线时序符合预期。 - 异常注入:故意在
clk_early_init窗口内打印,验证死机风险确实存在,从而确认打印屏蔽机制的必要性。 - 地址映射验证:读取
boot_info.sfc与boot_info.flash_size,对 Flash 首/尾地址做FLASH_ADDR2CPU_ADDR换算,确认与内存映射一致。 - 看门狗验证:在
app()前人为死循环,确认 8 秒后芯片复位。
Related Links
- boot.h — UBoot/App 握手数据结构
- main.c — c_main 启动入口与不可变电源/时钟初始化
- init.c — VM 参数区回填与 TZFlash 配置
- reserved_area.c — Flash/CPU 地址换算宏
- 相关主题:Flash 文件系统与 VM 存储(sdfile)、电源管理(power flow)、时钟树配置(clock)、OTA 升级流程——这些子系统各有独立页面,本页仅覆盖平台引导交接部分。