기초
5단원 · Keil·자료형/진수·비트연산·포인터/MMIO·함수
01. 임베디드 C와 Keil μVision 시작하기
기초 · 선수: 없음
PC에서 도는 C와 MCU에서 도는 C는 같은 언어지만 사고방식이 다릅니다. OS도 화면도 없는 칩 위에서 코드가 직접 하드웨어를 다뤄야 하죠. 임베디드 C가 무엇인지, 24편 내내 쓸 도구인 Keil μVision(C51/MDK-ARM)이 코드를 어떻게 빌드·디버그·시뮬레이션하는지, 그리고 임베디드 프로그램의 표준 골격(main + while(1))을 익힙니다. 보드가 없어도 PC의 gcc로 따라올 수 있습니다.
02. 자료형·진수·stdint·sizeof
기초 · 선수: 01강
MCU의 레지스터는 폭이 정확히 정해져 있고(8/16/32비트) RAM은 수 KB뿐입니다. "이 변수가 정확히 몇 비트인가"가 곧 레지스터에 제대로 들어가는지를 결정합니다. stdint.h 의 폭 고정 타입이 왜 임베디드의 사실상 표준인지, 10진·16진·2진 표기, sizeof, 그리고 부호 없는 값이 최댓값을 넘을 때의 랩어라운드까지 PC의 gcc 로 직접 확인합니다.
03. 비트 연산과 레지스터 조작 (set·clear·toggle)
기초 · 선수: 02강
하드웨어를 다룬다는 건 결국 레지스터의 특정 비트를 켜고·끄고·읽는 일입니다. 이때 나머지 비트는 절대 건드리면 안 됩니다(옆 비트가 다른 핀을 제어할 수 있으니까요). 비트 연산자(& | ^ ~ << >>)와 세 가지 황금 관용구 set(|=)·clear(&= ~)·toggle(^=), 그리고 test(>> &)를 PC의 gcc 로 익힙니다. 여기서 익힌 관용구는 6편 이후 8051·STM32 레지스터 제어에 그대로 쓰입니다.
04. 포인터·volatile·메모리 맵드 I/O
기초 · 선수: 03강
임베디드에서 레지스터는 곧 메모리의 특정 주소입니다(예: STM32 GPIOC->ODR 은 0x4001100C). 그 주소에 쓰면 핀이 움직이고, 읽으면 핀 상태가 들어옵니다 — 이것이 메모리 맵드 I/O(MMIO)입니다. 주소를 다루는 포인터와, 하드웨어가 멋대로 바꾸는 값을 안전하게 읽는 volatile 을 PC의 gcc 로 흉내 내며 익힙니다. (*(volatile uint32_t *)주소) 패턴은 14편 이후 STM32 제어의 토대입니다.
05. 함수·헤더 분리·#define 매크로
기초 · 선수: 04강
펌웨어는 "GPIO 다루는 부분", "UART 다루는 부분", "응용 로직" 식으로 모듈로 나눕니다. 모듈을 나누는 도구인 함수(동작 단위), 헤더(.h)/소스(.c) 분리(인터페이스/구현), 전처리기(#include·#define·헤더 가드)를 익힙니다. 비트 조작 로직을 ledbank 모듈로 떼어내고, 헤더 가드와 매크로 괄호 함정도 다룹니다. 모듈로 나누는 감각은 23편(HAL 추상화)까지 이어집니다.
8051
7단원 · SFR·GPIO·타이머·인터럽트·UART·7세그먼트
06. 8051 구조·SFR·reg52.h
8051 · 선수: 05강
1~5편의 토대 위에서 드디어 실제 칩에 올라섭니다. 첫 주인공은 임베디드 교육의 고전 8051(AT89C52)입니다. 8051의 주변장치(포트·타이머·시리얼·인터럽트)는 모두 SFR(특수 기능 레지스터)로 제어합니다. Keil C51의 <reg52.h> 가 SFR을 이름으로 미리 선언해 둬서 P1 = 0x0F; 처럼 변수에 대입하듯 하드웨어를 다룹니다. 포트에 쓰는 두 방법(전체 / sbit 비트)을 익힙니다.
07–08. GPIO 출력 — LED 점멸 · GPIO 입력 — 버튼/스위치 읽기
8051 · 선수: 06강 · 8051 · 선수: 07강
임베디드의 "Hello, World"는 LED 깜빡이기입니다. 포트에 어떤 값을 써야 LED가 켜지는지(액티브 로우/하이 배선), 얼마나 기다려야 보이는지(지연), 여러 LED로 패턴을 만드는 법(비트 마스크)까지 GPIO 출력의 핵심이 다 들어 있습니다. P1에 연결된 LED 8개로 흐르는 불빛(running light)을 만들고 Logic Analyzer로 핀 파형을 관찰합니다. 7편이 핀으로 신호를 내보냈다면 이번엔 핀으로 신호를 읽어들입니다. 8051 특유의 함정은 포트가 준쌍방향(quasi-bidirectional)이라, 입력으로 읽기 전에 그 핀에 먼저 1을 써 두어야 외부 신호를 제대로 읽는다는 점입니다. P3.2 버튼을 읽어 P1.0 LED를 제어하고, "누르는 순간마다 토글"하는 에지 검출 로직을 익힙니다. 채터링은 21편에서 다룹니다.
09–10. 타이머0/1 과 정확한 지연 · 외부·타이머 인터럽트
8051 · 선수: 08강 · 8051 · 선수: 09강
7편의 소프트웨어 지연은 부정확하고 그동안 CPU가 묶입니다. 정확한 시간은 하드웨어 타이머로 만듭니다 — CPU와 독립적으로 클럭을 세는 카운터죠. 8051의 Timer0/1을 16비트 모드(Mode1)로 설정해 정확한 지연을 만듭니다. 핵심은 TMOD로 모드 선택, TH/TL에 리로드 값(65536-카운트), TF(오버플로 플래그) 폴링입니다. 리로드 계산을 PC로 검증합니다. 폴링은 정확하지만 기다리는 동안 CPU가 묶입니다. 인터럽트는 평소 메인 코드가 제 일을 하다가 사건(버튼·타이머 오버플로)이 생기면 CPU가 잠깐 끼어들어 ISR을 실행하고 돌아옵니다. "사건이 생길 때만 반응"하는 이 방식이 임베디드의 핵심 패러다임입니다. IE 레지스터로 인터럽트를 켜고, C51의 interrupt N 문법으로 ISR을 쓰며, 공유 변수에 volatile이 왜 필요한지 익힙니다.
11–12. UART 시리얼 통신 · 7-세그먼트 표시
8051 · 선수: 10강 · 8051 · 선수: 11강
값을 "사람이 읽는 글자"로 PC와 주고받으려면 통신이 필요합니다. 가장 기본이 UART(시리얼)입니다 — 선 두 가닥(TXD/RXD)으로 텍스트를 주고받죠. UART는 비동기라 양쪽이 보율(baud)을 똑같이 약속해야 합니다. 8051은 SCON으로 모드를, Timer1로 보율을, SBUF로 한 바이트를 넣고 뺍니다. 9600bps·8N1로 문자열을 보내고, 보율 정확도를 위해 11.0592MHz를 쓰는 이유를 익힙니다. 사람이 읽을 수 있는 숫자를 표시합니다. 7-세그먼트는 막대 LED 7개(a~g)+소수점으로 0~9를 그립니다. 핵심은 ① 숫자를 "어떤 막대를 켤지"의 비트 패턴으로 바꾸는 룩업 테이블, ② 자리가 여러 개일 때 핀을 아끼며 모두 켜 보이게 하는 멀티플렉싱(동적 구동)입니다. "1234"를 한 자리씩 빠르게 번갈아 켜 잔상으로 네 자리가 동시에 켜진 것처럼 보이게 만듭니다.
STM32
8단원 · CMSIS·GPIO·SysTick·PWM·EXTI·USART·ADC
13. Cortex-M·CMSIS·클럭 트리
STM32 · 선수: 12강
8051을 졸업하고 32비트 Arm Cortex-M(STM32F103)으로 무대를 옮깁니다. CMSIS 표준 덕분에 어느 Cortex-M이든 비슷한 방식으로 레지스터에 접근합니다. 이 트랙은 HAL/SPL 없이 레지스터를 직접 제어합니다. 앞으로 7편을 떠받칠 세 토대 — Cortex-M 구조, CMSIS 헤더(stm32f10x.h), 클럭 트리(HSE→PLL→SYSCLK→AHB/APB) — 를 잡고, "주변장치를 쓰기 전에 RCC로 클럭부터 켠다"는 철칙을 익힙니다.
14–15. RCC·GPIO 레지스터로 LED · GPIO 입력·풀업/풀다운
STM32 · 선수: 13강 · STM32 · 선수: 14강
13편의 토대(CMSIS·RCC·클럭)를 처음으로 실전에 씁니다. STM32의 "Hello, World"인 LED 점멸을 HAL 없이 레지스터만으로 구현합니다. Blue Pill의 온보드 LED는 PC13에 액티브 로우로 달려 있죠. 핵심은 STM32F1 특유의 GPIO 설정 — 핀마다 4비트(MODE 2 + CNF 2)를 CRL(핀0~7)·CRH(핀8~15)에 쓰는 방식입니다. 이 4비트 조합을 이해하면 F1의 모든 GPIO 설정이 같은 패턴으로 풀립니다. 14편이 핀으로 신호를 내보냈다면 이번엔 핀으로 신호를 읽습니다. 초보자가 꼭 걸리는 함정은 플로팅 입력 — 아무것도 연결 안 된 핀은 0도 1도 아닌 떠 있는 상태라 노이즈에 흔들립니다. 이를 막는 것이 풀업/풀다운 저항이고, STM32는 내부에 내장합니다. PA0를 입력+내부 풀업으로 두고 IDR로 버튼을 읽어 PC13 LED를 제어합니다. F1은 풀업/풀다운 선택을 ODR로 한다는 특이점이 핵심입니다.
16–17. SysTick 타이머와 정확한 지연 · 범용 타이머·PWM 출력
STM32 · 선수: 15강 · STM32 · 선수: 16강
빈 루프 delay()는 시간이 부정확합니다. Cortex-M 코어에 내장된 SysTick 타이머로 해결합니다 — 모든 Cortex-M에 똑같이 들어 있는 24비트 카운터라 칩이 바뀌어도 같은 코드가 돕니다. 핵심 패턴은 "1ms마다 인터럽트 → 전역 밀리초 카운터 증가 → 그 카운터로 시간 측정"입니다. 이 한 패턴으로 정확한 지연·주기 실행·타임아웃을 모두 구현합니다. 부호 없는 뺄셈으로 래핑에 안전한 delay_ms도 익힙니다. 범용 타이머(TIM)는 채널·PWM·입력 캡처를 제공합니다. 가장 많이 쓰는 PWM은 켜진 시간의 비율(듀티)을 빠르게 조절해 LED 밝기나 모터 속도를 "아날로그처럼" 제어합니다. TIM2 채널1(PA0)로 1kHz PWM을 만들고 듀티를 조절합니다. 세 핵심 레지스터 PSC(분주)·ARR(주기)·CCR(듀티)의 관계와, APB1 타이머 클럭이 PCLK1의 2배가 되는 함정을 익힙니다.
18–20. NVIC 인터럽트와 EXTI · USART 시리얼 통신 · ADC 아날로그 입력
STM32 · 선수: 17강 · STM32 · 선수: 18강 · STM32 · 선수: 19강
버튼을 폴링으로 읽으면 CPU가 묶이고 짧은 입력을 놓칠 수 있습니다. 인터럽트는 평소 메인이 제 일을 하다가 사건이 생기면 즉시 핸들러(ISR)로 점프했다 돌아옵니다. Cortex-M에서 인터럽트를 총괄하는 NVIC와, 외부 핀 변화를 인터럽트로 바꾸는 EXTI를 다룹니다. PA0 버튼을 EXTI0 인터럽트로 받아 LED를 토글하고, ISR에서 펜딩 플래그(PR)를 반드시 클리어하는 패턴을 익힙니다. 이제 칩이 사람·PC와 텍스트로 대화합니다. UART(범용 비동기 직렬)는 디버그 메시지, 센서 모듈, 부트로더 등 어디에나 쓰입니다. USART1을 9600bps로 설정해 인사 문자열을 보내고, 받은 문자를 그대로 되돌리는 에코를 구현합니다. μVision Serial 창에서 송수신을 직접 볼 수 있어, UART는 시뮬레이터로 검증하기 가장 좋은 주변장치입니다. 지금까지 다룬 핀은 모두 디지털(0/1)이었습니다. 세상의 신호 대부분은 온도·밝기·소리처럼 연속적인 아날로그입니다. ADC(Analog-to-Digital Converter)는 아날로그 전압을 숫자로 바꿔 MCU가 다룰 수 있게 합니다. STM32F103의 12비트 ADC로 PA0의 전압을 0~4095로 읽고 밀리볼트로 환산합니다. STM32 주변장치 시리즈(13~20편)의 마지막으로, 클럭·핀·폴링이 한 번에 쓰입니다.
응용
4단원 · 디바운싱/FSM·링버퍼·HAL·종합 프로젝트
21–22. 디바운싱·유한 상태 머신 패턴 · 링버퍼와 인터럽트 기반 UART 수신
응용 · 선수: 20강 · 응용 · 선수: 21강
주변장치 사용법을 익혔으니 이제 펌웨어를 짜는 패턴으로 넘어갑니다. 거의 모든 프로젝트에 등장하는 두 가지 — 디바운싱과 유한 상태 머신(FSM)입니다. 기계식 버튼은 누르는 순간 접점이 수 ms 튀어(채터링) 한 번이 여러 번으로 읽힙니다. 디바운싱은 떨림을 걸러 진짜 입력만 남기고, FSM은 "상태+입력→다음 상태" 규칙으로 동작을 깔끔하게 표현합니다. 하드웨어 비의존 순수 로직이라 gcc로 정확히 검증합니다. UART로 데이터가 쏟아지면 수신 인터럽트가 매 바이트마다 걸립니다. ISR에서 다 처리하려 들면 ISR이 길어지고, 메인이 느리면 다음 바이트가 이전 바이트를 덮어써 유실됩니다. 해법은 ISR은 버퍼에 넣기만 하고 처리는 한가한 메인이 맡는 분업입니다. 그 핵심 자료구조가 링버퍼(원형 버퍼)입니다. 생산자(ISR)와 소비자(메인)가 서로 다른 인덱스만 건드리면 인터럽트를 끄지 않고도 안전합니다.
23–24. HAL 추상화·모듈 분리·이식성 · 종합 — UART 명령으로 LED·타이머 제어
응용 · 선수: 22강 · 응용 · 선수: 23강
레지스터 코드(GPIOC->BRR=...)가 응용 로직 곳곳에 박혀 있으면 칩을 바꿀 때 모든 곳을 고쳐야 합니다. HAL(Hardware Abstraction Layer)은 "무엇을 한다"(LED를 켠다)는 응용과 "어떻게 한다"(특정 레지스터를 쓴다)는 구현을 갈라놓습니다. 같은 app.c 를 세 HAL 구현 — PC(printf)·STM32(레지스터)·아두이노 우노(AVR ATmega328P) — 로 돌려, 아키텍처가 ARM에서 AVR로 바뀌어도 응용 코드는 한 줄도 안 바뀜을 봅니다. 마지막 캡스톤. 따로 배운 조각들 — USART(19)·인터럽트(18)·링버퍼(22)·SysTick(16)·GPIO(14)·명령 파싱/모듈 분리(22·23) — 을 하나의 펌웨어로 엮습니다. PC 터미널에서 on/off/blink/help 명령을 타이핑하면 보드가 해석해 LED와 타이머를 제어합니다. 설계 핵심은 역할 분리 — 인터럽트는 받기만, 메인은 처리만, 파서는 해석만. 각 모듈은 독립 테스트 가능하고, 명령 파서는 PC에서 검증합니다.