单片机STM32学习笔记之寄存器映射详解

  咱们知道,存储器自己没有地址,给存储器分配地址的过程叫存储器映射,那什么叫寄存器映射?寄存器究竟是什么?html

  在存储器Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共32bit,每个单元对应不一样的功能,当咱们控制这些单元时就能够驱动外设工做。咱们能够找到每一个单元的起始地址,而后经过C 语言指针的操做方式来访问这些单元,若是每次都是经过这种地址的方式来访问,不只很差记忆还容易出错,这时咱们能够根据每一个单元功能的不一样,以功能为名给这个内存单元取一个别名,这个别名就是咱们常常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。linux

  好比,咱们找到GPIOB 端口的输出数据寄存器ODR 的地址是0x4001 0C0C(至于这个地址如何找到能够先跳过,后面咱们会有详细的讲解),ODR 寄存器是32bit,低16bit有效,对应着16 个外部IO,写0/1 对应的的IO 则输出低/高电平。如今咱们经过C 语言指针的操做方式,让GPIOB 的16 个IO 都输出高电平。编程

  1 // GPIOB 端口所有输出 高电平学习

  2 *(unsigned int*)(0x4001 0C0C) = 0xFFFF;设计

  0x4001 0C0C 在咱们看来是GPIOB 端口ODR 的地址,可是在编译器看来,这只是一个普通的变量,是一个当即数,要想让编译器也认为是指针,咱们得进行强制类型转换,把它转换成指针,即(unsigned int *)0x4001 0C0C,而后再对这个指针进行 * 操做。刚刚咱们说了,经过绝对地址访问内存单元很差记忆且容易出错,咱们能够经过寄存器的方式来操做。指针

  1 // GPIOB 端口所有输出 高电平视频

  2 #define GPIOB_ODR (unsigned int*)(GPIOB_BASE+0x0C)htm

  3 * GPIOB_ODR = 0xFF;blog

  为了方便操做,咱们干脆把指针操做“*”也定义到寄存器别名里面。内存

  1 // GPIOB 端口所有输出 高电平

  2#define GPIOB_ODR * (unsigned int*)(GPIOB_BASE+0x0C)

  3 GPIOB_ODR = 0xFF;

  STM32 的外设地址映射

  片上外设区分为三条总线,根据外设速度的不一样,不一样总线挂载着不一样的外设,APB1挂载低速外设,APB2 和AHB 挂载高速外设。相应总线的最低地址咱们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中APB1 总线的地址最低,片上外设从这里开始,也叫外设基地址。

  1. 总线基地址

  

 

  2. 外设基地址

  总线上挂载着各类外设,这些外设也有本身的地址范围,特定外设的首个地址称为“XX 外设基地址”,也叫XX 外设的边界地址。具体有关STM32F10xx 外设的边界地址请参考《STM32F10xx 参考手册》的2.3 小节的存储器映射的表1:STM32F10xx 寄存器边界地址。

  这里面咱们以GPIO 这个外设来说解外设的基地址,GPIO 属于高速的外设 ,挂载到APB2 总线上。

  

 

  3. 外设寄存器

  在XX 外设的地址范围内,分布着的就是该外设的寄存器。以GPIO 外设为例,GPIO是通用输入输出端口的简称,简单来讲就是STM32 可控制的引脚,基本功能是控制引脚输出高电平或者低电平。最简单的应用就是把GPIO 的引脚链接到LED 灯的阴极,LED 灯的阳极接电源,而后经过STM32 控制该引脚的电平,从而实现控制LED 灯的亮灭。

  GPIO 有不少个寄存器,每个都有特定的功能。每一个寄存器为32bit,占四个字节,在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。这里咱们以GPIOB 端口为例,来讲明GPIO都有哪些寄存器。

  

 

  有关外设的寄存器说明可参考《STM32F10xx 参考手册》中具体章节的寄存器描述部分,在编程的时候咱们须要反复的查阅外设的寄存器说明。

  这里咱们以“GPIO 端口置位/复位寄存器”为例,告诉你们如何理解寄存器的说明。

  

 

  ①名称

  寄存器说明中首先列出了该寄存器中的名称,“(GPIOx_BSRR)(x=A…E)”这段的意思是该寄存器名为“GPIOx_BSRR”其中的“x”能够为A-E,也就是说这个寄存器说明适用于GPIOA、GPIOB 至GPIOE,这些GPIO端口都有这样的一个寄存器。

  ②偏移地址

  偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是0x18,从参考手册中咱们能够查到GPIOA 外设的基地址为0x4001 0800 ,咱们就能够算出GPIOA的这个GPIOA_BSRR 寄存器的地址为:0x4001 0800+0x18 ;同理,因为GPIOB 的外设基地址为0x4001 0C00,可算出GPIOB_BSRR 寄存器的地址为:0x4001 0C00+0x18 。其余GPIO端口以此类推便可。

  ③寄存器位表

  紧接着的是本寄存器的位表,表中列出它的0-31 位的名称及权限。表上方的数字为位编号,中间为位名称,最下方为读写权限,其中w 表示只写,r 表示只读,rw 表示可读写。本寄存器中的位权限都是w,因此只能写,若是读本寄存器,是没法保证读取到它真正内容的。而有的寄存器位只读,通常是用于表示STM32 外设的某种工做状态的,由STM32硬件自动更改,程序经过读取那些寄存器位来判断外设的工做状态。

  ④位功能说明

  位功能是寄存器说明中最重要的部分,它详细介绍了寄存器每个位的功能。例如本寄存器中有两种寄存器位,分别为BRy 及BSy,其中的y 数值能够是0-15,这里的0-15表示端口的引脚号,如BR0、BS0 用于控制GPIOx 的第0 个引脚,若x 表示GPIOA,那就是控制GPIOA的第0 引脚,而BR一、BS1 就是控制GPIOA 第1 个引脚。

  其中BRy 引脚的说明是“0:不会对相应的ODRx 位执行任何操做;1:对相应ODRx位进行复位”。这里的“复位”是将该位设置为0 的意思,而“置位”表示将该位设置为1;说明中的ODRx 是另外一个寄存器的寄存器位,咱们只须要知道ODRx 位为1 的时候,对应的引脚x 输出高电平,为0 的时候对应的引脚输出低电平便可(感兴趣的读者能够查询该寄存器GPIOx_ODR 的说明了解)。因此,若是对BR0 写入“1”的话,那么GPIOx 的第0 个引脚就会输出“低电平”,可是对BR0 写入“0”的话,却不会影响ODR0 位,因此引

  脚电平不会改变。要想该引脚输出“高电平”,就须要对“BS0”位写入“1”,寄存器位BSy 与BRy 是相反的操做。

  C 语言对寄存器的封装

  1. 封装总线和外设基地址

  在编程上为了方便理解和记忆,咱们把总线基地址和外设基地址都以相应的宏定义起来,总线或者外设都以他们的名字做为宏名。

  

 

  首先定义了 “片上外设”基地址PERIPH_BASE,接着在PERIPH_BASE 上加入各个总线的地址偏移, 获得APB1 、APB2 总线的地址APB1PERIPH_BASE 、APB2PERIPH_BASE,在其之上加入外设地址的偏移,获得GPIOA-G的外设地址,最后在外设地址上加入各寄存器的地址偏移,获得特定寄存器的地址。一旦有了具体地址,就能够用指针读写。

  

 

  该代码使用 (unsigned int *) 把GPIOB_BSRR 宏的数值强制转换成了地址,而后再用“*”号作取指针操做,对该地址的赋值,从而实现了写寄存器的功能。一样,读寄存器也是用取指针操做,把寄存器中的数据取到变量里,从而获取STM32 外设的状态。

  2. 封装寄存器列表

  用上面的方法去定义地址,仍是稍显繁琐,例如GPIOA-GPIOE 都各有一组功能相同的寄存器,如GPIOA_ODR/GPIOB_ODR/GPIOC_ODR 等等,它们只是地址不同,但却要为每一个寄存器都定义它的地址。为了更方便地访问寄存器,咱们引入C 语言中的结构体语法对寄存器进行封装。

  

 

  这段代码用typedef 关键字声明了名为GPIO_TypeDef 的结构体类型,结构体内有7 个成员变量,变量名正好对应寄存器的名字。C 语言的语法规定,结构体内变量的存储空间是连续的,其中32 位的变量占用4 个字节,16 位的变量占用2 个字节。

  

 

  也就是说,咱们定义的这个GPIO_TypeDef ,假如这个结构体的首地址为0x40010C00(这也是第一个成员变量CRL 的地址), 那么结构体中第二个成员变量CRH 的地址即为0x4001 0C00 +0x04 ,加上的这个0x04 ,正是表明CRL 所占用的4 个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给。

  这样的地址偏移与STM32 GPIO 外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址肯定下来,而后就能以结构体的形式访问寄存器。

  

 

  这段代码先用GPIO_TypeDef 类型定义一个结构体指针GPIOx,并让指针指向地址GPIOB_BASE(0x4001 0C00),使用地址肯定下来,而后根据C 语言访问结构体的语法,用GPIOx->ODR 及GPIOx->IDR 等方式读写寄存器。

  最后,咱们更进一步,直接使用宏定义好GPIO_TypeDef 类型的指针,并且指针指向各个GPIO端口的首地址,使用时咱们直接用该宏访问寄存器便可。

  

 

  这里咱们仅是以GPIO 这个外设为例,给你们讲解了C 语言对寄存器的封装。以此类推,其余外设也一样能够用这种方法来封装。好消息是,这部分工做都由固件库帮咱们完成了,这里咱们只是分析了下这个封装的过程,让你们知其然,也只其因此然。

 

stm32视频学习资料

STM32能够这样玩
http://www.makeru.com.cn/live/4034_1460.html?s=45051
分析STM32的的开发方式
http://www.makeru.com.cn/live/3523_1673.html?s=45051

释放潜能:学习效率提高、编程能力提高
http://www.makeru.com.cn/live/3507_1276.html?s=45051
从单片机到嵌入式linux咱们须要作什么
http://www.makeru.com.cn/live/5413_1994.html?s=45051

stm32 如何用DMA搬运数据
http://www.makeru.com.cn/live/detail/1484.html?s=45051

(STM32中断系统)
http://www.makeru.com.cn/live/1392_1124.html?s=45051

pdf以及相关文档下载群:830802928

相关文章
相关标签/搜索