本文以STM32F051K8U6为例,主要介绍GPIO寄存器特点及应用,以及CubeMX的配置和HAL库函数的使用。
GPIO接口简介
通用输入输出接口GPIO是嵌入式系统、单片机开发过程中最常用的接口,用户可以通过编程灵活的对接口进行控制,实现对电路板上LED、数码管、按键等常用设备控制驱动,也可以作为串口的数据收发管脚,或AD的接口等复用功能使用。因此其作用和功能是非常重要的。
SoC芯片上的单个引脚可以通过配置GPIO寄存器以实现多种不同的功能,这就是多路复用。下图是芯片上GPIOA这一组端口上的引脚复用功能示意。
STM32F051-GPIO常用寄存器
4个32位配置寄存器
GPIOx_MODER, GPIOx_OTYPER, GPIOx_OSPEEDR GPIOx_PUPDR
2个32位数据寄存器
GPIOx_IDR 、GPIOx_ODR
1 个32 位置位 / 复位寄存器
GPIOx_BSRR
2 个 32 位复用功能配置寄存器
GPIOx_AFRH 、GPIOx_AFRL
注:每一组GPIO端口都有一套上述的寄存器去配置它自己的功能
MODER
GPIO 端口模式寄存器 (GPIOx_MODER) (x = A..D,F),主要用于指定GPIO端口的工作模式。
MODER是32位寄存器,其中每两位对应一个引脚。对于每个GPIOx上的引脚y,存在位[2y+1:2y](y = 0...15),即MODERy[1:0],用于配置I/O口的工作模式,模式代码如下:
00:输入模式(复位状态)
01:通用输出模式
10:复用功能模式
11:模拟模式
OTYPER
GPIO 端口输出类型寄存器 (GPIOx_OTYPER) (x = A..D,F),主要用于指定GPIO端口的输出类型。
OTYPER是32位寄存器,其中31:16为保留位,15:0为有效位,每一位二进制位对应一个引脚。对于每个GPIO上的引脚y,存在位[y](y = 0...15),即OTy,用于配置引脚的输出类型,类型代码如下:
0:推挽输出(复位状态)
1:开漏输出
如下图所示,若将输出类型设置为推挽输出模式,则不需要外接上(下)拉电阻,直接使用输出驱动器内部的互补信号输出高低电平,且在这两种电平下都有一定的驱动能力;若将输出类型设置为开漏输出模式,则是从MOSFET的漏极输出的电路,要得到真正的高电平状态需要加上拉电阻才行,否则引脚的高电平状态为高阻态。
OSPEEDR
GPIO 口输出速度寄存器 (GPIOx_OSPEEDR) (x = A..D,F),主要用于配置I/O口的输出速度。
OSPEEDR是32位寄存器,其中每两位对应一个引脚。对于每个GPIOx上的引脚y,存在位[2y+1:2y](y = 0...15),即OSPEEDRy[1:0],用于配置引脚的输出速度,代码如下:
x0:低速 2MHz
01:中速 10MHz
11:高速 50MHz
速度越高,输出效果越好,但能耗相应的也越高,因此在配置过程中根据具体应用需要选择合适的速度进行配置即可。
PUPDR
GPIO 口上拉 / 下拉寄存器 (GPIOx_PUPDR) (x = A..D,F),用于配置引脚的上拉或下拉电阻。
PUPDR是32位寄存器,其中每两位对应一个引脚。对于每个GPIOx上的引脚y,存在位[2y+1:2y](y = 0...15),即PUPDRy[1:0],用于配置引脚的上拉或下拉电阻,代码如下:
00:无上拉或下拉
01:上拉
10:下拉
11:保留
IDR
GPIO 端口输入数据寄存器 (GPIOx_IDR) (x = A..D,F),用于读取GPIO引脚的输入数据。
IDR是32位寄存器,其中31:16为保留位,15:0为有效位,每一位二进制位对应一个引脚。对于每个GPIO上的引脚y,存在位[y](y = 0...15),即IDRy(只读),用于读取GPIO引脚的输入数据。
ODR
GPIO 端口输入数据寄存器 (GPIOx_ODR) (x = A..D,F),用于写入GPIO引脚的输输出数据。
ODR是32位寄存器,其中31:16为保留位,15:0为有效位,每一位二进制位对应一个引脚。对于每个GPIO上的引脚y,存在位[y](y = 0...15),即ODRy,用于写入GPIO引脚的输出数据。
BSRR
GPIO 端口置位 / 复位寄存器 (GPIOx_BSRR) (x = A..D,F),用于对GPIO端口进行置位/复位操作。
ODR是32位寄存器,其中31:16为端口x的复位位,15:0端口x的置位位,每一位二进制位对应一个引脚。
位31:16 BRy:对端口x的第y位进行复位操作(y = 0...15)。
这些位只写,读的时候返回0x0000。
写0:对相应的ODRy位无影响
写1:复位相应的ODRy位
位15:0 BSy:对端口x的第y位进行置位操作(y = 0...15)。
这些位只写,读的时候返回0x0000。
写0:对相应的ODRy位无影响
写1:置位相应的ODRy位
注:BRy和BSy同时写1时,BSy优先。
AFRL/AFRH
GPIO 复用功能低位寄存器 (GPIOx_AFRL) (x = A..B),以及GPIO 复用功能高位寄存器 (GPIOx_AFRH) (x = A..B),用于配置GPIO端口的复用功能。
位31:0 AFRLy:端口x引脚y的复用功能选择(y = 0...7),每四位对应一个引脚,代码如下:
0000:AF0
0001:AF1
0010:AF2
0011:AF3
0100:AF4
0101:AF5
0110:AF6
0111:AF7
其他代码保留。
位31:0 AFRHy:端口x引脚y的复用功能选择(y = 8...15),每四位对应一个引脚,代码如下:
0000:AF0
0001:AF1
0010:AF2
0011:AF3
0100:AF4
0101:AF5
0110:AF6
0111:AF7
其他代码保留。
STM32F051-GPIO输入输出配置实例(寄存器版)
由下图可知,开发板上的LED2~4分别对应GPIOB的第0号、第1号、第2号引脚,且当引脚为高电平时LED灯熄灭,引脚为低电平时LED灯点亮。
对GPIOB端口的第0号、第1号、第2号引脚进行初始化,代码如下:
RCC->AHBENR |= 1<<18; //开启AHB外部时钟
GPIOB->MODER |= (1<<0)|(1<<2)|(1<<4); //将GPIOB端口的第0号、第1号、第2号引脚配置为输出模式
GPIOB->OTYPER = 0x0; //所有引脚配置为推挽输出
GPIOB->OSPEEDR = 0x0; //引脚速度设置为低速
对GPIOB端口的BSRR置位/复位寄存器进行操作,使第0号、第1号、第2号引脚输出一个周期为1s的方波信号,同时LED以1s为周期闪烁,代码如下:
while (1)
{
GPIOB->BSRR = (1<<16)|(1<<17)|(1<<18); // 点亮LED
HAL_Delay(500); // 延时500ms
GPIOB->BSRR = (1<<0)|(1<<1)|(1<<2); // 熄灭LED
HAL_Delay(500); // 延时500ms
}
由下图可知,开发板上的五向按键对应GPIOA的第8号引脚。
对GPIOA端口的第8号引脚进行初始化,代码如下:
GPIOA->MODER &= ~(0x3<<16); //将GPIOA端口的第8号引脚配置为输入模式
GPIOA->OTYPER = 0x0; //所有引脚配置为推挽输出
GPIOA->OSPEEDR = 0x0; //引脚速度设置为低速
当GPIOA端口的第8号引脚为高电平时,说明有按键按下,此时可以控制某LED电平翻转。
while (1)
{
while(GPIOA->IDR & (1<<8))
{
if(GPIOB->ODR & (1<<0))
GPIOB->BSRR = (1<<16);
else
GPIOB->BSRR = (1<<0);
HAL_Delay(100);
}
}
STM32F051-GPIO输入输出配置实例(HAL库函数版)
使用HAL库函数可以避免在配置寄存器的过程中出错,从而导致查错困难。首先使用CubeMX对HAL库进行初始化,如下图所示:
然后导出CubeMX自动生成的MDK工程,则可以在工程内看到GPIO初始化代码如下:
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET);
/*Configure GPIO pins : PB0 PB1 PB2 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
此时,在main函数中,只需要调用如下函数就能对GPIO引脚进行操作:
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_RESET); // 复位PB0, PB1, PB2 即点亮LED
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2, GPIO_PIN_SET); // 置位PB0, PB1, PB2 即熄灭LED
GPIO_PinState PA8_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_8); // 读取PA8状态,返回值为GPIO_PIN_SET(按键按下)/GPIO_PIN_RESET(按键放开)
至此,GPIO的输入输出操作总结完毕。
注:本文仅由作者用于总结和记录学习ARM系列软件和Linux驱动开发的过程,未经允许请勿擅自转载,如有侵权请联系作者删除。