HAL库 - GPIO分析

本文章用于对于HAL库GPIO代码进行分析,并同时关心cubemx是如何使用hal库进行初始化的。芯片基于F429/F767两种。
前面从cubemx开始,到函数使用,再到具体的代码的分析。

cubemx设置
HAL库 - GPIO分析_第1张图片
设置比较简单:
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会为了思路清晰?

你可能感兴趣的:(HAL库)