本节介绍STM32单片机的GPIO编程的方法和步骤。
以STM32L431这颗芯片为例,下图是芯片的引脚图,红线标记的引脚就是GPIO引脚,单片机就是
利用这些引脚与外部相连,实现各种控制功能。

为了让GPIO能执行预定的功能,需要通过对一些GPIO相关的控制寄存器进行设置,与GPIO相关
的寄存器,包括:
《1》时钟使能寄存器:RCC_AHB2ENR
不仅是GPIO模块,芯片内许多其他的模块都会有时钟使能控制,要使模块正常工作,必须要使能
其时钟,这些时钟使能寄存器位于Reset and clock control (RCC模块),寄存器名称前面的字母,
是表示这个寄存器所属的模块。
《2》工作模式寄存器:GPIOx_MODER X: A B C D ……
这个寄存器可以设置GPIO的工作模式为:输入模式,输出模式,功能复用模式,模拟信号模式。
这里的x表示不同的GPIO组,可以是a b c d等。
《3》IO功能复用寄存器:GPIOx_AFRL,GPIOx_AFRH
这个寄存器可以将IO设置为某种复用的功能,IO口的复用功能列表,在器件手册中有详细说明。
《4》输出类型寄存器:GPIOx_OTYPER
可以设置输出类型为:推挽输出,漏极开路输出
《5》输出速度寄存器:GPIOx_OSPEEDR
可以根据具体需求设置输出速度为:低速,中速,高速,超高速
《6》上下拉寄存器:GPIOx_PUPDR
设置IO口输出时:上拉,下拉,无上下拉
以上是几个控制GPIO工作的控制寄存器。
《7》输入数据寄存器:GPIOx_IDR
可以通过其读入IO口当前的电平状态。
《8》输出数据寄存器:GPIOx_ODR
可以将需要输出到IO的电平写入此寄存器。
《9》数据位置位/复位寄存器:GPIOx_BSRR
对这个寄存器中相应的位写1,可以使对应的IO输出至1或至0。
《10》配置锁定寄存器:GPIOx_LCKR
如此寄存器中的某位按照特定的流程写1,那么对应的IO口的配置将会被锁定,直到下次芯片复位
才会解锁,可以防止IO的配置在程序运行中被意外修改,造成错误。
《11》数据位复位寄存器:GPIOx_BRR
对这个寄存器中相应的位写1,可以使对应的IO输出至0。
下面选定端口PA8,来举例说明具体的编程过程。这里将PA8配置成时钟输出,可以使用PA8来输
出各种时钟,下面将详细讲解如何将这几项功能配置到PA8。
1、GPIOA(PA8)时钟使能
首先需要使能PA8的时钟,因为PA8属于GPIOA,所以也就是要使能GPIOA的时钟,那我们如何确
定GPIOA的时钟控制在哪里呢,在前面的章节中,我们提到了两个重要的文档,其中一个是器件
手册,里面列表了各个片内设备的地址区间,找到包含GPIOA的那一部分,如图所示:

发现GPIOA属于AHB2总线,在编程手册的RCC模块中,找到AHB2总线的时钟使能寄存器
RCC_AHB2ENR,如下图:

GPIOA的时钟使能,是此寄存器的bit0,将此位写1就可以使能GPIOA的时钟。
图中蓝色圆圈处表示此寄存器相对于RCC模块基地址的偏移量是0x4C。
可是,怎么去操作这个RCC_AHB2ENR寄存器呢,我们发现,这个寄存器隶属于RCC模块,那么
再次从器件手册中查到RCC模块的地址区间,如图所示:

蓝色圆圈处,就是RCC模块寄存器的基地址:0x40021000
实现代码如下:
// 定义RCC模块的基地址
#define RCC ((uint32_t)0x40021000)
// 定义RCC_AHB2ENR寄存器的地址 (相对于RCC模块的基地址偏移量为0x4C):
#define RCC_AHB2ENR (*(volatile uint32_t *)(uint32_t)(RCC+0x4C))
// 定义GPIOA时钟使能位
#define RCC_AHB2ENR_GPIOAEN BIT(0)
// 将RCC_AHB2ENR【0】位置1,即使能GPIOA的时钟
RCC_AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
2、GPIOA(PA8)的工作模式
在器件手册上找到GPIOA的地址区间,如下图:

蓝色圆圈处,就是GPIOA模块寄存器的基地址:0x48000000
查阅编程手册,在GPIO模块的章节中找到GPIOx_MODER寄存器说明,如下图:

蓝色圆圈处,就是GPIOx_MODER寄存器地址相对于GPIOA模块基地址的偏移量:0x00
红色圆圈处,就是用来设置PA8端口工作模式的对应位。
红线处为PA8选择的工作模式:“10” 功能复用模式
实现代码如下:
#define GPIOA ((uint32_t)0x48000000)
#define GPIO_MODER(gpiox) (*(volatile uint32_t *)(uint32_t)(gpiox + 0x00))
#define GPIO_MODE_AF (BITS(0,1) & (uint32_t)0x02)
#define GPIO_MODE_MASK(n) ((uint32_t)((uint32_t)(3) << (2 * n)))
#define GPIO_MODE_SET(n, val) ((uint32_t)((uint32_t)(val) << (2 * n)))
// 将GPIOA_MODER【17:16】位清0,即清除PA8的工作模式控制位
// 清零的目的是:保证新的模式用或的方式写入时不会出现其他的值
GPIO_MODER(GPIOA) &= ~GPIO_MODE_MASK(8);
// 将GPIOA_MODER【17:16】位写入:PA8工作模式=GPIO_MODE_AF,即复用功能模式
GPIO_MODER(GPIOA) |= GPIO_MODE_SET (8, GPIO_MODE_AF);
3、GPIOA(PA8)的IO复用功能
PA8用于时钟输出,是利用PA8的IO复用功能,查阅芯片手册,找到PA8的复用功能列表如下:

由上图得到PA8的AF0的输出的功能是MCO(时钟输出)。
查阅编程手册,找到设置复用功能的寄存器GPIOx_AFRH,因为是用4个bit来设定一个IO的复用功
能所以PA0到PA7用GPIOx_AFRL来设置,PA8到PA15用GPIOx_AFRH来设置:

蓝色圆圈处表示GPIOx_AFRH相对于GPIOA基地址的偏移量是0x24,红色圆圈处的4个bit用来设置PA8的复用功能,红线处表示AF0的设置值为b0000。实现代码如下:
#define GPIO_AFRH(gpiox) (*(volatile uint32_t *)(uint32_t)(gpiox + 0x24))
#define GPIO_AF_0 (BITS(0,3) & (uint32_t)0x00)
#define GPIO_AFR_MASK(n) ((uint32_t)((uint32_t)(0xf)<< (4 * n)))
#define GPIO_AFR_SET(n, val) ((uint32_t)((uint32_t)(val) << (4 * n)))
//将GPIOA_AFRH【3 : 0】位清0,即清除PA8的复用功能控制位
GPIO_AFRH(GPIOA) &= ~GPIO_AFR_MASK(0);
//将GPIOA_AFRH【3 : 0】位写入:即设置PA8的复用功能 = GPIO_AF_0,就是MCO
GPIO_AFRH(GPIOA) |= GPIO_AFR_SET(0, GPIO_AF_0);
4、GPIOA(PA8)的输出类型
PA8用于时钟输出,所以输出类型为输出模式,查阅编程手册找到寄存器GPIOx_OTYPER:

设置PA8为漏极开路输出,实现代码如下:
#define GPIO_OTYPER(gpiox) (*(volatile uint32_t *)(uint32_t)(gpiox + 0x04))
// 将GPIOA_OTYPER【8】位置1 ,PA8开漏输出
GPIO_OTYPER(GPIOA) |= BIT(8);
5、GPIOA(PA8)的输出速度
将PA8设置为超高速输出,查阅编程手册找到寄存器GPIOx_OSPEEDR:

实现代码如下:
#define GPIO_OSPEEDR(gpiox) (*(volatile uint32_t *)(uint32_t)(gpiox + 0x08))
#define GPIO_OSPEED_VERYHIGH (BITS(0,1) & (uint32_t)0x03)
#define GPIO_OSPEED_MASK(n) ((uint32_t)((uint32_t)(0x3)<< (2 * n)))
#define GPIO_OSPEED_SET(n, val) ((uint32_t)((uint32_t)(val) << (2 * n)))
// 将GPIOA_OSPEEDR【17 : 16】位清0,即清除PA8的输出速度控制位
GPIO_OSPEEDR(GPIOA) &= ~GPIO_OSPEED_MASK(8);
// 将GPIOA_OSPEEDR【17 : 16】位写入:
// 即设置PA8的输出速度=GPIO_OSPEED_VERYHIGH,超高速
GPIO_OSPEEDR(GPIOA) |= GPIO_OSPEED_SET(8, GPIO_OSPEED_VERYHIGH);
6、GPIOA(PA8)的上下拉
将PA8设置为上拉,查阅编程手册找到寄存器GPIOx_PUPDR:

实现代码如下:
#define GPIO_PUPDR(gpiox) (*(volatile uint32_t *)(uint32_t)(gpiox + 0x0C))
#define GPIO_PUPD_PULLUP (BITS(0,1) & (uint32_t)0x01)
#define GPIO_PUPD_MASK(n) ((uint32_t)((uint32_t)(0x3)<< (2 * n)))
#define GPIO_PUPD_SET(n, val) ((uint32_t)((uint32_t)(val) << (2 * n)))
// 将GPIOA_PUPDR【17 : 16】位清0,即清除PA8的上下拉控制位
GPIO_PUPDR(GPIOA) &= ~GPIO_PUPD_MASK(8);
// 将GPIOA_PUPDR【17 : 16】位写入:即设置PA8的上下拉=GPIO_PUPD_PULLUP,上拉
GPIO_PUPDR(GPIOA) |= GPIO_PUPD_SET(8, GPIO_PUPD_PULLUP);
1503

被折叠的 条评论
为什么被折叠?



