STM32/이론

STM32 GPIO제어하기OUTPUT_3편(MX_GPIO_Init함수 분석)

원원 2023. 10. 22. 21:03

안녕하세요. 오늘은 NUCLEO-F103RB 보드로 HAL라이브러리를 사용하여 GPIO OUTPUT를 제어해보겠습니다.
3편에서는 OUTPUT을 설정할때 사용하는 코드를 분석해보겠습니다

우선 GPIO의 설정을 하는 MX_GPIO_Init(); 함수입니다

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

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

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);

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

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

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

}

 

typedef struct
{
  uint32_t Pin;       /*!< Specifies the GPIO pins to be configured.
                           This parameter can be any value of @ref GPIO_pins_define */

  uint32_t Mode;      /*!< Specifies the operating mode for the selected pins.
                           This parameter can be a value of @ref GPIO_mode_define */

  uint32_t Pull;      /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
                           This parameter can be a value of @ref GPIO_pull_define */

  uint32_t Speed;     /*!< Specifies the speed for the selected pins.
                           This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;

GPIO_InitTypeDef구조체가 나오는데 2편에 설정했던 GPIO MODE, PULL, SPEED를 설정하는 구조체입니다


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

GPIO 클럭을 Enable시키는 매크로입니다. 알아보고있는 GPIO는 PA5번이니  __HAL_RCC_GPIOA_CLK_ENABLE();매크로를 보겠습니다

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

1) do while(0)을 사용해서, 코드가 무조건 한번만 실행하는 구조입니다.
2) __IO는 volatile입니다. 
3) SET_BIT(REG,BIT)는 ((REG) |= (BIT))입니다. 
(RCC->APB2ENR) |=  (RCC_APB2ENR_IOPAEN)이고 RCC_APB2ENR_IOPAEN는 4입니다
(RCC->APB2ENR) |= 4 입니다

결론적으로 SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);\ 는 IOPAEN 레지스터를 SET시키는거고 IOPAEN레지스터는 port A clock enable기능을합니다. 

5) READ_BIT(REG,BIT)는 ((REG) & (BIT))입니다.
READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN)이고  RCC_APB2ENR_IOPAEN는 4입니다
(RCC->APB2ENR) &= 4 입니다.
결론적으로 이전에 SET했던 RCC->APB2ENR의 2번레지스터를 읽어서 tmpreg에 저장합니다

6) UNUSED(tmpreg);는 UNUSED(X) (void)X이고 지금 코드에서는 tmpreg가 사용하지 않으므로 경고가떠서 경고를 방지하는 목적의 함수입니다


/*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);

GPIO의 상태를 지정하는 코드입니다. 

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

 

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

assert_params은 assert_param(expr) ((void)0U) 입니다. 매크로의 내용이 0이여서 의미가 없는 매크로인데 이러한 매크로를 수정해서 parameters를 체크하라는 의미인거같습니다

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

GPIO_PIN_SET(HIGH) / GPIO_PIN_RESET(LOW)설정에 따라서 HIGH / LOW설정하는 코드입니다.
위의 코드에서 GPIOx는 GPIOA 이고, GPIO_PIN_5는 0x0020입니다.

GPIO_PIN_RESET일때 BS5를 1로 만들고 GPIO_PIN_SET일때 BR5을 1로 만듭니다.
그러므로 설명을보면 ODR 비트를 1 or 0로 설정합니다

실제로 PA5가 HIGH/LOW될때마다 ODR레지스터의 5번째비트가 1/0으로 변합니다


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

첫줄에서 GPIO_InitStruct을 선언했고, 이 구조체에 정보들을 넣습니다
GPIO_InitStruct.Pin : LD2_Pin (0b100000, 32)
GPIO_InitStruct.Mode : GPIO_MODE_OUTPUT_PP (1)
GPIO_InitStruct.Pull : GPIO_NOPULL (0)
GPIO_InitStruct.Speed : GPIO_SPEED_FREQ_LOW (2)
이제 PA5 포트설정을 하기위해서 HAL_GPIO_Init함수를 호출합니다.

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;


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

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

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

	position++;
  }
}

 

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

(((GPIO_Init->PIN) >> position) != 0x00u)은  PIN검사를 완료했는지 검사하는 코드입니다. 
position =0 일때 
ioposition = 0x01 << position = 0x01
iocurrent = 0b100000 & ioposition = 0x00
if(iocurrent ==ioposition)이 만족안합니다

position = 5일때
ioposition = 0x01 << 5 = 0b100000
iocurrent = 0b100000 & 0b100000 = 0b100000
if(iocurrent ==ioposition)을 만족합니다

      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;

config = GPIO_SPEED_FREQ_LOW +GPIO_CR_CNF_GP_OUTPUT_PP = 2+0 = 2

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

 

위에서 구한값을 총 정리하면 다음과 같습니다
position = 5
ioposition = 0b100000 = 32
iocurrent = 0b100000 = 32
config = 2

GPIO_PIN_8은 0x0100입니다. 그래서 iocurrent<GPIO_PIN_8은 TRUE입니다.
configregister = &GPIOx->CRL
GPIOx->CRL과 GPIOx->CRH가 있습니다. CRL은 configuration register low를 의미하고 0~7번 포트 설정을 의미합니다
CRH은 configuration register high을 의미하고 8~15번 포트 설정을 의미합니다. 저희가 설정하고있는게 PA5포트이므로 CRL가 맞습니다.

GPIOx_CRL레지스터는 speed와 pull을 설정합니다.PA5이므로 20~23번 비트를 설정하면 됩니다

registeroffset = (position << 2u)= 0b00011000  = 20


/* Apply the new configuration of the pin to the register */
      MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
이제 MODIFY_REG에서 위에있는 CRL레지스터를 수정할거같습니다

#define MODIFY_REG(REG, CLEARMASK, SETMASK)  WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
#define WRITE_REG(REG, VAL)   ((REG) = (VAL))
#define READ_REG(REG)         ((REG))

REG = (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK))
REG = REG & (~(CLEARMASK))) | (SETMASK))

*configregister =  *configregister & (~ ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset)) | config << registeroffset))
위의 코드를 해석하면 CRL에 있는 20~23번 비트를 clear하고나서, 설정한값으로 20~23번비트를 설정하는겁니다.
1) 20~23번비트를 clear하는 코드입니다. REG & (~ ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset))
(~(15 <<registeroffset)) = 0b000011111111111111111111
REG & 0b000011111111111111111111을 하면 20~23번 bit가 clear됩니다.


2)비트값을 설정하는 부분은 config<<registeroffset이고 0b001000000000000000000000 입니다
그러므로 (REG &0b000011111111111111111111) | 0b001000000000000000000000 입니다

 

      if ((GPIO_Init->Mode & EXTI_MODE) == EXTI_MODE)

외부인터럽트 설정하는 부분입니다. 지금은 output설정하는거이므로 if조건에 만족하지않습니다
(EXTI_MODE은 0x10000000u이고, 현재 GPIO_Init->Mode가 1임)


다음에는 입력에 대해 알아보겠습니다