观前提醒:本文详尽分析了HAL库中GPIO配置的相关函数,包括MX_GPIO_Init()、HAL_GPIO_WritePin()、HAL_GPIO_Init()。文末附f1系列GPIO口相关寄存器说明

MX_GPIO_Init()

先看源码:

void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

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

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = LED4_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LED4_GPIO_Port, &GPIO_InitStruct);

}

下面我们来逐行分析代码

void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

这里定义了一个名为 MX_GPIO_Init 的函数,没有输入参数,也没有返回值。同时创建了一个 GPIO_InitTypeDef 类型的结构体变量 GPIO_InitStruct 并初始化为零。

  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

通过调用 __HAL_RCC_GPIOD_CLK_ENABLE()__HAL_RCC_GPIOC_CLK_ENABLE()__HAL_RCC_GPIOA_CLK_ENABLE() 函数,使 GPIOD、GPIOC 和 GPIOA 端口的时钟被启用。

  HAL_GPIO_WritePin(LED4_GPIO_Port, LED4_Pin, GPIO_PIN_SET);

这行代码将 LED4_Pin 引脚的输出电平设置为高电平(GPIO_PIN_SET)。它使用了 HAL 库提供的 HAL_GPIO_WritePin 函数。

  GPIO_InitStruct.Pin = LED4_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LED4_GPIO_Port, &GPIO_InitStruct);

这几行代码配置了引脚 LED4_Pin 的模式、上下拉和速度。首先,将 LED4_Pin 赋值给 GPIO_InitStruct.Pin,然后将模式设置为输出模式(GPIO_MODE_OUTPUT_PP),无上下拉(GPIO_NOPULL),低速(GPIO_SPEED_FREQ_LOW)。最后,通过调用 HAL_GPIO_Init 函数,将配置应用到 LED4_GPIO_Port 上。

总结一下,以上代码的主要功能是初始化指定的 GPIO 引脚。它启用了相应端口的时钟,设置了一个引脚的输出电平,然后配置了该引脚的模式、上下拉和速度。这样,在调用该函数后,相关的 GPIO 引脚就被正确地初始化,可以进行后续的输入输出操作。
有聪明的小伙伴又要问了:为啥上述代码先配置IO口的电平后设置IO口的模式?
答:在初始化 IO 口之前,我们需要确保 IO 口的电平是正确的,以避免在切换为输出模式后产生意外的电平变化。如果我们先设置模式再设置电平,会导致在切换为输出模式之前,IO 口的电平发生瞬时变化,从而引起不必要的干扰或错误。

HAL_GPIO_WritePin()

Talk is cheap. Show me the code

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

这段代码是 HAL 库中的 HAL_GPIO_WritePin 函数的实现。该函数用于设置指定 GPIO 引脚的输出电平。

让我们逐行分析代码的功能和操作:

void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)

这是函数的定义,接受三个参数:GPIOx 是指向 GPIO 端口的指针,GPIO_Pin 是要设置的 GPIO 引脚,PinState 是要设置的输出电平。

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

这些代码行用于参数检查,确保传入的引脚和电平参数是有效的。assert_param() 是一个断言宏,用于在调试时检查参数的合法性。

  if (PinState != GPIO_PIN_RESET)
  {
    GPIOx->BSRR = GPIO_Pin;
  }
  else
  {
    GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;
  }

这是函数的主要逻辑。如果 PinState 不等于 GPIO_PIN_RESET,即要设置的输出电平为高电平,
那么将 GPIOx->BSRR 寄存器的对应引脚位置为 1,从而将引脚的输出电平设置为高电平。

如果 PinState 等于 GPIO_PIN_RESET,即要设置的输出电平为低电平,那么将 GPIOx->BSRR 寄存器的对应引脚位置为 1(设置引脚为低电平),同时将 GPIOx->BSRR 寄存器的高 16 位位置为 1(清除引脚为高电平),从而将引脚的输出电平设置为低电平。

通过这样的逻辑,根据 PinState 的值,可实现对 GPIO 引脚的输出电平设置。

总结:以上代码实现了 HAL_GPIO_WritePin 函数,用于设置指定 GPIO 引脚的输出电平。通过判断 PinState 的值,将对应的 GPIO 寄存器的位设置为高或低电平,实现了输出电平的设置。

HAL_GPIO_Init()

鉴于本函数过长,源码将放置在分析之后,省略了无关紧要的部分

uint32_t position = 0x00u;
  uint32_t ioposition;
  uint32_t iocurrent;
  uint32_t temp;
  uint32_t config = 0x00u;
  __IO uint32_t *configregister;
  uint32_t registeroffset;
  • position:用于追踪当前处理的引脚位置。在循环中递增,以处理下一个引脚。
  • ioposition:用于获取当前引脚的位位置。通过对 position 进行移位操作得到。
  • iocurrent:通过将 GPIO_Init->Pinioposition 进行位与操作,用于检查当前引脚是否需要初始化。如果 iocurrent 等于 ioposition,则说明当前引脚需要初始化。
  • temp:一个临时变量
  • config:用于存储引脚的配置值。根据不同的模式和参数,将相应的配置值写入该变量。
  • configregister:用于存储指向 CRLCRH 寄存器的指针,具体取决于引脚号的范围。在后续的代码中,将使用该指针来修改寄存器的值。
  • registeroffset:在计算 CNFMODE 位在 CRLCRH 寄存器中的位置时使用的偏移量。
while (((GPIO_Init->Pin) >> position) != 0x00u)
  {
    /* Get the IO position */
    ioposition = (0x01uL << position);

    /* Get the current IO position */
    iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;

    if (iocurrent == ioposition)
    {
      /* Check the Alternate function parameters */
      assert_param(IS_GPIO_AF_INSTANCE(GPIOx));

      /* Based on the required mode, filling config variable with MODEy[1:0] and CNFy[3:2] corresponding bits */
      switch (GPIO_Init->Mode)
      {
        /* If we are configuring the pin in OUTPUT push-pull mode */
        case GPIO_MODE_OUTPUT_PP:
        assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
          config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
          break;
        ....

让我们逐行分析以上代码段:

while (((GPIO_Init->Pin) >> position) != 0x00u)

这是一个循环的起始。它通过判断 (GPIO_Init->Pin) 右移 position 位后的值是否不等于 0,来确定是否继续执行循环。GPIO_Init->Pin 是一个表示引脚状态的位掩码,通过右移 position 位来检查每个引脚的状态。

ioposition = (0x01uL << position);

这行代码计算了当前引脚的位置。它使用了位运算,将 1 左移 position 位,并将结果存储在 ioposition 变量中。这样可以得到一个只有第 position 位为 1,其余位为 0 的值。

iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;

这行代码获取当前引脚的状态。它将 GPIO_Init->Pin 强制转换为 uint32_t 类型,然后与 ioposition 进行按位与运算,并将结果存储在 iocurrent 变量中。这样可以判断当前引脚的状态是否为 1。

if (iocurrent == ioposition)

这是一个条件语句,用于检查当前引脚是否需要进行初始化配置。如果当前引脚的状态等于 ioposition 的值,即当前引脚的状态为 1,则进入条件判断。

assert_param(IS_GPIO_AF_INSTANCE(GPIOx));

这是一个断言宏,用于检查 GPIOx 是否是有效的 GPIO 外设实例。如果 GPIOx 不满足条件,即不是有效的 GPIO 外设实例,则会触发断言错误。

switch (GPIO_Init->Mode)

这是一个 switch 语句,根据 GPIO_Init->Mode 的值进行多重分支判断。GPIO_Init->Mode 表示当前引脚的模式。

case GPIO_MODE_OUTPUT_PP:
 assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
          config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
          break;

这是一个分支语句,用于处理引脚配置为输出推挽模式(GPIO_MODE_OUTPUT_PP)的情况。在这个分支中,会检查GPIO的速度参数,然后根据速度参数和相应的控制寄存器配置值计算出 config 的值。

总结一下:它们共同组成了一段循环结构,用于遍历一组引脚的状态,并根据需要进行初始化配置。在每个循环迭代中,会获取当前引脚的位置和状态,并根据状态进行条件判断和配置操作。

      /* Check if the current bit belongs to first half or last half of the pin count number
       in order to address CRH or CRL register*/
      configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL     : &GPIOx->CRH;
      registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);

      /* Apply the new configuration of the pin to the register */
      MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));

这段代码主要用于将引脚的配置应用到相应的寄存器中。下面是对代码的详细分析:

首先,代码根据引脚编号 iocurrent 判断当前位是否属于引脚编号的前半部分(小于8)还是后半部分(大于等于8)。这是因为在寄存器中,引脚0到7的配置位于 Control Register Low (CRL),而引脚8到15的配置位于 Control Register High (CRH)。

代码使用三元条件运算符,将 configregister 设置为指向 CRL 或 CRH 寄存器的指针,具体取决于引脚编号 iocurrent 的范围。同样,使用三元条件运算符,将 registeroffset 设置为相应的寄存器偏移量。如果 iocurrent 小于8,说明引脚属于前半部分,寄存器偏移量为 position 左移2位;否则,引脚属于后半部分,寄存器偏移量为 (position - 8u) 左移2位。

接下来,代码使用 MODIFY_REG 宏将引脚的新配置应用到寄存器中。MODIFY_REG 宏用于修改寄存器的特定位域,其参数包括要修改的寄存器、要修改的位域以及要写入位域的新值。

在这里,(*configregister) 表达式解引用 configregister 指针,获取到要修改的寄存器。((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset) 表达式计算出要修改的位域的掩码,它将位域的配置值左移 registeroffset 位,并与 GPIO_CRL_MODE0GPIO_CRL_CNF0 的按位或结果相乘。config << registeroffset 是要写入位域的新配置值。

通过使用 MODIFY_REG 宏,代码将新的引脚配置写入到适当的寄存器位域中,以实现引脚的配置更新。

 if ((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
      {
        /* Enable AFIO Clock */
        __HAL_RCC_AFIO_CLK_ENABLE();
        temp = AFIO->EXTICR[position >> 2u];
        CLEAR_BIT(temp, (0x0Fu) << (4u * (position & 0x03u)));
        SET_BIT(temp, (GPIO_GET_INDEX(GPIOx)) << (4u * (position & 0x03u)));
        AFIO->EXTICR[position >> 2u] = temp;


        /* Enable or disable the rising trigger */
        if ((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
        {
          SET_BIT(EXTI->RTSR, iocurrent);
        }
        else
        {
          CLEAR_BIT(EXTI->RTSR, iocurrent);
        }

        /* Enable or disable the falling trigger */
        if ((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
        {
          SET_BIT(EXTI->FTSR, iocurrent);
        }
        else
        {
          CLEAR_BIT(EXTI->FTSR, iocurrent);
        }

        /* Configure the event mask */
        if ((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
        {
          SET_BIT(EXTI->EMR, iocurrent);
        }
        else
        {
          CLEAR_BIT(EXTI->EMR, iocurrent);
        }

        /* Configure the interrupt mask */
        if ((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
        {
          SET_BIT(EXTI->IMR, iocurrent);
        }
        else
        {
          CLEAR_BIT(EXTI->IMR, iocurrent);
        }
      }
    }

这段代码是用于配置GPIO外部中断的相关设置。让我们逐行分析这段代码的功能:

  1. 首先,通过判断GPIO_Init->Mode与EXTI_MODE的按位与运算结果是否等于EXTI_MODE,来确定是否需要配置外部中断。EXTI_MODE是一个宏定义,表示外部中断模式。

  2. 如果需要配置外部中断,首先使能AFIO(Alternate Function I/O)时钟。AFIO是一个外设,用于管理GPIO的复用功能。

  3. 接下来,从AFIO的EXTICR寄存器中读取当前位置对应的值。

  4. 根据GPIOx(GPIO端口号)和position(GPIO引脚号)的值,计算出需要设置的值,并将其设置到temp中。

  5. 将temp的值写入到AFIO的EXTICR寄存器中,完成对外部中断的配置。

  6. 根据GPIO_Init->Mode与RISING_EDGE的按位与运算结果,判断是否需要使能上升沿触发中断。如果需要,设置EXTI的RTSR寄存器中对应的位。

  7. 根据GPIO_Init->Mode与FALLING_EDGE的按位与运算结果,判断是否需要使能下降沿触发中断。如果需要,设置EXTI的FTSR寄存器中对应的位。

  8. 根据GPIO_Init->Mode与GPIO_MODE_EVT的按位与运算结果,判断是否需要配置事件屏蔽寄存器。如果需要,设置EXTI的EMR寄存器中对应的位。

  9. 根据GPIO_Init->Mode与GPIO_MODE_IT的按位与运算结果,判断是否需要配置中断屏蔽寄存器。如果需要,设置EXTI的IMR寄存器中对应的位。

void HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init)
{
  uint32_t position = 0x00u;
  uint32_t ioposition;
  uint32_t iocurrent;
  uint32_t temp;
  uint32_t config = 0x00u;
  __IO uint32_t *configregister; /* Store the address of CRL or CRH register based on pin number */
  uint32_t registeroffset;       /* offset used during computation of CNF and MODE bits placement inside CRL or CRH register */

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

  /* Configure the port pins */
  while (((GPIO_Init->Pin) >> position) != 0x00u)
  {
    /* Get the IO position */
    ioposition = (0x01uL << position);

    /* Get the current IO position */
    iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;

    if (iocurrent == ioposition)
    {
      /* Check the Alternate function parameters */
      assert_param(IS_GPIO_AF_INSTANCE(GPIOx));

      /* Based on the required mode, filling config variable with MODEy[1:0] and CNFy[3:2] corresponding bits */
      switch (GPIO_Init->Mode)
      {
        /* If we are configuring the pin in OUTPUT push-pull mode */
        case GPIO_MODE_OUTPUT_PP:
          /* Check the GPIO speed parameter */
          assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
          config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
          break;

        /* If we are configuring the pin in OUTPUT open-drain mode */
        case GPIO_MODE_OUTPUT_OD:
          /* Check the GPIO speed parameter */
          assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
          config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_OD;
          break;

        /* If we are configuring the pin in ALTERNATE FUNCTION push-pull mode */
        case GPIO_MODE_AF_PP:
          /* Check the GPIO speed parameter */
          assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
          config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_PP;
          break;

        /* If we are configuring the pin in ALTERNATE FUNCTION open-drain mode */
        case GPIO_MODE_AF_OD:
          /* Check the GPIO speed parameter */
          assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
          config = GPIO_Init->Speed + GPIO_CR_CNF_AF_OUTPUT_OD;
          break;

        /* If we are configuring the pin in INPUT (also applicable to EVENT and IT mode) */
        case GPIO_MODE_INPUT:
        case GPIO_MODE_IT_RISING:
        case GPIO_MODE_IT_FALLING:
        case GPIO_MODE_IT_RISING_FALLING:
        case GPIO_MODE_EVT_RISING:
        case GPIO_MODE_EVT_FALLING:
        case GPIO_MODE_EVT_RISING_FALLING:
          /* Check the GPIO pull parameter */
          assert_param(IS_GPIO_PULL(GPIO_Init->Pull));
          if (GPIO_Init->Pull == GPIO_NOPULL)
          {
            config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_FLOATING;
          }
          else if (GPIO_Init->Pull == GPIO_PULLUP)
          {
            config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD;

            /* Set the corresponding ODR bit */
            GPIOx->BSRR = ioposition;
          }
          else /* GPIO_PULLDOWN */
          {
            config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_INPUT_PU_PD;

            /* Reset the corresponding ODR bit */
            GPIOx->BRR = ioposition;
          }
          break;

        /* If we are configuring the pin in INPUT analog mode */
        case GPIO_MODE_ANALOG:
          config = GPIO_CR_MODE_INPUT + GPIO_CR_CNF_ANALOG;
          break;

        /* Parameters are checked with assert_param */
        default:
          break;
      }

      /* Check if the current bit belongs to first half or last half of the pin count number
       in order to address CRH or CRL register*/
      configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL     : &GPIOx->CRH;
      registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);

      /* Apply the new configuration of the pin to the register */
      MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));

      /*--------------------- EXTI Mode Configuration ------------------------*/
      /* Configure the External Interrupt or event for the current IO */
      if ((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)
      {
        /* Enable AFIO Clock */
        __HAL_RCC_AFIO_CLK_ENABLE();
        temp = AFIO->EXTICR[position >> 2u];
        CLEAR_BIT(temp, (0x0Fu) << (4u * (position & 0x03u)));
        SET_BIT(temp, (GPIO_GET_INDEX(GPIOx)) << (4u * (position & 0x03u)));
        AFIO->EXTICR[position >> 2u] = temp;


        /* Enable or disable the rising trigger */
        if ((GPIO_Init->Mode & RISING_EDGE) == RISING_EDGE)
        {
          SET_BIT(EXTI->RTSR, iocurrent);
        }
        else
        {
          CLEAR_BIT(EXTI->RTSR, iocurrent);
        }

        /* Enable or disable the falling trigger */
        if ((GPIO_Init->Mode & FALLING_EDGE) == FALLING_EDGE)
        {
          SET_BIT(EXTI->FTSR, iocurrent);
        }
        else
        {
          CLEAR_BIT(EXTI->FTSR, iocurrent);
        }

        /* Configure the event mask */
        if ((GPIO_Init->Mode & GPIO_MODE_EVT) == GPIO_MODE_EVT)
        {
          SET_BIT(EXTI->EMR, iocurrent);
        }
        else
        {
          CLEAR_BIT(EXTI->EMR, iocurrent);
        }

        /* Configure the interrupt mask */
        if ((GPIO_Init->Mode & GPIO_MODE_IT) == GPIO_MODE_IT)
        {
          SET_BIT(EXTI->IMR, iocurrent);
        }
        else
        {
          CLEAR_BIT(EXTI->IMR, iocurrent);
        }
      }
    }

    position++;
  }
}

GPIO相关寄存器

端口配置寄存器


端口输入数据寄存器

端口输出数据寄存器

端口位设置/清除寄存器

端口位清除寄存器

端口配置锁定寄存器