深入学习STM32 HAL库中GPIO初始化函数MX_GPIO_Init()的梳理和分析(二)

目录

一、GPIO外设时钟初始化

二、配置GPIO

2.1 配置 GPIO_InitTypeDef结构体成员变量

 2.2 把参数写到对应寄存器

2.2.1 io口的配置

2.2.2 外部中断的配置

三、相关知识分析

3.1 hal_gpio其他函数简单分析

3.1.1 HAL_GPIO_DeInit();

3.1.2  HAL_GPIO_ReadPin();

3.1.3  HAL_GPIO_WritePin();

3.1.4  HAL_GPIO_TogglePin();

3.1.5  HAL_GPIO_LockPin();

3.1.6 HAL_GPIO_EXTI_IRQHandler()

 3.1.7 HAL_GPIO_EXTI_Callback();

 3.2 剩余寄存器了解

 四、总结

本人使用的单片机stm32f407vg,代码来源stm32CubeMx。

GPIO配置如下:

 其中PB16是普通的开漏输出io口;PD16是外部中断输入;

代码如下:

void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = LED1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : PD6 */
  GPIO_InitStruct.Pin = GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI9_5_IRQn, 2, 0);
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

}

 有两个关于NVIC的初始化,下次再写。

一、GPIO外设时钟初始化

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

#define __HAL_RCC_GPIOC_CLK_ENABLE()  do { \
                                 __IO uint32_t tmpreg = 0x00U; \
                                 SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOCEN);\
                                  /* Delay after an RCC peripheral clock enabling */ \
                                 tmpreg = READ_BIT(RCC->AHB1ENR,RCC_AHB1ENR_GPIOCEN);\
                                 UNUSED(tmpreg); \
                                          } while(0U)

#define RCC_AHB1ENR_GPIOCEN_Pos       (2U)                                
#define RCC_AHB1ENR_GPIOCEN_Msk      (0x1UL <<RCC_AHB1ENR_GPIOCEN_Pos)  #define RCC_AHB1ENR_GPIOCEN               RCC_AHB1ENR_GPIOCEN_Msk 

也就是给RCC_AHB1ENR第2位赋1,使能GPIOC时钟;

这里初始化了其他几个GPIO因为RCC和系统烧录会用到。

二、配置GPIO

2.1 配置 GPIO_InitTypeDef结构体成员变量

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = LED1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : PD6 */
  GPIO_InitStruct.Pin = GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
这里配置了4个成员,分别是pin即第几位;

Mode,GPIO的工作模式,常用有开漏、推挽输出,输入,模拟,复用开漏、推挽,中断上升沿触发、下降沿触发、上下都触发,事件上升沿触发、下降沿触发、上下都触发;

Pull,配置io口上拉、下拉、无上拉和下拉;GPIO的速度(低\中\高\很高)。

 2.2 把参数写到对应寄存器

通过函数HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);

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++)
  {
    /* 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 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;
       }

      /* 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;

      /* 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;
      }

      /* 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;

      /*--------------------- EXTI Mode Configuration ------------------------*/
      /* Configure the External Interrupt or event for the current IO */
      if((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
      {
        /* 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;
      }
    }
  }
}

 首先是for循环,position 可以看成第几位,是整形变量;ioposition,iocurrent可以看成验证的。循环时position一直+,当1左移po2.2sition位和欲操作的位相同时,进行后面的操作。

2.2.1 io口的配置

首先要判断配置的功能是什么,如果不是开漏、推挽输出、开漏推挽复用就不进行后面操作。

        /* 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;

#define GPIO_OSPEEDER_OSPEEDR0           GPIO_OSPEEDR_OSPEED0

#define GPIO_OSPEEDR_OSPEED0_Pos   (0U)                                  
#define GPIO_OSPEEDR_OSPEED0_Msk  (0x3UL <<GPIO_OSPEEDR_OSPEED0_Pos) 
#define GPIO_OSPEEDR_OSPEED0           GPIO_OSPEEDR_OSPEED0_Msk  

#define  GPIO_SPEED_FREQ_LOW                 0x00000000U  
#define  GPIO_SPEED_FREQ_MEDIUM          0x00000001U  
#define  GPIO_SPEED_FREQ_HIGH                0x00000002U  
#define  GPIO_SPEED_FREQ_VERY_HIGH    0x00000003U 

首先要把GPIO_OSPEEDR寄存器里的值读出来;

再与等于后面一堆,就是把position*2和position*2+1两位置0(和0相与等于0);

或等于就是把想写入的值写进去(和0相或等于本身)

最后把这一个io口配置速度写入GPIOx_OSPEEDR;

         /* 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;

#define GPIO_OTYPER_OT_0                 GPIO_OTYPER_OT0

#define GPIO_OTYPER_OT0_Pos              (0U)                                  
#define GPIO_OTYPER_OT0_Msk              (0x1UL << GPIO_OTYPER_OT0_Pos)      

                                                                                        /*!< 0x00000001 */
#define GPIO_OTYPER_OT0                  GPIO_OTYPER_OT0_Msk

#define GPIO_OUTPUT_TYPE      0x00000010U

#define  GPIO_MODE_OUTPUT_PP                    0x00000001U  
#define  GPIO_MODE_OUTPUT_OD                    0x00000011U

#define  GPIO_MODE_AF_PP                        0x00000002U   
#define  GPIO_MODE_AF_OD                        0x00000012U

这儿用了GPIO_OUTPUT_TYPE主要是把普通io和复用区分开;

代码的逻辑和写入速度是一样的,把数据写入到GPIOx_OTYPER寄存器里。

       /* 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;

#define GPIO_PUPDR_PUPDR0                GPIO_PUPDR_PUPD0

#define GPIO_PUPDR_PUPD0_Pos             (0U)                                  
#define GPIO_PUPDR_PUPD0_Msk             (0x3UL << GPIO_PUPDR_PUPD0_Pos)                                                                                                        /*!< 0x00000003 */
#define GPIO_PUPDR_PUPD0                 GPIO_PUPDR_PUPD0_Msk

#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 

上下拉或者不上下拉,主要是把参数写入到GPIOx_PUPDR里

2.2.2 外部中断的配置

/*--------------------- EXTI Mode Configuration ------------------------*/
      /* Configure the External Interrupt or event for the current IO */
      if((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
      {
        /* 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_InitStruct.Mode = GPIO_MODE_IT_RISING;

#define  GPIO_MODE_IT_RISING                    0x10110000U   
#define  GPIO_MODE_IT_FALLING                   0x10210000U   
#define  GPIO_MODE_IT_RISING_FALLING            0x10310000U

#define EXTI_MODE             0x10000000U

先是判断配置的GPIO模式是不是中断的,再进行后面操作;

通过EXTI_MOOD和GPIO_Init.Mode,相与判断是否等于EXTI_MOOD来判断;

 先是使能系统配置控制器时钟;

        /* Enable SYSCFG Clock */
        __HAL_RCC_SYSCFG_CLK_ENABLE();

#define __HAL_RCC_SYSCFG_CLK_ENABLE()   do { \
                             __IO uint32_t tmpreg = 0x00U; \
                             SET_BIT(RCC->APB2ENR, RCC_APB2ENR_SYSCFGEN);\
                              /* Delay after an RCC peripheral clock enabling */ \
                             tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_SYSCFGEN);\
                             UNUSED(tmpreg); \
                                          } while(0U)

#define RCC_APB2ENR_SYSCFGEN_Pos           (14U)                               
#define RCC_APB2ENR_SYSCFGEN_Msk(0x1UL << RCC_APB2ENR_SYSCFGEN_Pos)                                                                                  /*!< 0x00004000 */
#define RCC_APB2ENR_SYSCFGEN               RCC_APB2ENR_SYSCFGEN_Msk 

这儿是对RCC_APB2ENR第14位置1,即使能系统配置控制器时钟;

 这儿解释的不是很好,半天没看懂为什么要使能这个时钟,而又查询f103的资料,发现其RCC_APB2ENR寄存器中没有SYSCFGEN位。

后来又翻阅英文资料如下

 就是在休眠模式下使能(关闭)这个时钟。猜测和GPIO外部中断有关,使能没毛病。

         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;

#define LED1_GPIO_Port GPIOB

#define GPIO_GET_INDEX(__GPIOx__)    (uint8_t)(((__GPIOx__) == (GPIOA))? 0U :\
                                               ((__GPIOx__) == (GPIOB))? 1U :\
                                               ((__GPIOx__) == (GPIOC))? 2U :\
                                               ((__GPIOx__) == (GPIOD))? 3U :\
                                               ((__GPIOx__) == (GPIOE))? 4U :\
                                               ((__GPIOx__) == (GPIOF))? 5U :\
                                               ((__GPIOx__) == (GPIOG))? 6U :\
                                               ((__GPIOx__) == (GPIOH))? 7U : 8U)

这儿第一个position右移两位就是除以4,把16位分为了4组,每组由一个寄存器控制。

寄存器的作用是为16个外部中断线选择中断源,例子是exti6。

        /* 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;

 #define  GPIO_MODE_IT_RISING                    0x10110000U   
#define  GPIO_MODE_IT_FALLING                   0x10210000U   
#define  GPIO_MODE_IT_RISING_FALLING            0x10310000U 

#define GPIO_MODE_IT          0x00010000U

iocurrent的值是1左移6位的整形值。

EXTI_IMR寄存器控制是否开放某条线上的中断。事件的配置和中断一样。

        /* 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;

#define  GPIO_MODE_IT_RISING                    0x10110000U   
#define  GPIO_MODE_IT_FALLING                   0x10210000U  
#define  GPIO_MODE_IT_RISING_FALLING            0x10310000U  

#define RISING_EDGE           0x00100000U
#define FALLING_EDGE          0x00200000U

逻辑和前面一样,给EXTI_RTSR和EXTI_FTSR寄存器对应位赋值。

 值得注意的点是,上升下降沿不能有毛刺信号;

如果在给寄存器赋值时产生了上升下降沿,中断挂起位不会置位,即不会提示产生中断;

可以设置上升沿下降沿都触发,写入结构体的值就是两者相或。

三、相关知识分析

3.1 hal_gpio其他函数简单分析

3.1.1 HAL_GPIO_DeInit();

void HAL_GPIO_DeInit(GPIO_TypeDef  *GPIOx, uint32_t GPIO_Pin)

作用和HAL_GPIO_Init相反,即取消初始化。

完成逻辑是,把每个成员的默认值作为结构体成员,赋值给对应寄存器。但是相比初始化,不用保护原寄存器的值,所以简单很多。

3.1.2  HAL_GPIO_ReadPin();

GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  GPIO_PinState bitstatus;

  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  if((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET)
  {
    bitstatus = GPIO_PIN_SET;
  }
  else
  {
    bitstatus = GPIO_PIN_RESET;
  }
  return bitstatus;
}

先访问寄存器得到的数和GPIO_Pin相与,得到的数就是给GPIO输入的数据(也可以理解为引脚的电平),相与后的值不一定为1,所以用if语句判断时条件是不等于0;

不等于0时表示引脚为高电平,等于0时表示引脚为低电平。

3.1.3  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;
  }
}

给GPIO某一位写入值比较简单,直接给GPIOx_BSRR写就行了。

高16位是清零,低16位是置1;

3.1.4  HAL_GPIO_TogglePin();

void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  if ((GPIOx->ODR & GPIO_Pin) == GPIO_Pin)
  {
    GPIOx->BSRR = (uint32_t)GPIO_Pin << GPIO_NUMBER;
  }
  else
  {
    GPIOx->BSRR = GPIO_Pin;
  }
}

此函数的作用是反转引脚的电平。即若此时引脚为低电平则会输出高电平,反之亦然。

实现是通过读取GPIO_ODR寄存器,即读取输出的数据,再取反输出就可以了,输出的方法是给BSRR赋值。

3.1.5  HAL_GPIO_LockPin();

HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
  __IO uint32_t tmp = GPIO_LCKR_LCKK;

  /* Check the parameters */
  assert_param(IS_GPIO_PIN(GPIO_Pin));

  /* Apply lock key write sequence */
  tmp |= GPIO_Pin;
  /* Set LCKx bit(s): LCKK='1' + LCK[15-0] */
  GPIOx->LCKR = tmp;
  /* Reset LCKx bit(s): LCKK='0' + LCK[15-0] */
  GPIOx->LCKR = GPIO_Pin;
  /* Set LCKx bit(s): LCKK='1' + LCK[15-0] */
  GPIOx->LCKR = tmp;
  /* Read LCKR register. This read is mandatory to complete key lock sequence */
  tmp = GPIOx->LCKR;

  /* Read again in order to confirm lock is active */
 if((GPIOx->LCKR & GPIO_LCKR_LCKK) != RESET)
  {
    return HAL_OK;
  }
  else
  {
    return HAL_ERROR;
  }
}

#define GPIO_LCKR_LCKK_Pos               (16U)                                 
#define GPIO_LCKR_LCKK_Msk               (0x1UL << GPIO_LCKR_LCKK_Pos)        

                                                                                         /*!< 0x00010000 */
#define GPIO_LCKR_LCKK                   GPIO_LCKR_LCKK_Msk 

作用是锁定GPIO某一位,实现方法是给GPIOx_LCKR赋值,直接看手册:

 所以在锁定时有一个键写序列,不能有错误,锁定后只有cpu复位时才能恢复。

3.1.6 HAL_GPIO_EXTI_IRQHandler()

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }

void EXTI9_5_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI9_5_IRQn 0 */

  /* USER CODE END EXTI9_5_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
  /* USER CODE BEGIN EXTI9_5_IRQn 1 */

  /* USER CODE END EXTI9_5_IRQn 1 */
}

#define __HAL_GPIO_EXTI_CLEAR_IT(__EXTI_LINE__) (EXTI->PR = (__EXTI_LINE__)

使用hal库时,stm32f4xx.it.c里面调用了这个函数,函数形参应该是区分的作用,if判断不同的实参对应不同的语句。可以不用管它,有中断回调函数。

中断的作用是,发生中断时,会调用这个函数,先调用EXTI_PR寄存器判断是否产生了中断,调用中断回调函数,清除中断标志位。

 3.1.7 HAL_GPIO_EXTI_Callback();

 __weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(GPIO_Pin);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_GPIO_EXTI_Callback could be implemented in the user file
   */
}

中断回调函数,使用中断时可以只使用这一个,中断处理函数不动,把回调函数放到主函数下面就可以了。回调的原理就是中断处理函数中调用了回调函数。

回调函数直接写自己想要的效果就可以了。

 3.2 剩余寄存器了解

 GPIO的复用功能,后面使用其他外设时会使用。

 四、总结

GPIO是单片机中的基础了,hal库的方法和库函数差不多,都是定义结构体再通过函数写入到对应的寄存器中。和之前一样,通过梳理了一遍GPIO初始化函数的逻辑,我对可以使用的GPIO函数更加熟悉,知其然而知其所以然,以后使用hal库或者标准库会更顺手。

物联沃分享整理
物联沃-IOTWORD物联网 » 深入学习STM32 HAL库中GPIO初始化函数MX_GPIO_Init()的梳理和分析(二)

发表评论