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关心的区域标出来了。

CMS32 存储器映射(Bootloader 视角) 地址范围:0x0000_0000 ~ 0xFFFF_FFFF(4GB 线性地址空间) 代码区(0x0000_0000 ~ 0x1FFF_FFFF) Flash 主存储区 + 系统存储器(Bootloader 驻留区) 片上 SRAM(0x2000_0000 ~ 0x3FFF_FFFF) 数据区 + 堆栈 + 升级缓冲区 外设区(0x4000_0000 ~ 0x5FFF_FFFF) GPIO、UART、SPI、I²C、定时器、DMA 等外设寄存器 系统控制区(0xE000_0000 ~ 0xE00F_FFFF) SCB、NVIC、SysTick、MPU(可选) 💡 Bootloader 重点关注区域: • Flash 首地址(0x0800_0000):向量表 + Bootloader 代码 • SRAM 起始地址(0x2000_0000):堆栈指针初始值、全局变量 • SCB 基址(0xE000_ED00):向量表偏移寄存器(VTOR)

这张图里,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的核心,改它就能切向量表
  • 中断管理要干净,跳转前必须清理干净