HAL库与标准库不同的特点:
标准库中初始化外设使用结构体变量+Init
函数实现,结构体变量作为局部变量,仅包含外设的基础参数。
HAL库中结构体变量Handler句柄作为全局变量在配置中断和其他功能的过程中给被广泛引用。
与标准库不同,HAL中的中断服务函数按HAL的处理机制统一处理,不需要我们编写具体内容;
在中断函数中完成标志位的重置和修改,并调用回调函数(Callback),可以在main.c
中重写相应外设的回调函数,实现需要完成的功能。(原回调函数有由_weak修饰,支持重复定义)
使用CubeMX
配置GPIO底层参数:
-
在配置GPIO_Input时需要明确IO的检测方式
GPIO Pull-up
:IO的初始电平被上拉电阻拉高,检测低电平信号作为输出信号。GPIO Pull-down
: IO的初始电平被下拉电阻拉低,检测高电平信号作为输入信号。 -
在配置GPIO_Output时,GPIO模式(Mode)在**开漏(Open Drain)模式和推挽(Push-Pull)**之间选择。
推挽模式下,高低电平具有一定的驱动能力(相对低阻抗,输出电流的能力较强)
开漏模式下,高电平没有驱动能力,需要借助外部上拉电阻完成对外驱动。
详细参考文章 GPIO输入输出各种模式(推挽、开漏、准双向端口)详解.
所以我们使用IO一般设为推挽输出。
总结HAL库中GPIO的相关功能:
-
函数库:
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init); void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin); GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState); void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//实现 HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin); void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);
对应函数的功能可以从函数名中直观看出。
GPIO的寄存器:
STM32的寄存器都是32位,对应每个寄存器由4个地址组成。(一个地址为八位,通常指字节地址)
在STM32f4系列中对应GPIO的12个寄存器,其地址偏移为48(0x2c)。
具体分析各个寄存器:
-
IDR与ODR:两个16位的端口状态寄存器,对应每组GPIO的16个IO的输入输出。
IDR标明管脚状态,是只读寄存器。可以使用
GPIOx->IDR & 0x0001
去探明GPIO_PIN_1的电平状态。ODR标明端口输出数据,是可读写寄存器。可以使用
GPIOx->ODR=1<<13
为特定位赋值。(如PIN_13)示例:读出PA1的状态赋值给PA13(先定义uint16_t temp(unsigned short))
uint16_t temp; temp=GPIOA->IDR&0x0001; GPIOA->ODR|=temp<<13;
-
MODER,OTYPER,OSPEEDR与PUPDR:分别为32位,低16位,32位和32位寄存器,配置IO的基本参数。
MODER为可读写的端口配置寄存器,由低到高每两位控制一个管脚的四种模式。
顺序排布为:
00:输入(复位状态),01:通用输出状态,10:复用功能模式,11:模拟模式
OTYPER为可读写输出模式配置寄存器,由低到高每一位控制一个IO的输出模式。
顺序排布为:
0:输出推挽(复位状态),1:输出开漏
注意:OTYPER寄存器仅用于输出状态,在MODER[1:0]=00/10时不起作用。
OSPEEDR为可读写输出速度配置寄存器,由低到高每两位控制一个IO的输出速度。
顺序排布为:
00:2MHz(低速),01:25MHz(中速),10:50MHz(快速),11:100MHz/80MHz(高速)
注意:OSPEEDR寄存器仅用于输出状态,在MODER[1:0]=00/10时不起作用。
PUPDR为可读写电平状态寄存器,由低到高每两位控制一个IO的上拉/下拉。
顺序排布为:
00:无上拉或下拉,01:上拉,10:下拉,11:保留
void GPIO_Set(GPIO_TypeDef* GPIOx,u32 BITx,u32 MODE,u32 OTYPE,u32 OSPEED,u32 PUPD)
{
u32 pinpos=0,pos=0,curpin=0;
for(pinpos=0;pinpos<16;pinpos++)
{
pos=1<<pinpos; //逐个位进行检查
curpin=BITx&pos;//检查引脚是否需要设置
if(curpin==pos) //需要设置
{
GPIOx->MODER&=~(3<<(pinpos*2)); //先清除原来的模式
GPIOx->MODER|=MODE<<(pinpos*2); //设置新的模式
if((MODE==0X01)||(MODE==0X02)) //若为输出模式/复用模式
{
GPIOx->OSPEEDR&=~(3<<(pinpos*2)); //清除原来的设置
GPIOx->OSPEEDR|=(OSPEED<<(pinpos*2));//设置新的速度
GPIOx->OTYPER&=~(1<<pinpos) ; //清除原来的设置
GPIOx->OTYPER|=OTYPE<<pinpos; //设置新的输出模式
}
GPIOx->PUPDR&=~(3<<(pinpos*2)); //清除原来的设置
GPIOx->PUPDR|=PUPD<<(pinpos*2); //设置新的上下拉
}
}
}
//对应的宏定义
#define GPIO_MODE_IN 0 //输入
#define GPIO_MODE_OUT 1 //输出
#define GPIO_MODE_AF 2 //复用
#define GPIO_MODE_AIN 3 //模拟
#define GPIO_SPEED_2M 0 //2Mhz
#define GPIO_SPEED_25M 1 //25Mhz
#define GPIO_SPEED_50M 2 //50Mhz
#define GPIO_SPEED_100M 3 //100Mhz
#define GPIO_PUPD_NONE 0 //无上下拉
#define GPIO_PUPD_PU 1 //上拉
#define GPIO_PUPD_PD 2 //下拉
#define GPIO_PUPD_RES 3 //保留
#define GPIO_OTYPE_PP 0 //推挽
#define GPIO_OTYPE_OD 1 //开漏
//GPIO引脚编号
#define PIN0 1<<0
#define PIN1 1<<1
#define PIN2 1<<2
#define PIN3 1<<3
#define PIN4 1<<4
#define PIN5 1<<5
#define PIN6 1<<6
#define PIN7 1<<7
#define PIN8 1<<8
#define PIN9 1<<9
#define PIN10 1<<10
#define PIN11 1<<11
#define PIN12 1<<12
#define PIN13 1<<13
#define PIN14 1<<14
#define PIN15 1<<15
-
AFRL与AFRH:控制IO的复用功能,两个32位可读写寄存器。
**AFR每四位控制一个IO,对应0~15个不同的复用功能。**对应每组16个IO需要64位进行控制,所以由低到高位分为AFRL与AFRH两个寄存器。
//BITx:0~15,代表 IO 引脚编号.
//AFx:0~15,代表 AF0~AF15.
//可以直接以整型数的形式输入BITS和AFx
void GPIO_AF_Set(GPIO_TypeDef* GPIOx,u8 BITx,u8 AFx)
{
GPIOx->AFR[BITx>>3]&=~(0X0F<<((BITx&0X07)*4));//清空AFR中IO引脚编号对应的四位寄存器内容
GPIOx->AFR[BITx>>3]|=(u32)AFx<<((BITx&0X07)*4);//通过神奇的计算方法设置IO引脚编号对应的四位寄存器内容
}
-
**BSRR:控制ODR寄存器的端口设置或清除。**分为高16位和低16位,并且只能以字(16位)的形式操作。
低16位从低到高对应一组IO的16个管脚的ODR位,写入的内容如下
0:对应位无影响,1:对应ODR位置1
。高16位从低到高对应一组IO的16个管脚的ODR位,写入的内容如下
0:对应位无影响,1:对应ODR位清0
。
SRR:控制ODR寄存器的端口设置或清除。**分为高16位和低16位,并且只能以字(16位)的形式操作。
低16位从低到高对应一组IO的16个管脚的ODR位,写入的内容如下
0:对应位无影响,1:对应ODR位置1
。
高16位从低到高对应一组IO的16个管脚的ODR位,写入的内容如下0:对应位无影响,1:对应ODR位清0
。
总结
HAL库GPIO的端口复用,模拟输入和外部中断等功能封装的都比较全面,个人感受不方便进行修改,但是如果需要对GPIO的数据位进行操作,使用ODR和IDR寄存器即可,感觉BRSS寄存器并不直观。