2. CMS32硬件架构速览:Cortex-M0+内核特性、存储器映射、Flash与SRAM分区、系统控制块(SCB)
做Bootloader,说白了就是跟硬件打交道。你得知道CPU怎么跑、内存怎么排、Flash怎么擦写。这些搞不清楚,后面写代码就是瞎蒙。这一章我带你把CMS32的硬件骨架摸一遍,重点挑跟Bootloader相关的部分讲。
2.1 Cortex-M0+内核特性:为什么选它做Bootloader
Cortex-M0+是ARM家族里最精简的内核之一。我刚开始接触它的时候,觉得“这玩意儿也太省了吧?”但用久了才发现,正是这种精简,让它特别适合做Bootloader这种底层固件。
几个关键点你得记住:
- 指令集:只支持Thumb/Thumb-2子集,没有ARM模式。好处是代码密度高,坏处是某些复杂运算得靠库。
- 中断响应:硬件自动压栈R0-R3、R12、LR、PC、xPSR,16个周期内就能进ISR。我做过一个项目,要求中断延迟不超过1μs,M0+轻松搞定。
- 单周期I/O:GPIO翻转只要一个时钟周期。这对Bootloader里做LED指示、状态输出非常友好。
- 休眠模式:支持WFI/WFE,配合SCB的深度休眠控制,可以在升级间隙省电。
Bootloader视角的M0+优势:
- 向量表重定位——这是Bootloader的核心机制,M0+原生支持
- 非对齐访问——虽然M0+不支持,但CMS32的SRAM控制器做了兼容处理
- SysTick定时器——做超时控制、升级进度计时,不用额外硬件
2.2 存储器映射:一张图看懂地址空间
CMS32的存储器映射遵循ARM标准布局,但各家芯片厂会在特定区域挂自己的外设。我画了一张图,把Bootloader关心的区域标出来了。
这张图里,Bootloader最常打交道的就三个区域:Flash代码区、SRAM数据区、系统控制区。外设区虽然也重要,但那是应用代码的事,Bootloader只用UART/SPI/I²C做通信,其他外设基本不动。
2.3 Flash与SRAM分区:Bootloader的“地盘”划分
做Bootloader,第一件事就是分地盘。Flash和SRAM怎么切,直接决定了升级方案的灵活性。我见过不少新手直接把Bootloader和应用写在一起,结果一升级就把自己刷死了——嗯,这种事我也干过。
2.3.1 Flash分区方案
CMS32的Flash通常从0x0800_0000开始。我建议这样分:
| 区域 | 起始地址 | 大小 | 内容 |
|---|---|---|---|
| Bootloader区 | 0x0800_0000 | 16KB ~ 32KB | 向量表、启动代码、升级逻辑 |
| 参数区 | 0x0800_4000 | 4KB | 升级标志、版本号、校验值 |
| 应用区 | 0x0800_5000 | 剩余Flash | 用户应用程序 |
| 备份区(可选) | 0x0800_5000 + AppSize | 与App等大 | 升级失败时的回滚副本 |
我的经验:Bootloader区留16KB其实就够了,但如果你要用到复杂的加密校验或者双备份,建议留32KB。参数区4KB绰绰有余,别浪费Flash。
2.3.2 SRAM分区策略
SRAM从0x2000_0000开始,大小通常在8KB~64KB之间。Bootloader运行时,SRAM的分配要精打细算:
- 堆栈区:从SRAM末尾向下生长。Bootloader的栈不需要太大,512字节~1KB足够。
- 全局变量区:紧接在向量表之后。Bootloader的全局变量很少,几十个字节而已。
- 升级缓冲区:这是大头。如果你用XMODEM或YMODEM协议,一次接收128字节或1KB的数据包,缓冲区至少得2KB。我习惯开4KB,留点余量。
- 临时计算区:做CRC校验、SHA256哈希时,需要一些临时空间。几百字节就够了。
⚠️ 注意:Bootloader和应用共用SRAM时,一定要确保Bootloader退出前把全局变量清零。否则应用启动时读到脏数据,可能直接跑飞。我曾经因为这个bug调了整整两天。
2.4 系统控制块(SCB):Bootloader的“控制面板”
SCB是Cortex-M0+内核里最重要的控制模块之一。它的基址在0xE000_ED00,里面有一堆寄存器,但Bootloader真正关心的就几个。
2.4.1 向量表偏移寄存器(VTOR)——Bootloader的灵魂
VTOR位于SCB基址+0x08处,32位寄存器。它的作用只有一个:告诉CPU,向量表在哪儿。
默认情况下,VTOR = 0x0000_0000,CPU从Flash首地址找向量表。但Bootloader启动后,需要把控制权交给应用,这时候就得改VTOR。
// 设置向量表偏移到应用区
#define SCB_VTOR (*(volatile uint32_t *)0xE000ED08)
#define APP_VECTOR_ADDR 0x08005000 // 应用区起始地址
void jump_to_app(void)
{
uint32_t app_stack;
void (*app_entry)(void);
// 1. 关闭全局中断
__disable_irq();
// 2. 清理外设状态(UART、定时器等)
deinit_peripherals();
// 3. 设置新的向量表
SCB_VTOR = APP_VECTOR_ADDR & 0xFFFFFF80; // 128字节对齐
// 4. 读取应用栈指针
app_stack = *(uint32_t *)APP_VECTOR_ADDR;
// 5. 读取应用入口地址(Reset_Handler)
app_entry = (void (*)(void))(*(uint32_t *)(APP_VECTOR_ADDR + 4));
// 6. 设置主栈指针
__set_MSP(app_stack);
// 7. 跳转
app_entry();
// 永远不会回到这里
}
关键点:VTOR必须128字节对齐。因为向量表的前16个异常向量占64字节,加上设备中断向量,总大小是128的倍数。如果你把VTOR设成非对齐地址,CPU会硬fault——我踩过这个坑,一次就够了。
2.4.2 其他SCB寄存器
| 寄存器 | 偏移 | Bootloader用途 |
|---|---|---|
| CPUID | 0x00 | 读取内核版本,做兼容性检查 |
| ICSR | 0x04 | 软件触发中断、查看当前异常号 |
| VTOR | 0x08 | 向量表重定位(核心!) |
| AIRCR | 0x0C | 系统复位、中断优先级分组 |
| SCR | 0x10 | 休眠模式控制(升级间隙省电) |
| CCR | 0x14 | 栈对齐、非对齐访问控制 |
我个人最常用的是AIRCR。做Bootloader时,如果升级失败需要复位重试,直接写这个寄存器就行:
// 软件复位
#define SCB_AIRCR (*(volatile uint32_t *)0xE000ED0C)
#define AIRCR_VECTKEY 0x05FA0000
#define AIRCR_SYSRESETREQ 0x00000004
void system_reset(void)
{
SCB_AIRCR = AIRCR_VECTKEY | AIRCR_SYSRESETREQ;
while(1); // 等待复位
}
2.5 中断与异常:Bootloader的“交通管制”
Cortex-M0+支持最多32个外部中断(IRQ 0~31),加上16个系统异常,总共48个中断源。Bootloader里,中断管理要格外小心。
我的原则是:Bootloader尽量少开中断。能用轮询就别用中断,因为中断向量表一旦切换,中断处理函数的位置就变了。如果你在Bootloader里开了UART中断,跳转到应用前没关干净,应用启动时可能直接跑进Bootloader的中断处理函数——那画面太美我不敢看。
避坑指南:我曾经在Bootloader里用了定时器中断做超时控制,结果跳转前忘了关。应用启动后,定时器中断触发,PC指针跳到了Bootloader的向量表里,应用直接死机。从那以后,我写了个deinit_all_interrupts()函数,跳转前把所有外设中断都清掉。
2.6 本章小结
CMS32的硬件架构其实不复杂。你只要记住:
- Flash从0x0800_0000开始,Bootloader占前面,应用占后面
- SRAM从0x2000_0000开始,堆栈从顶往下,缓冲区从底往上
- SCB的VTOR是Bootloader的核心,改它就能切向量表
- 中断管理要干净,跳转前必须清理干净