0. Cube IDE의 IOC 화면에서 각 GPIO 설정값들의 의미
1) GPIO output level (High / Low)
- 최초 동작시의 GPIO Output값 설정
- High 설정 시
- Low 설정 시
- 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 방식의 기본적인 회로도
- sw1이 연결되면 3.3V가 연결되며 Output에 High가 출력
- sw2가 연결되면 Ground(0V)가 연결되어 Low가 출력 (어떠한 전압도 출력 X)
- 출력으로 내보낼 수 있는 전압의 최대치는 IC 내부의 Vcc 전압 (위 회로의 경우 3.3V)
- 이 Vcc 전압을 통해 장치를 제어함 (Ex) LED 제어)
- 전압은 항상 높은 곳에서 낮은 곳으로 흐름
- Push - Pull은 BJT, MOSFET 2가지 종류가 있음
B. 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 사용 구별법
- 스위치가 닫히면 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 사용)


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


- 일반적으로 풀다운보다는 풀업이 노이즈나 충격에 강해서 풀업을 많이 사용
- 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를 공급하므로 주의할 것
- 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);
즉 위의 코드는
*(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 생성. 지금 사용하는 칩에서는 크리스탈 이용)
- 크리스탈(XTAL)은 전류를 넣으면 항상 일정한 clock 신호를 생성함. MCU내부의 clock 은 8~16MHz
- 크리스탈과 오실레이터의 차이
- 오실레이터는 발진회로가 있어 주변 회로가 간단하지만 주파수를 조정할 수 없음
- 크리스탈은 발진 회로가 필요한 대신 오실레이터보다 저렴하고 주파수 조정이 가능함
- 크리스탈은 발진회로가 없어 X-TAL + 74HC04 로 회로를 설계해야 하지만 MCU에는 내부 회로가 있기 때문에 X-TAL 만 있어도 됨
- 크리스탈과 오실레이터의 차이
- 클럭을 기준으로 동작(통신, 연산 등)을 함 -> 컴퓨터가 돌아가는 기준. 중요 컴퓨터의 심장과 같다
- 심장은 하나 : 외부를 쓰든 내부를 쓰든 하나만 씀
- 주변 기기와 통신시 장치들마다 필요한 clock이 다름 -> 빠른 장치와 느린 장치끼리 모아놓은 기준이 있음
- 두 기기가 서로 통신시 선을 연결함
- 서로 동시에 데이터를 보낼 시 겹치지 않도록 각자 데이터를 보내는 2개의 선을 놓음 (Full - Duplex)
- 위의 방식은 장치가 많아지면 연결해야 하는 선이 많아져 복잡함. Bus 방식 사용
- Clock 활성화 시 전류를 사용
- USB 아답타를 통해 상시 전원을 공급할 시에는 clock 사용 갯수에 따른 전기 사용량의 차이가 무의미함
- 하지만 건전지로 전원을 공급할 때는 Sleep 모드로 사용하지 않는 clock을 비활성화 해서 전류를 아낌
- 일반적인 동작시엔 Run mode 사용
- 동작 처리가 완료된 다음 외부 이벤트 발생 시까지 대기 시간이 긴 경우 Sleep mode 사용. CPU와 주변장치의 clock을 비활성화 하여 전류를 아낌
- 통신하는 데이터 용량이 크거나, 다른 기능들의 동작은 유지하며 CPU 종료시 발생하는 짧은 대기시간에 Idle mode 사용
- Bus
- 하나의 큰 통로를 만들어 각 장치들을 연결함
- 컨트롤러를 통해 통신할 장치를 선택함
- 장치간 통신 속도가 다르므로 속도에 따라 AHB와 APB로 구분
- AHB (Advanced High-performance Bus) : 빠른 버스. DMA, RCC, Flash Memoey I/F, OTG등
- APB (Advanced Peripheal Bus) : 느린 버스. 본 칩에서는 APB1, 2로 나뉨. APB1의 공급 clock은 약칭 PCLK1, APB2의 공급 clock은 약칭 PCLK2
- GPIOC는 APB2에 속함 : APB2에 clock을 입력
- STM32의 Clock Source들
- HSE (High Speed External) : 외부에서 입력되는 높은 주파수의 clock. PPL을 거쳐 System clock으로 입력됨. 크리스달이나 오실레이터와 같은 소자를 사용하여 생성
- HSI (High Speed Internal) : STM32에 내장되어 있는 RC발진 회로로, 전원 인가시 최초로 동작하는 clock
- LSE (Low Speed Internal) : RTC가 사용하는 clock
- RTC (Real Time Clock) : 현재의 시간을 유지시키는 컴퓨터 시계로, 기기의 전원이 차단되어도 캐패시터나 배터리 등을 통해 시간을 측정하고 전원이 인가되면 MCU가 RTC와 통신하여 현재 시간을 알 수 있게 됨. 시간을 측정하는 역할을 따로 수행하므로 MCU의 전력 소비를 줄여줌
- LSI (Low Speed Internal) : 저전력으로 동작하는 내부 clock으로 IWDG나 RTC에 사용
- IWDG (Independent Watchdog) : LSI의 clock을 사용하는 timer. HSI나 HSE clock에 문제가 발생하여도 독립적으로 동작할 수 있다
- Watchdog는 CPU가 올바르게 작동하지 않을 시 강제로 시스템을 Reset시키는 기능을 의미한다
- 위 사진은 내부 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(GPIO_LED_GPIO_Port, GPIO_LED_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, 1);
HAL_GPIO_WritePin(0x40011000, 8192, 1);
- 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를 사용하지 않을 때의 코드
- 위의 코드 실행시 D2 LED가 계속 깜빡깜빡 거림
위와 같이 HAL Drive 를 사용하지 않고 코드만으로 제어하는 법을 익혀두면 다른 통신방식 (SPI, I2C)에서도 제어를 할 수 있고 STM 외의 다른 MCU를 쓸 때도 도움이 됨
위 내용의 모든 출처는 유튜버 '[오제이 튜브]OJ Tube' 님께 있습니다.
https://www.youtube.com/watch?v=EepQaYWIEMM&t=2429s
'Study_Embedded > [오제이 튜브의 임베디드 실전 강의]' 카테고리의 다른 글
[오제이 튜브의 임베디드 실전 강의] 8강 지금까지 배운 것을 큰 그림에 저장하기 (0) | 2022.02.14 |
---|---|
[오제이 튜브의 임베디드 실전 강의] 7강 GPIO제어 고아 먹기 (0) | 2022.02.11 |
[오제이 튜브의 임베디드 실전 강의] 5강 혼자서 임베디드 고수 되는 법 (0) | 2022.01.24 |
[오제이 튜브의 임베디드 실전 강의] 3-1강 개발 환경 구축하기, 4강 Hello!! GPIO!! (2) | 2022.01.08 |
[오제이 튜브의 임베디드 실전 강의] 3강 전기 기본 상식 - 모르면 보드 태워먹는다 (0) | 2021.12.02 |