안녕하세요. 오늘은 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임)
다음에는 입력에 대해 알아보겠습니다
'STM32 > 이론' 카테고리의 다른 글
STM32 외부인터럽트 알아보기 (0) | 2024.11.09 |
---|---|
STM32 PWM TIMER 알아보기 (0) | 2023.12.31 |
STM32 GPIO제어하기 INPUT MODE (1) | 2023.11.18 |
STM32 GPIO제어하기OUTPUT_2편(컨피규레이션 설정) (1) | 2023.10.14 |
STM32 GPIO제어하기 OTUPUT_1편(LED깜빡이기) (0) | 2023.10.02 |