深入解析STM32存储器架构与总线系统
1. 简介
本文章主要讲解 MCU 存储器相关内容。阅读本文章需要有 MCU(单片机) 的使用经验。本文基于 STM32 讲解(因为资料好找)。
2. 易失和非易失
从最基本的功能分类来看,所有存储器都可以归入易失性和非易失性这两大类。
而RAM和ROM是其中的典型代表,所有其他存储器名词,本质上都是RAM和ROM的具体实现或特例。
2.1 RAM
RAM 全称是 Random Access Memory,中文名:随机存取存储器。
RAM 是易失的,当电源关闭时,RAM 内的数据会立即丢失。通常用于存储 MCU 运行时的数据(堆、栈、全局变量),MCU 重新上电后无法恢复之前的运行状态,总是从头开始运行。
虽然数据会丢失,但是它还是有好处的,比如读写速度快。
(PS:RAM也有好多种,也有非易失的RAM存在,例如 MRAM 磁阻式随机存取存储器)
2.2 ROM
ROM 全称是 Read-Only Memory,中文名:只读存储器。
ROM 的非易失的,也就是电源关闭时,ROM 内的数据不会丢失。所以通常用于存放永久保存的数据,例如:程序代码、启动程序、常量数据、中断向量表等。
(PS:现代 MCU 中不再使用传统意义上的 "只读ROM" ,而是 Flash 存储器)
3. MCU 的存储器
上面我们讲了存储器分为 RAM 和 ROM,现在讲讲实际 MCU 内部的存储器。
以 32 位 MCU为例:32 位二进制所能表示的范围是 0x0000 0000 ~ 0xFFFF FFFF,所以其理论的最大寻址能力是 4GB。不过实际芯片内一般不会集成这么大的存储空间。
如下图,以 STM32F10x 系列 的 中容量产品为例,其中一款芯片具备 64K 闪存 和 20K 的 RAM。闪存(Flash Memory) 用于存放程序代码、常量数据、中断向量表等只读数据。STM32 内部使用的 RAM 是 SRAM(Static Random Access Memory,中文名:静态随机存取存储器)。
4. 存储器映射
32 位的寻址空间 0x0000 0000 ~ 0xFFFF FFFF,但是我们需要知道芯片内部的各个存储单元(Flash、SRAM、外设寄存器等)对应的地址值,CPU才能够通过地址真正去操作存储器或外设。
MCU 的存储单元在寻址空间中的位置(即地址)是由芯片厂商在设计时预先定义好的。如果我们想了解某 MCU 的内存布局,需要找到对应芯片数据手册中的"存储器映像图"。
如下图,这是 STM32F103x8B 系列的"存储器映像图",从图中可以看出 Flash 的起始地址是 0x0800 0000,SRAM 的起始地址是 0x2000 0000。此系列的 STM32 使用的是 ARM Cortex-M3 内核,Cortex-M3 内核是 ARM 公司设计的,ST 是在 内核 之外设计部件并生产整个芯片,这些部件一般被称为片上外设。
看左边这个分布图,这个 4GB 的地址空间被平均分成了 8 个块,每个块的大小是 512MB (每块大小为 0x2000 0000字节,0x2000 0000 = 536870912,536870912 ÷ 1024 ÷ 1024 = 512 MB)。每块的功能如下表:
| 序号 | 用途 | 地址范围 |
| Block 0 | 代码区(code) | 0x0000 0000 ~ 0x1FFF FFFF |
| Block 1 | SRAM | 0x2000 0000 ~ 0x3FFF FFFF |
| Block 2 | 片上外设 | 0x4000 0000 ~ 0x5FFF FFFF |
| Block 3 | 无效 | 0x6000 0000 ~ 0x7FFF FFFF |
| Block 4 | 无效 | 0x8000 0000 ~ 0x9FFF FFFF |
| Block 5 | 无效 | 0xA000 0000 ~ 0xBFFF FFFF |
| Block 6 | 无效 | 0xC000 0000 ~ 0xDFFF FFFF |
| Block 7 | Cortex - M3 内部外设:NVIC、SysTick... | 0xE000 0000 ~ 0xEFFF FFFF |
块 3~6 是预留给 FSMC 控制的外部存储器地址映射,STM32F103x8B 属于中等容量型号,通常不集成 FSMC 控制块,所以对应的内存区域不可使用,无效。
4.1 Block 0
通过"存储器映像图"可以看到,块0包含着 启动配置、Flash、系统存储器、选项字节。
| 名称 | 用途说明 | 地址范围 |
| 启动映射 | 为了让芯片能从不同介质启动而存在。硬件会根据 BOOT0 和 BOOT1 引脚选择将这部分地址重映射到 FLASH、系统存储器 或 SRAM。 0x0000 0000 并不是真正存在的物理存储器,而是一个“窗口”,读取它实际读取读取的是被映射的物理存储器。 | 0x0000 0000 ~ 0x0007 FFFF (图没表示出来) |
| Flash | 存放程序的地方。 | 0x0800 0000 ~ 0x0801 FFFF |
| 系统存储器 | 内置官方的 Bootloader(ISP程序),出厂固化,只读不可修改。当设置 BOOT 引脚从系统存储器启动时,CPU就会执行这一段代码,通过 USART1 接收串口数据并烧写到 Flash 中,实现了从串口下载代码。 如果设置 BOOT 引脚 不从系统存储器启动,就会跳转到 Flash 或 SRAM 执行用户程序。 | 0x1FFF F000 ~ 0x1FFF F7FF |
| 选项字节 | 用于存放关键配置的特色区域。读保护、用户配置(看门狗模式等)、用户数据(固件版本等)、写保护 | 0x1FFF F800 ~ 0x1FFF F80F |
4.2 Block 1
Block1 用于设计片内的 SRAM, 通过"存储器映像图"可以看到没有 SRAM 以外的东西。不同的芯片,内部的 SRAM 空间不一定相同,详情可以看看第三章,不同容量的 SRAM 不同。
4.3 Block 2
Block2 用于设计片内的外设,几乎所有的外设都在这里,例如 GPIO、串口、定时器、USB、IIC、RTC、看门狗等外设都在这里。
5. 系统架构
5.1 系统架构图
下图是 STM32非互联型产品的系统结构图。互联型产品会多出 以太网MAC控制器 和 USB OTG FS 接口,不讲也没影响。
5.2 总线矩阵
图中红色框出了总线矩阵。总线矩阵连接了芯片上的大部分总线,主要功能是总线的仲裁与路由系统。
它里面有一个仲裁器,进行内核系统总线和DMA主控总线之间的访问仲裁,当它两要访问同一个从设备时(比如都想写SRAM),它会通过预设规则仲裁决定先响应哪个请求,同时管理多条总线之间的数据传输。
你可以将它理解成一个"无红绿灯的立交桥",它能够让多个主设备(总线)同时访问不同的从设备,互不拥堵。不能多个主设备同时访问同一个从设备。
5.3 内核总线接口
芯片和外设之间通过各种总线连接。
Cortex-M3 内核一共有五种总线接口:ICode 总线、Dcode总线、系统总线、外部私有外设总线(PPB)、调试访问端口总线(DAP)。
但是 ST 在将内核集成到 STM32 时,并没有使用全部的内核接口,只使用了ICode总线、DCode总线 和 系统总线,可查看图中蓝色框部分。(私有外设可以通过系统总线或内部映射访问,所以无需独立总线; ST 自主实现了 串行调试接口 接入内核,也不再需要单独占用一条调试访问端口总线)
| ICode | 指令总线,I表示instruction。程序编译后就是一条条指令,存放在 Flash 中,内核使用 ICode总线读取这些指令,即取指。但是如果代码在 SRAM 执行,则取指是通过系统总线完成。 |
| DCode | 数据总线,D表示data。DCode总线 用于从 Flash 中读取数据,如 const 常量、初始化值。 SRAM 中的变量读写不是通过 DCode,而是通过系统总线完成。 |
| 系统总线 | 系统总线的职责范围很大。系统总线通过总线矩阵可访问 内部SRAM、FSMC外部存储区,以及所有外设的寄存器。我们常说的“读写寄存器”就是通过系统总线完成的。 但是系统总线也承担着搬运变量、堆栈操作等任务。 |
5.3 DMA 总线
STM32额外增加了 DMA控制器 及其专用的 DMA总线 ,图中紫色框部分。该系列 STM32 拥有 DMA1 和 DMA2 两个控制器。
DMA控制器的地位与Cortex-M3内核是平级的。DMA控制器 通过 DMA总线直接和内存或外设交换数据,不经过 CPU内核。
| 内核 | 通过 ICode,DCode,系统总线发起读写 |
| DMA 控制器 | 通过 DMA总线发起读写 |
DMA总线 通过总线矩阵直接连接到SRAM 和 外设AHB总线,所以在数据传输时才完全不需要占用内核的系统总线。需要注意:DMA 总线是不能访问 Flash 的,Flash 的读取只能使用内核的 ICode 和 DCode 总线进行。
一个容易混淆的点:图上那些从外设(ADC、USART等)指向DMA的箭头,不是“DMA总线”,而是DMA请求信号。意思是:外设说“我数据准备好了,DMA快来搬”,请求DMA开始干活。真正的数据传输通路,还是从DMA出发,经总线矩阵去读/写内存或外设。
5.4 AHB、APB总线
AHB 和 APB 是两种不同性能的总线,AHB(Advanced High-performance Bus) 是高速总线,APB(Advanced Peripheral Bus) 是低速外设支路。
如图中黄色框部分,APB1 和 APB2 是通过桥接器从 AHB总线分频,转换协议后延伸出来的两条独立 APB 总线(AHB总线的传输规则比较复杂,所以要翻译成 APB总线能够理解的规则)。
AHB总线自己也会连接部分外设,例如 SDIO、RCC和DMA。因为存在 GPIO、串口、I2C 这种慢速外设,如果直接挂在 AHB 上,可能会频繁阻塞高速传输;同时 AHB 的协议复杂很多,每个外设都设计成 AHB接口会增加成本。所以 STM32 就在 AHB 后面加了桥接器和 APB。
其余的外设基本都在 APB1 和 APB2 上。根据"STM32参考手册"上的描述,APB1 的速度是 36MHz,APB2 的速度是全速 72MHz(跟 AHB 速度一样)。分成两根 APB 线是为了将 高速外设 与 低速外设 物理隔离,APB1跑半速可以降低功耗,并且两条总线可以并行访问提升效率。
5.5 驱动单元和被动单元
了解清楚上面的部分内容后,我们可以将架构上的总线分为"驱动单元"和"被动单元"。
| 具体成员 | 描述 | |
| 驱动单元 | DCode总线、系统总线、DMA总线 | 能够"主动发起"总线传输的模块,它们可以向被动单元发出读写请求 |
| 被动单元 | 内部Flash、内部SRAM、FSMC、AHB到APB的桥 | 只能"被动响应"总线传输的模块,不能主动发起请求。 |
ICode总线即不是"驱动单元"也不是"被动单元",它很特殊。ICode总线是一条只读、只取指令的总线,只服务于内核取指。它一般直接连接到 Flash存储器接口,导致只能访问Flash同时又不经过总线矩阵仲裁。
6. Flash 特性
根据中容量产品的闪存模块的组织表,可以看出 Flash空间 是按页分的,每页 1K字节(大容量产品每页 2K字节;部分更高级的芯片大是用“扇区”作为最小擦除大小,扇区的大小不等,通常是 16KB / 64KB / 128KB)。
Flash 写操作不可逆,只能将某一位上的 1 变成 0,不能反向,所以 Flash 不能像 RAM 那样按字节随意改写,而是有特殊的操作要求:写入前必须先擦除。擦除以“页”或“扇区”为单位,擦除后对应“页”或“扇区”上的值就全部都是 0xFF。
想要读取 Flash 还是简单的,直接通过指令读取,走系统总线获取数据。但是 Flash 除了写入前必现擦除以外,还有写保护,在擦除和写入之前,要先解锁写保护,写入完成后重新锁定。具体的可以阅读对应的 “闪存编程参考手册”。
7. MCU 与 C/C++ 程序内存分区的关系
在 C/C++ 程序中,程序运行时占用的内存通常被划分为几个不同的区域(分区),每个区域负责存储特定类型的数据,并具有不同的生命周期和访问特性。典型的内存分区如下:
| 代码段(.text) | 存放代码。对应物理存储器是 Flash,这个讲过好多次了。 |
| 常量区(.rodata) | 字符串字面量、const 修饰的全局/静态变量、所有只读数据。对应物理存储器是 Flash。 |
| 数据段 | 初始化数据段(.data):已初始化的全局/静态变量(初值非零)。对应物理存储器是 Flash,但是运行时会拷贝到SRAM。 未初始化数据段(.bss):未初始化或初始化为 0 的全局/静态变量。对应物理存储器是 SRAM,启动时会由启动代码清0. |
| 堆(Heap) | 动态分配(malloc/new)的内存。对应物理存储器是 SRAM。 |
| 栈(Stack) | 非静态局部变量、函数参数、函数返回地址、临时变量。对应物理存储器是 SRAM。 |
上表中,堆和栈的大小由启动文件(.s)决定,可以手动更改 .s 文件以增大或减小堆和栈。
初始化数据段存放在 Flash,是因为 SRAM 掉电丢失,已初始化变量的初值必须存放在非易失的 Flash 中,上电后才能拷贝到 SRAM 里供读写。
