본문 바로가기
임베디드

[오제이 튜브의 임베디드 강의] 8강. GPIO제어 고아먹기

by 덤더리덤떰 2025. 1. 7.

1. MX_GPIO_Init();

=> 7강 글에 나와있듯이 MX_GPIO_Init();에서 핵심 함수는 _HAL_RCC_GPIOC_CLK_ENABLE();이었고

=> 또 이 함수에서의 핵심 함수는 SET_BIT(RCC->APB2EENR, RCC_APB2ENR_IOPCEN);이었고

=> 또 이 함수의 실제 연산은 SET_BIT(REG,BIT) ((REG) |= (BIT))였기에 

=> 결론적으로 *(0x40021018) |= 16과 같았다. 

=> 따라서 아래 오른쪽 사진과 같이 volatile unsigned int * reg = 0x40021018; *reg |= 16;과 작성했다.

 

 

2. MX_GPIO_Init(); 내부에 있는 다른 코드를 분석해보자

: 현재 __HAL_RCC_GPIOC_CLK_ENABLE();은 위에서와 같이 분석한 상태 (PC13 제어하기에 GPIOA는 구현 X)

 

 

* HAL_GPIO_WritePin();은 이후에 나오는 코드와 동일하기에 생략

 

 

 

 

 

 

 

 

 

 

2-1. GPIO_InitTypeDef GPIO_InitStruct = {0};

 

 

  • uint32_t 이므로 unsigned int 32bit 이기에 4byte씩 공간 존재

 

 

 

 

 

 

 

 

 

2-2. 2번째 빨간 네모박스 부분

 

  • GPIO_LED_Pin(우리는 GPIO PC13을 제어하기에 PC13 핀을 의미)은 GPIO_PIN_13으로 정의되어있고, GPIO_PIN_13은 ((uint16_t)0x2000)로 정의되어있음 (이때, 0x2000 = 8192 = 1 << 13)
  • GPIO_MODE_OUTPUT_PP은 0x00000001u로 정의
  • GPIO_PULLDOWN은 0x00000002u로 정의
  • GPIO_SPEED_FREQ_HIGH은 (GPIO_CRL_MODE0)로 정의되어있고, GPIO_CRL_MODE0 은 GPIO_CRL_MODE0_Msk로, GPIO_CRL_MODE0_Msk는 (0x3UL << GPIO_CRL_MODE0_Pos)로, 이때 GPIO_CRL_MODE0_Pos는 (0U)로 정의되어있기에결론적으로 3과 같다 

결론.
GPIO_InitStruct.Pin = 1 << 13;
GPIO_InitStruct.Mode = 1;
GPIO_InitStruct.Pull = 2;
GPIO_InitStruct.Speed = 3;

+ HAL_GPIO_Init(GPIO_LED_GPIO_Port, &GPIO_InitStruct);

  • GPIO_LED_GPIO_Port는 0x40011000
  • &GPIO_InitStruct는 위에 있는 구조체의 주소값 전달 

2-3. HAL_GPIO_Init()함수에 들어가보자

 

 

  • GPIO 모임 중에 우리는 C를 볼 것
  • 이때, C모임 중 GPIO 개수는 16개 존재
  • 우리는 13이라는 번호 가진 핀 제어하고 싶음

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(1) assert 부분

: 정작 하는 일은 없지만 인자값이 0/1이면 어떤 msg를 보내는 함수

: GPIO pin이 제대로 맞는지 chk 함수 (이때, GPIO_Init->Pin)은 1을 옆으로 13번 민 LED PC13

: GPIO mode에 아까 GPIO_MODE_OUTPUT_PP을 대입했었는데 이 값이 유효한지 체크하는 함수 

 

 

 

 

 

(2) while문 ( 사진에서 긴 부분이 다 while문에 속해있음)

=> 이때 큰 부분만 봐보자

 

 

 

=> GPIO_Init으로 GPIO_InitTypeDef 구조체 주소 넘겼었음

=> 이때 이 구조체의 Pin은 GPIO_LED_Pin으로 초기화했었고 이때 GPIO_LED_Pin은 PC13을 나타내는 (1<<13)이었음

=> 이때 position은 HAL_GPIO_Init 함수 초기 부분에서 position = 0x00u로 초기화했었음 

* position은 while문 돌때마다 마지막에 position++; 통해 1씩 증가

=> while문의 전체적인 동작 방식은 

while ((1 << 13) >> position) != 0x00u){
...
position++;
}

이므로 

1000000000000
0100000000000
0010000000000
0001000000000
...
0000000000001
0000000000000 <- 이때 멈춤

1) while문 내부의 if문

 

  • ioposition = 1 << 0(현재 position : 0)
  • iocurrent = GPIO_Init->Pin(1 << 13) & 1;
  • if (iocurrent == ioposition)이 참이되려면 결국 iocurrent와 ioposition 모두 1 << 13이 되는 방법밖에 없다. 
    <=> 즉, Pin13번인지 알고 싶어서 chk하는 코드

  • ioposition은,
    1
    10
    100
    ...
  • iocurrent는
    10 0000 0000 0000 & 1(00 0000 0000 0001) = 0
    10 0000 0000 0000 & 10(00 0000 0000 0010) = 0
    10 0000 0000 0000 & 100(00 0000 0000 0100) = 0
    ...
  • 결국, ioposition과 iocurrent 모두 10 0000 0000 0000 (1 << 13)일 때 if문 만족 
  • 즉, position이 13이 될 때 ioposition이 10 0000 0000 0000이 되고 결국 iocurrent도 10 0000 0000 0000 & 10 0000 00000 0000이 되어 둘 다 10 0000 0000 0000이 된다. 
  • if문 만족하면 assert_param(IS_GPIO_AF_INSTANCE(GPIOx));가 실행되는데 이건 아무 동작도 x

2) if문 내부 switch문 

=> switch문의 목적 : config 값 설정

 

  • switch문의 GPIO_Init->Mode= GPIO_MODE_OUTPUT_PP(1);로 설정했었음
  • 따라서 첫번째 case문에 바로 걸림
  • config = GPIO_Init->Speed + GPIO_CR_CNF_GP_OUTPUT_PP;
    ⇔ config = 3 + 0 (speed는 우리가 정의했었고, GPIO_CR_CNF_GP_OUTPUT_PP는 0으로 정의되어있음)
  • break; 실행되어 switch문 나가짐

 

 

3) switch문 탈출 후 3문장(if문 내부이기에 현재 ioposition, iocurrent 모두 1 << 13인 상태 

  • configregister = (iocurrent < GPIO_PIN_8) ? &GPIOx->CRL : &GPIOx->CRH;
    => GPIO에서 8핀 이상인지 아닌지 구분하기 위한 코드
    => 0~7핀이면 configregister에 CRL부분을, 8핀 이상이면 CRH 부분을 조작하게 하는 코드 (CRL, CRH : Control register low/high)
    => (1 << 13 < 0x0100) 이므로 configregister = &GPIOx->CRH ⇔ configregister = (0x40011004); 
    *GPIOC = 0x40011000
  • registeroffset = (iocurrent < GPIO_PIN_8) ? & (position << 2u) : ((position - 8u) << 2u);
    => ( 1 << 13 < 0x0100) 이므로 registeroffset = (13 - 8) << 2; ⇔ registeroffset = 20;

  • MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));
    * GPIO_CRL_MODE0, GPIO_CRL_CNF0는 ctrl + 왼쪽 마우스로 다 찾을 수 있음
    ⇔ MODIFY_REG(*(0x40011004) , (3| 3 << 2) << 20 , (3 << 20));
    ⇔ MODIFY_REG(*(0x40011004), (3 | 12) << 20, (3 << 20));
    ⇔ *(0x40011004)에 값이 있는데 변화시키고 싶은 bit가 있어서 바꾸고자하는 코드  
    => #define MODIFY_REG(REG, CLEARMASK, SETMASK)   WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
    => #define READ_REG(REG) ((REG))
    => #define WRITE_REG(REG, VAL) ((REG) = (VAL))
    ⇔ *(0x40011004) = VAL
    ex) *(0x40011004)에 111100001이 저장되어있을 때 두 bit를 바꾸기 위해 ~001100000 = 110011111로 만들고
    111100001 & 110011111 = 110000001이 된다.
    => 이때 SETMASK = 000100000라면, 최종적으로 110000001 | 000100000 = 110100001이 만들어짐

  • WRITE_REG의 매개변수 더 자세히 분석해보자
    - READ_REG(REG) = READ_REG(*configregister) = 1145324612 = 1000100010001000100010001000100

    - CLEARMASK = (3 | 12) << 20 = ((0011) | (1100)) << 20 = 15 << 20 = 1111 0000 0000 0000 0000 0000
    - SETMASK = 3 << 20 = 0011 0000 0000 0000 0000 0000
    => READ_REG(REG) & 1111 1111 0000 1111 1111 1111 1111 1111(unsigned int라서 뒤집으면 앞 8bit 1이됨)
    = 0100 0100 0100 0100 0100 0100 0100 0100 & 1111 1111 0000 1111 1111 1111 1111 1111
    = 0100 0100 0000 0100 0100 0100 0100 0100 
    => 0100 0100 0000 0100 0100 0100 0100 0100 | SETMASK
    = 0100 0100 0000 0100 0100 0100 0100 0100 | 0000 0000 0011 0000 0000 0000 0000 0000
    = 0100 0100 0011 0100 0100 0100 0100 0100
결론.

MODIFY_REG((*configregister), ((GPIO_CRL_MODE0 | GPIO_CRL_CNF0) << registeroffset), (config << registeroffset));은
*(0x40011004) = (*(0x40011004) & ~(15UL << 20)) | (3 << 20);과 같다 

 

3. 최종코드 

 

 

 

4. 주소

 

 

  • 우리가 주소 접근한 곳은 GPIO Port C이고 이때 메뉴얼을 보면, 
    0x40011000 ~ 0x400113ff라고 되어있음 
  • GPIO를 제어하고 싶다면 A~G 주소번지를 접근해서 조작해야함
  • 이러한 설계는 ARM코어 설계자가 정한것

 

 

 

 

 

 

 

 

  • struct GPIO_TypeDef에서 CRL, CRH,...들이 __IO uint32_t로 정의된 이유를 설명해줌
  • 이때 우리는 GPIOC_CRH를 설정하였음 

 

 

 

 

 

 

 

 

  • GPIO 0부터 7까지는 CRL(Low), 8 ~ 15는 CRH(High)
  • 4비트(MODE 2bit + CNFO 2 bit)마다 GPIOx번을 설정함
  • 우리는 CNF는 output mode였기에 00(general purpose output push-pull)로 설정
  • MODE는 output mode였기에 01/10/11 중 11로 설정

5. 궁극적인 목적

- GPIO가 뭐지 ? 
: 결국 내가 프로그래밍한 소스로 인해 신호를 주면 output일 경우엔 High 전압을 내보내고, 밑으로 내리면 Low 전압을 보내게 하는 것 / input일 경우엔  high 전원이 들어오면 프로그램상에서 1인지 0인지 구분하는 게 GPIO 하는것

- 이 칩에서는 어떻게 GPIO 제어하는 거지?

: 데이터시트를 보면서 GPIO제어하고 싶으면 어떤 방법으로 제어하는지 파악

- 결국 어떻게 소스로 작성하지?

: 학습된 정보를 통해 소스코드를 만들어냄