2. CMS32 存储器架构:Flash、RAM、系统寄存器映射、启动向量表详解

做嵌入式开发,说白了就是在跟存储器打交道。你写的代码放哪?变量存哪?外设怎么控制?这些都绕不开存储器架构。CMS32 系列虽然是 Cortex-M0+ 内核,但各家厂商的存储器布局差异还挺大。我刚开始用 CMS32 时,就因为在 Flash 分区上想当然,吃过一次亏——后面会细说。

2.1 整体存储器映射

CMS32 采用统一的 4GB 地址空间,这是 ARM 架构的老传统了。但具体怎么分配,每个厂家都有自己的玩法。CMS32 的布局大致如下:

地址范围 用途 大小
0x0000_0000 – 0x0000_FFFF Code 区(Flash / Boot ROM) 64 KB
0x1FFF_0000 – 0x1FFF_FFFF SRAM 64 KB
0x4000_0000 – 0x400F_FFFF 外设寄存器(APB 总线) 1 MB
0xE000_0000 – 0xE00F_FFFF 系统控制块(SCB、NVIC 等) 1 MB

注意看,Flash 和 RAM 并不连续。这一点跟某些 Cortex-M 系列不同。你想想看,如果 Bootloader 要搬运代码,就得明确知道这两块区域的起始地址。

关键点:CMS32 的 Flash 起始地址是 0x0000_0000,SRAM 起始地址是 0x1FFF_0000。这两个地址在 Bootloader 里会反复用到。

2.2 Flash 存储器详解

CMS32 的 Flash 分为主存储区和信息存储区。主存储区放用户代码,信息存储区放系统配置和用户数据。

2.2.1 主存储区结构

主存储区通常为 64 KB,按页(Page)管理。每页 512 字节。擦除操作以页为单位,写入可以按字(32 位)或半页(256 字节)进行。

属性 值
总容量 64 KB
页大小 512 字节
页数量 128 页
最小擦除单位 1 页
最小写入单位 32 位(字)
擦除次数 ≥ 10,000 次

我个人习惯把 Bootloader 放在前 8 KB(前 16 页),应用程序从 0x0000_2000 开始。这样分区清晰,也方便后期升级。

注意:Flash 写入前必须先擦除。我曾经在项目中直接调用写入函数,结果数据没写进去,排查了半天才发现是忘了擦除。嗯,这个坑你们别踩。

2.2.2 信息存储区

信息存储区通常有 1–2 KB,用于存放系统校准参数、用户配置字等。这部分区域在芯片出厂时可能已经写入了数据,操作时要格外小心。

  • 系统配置字:包括时钟校准值、LVD 电压阈值等。
  • 用户数据区:可以存 Bootloader 标志、固件版本号等。
  • 保护位:用于设置 Flash 读/写保护。

建议:信息存储区的擦写次数有限,别频繁操作。我一般只在固件升级成功后才更新一次版本号。

2.3 SRAM 存储器详解

CMS32 的 SRAM 共 64 KB,地址范围 0x1FFF_0000 – 0x1FFF_FFFF。SRAM 分为两个部分:

  • 系统 SRAM:前 60 KB,供用户程序使用。
  • 保留 SRAM:最后 4 KB,用于系统堆栈和中断向量表重映射。

为什么要保留 4 KB?因为 Cortex-M0+ 的中断向量表可以重定位到 SRAM。这在 Bootloader 里很常见——先把中断向量表搬到 SRAM,再跳转到应用程序。

实践要点:Bootloader 运行时,中断向量表在 Flash 里。跳转到 App 前,需要把 App 的中断向量表地址写入 SCB->VTOR 寄存器。如果 App 也用了中断,这一步不能省。

2.4 系统寄存器映射

系统寄存器映射,说白了就是外设的“门牌号”。CMS32 的外设寄存器都挂在 APB 总线上,地址范围 0x4000_0000 – 0x400F_FFFF。

外设 基地址 说明
GPIO 0x4000_0000 通用输入输出
UART0 0x4000_4000 串口 0
UART1 0x4000_5000 串口 1
SPI0 0x4000_6000 SPI 接口 0
I2C0 0x4000_7000 I2C 接口 0
Timer0 0x4000_8000 定时器 0
ADC 0x4000_9000 模数转换器
WDT 0x4000_A000 看门狗定时器

每个外设的寄存器偏移量在数据手册里都有。我建议你把常用的外设基地址定义成宏,写代码时直接调用,既清晰又不容易出错。

// 示例:UART0 基地址定义
#define UART0_BASE      (0x40004000UL)
#define UART0_SR        (*(volatile uint32_t *)(UART0_BASE + 0x00))
#define UART0_DR        (*(volatile uint32_t *)(UART0_BASE + 0x04))
#define UART0_BRR       (*(volatile uint32_t *)(UART0_BASE + 0x08))
#define UART0_CR        (*(volatile uint32_t *)(UART0_BASE + 0x0C))

2.5 启动向量表详解

启动向量表是芯片上电后第一个执行的东西。它放在 Flash 起始地址 0x0000_0000。向量表的前两个条目特别重要:

  • 0x0000_0000:栈顶指针(MSP 初始值)
  • 0x0000_0004:复位向量(Reset_Handler 地址)

剩下的条目是各种中断服务函数的入口地址。CMS32 的向量表共 32 个条目(0–31),其中 0–15 是系统异常,16–31 是外部中断。

向量号 偏移地址 异常/中断
0 0x0000_0000 栈顶指针
1 0x0000_0004 复位
2 0x0000_0008 NMI
3 0x0000_000C 硬错误
4–10 0x0000_0010 – 0x0000_002C 系统异常(SVCall、PendSV、SysTick 等)
16–31 0x0000_0040 – 0x0000_007C 外部中断(GPIO、UART、Timer 等)

重要:Bootloader 和 App 各自有独立的向量表。Bootloader 的向量表在 0x0000_0000,App 的向量表在 0x0000_2000(假设偏移 8 KB)。跳转到 App 前,必须把 SCB->VTOR 设置为 App 的向量表基地址。否则,中断来了会跑错地方。

2.6 存储器架构总览图

下面这张图把 CMS32 的存储器架构串起来了。你可以看到 Flash、RAM、外设寄存器、系统控制块之间的层级关系。

CMS32 存储器架构总览 Flash (64 KB) 0x0000_0000 Bootloader: 0x0000_0000 App: 0x0000_2000 SRAM (64 KB) 0x1FFF_0000 系统 SRAM: 60 KB 保留 SRAM: 4 KB 外设寄存器 0x4000_0000 GPIO、UART、SPI Timer、ADC、WDT 系统控制块 0xE000_0000 SCB、NVIC、SysTick 关键说明 • Flash 和 SRAM 地址不连续,Bootloader 搬运代码时需明确源地址和目标地址。 • 外设寄存器通过 APB 总线访问,基地址在 0x4000_0000 之后。 • 系统控制块位于 0xE000_0000,用于配置中断、时钟和系统异常。

2.7 避坑指南

最后,分享几个我在项目中踩过的坑:

  • Flash 写入前没擦除:数据写不进去,还以为是芯片坏了。后来养成习惯,每次写入前先调擦除函数。
  • 中断向量表没重映射:App 里开了中断,结果一触发就跑回 Bootloader 的向量表。排查了整整一天。
  • SRAM 地址算错:把变量定义在保留 SRAM 区域,程序跑飞了。后来仔细看手册,才发现那 4 KB 是系统保留的。

我的习惯:每次拿到新芯片,第一件事就是画一张存储器映射图,贴在工位上。写代码时扫一眼,心里就有数了。

好了,CMS32 的存储器架构就讲到这里。记住 Flash 分区、SRAM 布局、外设基地址和向量表重映射这几个关键点,后面的 Bootloader 开发会顺畅很多。