/******************************************************************************* * @file led_control.c * @brief LED 직접 제어 드라이버 (BSP 미사용) * @date 2026-03-30 * * app_timer 1개로 2색 LED(녹색/주황)의 복합 blink 패턴 구현 * 단순 on/off 상태는 타이머 없이 즉시 GPIO 제어 * 복합 패턴(에러 등)은 phase 기반 state machine으로 처리 ******************************************************************************/ #include "led_control.h" #include "nrf_gpio.h" #include "app_timer.h" /*============================================================================== * 내부 상수 *============================================================================*/ #define MS_TO_TICKS(ms) APP_TIMER_TICKS(ms) /*============================================================================== * 색상 *============================================================================*/ #define COLOR_NONE 0 #define COLOR_GREEN 1 #define COLOR_ORANGE 2 /*============================================================================== * 에러 패턴 상수 (No.7) * 3Hz 깜빡 3회 = 166ms on + 166ms off × 3 = ~1초, 이후 꺼짐 1초 *============================================================================*/ #define ERROR_BLINK_ON_MS 166 #define ERROR_BLINK_OFF_MS 166 #define ERROR_BLINK_COUNT 3 #define ERROR_PAUSE_MS 1000 /*============================================================================== * 패턴 테이블 (단순 blink 용) *============================================================================*/ typedef struct { uint32_t on_ms; /* LED 켜짐 시간 (ms) */ uint32_t off_ms; /* LED 꺼짐 시간 (ms) */ uint8_t color; /* COLOR_GREEN 또는 COLOR_ORANGE */ bool repeat; /* true: 무한 반복, false: 1회 후 OFF */ } led_pattern_t; static const led_pattern_t m_patterns[LED_STATE_COUNT] = { [LED_STATE_OFF] = { 0, 0, COLOR_NONE, false }, [LED_STATE_POWER_ON] = { 2000, 0, COLOR_GREEN, false }, /* 초록 점등 2초 → 유지 */ [LED_STATE_POWER_OFF] = { 2000, 0, COLOR_GREEN, false }, /* 초록 점등 2초 → OFF */ [LED_STATE_ADVERTISING] = { 500, 500, COLOR_GREEN, true }, /* 초록 점멸 1초 */ [LED_STATE_DETACH_WARNING] = { 1000, 3000, COLOR_GREEN, true }, /* 초록 1초 on / 3초 off */ [LED_STATE_ALIGN_SEARCHING] = { 1000, 1000, COLOR_ORANGE, true }, /* 주황 점멸 1초 */ [LED_STATE_ALIGN_COMPLETE] = { 3000, 1000, COLOR_GREEN, true }, /* 초록 3초 on / 1초 off */ [LED_STATE_ERROR] = { 0, 0, COLOR_ORANGE, true } /* 별도 state machine */ }; /*============================================================================== * 모듈 변수 *============================================================================*/ APP_TIMER_DEF(m_led_timer); static led_state_t m_current_state = LED_STATE_OFF; static bool m_phase_on; /* true: LED 켜진 구간, false: 꺼진 구간 */ /* 에러 패턴 전용 */ static uint8_t m_error_blink_cnt; /* 현재까지 깜빡인 횟수 */ static uint8_t m_error_phase; /* 0: blink-on, 1: blink-off, 2: pause */ /*============================================================================== * GPIO 헬퍼 *============================================================================*/ static inline void led_green_on(void) { #if LED_ACTIVE_LOW nrf_gpio_pin_clear(LED_PIN_GREEN); #else nrf_gpio_pin_set(LED_PIN_GREEN); #endif } static inline void led_green_off(void) { #if LED_ACTIVE_LOW nrf_gpio_pin_set(LED_PIN_GREEN); #else nrf_gpio_pin_clear(LED_PIN_GREEN); #endif } static inline void led_orange_on(void) { #if LED_ACTIVE_LOW nrf_gpio_pin_clear(LED_PIN_ORANGE); #else nrf_gpio_pin_set(LED_PIN_ORANGE); #endif } static inline void led_orange_off(void) { #if LED_ACTIVE_LOW nrf_gpio_pin_set(LED_PIN_ORANGE); #else nrf_gpio_pin_clear(LED_PIN_ORANGE); #endif } static void led_all_off(void) { led_green_off(); led_orange_off(); } static void led_color_on(uint8_t color) { led_all_off(); if (color == COLOR_GREEN) led_green_on(); else if (color == COLOR_ORANGE) led_orange_on(); } /*============================================================================== * 타이머 시작 헬퍼 *============================================================================*/ static void timer_start_ms(uint32_t ms) { if (ms == 0) return; app_timer_start(m_led_timer, MS_TO_TICKS(ms), NULL); } /*============================================================================== * 에러 패턴 state machine (No.7) * phase 0: LED ON (166ms) → phase 1 * phase 1: LED OFF (166ms) → cnt++ → cnt<3 ? phase 0 : phase 2 * phase 2: PAUSE (1000ms) → cnt=0, phase 0 *============================================================================*/ static void error_pattern_start(void) { m_error_blink_cnt = 0; m_error_phase = 0; led_color_on(COLOR_ORANGE); timer_start_ms(ERROR_BLINK_ON_MS); } static void error_pattern_tick(void) { switch (m_error_phase) { case 0: /* ON 구간 끝 → OFF */ led_all_off(); m_error_phase = 1; timer_start_ms(ERROR_BLINK_OFF_MS); break; case 1: /* OFF 구간 끝 */ m_error_blink_cnt++; if (m_error_blink_cnt < ERROR_BLINK_COUNT) { /* 다시 ON */ m_error_phase = 0; led_color_on(COLOR_ORANGE); timer_start_ms(ERROR_BLINK_ON_MS); } else { /* 3회 완료 → pause */ m_error_phase = 2; timer_start_ms(ERROR_PAUSE_MS); } break; case 2: /* pause 끝 → 처음부터 */ m_error_blink_cnt = 0; m_error_phase = 0; led_color_on(COLOR_ORANGE); timer_start_ms(ERROR_BLINK_ON_MS); break; default: break; } } /*============================================================================== * 타이머 콜백 *============================================================================*/ static void led_timer_handler(void * p_context) { (void)p_context; /* 에러 상태는 별도 처리 */ if (m_current_state == LED_STATE_ERROR) { error_pattern_tick(); return; } const led_pattern_t * p = &m_patterns[m_current_state]; if (m_phase_on) { /* ON→OFF 전환 */ led_all_off(); m_phase_on = false; if (p->off_ms > 0) { timer_start_ms(p->off_ms); } else if (!p->repeat) { /* 1회성: off_ms == 0 이면 그냥 유지 (POWER_ON) 또는 끄기 (POWER_OFF) */ if (m_current_state == LED_STATE_POWER_OFF) { led_all_off(); m_current_state = LED_STATE_OFF; } /* POWER_ON: 점등 유지 상태 → 타이머 x */ } } else { /* OFF → ON 전환 */ if (p->repeat) { led_color_on(p->color); m_phase_on = true; timer_start_ms(p->on_ms); } /* repeat == false && off_ms > 0 인 경우는 현재 없음 */ } } /*============================================================================== * 공개 함수 *============================================================================*/ void led_init(void) { /* GPIO 출력 설정 */ nrf_gpio_cfg_output(LED_PIN_GREEN); nrf_gpio_cfg_output(LED_PIN_ORANGE); led_all_off(); /* 타이머 생성 (single-shot) */ app_timer_create(&m_led_timer, APP_TIMER_MODE_SINGLE_SHOT, led_timer_handler); m_current_state = LED_STATE_OFF; } void led_set_state(led_state_t state) { if (state >= LED_STATE_COUNT) return; /* 이전 패턴 중단 */ app_timer_stop(m_led_timer); led_all_off(); m_current_state = state; m_phase_on = false; const led_pattern_t * p = &m_patterns[state]; switch (state) { case LED_STATE_OFF: /* 이미 all off */ break; /* 에러 패턴: 별도 state machine */ case LED_STATE_ERROR: error_pattern_start(); break; /* 그 외: on 구간부터 시작 */ default: led_color_on(p->color); m_phase_on = true; if (p->on_ms > 0) { timer_start_ms(p->on_ms); } break; } } led_state_t led_get_state(void) { return m_current_state; }