LED 시세 전광판 제작기 · 04

[IoT DIY 4편] 다중 센서 퓨전과 난수 알고리즘을 적용한 인터랙티브 NeoPixel 무드등 설계

[IoT DIY 4편] 다중 센서 퓨전과 난수 알고리즘을 적용한 인터랙티브 NeoPixel 무드등 설계

세로형 시세 전광판의 왼쪽 공백을 채우기 위해 시작한 보조 조명 프로젝트는, 결국 전광판 본체보다 더 많은 센서와 복잡한 상태 머신(State Machine) 알고리즘이 탑재된 핵심 임베디드 디바이스로 진화했습니다.

단순히 지정된 RGB LED의 색상을 반복해서 켜는 것은 기술적인 완성도를 낮추며, 사용자의 눈을 피로하게 만듭니다. 본 글에서는 하드웨어 외형 재료가 빛의 확산에 미치는 광학적 영향, 도플러 레이더·사운드·조도 센서를 융합한 센서 퓨전(Sensor Fusion) 기법, 그리고 시각적 피로도를 극복하기 위한 소프트웨어 로직을 엔지니어링 관점에서 정리합니다.

[구동 영상] 80개 NeoPixel 무드등의 심야 시계 모드와 비동기 상태 머신 구동 테스트입니다.

1. 하드웨어 및 광학 설계: 빛의 투과율과 전력 엔지니어링

아크릴 색상에 따른 광학적 광확산(Light Diffusion) 차이

전광판과 동일한 높이와 폭(8 × 7 × 80cm)의 외형을 유지하기 위해 처음에는 전광판과 동일한 검정 반투명 아크릴을 테스트했습니다. 하지만 시세 문자 출력에 최적화된 검정 아크릴은 내부 LED 빛의 약 70% 이상을 흡수해버렸으며, 빛이 원기둥 전체로 퍼지지 못하고 픽셀별로 점으로 맺히는 핫스팟(Hot-spot) 현상이 발생했습니다.

이를 해결하기 위해 고휘도 흰색 형광 반투명 아크릴로 설정을 변경했습니다.

  • 광학 원리: 흰색 반투명 재질은 람베르트 반사(Lambertian reflectance) 및 난반사를 유도하여 WS2812B LED의 직진성 빛을 부드럽게 흩뿌려줍니다.
  • 실제 비용의 교훈: 두께감 있고 내구성이 뛰어난 맞춤 제작 아크릴을 발주하면서 배송비를 포함해 개당 약 24만 원의 비용이 소요되었습니다. 임베디드 하드웨어 제작 시 내부 IC나 마이크로컨트롤러 부품값보다 기구 설계 및 외형 맞춤 가공비가 배 이상 높게 책정될 수 있다는 실무적 교훈을 얻은 지점이었습니다.

80개 NeoPixel(WS2812B) 제어를 위한 전력 설계(Power Safety)

80개의 WS2812B LED가 동시에 최대 밝기(White 100%)로 발광할 경우, 픽셀당 약 60mA의 전류를 소모합니다.

Imax = 80 × 60mA = 4,800mA(4.8A)

이 전류(약 5V / 4.8A)를 ESP32 보드의 5V 핀으로 직접 공급하면 보드의 패턴이나 레귤레이터가 즉시 소손됩니다. 따라서 다음과 같은 하드웨어 안전 설계를 적용해야 합니다.

  1. 외부 전원 분리: LED 스트립의 전원(VCC, GND)은 고용량 외부 SMPS 5V 어댑터에 직접 연결하고, ESP32와는 GND만 공통으로 묶어줍니다(Common Ground).
  2. 서지 보강: 전원 입력단에 1000µF 이상의 바이패스 커패시터를 병렬 연결하여 돌입 전류(Inrush Current)로 인한 첫 픽셀 소손을 방지합니다.
  3. 신호 보호: ESP32의 데이터 출력 핀과 LED 스트립 데이터 입력 핀 사이에 330Ω ~ 470Ω 저항을 직렬 연결하여 신호 반사 및 임피던스 불일치를 완화합니다.

2. 다중 센서 퓨전(Sensor Fusion) 및 트러블슈팅

무드등이 단순한 인테리어 소품을 넘어 사용자의 상황과 공간을 인식하려면 센서 데이터 간의 유기적인 연동이 필요합니다. 본 기기에는 3가지 핵심 센서가 통합되었습니다.

1) 마이크로웨이브 도플러 레이더 센서(RCWL-0516)

  • 적용 목적: 사용자가 접근할 때 조명이 심장박동(Heartbeat Pulse)처럼 자연스럽게 밝아지며 반응하도록 유도합니다.
  • 트러블슈팅: 일반 PIR(적외선) 감지 센서는 아크릴 케이스 내부에서 투과율이 떨어져 동작하지 않는 문제가 있었습니다. 따라서 아크릴과 목재 케이스를 완벽히 투과하여 360도 거리를 감지할 수 있는 마이크로웨이브 도플러 센서로 변경하여 외관 파손 없이 접근 인식을 구현했습니다.

2) 사운드 센서(MAX4466 / INMP441)

  • 적용 목적: 음악 재생 시 특정 데시벨 이상을 감지하면 오디오 이퀄라이저(EQ) 이펙트로 자동 전환합니다.
  • 트러블슈팅(아날로그 노이즈 극복): 아날로그 사운드 센서의 Raw 데이터는 미세한 전원 노이즈에도 급격히 흔들립니다. 단순 임계값(Threshold) 비교 방식으로는 감도를 높이면 키보드 타건음 같은 잡음에도 에러가 발생하고, 낮추면 실제 음악 소리를 잡지 못했습니다.
  • 해결책: 소프트웨어 내에서 지수 이동 평균(EMA, Exponential Moving Average) 필터를 구현하여 배경 소음의 기준선(Baseline)을 실시간으로 추적하고, 기준선에서 갑자기 튀어 오르는 오디오 피크(Peak)만을 정확히 타격하도록 알고리즘을 수정했습니다.

3) 조도 센서(LDR) 및 NTP 시간 동기화

밤인지 낮인지를 인지해야 야간 슬립 로직이 작동합니다. 조도 센서와 더불어 Wi-Fi 기반의 NTP(Network Time Protocol) 시간 동기화를 결합하여, 단순 불투명도 감지를 넘어 정확한 밤 12시에 맞춰 제어 로직을 전환합니다.

3. 시각적 피로도 감소를 위한 난수(PRNG) 알고리즘 설계

정해진 순서대로 색상이 바뀌는 RGB 조명은 뇌가 곧바로 패턴을 인식하여 며칠 안에 지루함을 느끼게 됩니다. 완성도 높은 인테리어 장치로 만들고자 의사 난수 생성기(PRNG, Pseudo-Random Number Generator)를 활용하여 시각적 예측 가능성을 완벽히 배제했습니다.

  • 동적 이펙트 분기: 레인보우 흐름, 그라데이션 트랜지션, 수직 스와이프, 듀얼 레이어 점멸 등 4가지 메인 알고리즘 중 하나를 난수로 추출합니다.
  • 색상 및 지속 시간의 가변성: 다음 전환될 타겟 팔레트(Color Palette)를 랜덤 생성하며, 단일 이펙트의 유지 시간 또한 최소 3초에서 최대 30초 사이의 비동기 난수 타이머로 구동합니다.
  • 반복 횟수 무작위 제어: 특정 효과가 연속해서 중복되지 않도록 직전 실행 인덱스를 기억하고 가중치(Weight)를 조정하여 매번 새로운 느낌을 주도록 설계했습니다.

4. FastLED 기반 비동기 상태 머신 및 심야 시계 모드 구현

다중 센서를 실시간으로 읽으면서 LED 애니메이션이 끊기지 않고 부드럽게 동작하려면, C++의 delay() 함수를 절대 사용해서는 안 됩니다. millis() 타임스탬프를 이용한 논블로킹(Non-blocking) 상태 머신과, 밤 12시 정각에 발동하는 심야 1초 초침 모드 보일러플레이트 코드입니다.

FastLED 기반 비동기 상태 머신 및 심야 시계 모드 구현
#include <FastLED.h>
#include <time.h>

#define LED_PIN     18
#define NUM_LEDS    80
#define BRIGHTNESS  150
#define LED_TYPE    WS2812B
#define COLOR_ORDER GRB

CRGB leds[NUM_LEDS];

// 시스템 상태를 관리하는 열거형 (State Machine)
enum LampState {
    STATE_NORMAL_RANDOM,
    STATE_SOUND_REACTIVE,
    STATE_NIGHT_CLOCK_MODE
};

LampState currentState = STATE_NORMAL_RANDOM;
unsigned long lastUpdate = 0;
uint8_t clockHandIndex = 0;

// 1. 초기화 및 FastLED 설정
void setup() {
    Serial.begin(115200);

    // 전력 보호 및 핀 매핑
    FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS)
           .setCorrection(TypicalLEDStrip);
    FastLED.setBrightness(BRIGHTNESS);

    // NTP 시간 설정 (서울 기준 KST = UTC + 9시간)
    configTime(9 * 3600, 0, "pool.ntp.org", "time.nist.gov");
}

// 2. 심야 시계 초침 모드 (00:00 ~ 06:00 동작 로직)
void renderNightClockMode() {
    unsigned long currentMillis = millis();

    // delay(1000)을 쓰지 않는 논블로킹 1초 타이머
    if (currentMillis - lastUpdate >= 1000) {
        lastUpdate = currentMillis;

        // 전체 LED 지우기 (완전 암전)
        FastLED.clear();

        // 80개의 LED 기둥을 60초 주기로 순회하는 계산식
        time_t now = time(nullptr);
        struct tm* timeinfo = localtime(&now);
        int currentSecond = timeinfo->tm_sec;

        clockHandIndex = map(currentSecond, 0, 59, 0, NUM_LEDS - 1);

        // 파란 불 하나만 초침처럼 부드럽게 점등
        leds[clockHandIndex] = CRGB::DeepSkyBlue;

        // 초침 잔상 효과
        if (clockHandIndex > 0) leds[clockHandIndex - 1] = CRGB(0, 10, 30);

        FastLED.show();
    }
}

// 3. 시간 감지 및 상태 전환 메인 컨트롤러
void checkTimeAndSwitchState() {
    time_t now = time(nullptr);
    struct tm* timeinfo = localtime(&now);
    int currentHour = timeinfo->tm_hour;

    // 밤 0시부터 새벽 6시까지는 초침 모드로 강제 전환
    if (currentHour >= 0 && currentHour < 6) {
        if (currentState != STATE_NIGHT_CLOCK_MODE) {
            Serial.println("[System] 심야 시계 모드로 전환 (00:00 - 06:00)");
            currentState = STATE_NIGHT_CLOCK_MODE;
            FastLED.setBrightness(30); // 야간 눈부심 방지를 위한 디밍
        }
    } else {
        if (currentState == STATE_NIGHT_CLOCK_MODE) {
            Serial.println("[System] 주간 난수 모드로 전환 및 밝기 복구");
            currentState = STATE_NORMAL_RANDOM;
            FastLED.setBrightness(BRIGHTNESS);
        }
    }
}

// 4. 비동기 메인 이벤트 루프
void loop() {
    checkTimeAndSwitchState();

    switch (currentState) {
        case STATE_NIGHT_CLOCK_MODE:
            renderNightClockMode();
            break;

        case STATE_SOUND_REACTIVE:
            // 사운드 센서 인터럽트 혹은 EMA 필터 처리 함수 호출
            // renderSoundEqualizer();
            break;

        case STATE_NORMAL_RANDOM:
        default:
            // 비동기 난수 기반 LED 애니메이션 호출
            // renderRandomPaletteFlow();
            break;
    }

    // 센서 읽기 등 기타 백그라운드 태스크는 블로킹 없이 상시 동작함
}

5. 공간 인지적 배치와 시스템의 결합

최종적으로 완성된 기기를 책상 위에 배치할 때는 공간 인지적 균형(Spatial Ergonomics)을 고려했습니다.

  • 가로로 눕히는 방식은 책상 면적을 크게 차지하며 빛이 직사로 눈에 닿아 피로감을 유발합니다.
  • 반면 80cm 기둥을 세로로 세워 왼쪽에는 흰색 아크릴의 부드러운 무드등을, 오른쪽에는 검정 아크릴의 명확한 시세 전광판을 나란히 배치함으로써 기능 분리와 디자인의 일체감을 동시에 획득했습니다.
  • 또한, 시세 전광판의 우측 측면에 1×18 픽셀 LED 배열을 추가 부착하여 정면뿐 아니라 측면으로 이동하는 중에도 시간과 지수를 확인할 수 있도록 3차원 시야각을 확보했습니다.

이번 4편을 통해 단순 시각적 출력 장치에 다중 센서와 상태 머신을 결합해 주변 환경에 유기적으로 반응하는 하드웨어 설계법을 검증할 수 있었습니다. 이어서 다음 편에서는 이 두 기기를 실제 지인들에게 선물 및 판매하면서 마주한 와이파이 하드코딩 문제, 그리고 이를 극복하기 위한 Captive Portal 웹 설정 및 OTA 원격 업데이트 도입기에 대해 다루어 보겠습니다.

엔지니어를 위한 팁: NeoPixel 제어 시 FastLED.show()가 호출되는 순간에는 마이크로컨트롤러의 인터럽트가 잠시 차단됩니다. 만약 Wi-Fi 통신 끊김이나 사운드 센서 데이터 누락이 발생한다면, FastLED.show()의 호출 주기(FPS)를 의도적으로 30~50Hz로 제한(Throttling)하여 CPU의 네트워크 및 아날로그 변환 여유 시간을 확보하시기 바랍니다.