2. CMS32 启动流程详解
芯片上电之后到底发生了什么?这个问题,我当年刚入行时也困惑了很久。说白了,启动流程就是芯片从“死”到“活”的第一步。CMS32 的启动路径并不复杂,但有几个关键节点你必须吃透——中断向量表、启动模式选择,还有那个容易踩坑的重映射机制。
2.1 芯片上电复位流程
按下复位键,或者电源刚稳定,CMS32 内部会走一套固定的“开机动作”。我把它拆成四个阶段,你对照着看:
| 阶段 | 硬件行为 | 耗时(典型) |
|---|---|---|
| 1. 电源稳定 | POR(上电复位)检测 VDD 是否达到阈值 | ~2 ms |
| 2. 时钟起振 | 内部高速振荡器(HSI)启动,等待稳定 | ~1 ms |
| 3. 向量表加载 | 从 Flash 起始地址读取栈指针和复位向量 | ~10 µs |
| 4. 跳转执行 | PC 指向复位向量,开始执行第一条指令 | 即时 |
这里有个细节:CMS32 的复位向量地址是 0x0000_0004,而栈指针在 0x0000_0000。硬件自动完成这两个加载动作,不需要软件干预。嗯,但你要知道——这只是默认情况。
2.2 中断向量表重映射
为什么需要重映射?你想想看,如果 Bootloader 和 App 都抢同一个向量表地址,那不乱套了?
CMS32 提供了一个叫 VTOR(向量表偏移寄存器) 的东西。通过修改它,你可以把向量表搬到任意地址。我一般在 Bootloader 里把 VTOR 设到 0x0000_0000,在 App 里设到 0x0001_0000(假设 App 起始地址)。
/* 设置向量表偏移到 0x0001_0000 */
SCB->VTOR = 0x00010000;
/* 或者用 CMSIS 标准函数 */
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x10000);
重映射的时机也很关键。我个人习惯在跳转前的最后一步才改 VTOR,改完立刻执行一条 DSB 指令保证生效,然后再跳。
2.3 启动模式选择:Boot 模式 vs App 模式
CMS32 怎么知道这次该跑 Bootloader 还是 App?说白了,就是看一个标志。这个标志可以放在 RAM 里、备份寄存器里,或者 Flash 的某个固定地址。
我常用的方案是这样的:
- Boot 模式: 上电后检查到标志为
0xA5A5,或者检测到外部引脚拉低,就留在 Bootloader 等待升级。 - App 模式: 标志为
0x5A5A,或者超时无操作,直接跳转到 App 入口。
下面这张图是我自己项目里用的启动决策流程,你可以参考:
你可能会问:为什么不用 GPIO 电平来判断?当然可以。但用标志位更灵活——你可以通过 OTA 远程切换启动模式,不用改硬件。我在一个量产项目里就靠这个标志位实现了“强制恢复模式”:如果 App 连续三次启动失败,Bootloader 自动把标志改成 0xA5A5,下次就留在 Boot 模式等升级。
2.4 启动流程中的避坑清单
这些年我帮客户排查过不少启动相关的问题,总结下来就这几条:
- 向量表对齐: CMS32 要求 VTOR 的值必须是 512 字节对齐的。如果你设了个奇数地址,芯片直接忽略低 9 位。
- 栈指针有效性: 跳转前检查 App 起始地址的前 4 个字节(栈顶指针)是否在 RAM 范围内。我曾经见过有人把 Flash 里的随机数据当栈指针用,一跑就飞。
- 外设状态清理: Bootloader 里开了看门狗、定时器、DMA,跳转前必须关干净。App 不会帮你做这些。
- 中断优先级分组: 如果 Bootloader 和 App 用了不同的优先级分组(比如 4 位 vs 3 位),跳转后中断行为会乱掉。我建议统一用 4 位抢占优先级。
嗯,启动流程这部分就这些。你只要把向量表重映射和模式选择这两个点吃透,后面写 Bootloader 和 App 的跳转逻辑就会顺手很多。