본문 바로가기

Study_Embedded/[오제이 튜브의 임베디드 실전 강의]

[오제이 튜브의 임베디드 실전 강의] 6강 GPIO제어 뿌셔 먹기

728x90
반응형

 

0. Cube IDE의 IOC 화면에서 각 GPIO 설정값들의 의미

Cube IDE의 GPIO Configuration 창

 

1) GPIO output level (High / Low)

  • 최초 동작시의 GPIO Output값 설정
  • High 설정 시

pin output level 설정에 GPIO_PIN_SET이 생성

 

  • Low 설정 시

GPIO_PIN_RESET이 생성

 

enum으로 정의된 GPIO Pin 설정값

  • GPIO_PIN_RESET = 0
  • GPIO_PIN_SET = 1

 

2) GPIO mode (Output Push Pull / Output Open Drain) 

  • 회로의 방식을 설정
  • Push Pull : MCU에서 제공하는 3.3V를 통해 장치를 제어함
  • Open Drain : 단순히 Ground를 열고 닫음

 

A. Push - Pull

기본적인 Push-Pull 방식의 회로

  • Push - Pull 방식의 기본적인 회로도
  • sw1이 연결되면 3.3V가 연결되며 Output에 High가 출력
  • sw2가 연결되면 Ground(0V)가 연결되어 Low가 출력 (어떠한 전압도 출력 X)
    • 출력으로 내보낼 수 있는 전압의 최대치는 IC 내부의 Vcc 전압 (위 회로의 경우 3.3V) 
    • 이 Vcc 전압을 통해 장치를 제어함 (Ex) LED 제어)
  • 전압은 항상 높은 곳에서 낮은 곳으로 흐름

 

BJT, MOSFET 2가지 경우의 회로

  • Push - Pull은 BJT, MOSFET 2가지 종류가 있음

 

B. Open Drain

기본적인 Open Drain 회로(왼쪽) 및 2가지 종류의 Open Drain 회로

  • Open Drain : 스위치를 연결시키면 GND와 연결되어 Low 출력
  • 스위치가 Open되어있으면 Output pin은 동작 상태가 0인지 1인지 모르는 Floating 상태가 됨
    • Floating : 디지털 신호는 0, 1로 구분되지만, 정전기, 잡음등에 의해 신호의 상태를 명확히 알 수 없는 경우
    • 확실한 전압 차이가 나지 않아 전류가 흐르는지 여부를 알 수 없음
    • LED를 제어하기엔 부적합한 형태

 

- Open Drain 사용 이유

하나의 저항을 풀업 또는 풀다운 저항으로 사용

  • 보통 MCU의 출력 전압은 정해져 있음 (Ex) 3.3V)
  • 따라서 더 높은 전압을 만들려면 별도의 회로가 필요 -> 풀업 저항을 사용하여 전압을 높임
    • Ex) 5V에서 동작하는 장치에 전원 공급
    • 별도로 5V의 Pull Up Resistor 연결
  • sw2 연결 시 전류가 Ground로 흐름
  • sw2 open 시 전류가 IC로 흐름

 

- Pull-up/Pull-down 사용 구별법

위처럼 GND쪽에 저항이 없으면 Pull-up 사용

  • 스위치가 닫히면 GPIO Pin에 1이 흘러야 함
  • PA0에 내부적으로 풀업 회로(풀업 저항)가 구성되어 있음 -> 풀업이 되어있어야 올바르게 동작
    • 풀업은 전압이 위에 묶여있어서 약간의 튀는 전압은 견딤

 

- 강의 사용 회로도 구성

  • 우리는 0V, Vcc값 둘 다를 사용해야 하는 상황이므로 Push Pull 사용LED를 켜기위해 제어하는 GPIO
  • 전류는 항상 높은 전압 -> 낮은 전압으로 흐름  
  • PC13이 0으로 떨어져야 3.3V가 PC13쪽으로 흘러 LED가 켜짐
  • PC13을 3.3V로 올리면 양 끝단의 전압이 동일해져 LED가 안켜짐

 

  • LED 제어 부분 코드
  • Low(0) 인가시 LED가 켜지고 High(1) 인가 시 꺼짐

 

  • Pull-up / Pull-down Resistor : Floating 현상을 해결하기 위해 사용

Pull-up resistor : 전원쪽에 저항을 연결 (보통 4.7k ~ 10k 사용)

스위치가 열려있을 시 Input pin에는 high가 , 닫혀있을 시 low가 흐름

 

  • Pull-down resistor : GND쪽에 저항을 연결

 

스위치가 열려있을시 전류가 GND쪽으로 흘러 Input pin은 low, 닫혀있으면 5V가 흐름

 

  • 일반적으로 풀다운보다는 풀업이 노이즈나 충격에 강해서 풀업을 많이 사용

 

- BJT와 MOSFET의 차이

  • BJT (Bipolar Junction Transistor) : 단자 중 하나에 작은 전류를 주입하여 두개의 다른 단자 사이에 흐르는 훨씬 더 큰 전류를 제어할 수 있도록 하여 장치를 증폭하거나 전환함
    • 전류로 전류를 제어. 속도가 빠름, 전류 용량이 큼
  • MOSFET (Metal Oxide Semiconductor Field Effect Transistor) : 전압으로 전류를 제어
    • 임력 임피던스가 큼, 온도에 덜 예민함, 제조가 간편 : IC 제조에 용이, 동작 해석이 단순함
  • Push Pull은 2개의 스위치로 MCU의 Vcc값을 넣느냐 빼느냐를 조절 (Output : 0, 1)
  • Open Drain은 1개의 스위치로 GND를 열었다 닫았다 함 (Output : 0, Open==Floating 상태)

 

if(!HAL_GPIO_ReadPin(GPIO_SW_GPIO_Port, GPIO_SW_Pin)) {	// 스위치 누른게 감지되면
  HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, 0);	
  // PC13의 GPIO Output을 Low로 만듦 -> LED 켜짐 (회로도상)
}

else {
  HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, 1);	
  // ""을 High로 만듦 -> LED 꺼짐
}

 

 

3) GPIO Pull-up/Pull-down (No pull-up and pull-down / Pull-up / Pull-down)

  • 주로 입력모드 (GPIO Input)에서 사용하나 Open Drain 회로일 때는 Output에서도 사용
  • Open Drain은 외부의 전압을 이용해 제어하는 형태 : 외부 회로와 결합해서 사용할 때 사용하는 요소
    • Pull-up : 전압을 위로 끌어 당겨 묶어놓음 -> 높은 전압을 기준으로 묶어놓음
    • Pull-down: 전압을 아래로 끌어 당겨 묶어놓음 -> GND 쪽으로 묶어놓음

 

4) Maximum output speed (Low / Midium / High)

  • GPIO를 High <-> Low State 로 바꿀 때 올라가거나 떨어지는 시간이 필요 (Rise, Fall time) 
    • 그 시간을 조절
  • 즉 논리값 변화에 소요되는 시간을 설정 (0 -> 3.3V,  3.3V -> 0V로 변하는 속도) 

- 사용하는 경우

  • 외부 장치와 GPIO를 통해 IC2, SPI 통신을 할 경우 그 장치와 속도를 맞춰야 할 때 사용
  • High / Low를 바꾼다고 크게 달라지는 부분이 있지는 않음 : 크게 중요 X

 

- 보드 전원 연결시 주의사항

  • 보드의 동작 전압은 4V 미만
  • Power supply, STM32-USB Slave와 Extended power supply에는 5V를 넣으면 3.3V로 변환시켜서 MPU에 전원을 공급하나 GPIO Connector에는 5V를 넣으면 칩이 전압을 버티지 못하고 타버림
  • 보통의 USB 포트는 5V를 공급하므로 주의할 것

 

위 그림들과 같은 곳에는 5V를 넣으면 3.3V로 변해서 나옴. 왼쪽 그림은 USB가 꽂히는 곳
이곳에 5V를 직접 넣으면 보드가 탐

 

 

- GPIO 제어

int main(void) 
{
  HAL_Init();
    
  SystemClock_Config();

  MX_GPIO_Init();
 
  while (1) { 
  	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 1);
  	HAL_Delay(100);			// 0.1ms term
  	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 0);
  	HAL_Delay(100);	
  }
}

 

D2 위치의 LED를 0.1초 간격으로 끄고 키는 위 코드를 HAL Drive를 쓰지 않고 제어하는 것이 목표

 

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

  /* GPIO Ports Clock Enable */		// GPIO 포트의 클럭을 인에이블 하는 부분
  __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_PULLDOWN;
  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);

}
  • MX_GPIO_Init( )의 정의 부분

1) GPIO Clock 제어

  • GPIO Ports Clock Enable 는 GPIO 포트의 클럭을 인에이블 하는 부분
    • D2 LED 제어를 위해선 PC13 제어 필요, PC13 제어를 위해서는 GPIOC CLK를 Enable 시켜야 함  
    • 위 내용은 데이터 시트를 보면 알 수 있음

 

#define __HAL_RCC_GPIOC_CLK_ENABLE()   do { 
  __IO uint32_t tmpreg;  // 의미 없는 부분
  SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPCEN);	
  
  /* Delay after an RCC peripheral clock enabling */
  tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPCEN);
  // 결과를 읽는 부분
  
  UNUSED(tmpreg); 
} 
while(0U)
#define UNUSED(X) (void)X      /* To avoid gcc/g++ warnings */
  • UNUSED 함수엔 아무것도 없음 : 경고가 뜨지 않게 하기 위해 사용하는 함수
  • 지금은 tmpreg 관련 코드는 의미가 없음.

 

SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPCEN);
  • __HAL_RCC_GPIOC_CLK_ENABLE( ) 에서 분석해야 할 가장 중요한 코드
  • RCC->APB2ENR을RCC_APB2ENR_IOPCEN으로 설정함

그럼 SET_BIT는 뭔가?

#define SET_BIT(REG, BIT)  ((REG) |= (BIT))  // OR 연산
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPCEN);

 

SET_BIT의 paramater들의 주소값과 값들

즉 위의 코드는

*(0x40021018) |= 16

을 의미한다

#define RCC_APB2ENR_IOPCEN_Pos  (4U)                              
#define RCC_APB2ENR_IOPCEN_Msk  (0x1UL << RCC_APB2ENR_IOPCEN_Pos)  //!< 0x00000010 
#define RCC_APB2ENR_IOPCEN  RCC_APB2ENR_IOPCEN_Msk  // !< I/O port C clock enable
  • RCC_APB2ENR_IOPCEN 는 1을 옆으로 4칸 밀어(bitwise shift) 16이 됨

 

결론 : 

__HAL_RCC_GPIOC_CLK_ENABLE(); 	// 는
*(0x40021018) |= 16 		// 와 같은 의미
    
__HAL_RCC_GPIOA_CLK_ENABLE();	// 는 GPIO Input 설정이므로 생략 (지금은 GPIO Output만)

 

그럼 Clock은 뭔가?

  • Clock : 모든 일을 함에 있어 기준점 (내부/외부 클럭이 있음)
    • 내부 clock은 MCU 자체에서 생성. 조금 불안함
    • 보통은 외부 clock을 많이 씀 ( 크리스탈이나 오실레이터를 이용하여 외부 clock 생성. 지금 사용하는 칩에서는 크리스탈 이용)

 

크리스탈은 4~16MHz 주파수의 Clock 생성

 

  • 크리스탈(XTAL)은 전류를 넣으면 항상 일정한 clock 신호를 생성함. MCU내부의 clock 은 8~16MHz  
    • 크리스탈과 오실레이터의 차이
      • 오실레이터는 발진회로가 있어 주변 회로가 간단하지만 주파수를 조정할 수 없음
      • 크리스탈은 발진 회로가 필요한 대신 오실레이터보다 저렴하고 주파수 조정이 가능함
      • 크리스탈은 발진회로가 없어 X-TAL + 74HC04 로 회로를 설계해야 하지만 MCU에는 내부 회로가 있기 때문에 X-TAL 만 있어도 됨
  • 클럭을 기준으로 동작(통신, 연산 등)을 함 -> 컴퓨터가 돌아가는 기준. 중요 컴퓨터의 심장과 같다
  • 심장은 하나 : 외부를 쓰든 내부를 쓰든 하나만 씀
  • 주변 기기와 통신시 장치들마다 필요한 clock이 다름 -> 빠른 장치와 느린 장치끼리 모아놓은 기준이 있음 
    • 두 기기가 서로 통신시 선을 연결함
    • 서로 동시에 데이터를 보낼 시 겹치지 않도록 각자 데이터를 보내는 2개의 선을 놓음 (Full - Duplex)
    • 위의 방식은 장치가 많아지면 연결해야 하는 선이 많아져 복잡함. Bus 방식 사용
  • Clock 활성화 시 전류를 사용
    • USB 아답타를 통해 상시 전원을 공급할 시에는 clock 사용 갯수에 따른 전기 사용량의 차이가 무의미함
    • 하지만 건전지로 전원을 공급할 때는 Sleep 모드로 사용하지 않는 clock을 비활성화 해서 전류를 아낌

 

MCU에서 사용 가능한 모드. 출처 :  https://elec4.co.kr/article/articleView.asp?idx=2317

  • 일반적인 동작시엔 Run mode 사용
    • 동작 처리가 완료된 다음 외부 이벤트 발생 시까지 대기 시간이 긴 경우 Sleep mode 사용. CPU와 주변장치의 clock을 비활성화 하여 전류를 아낌
    • 통신하는 데이터 용량이 크거나, 다른 기능들의 동작은 유지하며 CPU 종료시 발생하는 짧은 대기시간에 Idle mode 사용
  • Bus
    • 하나의 큰 통로를 만들어 각 장치들을 연결함
    • 컨트롤러를 통해 통신할 장치를 선택함
    • 장치간 통신 속도가 다르므로 속도에 따라 AHB와 APB로 구분
      1. AHB (Advanced High-performance Bus) : 빠른 버스. DMA, RCC, Flash Memoey I/F, OTG등
      2. APB (Advanced Peripheal Bus) : 느린 버스. 본 칩에서는 APB1, 2로 나뉨. APB1의 공급 clock은 약칭 PCLK1, APB2의 공급 clock은 약칭 PCLK2 

 

레퍼런스 메뉴얼 중 System architecture 부분. 각 bus의 구성을 알 수 있다

  • GPIOC는 APB2에 속함 : APB2에 clock을 입력
  • STM32의 Clock Source들
    1. HSE (High Speed External) : 외부에서 입력되는 높은 주파수의 clock. PPL을 거쳐 System clock으로 입력됨. 크리스달이나 오실레이터와 같은 소자를 사용하여 생성
    2. HSI (High Speed Internal) : STM32에 내장되어 있는 RC발진 회로로, 전원 인가시 최초로 동작하는 clock
    3. LSE (Low Speed Internal) : RTC가 사용하는 clock
      • RTC (Real Time Clock) : 현재의 시간을 유지시키는 컴퓨터 시계로, 기기의 전원이 차단되어도 캐패시터나 배터리 등을 통해 시간을 측정하고 전원이 인가되면 MCU가 RTC와 통신하여 현재 시간을 알 수 있게 됨. 시간을 측정하는 역할을 따로 수행하므로 MCU의 전력 소비를 줄여줌
    4. LSI (Low Speed Internal) : 저전력으로 동작하는 내부 clock으로 IWDG나 RTC에 사용
      • IWDG (Independent Watchdog) : LSI의 clock을 사용하는 timer. HSI나 HSE clock에 문제가 발생하여도 독립적으로 동작할 수 있다
      • Watchdog는 CPU가 올바르게 작동하지 않을 시 강제로 시스템을 Reset시키는 기능을 의미한다

 

<STM32CubeMX Guide In Korean> 중 Clock 설명부분 https://www.st.com/content/dam/kms/Contents/Reflibrary/Getting_started_with_STM32_MCU_hardware_development.pdf

 

STM32 IDE의 IOC중 Clock Configuration 부분

  • 위 사진은 내부 clock을 사용하고 있는 상황으로, HSI에서 시작한 internal clock을 System Clock Mux에서 Prescaler를 통해 속도를 조절함
    • Prescaler : 타이머에 공급하는 입력 클럭의 속도를 조절하는 분주기
  • 위처럼 클럭은 버스의 통신을 위한 기준 뿐만 아니라 타이머 등 다른 용도로도 쓰임

 

2) HAL_GPIO_WritePin 코드 분석

/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, GPIO_PIN_SET);

위 코드 분석

void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
  /* Check the parameters */
  assert_param(IS_GPIO_PIN(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;
  }
}
  • assert_param 은 debug message를 출력함
#define assert_param(expr) ((void)0U)

 

HAL_GPIO_WritePin 함수의 각 parameter들의 값. GPIO_PIN_SET == 1

HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 1);
HAL_GPIO_WritePin(0x40011000, 8192, 1);

 

 

GPIOx_BSRR

 

  • 8192 == 0b1000000000000 ==  (1 << 13) : PC13 (C13 포트)제어
  • 위 레지스터의(GPIOx_BSRR) BS13에 1을 입력하여 C13 포트를 Set

 

// 즉, HAL_GPIO_WritePin의 if else문은
if (PinState != GPIO_PIN_RESET) {
  GPIOx->BSRR = GPIO_Pin;
}
else {
  GPIOx->BSRR = (uint32_t)GPIO_Pin << 16u;
}

// 아래의 if else문과 같다
if (PinState != GPIO_PIN_RESET)	{	// GPIO_PIN_RESET = 0, 현재 PinState는 1
    *(0x40011000) = 8192;		// LED13을 킴
  }
  else }				 // PinState가 0일 때
    *(0x40011000) = (8192 << 16u);	// LED13을 끔
  }
  • 일반적으로는  제어할 port 하나만 0과 1을 바꿔가며 제어함
    • ex) 11111111 -> 11110111 (3번만 끔)
  • 하지만 위에 사진처럼 GPIOx_BSRR은
    • 0 ~ 15는 Portx Set bit,
    • 16 ~ 31은 Portx Reset bit
  • 이므로 13에 있는 숫자 1을 16칸 bitwise 이동시키면 29로 가서 C13포트를 Reset 시킴

 

- 코드 작성

  • 이번 강의 목표인 HAL Drive 없이 D2 LED를 0.1초 간격으로 끄고 키는 코드

 

int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
 
  while (1) {  
    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 1);
  	HAL_Delay(100);			// 0.1ms term
  	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 0);
  	HAL_Delay(100);
  }
}
  • HAL Drive 사용시의 코드

 

 

int main(void) {
  // HAL_Init();
  SystemClock_Config();
  // MX_GPIO_Init();
  volatile unsigned int *reg = 0x40021018; // *(0x40021018) |= 16 == __HAL_RCC_GPIOC_CLK_ENABLE()
  *reg |= 16;	  // clock 설정. D2 LED 제어를 위해 필요한 PC13 제어를 위한 GPIOC CLK를 Enable
 
  // MX_GPIO_Init의 Configure GPIO pin Output Level 부분 : 다음시간에 공부
  GPIO_InitTypeDef GPIO_InitStruct = {0};		
  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);

  volatile unsigned int *reg2 = 0x40011010;	 // *(0x40011000) 을 선언한 것 (GPIOC)
  while (1) {
    *reg2 = 0x2000;	// HEX, 10진수로 8192. GPIOC를 High 만듦
    // == HAL_GPIO_WritePin(GPIO_LED_GPIO_Port, GPIO_LED_Pin, GPIO_PIN_SET)
    HAL_Delay(100);
      
    *reg2 = (0x2000 << 16);	// GPIOC를 Low로 떨어뜨림
    HAL_Delay(100);
  }
}
  • HAL Drive를 사용하지 않을 때의 코드

 

PC13이 High가 되면 LED 양단이 다 High가 되어 전류가 흐르지 않아 LED가 켜지지 않음

 

  • 위의 코드 실행시 D2 LED가 계속 깜빡깜빡 거림
코드 실행 결과

 

 

 

위와 같이 HAL Drive 를 사용하지 않고 코드만으로 제어하는 법을 익혀두면 다른 통신방식 (SPI, I2C)에서도 제어를 할 수 있고 STM 외의 다른 MCU를 쓸 때도 도움이 됨

 

 

 

 

 

 


위 내용의 모든 출처는 유튜버 '[오제이 튜브]OJ Tube' 님께 있습니다.

https://www.youtube.com/watch?v=EepQaYWIEMM&t=2429s 

 

728x90
반응형