本文章用于对于HAL库GPIO代码进行分析,并同时关心cubemx是如何使用hal库进行初始化的。芯片基于F429/F767两种。
前面从cubemx开始,到函数使用,再到具体的代码的分析。
cubemx设置
设置比较简单:
GPIO mode:GPIO的工作模式,比如开漏输出,推挽输出,输入等。
GPIO Pull-up/Pull-down:上下拉选择
Max output speed:最大切换速度
User Label:用户标签,这个主要用于在main.h中产生相应的宏名,以及在GPIO拓扑图中显示引脚名称
在引脚拓扑图中我们能直接选择引脚相应的功能,一般来说,使用引脚复用功能的话,GPIO会自动初始化配置(不一定是对的,例如LCDT数据引脚速度模式是LOW,但是主要的模式不会有问题),使用输入、输出的话就需要根据实质去配置了。一般就是上下拉,切换速度,初始电平等。
中断事件引脚:如果要使用外部中断的话,也很方便,选择中断/事件,并设置好触发即可。各中断线如下:
EXTI线0-15:对应外部IO口的输入中断
EXTI线16:连接到PVD输出
EXTI线17:连接到RTC闹钟事件
EXTI线18: 连接到USB OTG FS 唤醒事件
EXTI线19:连接到以太网唤醒事件
EXTI线20:连接到USB OTG HS唤醒事件
EXTI线21:连接到RTC 入侵和时间戳事件
EXTI线22:连接到RTC唤醒事件
EXTI线23:连接到LPTIMI异步时间
使用中断,请记得打开中断。
HAL库API使用
HAL库API有配置函数和使用函数两种。cubemx会自动调用HAL_GPIO_Init来初始化。而使用者一般只需要使用函数即可。
使用函数如下:
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); //引脚中断回调函数
这些函数使用很简单。基本参考注释接口快捷使用。另外如果在cubemx对引脚使用了用户标签,可以搭配main.h中的标签定义使用。这里注意下中断。在c文件中,HAL_GPIO_EXTI_Callback回调函数使用了__weak修饰,这意味着用户可以方便的重定义HAL_GPIO_EXTI_Callback函数。
一些额外的使用说明:
1.HAL_EnableCompensationCell: 再电源电压为2.4V-3.6V时可以使用这个IO补偿函数。这个函数可以用于降低IO端口噪声对电源的影响。
2.GPIO翻转:最快只需要两个时钟周期(AHB),GPIO读取:1个时钟周期(AHB)
3.GPIO速度:最好选择满足即可,不要开到最大。
4.未使用引脚:连接一个固定电平,对于防止静电来说,要么引脚接地,要么定义推挽输出,强制输出低电平。
代码分析环节
只对个人认为有价值的代码部分进行分析
HAL_GPIO_MODULE_ENABLED: 打开头文件首先看到这个宏定义,这个宏定义了整个GPIO模块是否使用,这个定义再config中定义,可以看出来cubemx产生config文件,再生成工程中也是通过config文件往工程中添加指定的文件。
// HAL_GPIO_Init代码
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
{
uint32_t position; // 比较严格的变量定义
uint32_t ioposition = 0x00U;
uint32_t iocurrent = 0x00U;
uint32_t temp = 0x00U;
/* Check the parameters */ // 检查参数
assert_param(IS_GPIO_ALL_INSTANCE(GPIOx));
assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
assert_param(IS_GPIO_PULL(GPIO_Init->Pull));
/* Configure the port pins */
for(position = 0U; position < GPIO_NUMBER; position++)
{ //一个一个IO的检查,如果那个IO口设置为1则有效,这意味着可以同时配置多个相同的IO口
/* Get the IO position */
ioposition = 0x01U << position;
/* Get the current IO position */
iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
if(iocurrent == ioposition)
{
/*--------------------- GPIO Mode Configuration ------------------------*/
/* In case of Alternate function mode selection */ // 复用配置
if((GPIO_Init->Mode == GPIO_MODE_AF_PP) || (GPIO_Init->Mode == GPIO_MODE_AF_OD))
{
/* Check the Alternate function parameter */
assert_param(IS_GPIO_AF(GPIO_Init->Alternate));
/* Configure Alternate function mapped with the current IO */
temp = GPIOx->AFR[position >> 3U];
temp &= ~(0xFU << ((uint32_t)(position & 0x07U) * 4U)) ;
temp |= ((uint32_t)(GPIO_Init->Alternate) << (((uint32_t)position & 0x07U) * 4U));
GPIOx->AFR[position >> 3U] = temp;
} //可以看出只复用配置的AFR寄存器,这意味着复用也需要配置IO端口的电路设置
/* Configure IO Direction mode (Input, Output, Alternate or Analog) */
temp = GPIOx->MODER;
temp &= ~(GPIO_MODER_MODER0 << (position * 2U));
temp |= ((GPIO_Init->Mode & GPIO_MODE) << (position * 2U));
GPIOx->MODER = temp; //这里对mode进行了更改,但是使用了中间变量
/* In case of Output or Alternate function mode selection */
if((GPIO_Init->Mode == GPIO_MODE_OUTPUT_PP) || (GPIO_Init->Mode == GPIO_MODE_AF_PP) ||
(GPIO_Init->Mode == GPIO_MODE_OUTPUT_OD) || (GPIO_Init->Mode == GPIO_MODE_AF_OD))
{
/* Check the Speed parameter */
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
/* Configure the IO Speed */
temp = GPIOx->OSPEEDR;
temp &= ~(GPIO_OSPEEDER_OSPEEDR0 << (position * 2U));
temp |= (GPIO_Init->Speed << (position * 2U));
GPIOx->OSPEEDR = temp;
/* Configure the IO Output Type */
temp = GPIOx->OTYPER;
temp &= ~(GPIO_OTYPER_OT_0 << position) ;
temp |= (((GPIO_Init->Mode & GPIO_OUTPUT_TYPE) >> 4U) << position);
GPIOx->OTYPER = temp;
} // 根据参数配置mode和IO速率
/* Activate the Pull-up or Pull down resistor for the current IO */
temp = GPIOx->PUPDR;
temp &= ~(GPIO_PUPDR_PUPDR0 << (position * 2U));
temp |= ((GPIO_Init->Pull) << (position * 2U));
GPIOx->PUPDR = temp; // 根据参数配置上下拉
/*--------------------- EXTI Mode Configuration ------------------------*/
/* Configure the External Interrupt or event for the current IO */
if((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE) // 可以看到中断与引脚的寄存器无关。以下没有一个隶属于GPIO的寄存器。
{ //我们可以理解为中断有个开关允许IO连入的感觉,这意味着IO跟正常的输入设置一样,操作函数也是可以使用的,仅仅将信号输给了中断
/* Enable SYSCFG Clock */
__HAL_RCC_SYSCFG_CLK_ENABLE();
temp = SYSCFG->EXTICR[position >> 2U];
temp &= ~(0x0FU << (4U * (position & 0x03U)));
temp |= ((uint32_t)(GPIO_GET_INDEX(GPIOx)) << (4U * (position & 0x03U)));
SYSCFG->EXTICR[position >> 2U] = temp;
/* Clear EXTI line configuration */
temp = EXTI->IMR;
temp &= ~((uint32_t)iocurrent);
if((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
{
temp |= iocurrent;
}
EXTI->IMR = temp;
temp = EXTI->EMR;
temp &= ~((uint32_t)iocurrent);
if((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
{
temp |= iocurrent;
}
EXTI->EMR = temp;
/* Clear Rising Falling edge configuration */
temp = EXTI->RTSR;
temp &= ~((uint32_t)iocurrent);
if((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
{
temp |= iocurrent;
}
EXTI->RTSR = temp;
temp = EXTI->FTSR;
temp &= ~((uint32_t)iocurrent);
if((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
{
temp |= iocurrent;
}
EXTI->FTSR = temp;
}
}
}
}
GPIO_DeInit:
这个代码没啥好分析的主要就是参数归零。(先复位中断,再复位GPIO寄存器)
GPIO_ReadPin:
读IDR寄存器。
GPIO_WritePin:
通过BSRR寄存器改变IO状态,注意不是ODR寄存器。
GPIO_LockPin:
用于锁定寄存器的配置,锁定后除了复位否则不可解锁,谨慎操作。
HAL_GPIO_EXTI_IRQHandler:中断,会被中断向量表中的中断函数调用,内容是清理中断标识并调用回调。
HAL_GPIO_EXTI_Callback: 中断回调,给用户折腾的。
总结:
仔细看了一圈,代码实现还是蛮优雅的。就是不理解有些地方 使用 * 2U和 *4U而不使用 << 1 和 << 2会为了思路清晰?