세 값으로 전이를 관리한다
stable은 마지막으로 확정한 논리 상태, candidate는 현재 관찰 중인 후보, since는 후보가 시작된 시각이다. 새 샘플이 candidate와 다르면 후보와 시각만 갱신한다. 후보가 stable과 다르고 정해진 안정 시간이 지났을 때에만 stable을 바꾸고 이벤트를 한 번 내보낸다.
새 샘플 ≠ 후보: 후보와 시작 시각 갱신
같은 후보, 대기 시간 미달: 확정 상태 유지
시간 충족·후보 ≠ 확정: 상태 변경, 이벤트 1회
이 그림은 주기적으로 입력을 읽는 상태 기계다. 샘플 사이에서 생겼다가 사라진 짧은 펄스는 관찰하지 못할 수 있다. 스캔 간격, 허용 지연, 접점 잡음의 특성을 함께 정해야 한다.
동작을 검증할 최소 의사코드
sample(raw, now):
if raw != candidate:
candidate = raw
since = now
if candidate != stable and elapsed(now, since) >= wait:
stable = candidate
emit(stable)read_gpio 어댑터에서 active-low 같은 전기적 극성을 논리적 눌림으로 바꾼다. 시작할 때 읽은 값을 stable·candidate에 넣어 기동 이벤트를 생략할지, 초기 눌림도 이벤트로 알릴지를 API 계약으로 정한다. 보드 GPIO 번호나 OS 호출은 이 상태 기계 밖에 둔다.
elapsed는 사용하는 타이머 폭의 unsigned 차이를 일관되게 계산해야 한다. 한 바퀴 이상 관찰을 놓친 시간을 복원할 수는 없으므로 최대 스캔 공백과 비교 가능한 시간 범위를 제한한다. 위 코드는 설명용이며 특정 MCU에 그대로 적용할 드라이버가 아니다.
tick을 ms라고 부르지 않는다
FreeRTOS V11.1.0의 pdMS_TO_TICKS는 tick rate를 곱한 뒤 1000으로 정수 나눗셈한다. 예를 들어 100 Hz에서 5 ms 요청은 0 tick이 된다. xTaskGetTickCount 결과를 now_ms라는 변수에 그대로 넣어 ms 임계값과 비교하면 1 kHz가 아닌 설정에서 판단 시간이 달라진다.
현재 시각·since·wait를 모두 tick으로 유지하거나, 경계에서 명시적으로 같은 시간 단위로 변환한다. 0 tick을 허용할지 최소 1 tick으로 제한할지도 설계 선택이다. 최소 지연을 엄밀히 보장해야 한다면 tick 위상과 스케줄링 지연까지 포함해 검사한다.
잡음·반복·경계 조건을 따로 검사한다
| 입력 | 기대 결과 |
|---|---|
| 짧은 0→1→0 튐 | 확정 전이면 이벤트 없음 |
| 눌림을 계속 유지 | 눌림 이벤트 1회 |
| 해제도 안정 시간을 충족 | 해제 이벤트 1회 |
| 타이머 wrap 부근의 전이 | 허용 간격 내에서 동일 결과 |
| 대기열 포화·소비자 지연 | 유실·재시도 정책을 명시 |
ISR과 task가 상태를 동시에 바꾸게 두지 않는다. 한 문맥이 상태 기계를 소유하고 다른 문맥은 샘플이나 이벤트만 전달하도록 설계하면 검사 범위가 작아진다. 길게 누르기·자동 반복·더블 클릭은 확정된 눌림/해제 이벤트 위에 별도 상태로 올린다.
원문과 검증 범위
이 글의 사용자 공유 근거는 Part 3 software debouncing(#174)과 Part 4 reusable button driver(#149)다. Part 1의 직접 게시물과 첨부는 확인하지 못했다. 작성자의 Part 2·5는 시리즈 맥락이며 사용자 공유 기록으로 세지 않는다.
Yamil Garcia의 software debouncing 및 reusable button driver 자료를 계기로 입력 추상화와 시간 단위를 정리했다. 원문 코드 전체를 복제하지 않았으며 위 의사코드와 테스트 계획은 독립 구성이다. 실제 보드의 바운스 파형, RTOS 스케줄링, 전기적 입력 조건은 이 글에서 측정하지 않았다.