STM32入门——寄存器与GPIO

寄存器与GPIO的关系

相信大家都是从GPIO过来的,截止到这里大家对寄存器的印象就是,每个IO口都是由7个寄存器来控制,有什么CRL,CRH,IDR,ODR,BRR,BSRR,LCKR。在这里不会对每个寄存器的功能进行介绍,而是帮助大家去理解GPIO和寄存器的关系,同时该如何去查看参考手册。
首先,大家把GPIO和单片机的PA0之类的引脚不要混淆,GPIO是通用输入输出端口,是一种总称。它包含有GPIOA,GPIOB等等这些GPIO端口组,在GPIOA里,又有PA0~PA15这十六个端口。用一张通俗的图来理解GPIO与PXx引脚还有寄存器的关系就是这样:
在这里插入图片描述
通俗的理解就是,我么可以通过控制寄存器,来控制相应的IO口,并且寄存器的操作位和IO口是具有着唯一对应关系的,举个例子就是,CRL寄存器有32位,每4位控制一个端口的出入输出状态,那么CRL就可以控制8个IO口的输入输出状态,那我们有PA0—PA15一共16个IO怎么办呢?再加一个一摸一样的寄存器CRH就完了,一个控制0-7,一个控制8-15。
对于IDR和ODR数据寄存器来说,他们也是32位的,低16位有效,也就是说每一位就正好对应一个IO口。剩下的BRR,BSRR,LCKR也是如此的。

总而言之就是一个GPIOx组有七个寄存器控制16个IO口。

如何操作寄存器去控制某一个IO口?

方法一:通过绝对地址访问内存单元

*unsigned int*)(0x40010C0C= 0xFFFF

这段代码实现的功能是通过指针对GPIOB的ODR寄存器进行操作,使其全部输出高电平,也就是全部赋值为1.
那么如何找到GPIOB的ODR地址呢?首先GPIO是挂载到APB总线的APB2上的,APB2的总线基地址为0x40010000,GPIOB相对APB2的偏移地址为0x0C00,GPIOB_ODR相对于GPIOB的偏移为0x0C。
将偏移量相加,就算出0x40010C0C就是GPIOB的ODR寄存器地址。
这里的0xFFFF在我看来展开为二进制数可能更好理解:1111 1111 1111 1111
相信到这里,大家已经能很直观的理解如何通过绝对地址访问内存单元了。

方法二:通过寄存器别名方式访问内存单元

通过绝对地址访问是为了帮助大家理解单片机的工作方式,正常开发的时候,基本上都是通过操作寄存器别名来对寄存器进行操作,其思想就是根据每块内存单元所具有功能的不同,给这些地址去取一个别名,这个过程也叫作寄存器映射。通过操作寄存器来控制IO端口,显然更加直接,也更加方便。

// 通过操作寄存器别名让GPIOB所有端口全部输出高电平
#define GPIO_ODR			*(unsigned int*)(GPIO_BASE + 0x0C)

GPIO_ODR = 0xFFFF;

寄存器的封装

总而言之,为了方便大家的操作,我们一般都会对寄存器进行封装,那么寄存器有这么多,寄存器之外还有很多外设,如果我们每次都是一个一个对应的去设置,那岂不是很麻烦?而这一部分的内容就是如何直观快速的进行这一操作。

1、宏定义对寄存器封装:
//外设基地址
#define PERIPH_BASE				    ((unsigned int)0x40000000)

//总线基地址
#define APB1PERIPH_BASE				PERIPH_BASE
#define APB2PERIPH_BASE				(PERIPH_BASE + 0x00010000)
#define AHBPERIPH_BASE				(PERIPH_BASE + 0x00020000)

//GPIO外设基地址
#define GPIOA_BASE				    (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE				    (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE				    (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE				    (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE				    (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE				    (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE				    (APB2PERIPH_BASE + 0x2000)

//寄存器地址,以GPIOA为例
#define GPIOA_CRL					(GPIOA_BASE + 0x00)
#define GPIOA_CRH					(GPIOA_BASE + 0x04)
#define GPIOA_IDR					(GPIOA_BASE + 0x08) 
#define GPIOA_ODR					(GPIOA_BASE + 0x0C)
#define GPIOA_BRR					(GPIOA_BASE + 0x10)
#define GPIOA_BSRR					(GPIOA_BASE + 0x14)
#define GPIOA_LCKR					(GPIOA_BASE + 0x08)

在我们定义好了外设的地址之后,就可以利用指针来对寄存器进行读写操作,下面以BSRR寄存器为例,看看指针是如何来控制寄存器的:

//控制GPIOA的PA0引脚输出低电平,将BSRR寄存器的BR0置1
*(unsigned int*)GPIOA_BSRR = (0x01 << (16+0));

//控制GPIOA的PA0引脚输出高电平,将BSRR寄存器的BS0置1
*(unsigned int*)GPIOA_BSRR = 0x01 << 0;

//通过IDR寄存器读取GPIOA端口所有引脚的电平
unsigned int temp;
temp = *(unsigned int*)GPIOA_IDR;

在这里插入图片描述
在这里插入图片描述
上面的这段代码使用(unsigned int*)把GPIOA_BSRR的宏的数值强制转换为了地址,然后利用’ * '做取指针的操作,对该地址进行赋值,从而实现写寄存器的功能。对于修改寄存器某一位的数值,其实有多种方法,在稍后的位操作方法中会讲到。
同样的读取寄存器也是取指针操作,把寄存器中的数据取到变量之中。

2、封装寄存器列表

上面所采用的方式,虽然对比之前的办法有了一定改善,但是还是很麻烦,因为GPIOA到GPIOG有很多组寄存器,但是每一组的寄存器位数以及相对偏移地址都是一样的,我们可以大胆地设想一下,能不能用一个方法,建立一个模板,GPIOA和GPIOB之类的都可以用呢?答案当然是肯定的,这就是C语言中的结构体~
在这里补充一下C语言结构体的基本知识:

1、结构体是个甚么东西?

我们知道数据的基本类型有:int (整形 ), char(字符形) , unsigned int (无符号整形),unsigned char (无符号字符形) 等,还包括数组等,但有些情况下 这些都无法满足现实的需求,于是程序员把所需变量组织起来,类似数组,便不同于数组,定义成一个新的数据类型,这就是结构体struct,.结构体类型是一种新的数据类型,在程序中,可以像基本数据类型一样对待.定义结构体类型变量, 表达方式如下

struct 结构体名
{
    
    
	类型+自定义的成员名称;
}变量名1,变量名2....;  <----------注意这里还有个分号

在这里呢,struct表示声明结构,结构体名用于标记结构体类型,也可以省略。

typedef为C语言的关键字,作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明

好了,大概了解一下就完事儿,下面用实例来看看这个结构体是怎么用的:

使用结构体对GPIO寄存器组进行封装
typedef unsigned 				int unit32_t;//无符号32位变量

typedef struct{
    
    
	unit32_t CRL;							//端口配置低寄存器		0x00
	unit32_t CRH;							//端口配置高寄存器		0x04
	unit32_t IDR;							//数据输入寄存器		    0x08
	unit32_t ODR;							//数据输出寄存器		    0x0C
	unit32_t BSRR;							//位设置/清除寄存器		0x10
	unit32_t BRR;							//端口位清除寄存器		0x14
	unit32_t LCKR;							//端口配置锁存寄存器	    0x18
}GPIO_Typedef;                              //<----------注意最后还有个分号

这段代码用typedef关键字声明了一个GPIO_Typedef的结构体类型,结构体内部有7个成员变量,也就是我们的七个寄存器。这就完事儿了?当然不是,继续往下看,C语言的语法规定了,结构体内变量的存储空间是连续的,其中32位的变量正好占据4个字节,16位的占据两个字节。也就是说,如果我们把这个结构体的首地址设为GPIO中某一组的基地址,比如GPIOA吧,查一下表(其实这个用久了自己就记住了):
在这里插入图片描述
我们找到GPIOA基地址是0x40010800,那么我们让结构体的首地址设定为0x40010800,那么第一个结构体成员的地址就是:0x40010800+0x00,这不就是GPIOA_CRL的地址嘛?再往后加四位:0x40010800+0x04,正好就是我们的CRH寄存器的地址,后面的也一样。这样一来,就和寄存器的地址对应上了。
在这里插入图片描述
既然结构体成员的地址定下来了,那我们就试一下怎么用结构体的形式来访问寄存器:

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

GPIOx = GPIOA_BASE;				//把指针地址设为宏GPIOA_BASE
GPIOx->IDR = 0xFFFF;
GPIOx->ODR = 0xFFFF;

unit32_t temp;
temp = GPIOx->IDR;

在这里我们首先使用GPIO_Typedef类型定义了一个结构体指针,然后把指针指向GPIOA的基地址GPIOA_BASE(0x40010800),然后我们再用GPIOx->ODR去访问读写寄存器就好了,至于为什么要这么写,这是C语言的语法规定。

到这里呢相信大家对C语言的寄存器封装已经了解的差不多了,我也告诉大家一个好消息,那就是,封装的过程STM32的固件库里已经封装好了,不需要咱们自己去动手,直接用就好了。我们下期再见,下一节的内容是寄存器的位操作,属于是C语言的基础内容了。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_36535414/article/details/115047115
今日推荐