引言
从早期的微控制器如STM32与外部I²C EEPROM(如24C02)和SPI Flash(如NOR Flash)的通信,到DMA通道中ROM到RAM的数据传输,无不体现出存储器在计算机体系结构中的核心地位。STM32微控制器内部集成的RAM和Flash(作为一种ROM)也进一步印证了存储器无处不在的重要性。此外,每种外设模块中都包含的寄存器,虽然也具备存储功能,但其具体作用和与存储器的区别常使学习者感到困惑。本章将对这些概念进行系统梳理,全面解析存储器和寄存器的本质、分类及其在现代计算机系统中的应用。
一、存储器介绍
1.1 存储器概述
存储器是计算机中用于存储信息(数据和程序指令)的关键部件,是冯·诺依曼体系结构计算机存储程序和数据、执行控制功能的基础。它存储的信息本质上是二进制的0和1,这些二进制代码可以是待执行的指令,也可以是程序运行时所需的数据。存储器的存在使得计算机能够"记住"数据和执行流程,从而实现复杂任务。
1.2 存储器划分
1.2.1 原理上划分
存储器可大致分为随机存取存储器(RAM) 和只读存储器(ROM) 。从名字上来看,其初期是按照只读和可读可写进行分类的,但随着技术发展,现代ROM也普遍具备可读写能力,因此下电易失性成为区分两者的主要标准。所以它们最主要的区别在于数据的易失性:RAM是易失性存储器,断电后数据会丢失;而ROM是非易失性存储器,断电后数据依然保留。
RAM根据其工作原理又可细分为静态RAM(SRAM) 和动态RAM(DRAM)。SRAM通常速度更快,但成本较高,常用于CPU缓存;DRAM则成本较低,密度更高,广泛应用于主内存。
ROM的发展则经历了多个阶段:从早期的掩模ROM(Mask ROM) ,到可编程ROM(PROM) 、可擦除可编程ROM(EPROM) 、电可擦除可编程ROM(EEPROM) ,直至当前广泛使用的闪存(Flash Memory)。

闪存是EEPROM技术的发展产物,具备非易失性、高速读写和大容量等特点,如今已成为电脑、手机等设备中主要的非易失性存储介质。
1.2.2 介质上划分
如前面图所示,ROM中还包括了硬盘这一种存储器,本身其不应该放在这部分,因为硬盘这种存储器与其他存储器还是存在一定差异,就其介质上就与其他存储器存在本质不同:硬盘属于磁介质,而该分类中其他存储器属于半导体介质。因此,实际上除了基于易失性的分类,存储器还可以根据其存储介质进行划分。

半导体存储器(如RAM和ROM)主要通过电子方式存储信息。
而磁介质存储器,如硬盘(HDD)、软盘和磁带,则利用磁性材料存储数据。传统硬盘作为机械设备,其读写速度相对较慢,且易受物理冲击影响,但其单位存储容量成本较低。
软盘和磁带作为更早期的磁性存储介质,因容量小、速度慢等原因已基本被淘汰,但在计算机发展史上留下了印记(例如Word文档的保存图标仍是软盘形状)。
光盘(如CD、DVD、蓝光盘)则属于光磁性存储介质,具备容量大、稳定性高等特点,在某些场景下仍有应用。
然而,随着闪存技术(固态硬盘SSD)的快速发展,其在速度、抗震性和体积等方面的优势日益突出,传统机械硬盘的市场份额正在被逐步取代。未来,机械硬盘可能会逐渐退出历史舞台,只在特定的大容量、低成本存储需求场景中存在。
1.3 RAM介绍
RAM,即Random-Access Memory随机存取存储器,以其"随机访问"的特性而闻名,意味着可以根据地址直接读写任何存储单元,而非顺序访问。根据前面介绍,RAM主要分为静态随机存取存储器(SRAM)和动态随机存取存储器(DRAM)两大类。
1.3.1 SRAM
Static Random-Access Memory,静态随机存取存储器 。是RAM的一种,所谓的"静态",是指这种存储器只要保持通电,里面储存的数据就可以恒常保持。
SRAM是用电路存储数据,基本结构就是数电学习过的那种触发器结构(比如D触发器)。它通过一个由六个MOS晶体管构成的双稳态触发器电路来存储数据。这种结构保证了只要供电,数据就能稳定保持,无需像DRAM那样进行周期性刷新。

然而,很显然SRAM的电路比较复杂,这就导致其不方便集成、且成本高,因此容量也相对较小。在实际应用中,SRAM常被用作CPU内部的高速缓存(Cache),如一级、二级甚至三级缓存,以弥补CPU与主内存之间的速度差异。当CPU需要数据时,首先从高速缓存中查找,若命中则能极大地提升效率,从而加速整体系统性能。
1.3.2 DRAM
动态随机存储器 DRAM则采用更简单的结构,仅由一个MOS晶体管和一个电容组成 。DRAM的存储单元以电容的电荷来表示数据,有一定电荷代表逻辑1,无电荷代表0。

这种设计使得DRAM具有更高的集成度和更低的成本,因此成为个人电脑主内存(RAM)的首选 。然而,电容的电荷会随时间泄漏,导致数据丢失,所以DRAM必须进行周期性刷新操作以维持数据完整性 ,这也是其"动态"名称的由来。其中的刷新操作会对电容进行检查,若电量大于满电量的1 / 2,则认为其代表1,并把电容充满电;若电量小于1 / 2,则认为其代表 0,并把电容放电,以此来保证数据的正确性。
故而,虽然DRAM速度相对较慢,但其巨大的容量优势使其成为存储大量运行中程序和数据的主要存储器。例如,现代PC则普遍采用DRAM作为主内存,并辅以SRAM构成的高速缓存,而早期的单片机因容量需求小(如64KB),可能会直接使用SRAM作为内存。
1.4 ROM介绍
ROM,即Read-Only Memory只读存储器,最初顾名思义是只能读取而不能写入的存储器。然而,随着技术的发展,ROM家族经历了显著的演进,从最初的永久固化数据,已经发展到如今具备可擦写能力的灵活存储介质。
1.4.1 Mask ROM
最原始的ROM是Mask ROM(掩膜ROM),其数据在制造时通过特殊的掩膜工艺固化,出厂后无法更改。这种ROM适用于大批量生产且数据永不改变的应用,如早期计算机的BIOS或嵌入式设备的固件,因其成本低廉。
随着用户对灵活性需求的增加,PROM应运而生。
1.4.2 PROM
PROM,即Programmable ROM(PROM,可编程ROM)。PROM只允许用户一次性写入数据,通常通过烧断内部熔丝的方式来实现"0"或"1"的编程。一旦写入,数据便不可更改,因此被称为"一次性编程ROM"。
1.4.3 EPROM
为了进一步提升灵活性,**Erasable Programmable ROM(EPROM,可擦除可编程ROM)**被开发出来。EPROM允许数据被擦除并重新编程。早期的EPROM需要通过紫外线照射芯片窗口约30分钟来擦除所有数据,然后才能重新写入。这种擦除方式虽然实现了可重编程,但操作繁琐,需要专门的设备才行,且只能整片擦除。

现在这种存储器基本淘汰,被EEPROM取代。
1.4.4 EEPROM
电可擦除可编程只读存储器(Electrically Erasable Programmable ROM,EEPROM)的出现标志着ROM技术的一大飞跃。EEPROM实现了通过电信号进行擦除和编程,无需紫外线照射,极大地提高了操作便利性。
EEPROM可以在电路板上直接进行擦写,并且能够以字节为单位进行擦写,而非整片擦除,这使其在需要频繁更新少量数据的应用中变得非常实用。现在主要使用的ROM芯片都是EEPROM。在EEPROM的基础上,Flash(闪存)技术进一步发展。
1.4.5 Flash
Flash同样是电可擦除的存储器,但其容量更大、读写速度更快。Flash的底层原理与EEPROM相似,但其擦写操作通常以块为单位,而非字节,这使得其擦写速度介于EPROM和EEPROM之间,但容量和成本效益更优。
Flash已成为现代电子设备中应用最广泛的ROM类型,例如固态硬盘(SSD)、U盘、手机存储以及各种嵌入式系统的代码存储。尽管Flash具备了可重复擦写能力,但由于历史原因和与RAM存储原理的本质区别,它仍被归类为ROM家族的一部分,有时仍被称为"Flash ROM"。
需要注意的是,Flash的擦写次数是有限制的,通常可达数万到数十万次,而RAM理论上可无限次读写。同时,Flash具备非易失性,即断电后数据仍能保存,而RAM则需要持续供电才能维持数据。
1.5 存储器小结
总而言之,RAM以其高速、易失性成为临时数据处理的核心,SRAM作为高速缓存,DRAM作为主内存,共同加速了CPU的运行。而ROM则从最初的"只读"特性,逐步发展为具备电可擦写能力的Flash存储,成为永久存储程序和海量数据的理想选择,广泛应用于固态硬盘、U盘、嵌入式系统等领域,展现出超强的灵活性和持久性。尽管一些存储器名称或者日常生活很多东西名字仍沿袭了历史,但其功能和技术内涵已今非昔比,展现了技术发展中"旧瓶装新酒"的独特魅力。
二、STM32的存储器介绍
在嵌入式系统设计中,存储器是核心组成部分,其性能和组织方式直接影响到整个系统的效率。STM32微控制器作为广泛应用的32位单片机,其内部集成了多种类型的存储器,包括SRAM(静态随机存取存储器)和Flash(闪存)。SRAM以其高速读写特性常被用作MCU的运行内存,而Flash则用于存储程序代码和固定数据。
除了片内存储器,STM32还支持通过外部接口扩展多种存储芯片,例如外接SRAM、NOR Flash、NAND Flash以及EEPROM等,以满足不同应用场景对存储容量和速度的需求。例如,通过I²C或SPI通信协议,可以与外部EEPROM或NOR Flash进行数据交互。然而,所有这些存储资源,无论是片内的还是片外的,以及各种外设的寄存器,都需要一个统一的寻址机制才能被CPU有效访问。
这就引出了"存储器映射"这一概念,它定义了每个存储单元和外设寄存器在整个地址空间中的唯一位置。理解STM32的存储器映射,对于开发者而言,意味着能够更准确地定位和操作硬件资源,从而编写出更高效、更稳定的嵌入式应用程序。
2.1 STM32的存储器
STM32F103系列芯片内部集成了SRAM和Flash 两种主要存储器。其中,根据手册描述:SRAM的容量为64KB ,而Flash的容量为512KB。

SRAM以其快速访问特性,常被用作CPU的工作内存,支持字节(8位)、半字(16位)或全字(32位)的灵活访问 。SRAM的起始地址是0x20000000 。相比之下,Flash的容量更大,主要用于存储程序代码和常数数据,Flash起始地址在0x08000000。可利用Keil随便打开一个stm32工程即可看见:

这种内存分配与PC端的内存管理有所不同,STM32单片机因其本身存储较小,故直接使用的是类似于PC高速缓存的底层存储器SRAM,而非大规模的DRAM。
2.2 存储器映射
为了让CPU能够准确找到并访问各种存储单元(如SRAM、Flash)和外设寄存器,STM32引入了"存储器映射"的概念。
由于存储器本身并不具备地址信息,那么CPU要准确找到存储某个信息的存储单元,就必须为这些单元分配一个相互可区分的标识,这个标识就是常说的地址编码 。这就像为一排房子分配门牌号,每个存储单元都被赋予一个唯一的32位地址码,作为其固定的标识。这些地址码是无符号整数,通常以16进制表示。
而STM32中集成多种存储器(各种外设也需要分配地址),特定类型的存储器当作一组block,为每一个block分配一个数值连续、存储单元数相等、以16进制表示的自然数集合作为存储器Block的地址编码。这种自然数集合与存储器Block的对应关系 就是存储器映射 。总结一句话来说:存储器映射其实就是将芯片理论上的地址分配给各个存储器,实现存储器与理论地址的一一对应。
在STM32中,不同类型的存储器或外设被分配到连续的地址空间内,形成逻辑上的"街区",便于CPU查找和管理。例如,所有的SRAM存储单元会占据一段连续的地址范围,而某个特定外设(如SPI)的所有寄存器也会被分配到另一段连续的地址范围。
2.3 STM32的存储器映像
那么,STM32中的存储器映像图是什么样呢?这其实还与STM32芯片本身的地址总线数量有关。STM32采用32位地址总线 ,理论上可以寻址2^32个字节 ,即4GB 的线性地址空间。这意味着从0x00000000到0xFFFFFFFF的地址范围,所有程序存储器、数据存储器、寄存器以及输入输出端口都被组织在这一连续的地址空间内。值得注意的是,STM32采用小端存储格式。
小端存储 意味着在多字节数据中,最低有效字节存储在最低地址处,而最高有效字节存储在最高地址处。例如,一个32位整数0x12345678,在小端模式下,字节0x78会存储在起始地址,0x56在起始地址+1,以此类推。这种存储方式的优势在于进行数据类型转换时,如将32位数据转换为8位或16位,可以直接截取低地址处的字节,简化了操作。
反之还有一种存储方式称为大端存储 格式,即多字节数据中,最低有效字节存储在最高地址、最高有效字节存储在最低地址处。这种方式虽然给人感觉好理解一点,但是实际处理数据转换却会麻烦一些。
ARM把可访问的存储器空间分成8个主要块 ,由于一共4G,故每个块为4G/8=512MB 。这个容量是非常大的,因此芯片厂商就在每块容量范围内设计各自特色的外设 。但是每块区域容量占用越大,芯片成本就越高,所以说我们使用的 STM32 芯片都是只用了其中一部分。ARM 在对这 4GB 容量分块的时候是按照其功能划分,每块都有它特殊的用途。STM32数据手册中可找到其存储器映像图如下:

1、Block0 (0x00000000 - 0x1FFFFFFF) :主要用于代码执行。这部分地址空间主要是一个别名区域 (Alias),其内容取决于启动引脚(Boot Pins)的配置。它可以映射到片内Flash(通常用于正常启动时执行用户程序)或者系统存储器(System Memory,用于自举加载或烧写Flash)。

**0x0000 0000-0x0007 FFFF:**其中描述是Flash或System memory的别名区域。取决于BOOT引脚,即可以是 Flash 的别名,也可以是系统存储器的别名。然后Flash和系统存储器也是放在Block0中,由图可看到位于0x08000000-0x07FFFFFF和0x1FFFF000-0x1FFFF7FF中。
0x0800 0000-0x0807 FFFF:即片内 FLASH,我们编写的程序就放在这一区域,根据地址范围可计算的Flash包含的数据一共有0x0007FFFF+1也就是8*2^16=2^9KB即512KB的数据,与手册描述的Flash存储量一样。
0x1FFF F000-0x1FFF F7FF:即所说的系统存储器,里面存放的是 ST 出厂时烧写好的ISP自举程序,用户无法改动。使用串口下载的时候需要用到这部分程序。通过计算可知该部分占了2KB大小。
0x1FFF F800-0x1FFF F80F:Option Bytes即可操作字节,用于配置读写保护、BOR级别、软件/硬件看门狗以及器件处于待机或停止模式下的复位。当芯片不小心被锁住之后,我们可以从RAM里面启动来修改这部分相应的寄存器位。
然后Block0剩余部分均为保留地址,没有使用。
2、Block1 (0x20000000 - 0x3FFFFFFF) :主要分配给片内SRAM。SRAM的地址范围是从0x20000000到0x2000FFFF,即总容量为2^16B=2^6KB=64KB,也与手册描述相同。虽然整个Block1有512MB,但SRAM只占据了其中很小一部分,大部分地址空间被保留。

3、Block2 (0x40000000 - 0x5FFFFFFF):分配给所有片内外设的寄存器。例如,GPIO A端口的寄存器地址范围是0x40010800到0x40010BFF,DMA1的寄存器地址范围是0x40020000到0x400203FF。所有外设的基地址都以0x40000000开始,然后根据不同的模块进行细分。与SRAM类似,由于外设寄存器数量有限,Block2的大部分地址空间也是保留的。

根据外设总线速度的不同,Block2被划分为AHB和APB 两部分,APB又被分成APB1和APB2。这些都可以在手册中的存储器映射图中可看到,由于图较长,这里不展示了就。
0x4000 0000-0x4000 77FF:APB1总线外设。
0x4001 0000-0x4001 57FF:APB2总线外设。
0x4001 8000-0x4002 33FF:AHB总线外设。
4、Block3 和 Block4 (0x60000000 - 0x9FFFFFFF):这两个块专门用于FSMC(灵活静态存储控制器)模块,主要支持外部存储器扩展。FSMC将这两个1GB的地址空间进一步划分为四个Bank,每个Bank为256MB。Bank1通常用于扩展NOR Flash或PSRAM,Bank2和Bank3用于NAND Flash,而Bank4则用于PC Card等存储卡。

这部分因为是用于扩展存储器的,所以专门区别于其他常见的外设存在了其他块上,可见Block3和4全部用于进行存储器扩展,可扩展多种不同类型的存储器。
5、Block5 (0xA0000000 - 0xBFFFFFFF):保留用于FSMC模块自身的寄存器。

6、Block6 (0xC0000000 - 0xDFFFFFFF):完全保留,未被使用。
7、Block7 (0xE0000000 - 0xFFFFFFFF):分配给CM3内核内部外设的地址空间。

总的来说,在这8个Block里面,主要特别注意Block0、Block1和Block2这3个块。因为其中包含了STM32芯片的内部 Flash、RAM和片上外设。
2.4 小结
STM32通过将4GB的线性地址空间划分为八个逻辑块 ,并为片内SRAM、Flash、外设寄存器以及外部存储器扩展分配特定的地址范围。理解STM32的存储器映射图,特别是Block0、Block1和Block2的详细划分,是进行STM32开发的基础。小端存储格式的应用则进一步优化了数据类型转换的效率。尽管FSMC模块和其对应的Block3、Block4、Block5在初级开发中可能不常用,但对于需要扩展外部大容量存储的应用而言,其重要性不言而喻。
三、寄存器介绍
在STM32的存储器映射中,SRAM(静态随机存取存储器)和Flash(闪存)共同构成了内部存储器。SRAM类似于个人电脑PC中的内存,用于存放程序运行时的数据,而Flash则承担了存储全局常量和程序代码的任务,其作用更像是内存的一部分而非传统意义上的硬盘。除了这两种核心存储器,STM32的地址空间中还有专门用于外设的部分,即Block 2的256MB地址空间。这些外设本身并不直接存储数据,而是通过一系列特殊的存储单元------寄存器来与CPU进行交互,实现对外设的配置、状态读取和数据传输。
3.1 寄存器概述
通过前面存储器映射的介绍,我们知道在存储器 Block2 这块区域,设计的是片上外设 ,它们以4个字节为1个单元,共 32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名。这个别名就是我们经常说的寄存器****。
寄存器在STM32中扮演着至关重要的角色,它是CPU与外设之间沟通的桥梁。
从软件层面看,C语言代码中的寄存器名称(如DR、CR、SR)本质上是通过宏定义指向特定内存地址的指针别名。这些地址对应着存储器映射图中的某块地址空间,专用于某个外设。
从硬件层面 看,寄存器是由锁存器或触发器等电子器件构成,每个位(bit)可以存储一个0或1。一个32位寄存器即由32个这样的锁存器或触发器组成。这些位的输出可以连接到其他电路,从而控制外设 的行为(控制寄存器,如CR),或者接收来自外部电路的信号,反映外设的状态 (状态寄存器,如SR),亦或是单纯地存储数据(数据寄存器,如DR)。
寄存器可以被视为一种特殊的存储器,可以实现对单片机各个功能的控制其特点是访问速度极快,但数量相对较少。在ARM架构(精简指令集RISC)的处理器中,通常可以配置更多的寄存器,以提高执行效率。
3.2 寄存器映射
与前面存储器映射同理,大量寄存器的地址与功能单元一一对应也构成了寄存器的地址空间即寄存器映射。
寄存器映射是指为每个片上外设的每个功能单元(通常以32位,即四个字节为一个单元)分配一个唯一的地址。这些地址组成了外设的地址空间。在ST提供的头文件stm32f10x.h中,寄存器映射已经通过预编译的形式完全映射好了,以后如果再操作某个特定外设的时候,就不用直接操作地址,直接操作对应的寄存器名就可以了。
也就是说,在C语言编程中,为了方便操作,我们通常会定义一个结构体类型 ,将一个外设模块的所有寄存器都作为结构体的成员 。接着,定义一个指向该结构体类型的指针 ,并将其指向外设模块的基地址。

如上动画所示,大家可自行去查看源代码,就会发现C语言定义的与手册中的地址映射是对应上的,只不过定义了很多层。


从代码和手册所示的地址进行对照来看,确确实实是对应上的。
可以发现,也确实有GPIO_TypeDef结构体定义了GPIO端口的所有寄存器,而GPIOA宏则是一个指向GPIO_TypeDef类型的指针,其值被设定为GPIOA模块的起始地址(0x40010800)。


通过这种方式,我们可以使用GPIOA->CRL这样的语法来访问GPIOA控制寄存器,而无需直接操作原始的内存地址,大大简化了编程复杂度。这种映射关系保证了对特定寄存器的访问能够准确无误地定位到硬件层面的相应功能单元。
3.3 C语言操作寄存器底层逻辑
当然了,大家可能注意到,宏定义那些地址时,采用了强制类型转换为结构体指针类型的方法完成地址与寄存器成员的映射的,这看着与我们平时定义地址赋值不太像,可能还有些奇怪,原因是什么呢?

实际上是这样:在C语言中,直接定义结构体变量 会导致编译器在SRAM中自动分配内存,这与外设寄存器固定在特定地址空间的设计相悖。 因此,STM32的寄存器操作通常采用一种**"跳过变量定义"** 的方式。即直接定义一个结构体类型的指针,并将其强制类型转换为一个固定的内存地址(如0x40010800) 。这个固定地址就是外设模块的基地址。这样,当通过指针访问结构体成员时(例如GPIOA->CRL),编译器会根据结构体成员在结构体中的偏移量,自动计算出该寄存器在内存中的实际地址。

例如,如果CRL是结构体的第一个成员,其偏移量为0;如果CRH紧随其后,且每个寄存器占4个字节(即成员是32位),则CRH的偏移量为4。当然,查看源代码确实寄存器成员定义的都是占4个字节。

这种机制确保了C语言代码能够精确地访问到硬件层面上预设的寄存器地址,实现对外设的直接控制。
3.4 寄存器小结
寄存器作为连接软件与硬件的桥梁,其软硬件双重含义的理解对于嵌入式开发至关重要。硬件层面上,寄存器是锁存器和触发器的集合,用于存储和控制二进制数据;软件层面上,它们是C语言中指向特定内存地址的指针别名,通过结构体和宏定义实现抽象化操作。
以上便是本次文章的所有内容,欢迎各位朋友在评论区讨论,本人也是一名初学小白,愿大家共同努力,一起进步吧!
鉴于笔者能力有限,难免出现一些纰漏和不足,望大家在评论区批评指正,谢谢!
