본문 바로가기
임베디드

[오제이 튜브의 임베디드 강의] 7강. GPIO제어 부셔먹기

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

1. GPIO 옵션 설정의 의미

1-1. GPIO Output

  • GPIO output level : GPIO를 3.3V/0V로 높여서/낮춰서 출력을 할 것인지 
    => enum이므로 1씩 증가하기에 GPIO_PIN_RESET = 0, GPIO_PIN_SET = 1
    => 'High로 설정' = '내 기계가 동작할 때 3.3V로 나오는 게 최초 동작해야 할 동작이다'

GPIO output level 설정을 Low/High로 한 경우

 

  • GPIO mode
    (1) Output Push Pull
    : Push Pull 구성 회로도 방식이 BJT/ MOSFET 존재
    : 전류는 항상 높은 전압 -> 낮은 전압으로 흐른다 
    (만약 기준점이 1V라면 +3.3V이 아닌 +2.3V가 된다)


① GPIO High (3.3v)
: sw1이 닫히면 sw2는 열려있으므로 output에 3.3v가 나감
: " 3.3V가 있고 GPIO를 코드 상에서 1로(High)로 하면 sw1이 연결되면서 output으로 전류 3.3v가 흐르면서 전류가 led로 가고 led가 켜진것 "
 
② GPIO Low (0v)
: sw1이 열리고 sw2가 닫히게 되어 output에 0v가 나감 ( output에 어떠한 전압도 나가지 않음)
: " GPIO를 코드 상에서 0으로(Low)로 하면 sw2가 연결되면서 output으로 전류가 흐르지않아 led는 켜지지 않는 것 "
 

*왼쪽 사진은 BJT 회로이고 MOSFET 회로도 비슷하게 동작

(2) Output Open Drain

: 3.3V가 없고 그라운드 쪽에 스위치를 열었다 닫았다 열었다 닫았다만 함
=> sw2를 열면 output에 0으로 흘러갈 때 열려있으면 플로팅(=floating) 상태가 되어 0인지 1인지 모르는 중간 상태가 됨
=> 따라서, LED를 제어하려면 Open Drain을 사용하면 xx!! (닫아봤자 3.3v가 나가지 않고 led가 켜지지 않음)
=> LED를 키려면 Push Pull방식으로 접근
=> Open Drain도 BJT/MOSFET 회로로 나뉨

cf. floating 상태가 되는데 왜 Open Drain을 사용?

 

  • MCU에서 나오는 전압은 3.3V로 정해져있음 
    => 따라서, MCU에서 더 높은 전압을 만들기위해 별도회로 필요 
    ex) 내가 구한 칩이 3.3V가 아닌 5V에 동작하는 경우 3.3V넣어봤자 의미X
    => 왼쪽 사진처럼 Pull Up 저항 걸어놓음
    => 만약 sw2가 on이면 5v가 sw2로 흐르고 off이면 ic chip에 5v가 흐름.




    => PC13이 0이 되어야 3.3V가 0쪽으로 흐름 (LED 켜짐)  
    => 만약 PC13이 3.3V에 High로 올린다면 양쪽 모두 3.3V이기에 전류 흐르지 않음 (LED 안켜짐)
    => 따라서 아래 코드와 같이 스위치 누른 게 감지되면 Low(0)으로 만들어서 LED켜지고 
    감지가 안되면 High(1)로 만들어서 LED가 꺼짐
if(!HAL_GPIO_ReadPin(GPIO_SW_GPIO_Port, GPIO_SW_Pin)) {	
  HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, 0);	
}

else {
  HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, 1);	
}

cf. 이때, 스위치가 누른 게 감지될때 ReadPin이 0을 반환하는 이유
:  풀업저항이기에 풀업저항은 기본값이 high이므로 스위치를 닫으면 gnd로 연결되어 low로 전환(즉 0 반환)

  • Maximum output speed(논리값 변화)
    : GPIO를 High에서 Low로 떨어뜨릴 때 Low로 떨어지는데 시간이 필요한데 그 시간을 어떻게 설정?
    : High면 빨리, Low면 느리게 
    : 0v -> 3.3v, 3.3v-> 0v로 변하는 속도 

 

1-2. GPIO Input

(0) pull up/ pull down 


: 위로 끌어 당기다/ 아래로 끌어 당기다 
: input을 높은 전압을 기본으로 묶어놓기/ input을 높은 전압을 그라운드 쪽으로 묶어놓기 


cf. floating

: 디지털 신호는 High / Low
=> 0/1로 되지만 실제로 그렇지 않은 경우가 대다수
: 펄럭거리는 상황이기에 왼쪽사에선 1로 볼 수도 0으로 볼 수도 있는 상태 (확실한 전압차가 나타나지않아서)
=> 확실한 구분 위해 Pull Up 저항 사용하여 전원 앞에 붙여줌
 
 
 

(1) pull- up

pull up저항을 달아놓은 상태

: pull up저항을 전원 앞에 달아놓은 상태
=> 전류가 GPIO쪽으로 흐르기에 항상 기본값이 5V로 묶여있게 됨 
(이때, 저항은 보통 4.7k~10k)
⇔ pull up ( 기본값은 항상 읽으면 1)
 
cf. 이때 스위치를 닫으면?
: 그라운드(GND)는 0V이기에 5V와 input에서 모두 GND로 흐르면서 input pin은 0이 됨
 
 
cf. pull-up 저항이 없을 때 sw를 연결하면?

 
=> 쇼트 상태 (저항이 없기에 과전류가 흐름)
ex. 건전지 잡고 전선을 (+)에서 (-)로 바로 direct 연결하면 엄청 뜨거워지고 타버림
 
∴ 저항은 반드시 존재해야 한다.
 
 
 
 
 
cf. pull-up 저항이 없을 때 5V도 없이 스위치만 연결한다면?

 
=> 열면 항상 floating 상태
=> 닫으면 항상 0이어서 1로 만들 수 x
 
 
 
 
 
 
 

(2) pull -down

 
: pull-down 저항을 그라운드 쪽에 붙여준다

  • 만약 sw1을 열어둔다면 전류가 흐르지 않기에 MCU에서 전류가 그라운드 쪽으로 흐르기에 항상 입력값(Input)이 0이 됨
  • 만약 sw1을 닫아둔다면 ground에 갈 때 저항이 있으니까 상대적으로 저항 값이 적은 MCU Input쪽으로 5v가 흐르게 되어 1이 됨

cf. 만약 저항이 없다면?
: pull-up과 마찬가지로 쇼트 발생
 


결론.

pull - up은 높은 전압에 묶여서기본값이 1이고/ pull -down은 ground쪽에 묶여서 기본값이 0이다
(일반적으로 pull-down보다는 pull-up이 노이즈나 충격에 강하기에 많이 사용) 

 

2. Pull up/ Pull down을 왜 입력모드에서만 사용안하고 output 설정 플래그에도 존재?

: 일반적으로는 의미 없지만, GPIO mode에서 GPIO Open Drain일때만 pull up/down이 의미 있다.
: Open Drain으로 해놓으면 외부 회로와 같이 결합해서 사용할 때 사용되는 요소




 
 
 
 
 
 
 

3. HAL 드라이브 없이 GPIO 제어하기 

- MX_GPIO_Init();
- HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, 0);
- HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, 1);
 

3-1. MX_GPIO_Init();

 

  • PC13은 GPIOC CLK을 켜주어야함
    (GPIOA는 GPIO input 세팅하는 코드)

 
 
 
 
 
 
 
 
 
 
 
 
 

  • 여기서 중요한 건, SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPCEN);
    → #define SET_BIT(REG, BIT) ((REG) |= (BIT)) // OR연산을 하는 것 
    #define RCC ((RCC_TypeDef *)RCC_BASE)
결론.

SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPCEN); 은
*(0x40021018) |= 16과 같다.

 
cf. RCC_APB2ENR_IOPCEN은 16인데 이 16도 shift 연산한 것

 
즉, RCC_APB2ENR_IOPCEN = RCC_APB2ENR_IOPBEN_Msk = 0x1UL << 4U = 1을 4칸 왼쪽으로 쉬프트 연산
cf. UNUSED(); 는 디버깅할 때 사용하는 것으로 에러 메세지 출력하는데 사용될 수 있음


 

(1) 클럭의 개념

: 클럭에는 내부/외부 클럭 존재
: 모든 일을 하는데의 기준점(심장)
: 외부/내부 클럭이든 시작점은 하나이고 이때 나눠서 조정해서용
=> 이때 STM32는 외부, 내부 클럭을 가져다 쓸 수 있음 

  • 내부 클럭 : MCU 자체에서 클럭을 발생시키는 기능 존재(약간불안)
  • 외부 클럭 : 전류를 넣으면 항상 일정하게 주기적으로 심장을 뛴다(주로 이용)
외부 클럭 (크리스탈)

=> 여러 부품들이 동시에 동작할 때 필요한 클럭주기가 모두 다름 + 서로 통신할 때 각자의 연결선이 있다면 여러 부품이 동시에 통신할 때는 더욱 더 복잡함
=> 'BUS' 라는 개념 등장
 

(2) 버스

: 하나의 버스(통로)에 여러 장치 연결 
: 컨트롤러를 통해 통신할 장치 선택

 
 
 
 

  • 버스에 여러 장치가 연결되다보니 통신 장치들의 속도 차이가 생겨남
  • AHB : 빠른 장치들끼리 모아놓은 버스
  • APB : 느린 장치들끼리 모아놓은 버스 
  • 우리는 GPIOC의 클럭을 활성화하였기에 APB2 쪽에 클럭을 넣어준 것 

 
 
 

3-2. HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, GPIO_PIN_SET);

 

(1) assert_param(); 

: GPIO Pin의 유효성 검사 ( 아래 코드와 같이 실질적 동작은 하지 않지만 에러가 나면 디버거 메세지를 출력
#define assert_param(expr) ((void)0U)
#define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__))
 

(2) HAL_GPIO_WritePin();의 매개변수 

 

  • GPIO_LED_GPIO_Port
    : 0x40011000
  • GPIO_LED_Pin
    : 8192 = 13번째 bit가 1 ( 1 << 13 )
    <=> C13 포트를 제어
  • GPIO_PIN_SET 
    : 1 (enum 구조체로 RESET = 0u이므로 SET은 1)

 
 
 
 
 
 
 
 
 

 

  • PinState는 현재 1이고 GPIO_PIN_RESET은 0이므로 if문에서 true가 됨
  • GPIOx->BSRR = GPIO_Pin;
    <=> GPIOx->BSRR = 8192(1 << 13);
    <=> BS13으로 설정 
    <=> LED 13을 킨다 

cf. HAL_GPIO_WritePin의 매개변수에서 GPIO_PinState에 0을 넣으면 어떻게 될까?
: else문으로 가서 GPIOx->BSRR = (uint32_t) GPIO_Pin << 16u;이 실행됨
ex) 1111111111인 상태에서 하나를 끄고 싶으면 해당 bit를 0으로 해서 11111011111로 만들면 됨
=> but 이 데이터시트는 set과 해당 bit를 reset하는 부분이 따로 존재하기에 reset하려면 그 bit를 0으로 하는 게 아닌 reset bit를 1로 만들어야하기에 GPIO_Pin << 16을 해야함 (오른쪽 위 사진 보면 총 16개의 port bit존재)
 

4. 직접 코드 작성해보기

: 아래 코드에서 HAL 드라이버를 이용하지 않고 직접 코드 작성해보자 (PC13의 LED 깜빡이게하기)

LED를 0.1초마다 껐다켜서 깜빡이는 코드

 

HAL_GPIO_WritePin 함수

 
 
 

4-1. volatile unsigned int * reg = 0x40011010;

  • GPIO_LED_GPIO_Port : 0x40011000이고, 이때 GPIO을 SET/RESET 하려면 위 사진을 보면 알 수 있듯이, BSRR을 조작해야함
  • 레퍼런스 메뉴얼을 보면 BSRR의 offset은 0x10임
  • 최종적으로, 0x40011010에 접근해야함

4-2. *reg2 = 0x2000;

 

 

  • 현재 *(0x40011010) = 0x2000; <=> *(0x40011010) = 8192(10진수로, 즉 1<<13)
  • 13번째 bit가 1이 되어 BS13의 bit가 1이 됨
    (ODRx : GPIO 핀의 출력 상태 저장하고 제어하는 레지스터로, 출력 데이터 레지스터)
    => BS13의 의미는 ODR 레지스터의 13번째 bit를 1로 설정하여 GPIO 13번 핀을 high 상태로 만든다

 
 

 

4-3. * reg2 = (0x2000 << 16);

: 위의 사진처럼 GPIO 13번 핀을 reset하려면 13번째 bit가 아닌 29번째 bit를 1로 설정해야하므로 0x2000 << 16을 설정
 

5. 최종 코드 

(왼) HAL 드라이버 이용 (오) 직접 코드작성