GPIO的工作模式
GPIO共有八种模式,在参考手册的8.1章节有描述,可总结为下表
输入用,完全浮空,状态不定,IO口没有上下拉电阻,适用于外部数字信号的输入检测,,但需要注意在输入之前要先设置上下拉电阻或者确保外部电路有上拉或下拉电阻,否则会导致IO口信号的漂移。
输入用,用内部上拉,默认是高电平,适用于外部数字信号的输入检测,且外部电路没有上拉电阻时。
输入用,用内部下拉,默认是低电平,适用于外部数字信号的输入检测,且外部电路没有下拉电阻时。
ADC、DAC,可以进行模拟信号的输入和输出。适用于模拟量采集和输出等场合。
IO口可以输出低电平,但驱动能力弱,不能输出高电平适用于需要外部上拉电阻的场合,如软件IIC的SDA、SCL等
驱动能力强,25mA(max),通用输出,如驱动LED设备
片上外设功能(硬件IIC 的SDA、SCL引脚等)
片上外设功能(SPI 的SCK、MISO、MOSI引脚等)
在参考手册的8.1.9章节给出了GPIO复用时的设置,下面给出部分设置截图,但是并没有给出上下拉的具体设置,应当是需要根据实际外部硬件设置的,也有可能与连接的设备有关,查考连接的设备的数据手册,可以参考正点原子例程,或参考STM32cubeIDE的固件库的Project的相关例程,如果不确定在画PCB时可以预留一下上下拉电阻的焊盘。
在HAL实际操作中,在stm32f1xx_hal_gpio.h进行了宏定义,可以设置以下几个类型,
#define GPIO_MODE_INPUT 0x00000000u /*!< Input Floating Mode */
#define GPIO_MODE_OUTPUT_PP 0x00000001u /*!< Output Push Pull Mode */
#define GPIO_MODE_OUTPUT_OD 0x00000011u /*!< Output Open Drain Mode */
#define GPIO_MODE_AF_PP 0x00000002u /*!< Alternate Function Push Pull Mode */
#define GPIO_MODE_AF_OD 0x00000012u /*!< Alternate Function Open Drain Mode */
#define GPIO_MODE_AF_INPUT GPIO_MODE_INPUT /*!< Alternate Function Input Mode */
#define GPIO_MODE_ANALOG 0x00000003u /*!< Analog Mode */
#define GPIO_MODE_IT_RISING 0x10110000u /*!< External Interrupt Mode with Rising edge trigger detection */
#define GPIO_MODE_IT_FALLING 0x10210000u /*!< External Interrupt Mode with Falling edge trigger detection */
#define GPIO_MODE_IT_RISING_FALLING 0x10310000u /*!< External Interrupt Mode with Rising/Falling edge trigger detection */
#define GPIO_MODE_EVT_RISING 0x10120000u /*!< External Event Mode with Rising edge trigger detection */
#define GPIO_MODE_EVT_FALLING 0x10220000u /*!< External Event Mode with Falling edge trigger detection */
#define GPIO_MODE_EVT_RISING_FALLING 0x10320000u /*!< External Event Mode with Rising/Falling edge trigger detection */
GPIO_MODE_INPUT:输入模式,引脚浮空输入。
GPIO_MODE_OUTPUT_PP:推挽输出模式,引脚输出高或低电平。
GPIO_MODE_OUTPUT_OD:开漏输出模式,引脚输出高电平或者不输出电平。
GPIO_MODE_AF_PP:复用推挽输出模式,引脚输出复用功能。
GPIO_MODE_AF_OD:复用开漏输出模式,引脚输出复用功能。
GPIO_MODE_AF_INPUT:复用输入模式,引脚浮空输入。
GPIO_MODE_ANALOG:模拟输入模式,引脚输入模拟信号。
GPIO_MODE_IT_RISING:上升沿中断模式,引脚上升沿触发中断。
GPIO_MODE_IT_FALLING:下降沿中断模式,引脚下降沿触发中断。
GPIO_MODE_IT_RISING_FALLING:上升和下降沿中断模式,引脚上升或下降沿触发中断。
GPIO_MODE_EVT_RISING:上升沿事件模式,引脚上升沿触发事件。
GPIO_MODE_EVT_FALLING:下降沿事件模式,引脚下降沿触发事件。
GPIO_MODE_EVT_RISING_FALLING:上升和下降沿事件模式,引脚上升或下降沿触发事件。
上下拉的有以下几种类型
#define GPIO_NOPULL 0x00000000u /*!< No Pull-up or Pull-down activation */
#define GPIO_PULLUP 0x00000001u /*!< Pull-up activation */
#define GPIO_PULLDOWN 0x00000002u /*!< Pull-down activation */
GPIO_NOPULL:不使用上下拉电阻,引脚的电平由外部电路确定。
GPIO_PULLUP:使用上拉电阻,引脚在未连接时会被拉高。
GPIO_PULLDOWN:使用下拉电阻,引脚在未连接时会被拉低。
GPIO的初始化
以正点原子的精英版跑马灯实验为例,LED的初始化代码如下
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE(); //开启GPIOB时钟
__HAL_RCC_GPIOE_CLK_ENABLE(); //开启GPIOE时钟
GPIO_Initure.Pin=GPIO_PIN_5; //PB5
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
GPIO_Initure.Pin=GPIO_PIN_5; //PE5
HAL_GPIO_Init(GPIOE,&GPIO_Initure);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET); //PB5置1,默认初始化后灯灭
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_5,GPIO_PIN_SET); //PE5置1,默认初始化后灯灭
}
分为以下几个步骤
进行IO的初始化,设置驱动的引脚模式,上下拉,以及速度
详解HAL_GPIO_Init函数
该函数在stm32f1xx_hal_gpio.c文件中定义,有两个入口参数
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
GPIO_TypeDef
一个是GPIO_TypeDef类型指针,参数传入的正是宏定义的寄存器指针,可以传入GPIOA,GPIOB....这样的宏定义,在stm32f103xe.h文件中宏定义,实际上就是在告诉HAL_GPIO_Init函数要操作的引脚组的寄存器地址
#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)
#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *)GPIOG_BASE)
其中GPIO_TypeD类型的结构体的定义如下,成员全是要操作的寄存器,排列顺序实际上是按照他们的实际物理地址的排列顺序,这样的好处是,传入GPIOA_BASE这样的基地址(首地址,该地址等于CRL寄存器地址)即可,后续HAL_GPIO_Init函数要操作CRH,IDR寄存器时就无需在传入具体地址,也无需进行地址计算,直接用GPIOx->BSRR这样的形式访问机构提成员即可,因为形参GPIOx已经被传入GPIOA_BASE这样的基地址,而C语言结构体成员的地址偏移是4字节,即C语言上如果CRL成员的地址是0x01,则CRH的地址是0X05,以此类推,而我们要设置GPIOA,给CRL传入了它的寄存器实际地址0x4001 0800,那么GPIOx->CRH的地址就会是0x4001 0804,与CRH的寄存器实际地址一致,因为寄存器的偏移也是4字节。
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
GPIO_InitTypeDef
实际上第二个参数就是GPIO_InitTypeDef类型指针,传入的正是GPIO的速度,模式属性。在该函数中会将这些属性解析出对应的值操作GPIOx结构体写入寄存器中,实现GPIO外设的初始化操作。
GPIO的置位与复位
其实就是设置输出高电平还是低电平,可以通过操作端口位设置/清除寄存器(GPIOx_BSRR)或端口位清除寄存器(GPIOx_BRR),两者的具体描述可看参考手册,一般是操作GPIOx_BSRR寄存器,在HAL库中操作HAL_GPIO_WritePin函数
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));
if (PinState != GPIO_PIN_RESET)
{
GPIOx->BSRR = GPIO_Pin;
}
else
{
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;
}
}
GPIOx:表示要使用的GPIO端口,可以是A~G中的一个,根据不同的芯片型号而有所不同。
GPIO_Pin:表示要设置/清除的GPIO引脚,可以是0~15中的一个,根据不同的芯片型号而有所不同。
PinState:表示要设置的引脚状态,可以是GPIO_PIN_RESET或GPIO_PIN_SET,分别表示清除引脚和设置引脚。 这个函数在实现时使用了GPIOx_BSRR寄存器,以确保原子性的读/修改操作,避免在修改过程中被中断打断的风险。该函数可以用于控制GPIO引脚的状态,例如控制LED灯的亮灭、控制继电器的开关等等。
举例:HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET);
GPIO的状态读取
通过操作端口输入数据寄存器(GPIOx_IDR)实现,在HAL库中可以使用
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)