BES 芯片上电启动时序详细分析
文档元数据
| 项目 | 说明 |
|---|---|
| 芯片型号 | BES1502P(内部代号 BES2710Y) |
| CPU 内核 | ARM Cortex-M33(ARMv8-M 架构) |
| 浮点单元 | 支持(单精度 FPU) |
| TrustZone | 支持(ARM CMSE) |
| RTOS | RTX5(CMSIS-RTOS2) |
| Flash 控制器 | V7 版本 |
| 安全引导 | 支持(RSA3072 / ED25519) |
目录
-
Part 1:硬件架构与存储系统
-
1.1 内存映射
-
1.2 TrustZone 安全/非安全分区
-
1.3 存储介质、总线与 Cache 体系
-
-
Part 2:电源、时钟、模拟外设与 OTP/eFuse 管理
-
2.1 关键硬件外设一览
-
2.2 时钟管理初始化(hal_cmu_setup)
-
2.3 PMU 电源管理初始化
-
2.4 模拟驱动层
-
2.5 OTP / eFuse 原理详解
-
-
Part 3:完整启动时序深度解析
-
3.1 启动流程总览
-
3.2 启动时序图
-
3.3 Stage 1:硬件复位与 ROM 引导
-
3.4 Stage 2:Flash 汇编入口(startup_main.S)
-
3.5 Stage 3:NVIC_InitVectors 中断向量表初始化
-
3.6 Stage 4:BootInit 核心引导初始化
-
3.7 Stage 7:CMSE 安全世界主函数
-
3.8 Stage 8:Non-Secure World main()
-
-
Part 4:系统核心并发机制与 RTOS
-
4.1 RTX5 集成
-
4.2 中断锁机制:int_lock_global vs osMutexWait
-
-
Part 5:存储布局与数据持久性策略
-
5.1 Flash 存储结构
-
5.2 关键数据结构
-
5.3 供电域、RTC 与数据持久性
-
-
Part 6:工程参考手册与 FAQ
-
6.1 源文件索引
-
6.2 编译配置摘要
-
6.3 常见问题解答(FAQ)
-
Part 1:硬件架构与存储系统
本部分从硬件视角梳理 BES1502P 的内存映射、TrustZone 分区、总线拓扑、Cache 机制及地址别名系统,是理解后续启动流程的基础。
1.1 内存映射
┌─────────────────────────────────────────────────────────────────────┐
│ BES1502P Memory Map │
├────────────────────┬───────────────────────┬────────────────────────┤
│ Region │ Address Range │ Size │
├────────────────────┼───────────────────────┼────────────────────────┤
│ ROM (Code) │ 0x00020000 │ 64 KB │
│ ROM (Data) │ 0x22000000 │ 64 KB │
│ RAM0 (SRAM) │ 0x20000000 │ 128 KB │
│ RAM1 │ 0x20020000 │ 128 KB │
│ RAM2 │ 0x20040000 │ 128 KB │
│ RAM3 │ 0x20060000 │ 256 KB │
│ RAM4 │ 0x200A0000 │ 256 KB │
│ RAM5 │ 0x200E0000 │ 256 KB │
│ RAM6 │ 0x20120000 │ 256 KB │
│ RAM7 │ 0x20160000 │ 256 KB │
│ RAM8 │ 0x201A0000 │ 256 KB │
│ RAM9 │ 0x201E0000 │ 128 KB │
│ RAMX0(执行区) │ 0x00200000 │ 128 KB │
│ Flash (Data) │ 0x2C000000 │ 可配置 │
│ Flash (Execute) │ 0x0C000000 │ 可配置 │
│ PSRAM │ 可配置 │ 可选 │
├────────────────────┼───────────────────────┼────────────────────────┤
│ Flash Secure Zone │ FLASHX_BASE │ 512 KB (FLASH_S_SIZE) │
│ Flash NS Zone │ FLASHX_BASE+0x80000 │ 剩余空间 │
└────────────────────┴───────────────────────┴────────────────────────┘
1.2 TrustZone 安全/非安全分区
TrustZone 将 Flash 和 SRAM 从物理上分为 Secure(S)和 Non-Secure(NS)两个区域:
Flash Layout(TrustZone 已启用):
┌──────────────────────────────────────────────┐
│ Secure World (S) │ 0x0C000000 │
│ ├── Boot Header │ boot_struct_t │
│ ├── CMSE Code │ cmse_main.cpp │
│ ├── Secure Drivers │ hal_sec, mpc, SAU │
│ └── NSC Veneers │ Non-Secure Callable │
│ 大小:512 KB (0x80000) │
├──────────────────────────────────────────────┤
│ Non-Secure World (NS)│ 0x0C080000 │
│ ├── NS Boot Header │ boot_struct_t │
│ ├── main.cpp │ App Entry │
│ ├── RTOS Kernel │ RTX5 │
│ ├── BT Stack │ │
│ └── Application │ │
│ 大小:Flash 剩余空间 │
└──────────────────────────────────────────────┘
1.3 存储介质、总线与 Cache 体系
1.3.1 三类存储介质的本质差异
| 介质 | 断电保持 | 读速度 | 写速度 | 可擦写次数 | 典型用途 |
|---|---|---|---|---|---|
| SRAM(片内) | ✗ 易失 | ~1 ns(零等待) | ~1 ns | 无限 | 代码/数据运行区、栈、堆 |
| NOR Flash(片内) | ✓ 非易失 | ~50 ns(XIP) | 极慢(ms/page) | ~10 万次 | 程序存储、只读资源 |
| PSRAM(片外) | ✗ 易失 | ~30 ns(XIP+Cache) | ~30 ns | 无限 | 大缓冲区、音频/图像数据 |
核心差异:Flash 是"仓库"——上电后数据仍在,但不能高速执行;SRAM 是"工作台"——极快但断电清空。程序必须在启动阶段将 Flash 中的热路径代码和数据"搬运"到 SRAM,这是 Boot 阶段 section 拷贝存在的根本原因。
1.3.2 冯诺依曼架构 vs 哈佛架构
冯诺依曼架构 (Von Neumann):
┌──────────┐ 单总线 ┌──────────────────┐
│ CPU │◄────────────►│ 统一存储 │
└──────────┘ │(代码 + 数据同一空间)│
└──────────────────┘
缺点:取指与读数据共用总线 → 结构性冲突(Von Neumann Bottleneck)
哈佛架构 (Harvard):
┌─── 指令总线 ───► 指令存储
┌──────────┐ │
│ CPU │──┤
└──────────┘ │
└─── 数据总线 ───► 数据存储
优点:取指与读数据并行 → 消除结构性冲突
ARM Cortex-M33 采用改进型哈佛架构:对程序员呈现统一的 32-bit 地址空间(形如冯诺依曼),但 CPU 内部有三条独立总线,实现哈佛的并行优势。
1.3.3 Cortex-M33 三总线架构
┌─────────────────────────────────────────────────────┐
│ Cortex-M33 Core │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ 指令取指 │ │ 数据访问 │ │ 外设/大内存访问 │ │
│ │(Fetch) │ │(Load/Store)│ │ │ │
│ └────┬─────┘ └────┬──────┘ └────────┬─────────┘ │
└───────┼─────────────┼──────────────────┼────────────┘
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ ICode Bus │ │ DCode Bus │ │ System Bus │
│(指令总线) │ │(数据总线) │ │(系统总线 AHB) │
│ 0x00000000 │ │ 0x00000000 │ │ 0x20000000+ │
│ ~0x1FFFFFFF │ │ ~0x1FFFFFFF │ │ │
└─────┬──────┘ └──────┬─────┘ └────────┬─────────┘
│ │ │
-
ICode Bus:专用于从
0x00000000~0x1FFFFFFF取指令(只读) -
DCode Bus:专用于从同一地址范围读写数据(Load/Store 指令)
-
System Bus:访问 SRAM(
0x20000000+)、外设、Flash 数据别名
ICode 与 DCode 可同时工作,取指和读数据互不阻塞——这是改进型哈佛架构的核心价值。
1.3.4 地址别名系统:同一物理存储映射多个地址
这是理解整个存储系统的关键。同一块物理 Flash 被映射到 4 个不同的地址段,每个别名提供不同的访问属性:
物理 NOR Flash(片内, FLASH0)
│
├─ 0x0C000000 FLASHX_BASE (执行地址,可缓存,走 ICode Bus) ← 代码执行
├─ 0x2C000000 FLASH_BASE (数据地址,可缓存,走 DCode/System Bus)← 数据读
├─ 0x08000000 FLASHX_NC_BASE (执行地址,不可缓存)← DMA/调试用
└─ 0x28000000 FLASH_NC_BASE (数据地址,不可缓存)← DMA/需要实时内容
物理 SRAM(RAM0~RAM9)
│
├─ 0x20000000 RAM_BASE (数据地址,走 System Bus)← 数据读写
└─ 0x00200000 RAMX_BASE (执行地址,走 ICode Bus) ← 搬运后代码执行
物理 ROM(片内, 64 KB)
│
├─ 0x00020000 ROMX_BASE (执行地址,ICode Bus)← ROM 代码执行
└─ 0x22000000 ROM_BASE (数据地址,System Bus)← 读 ROM 中的常量
物理 PSRAM(片外,SPI 接口)
│
├─ 0x1C000000 PSRAMX_BASE (执行地址,可缓存)
├─ 0x3C000000 PSRAM_BASE (数据地址,可缓存)
├─ 0x18000000 PSRAMX_NC_BASE (执行地址,不可缓存)
└─ 0x38000000 PSRAM_NC_BASE (数据地址,不可缓存)
为什么需要执行地址与数据地址之分?
ARM Cortex-M33 规定 ICode Bus 只能访问 0x00000000~0x1FFFFFFF(代码区),DCode/System Bus 访问 0x20000000+(数据区)。同一物理 Flash 映射两个地址段,使 CPU 既能通过 ICode 总线直接取指(XIP),又能通过 System Bus 读取 Flash 中的只读数据表。
// platform/hal/best1502p/plat_addr_map_best1502p.h
#define REAL_FLASHX_BASE 0x0C000000 // 执行别名(ICode 可达)
#define REAL_FLASH_BASE 0x2C000000 // 数据别名(System Bus)
#define REAL_FLASHX_NC_BASE 0x08000000 // 执行,不缓存
#define REAL_FLASH_NC_BASE 0x28000000 // 数据,不缓存
#define ROMX_BASE 0x00020000 // ROM 执行别名
#define ROM_BASE 0x22000000 // ROM 数据别名
#define RAMX0_BASE 0x00200000 // SRAM 执行别名
#define RAM0_BASE 0x20000000 // SRAM 数据别名
1.3.5 Cache 机制与初始化
Flash 原始读速度约 50~100 ns(26~104 MHz 接口),而 CPU 跑 208 MHz 时一个周期仅 ~5 ns。没有 Cache,每条指令需等待 10~20 个周期。
BES1502P Cache 配置(platform/hal/hal_cache.h):
HAL_CACHE_ID_FLASH_EXEC → I-Cache (Flash 上的代码)
HAL_CACHE_ID_FLASH_DATA → D-Cache (Flash 上的数据,如只读表)
HAL_CACHE_ID_PSRAM_EXEC → I-Cache (PSRAM 上的代码)
HAL_CACHE_ID_PSRAM_DATA → D-Cache (PSRAM 上的数据)
Cache Line = 32 字节(标准)/ 64 字节(double linefill 模式)
CPU 取指流程(XIP,经 I-Cache):
CPU → ICode Bus → I-Cache 查找
│
├─ Cache Hit(命中): 直接返回,~1 ns
│
└─ Cache Miss(未命中):向 Flash 控制器发请求
│
└─ 从 Flash 读 32 字节 Cache Line(~8 cycles)
填入 Cache → 返回给 CPU
下次访问同一行:命中,零等待
典型命中率:>95%,等效延迟接近 SRAM
Cache 初始化顺序(在 BootInit() 中):
void BootInit(void)
{
mpu_boot_cfg(); // 先配 MPU
hal_cache_enable(HAL_CACHE_ID_I_CACHE); // 开 I-Cache
hal_cache_enable(HAL_CACHE_ID_D_CACHE); // 开 D-Cache
hal_cache_writebuffer_enable(HAL_CACHE_ID_D_CACHE); // D-Cache 写缓冲
hal_cache_writeback_enable(HAL_CACHE_ID_D_CACHE); // D-Cache 写回模式
...
}
// Cache 必须在 section 搬运之前开启,否则搬运本身会极慢
写策略说明:
-
Write Buffer(写缓冲):CPU 写数据直接入队列即返回,由硬件异步刷入 SRAM/PSRAM
-
Write Back(写回):修改只在 Cache 中,Cache Line 被替换时才写回内存(最优性能)
-
Write Through(写直达):每次写同时更新 Cache 和内存(简单但慢,不启用)
1.3.6 NC(Non-Cacheable)地址别名的用途
NC 别名是解决 Cache 一致性(Cache Coherency) 问题的工具。当 CPU+Cache 与 DMA/其他处理器同时访问同一物理内存时,必须通过 NC 别名或显式 Cache 操作保证数据一致:
场景:DMA 直接读 Flash 数据到 SRAM
┌─────────────────────────────────────────────────────────────────┐
│ 错误做法:用 FLASH_BASE (0x2C000000, Cacheable) │
│ DMA 读的是物理 Flash,但 Cache 中可能有旧版本 │
│ CPU 随后读同地址 → 命中 Cache → 读到脏数据(不一致!) │
│ │
│ 正确做法:用 FLASH_NC_BASE (0x28000000, Non-Cacheable) │
│ DMA 绕过 Cache 直接读物理 Flash │
│ 或:DMA 完成后调用 hal_cache_invalidate() 使对应行失效 │
└─────────────────────────────────────────────────────────────────┘
1.3.7 完整存储层次图
CPU Core (Cortex-M33 @ 208 MHz)
│
┌────────────┼────────────┐
│ │ │
ICode Bus DCode Bus System Bus
(取指) (数据访问) (外设/SRAM)
│ │ │
└────────────┴────────────┘
│
┌────────────┴────────────┐
│ │
┌────┴────┐ ┌────┴────┐
│ I-Cache │ │ D-Cache │
│(代码缓存)│ │(数据缓存)│
│ 命中→1 ns│ │ 命中→1 ns│
└────┬────┘ └────┬────┘
│ miss │ miss
└────────────┬────────────┘
│
┌─────────────┴──────────────┐
│ 片内总线矩阵(AHB) │
│ │
┌────────┴──────────┐ ┌────────────┴──┐
│ Flash 控制器 V7 │ │ SRAM(~2 MB) │
│ NOR Flash(内置) │ │ 零等待 │
│ 读:~8 cycles │ │ 断电清空 │
│ 写:ms 级 │ └───────────────┘
│ 断电不丢失 │ ┌───────────────┐
└───────────────────┘ │ PSRAM 控制器V8│
┌───────────────────┐ │(外置) │
│ ROM(片内 64 KB) │ └───────────────┘
│ BootLoader,只读 │
└───────────────────┘
延迟汇总:
SRAM 直接访问: ~1 ns (1 cycle @208 MHz)
Flash XIP(Cache 命中): ~5 ns (1 cycle)
Flash XIP(Cache 未命中):~40 ns (8 cycles,linefill)
Flash 原始读(无 Cache): ~50 ns+ (受 Flash 时钟限制)
PSRAM(Cache 命中): ~5 ns (1 cycle)
PSRAM(Cache 未命中): ~30 ns (XIP DQS 模式)
1.3.8 各类代码/数据的运行位置
| 代码/数据类型 | 存放位置 | 运行/访问位置 | 原因 |
|---|---|---|---|
| 普通应用代码 | Flash (FLASHX) | Flash XIP + I-Cache | 体积大,Cache 命中率高,无需搬运 |
时序关键代码(.sram_text) |
Flash → 搬运 | SRAM (RAMX) | 零等待,确定性延迟 |
Boot 阶段代码(.boot_text_sram) |
Flash → 搬运 | SRAM (RAMX) | Flash 控制器未完全初始化时 SRAM 是唯一可靠执行区 |
NSC Veneer(.nsc_text) |
Flash → 搬运 | SRAM(安全属性区) | SAU 须控制其安全属性,Flash XIP 地址不满足 |
全局变量(.data) |
Flash(初值)→ 搬运 | SRAM (RAM) | 变量必须可写 |
零初始化变量(.bss) |
不存在于 Flash | SRAM(清零) | 无需存储,启动时清零 |
只读常量(const) |
Flash(DATA 别名) | Flash + D-Cache | 无需写,留在 Flash 节约 SRAM |
| 栈(Stack) | — | SRAM 高端地址 | 频繁读写,必须在 SRAM |
| RTOS 堆(Heap) | — | SRAM / PSRAM | 动态分配,必须可写 |
| 音频 PCM 缓冲 | — | PSRAM | 体积大(>SRAM 容量),低延迟要求 |
1.3.9 冯诺依曼瓶颈的规避策略
BES1502P 通过以下多层机制应对总线带宽瓶颈:
-
分离总线(哈佛特性):ICode 取指与 DCode 读数据同时进行,消除总线争用
-
Cache:将热点代码/数据的访问延迟从 50 ns 压缩到 ~5 ns,减少 90% 的总线流量
-
SRAM 执行区:热路径代码直接在 SRAM 运行,零等待、零缓存 miss
-
NC 别名:DMA 传输使用非缓存地址,不污染 CPU 的 Cache 空间
-
PSRAM XIP:外置 PSRAM 通过 SPI 接口支持 XIP + Cache,扩展可执行内存空间
-
Write Buffer:CPU 写操作不等待 SRAM 完成,继续执行下一条指令
实际性能参考(@208 MHz):
纯 SRAM 执行: 理论峰值 ~208 MIPS(IPC ≈ 1)
Flash XIP + Cache:热循环 ~150~190 MIPS(高命中率)
Flash XIP 无 Cache:~20~40 MIPS(严重受 Flash 速度限制)
→ Cache 开启后性能提升 4~8 倍,这是 BootInit() 最优先开启 Cache 的原因
Part 2:电源、时钟、模拟外设与 OTP/eFuse 管理
本部分介绍芯片的电源管理单元(PMU)、时钟管理单元(CMU)、模拟外设驱动以及一次性可编程存储(OTP/eFuse)的工作原理。这些子系统在启动早期即被初始化,是整个系统稳定运行的基础。
2.1 关键硬件外设一览
| 外设 | 说明 |
|---|---|
| PMU | 电源管理单元(片内集成) |
| CMU | 时钟管理单元 |
| ISPI | 内部 SPI(模拟寄存器访问总线) |
| NOR Flash | 片内 Flash 控制器 V7 |
| PSRAM | 外置 PSRAM 控制器 V8 |
| DMA | 音频 DMA + 通用 DMA |
| CODEC | 片内音频编解码器 |
| BT Core | 蓝牙双模控制器 |
| USB | USB 2.0 OTG |
2.2 时钟管理初始化(hal_cmu_setup)
源文件:platform/hal/hal_cmu_common.c:576
2.2.1 时钟树结构
┌─────────────┐
│ 26 MHz XTAL │ ← 外部晶振
└──────┬───────┘
│
┌─────────┼──────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌────────┐ ┌────────┐
│ X2 │ │ X4 │ │ PLL │
│ 52 MHz │ │ 104 MHz│ │ BBPLL │
└────┬────┘ └───┬────┘ └───┬────┘
│ │ │
└──────────┼──────────┘
│
┌─────┴─────┐
│ CMU MUX │
└─────┬─────┘
│
┌───────┬───────┼───────┬───────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
SYS_CLK FLASH_CLK MEM_CLK PERIPH UART
(CPU) (NorFlash) (PSRAM) (SPI) (Baud)
2.2.2 hal_cmu_setup() 完整流程
void hal_cmu_setup(void)
{
// [1] IOMUX 默认配置
hal_iomux_set_default_config();
// [2] JTAG 调试接口(仅 DEBUG 模式)
hal_iomux_set_jtag();
hal_cmu_jtag_clock_enable();
// [3] 系统定时器(用于延时的基础时基)
hal_sys_timer_open();
// [4] Boot Mode 初始化(读取硬件启动模式寄存器)
hal_hw_bootmode_init();
// [5] Flash 时钟设置(初始 26 MHz,保守频率)
hal_norflash_set_boot_freq(HAL_CMU_FREQ_26M);
// [6] 内存时钟设置
hal_cmu_mem_set_freq(HAL_CMU_FREQ_26M);
// [7] 系统时钟设置
hal_cmu_sys_set_freq(HAL_CMU_FREQ_26M);
// [8] 模块时钟状态初始化
hal_cmu_module_init_state();
// [9] ISPI 时钟配置(用于访问模拟寄存器)
hal_cmu_ispi_set_freq(HAL_CMU_PERIPH_FREQ_26M);
// [10] 打开 ISPI 通道(模拟接口使能)
hal_analogif_open();
// [11] 芯片 ID 识别(读取 metal ID / revision)
hal_chipid_init();
// [12] PMU Boot 初始化(电源基础配置)
pmu_boot_init();
// [13] 振荡器倍频使能
hal_cmu_osc_x2_enable(); // 52 MHz
hal_cmu_osc_x4_enable(); // 104 MHz
}
2.2.3 ISPI 通信协议
BES1502P 通过内部 SPI(ISPI)访问模拟寄存器,数字域与模拟域之间完全解耦:
┌────────────┐ ISPI Bus ┌────────────┐
│ Digital │◄──────────────────►│ Analog │
│ Core │ (Reg R/W) │ Domain │
│ (CMU) │ │ (PMU/RF) │
└────────────┘ └────────────┘
Register Access:
ana_read(reg, &val) → hal_analogif_reg_read(ANA_REG(reg), val)
ana_write(reg, val) → hal_analogif_reg_write(ANA_REG(reg), val)
rf_read(reg, &val) → hal_analogif_reg_read(RF_REG(reg), val)
2.3 PMU 电源管理初始化
源文件:platform/drivers/ana/best1502p/pmu_best1502p.c
2.3.1 PMU 架构
VBAT(Battery 3.0~4.2 V)
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ DCDC_DIG │ │ DCDC_ANA │ │ DCDC_HPPA │
│(数字核) │ │(模拟核) │ │(耳机功放) │
│ 0.65~1.1 V │ │ 1.4~1.9 V │ │ 1.4~1.9 V │
└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│CPU/SRAM │ │Codec/PLL │ │Headphone │
│Flash/IO │ │RF/XTAL │ │Amplifier │
└──────────┘ └──────────┘ └──────────┘
2.3.2 pmu_boot_init():早期 PMU 初始化
在 hal_cmu_setup() 中被调用,负责上电初期最基础的电源配置:
void pmu_boot_init(void)
{
// [1] 读取硬件版本(metal ID)
read_hw_metal_id();
// [2] 判断 Boot 原因:POR / WDT / Charger / PowerKey
pmu_boot_cause_init();
// [3] 读取 eFuse 校准值(DCDC 电压补偿 / LDO 电压补偿)
pmu_get_dcdc_calib_value();
pmu_get_ldo_dig_calib_value();
// [4] 禁用 LDO 硬件 Ramp(避免电压缓慢爬升影响启动速度)
// [5] 基础电源配置(VCORE / VANA / VMEM 初始电平设置)
}
2.3.3 pmu_open():完整 PMU 初始化
在 main() 中被调用,完成 PMU 的全量初始化:
int pmu_open(void)
{
// [1] 防重入保护
if (pmu_opened) return 0;
pmu_opened = true;
// [2] 电压断言检查
ASSERT(vcodec_mv == 1400~1900);
ASSERT(vhppa_mv == 1400~1900);
// [3] eFuse 初始化
pmu_efuse_init();
// [4] 清除所有 PMU 中断
pmu_write(PMU_REG_INT_MASK, 0);
pmu_write(PMU_REG_INT_EN, 0);
// [5] 电源键配置(允许 PMU 在按键按下时进入 Sleep)
// [6] Bandgap 配置(低功耗模式下使用 small bandgap)
// [7] 32K 时钟源选择(外部 32.768 KHz 或内部 RC)
// [8] DCDC 模式配置(PWM / PFM 自动切换)
// [9] 各 LDO 模块使能(VCODEC / VMEM / VIO 等)
// [10] 充电器检测配置(PMU_CHARGER_PLUGIN / PLUGOUT 中断)
return 0;
}
2.3.4 关键时序参数
#define PMU_LDO_PU_STABLE_TIME_US 1800 // LDO 上电稳定:1.8 ms
#define PMU_LDO_PULLDOWN_STABLE_TIME_US 1000 // LDO 下拉稳定:1 ms
#define PMU_DCDC_PU_STABLE_TIME_US 100 // DCDC 上电稳定:100 us
#define PMU_VANA_STABLE_TIME_US 10 // VANA 稳定:10 us
#define PMU_VCORE_STABLE_TIME_US 10 // VCORE 稳定:10 us
#define PMU_BIG_BG_STABLE_TIME_US 200 // 大 Bandgap 稳定:200 us
#define PMU_EXT_32K_STABLE_TIME 300 // 32K 晶振稳定:300 ms
2.4 模拟驱动层
2.4.1 驱动文件架构
| 文件 | 功能 |
|---|---|
pmu_best1502p.c |
电源管理(DCDC/LDO/充电/休眠) |
analog_best1502p.c |
模拟外设(CODEC/ADC/DAC) |
charger_best1502p.c |
充电管理(CC/CV/OVP) |
rf_xtal_best1502p.c |
RF 和晶振校准 |
usbphy_best1502p.c |
USB PHY 控制 |
analog_sens_best1502p.c |
Sensor Hub 模拟 |
2.4.2 寄存器访问模型
// 所有模拟寄存器通过 ISPI 总线访问
#define ana_read(reg,val) hal_analogif_reg_read(ANA_REG(reg), val)
#define ana_write(reg,val) hal_analogif_reg_write(ANA_REG(reg), val)
#define rf_read(reg,val) hal_analogif_reg_read(RF_REG(reg), val)
#define rf_write(reg,val) hal_analogif_reg_write(RF_REG(reg), val)
// ANA_REG / RF_REG 区分模拟/射频寄存器地址空间
2.4.3 PMU 电源模式
┌─────────────────────────────────────────────────────────┐
│ PMU Power Modes │
├─────────────────────────────────────────────────────────┤
│ Active Mode: │
│ CPU 全速运行,所有外设可用 │
│ DCDC_DIG = 0.9 V(正常)/ 1.1 V(高性能) │
│ 系统时钟:最高 208 MHz │
│ │
│ Light Sleep: │
│ CPU WFI,外设处于待机 │
│ DCDC_DIG → LP 模式(0.65 V~0.75 V) │
│ 唤醒:任意使能中断 │
│ │
│ Deep Sleep: │
│ CPU 断电,SRAM 保持数据 │
│ 仅 PMU + 32K 时钟运行 │
│ 唤醒:GPIO / Timer / Charger │
│ │
│ Hibernate: │
│ 仅 VRTC 域存活 │
│ 极低电流(< 1 μA) │
│ 唤醒:仅电源键 │
└─────────────────────────────────────────────────────────┘
2.5 OTP / eFuse 原理详解
2.5.1 什么是 OTP / eFuse
OTP(One-Time Programmable)和 eFuse 都是只能写一次、永久保存的非易失性存储单元,本质是同一类技术的不同叫法:
-
eFuse(电熔丝):出厂时每个 bit 为"0"(熔丝完整)。编程时向特定地址施加高电流,将熔丝烧断,该 bit 变为"1",不可逆。
-
OTP:泛指一次性可编程存储器,eFuse 是其一种实现方式。BES1502P 文档通常混用这两个词。
物理存储结构(eFuse bit):
┌──────────────────────────────────────────┐
│ 未编程态:熔丝完整 → bit = 0 │
│ ┌──┐ │
│ │==│ ← 完整的金属熔丝 │
│ └──┘ │
│ │
│ 已编程态:熔丝烧断 → bit = 1 │
│ ┌ ┐ │
│ │ │ ← 熔断缺口(不可逆) │
│ └ ┘ │
└──────────────────────────────────────────┘
2.5.2 BES1502P eFuse 存储的内容
eFuse 在芯片生产/测试阶段由厂商或产线烧录,启动时由 ROM 读取:
// platform/drivers/ana/best1502p/pmu_best1502p.c
// eFuse Page 分配:
PMU_EFUSE_PAGE_DCDC1_VOLT // DCDC 数字核(CPU)电压校准补偿值
PMU_EFUSE_PAGE_DCDC2_VOLT // DCDC 模拟核(Codec/PLL)电压校准补偿值
PMU_EFUSE_PAGE_DCDC3_VOLT // DCDC HPPA(耳机功放)电压校准补偿值
PMU_EFUSE_PAGE_LDO_CORE // LDO CORE 电压校准补偿值
// + wafer ID / metal ID(芯片版本标识)
// + Secure Boot 公钥哈希(启用 Secure Boot 时)
为什么要存电压校准值? 芯片制造工艺存在偏差(工艺角),同批芯片的最优工作电压略有不同。测试时每颗单独测量,将补偿值烧入 eFuse,固件启动时读出并微调 DCDC/LDO 输出,保证全批次在最优电压下工作。
2.5.3 OTP 读取时机与流程
上电
│
▼
ROM BootLoader(0x00020000)
│
├─ [1] 读取 wafer ID / metal ID
│ → hal_chipid_init()(在 hal_cmu_setup() 中调用)
│ → 确定芯片版本,选择对应驱动参数
│
├─ [2] 读取 DCDC/LDO 校准值
│ → pmu_boot_init() → pmu_get_dcdc_calib_value()
│ → 调整 DCDC 输出电压,保证 CPU/SRAM 稳定供电
│
├─ [3] 读取 Secure Boot 公钥哈希(可选)
│ → 验证 Flash 中 boot_struct_t 的 RSA3072 公钥合法性
│ → 防止攻击者替换公钥来绕过验签
│
└─ [4] 读取 Dolby 私钥地址
→ dlb_cmse_setup(DOLBY_PRIVATE_KEY_ADDR)
→ OTP 地址 0x38 存放 Dolby 解密私钥
2.5.4 eFuse 安全属性
| 安全属性 | 说明 |
|---|---|
| 不可逆 | bit 只能 0→1,攻击者无法擦除 |
| 读保护 | 敏感 page(如密钥哈希)可设置"读锁",锁定后 CPU 也无法读出明文 |
| 写保护 | 每个 page 可单独设置"写锁",防止二次编程 |
| TrustZone 隔离 | 密钥相关 eFuse page 仅在 Secure World 可访问 |
Part 3:完整启动时序深度解析
本部分按时间顺序逐阶段解析从上电复位到 RTOS 调度器就绪的完整启动流程,涵盖汇编入口、向量表初始化、Boot 初始化、TrustZone 安全世界以及非安全世界的主函数。
3.1 启动流程总览
┌─────────────────────────────────────────────────────────────────────┐
│ POWER ON RESET │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ HW Reset │ ← POR / BOR / WDT / Pin Reset │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ ROM BootLoader │ ← 片内 ROM 执行,读取 OTP 配置 │
│ │ (0x00020000) │ │
│ └────────┬─────────┘ │
│ │ 跳转到 Flash │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Boot_Loader() │ ← startup_main.S │
│ │ (Flash Entry) │ 设置 MSP,初始化硬件 │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ NVIC_InitVectors│ ← 中断向量表重定位 │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ SystemInit() │ ← 系统时钟初始化 │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ BootInit() │ ← Cache / MPU / RAM 搬运 │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ _start / main │ ← C 运行时初始化 │
│ │ (Secure World) │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ CMSE_MAIN() │ ← 安全世界主函数 │
│ │ ├─ PMU Init │ 电源管理初始化 │
│ │ ├─ Flash Init │ Flash 安全配置 │
│ │ ├─ SEC Init │ 安全外设初始化 │
│ │ ├─ MPC Init │ 内存保护控制器 │
│ │ ├─ SAU Setup │ 安全属性单元 │
│ │ ├─ DLB Verify │ Dolby 验签解密 │
│ │ └─ Secure Boot │ 用户安全引导校验 │
│ └────────┬─────────┘ │
│ │ 跳转 NS 世界 │
│ ▼ │
│ ┌──────────────────┐ │
│ │ NS Reset Handler│ ← 非安全世界入口 │
│ │ (0x0C080000+off)│ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ NS BootInit() │ ← 非安全世界 Boot │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ NS main() │ ← main.cpp │
│ │ ├─ WDT Init │ 看门狗 │
│ │ ├─ HW Setup │ GPIO / IOMUX │
│ │ ├─ DMA Open │ DMA 通道 │
│ │ ├─ Trace Init │ 调试串口 │
│ │ ├─ PMU Open │ 电源完整初始化 │
│ │ ├─ Analog Open │ 模拟外设 │
│ │ └─ app_init() │ 应用层初始化 │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ RTOS Scheduler │ ← RTX5 多任务调度 │
│ │ ├─ BT Task │ │
│ │ ├─ Audio Task │ │
│ │ ├─ App Task │ │
│ │ └─ Idle Task │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
3.2 启动时序图
3.2.1 上电到 RTOS 就绪时序
时间轴(近似值):
t=0 ms t≈0.5 ms t≈2 ms t≈5 ms t≈20 ms t≈50 ms
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌─────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│POR │ │ROM Boot │ │Flash │ │CMSE Main │ │NS Boot │ │RTOS │
│Reset│ │OTP Read │ │Boot_Ldr │ │Secure │ │main() │ │Scheduler │
│ │ │SecCheck │ │BootInit │ │Init │ │app_init │ │Ready │
└─────┘ └─────────┘ └─────────┘ └──────────┘ └──────────┘ └──────────┘
│ │ │ │ │ │
│ XTAL 稳定 │ Cache/RAM │ PMU/MPC │ BT Init │ 任务创建 │
│ DCDC 启动 │ 搬运完成 │ SAU 配置 │ Audio Init │ 开始调度 │
│ Bandgap 稳 │ 时钟切换 │ NS 跳转 │ WDT 喂狗 │ │
3.2.2 中断响应时序
ARM Cortex-M33 中断响应:
中断触发 → 硬件自动压栈(xPSR, PC, LR, R12, R3-R0)
→ 取向量(从 VTOR + IRQ# × 4)
→ 跳转 ISR
→ ISR 执行
→ 异常返回(EXC_RETURN)
→ 硬件自动弹栈
响应延迟:12 cycles(zero wait state)
尾链优化:6 cycles(中断嵌套场景)
TrustZone 额外开销:
S → NS 切换: ~24 cycles(寄存器清零 + 安全状态保存)
NS → S 调用(NSC veneer):~16 cycles
3.3 Stage 1:硬件复位与 ROM 引导
3.3.1 Power-On Reset(POR)
当电池/充电器为芯片供电后,硬件层面依次发生:
-
电源域上电顺序:
-
VBAT 上电 → PMU 内部 BOR(Brown-Out Reset)检测
-
PMU 稳压器启动:DCDC_DIG → DCDC_ANA → LDO_VCORE
-
晶振起振等待(26 MHz XTAL,约 300 us 稳定时间)
-
-
复位释放:
-
PMU 释放全局复位信号
-
ARM Core 从
SCB->VTOR指向的地址读取初始 MSP 和 Reset_Handler
-
-
OTP/eFuse 读取:
-
片内 ROM 读取 OTP 区域
-
获取 DCDC 校准值、LDO 补偿值、Secure Boot 公钥哈希
-
获取 wafer ID、metal ID 用于芯片版本识别
-
// OTP/eFuse 校准数据结构(pmu_best1502p.c)
// PMU_EFUSE_PAGE_DCDC1_VOLT:DCDC 数字核校准补偿
// PMU_EFUSE_PAGE_DCDC2_VOLT:DCDC 模拟核校准补偿
// PMU_EFUSE_PAGE_DCDC3_VOLT:DCDC HPPA 校准补偿
// PMU_EFUSE_PAGE_LDO_CORE: LDO CORE 电压校准
3.3.2 ROM Boot Loader
片内 ROM(64 KB @ 0x00020000)负责:
-
验证 Flash 中的 Boot Magic Number(
0xBE57EC1C) -
如果启用了 Secure Boot,验证 Flash 镜像签名
-
确定跳转地址(Flash 入口或 Programmer 模式)
// Boot Header 结构(tool_msg.h)
struct boot_hdr_t {
unsigned int magic; // 必须为 0xBE57EC1C
unsigned short flag; // 安全引导标志
unsigned short version; // 版本信息
unsigned int reserved_008;
unsigned int build_info_start; // 构建信息指针
};
struct boot_struct_t {
struct boot_hdr_t hdr;
unsigned short security; // 安全设置
unsigned short reserved_012;
unsigned char key[MAX_KEY_LEN]; // RSA3072 公钥(384+384+4 bytes)
unsigned char sig[MAX_SIG_LEN]; // RSA3072 签名(384 bytes)
};
3.4 Stage 2:Flash 汇编入口(startup_main.S)
源文件:platform/main/startup_main.S
这是 Flash 中第一段被执行的代码,以纯汇编实现:
Boot_Loader:
ldr r0, =.L_boot_loader_start
bx r0 ; 跳转到代码地址空间
.L_boot_loader_start:
; [1] boot_loader_entry_hook(BOOT_LOADER_ENTRY_HOOK=1 时执行)
bl boot_loader_entry_hook
; [2] 设置主堆栈指针 MSP
ldr r0, =__StackTop
msr msp, r0
; [3] ARMv8-M 堆栈限制寄存器
ldr r0, =__StackLimit
msr msplim, r0
; [4] 使用 MSP,设置特权模式
movs r0, #0
msr control, r0
isb
; [5] 中断向量表初始化
bl NVIC_InitVectors
; [6] 系统初始化(时钟)
bl SystemInit
; [7] 引导初始化(Cache,RAM 搬运)
bl BootInit
; [8] 清零 BSS 段(NOSTD 模式下由此汇编完成)
; [9] 跳转 C 运行时
bl _start ; → 最终调用 main() / MAIN_ENTRY()
3.4.1 boot_loader_entry_hook
BES1502P 配置了 BOOT_LOADER_ENTRY_HOOK=1,此 hook 在 MSP 设置前执行,用于:
-
切换 SP 到正确的 RAM bank
-
处理 RAM boot 模式
-
早期 GPIO 状态配置
3.4.2 Stack Pointer 设置
MSP(Main Stack Pointer):
__StackTop = RAM 最高地址(由链接脚本定义)
__StackLimit = Stack 下界(ARMv8-M 硬件栈溢出检测)
MSPLIM 寄存器是 ARMv8-M 新增功能:
当 SP < MSPLIM 时,硬件自动触发 UsageFault,无需软件检测栈溢出
3.4.3 CONTROL 寄存器
CONTROL = 0:
bit[1] SPSEL = 0 → 使用 MSP(非 PSP)
bit[0] nPRIV = 0 → 特权模式
所有 Boot 阶段运行在特权 + MSP 模式。
RTOS 启动后,任务切换到 PSP + 非特权模式。
3.5 Stage 3:NVIC_InitVectors 中断向量表初始化
源文件:platform/cmsis/cmsis_nvic.c
3.5.1 中断向量表结构
// Flash 中的只读向量表(初始模板)
static const uint32_t fault_handlers[NVIC_USER_IRQ_OFFSET] = {
(uint32_t)__stack, // [0] 初始 MSP 值
(uint32_t)Reset_Handler, // [1] 复位处理
(uint32_t)NMI_Handler, // [2] NMI
(uint32_t)HardFault_Handler, // [3] 硬故障
(uint32_t)MemManage_Handler, // [4] 内存管理故障
(uint32_t)BusFault_Handler, // [5] 总线故障
(uint32_t)UsageFault_Handler, // [6] 用法故障
(uint32_t)SecureFault_Handler, // [7] 安全故障(ARMv8-M 新增)
(uint32_t)NVIC_default_handler, // [8] Reserved
(uint32_t)NVIC_default_handler, // [9] Reserved
(uint32_t)NVIC_default_handler, // [10] Reserved
(uint32_t)SVC_Handler, // [11] SVC(RTOS 系统调用)
(uint32_t)DebugMon_Handler, // [12] 调试监控
(uint32_t)NVIC_default_handler, // [13] Reserved
(uint32_t)PendSV_Handler, // [14] PendSV(RTOS 任务切换)
(uint32_t)SysTick_Handler, // [15] SysTick(RTOS 时间片)
};
3.5.2 向量表重定位
void NVIC_InitVectors(void)
{
// 将向量表从 Flash 复制到 RAM(可动态修改)
for (i = 0; i < NVIC_NUM_VECTORS; i++) {
vector_table[i] = (i < ARRAY_SIZE(fault_handlers)) ?
fault_handlers[i] : (uint32_t)NVIC_default_handler;
}
// 设置 VTOR 指向 RAM 向量表
SCB->VTOR = (uint32_t)vector_table;
__DSB(); // 数据同步屏障,确保写入生效
}
为什么要重定位到 RAM?
-
RAM 中的向量表可以动态修改(运行时注册中断处理函数)
-
RTOS 需要注册 SVC_Handler、PendSV_Handler、SysTick_Handler
-
外设驱动需要注册各自的 IRQ Handler
3.5.3 BES1502P 中断源列表(部分)
| IRQ# | 名称 | 说明 |
|---|---|---|
| 0 | FPU_IRQn | 浮点运算异常 |
| 1 | SDMMC_IRQn | SD/MMC 控制器 |
| 2 | AUDMA_IRQn | 音频 DMA |
| 3 | GPDMA_IRQn | 通用 DMA |
| 4 | FLASH_IRQn | Flash 控制器 |
| 5 | WAKEUP_IRQn | 唤醒 |
| 6-7 | USB_IRQn | USB |
| 8-11 | I2C_IRQn | I2C |
| 12-15 | SPI_IRQn | SPI |
| 16-18 | UART_IRQn | UART |
| ... | BT_IRQn | 蓝牙控制器 |
| ... | CODEC_IRQn | 音频编解码 |
| ... | PMU_IRQn | 电源管理 |
3.6 Stage 4:BootInit 核心引导初始化
源文件:platform/cmsis/system_utils.c:380
3.6.1 完整执行序列
void BootInit(void)
{
// ═══ Phase 1:MPU 初始化 ═══
mpu_boot_cfg(); // 配置内存保护区域
// ═══ Phase 2:Cache 使能 ═══
hal_cache_enable(HAL_CACHE_ID_I_CACHE); // 指令缓存
hal_cache_enable(HAL_CACHE_ID_D_CACHE); // 数据缓存
hal_cache_writebuffer_enable(HAL_CACHE_ID_D_CACHE); // 写缓冲
hal_cache_writeback_enable(HAL_CACHE_ID_D_CACHE); // 回写模式
// ═══ Phase 3:GOT 基址初始化 ═══
GotBaseInit(); // 位置无关代码支持
// ═══ Phase 4:Boot Section 搬运 ═══
boot_init_boot_sections(); // Flash → SRAM(Boot 代码段)
// ═══ Phase 5:时钟系统初始化 ═══
hal_cmu_setup(); // 完整时钟树配置
// ═══ Phase 6:SRAM Section 搬运 ═══
boot_init_sram_sections(); // Flash → SRAM(运行时代码/数据)
// ═══ Phase 7:安全段初始化(仅 ARM_CMSE)═══
boot_init_security_sections(); // NSC veneer 代码搬运
// ═══ Phase 8:PSRAM 初始化(如果启用)═══
pmu_open(); // 先开 PMU 给 PSRAM 供电
hal_psram_init(); // PSRAM 控制器初始化
boot_init_psram_sections(); // PSRAM 代码/数据搬运
}
3.6.2 Memory Section 搬运详解
链接脚本定义了多个 section,需要从 Flash 搬运到 RAM 才能运行:
┌─────────────────────────────────────────────────────┐
│ Flash(LMA - Load Memory Address) │
│ ┌──────────────────┐ │
│ │ .boot_text_sram │───┐ │
│ │ .boot_data_sram │───┼──→ RAM(VMA) │
│ ├──────────────────┤ │ ┌──────────────────┐ │
│ │ .sram_text │───┤ │ .boot_text_sram │ │
│ │ .sram_data │───┤ │ .boot_data_sram │ │
│ ├──────────────────┤ │ │ .sram_text │ │
│ │ .fast_sram_text │───┤ │ .sram_data │ │
│ ├──────────────────┤ │ │ .fast_sram_text │ │
│ │ .nsc_text (CMSE) │───┘ │ .nsc_text │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ 搬运原因: │
│ 1. Flash 读取速度慢(~26 MHz),SRAM 可全速执行 │
│ 2. Boot 阶段 Flash 控制器未完全配置,RAM 是唯一可靠执行区 │
│ 3. 时序关键代码需要零等待执行 │
│ 4. NSC veneer 必须在特定安全属性的 RAM 区域 │
└─────────────────────────────────────────────────────┘
// 搬运实现(system_utils.c)
void boot_init_boot_sections(void)
{
uint32_t *dst, *src;
for (dst = __boot_sram_start__, src = __boot_sram_start_flash__;
src < __boot_sram_end_flash__;
dst++, src++) {
*dst = *src;
}
}
3.7 Stage 7:CMSE 安全世界主函数
源文件:platform/main/cmse_main.cpp
3.7.1 TrustZone 安全模型
┌─────────────────────────────────────────────────────────────────┐
│ ARM TrustZone Architecture │
│ │
│ ┌─────────────────────┐ ┌─────────────────────────────────┐ │
│ │ Secure World (S) │ │ Non-Secure World (NS) │ │
│ │ │ │ │ │
│ │ - Boot Loader │ │ - Application (main.cpp) │ │
│ │ - Secure Boot │ │ - RTOS (RTX5) │ │
│ │ - Key Storage │ │ - BT Stack │ │
│ │ - Crypto Engine │ │ - Audio │ │
│ │ - OTP Access │ │ - User App │ │
│ │ - MPC/SAU Config │ │ │ │
│ │ │ │ │ │
│ │ NSC Veneer │ │ │ │
│ │ ┌───────────────┐ │ │ │ │
│ │ │cmse_nonsecure │◄─┼────┼── NS 通过 veneer 调用 S 函数 │ │
│ │ │ _entry │ │ │ │ │
│ │ └───────────────┘ │ │ │ │
│ └─────────────────────┘ └─────────────────────────────────┘ │
│ │
│ 硬件隔离:SAU + MPC + NVIC.ITNS 三重保护 │
└─────────────────────────────────────────────────────────────────┘
3.7.2 CMSE Main 执行流程
int MAIN_ENTRY(void) // Secure World 入口
{
// ═══ [1] DMA 通道打开 ═══
hal_audma_open();
hal_gpdma_open();
// ═══ [2] 调试串口初始化(仅 DEBUG)═══
cmse_trace_init();
hal_iomux_set_uart0/1/2();
hal_trace_open(HAL_TRACE_TRANSPORT_UARTx);
// ═══ [3] Flash 安全初始化 ═══
sec_flash_init(HAL_FLASH_ID_0);
// ═══ [4] Flash ID 显示 ═══
hal_norflash_show_id_state(HAL_FLASH_ID_0, true);
// ═══ [5] PMU 完整初始化 ═══
pmu_open();
// ═══ [6] 左右耳识别(GPIO 板级 ID)═══
cmse_BoardidInit(); // P0_3 GPIO 读取
// ═══ [7] UART 通信测试 ═══
uart_init_cmse(); // UART1 @ 38400 bps
// 左耳发送 0xAA*4(9次),右耳发送 0xBB*4(6次)
// ═══ [8] 提升系统频率 ═══
hal_sysfreq_req(HAL_SYSFREQ_USER_INIT, HAL_CMU_FREQ_104M);
// ═══ [9] 安全外设初始化 ═══
hal_sec_init();
// ═══ [10] MPC(Memory Protection Controller)═══
mpc_init();
// ═══ [11] SAU(Security Attribution Unit)═══
TZ_SAU_Setup();
__ISB();
// ═══ [12] Dolby 解密区域加载 ═══
tz_customer_load_ram_ramx_section_info_t info;
info.flash_addr = inc_enc_bin_sec_start;
info.dst_text_sramx_addr_start = __customer_load_sram_text_start__;
// ... 配置地址
sec_decrypted_section_load_init(&info);
// ═══ [13] Dolby 安全启动验证 ═══
dlb_cmse_setup(DOLBY_PRIVATE_KEY_ADDR); // OTP 地址 0x38
// ═══ [14] 用户安全引导校验(可选)═══
#ifdef USER_SECURE_BOOT
if (user_secure_boot_check() != 0) {
ASSERT(0, "security check fail");
}
#endif
// ═══ [15] 释放高频请求,准备跳转 ═══
hal_sysfreq_req(HAL_SYSFREQ_USER_INIT, HAL_CMU_FREQ_32K);
// ═══ [16] 计算 NS 入口地址 ═══
uint32_t ns_app_start_addr = FLASHX_BASE + NS_APP_START_OFFSET;
// NS_APP_START_OFFSET = FLASH_S_SIZE = 0x80000(512 KB)
// ═══ [17] 验证 NS 镜像 Magic ═══
if (*(uint32_t *)ns_app_start_addr != BOOT_MAGIC_NUMBER) {
TRACE(0, "nonsec image magic error");
} else {
// ═══ [18] 跳转 Non-Secure World ═══
funcptr_void NonSecure_ResetHandler;
NonSecure_ResetHandler = (funcptr_void)(
(uint32_t)(&((struct boot_struct_t *)ns_app_start_addr)->hdr + 1)
);
NonSecure_ResetHandler(); // 永不返回
}
SAFE_PROGRAM_STOP();
}
3.7.3 SAU(Security Attribution Unit)配置
SAU 区域配置:
┌─────────────────────────────────────────────────────┐
│ Region │ Base Addr │ Limit Addr │ Attribute │
├────────┼──────────────┼──────────────┼──────────────┤
│ 0 │ NS Flash │ NS Flash End │ Non-Secure │
│ 1 │ NS RAM │ NS RAM End │ Non-Secure │
│ 2 │ Peripherals │ Periph End │ Non-Secure │
│ 3 │ NSC Veneer │ NSC End │ NS-Callable │
└────────┴──────────────┴──────────────┴──────────────┘
未被 SAU 覆盖的区域默认为 Secure。
3.7.4 MPC(Memory Protection Controller)
MPC 以 block 为粒度控制 SRAM 和 Flash 的安全属性:
MPC 配置 Flash:
[0x0C000000 ~ 0x0C080000) → Secure(CMSE 代码)
[0x0C080000 ~ Flash End) → Non-Secure(应用代码)
MPC 配置 SRAM:
[RAM_S_START ~ RAM_S_END) → Secure(安全世界数据)
[RAM_NS_START ~ RAM_END) → Non-Secure(应用数据)
3.8 Stage 8:Non-Secure World main()
源文件:platform/main/main.cpp
NS 世界经历相同的 Boot_Loader → NVIC_InitVectors → BootInit 流程后,进入 main():
int main(void)
{
// ═══ Phase 1:看门狗 ═══
app_wdt_open(15); // 15 秒超时
// ═══ Phase 2:硬件 GPIO 配置 ═══
tgt_hardware_setup(); // LED / 充电指示 GPIO
// ═══ Phase 3:ROM 工具函数初始化 ═══
rom_utils_init();
// ═══ Phase 4:获取当前线程 ID ═══
main_thread_tid = osThreadGetId();
// ═══ Phase 5:硬件定时器初始化 ═══
hwtimer_init();
// ═══ Phase 6:DMA ═══
hal_dma_set_delay_func((HAL_DMA_DELAY_FUNC)osDelay);
hal_audma_open();
hal_gpdma_open();
// ═══ Phase 7:Flash API ═══
norflash_api_init();
// ═══ Phase 8:日志系统 ═══
log_dump_init();
custom_offline_log_init();
crash_dump_init();
// ═══ Phase 9:调试串口 ═══
hal_iomux_set_uart0/1/2();
hal_trace_open(HAL_TRACE_TRANSPORT_UARTx);
hal_trace_set_log_level(TR_LEVEL_DEBUG);
// ═══ Phase 10:ISPI 访问初始化 ═══
hal_iomux_ispi_access_init();
// ═══ Phase 11:Flash 大小校验 ═══
uint32_t actualFlashSize = hal_norflash_get_flash_total_size(HAL_FLASH_ID_0);
ASSERT(FLASH_SIZE <= actualFlashSize);
// ═══ Phase 12:MPU 配置 ═══
mpu_cfg();
// ═══ Phase 13:PMU + 模拟外设 ═══
pmu_open();
analog_open();
// ═══ Phase 14:随机数种子 ═══
srand(hal_sys_timer_get());
// ═══ Phase 15:工厂区读取 ═══
factory_section_open();
// ═══ Phase 16:应用初始化 ═══
ret = app_init(); // BT / Audio / App 全部初始化
// ═══ Phase 17:主循环(RTOS 事件等待)═══
while(1) {
evt = osSignalWait(0x0, osWaitForever);
if (evt.value.signals & 0x04) break; // 关机
if (evt.value.signals & 0x08) break; // 重启
}
// ═══ Phase 18:系统关机/重启 ═══
app_deinit(ret);
pmu_shutdown(); // 或 pmu_reboot()
}
Part 4:系统核心并发机制与 RTOS
本部分介绍 RTX5 实时操作系统的集成配置,以及系统中两种典型的并发保护机制——硬件中断锁与 RTOS 互斥量,阐明各自适用场景及关键差异。
4.1 RTX5 集成
4.1.1 RTX5 配置参数
// config/common.mk 中定义的 RTOS 参数:
KERNEL = RTX5 // CMSIS-RTOS2 内核
OS_CLOCK_NOMINAL = 16000 // SysTick 频率 16 KHz(M33)
OS_TASKCNT = 12 // 最大任务数(TWS 模式)
OS_SCHEDULERSTKSIZE = 768 // 调度器堆栈
OS_IDLESTKSIZE = 512 // 空闲任务堆栈
4.1.2 RTOS 关键中断
RTX5 使用的 ARM 异常:
┌──────────────────────────────────────────────────┐
│ SysTick(IRQ#15) │
│ 频率:16 KHz(每 62.5 us 触发一次) │
│ 功能:时间片调度,osDelay 计时 │
├──────────────────────────────────────────────────┤
│ PendSV(IRQ#14) │
│ 优先级:最低(0xFF) │
│ 功能:上下文切换(任务调度器触发) │
├──────────────────────────────────────────────────┤
│ SVC(IRQ#11) │
│ 功能:系统调用入口 │
│ 用途:osKernelStart、osThreadCreate 等 │
└──────────────────────────────────────────────────┘
4.1.3 任务切换流程
Current Task (TaskA) PendSV Handler New Task (TaskB)
│ │ │
│ osSignalSet/osDelay │ │
│─────────────────────────► │ │
│ │ 1. 保存 TaskA 上下文 │
│ │ (R4-R11, PSP, LR) │
│ │ │
│ │ 2. 选择最高优先级任务 │
│ │ │
│ │ 3. 恢复 TaskB 上下文 │
│ │────────────────────────►│
│ │ │ 继续执行
4.2 中断锁机制:int_lock_global vs osMutexWait
核心区分:
int_lock_global= 关闭 CPU 中断(硬件层面,纳秒级);osMutexWait= RTOS 互斥锁(软件层面,需要调度器参与)。
4.2.1 int_lock_global 原理
源文件:platform/cmsis/inc/cmsis.h:56
__STATIC_FORCEINLINE uint32_t int_lock_global(void)
{
uint32_t pri = __get_PRIMASK(); // 读当前 PRIMASK 寄存器(保存旧状态)
if ((pri & 0x1) == 0) { // 如果中断当前是开着的
__disable_irq(); // 写 PRIMASK=1 → 关闭所有可屏蔽中断
}
return pri; // 返回旧状态(用于恢复)
}
__STATIC_FORCEINLINE void int_unlock_global(uint32_t pri)
{
if ((pri & 0x1) == 0) { // 加锁前中断是开的
__enable_irq(); // 写 PRIMASK=0 → 恢复中断
}
}
PRIMASK 寄存器是 ARM Cortex-M 的 1-bit 特殊寄存器:
-
PRIMASK = 0:允许所有可屏蔽中断(正常状态) -
PRIMASK = 1:屏蔽所有优先级的可屏蔽中断(NMI 和 HardFault 除外)
执行流程:
Task A 所有 IRQ
│ │
│ uint32_t lock = int_lock_global()
│ PRIMASK ← 1 ──────────────── X (硬件门控,中断请求被 CPU 忽略)
│
│ <临界区,安全访问共享资源>
│
│ int_unlock_global(lock)
│ PRIMASK ← 0 ──────────────── ✓ (立即恢复中断响应)
关键特性:
-
单条汇编指令(
CPSID I/CPSIE I),2 个 CPU 周期内完成 -
不涉及 RTOS,在中断上下文、Boot 阶段、RTOS 启动前均可使用
-
临界区必须极短(通常 < 1 μs)
-
嵌套安全:返回值
pri记录调用前状态,unlock时只恢复原状态
4.2.2 int_lock(BASEPRI 模式)— INT_LOCK_EXCEPTION 变体
当编译宏 INT_LOCK_EXCEPTION 开启时,int_lock() 改用 BASEPRI 寄存器:
// BASEPRI 模式:只屏蔽优先级 >= IRQ_PRIORITY_HIGHPLUS(2) 的中断
// 允许 IRQ_PRIORITY_REALTIME(0) 和 IRQ_PRIORITY_HIGHPLUSPLUS(1) 继续响应
__set_BASEPRI(((IRQ_PRIORITY_HIGHPLUS << (8 - __NVIC_PRIO_BITS)) & 0xFFUL));
// BES1502P:__NVIC_PRIO_BITS = 3
// BASEPRI 写入值 = (2 << 5) = 0x40
// 效果:只有优先级 0(REALTIME) 和 1(HIGHPLUSPLUS) 的 IRQ 可以打断临界区
优先级对照(BES1502P,3 priority bits):
0 = REALTIME ← 不被屏蔽(最高优先级实时中断)
1 = HIGHPLUSPLUS ← 不被屏蔽
2 = HIGHPLUS ← 被屏蔽(BASEPRI 阈值)
3 = HIGH ← 被屏蔽
4 = ABOVENORMAL ← 被屏蔽
5 = NORMAL ← 被屏蔽
6 = BELOWNORMAL ← 被屏蔽
7 = LOW ← 被屏蔽(最低优先级)
4.2.3 osMutexWait 原理
源文件:services/interconnection/umm_malloc/umm_malloc.c:70
void LOCK_UMM_CLOCK(void)
{
if (osMutexWait(umm_clock_mutex_id, osWaitForever) != osOK)
DBGLOG_INFO(1, "%s Error", __func__);
}
osMutexWait 是 CMSIS-RTOS v1 API(CMSIS-RTOS2 对应 osMutexAcquire),实现原理:
Task A 调用 osMutexWait(mutex, osWaitForever):
│
├─ 互斥锁未被占用?
│ YES → 原子地标记"被 Task A 持有",立即返回 osOK
│ NO → Task A 进入 BLOCKED 状态,加入等待队列
│ │
│ └─ RTX5 调度器切换到其他就绪任务
│ ...(Task B/C 运行中)...
│ Task X 调用 osMutexRelease(mutex)
│ │
│ └─ 唤醒等待队列中优先级最高的任务(Task A)
│ Task A 变为 READY,重新参与调度
│ 下次 Task A 被调度时,从 osMutexWait 返回 osOK
4.2.4 核心差异对比
| 维度 | int_lock_global |
osMutexWait |
|---|---|---|
| 实现层 | CPU 硬件寄存器(PRIMASK) | RTOS 软件对象 |
| 速度 | 2 个 CPU 周期(~10 ns @208 MHz) | 数百 CPU 周期(涉及调度器) |
| 阻塞行为 | 不阻塞,立即关中断 | 可阻塞,锁忙时任务挂起 |
| 中断上下文 | ✅ 可以使用 | ❌ 不能在 ISR 中调用(触发 HardFault) |
| RTOS 启动前 | ✅ Boot 阶段可用 | ❌ 必须在 osKernelStart 后才有效 |
| 优先级反转 | 无(无调度概念) | RTX5 内置优先级继承机制,防止反转 |
| 嵌套 | ✅ 支持(靠返回值记录状态) | ⚠️ RTX5 mutex 支持递归(需配置) |
| 保护粒度 | 必须极短(影响中断实时性) | 任意长度 |
| 适用场景 | 极短临界区、Boot/ISR 上下文 | 任务间共享资源(堆、链表、Flash API 等) |
4.2.5 umm_malloc 为什么两种方式并存
// services/interconnection/umm_malloc/umm_malloc.c
#ifdef RS_BASE_LINE
// RS 基线版本:Boot 阶段或无 RTOS 场景,用中断锁
static uint32_t umm_lock = 0;
void LOCK_UMM_CLOCK(void) { umm_lock = int_lock(); }
void UNLOCK_UMM_CLOCK(void) { int_unlock(umm_lock); }
#else
// 正常运行版本(有 RTOS):用互斥锁,支持任务间安全并发
void LOCK_UMM_CLOCK(void) { osMutexWait(umm_clock_mutex_id, osWaitForever); }
void UNLOCK_UMM_CLOCK(void) { osMutexRelease(umm_clock_mutex_id); }
#endif
-
RS_BASE_LINE场景下,RTOS 可能尚未启动,int_lock是唯一安全选择 -
正常运行时,堆操作可能耗时较长,用 Mutex 让其他任务不被"饿死"
4.2.6 使用决策树
需要保护共享资源?
│
├─ 是否在 ISR / Boot 阶段 / RTOS 启动前?
│ YES → 必须用 int_lock_global / int_lock
│
├─ 临界区是否极短(< ~10 条指令)?
│ YES → 用 int_lock(代价最小)
│ NO → 考虑 Mutex,避免屏蔽中断过久
│
└─ 是否跨任务共享(任一任务可能等待)?
YES → 用 osMutexWait(任务可休眠让出 CPU)
NO → int_lock 足够
Part 5:存储布局与数据持久性策略
本部分从工程实践视角梳理 Flash 分区布局、供电域划分、RTC 独立运行机制,以及各类复位/关机场景下不同存储介质的数据生存性,为固件设计和数据可靠性保障提供参考。
5.1 Flash 存储结构
5.1.1 NOR Flash 配置
// norflash_cfg.h
struct norflash_cfg_struct_t {
uint8_t neg_phase:1; // 负相位
uint8_t pos_neg:1; // 正/负相位
uint8_t samdly:3; // 采样延时(0-7)
uint8_t div; // 时钟分频(最小 2)
uint8_t dualmode:1; // 双线模式
uint8_t quadmode:1; // 四线模式
uint8_t mod_clk:4; // 模块时钟选择
uint8_t rdcmd; // 读命令(0x03 慢速 / 0x0B 快速)
uint8_t frdcmd; // 快读命令(0x0B)
uint8_t qrdcmd; // 四线读命令(0xEB)
};
5.1.2 Flash 分区布局
┌──────────────────────────────────────────────────────┐
│ Flash Physical Layout │
├──────────────────────────────────────────────────────┤
│ 0x00000 ┌─────────────────────────────────────┐ │
│ │ Secure Boot Header (boot_struct_t) │ │
│ │ + CMSE Firmware │ │
│ 0x80000 ├─────────────────────────────────────┤ │
│ │ NS Boot Header (boot_struct_t) │ │
│ │ + Application Firmware │ │
│ │ + BT Stack │ │
│ │ + Resources │ │
│ ├─────────────────────────────────────┤ │
│ ├─────────────────────────────────────┤ │
│ │ REGISTER_ENC_CRC(192 KB) │ │
│ ├─────────────────────────────────────┤ │
│ │ IPU_ENC_CRC(192 KB) │ │
│ ├─────────────────────────────────────┤ │
│ │ OTA Backup Area │ │
│ ├─────────────────────────────────────┤ │
│ │ Factory Section(NV Data) │ │
│ End └─────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
5.2 关键数据结构
5.2.1 Boot Header Chain
// Flash 中的镜像布局:
//
// |<--- boot_struct_t --->|<--- code_sig_struct_t --->|<--- Code --->|
//
// boot_struct_t:
// magic = 0xBE57EC1C
// flag, version
// security settings
// RSA3072 public key(772 bytes)
// RSA3072 signature(384 bytes)
//
// code_sig_struct_t:
// code_size(被签名的代码大小)
// code signature(384 bytes)
//
// 验签流程:
// 1. ROM 读取 boot_struct_t 中的 public key
// 2. 计算 code 区域的 SHA256 哈希
// 3. 用 RSA3072 验证 sig 是否匹配
// 4. 通过则允许执行,否则卡死
5.2.2 系统频率请求模型
// 多模块独立请求系统频率,取最大值
hal_sysfreq_req(HAL_SYSFREQ_USER_INIT, HAL_CMU_FREQ_104M);
hal_sysfreq_req(HAL_SYSFREQ_USER_RS_DOLBY_DECRYPTED, HAL_CMU_FREQ_208M);
// 模块完成工作后释放:
hal_sysfreq_req(HAL_SYSFREQ_USER_INIT, HAL_CMU_FREQ_32K);
// 系统实际频率 = max(所有模块请求)
// 32K 表示"我不需要高频",不是真的跑 32 KHz
5.3 供电域、RTC 与数据持久性
5.3.1 BES1502P 供电域全景
芯片内部存在多个完全独立的供电域,可在不同上电/断电状态下独立工作:
VBAT(电池 3.0~4.2 V)
│
├─── VRTC 域(LDO_VRTC, 0.5~0.825 V)←─ 最小常供电域
│ │
│ ├─ PMU 核心逻辑(寄存器状态)
│ ├─ RTC 计数器 + 日历模块
│ ├─ PMU_REG_NOT_RESET_61 等"不复位寄存器"
│ └─ 32K 时钟(LPO / 外部晶振)
│
├─── AON 域(Always-On)
│ │
│ ├─ AON CMU(AONCMU_BASE = 0x40080000)
│ │ └─ BOOTMODE 寄存器(0x38 偏移)
│ └─ 部分 GPIO(唤醒用)
│
├─── DCDC_DIG(数字核, 0.65~1.1 V)── CPU 关机后断电
│ └─ ARM Cortex-M33 Core
│ SRAM(RAM0~RAM9,约 2 MB)
│ Flash 控制器 / PSRAM 控制器
│
├─── DCDC_ANA(模拟核, 1.4~1.9 V)── CPU 关机后可断电
│ └─ CODEC / PLL / RF / XTAL
│
└─── DCDC_HPPA(耳机功放, 1.4~1.9 V)── 按需开关
└─ Headphone PA
关键结论:
-
SRAM 属于
DCDC_DIG域 → 断电/关机后全部清零 -
Flash 是独立的非易失性存储 → 任何复位/断电后数据不变
-
VRTC 域由 VBAT 直接维持 → 只要电池在,VRTC 域就存活
5.3.2 RTC:关机状态下独立运行的时钟
RTC 集成在 PMU 模拟域内,由 VRTC LDO 独立供电。
供电配置:
// platform/drivers/ana/best1502p/reg_pmu_best1502p.h
PMU_REG_LDO_VRTC_CFG = 0x129
PMU_REG_LDO_VRTC_VOLT = 0x12A
// VRTC 电压范围:0.45 V ~ 0.825 V
// 默认活跃电压:PMU_LDO_VRTC_DEFAULT_ACT = 0.8 V
// 默认低功耗: PMU_LDO_VRTC_DEFAULT_LP = 0.65 V
RTC 寄存器:
// PMU RTC 寄存器(通过 ISPI 访问)
PMU_REG_RTC_LOAD_LOW/HIGH // 设置初始计数值
PMU_REG_RTC_VAL_LOW/HIGH // 读取当前计数值
PMU_REG_RTC_DIV_1HZ // 分频:32768 Hz → 1 Hz
PMU_REG_RTC_MATCH1_LOW/HIGH // 匹配/唤醒比较值
// 日历格式寄存器(年月日时分秒)
PMU_REG_RTC_LOAD_MIN_SEC_EN // 加载分/秒
PMU_REG_RTC_LOAD_HOURS // 加载小时
PMU_REG_RTC_LOAD_DATAS_EN // 加载日期
PMU_REG_RTC_LOAD_YEARS // 加载年份
PMU_REG_RTC_GET_MIN_SEC // 读取分/秒
PMU_REG_RTC_GET_HOURS // 读取小时
PMU_REG_RTC_GET_DATAS // 读取日期
PMU_REG_RTC_GET_YEARS // 读取年份
RTC 时钟源(二选一,由 CLK_32K_SEL 配置):
┌─ LPO(Low Power Oscillator):片内 RC 振荡器
│ 精度:~±3%,无需外部元件,功耗极低
│ 适用:对时间精度要求不高的场景
│
└─ XO_32K:外部 32.768 KHz 晶振
精度:±20 ppm(±1.7 秒/天)
稳定时间:PMU_EXT_32K_STABLE_TIME = 300 ms
适用:需要精确日历/闹钟的场景
关机后 RTC 的行为:
pmu_shutdown() 执行后(SOFT_POWER_OFF=1):
DCDC_DIG 断电 → CPU 停止 → SRAM 清零
DCDC_ANA 断电 → PLL/RF/CODEC 停止
│
▼
VRTC 域仍然由 VBAT 维持:
RTC 计数器继续计数 ✓ (32K 时钟仍运行)
RTC 日历继续走时 ✓
PMU 状态寄存器保持 ✓
PMU_REG_NOT_RESET_61 保持 ✓
│
├─ [场景 1] RTC 闹钟触发唤醒
│ pmu_rtc_calendar_pwron_enable(true) 设置允许 RTC 开机
│ → 到达设定时间 → PMU 触发开机
│ → pmu_boot_cause_get() == PMU_BOOT_CAUSE_RTC
│
└─ [场景 2] 用户按电源键开机
→ pmu_boot_cause_get() == PMU_BOOT_CAUSE_POWER_KEY
代码示例:
// 设置 RTC 日历时间
struct RTC_CALENDAR_FORMAT_T cal = {
.year = 24, .month = 6, .day = 10,
.hour = 8, .min = 0, .sec = 0,
};
pmu_rtc_calendar_set(&cal);
// 设置 10 分钟后 RTC 唤醒开机
struct RTC_CALENDAR_FORMAT_T alarm = {
.hour = 8, .min = 10, .sec = 0, ...
};
pmu_rtc_calendar_alarm_set(&alarm);
pmu_rtc_calendar_pwron_enable(true); // 允许 RTC 触发开机
// 执行关机(VRTC 域继续运行,CPU 停止)
pmu_shutdown();
// 10 分钟后 RTC 匹配 → PMU 自动上电 → 正常启动流程
// pmu_boot_cause_init() 中识别到 PMU_BOOT_CAUSE_RTC
5.3.3 各复位/关机场景下的数据存活性
复位/关机类型 │ SRAM │ AON_CMU.BOOTMODE │ PMU VRTC 寄存器 │ NOR Flash │ eFuse
───────────────────────┼──────┼──────────────────┼────────────────┼───────────┼──────
软件重启 │ 清零 │ 保持 │ 保持 │ 保持 │ 永久
(hal_cmu_sys_reboot)
───────────────────────┼──────┼──────────────────┼────────────────┼───────────┼──────
WDT 复位 │ 清零 │ 清零(注意!) │ 保持 │ 保持 │ 永久
(pmu_wdt_reboot)
───────────────────────┼──────┼──────────────────┼────────────────┼───────────┼──────
软件关机 │ 清零 │ 可能保持/清零 │ 保持 │ 保持 │ 永久
(pmu_shutdown)
───────────────────────┼──────┼──────────────────┼────────────────┼───────────┼──────
Deep Sleep │ 可配置│ 保持 │ 保持 │ 保持 │ 永久
(pmu_sleep) │(保持)│ │ │ │
───────────────────────┼──────┼──────────────────┼────────────────┼───────────┼──────
电池彻底拔除 │ 清零 │ 清零 │ 清零 │ 保持 │ 永久
───────────────────────┴──────┴──────────────────┴────────────────┴───────────┴──────
关键实现细节:
// pmu_sys_ctrl() 中复位前写 PMU 魔术字 → PMU 内部复位
pmu_write(PMU_REG_METAL_ID, 0xCAFE);
pmu_write(PMU_REG_METAL_ID, 0x5FEE);
// WDT 复位和 pmu_sys_ctrl() 最终都调用:
pmu_wdt_reboot();
// 注释:"Use pmu wdt reset instead of global reset, bootmode will be cleared"
// ↑ WDT 复位会清 BOOTMODE,而全局复位不会
// PMU_REG_NOT_RESET_61:VRTC 域内的"不被复位"寄存器,用于判断是否首次上电
pmu_read(PMU_REG_NOT_RESET_61, &val);
if ((val & REG_NOT_RESET_CHIP_PWR_ON) == 0) {
pmu_boot_first_pwr_up = true; // 真正的首次上电(电池刚插入)
}
val |= REG_NOT_RESET_CHIP_PWR_ON;
pmu_write(PMU_REG_NOT_RESET_61, val);
// 此位在 VRTC 掉电前(电池拔除)才会变回 0
// 软件重启/WDT/关机 都不影响它 → 所以叫 NOT_RESET
5.3.4 BOOTMODE 寄存器:跨重启的通信桥梁
AON_CMU.BOOTMODE(物理地址 0x40080038)存在于 AON 域,被软件复位时通常不会被自动清零:
┌─────────────────────────────────────────────────────────────┐
│ AON_CMU.BOOTMODE 寄存器(32-bit) │
│ │
│ [3:0] HW_BOOTMODE(硬件置位,标识复位原因) │
│ bit0:watchdog 复位 │
│ bit1:global 复位 │
│ bit2:RTC 唤醒 │
│ bit3:charger 插入 │
│ │
│ [31:4] SW_BOOTMODE(软件置位,跨重启传递意图) │
│ bit4: READ_ENABLED │
│ bit7: FORCE_USB_DLD(强制进 USB 下载模式) │
│ bit12:FACTORY(进入产测模式) │
│ bit16:REBOOT(主动软重启标记) │
│ bit19:POWER_DOWN_WAKEUP │
│ bit25:REBOOT_FROM_CRASH(崩溃重启标记) │
└─────────────────────────────────────────────────────────────┘
用法示例:
// 重启前标记"进入 OTA 模式"
hal_sw_bootmode_set(HAL_SW_BOOTMODE_FORCE_USB_DLD);
pmu_reboot();
// 重启后读取
uint32_t bm = hal_sw_bootmode_get();
if (bm & HAL_SW_BOOTMODE_FORCE_USB_DLD) {
// 进入 USB 下载模式
}
5.3.5 Flash 各分区读写权限与持久性
| 偏移 | 分区 | 持久性 | 固件改写条件 |
|---|---|---|---|
| 0x00000 | Secure Boot Header + CMSE 固件(512 KB) | 永久 | 需签名验证,通常不改写 |
| 0x80000 | NS App 固件 + BT Stack | 永久 | OTA 升级时改写,写前须先擦除 |
| CUSTOM_(64 KB) | 永久 | 专用烧录流程 | |
| REGISTER_ENC / IPU_ENC_CRC | ENC 注册数据(各 192 KB) | 永久 | 出厂烧录,不应被用户改写 |
| OTA Backup | OTA 备份区 | 永久 | OTA 流程改写 |
| Factory Section | 产测/配对数据(NV Data) | 永久 | 运行时可读写(见下方说明) |
Factory Section — 运行时持久化存储
这是固件运行期间唯一会主动写 Flash 的用户数据区:
// services/nv_section/factory_section/factory_section.c
// 写入:先擦扇区再写整页(4 KB sector)
norflash_api_erase(NORFLASH_API_MODULE_ID_FACTORY,
(uint32_t)(__factory_start) & 0x00FFFFFF,
FACTORY_SECTOR_SIZE, false);
norflash_api_write(NORFLASH_API_MODULE_ID_FACTORY,
(uint32_t)(__factory_start) & 0x00FFFFFF,
(uint8_t *)tmpBuf, FACTORY_SECTOR_SIZE, false);
存储内容:蓝牙配对记录(MAC + LinkKey)、音量/EQ 偏好设置、工厂校准数据、入耳检测阈值、产测标记位。这些数据跨任何复位/关机都能保持,只要不调用 Factory Reset(擦除)。
NV Record — 用户运行时数据:
// services/nv_section/userdata_section/
// 存储:蓝牙配对历史、用户设置、ANC 配置、Fast Pair 账户密钥等
// 机制:类似日志追加,定期整理(wear leveling)
5.3.6 实际工程场景对照
| 场景 | 发生了什么 | 数据丢失 | 数据保留 |
|---|---|---|---|
| 代码崩溃 WDT 重启 | 数字域复位,SRAM 清零 | 堆/栈上所有临时数据 | Flash 配对记录、用户设置、RTC 时间 |
| 长按电源键关机 | SOFT_POWER_OFF,DCDC_DIG 断电 | SRAM 全部 | RTC 继续走时、Flash 所有数据 |
| OTA 升级重启 | 先写 Flash 备份区,再 reboot | 重启前 RAM 数据 | 新固件、旧配对记录、设置 |
| 电池拔除重插 | VRTC 掉电,PMU 寄存器复位 | SRAM、RTC 时间、BOOTMODE | Flash 配对记录、用户设置、eFuse |
| 工厂复位 | 主动擦除 Factory Section | Factory Section 数据 | 固件本体、eFuse |
| 固件烧录(全擦) | 整片 Flash 擦除再写入 | 所有 Flash 数据(含配对) | eFuse(物理熔丝,无法擦除) |
5.3.7 Deep Sleep 时的 SRAM 数据保持
BES1502P 的 SRAM 在 Deep Sleep 模式下可配置为 Retention(数据保持) 模式:
// AON CMU RAM 配置寄存器
// platform/hal/best1502p/reg_aoncmu_best1502p.h
__IO uint32_t RAM_CFG; // 0x5C - 控制每块 SRAM 的低功耗模式
__IO uint32_t RAM2_CFG0; // 0x88
__IO uint32_t RAM2_CFG1; // 0x8C
__IO uint32_t RAM3_CFG0; // 0x90
__IO uint32_t RAM3_CFG1; // 0x94
// 每块 SRAM 可独立选择:
// Active: 正常读写
// Light Sleep: 保持数据,降低功耗(几十 nA/KB 量级)
// Deep Sleep: 保持数据,极低功耗(几 nA/KB 量级)
// Power Down: 断电,数据丢失,功耗最低
Deep Sleep 时的存储层次:
┌────────────────────────────────────────────────┐
│ 电源状态 数据状态 功耗 │
│ VRTC: ON RTC 计数持续 < 1 μA │
│ DCDC_DIG: OFF─┐ │
│ ├─ Retention SRAM:数据保持 ✓ │
│ │ (数十 KB 关键数据)~μA 量级 │
│ └─ 其他 SRAM:断电,数据丢失 │
│ Flash: OFF(不通电,数据天然保持)✓ │
└────────────────────────────────────────────────┘
唤醒后 CPU 恢复执行,Retention SRAM 中的数据
(如 BT 连接状态、快速重连信息)可直接使用,
无需重新从 Flash 读取 → 显著缩短唤醒延迟。
Part 6:工程参考手册与 FAQ
本部分提供源文件索引、编译配置摘要和常见问题解答,供日常开发查阅。
6.1 源文件索引
| 启动阶段 | 源文件 | 关键函数 |
|---|---|---|
| ASM 入口 | platform/main/startup_main.S |
Boot_Loader |
| 向量表 | platform/cmsis/cmsis_nvic.c |
NVIC_InitVectors |
| Boot 初始化 | platform/cmsis/system_utils.c |
BootInit |
| 时钟配置 | platform/hal/hal_cmu_common.c |
hal_cmu_setup |
| PMU 驱动 | platform/drivers/ana/best1502p/pmu_best1502p.c |
pmu_open |
| 模拟驱动 | platform/drivers/ana/best1502p/analog_best1502p.c |
analog_open |
| 充电管理 | platform/drivers/ana/best1502p/charger_best1502p.c |
charger_init |
| RF/XTAL | platform/drivers/ana/best1502p/rf_xtal_best1502p.c |
rf_xtal_init |
| CMSE 主函数 | platform/main/cmse_main.cpp |
MAIN_ENTRY |
| NS 主函数 | platform/main/main.cpp |
main |
| Boot 结构体 | utils/boot_struct/tool_msg.h |
boot_struct_t |
| Flash 配置 | utils/boot_struct/norflash_cfg.h |
norflash_cfg_struct_t |
| 内存映射 | platform/hal/best1502p/plat_addr_map_best1502p.h |
地址宏定义 |
| 编译配置 | config/common.mk |
芯片/RTOS/功能开关 |
| 链接脚本 | scripts/link/tail_section.lds.S |
Flash 分区 |
| RTOS 内核 | rtos/rtx5/rtx_kernel.c |
osKernelStart |
| RTOS 线程 | rtos/rtx5/rtx_thread.c |
上下文切换 |
6.2 编译配置摘要
# config/common.mk(best1502p 关键配置):
CHIP = best1502p
CPU = m33 # Cortex-M33
CHIP_HAS_FPU = 1 # 硬件浮点
CHIP_HAS_USB = 1 # USB OTG
CHIP_HAS_USBPHY = 1 # USB PHY
CHIP_HAS_PSRAM = 1 # 外部 PSRAM
CHIP_HAS_CP = 1 # Co-Processor
CHIP_HAS_PSC = 1 # Power State Controller
CHIP_HAS_SECURE_BOOT = 1 # 安全引导
CHIP_FLASH_CTRL_VER = 7 # Flash 控制器 V7
CHIP_PSRAM_CTRL_VER = 8 # PSRAM 控制器 V8
CHIP_CACHE_VER = 5 # Cache V5
CHIP_RAM_BOOT = 1 # 支持 RAM Boot
PMU_IRQ_UNIFIED = 1 # PMU 中断统一处理
PMU_FORCE_LP_MODE = 1 # 强制低功耗模式
BOOT_LOADER_ENTRY_HOOK = 1 # Boot 入口 Hook
PROGRAMMER_BOOT_ENTRY_HOOK = 1 # 编程器 Boot Hook
# RTOS 配置:
RTOS = 1
KERNEL = RTX5
OS_CLOCK_NOMINAL = 16000 # 16 KHz tick
OS_TASKCNT = 12 # TWS 模式最大任务数
6.3 常见问题解答(FAQ)
Q1:为什么有 Secure 和 Non-Secure 两个 main()?
ARM TrustZone 将 CPU 分为两个世界。Secure World(cmse_main.cpp)先启动,完成安全初始化和验签后,跳转到 Non-Secure World(main.cpp)运行实际应用。即使 NS 代码被篡改,S 世界的密钥和验签逻辑也不会泄露。
Q2:为什么要把代码从 Flash 搬到 RAM?
三个原因:
-
速度:Flash 读取有等待周期,SRAM 是零等待
-
启动依赖:Boot 阶段 Flash 控制器还未完全配置,某些代码必须从 RAM 执行
-
安全:NSC veneer 必须放在安全属性的 RAM 中
Q3:ISPI 是什么?
Internal SPI——芯片数字域与模拟域之间的内部通信总线。CPU 不能直接访问 PMU/RF 寄存器(它们在模拟域),必须通过 ISPI 发送读写命令。这就是为什么 hal_analogif_open() 必须在 pmu_boot_init() 之前调用。
Q4:RTX5 的任务在哪里创建的?
在 app_init() 中。BES SDK 使用 CMSIS-RTOS2 API:
osThreadNew(app_thread, NULL, &app_thread_attr);
osThreadNew(bt_thread, NULL, &bt_thread_attr);
// 等等...
main() 本身运行在 RTX5 自动创建的一个线程中。
Q5:看门狗什么时候开始工作?
main() 的第一行就开启了 15 秒超时的看门狗。如果 Boot 过程卡住超过 15 秒,WDT 会自动复位芯片。正常工作后由各任务定期喂狗。
Q6:OTP 烧录后能读出来吗?
取决于保护设置。普通校准数据(DCDC 电压)可读;读保护的 page(如密钥哈希)一旦锁定,CPU 也读不出明文,只能被特定硬件加速器直接使用。
Q7:为什么 CMSE 阶段要"降频到 32K"再跳 NS?
hal_sysfreq_req(HAL_SYSFREQ_USER_INIT, HAL_CMU_FREQ_32K) 是"释放本模块的高频请求",不是真的把 CPU 降到 32 KHz。系统实际频率取所有模块请求的最大值。CMSE 完成工作后释放高频占用,让 NS 世界自己按需提频,避免浪费功耗。
Q8:int_lock 和 int_lock_global 有什么区别?
在不开 INT_LOCK_EXCEPTION 宏时两者完全相同(int_lock 直接调用 int_lock_global)。开启 INT_LOCK_EXCEPTION 后:
-
int_lock_global始终操作 PRIMASK(关闭所有可屏蔽中断) -
int_lock改用 BASEPRI(只屏蔽优先级 ≥ HIGHPLUS 的中断,保留 REALTIME/HIGHPLUSPLUS 级别的实时响应)
BES1502P 工程中 INT_LOCK_EXCEPTION 的使能状态决定了实际行为,需查看 config/best1502p/target.mk。
*芯片:BES1502P (BES2710Y) \
更多推荐

所有评论(0)