存储器概述
存储器是许多存储单元的集合,存储器单元实际上是时序逻辑电路(锁存器)的一种,按单元号顺序排列。每个单元由若干二进制位构成,以表示存储单元中存放的数值,这种结构和数组的结构非常相似。按存储器的使用类型可分为只读存储器(ROM)和随机存取存储器(RAM)。
存储单位
位(bit):它是计算机中最小的数据单位。由于计算机采用二进制数,所以1位二进制数称作1bit,例如101011为6bit。
字节(Byte,单位简写为B):8位二进制数称为一个字节,1B=8bit。
字(Word):两个字节构成一个字,即2Byte=1Word。
存储器编址
如下图所示是一个容量为256字节的存储器,内部有256个存储单元,每个存储单元可以存放8位二进制数,为了存取数据方便,需要对每个存储单元进行编号,也即对存储单元编址,编址采用二进制数,对256个存储单元全部编址至少要用到8位二进制数,第1个存储单元编址为00000000,编写程序时为了方便,一般用十六进制数表示,二进制数00000000用十六进制表示就是00H,H表示十六制数,第二个存储单元编址为01H,第256个存储单元编址为FFH(也可以写成0FFH)。
存储器数据读写说明
要对256字节存储器的每个存储单元进行读写,需要8根地址线和8根数据线,先送8位地址选中某个存储单元,再根据读控制或写控制,将选中的存储单元的8位数据从8根数据线送出,或通过8根数据线将8位数据存入选中的存储单元中。以图1 存储器结构为例,当地址总线A7~A0将8位地址00011111(1FH)送入存储器时,会选中内部编址为1FH的存储单元,这时再从读控制线送入一个读控制信号,1FH 存储单元中的数据00010111从8根数据总线D7~D0送出。
51单片机存储结构
ROM(程序存储器)
ROM(程序存储器)存放程序、表格和始终要保留的常数,相当于计算机系统的硬盘;
数据存储器RAM
作用:存放程序运行结果
什么是存储器映射
存储器本身不具备地址,所以把芯片内核所预先设定好的地址分配给寄存器,就是存储器映射。因为stm32的地址线是32位,也就是2的32次方,正好是对应4G的虚拟存储空间。把内核厂商(也就是ARM公司)定义的这个虚拟空间与芯片厂商(这里是ST)芯片内部外设进行对应,也就是给存储器分配地址,即存储器映射。4个G的地址这么大,用不完没关系,可以保留。
stm32f4存储器映射分析
详细解释
Block 0代码区
Aliased to flash ,system memory or SRAM depending on the Boot pins
开始运行,BOOT1、BOOT0这两个引脚的电平值选择0X0000 0000–0X001F FFFF映射到不同的存储器上,通过BOOT引脚选择启动模式。篇幅所限,关于BOOT1、BOOT0的引脚设置选择不同的启动方式,这里就不详细展开了。
主Flash:用于保存数据的区域,每个芯片都有一个参数Flash空间大小,指的就是这部分。
CCM data RAM:Code区域是用I-Code和D-Code访问,作用是为了加快数据处理速度。
system memory :STM32在出厂时,已经固化了一段程序在System memory(medium-density devices的地址为:0x1FFF_F000,大小为2KB)存储器中。这段程序就是一个固定好的,并且没法修改的Boot Loader
Options Bytes :可以按照用户的需要进行配置(如配置看门狗为硬件实现还是软件实现);
PS:存储器的重映射
通常系统启动都是从0地址处开始,但是为了支持不同的存储介质,不同的存储介质被分配了一个非0地址区域。这就是为什么要进行重映射。
因此重映射主要发生在两种情况下,一是系统启动的过程中;二是如果中途遇到需要在不同的存储器之间进行切换的时候也需要进行重映射
我们一般的单片机自举(启动)单片机地址,都是从0开始运行的,STM32启动需要重映射地址,F4xx的0X0000 0000~0x001F FFFF地址映射了到什么存储器上,那么就从该存储器上读取指令。
Block 1
SRAM运行时临时存放代码的地方。不同类型的STM32单片机的SRAM大小是不一样的,但是他们的起始地址都是0x2000 0000,终止地址都是0x2000 0000+其固定的容量大小。SRAM的理解比较简单,其作用是用来存取各种动态的输入输出数据、中间计算结果以及与外部存储器交换的数据和暂存数据,用于程序运行的堆栈开销。设备断电后,SRAM中存储的数据就会丢失。
Block 2
Block2 用于设计片内外设,根据总线速度的不同,Block2被分为了APB和AHB。在上图所示的stm32f40x的映射框图中可以看到,APB分为APB1和APB2,AHB分为AHB1和AHB2,AHB3(不在Block2的映射范围)
ps:什么是寄存器映射
在存储器Block2这块区域,设计的是片上外设,它们以4个字节为一个单元,共32bit,那么每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过C语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这是我们可以根据每个单元的功能不同,以功能为名给这个存储单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
举例说明:
第一步:宏定义GPIO 口的基地址,AHB1PERIPH_BASE 依次累加 0x400的地址偏移量,就得到GPIOA~GPIOK的基地址。
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00)
#define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000)
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)
#define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800)
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)
#define GPIOI_BASE (AHB1PERIPH_BASE + 0x2000)
#define GPIOJ_BASE (AHB1PERIPH_BASE + 0x2400)
#define GPIOK_BASE (AHB1PERIPH_BASE + 0x2800)
第二步:把这个GPIO的基地址通过加上(GPIO_TypeDef *)这步骚操作,来把地址强转成具有GPIO_TypeDef 性质的指针变量,并且用#define进行宏定义,实现取了个别名的效果。这就是寄存器的映射。
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
#define GPIOI ((GPIO_TypeDef *) GPIOI_BASE)
#define GPIOJ ((GPIO_TypeDef *) GPIOJ_BASE)
#define GPIOK ((GPIO_TypeDef *) GPIOK_BASE)
ps:GPIO_TypeDef结构体的声明
typedef struct
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint16_t BSRRL; /*!< GPIO port bit set/reset low register, Address offset: 0x18 */
__IO uint16_t BSRRH; /*!< GPIO port bit set/reset high register, Address offset: 0x1A */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
那么如果我想往0x4002 0410这个地址写入数值0xFFFF FFFF,应该怎么操作呢?
*(unsigned int*)(0x4002 0410) = 0xFFFF;
在它的前面加上(unsigned int*)变成*(unsigned int*)(0x4002 0410)就把这个数变成一个地址了?但是我们操作的是这个地址里面的内容,是不是再在前面加上一个星号变成*(unsigned int*)(0x4002 0410)就可以了,然后就可以给它赋值了:*(unsigned int*)(0x4002 0410) = 0xFFFF;
补充
为什么单片机启动时,不需要用bootloader将代码从ROM搬移到RAM,而ARM则需要。这里我们可以先看看单片机程序执行的过程,单片机执行分三个步骤,取执行—分析指令----执行指令。取指令的任务是:根据PC的值从程序存储器读出指令,送到指令寄存器。然后分析执行执行。这样单片机就从内部程序存储器去代码指令,从RAM存取相关数据。要知道RAM取数的速度是远高于ROM的,但是单片机因为本身运行频率不高,所以从ROM取指令慢并不影响。而ARM不同,cpu运行的频率高,远大于从ROM读写的速度,所以一般有操作系统,都需要将代码部分拷贝到RAM中再执行。
STM32存储空间
芯片能访问的存储空间有多大,是由谁定的?这个是由芯片内 CPU 的地址总线的数量决来定的,STM32 芯片内部的地址总线为32 根,
1、1 根地址线:可以传输的地址为 0 和 1 的,那么理论上就可以访问 2 个字节
2、2 跟地址线:可以传输地址为 00、01、10、11,理论上可以访问 4 个字节
3、3 个地址线:可以传输的地址为 000、001、010、011、100、101、110、111,理论上可以访问 8 个字节。
4、32 根地址线:可以产生 00000000 00000000 00000000 00000000、…、11111111 11111111 11111111 11111111的 2^32个地址,范围刚好为 4G,所以我们就说STM32的32 根地址线,理论上可以访问4G字节的存储器空间。
STM32地址是从0x0000 0000到0xFFFF FFFF,这就是4GB的存储空间。但是STM32真的有4GB的存储空间吗?
**你在想啥呢?**答案当然不是,我们的PC电脑也才4GB的内存。一个小小的单片机怎么可能有4GB的存储空间!这个4GB的是STM32理论分配的地址空间。也就是说实际上并不是有折磨大的存储单元。上图中第二排可以看到有很多预留的地址,这些地址并没有给他分配存储单元。
所有的存储器都是与地址线连着的,但是实际上如果你只接了一个 1M 的存储器,而且是从0地址开始映射的,那么32 根地址线所产生的0~1M 的地址信号其实才是有意义的,因为这些地址信号才有对应真实的存储器,而所产生的1M 以上地址信号其实并无意义,因为并不对应真实的存储器。
举个例子,政府给你化了10栋楼房的面积用来盖房子,但是实际上你没有那么多钱,只盖了3栋楼,其他的7栋房子预留的面积只能放在那里,凭什么不允许呢?这样说你应该明白了吧。