存储器区域功能划分
在这4GB 的地址空间中,ARM 已经粗线条的平均分成了 8 个块,每块 512MB,每个块也都规定了用途,具体分类见下表格 。每个块的大小都有 512MB
其中我们常用的是片上外设,也就是Block2的区域,以下是Block2的进一步划分,在不同的地址区域,包含着不同的外设,下表也说明出了相应外设对应的总线。Block2 用于设计片内的外设,根据外设的总线速度不同,Block 被分成了 APB 和 AHB两部分,其中 APB 又被分为 APB1 和 APB2
寄存器到底是什么?
在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。
下面我们以GPIOA举例,对于上面表格中的GPIOA外设,其地址范围是0x4002 0000 - 0x4002 03FF,包含了10个32位的寄存器用来对GPIOA相应的引脚进行配置,由此可以进一步划分:
例如对于外设GPIOA,一共可以看成10个内存单元,一个内存单元是32bit,每个内存单元有不同的功能,例如单元 0x4002 0000单元,是用来设置引脚的模式的,((unsigned int*))0x4002 0000=0x01就是设置GPIO引脚为普通输出,但是这样不好记,就给这个单元起个名字:GPIOA_MODE,通过GPIOA_MODE=0x01,同样可以设置引脚为普通输出,且一看名字就知道是用来设置引脚模式的。
举例:
下面以让GPIOB端口的16个引脚输出高电平,要怎么实现?
1)通过绝对地址访问内存单元
1、0X40010C0C 是GPIOB输出数据寄存器ODR的地址。
2、(unsigned int*)的作用是什么?
将0x40010c0c这个立即数 强制类型位无符号整形地址 (说白了就是告诉编译器0x40010c0c这个对应的不是立即数而是一个无符号的整形地址)
3、学会使用C语言的 * 号
* 符号用法很多:
比方上面(unsigned int*)+立即数,就是把立即数强制转换为32位地址;
那么 *地址 是什么意思呢就像上面 *(unsigned int*) (0x40010c0c) 这样的形式就代表了这种无符号整形地址对应的内存空间,其实就是解引用操作:
*(unsigned int*) (0x40010c0c) = 0xFFFF; 就是对这个内存空间赋值0xFFFF
2)通过寄存器别名方式访问内存单元
上面就是stm32对寄存器内存单元的操作形式,对相应寄存器对应的地址进行宏定义 (已将将立即数强制转换成无符号整形地址)之后, *(宏定义名)就代表是这个寄存器的内存单元了,但是这么操作还不是很完美毕竟在宏定义寄存器的名字前面还加了一个 *
3)所以为了方便操作,我们干脆把指针操作“*”也定义到寄存器别名里面
这么看了就和1)中达到的效果是一样的,都是向0x40010c0c内存中写入数据,3)相当于给这个内存空间起个名字,更加简洁明了。