如何理解寄存器在STM32工作机制的重要作用

在生活工作中我们接触的电子产品都有STM32的身影,如平衡车、打印机、冰箱、咖啡机、智能电饭煲等,对STM32这款Cortex-M系列内核的微控制器产品,其工作机制也是我们比较好奇的地方,下面就赶紧来领略一下这款MCU迷人之处。

首先,但从产品构成上来看,STM32主要由内核和片上外设组成,这就好比电脑的CPU与显卡、硬盘、内存条的关系,内核就好比CPU,Cortex-M系列内核是由ARM公司授权,芯片厂商则是在内核之外设计并生产芯片,内核之外最重要的就是片上外设,它包括GPIO、UART、I2C、USB、SPI、TIM、ADC等常用功能。以Cortex-M4内核存储器映射为例,FALSH、RAM和片上外设都分布在总线上,这些部件排列在一个4G的地址空间内,存储器本身不具有地址属性,这些地址由芯片厂商和用户来分配,分配地址的过程就是通常所说的存储器映射,重映射也是这样一个过程。

4G的地址空间是0x0000 0000到0xFFFF FFFF,把十六进制换算为二进制来看,总共有2^32=4294967296个byte,及4294967296byte/1024=4194304KB,4194304KB/1024=4096MB,4096MB/1024=4GB。就目前STM32的产品来看,Flash地址在0x0800 0000-0x081F FFFF之间,也就是2MB,这也是目前STM32所支持的最大Flash,每个型号所具有Flash大小也不一样。片上的外设由总线来控制,根据外设的总线速度不同,Block被分成了APB和AHB两部分,其中APB又被分为APB1和APB2,AHB分为AHB1和AHB2,还有AHB3,用于扩展外部存储器,如SDRAM、NORFLASH和NANDFLASH等。

以上,大家对存储器映射也有了大概的了解,下面进入到文章的核心——操作寄存器。

每个外设都要自己的地址单元,我们可以用C语言的指针来访问并操作它们,但为了是这些外设使用起来更加方便,就根据功能给这些单元取了不同的名字,这个名字就是寄存器,这个过程就是寄存器映射。外设是挂载在总线上的,每个总线都有一个基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中 APB1总线的地址最低,片上外设从这里开始,也叫外设基地址。如下图所示:


总线上挂载着各种外设,这些外设也有自己的地址范围,以外设GPIO为例:

GPIOA的基址相对于AHB1总线的地址偏移为0,那说明AHB1总线的第一个外设就是GPIOA。

在每个外设的地址范围内,分布着的就是该外设的寄存器。每个寄存器为 32bit,占四个字节,在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。下图以GPIOH为例:

由上图提供的信息,如果要使GPIOH的所有IO口都输出高电平,只需操作ODR寄存器即可:*(unsigned int*)(0x4002 1C14) = 0xFFFF;注意,因为ODR寄存器是低16bit有效,所以0xFFFF实际上是0x0000 FFFF。现在使用的固件库里寄存器是已经封装好了的,如:

# define   GPIOH_ODR     *(unsigned int*)(GPIOH_BASE+0x14)  

在经过封装之后,我们就可以更加直观的来操作这个寄存器了,可以这样操作:GPIOH_ODR=0xFF

接下来我们一起来看一下寄存器说明,以“GPIO 端口置位/复位寄存器”为例:

“(GPIOx_BSRR)(x=A…I)”表示x的值可以为A—I,如GPIOA、GPIOH等,这些GPIO口都有这个寄存器。

偏移地址是指本寄存器相对于这个外设的基地址的偏移。这个寄存器的偏移地址是 0x18,从参考手册中可以查到 GPIOA外设的基地址为 0x4002 0000 ,就是AHB1这一块内存的首地址,GPIO是挂载在AHB1的总线上面的,这样就可以算出GPIOA的这个 GPIOA_BSRR 寄存器的地址为:0x4002 0000+0x18 ;同理,由于GPIOB的外设基地址为 0x4002 0400,可算出 GPIOB_BSRR 寄存器的地址为:0x4002 0400+0x18 。其他 GPIO端口以此类推即可。

寄存器有32位,位表中列出了0至31位的名称和权限,权限是指只读r、只写w和可读可写rw,该寄存器的位权限都是w,只能写,如果读该寄存器,不能保证读取到它的真正内容,有的寄存器权限为只读,一般用于表示该外设的某种工作状态,由硬件状态来决定,可以通过读取这些寄存器位来获取或者判断该外设的工作状态。

图下面的位功能说明是最重要的,这里介绍了寄存器每一位的功能。如图中介绍的BRy和BSy,其中y可以是0—15,表示的是引脚号,如BS10可以用于表示GPIOA的第十个引脚,即PA10引脚该寄存器的BS位。在说明中,复位是指将该位设置为0,置位是指将该位设置为1,对于ODRx我需要知道:ODRx位为 1 的时候,对应的引脚 x输出高电平,为 0 的时候对应的引脚输出低电平。所以,如果对 BR0 写入“1”的话,那么 GPIOx的第0 个引脚就会输出“低电平”,但是对 BR0写入“0”的话,却不会影响 ODR0位,所以引脚电平不会改变。要想该引脚输出“高电平”,就需要对“BS0”位写入“1”,寄存器位BSy与 BRy是相反的操作。

/*外设基地址,也是APB1地首地址*/

#define PERIPH_BASE         0x40000000UL

/*总线基地址 */

#define APB1PERIPH_BASE       PERIPH_BASE

#define APB2PERIPH_BASE       (PERIPH_BASE + 0x00010000UL)

#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000UL)

#define AHB2PERIPH_BASE       (PERIPH_BASE + 0x10000000UL)

/*GPIO外设基地址 */

#define GPIOA_BASE           (AHB1PERIPH_BASE + 0x0000UL)

#define GPIOB_BASE           (AHB1PERIPH_BASE + 0x0400UL)

#define GPIOC_BASE           (AHB1PERIPH_BASE + 0x0800UL)

#define GPIOD_BASE           (AHB1PERIPH_BASE + 0x0C00UL)

#define GPIOE_BASE           (AHB1PERIPH_BASE + 0x1000UL)

#define GPIOF_BASE           (AHB1PERIPH_BASE + 0x1400UL)

#define GPIOG_BASE           (AHB1PERIPH_BASE + 0x1800UL)


#define GPIOH_BASE           (AHB1PERIPH_BASE + 0x1C00UL)

#define GPIOI_BASE           (AHB1PERIPH_BASE + 0x2000UL)

/* 寄存器基地址,以 GPIOA 为例 */

#define GPIOA_MODER         (GPIOA_BASE+0x00)

#define GPIOA_OTYPER         (GPIOA_BASE+0x04)

#define GPIOA_OSPEEDR       (GPIOA_BASE+0x08)

#define GPIOA_PUPDR         (GPIOA_BASE+0x0C)

#define GPIOA_IDR           (GPIOA_BASE+0x10)

#define GPIOA_ODR           (GPIOA_BASE+0x14)

#define GPIOA_BSRR           (GPIOA_BASE+0x18)

#define GPIOA_LCKR           (GPIOA_BASE+0x1C)

#define GPIOA_AFRL           (GPIOA_BASE+0x20)

#define GPIOA_AFRH           (GPIOA_BASE+0x24)

代码先后定义片上外设基地址和经过地址偏移后得到的各总线地址,再到总线地址偏移后总线上挂载的各外设地址,最后外设地址偏移后得到各外设的寄存器地址,这就是我们要操作的部分。比如:

/* 控制 GPIOA 引脚 10 输出低电平(BSRR 寄存器的 BR10 置 1) */

*(unsigned int *)GPIOA_BSRR = (0x01<<(16+10));

/* 控制 GPIOA 引脚 10 输出高电平(BSRR 寄存器的 BS10 置 1) */

*(unsigned int *)GPIOA_BSRR = 0x01<<10;

unsigned int temp;

/* 控制 GPIOH 端口所有引脚的电平(读 IDR 寄存器) */

temp = *(unsigned int *)GPIOA_IDR;

上面通过定义寄存器地址的方法稍显繁琐,以GPIO为例,GPIOA至GPIOH等都有一组功能相同的寄存器,它们的地址也不相同,这样一个个去进行定义代码量很大,于是我们可以通过结构体来统一对这些寄存器进行封装。比如:

typedef unsigned int uint32_t;       /*无符号 32 位变量 占4个字节*/

typedef unsigned short int uint16_t; /*无符号 16 位变量 占2个字节*/

/* GPIO 寄存器列表 */

typedef struct{

   uint32_t MODER;   /*GPIO 模式寄存器 地址偏移: 0x00 */

   uint32_t OTYPER;   /*GPIO 输出类型寄存器 地址偏移: 0x04 */

   uint32_t OSPEEDR; /*GPIO 输出速度寄存器 地址偏移: 0x08 */

   uint32_t PUPDR;   /*GPIO 上拉/下拉寄存器 地址偏移: 0x0C */

   uint32_t IDR;     /*GPIO 输入数据寄存器 地址偏移: 0x10 */

   uint32_t ODR;     /*GPIO 输出数据寄存器 地址偏移: 0x14 */

   uint16_t BSRRL;   /*GPIO 置位/复位寄存器低 16 位部分 地址偏移: 0x18 */

   uint16_t BSRRH;   /*GPIO 置位/复位寄存器高 16 位部分 地址偏移: 0x1A */

   uint32_t LCKR;   /*GPIO 配置锁定寄存器 地址偏移: 0x1C */

   uint32_t AFR[2]; /*GPIO 复用功能配置寄存器 地址偏移: 0x20-0x24 */

} GPIO_TypeDef;

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

在上面结构体中,4个字节地址的偏移量与STM32 GPIO外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存器了,如下代码:

GPIO_TypeDef * GPIOx;     //定义一个 GPIO_TypeDef 型结构体指针 GPIOx

GPIOx = GPIOH_BASE;       //把指针地址设置为宏 GPIOH_BASE 地址

GPIOx->BSRRL = 0xFFFF;     //通过指针访问并修改 GPIOH_BSRRL 寄存器

GPIOx->MODER = 0xFFFFFFFF; //修改 GPIOH_MODER 寄存器

GPIOx->OTYPER =0xFFFFFFFF; //修改 GPIOH_OTYPER 寄存器

uint32_t temp;

temp = GPIOx->IDR;       //读取 GPIOH_IDR 寄存器的值到变量 temp 中

上面代码先使结构体指针GPIOx指向地址GPIOH_BASE(0x4002 1C00),然后就可以对结构体里的寄存器进行读写了。为了更方便对每组GPIO口进行操作,可以使用宏定义好的GPIO_TypeDef类型的指针指向各个GPIO端口的首地址,以便使用时可以直接访问宏寄存器即可。比如:

/*使用 GPIO_TypeDef 把地址强制转换成指针*/

#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)

 

到这里大家应该对寄存器在STM32中的作用有了深刻理解,在我们现在的项目开发中,有ST已经做好封装的固件库给以供我们直接使用,方便了很多。寄存器在STM32运行机制中的是核心,库函数操作的本质就是寄存器。以上以GPIO为例,也更为直观的展示了库函数的封装过程,在此就不延伸到库函数的操作了,相信大家对库函数的使用也很熟悉了。

ST官方提供了丰富的生态系统,提供各种STM32相关Demo板,以最新出来的MCU G0为例,可以在NUCLEO-G071RB Nucleo板上进一步验证理解上文的内容。  

 

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论