728x90
- GPIO 수동 제어 2
지난 시간에 공부했던 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_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, GPIO_PIN_SET);
/*Configure GPIO pin : GPIO_LED_Pin */ // 이부분 공부
GPIO_InitStruct.Pin = GPIO_LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIO_LED_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : GPIO_SW_Pin */
GPIO_InitStruct.Pin = GPIO_SW_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIO_SW_GPIO_Port, &GPIO_InitStruct);
}
이 부분 공부
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pin : GPIO_LED_Pin */
GPIO_InitStruct.Pin = GPIO_LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIO_LED_GPIO_Port, &GPIO_InitStruct);
GPIO_InitTypeDef GPIO_InitStruct = {0}; // GPIO_InitTypeDef는 아래처럼 구조체로 정의되어있다
typedef struct
{
uint32_t Pin;
uint32_t Mode;
uint32_t Pull;
uint32_t Speed;
} GPIO_InitTypeDef; // 각 member가 4byte간격 == 크기가 4byte
- 즉 Configure GPIO Pin 부분은 GPIO_InitTypeDef 구조체의 맴버들에 값을 대입하는 것
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pin : GPIO_LED_Pin */
GPIO_InitStruct.Pin = GPIO_LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIO_LED_GPIO_Port, &GPIO_InitStruct);
}
- GPIO_LED_PIN : PC13의 GPIO Pin
- GPIO_LED_GPIO_Port
- &GPIO_InitStruct는 구조체의 주소값
맴버들에 값을 대입한 후 HAL_GPIO_Init 함수로 넘어감
- HAL_GPIO_Init 함수의 정의 부분
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init) // GPIOx == GPIOC
{
uint32_t position = 0x00u; // 1)
uint32_t ioposition;
uint32_t iocurrent;
uint32_t temp;
uint32_t config = 0x00u;
__IO uint32_t *configregister;
uint32_t registeroffset;
assert_param(IS_GPIO_ALL_INSTANCE(GPIOx)); // 2)
assert_param(IS_GPIO_PIN(GPIO_Init->Pin));
assert_param(IS_GPIO_MODE(GPIO_Init->Mode));
while (((GPIO_Init->Pin) >> position) != 0x00u) // 3)
{
ioposition = (0x01uL << position);
iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
if (iocurrent == ioposition)
{
assert_param(IS_GPIO_AF_INSTANCE(GPIOx));
switch (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;
case GPIO_MODE_OUTPUT_OD:
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed));
config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_OD;
break;
...
...
...
}
configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH; // 4)
registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);
// 5)
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)
{
...
...
...
}
position++;
}
}
1) 변수 선언
uint32_t position = 0x00u;
uint32_t ioposition;
uint32_t iocurrent;
uint32_t temp;
uint32_t config = 0x00u;
__IO uint32_t *configregister;
uint32_t registeroffset;
2) Check the parameters
/* Check the parameters */
// 괄호 안이 0이거나 1일때에 따라 메세지를 보내려 만든 부분. 지금은 메세지를 출력하는 기능 자체가 생략되어 있음
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은 debug message를 출력. 지금은 실질적 의미가 없음
-
- GPIOx의 그룹이 A~E인지 (유효한지) 검사#define IS_GPIO_ALL_INSTANCE(INSTANCE) (((INSTANCE) == GPIOA) || \ ((INSTANCE) == GPIOB) || \ ((INSTANCE) == GPIOC) || \ ((INSTANCE) == GPIOD) || \ ((INSTANCE) == GPIOE))
-
- GPIO Pin이 유효한지 검사#define IS_GPIO_PIN(PIN) (((((uint32_t)PIN) & GPIO_PIN_MASK ) != 0x00u) && ((((uint32_t)PIN) & ~GPIO_PIN_MASK) == 0x00u))
-
- GPIO의 mode가 유효한지 검사. 현재 mode는 GPIO_MODE_OUTPUT_PP#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_MODE_INPUT) ||\ ((MODE) == GPIO_MODE_OUTPUT_PP) ||\ // 현재 GPIO_Mode ((MODE) == GPIO_MODE_OUTPUT_OD) ||\ ((MODE) == GPIO_MODE_AF_PP) ||\ ((MODE) == GPIO_MODE_AF_OD) ||\ ((MODE) == GPIO_MODE_IT_RISING) ||\ ((MODE) == GPIO_MODE_IT_FALLING) ||\ ((MODE) == GPIO_MODE_IT_RISING_FALLING) ||\ ((MODE) == GPIO_MODE_EVT_RISING) ||\ ((MODE) == GPIO_MODE_EVT_FALLING) ||\ ((MODE) == GPIO_MODE_EVT_RISING_FALLING) ||\ ((MODE) == GPIO_MODE_ANALOG))
3) while 문
while (((GPIO_Init->Pin) >> position) != 0x00u) // GPIO_Init->Pin 의 값이 0이랑 다르면 while문 실행
{
ioposition = (0x01uL << position);
iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition;
if (iocurrent == ioposition)
{
assert_param(IS_GPIO_AF_INSTANCE(GPIOx));
switch (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;
...
...
...
} // switch문이 끝나면 여기로 나옴
...
...
}
position++;
}
GPIO_InitStruct.Pin = GPIO_LED_Pin; // == 8192 (0b10 0000 0000)
-
while (((GPIO_Init->Pin) >> position) != 0x00u)
- 1을 position만큼 오른쪽으로 bitwise shift (position은 0에서 while문이 한번 돌때마다 1씩 증가 )
- 10 0000 0000 (position = 0)
- 01 0000 0000 (position = 1)
- 00 1000 0000 (position = 2)
- ......
- ......
- 00 0000 0001 (position = 13)
- 00 0000 0000 -> 이때(13번 후) while문의 조건에 맞지 않으므로 (0이므로)멈춤. (position == 14)
- Pin의 번호값만큼 while문이 돔. Pin이 몇번인지를 세기 위해 while문을 pin의 번호만큼 돌리며 1을 옆으로 옮김
- 1을 position만큼 오른쪽으로 bitwise shift (position은 0에서 while문이 한번 돌때마다 1씩 증가 )
-
ioposition = (0x01uL << position); // 1을 position만큼 왼쪽으로 bitwise shift iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition; // bitwise AND 연산 if (iocurrent == ioposition) { ..... ..... }
- GPIO_Init->Pin의 값은 8192로 고정. ioposition만 1이 왼쪽으로 한칸씩 옮겨지며 값이 바뀜
- bitwise AND 연산은 연산은 둘중 하나라도 0이면 결과값이 0
- 10 0000 0000 & 1 = 0
- 10 0000 0000 & 10 = 0
- ......
- ......
- 10 0000 0000 & 10 0000 0000 = 1000000000 == ioposition
- while문이 13번 돌아 ioposition == 1000000000 == iocurrent가 되면 아래의 if else문으로 들어갈 수 있음
- Pin이 몇번째인지를 새려는 의도
-
assert_param(IS_GPIO_AF_INSTANCE(GPIOx)); // == (void)0; 하는게 없음 /******************************* GPIO Instances *******************************/ #define IS_GPIO_ALL_INSTANCE(INSTANCE) (((INSTANCE) == GPIOA) || \ ((INSTANCE) == GPIOB) || \ ((INSTANCE) == GPIOC) || \ ((INSTANCE) == GPIOD) || \ ((INSTANCE) == GPIOE)) /**************************** GPIO Alternate Function Instances ***************/ #define IS_GPIO_AF_INSTANCE(INSTANCE) IS_GPIO_ALL_INSTANCE(INSTANCE)
- GPIOA ~ GPIOE중 소속되어있는게 있는지 (제대로 된 값인지) 검사
-
switch (GPIO_Init->Mode) { case GPIO_MODE_OUTPUT_PP: // GPIO_Init->Mode = GPIO_MODE_OUTPUT_PP 이므로 바로 여기로 들어옴 assert_param(IS_GPIO_SPEED(GPIO_Init->Speed)); // 의미 없는 statement config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP; break; ..... ..... ..... }
- main.c에서 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP 로 설정했음
- GPIO_MODE_OUTPUT_PP = 1이므로 GPIO_Init->Mode도 1
- switch문의 주 목적 : config 설정
- main.c에서 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH == 3으로 설정했었음
-
#define GPIO_CR_CNF_GP_OUTPUT_PP 0x00000000u /*!< 00: General purpose output push-pull */
- GPIO_CR_CNF_GP_OUTPUT_PP == 0. GPIO Mode가 Output Push Pull임을 의미
- config = 3 + 0 = 3;
4)
configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH;
// iocurrent(0b10 0000 0000)가 GPIO_PIN_8보다 작은가
// configregister = &GPIOx->CRH = 0x40011000
registeroffset = (iocurrent < GPIO_PIN_8) ? (position << 2u) : ((position - 8u) << 2u);
// iocurrent = 8192 > GPIO_PIN_8 이므로 (position - 8u) << 2u 로 들어감
// (13 - 8 = 5 == ob0101) << 2u = ob 0001 0100 == 20 이 registeroffset에 대입
#define GPIO_PIN_8 ((uint16_t)0x0100) /* Pin 8 selected */
// == 0b 1 0000 0000 == 256
- GPIOC의 8Pin 이상은 값을 CRH(Configuration Register High)로, 이하는 CRL(Configuration Register High) 의 주소값을 대입
- Pin0~7은 Low, Pin8~15는 High
- iocurrent > GPIO_PIN_8 = 0b1 0000 0000 이므로 configregister에는 &GPIOx -> CRH가 들어감
- GPIOC와 Configuration Register의 주소가 동일 : GPIOC 그룹의 레지스터는 Configuration Register 부터 시작함
5)
MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
- (GPIO_CRL_MODE0 | GPIO_CRL_CNF0) = 0011 | 1100 = 1111 == 15
- 15 << 20 = 15,728,640 == 0b1111 0000 0000 0000 0000 0000
- config << registeroffset == 3 << 20 = 0b1100000000000000000000 == 3145728
즉,
MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
// 는
MODIFY_REG(1145324612, 15728640, 3145728); // 와 같다
// 1145324612 == 0b100 0100 0100 0100 0100 0100 0100 0100
// 15728640 == 0b1111 0000 0000 0000 0000 0000
// 3145728 == 0b11 0000 0000 0000 0000 0000
#define MODIFY_REG(REG, CLEARMASK, SETMASK)
WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
#define READ_REG(REG) ((REG)) // register에 들어있는 값을 의미
#define WRITE_REG(REG, VAL) ((REG) = (VAL)) // == (*configregister) = VAL;
// register에 value값을 넣음
- VAL == (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)) = 0b100 0100 0000 0100 0100 0100 0100 0100
- configregister = 0x40011004에 이미 설정된 값이 있는데, 그 값중에 변화시키고 싶은 bit가 있음
- READ_REG를 통해 configregister에 있는 전체 값을 읽어옴
- *(configregister) = 1145324612 == 0b100 0100 0100 0100 0100 0100 0100 0100
- 바꾸고 싶은 bit들의 위치들만 1, 나머지는 0으로 만듦
- CLEARMASK = 15728640 == 0b1111 0000 0000 0000 0000 0000
- Bitwise NOT 연산
- ~(CLEARMASK) = ~(0b1111 0000 0000 0000 0000 0000) = 0b1111 1111 0000 1111 1111 1111 1111 1111
- 앞의 1들은 not 연산을 하기 전에 unsinged int의 쓰지 않던 (0이던) 앞 비트들
- 전체 값과 NOT 연산한 값을 Bitwise AND 연산
- ((READ_REG(REG)) & (~(CLEARMASK)) = 0b100 0100 0000 0100 0100 0100 0100 0100
- 위의 결과값에 SETMASK를 Bitwise OR 연산
- SETMASK = 0b0011 0000 0000 0000 0000 0000 == 3145728
- ((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)) = 0b100 0100 0011 0100 0100 0100 0100 0100
- WRITE_REG를 통해 결과값을 *(configregister)에 대입
- 1,144,276,036 == 0b100 0100 0011 0100 0100 0100 0100 0100
위의 과정을 bit masking 이라 한다
* Bit Masking : 정수의 2진수 표현을 활용해 집합의 원소들을 표현하는 방법
ex) {1, 4, 3} -> {1, 4} = 110, {3} = 001, {1, 3} = 101
결론 :
MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
위 코드는
*(0x40011004) = (*(0x40011004) & ~(15 << 20)) | (3 << 20);
를 의미한다. 이는
/*Configure GPIO pin : GPIO_LED_Pin */
GPIO_InitStruct.Pin = GPIO_LED_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIO_LED_GPIO_Port, &GPIO_InitStruct);
이 부분이 위 코드와 같다는 것을 의미한다
- GPIO 제어를 하려면 GPIO Port A ~ G (0x4001 0800 ~ 0x4001 23FF) 에 접근을 해야 함
- 우리가 접근한 주소는 GPIO Port C의 주소 (0x4001 1000 ~ 0x4001 13FF)
typedef struct // 위의 설계에 따라 구조체 구성을 함
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
- GPIOx = 모든 GPIO Port들이 위의 구조를 가지고 있다는 뜻
- 이번 강의에선 저 register들 중 CRH를 다룸
- GPIO 0 ~ 7은 low, 8 ~ 15는 high
- 4비트가 (MODE 2bit + CNFO 2 bit) GPIOx번을 설정함
-
configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH; // 드라이버 설계자가 위의 데이터 시트를 보고 코드로 해석을 해놓은 것
아래의 코드 또한 위의 데이터 시트를 토대로 만들어진 설정값들 -
/* Definitions for bit manipulation of CRL and CRH register */ #define GPIO_CR_MODE_INPUT 0x00000000u /*!< 00: Input mode (reset state) */ #define GPIO_CR_CNF_ANALOG 0x00000000u /*!< 00: Analog mode */ #define GPIO_CR_CNF_INPUT_FLOATING 0x00000004u /*!< 01: Floating input (reset state) */ #define GPIO_CR_CNF_INPUT_PU_PD 0x00000008u /*!< 10: Input with pull-up / pull-down */ #define GPIO_CR_CNF_GP_OUTPUT_PP 0x00000000u /*!< 00: General purpose output push-pull */ #define GPIO_CR_CNF_GP_OUTPUT_OD 0x00000004u /*!< 01: General purpose output Open-drain */ #define GPIO_CR_CNF_AF_OUTPUT_PP 0x00000008u /*!< 10: Alternate function output Push-pull */ #define GPIO_CR_CNF_AF_OUTPUT_OD 0x0000000Cu /*!< 11: Alternate function output Open-drain */ /* defgroup GPIO_speed_define GPIO speed define, brief GPIO Output Maximum frequency */ #define GPIO_SPEED_FREQ_LOW (GPIO_CRL_MODE0_1) /*!< Low speed */ #define GPIO_SPEED_FREQ_MEDIUM (GPIO_CRL_MODE0_0) /*!< Medium speed */ #define GPIO_SPEED_FREQ_HIGH (GPIO_CRL_MODE0) /*!< High speed */
- 위처럼 ARM Core의 설계 내용이 담긴 데이터 시트를 토대로 source code를 작성함
- 간단한 설정값만 넣으면 사용할 수 있게끔 다양한 변수들을 고려하며 library를 설계
- 덕분에 STM32 IDE는 설정값만 조절하면 위 같은 소스 코드들이 생성되므로 편리
- I2C, SPI등의 ARM Core를 통해 다루는 모든 것들이 위처럼 데이터 시트를 토대로 코드가 작성되어 있음
- 보통 임베디드 개발자가 새로운 기능을 제어하는 과정
- 데이터 시트를 보며 어떤 기능이 하는 역할을 공부 (이론 공부)
- 현재 사용중인 MCU에서 그 기능을 어떻게 제어하는지 공부
- 공부한 제어방법대로 소스 코드 작성
- 하지만 위 방법은 시간이 오래 걸리므로, 샘플 소스가 있다면 그 코드의 실행결과를 분석하며 역으로 데이터 시트에서 추적을 하는 방법으로 시간을 단축할 수 있음
위 내용의 모든 출처는 유튜버 '[오제이 튜브]OJ Tube' 님께 있습니다.
https://www.youtube.com/watch?v=n5yUMV3fJuM&list=PLz--ENLG_8TNjRg1OtyFBvUyV4PHaKwmu&index=10
'Study_Embedded > [오제이 튜브의 임베디드 실전 강의]' 카테고리의 다른 글
[오제이 튜브의 임베디드 실전 강의] 9강 회로도 딱 필요한 만큼만 배우자! (0) | 2022.03.03 |
---|---|
[오제이 튜브의 임베디드 실전 강의] 8강 지금까지 배운 것을 큰 그림에 저장하기 (0) | 2022.02.14 |
[오제이 튜브의 임베디드 실전 강의] 6강 GPIO제어 뿌셔 먹기 (0) | 2022.02.08 |
[오제이 튜브의 임베디드 실전 강의] 5강 혼자서 임베디드 고수 되는 법 (0) | 2022.01.24 |
[오제이 튜브의 임베디드 실전 강의] 3-1강 개발 환경 구축하기, 4강 Hello!! GPIO!! (2) | 2022.01.08 |