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、外设寄存器、系统控制块之间的层级关系。
2.7 避坑指南
最后,分享几个我在项目中踩过的坑:
- Flash 写入前没擦除:数据写不进去,还以为是芯片坏了。后来养成习惯,每次写入前先调擦除函数。
- 中断向量表没重映射:App 里开了中断,结果一触发就跑回 Bootloader 的向量表。排查了整整一天。
- SRAM 地址算错:把变量定义在保留 SRAM 区域,程序跑飞了。后来仔细看手册,才发现那 4 KB 是系统保留的。
我的习惯:每次拿到新芯片,第一件事就是画一张存储器映射图,贴在工位上。写代码时扫一眼,心里就有数了。
好了,CMS32 的存储器架构就讲到这里。记住 Flash 分区、SRAM 布局、外设基地址和向量表重映射这几个关键点,后面的 Bootloader 开发会顺畅很多。