LED 시세 전광판 제작기 · 03
[IoT DIY 3편] 야간 시각적 피로도 해결을 위한 다중 센서 퓨전: 조도, 도플러 레이더, 유한 상태 머신(FSM) 설계 및 오탐 필터링 노하우
![[IoT DIY 3편] 야간 시각적 피로도 해결을 위한 다중 센서 퓨전: 조도, 도플러 레이더, 유한 상태 머신(FSM) 설계 및 오탐 필터링 노하우](/iot-lab/assets/led-ticker/hero-3.jpg)
임베디드 하드웨어 제작에서 센서를 부품에 물리적으로 연결하고 데이터를 읽어오는 일은 기술적으로 가장 쉬운 단계에 속합니다. 진짜 어려운 엔지니어링의 영역은 수집된 날것의(Raw) 데이터를 조합하여, 주변 환경과 사용자 행동에 맞게 기기가 어떻게 반응할지 동작 규칙(State Machine)을 설계하는 것입니다.
본 글에서는 LED 시세 전광판을 완성한 직후 마주한 심각한 야간 눈부심 문제를 해결하기 위해 조도 센서(CDS)와 마이크로웨이브 도플러 레이더(RCWL-0516)를 도입하면서 겪은 광학적 한계, 새벽 시간대 오탐 트러블슈팅, 그리고 이를 비동기 로직으로 제어한 알고리즘 구현 과정을 상세히 정리합니다.
1. 조도 센서(CDS)와 PWM 디밍(Dimming)의 광학적 한계
전광판 완성 초기에는 주간 가독성이 매우 뛰어났지만, 소등 후 취침을 위해 침대에 누웠을 때 치명적인 문제가 발생했습니다. 8×16 배열의 빨간색 LED 매트릭스, 총 128개 픽셀이 방 전체를 붉게 물들이는 강력한 광원이 되었기 때문입니다.
이를 해결하기 위해 가장 먼저 CDS 조도 센서를 달아 주변 밝기에 따라 LED 출력을 조절하는 PWM 디밍 로직을 적용했습니다. 하지만 여기에는 데이터시트만 보고는 알 수 없는 인간의 시각적, 광학적 한계가 존재했습니다.
- 인간의 비선형적 시각 민감도: 인간의 눈은 어두운 곳에서 빛의 변화에 대수적(Logarithmic)으로 훨씬 민감하게 반응합니다.
- 최저 PWM의 한계: 8비트 PWM 제어(0~255)에서 밝기를 최저 수준인 1까지 낮추어도, 완전한 암흑 상태인 수면 환경에서는 빨간색 파장의 직진성 때문에 눈을 감아도 시각적 거슬림이 남았습니다.
결국 “어두울 때 밝기를 줄인다”는 단순한 선형 규칙만으로는 수면 방해를 해결할 수 없었으며, 사람이 없을 때는 화면을 완전히 차단하는 능동적 소등(Sleep) 알고리즘이 필요하다는 결론에 도달했습니다.
2. 마이크로웨이브 도플러 센서(RCWL-0516) 도입과 새벽 오탐 트러블슈팅
사람의 존재 여부를 파악하기 위해 일반적인 적외선(PIR) 센서 대신 마이크로웨이브 도플러 레이더 센서(RCWL-0516)를 적용했습니다. PIR 센서는 아크릴 케이스 내부에 숨길 경우 투과율이 떨어져 동작하지 않지만, 도플러 센서(3.2GHz~5.8GHz 대역)는 비금속 케이스를 완벽히 투과하여 깔끔한 외관을 유지할 수 있기 때문입니다.
하지만 도플러 센서 특유의 높은 민감도로 인해 예상치 못한 장애를 겪었습니다.
새벽 3시의 령(靈) 현상과 주파수 반사
규칙을 적용한 첫날 밤, 새벽 3시경 아무도 움직이지 않은 방에서 전광판이 갑자기 최대 밝기로 켜지는 현상이 반복되었습니다. 시스템 로그를 분석하고 원인을 추적한 결과, 문제는 센서의 고주파 특성에 있었습니다.
- 장애 원인: 마이크로웨이브 주파수는 공기의 온도 변화나 미세한 움직임도 반사파로 감지합니다. 창문 틈새로 들어온 외풍에 커튼이 미세하게 흔들리거나, 난방기가 가동되면서 발생한 대류(Warm air turbulence)를 사람의 움직임으로 오인한 것입니다.
- 해결 알고리즘(소프트웨어 필터링): 센서의 핀이 High(1)로 바뀐다고 해서 즉시 화면을 켜면 안 됩니다. 노이즈에 의한 순간적인 튀는 현상을 걸러내기 위해 디바운스(Debounce) 및 시간 윈도우 필터를 코드로 구현했습니다.
- 최소 지속 시간 검증: 감지 신호가 연속으로 1,000ms(1초) 이상 유지될 때만 유효한 움직임으로 인정합니다.
- 히스테리시스(Hysteresis) 타임아웃: 움직임이 끝나고 60초가 지나야만 소등 상태로 진입하도록 하여, 화면이 깜빡거리며 켜지고 꺼지를 반복하는 플래터링(Flattering) 현상을 방지했습니다.
3. 다중 조건 동작 규칙과 유한 상태 머신(FSM) 설계
단순한 가로등처럼 “사람이 오면 켜고, 가면 끈다”는 방식은 디스플레이 기기에 적합하지 않습니다. 사용자의 생활 패턴을 고려하여 다음과 같은 3단계 다중 조건 규칙을 수립했습니다.
| 현재 상태 | 조도(CDS) 조건 | 움직임(Doppler) 조건 | 디스플레이 출력 동작 |
|---|---|---|---|
| 주간 모드(Day) | 밝음 | 무관(체크 안 함) | 전체 화면(시세 + 시계) 최대 밝기(PWM 255) |
| 야간 시계 모드(Night Clock) | 어둠 | 감지됨(1초 이상) | 정보창 소등, 시계만 최저 밝기(PWM 5) 출력 |
| 야간 소등 모드(Sleep) | 어둠 | 없음(60초 이상) | 전체 디스플레이 완전 소등(PWM 0) |
이 규칙을 구현하기 위해 조건문을 무작정 중첩하면 코드가 스파게티처럼 꼬이고 버그를 잡기 어려워집니다. 따라서 기기의 상태를 명확히 나눈 유한 상태 머신(FSM, Finite State Machine) 아키텍처로 소프트웨어를 재구축했습니다.
4. [실무 코드 블록] CDS + 도플러 센서 퓨전 FSM 보일러플레이트
다음은 C++ millis() 기반의 논블로킹 타이머와 소프트웨어 노이즈 필터링이 결합된 상용 레벨의 센서 제어 보일러플레이트 코드입니다.
#include <Arduino.h> #define PIN_CDS 34 // 조도 센서 아날로그 입력 #define PIN_DOPPLER 35 // 도플러 레이더 디지털 입력 #define PIN_LED_EN 5 // LED 매트릭스 디스플레이 출력 EN // 시스템 상태 정의 (FSM) enum DisplayState { STATE_DAY_ALL_ON, STATE_NIGHT_CLOCK_ONLY, STATE_NIGHT_SLEEP }; DisplayState currentState = STATE_DAY_ALL_ON; // 타이머 및 필터링 변수 unsigned long lastMotionDetectedTime = 0; unsigned long motionPulseStartTime = 0; bool isMotionValid = false; const int CDS_THRESHOLD_DARK = 500; // 어둠 판단 임계값 (0~4095) const unsigned long MOTION_DEBOUNCE = 1000; // 오탐 방지 유효 움직임 시간 (1초) const unsigned long SLEEP_TIMEOUT = 60000; // 소등 진입 대기 시간 (60초) // ============================================================================== // 1. 도플러 센서 소프트웨어 디바운스 필터 // ============================================================================== bool readDopplerWithFilter() { int rawDoppler = digitalRead(PIN_DOPPLER); unsigned long currentMillis = millis(); if (rawDoppler == HIGH) { if (motionPulseStartTime == 0) { motionPulseStartTime = currentMillis; // 감지 시작 시간 기록 } // 연속으로 1초 이상 HIGH가 유지되어야만 실제 움직임으로 인정 (커튼 흔들림 무시) if (currentMillis - motionPulseStartTime >= MOTION_DEBOUNCE) { lastMotionDetectedTime = currentMillis; isMotionValid = true; } } else { motionPulseStartTime = 0; // 신호가 끊기면 타이머 초기화 } // 마지막 유효 움직임 이후 타임아웃(60초) 이내면 계속 활성 상태 유지 if (currentMillis - lastMotionDetectedTime < SLEEP_TIMEOUT) { return true; } else { isMotionValid = false; return false; } } // ============================================================================== // 2. 유한 상태 머신(FSM) 상태 전이 로직 // ============================================================================== void updateDisplayState() { int cdsValue = analogRead(PIN_CDS); bool isDark = (cdsValue < CDS_THRESHOLD_DARK); bool isPersonPresent = readDopplerWithFilter(); // 상태 전이 규칙 판단 if (!isDark) { currentState = STATE_DAY_ALL_ON; } else { if (isPersonPresent) { currentState = STATE_NIGHT_CLOCK_ONLY; } else { currentState = STATE_NIGHT_SLEEP; } } } // ============================================================================== // 3. 상태별 디스플레이 렌더링 컨트롤러 // ============================================================================== void executeDisplayOutput() { switch (currentState) { case STATE_DAY_ALL_ON: // 주간: 밝기 100%, 시세 + 시계 전체 출력 // Matrix.setBrightness(255); // renderFullInfo(); break; case STATE_NIGHT_CLOCK_ONLY: // 야간 활동: 밝기 최저(2%), 시세 끄고 시계만 조용히 출력 // Matrix.setBrightness(5); // renderClockOnly(); break; case STATE_NIGHT_SLEEP: // 야간 취침: 완전 소등 (화면 차단) // Matrix.clear(); break; } } void setup() { Serial.begin(115200); pinMode(PIN_CDS, INPUT); pinMode(PIN_DOPPLER, INPUT); pinMode(PIN_LED_EN, OUTPUT); } void loop() { updateDisplayState(); executeDisplayOutput(); // 비동기 루프이므로 네트워크 통신 등 타 작업에 지장을 주지 않음 }
5. 온습도 센서(DHT22) 추가 시 발생하는 발열 오차 보정
도플러 센서와 조도 센서를 통합하면서 기기 제어의 뼈대가 완성되자, 실내 온습도 측정을 위해 DHT22 센서를 추가했습니다. 기상청 API 데이터는 실외 기준이므로, 실제 내 방 책상 위의 환경을 모니터링하기 위함이었습니다.
하지만 이 과정에서도 임베디드 기기의 물리적 한계가 드러났습니다.
- 문제의 발견: 밀폐된 아크릴 케이스 내부에 DHT22를 배치하자, ESP32 보드와 128개 LED 매트릭스에서 발생하는 자체 발열(Self-heating)로 인해 실제 실내 온도보다 약 2~3°C 높게 측정되는 왜곡이 발생했습니다.
- 하드웨어 배치: 온습도 센서는 발열 부품의 위쪽이 아닌, 대류 현상의 영향을 가장 적게 받는 케이스 최하단 외부 흡기구 쪽으로 격리 배치했습니다.
- 소프트웨어 오프셋 보정: 오랜 시간 켜져 있을 때의 열 평형 상태를 계측하여, 수집된 온도값에서 항상 -2.2°C를 차감하는 오프셋(Offset) 알고리즘을 추가하여 측정 데이터의 신뢰성을 확보했습니다.
6. 결론: 단순 조립을 넘어선 동작 알고리즘의 가치
센서 몇 개를 마이크로컨트롤러에 연결하는 작업은 튜토리얼 몇 번이면 누구나 할 수 있습니다. 하지만 그 기기가 새벽에 오작동하지 않고, 사람의 시각적 피로도를 배려하며, 발열까지 고려된 정확한 데이터를 제공하기 위해서는 철저한 검증과 소프트웨어 필터링 알고리즘이 뒷받침되어야 합니다.
3일간 밤마다 침대에 누워 소등 타이머를 수정하고, 창문 바람에 의한 레이더 반사파를 코드로 잡아내던 이 치열한 트러블슈팅 과정이야말로 단순한 DIY 하드웨어를 쓸만한 완성품으로 만드는 가장 중요한 설계 단계였습니다. 이 성공적인 센서 퓨전 경험은, 다음 편에서 소개해 드릴 전광판의 단짝 장치 네오픽셀 무드등(4편)에서 랜덤 애니메이션과 소리 감지(EQ) 모드를 설계하는 든든한 기술적 지렛대가 되었습니다.