24. 종합 — UART 명령으로 LED·타이머 제어
마지막 캡스톤. 따로 배운 조각들 — USART(19)·인터럽트(18)·링버퍼(22)·SysTick(16)·GPIO(14)·명령 파싱/모듈 분리(22·23) — 을 하나의 펌웨어로 엮습니다. PC 터미널에서 on/off/blink/help 명령을 타이핑하면 보드가 해석해 LED와 타이머를 제어합니다. 설계 핵심은 역할 분리 — 인터럽트는 받기만, 메인은 처리만, 파서는 해석만. 각 모듈은 독립 테스트 가능하고, 명령 파서는 PC에서 검증합니다.
이 강의에서 배우는 것
- 1여러 주변장치·모듈을 하나의 펌웨어로 통합한다
- 2인터럽트(수신)와 메인(처리)의 역할을 분리한다(생산자/소비자)
- 3링버퍼로 UART 입력을 모으고 줄 단위로 조립한다
- 4명령을 파싱해 LED/타이머 동작에 연결한다
- 5파서를 모듈로 분리해 PC에서 단위 테스트한다
소개
1편의 Hello, Embedded C! 에서 시작해 8051·STM32 레지스터, 인터럽트·통신·타이머, 디바운싱·링버퍼·HAL 패턴까지 온 여정의 마무리입니다. 이 캡스톤이 끝나면 "레지스터를 직접 다루면서도 구조가 깔끔한" 펌웨어를 짤 수 있습니다.
핵심 개념
1) 전체 아키텍처와 모듈
[PC 터미널] --UART--> RX 인터럽트 --push--> [링버퍼] --pop--> 메인 루프
│
줄 조립 → command_parse() → handle_command()
│
LED(PC13) / blink 모드 / UART 응답
SysTick(1ms) ──(blink 모드면 500ms 토글)──> LED순수 모듈(ringbuffer·command)은 칩에 의존하지 않아 그대로 PC에서 테스트됩니다. main.c 만 하드웨어 설정·통합을 맡습니다.
2) 생산자/소비자 분업과 명령 프로토콜
| 명령 | 동작 | 응답 |
|---|---|---|
| on | LED 켜기·blink 해제 | LED ON |
| off | LED 끄기·blink 해제 | LED OFF |
| blink | 500ms 자동 점멸 | BLINK mode |
| help | 명령 목록 | cmds: ... |
RX 인터럽트(생산자)는 rb_push만(짧게), 메인(소비자)은 rb_pop으로 꺼내 \n/\r에서 한 줄을 확정해 파싱·실행. blink_mode·g_ms는 ISR·메인 공유라 volatile.
핵심 예제
/* command.c — 순수 파서(하드웨어 비의존, PC 테스트 가능) */
command_t command_parse(const char *line) {
if (line[0] == '\0') return CMD_NONE;
if (streq(line, "on")) return CMD_ON;
if (streq(line, "off")) return CMD_OFF;
if (streq(line, "blink")) return CMD_BLINK;
if (streq(line, "help")) return CMD_HELP;
return CMD_UNKNOWN;
}/* main.c — 통합(요약) */
while (1) {
if (rb_pop(&rx_rb, &c)) {
if (c == '\n' || c == '\r') {
if (idx > 0u) { line[idx]='\0'; handle_command(command_parse(line)); idx=0u; }
} else if (idx < LINE_MAX-1u) line[idx++] = (char)c;
}
}파서 검산: "on"→ON, "blink"→BLINK, "xyz"→UNKNOWN, ""→NONE. 빌드: gcc pc_test.c command.c -o pc_test. 시뮬레이터 UART #1에 on/off/blink/help를 입력해 LED·점멸·응답을 확인합니다.
자주 하는 실수
Q. 명령을 입력해도 반응이 없어요.
A. 줄 끝(\n/\r)이 와야 한 줄로 확정됩니다. 터미널이 Enter에 개행을 보내는지, RXNEIE와 NVIC_EnableIRQ(USART1_IRQn)를 켰는지 확인하세요.
Q. 빠르게 입력하면 글자가 사라져요.
A. 메인이 rb_pop을 충분히 자주 호출하는지, 링버퍼 크기가 충분한지 보세요. ISR에서 무거운 일을 하면 다음 바이트를 놓칩니다(ISR은 push만).
Q. 파서를 고쳤는데 보드 테스트가 번거로워요.
A. 그래서 파서를 순수 모듈로 분리했습니다. command.c 만 pc_test.c와 gcc로 빌드해 로직을 먼저 검증하고 보드엔 그 다음에 올리세요.
정리
- 캡스톤은 USART·인터럽트·링버퍼·SysTick·GPIO·파서를 하나로 엮는다
- 인터럽트는 받아서 push만, 메인은 pop·조립·파싱·실행을 맡는다(분업)
- 순수 모듈(ringbuffer/command)은 PC에서 단위 테스트하고 보드에 올린다
- 명령 프로토콜로 펌웨어를 외부에서 제어·확장할 수 있다
- 레지스터 직접 제어와 깔끔한 구조는 양립한다 — 24편 완주!
과제
- blink 속도를 바꾸는 "fast"/"slow" 명령을 파서와 핸들러에 추가
- 인자가 있는 명령(예: "pwm 50")을 파싱하도록 확장
- I2C/SPI 센서·RTOS·DMA 등 다음 학습 주제 중 하나를 골라 이 캡스톤에 어떻게 붙일지 설계 스케치