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 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저항을 전원 앞에 달아놓은 상태
=> 전류가 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 깜빡이게하기)


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. 최종 코드


'임베디드' 카테고리의 다른 글
[오제이 튜브의 임베디드 강의] 9강. 지금까지 배운 것을 큰 그림에 저장하기 (0) | 2025.01.09 |
---|---|
[오제이 튜브의 임베디드 강의] 8강. GPIO제어 고아먹기 (0) | 2025.01.07 |
[오제이 튜브의 임베디드 강의] 6강. 혼자서 임베디드 고수 되는 방법 (1) | 2025.01.06 |
[오제이 튜브의 임베디드 강의] 5강. 환경 구축해보기 (1) | 2025.01.02 |
[오제이 튜브 임베디드 강의] 4강. 전기 기본 상식 (1) | 2025.01.01 |