21. 디바운싱·유한 상태 머신(FSM) 패턴
주변장치 사용법을 익혔으니 이제 펌웨어를 짜는 패턴으로 넘어갑니다. 거의 모든 프로젝트에 등장하는 두 가지 — 디바운싱과 유한 상태 머신(FSM)입니다. 기계식 버튼은 누르는 순간 접점이 수 ms 튀어(채터링) 한 번이 여러 번으로 읽힙니다. 디바운싱은 떨림을 걸러 진짜 입력만 남기고, FSM은 "상태+입력→다음 상태" 규칙으로 동작을 깔끔하게 표현합니다. 하드웨어 비의존 순수 로직이라 gcc로 정확히 검증합니다.
이 강의에서 배우는 것
- 1버튼 채터링(바운스)의 원인과 증상을 설명한다
- 2적분기(연속 샘플 카운트) 방식의 디바운서를 구현한다
- 3디바운서를 모듈로 분리해 재사용·테스트 가능하게 만든다
- 4유한 상태 머신의 구성요소(상태·입력·전이)를 이해한다
- 5디바운스된 확정 에지로 FSM을 구동한다
소개
기계식 버튼의 금속 접점은 닿는 순간 미세하게 튕겨, 한 번 누름이 전기적으로 0→1→0→1…처럼 수 ms간 진동합니다. 이 신호를 그대로 읽으면 여러 번으로 카운트됩니다. 이번 편은 하드웨어 비의존 순수 로직 — 디바운서를 모듈(debounce.c/.h)로 분리하고 그 위에 LED 모드 FSM을 얹습니다.
핵심 개념
1) 적분기 디바운서
일정 주기(예: SysTick 1~5ms)로 핀을 샘플링하고, 안정 상태와 다른 값이 THRESHOLD번 연속될 때만 상태를 바꿉니다. 중간에 한 번이라도 원래 값으로 돌아오면 카운터가 0으로 리셋되어 글리치가 걸러집니다(논블로킹·견고).
uint8_t debounce_update(debounce_t *db, uint8_t raw) {
uint8_t r = raw ? 1u : 0u;
if (r == db->stable) { db->count = 0u; return 0u; } /* 떨림 없음 */
if (++db->count >= DEBOUNCE_THRESHOLD) { /* N번 연속 */
db->stable = r; db->count = 0u; return 1u; /* 확정 변경 */
}
return 0u;
}2) 유한 상태 머신(FSM)
| 현재 상태 | 입력(눌림 확정) | 다음 상태 |
|---|---|---|
| OFF | press | SLOW |
| SLOW | press | FAST |
| FAST | press | OFF |
C에서는 enum + switch로 구현합니다. 상태가 명시적이라 디버깅·확장이 쉽습니다.
FSM은 디바운서가 확정한 "눌림 에지"만 입력으로 받습니다. 입력 정제(디바운스)와 동작 결정(FSM)을 분리하면 코드가 깔끔합니다. 보드에서는 SysTick 같은 주기 인터럽트에서 raw = (GPIOA->IDR >> pin) & 1 을 읽어 넣기만 하면 됩니다.
핵심 예제
/* 노이즈 낀 raw 스트림 → 디바운스 → 눌림 확정 에지마다 OFF→SLOW→FAST 순환 */
typedef enum { MODE_OFF=0, MODE_SLOW=1, MODE_FAST=2 } led_mode_t;
for (i = 0; i < n; i++) {
if (debounce_update(&db, raw[i]) && debounce_state(&db) == 1u) {
mode = (led_mode_t)((mode + 1) % 3); /* 확정 에지에서만 전진 */
}
}검증: 짧은 글리치(1샘플)는 무시되고 진짜 누름(THRESHOLD 연속)에서만 모드가 바뀝니다. 빌드: gcc pc_test.c debounce.c -o pc_test.
자주 하는 실수
Q. 디바운스했는데도 가끔 두 번 눌린 것처럼 동작해요.
A. THRESHOLD가 너무 작거나 샘플 주기가 너무 빠릅니다. 보통 샘플 주기 × THRESHOLD가 10~30ms가 되도록 잡습니다(예: 5ms × 3 = 15ms).
Q. 버튼을 누르고 있는 동안 모드가 계속 바뀌어요.
A. "눌림 상태"가 아니라 "눌림으로 바뀐 에지"에서만 동작해야 합니다. debounce_update의 반환값(변경됨)과 새 상태가 1인지를 함께 확인하세요.
Q. FSM을 if-else 더미로 짰더니 상태가 꼬여요.
A. 상태를 enum으로 명시하고 switch로 전이를 한곳에 모으세요. 상태가 코드 곳곳에 흩어지면 추적이 어렵습니다.
정리
- 채터링은 기계 접점 떨림으로 한 번 누름이 여러 번으로 읽힌다
- 적분기 디바운서는 같은 값이 N번 연속될 때만 상태를 확정해 글리치를 거른다
- 디바운서를 모듈로 분리하면 어느 칩에서도 재사용·테스트할 수 있다
- FSM은 상태·입력·전이로 동작을 표현하며 enum+switch로 구현한다
- 입력 정제(디바운스)와 동작 결정(FSM)을 분리하면 코드가 깔끔하다
과제
- THRESHOLD를 2/5로 바꿔 가며 글리치 거름이 어떻게 달라지는지 관찰
- 길게 누르면(long press) 별도 동작을 하는 상태를 FSM에 추가
- 두 버튼을 각각 디바운스해 독립적으로 FSM을 구동