코드 정리

This commit is contained in:
2026-06-26 17:20:45 +09:00
parent 6c7fa6d0e6
commit 1a78656c8f
14 changed files with 105 additions and 265 deletions
+54 -101
View File
@@ -1,16 +1,26 @@
/*******************************************************************************
* @file main.c
* @brief VesiScan BASIC - Zephyr 포팅 (Stage 1: GPIO + 전원 + LED + BLE)
* @brief VesiScan-Basic
*
* 부팅 시퀀스:
* Phase 1: 전원 유지 핀, GPIO, 타이머, LED 초기화
* Phase 2: BLE 스택 + NUS 초기화, advertising 시작
* Phase 3+: (이후 단계: 센서 등)
* 부팅 흐름:
* 1. 리셋/DFU 복구 상태 확인, 전원 유지 핀 초기 설정
* 2. GPIO, 타이머, workqueue, LED, 배터리 ADC, IMU, piezo 설정 초기화
* 3. BLE 스택 + NUS 초기화
* 4. 전원 버튼 상태머신 시작
*
* 전원 버튼 상태머신 (5ms 폴링, k_timer):
* [OFF→ON] 버튼 2초 이상 유지 P0.08 래치 ON + 녹색 LED 깜빡임
* 2초 미만 놓으면 전원 OFF (래치 안 됨)
* [ON→OFF] 버튼 2초 이상 유지 → sleep_mode_enter() → 전원 차단
* 일반 버튼 부팅:
* - 버튼을 BOOT_THRESHOLD 동안 유지하면 P0.08 전원 래치 ON
* - 래치 후 LED advertising 패턴, 배터리 모니터, BLE advertising 시작
* - 기준 시간 = POWER_ON_DELAY * BOOT_THRESHOLD
*
* 리셋/DFU 복구:
* - 버튼 없이 전원 래치와 advertising을 자동 복구할 수 있음
*
* 전원 버튼 상태머신:
* [OFF→ON] 2초 이상 버튼 유지 → 전원 래치 ON (초기화 시간 고려 체감상 2초, 실제 코드상 1초)
* 임계 시간 전에 놓으면 전원 래치 해제
* [ON→OFF] 버튼 유지 → advertising 중지 + POWER_OFF_DELAY 후 전원 차단
*
******************************************************************************/
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
@@ -81,13 +91,7 @@ static bool power_btn_suspended; /* 측정 중 전원 버튼 상태
/*
* BLE advertising 제어용 워크 아이템
*
* BLE HCI 명령(bt_le_adv_start/stop 등)은 내부적으로 세마포어 대기가 필요하므로
* 인터럽트 컨텍스트(타이머 콜백 등)에서 직접 호출하면 커널 oops가 발생한다.
* (증상: "Controller unresponsive, command opcode 0x2006 timeout")
*
* k_work를 사용하면 시스템 워크큐 스레드에서 실행되므로 안전하게 BLE API 호출 가능.
* main_s() 타이머 콜백에서 k_work_submit()으로 예약하여 사용한다.
* - main_s()에서 k_work_submit()으로 예약하여 사용
*/
static bool resume_without_power_button; /* 리셋 후 버튼 없이 복귀 */
static bool dfu_confirm_pending_boot; /* DFU test 이미지 확인 전 자동 복귀 */
@@ -95,7 +99,7 @@ static bool dfu_reset_resume_request; /* DFU 완료 리셋 후 자동 복
static uint32_t boot_reset_reason; /* RESETREAS 원본 값 */
static struct k_work adv_start_work; /* advertising 시작 work */
static struct k_work adv_stop_work; /* advertising 중지 work */
static bool dfu_led_active; /* DFU 업로드 중 초록 LED 고정 */
static bool dfu_led_active; /* DFU 업로드 중 초록 LED ON */
static const char *power_off_pending_reason = "none";
static enum mgmt_cb_return dfu_status_cb(uint32_t event,
@@ -128,16 +132,8 @@ static struct mgmt_callback smp_cmd_status_callback = {
};
/*
* BLE RX 콜백 안에서 무거운 일을 바로 처리하지 않기 위해 별도 work queue를 둔다.
*
* 이유:
* - BLE 콜백은 "일단 빨리 빠져나오는 것"이 중요하다.
* - 그 안에서 mbb? 같은 긴 측정을 돌리면 BLE 스택 타이밍을 망칠 수 있다.
* - 그래서 수신 데이터는 일단 복사만 하고,
* 실제 파싱/측정은 아래 work queue 스레드에서 천천히 처리한다.
*
* 현재 구조는 "동시에 여러 명령 처리"가 아니라 "한 번에 하나만 처리" 모델이다.
* 즉, 긴 명령 하나가 끝날 때까지 다음 명령은 drop된다.
* BLE RX 콜백 안에서 무거운 일을 바로 처리하지 않기 위해 별도 work queue를
* - 명령 하나가 끝날 때까지 다음 명령 drop : 확인 필요
*/
static struct k_work_q ble_cmd_work_q;
K_THREAD_STACK_DEFINE(ble_cmd_workq_stack, BLE_CMD_WORKQ_STACK_SZ);
@@ -155,7 +151,7 @@ static void ble_cmd_work_handler(struct k_work *work)
uint8_t local_buf[BLE_CMD_MAX_LEN];
uint16_t local_len = 0U;
/* 공유 RX 버퍼 짧게 잠금 */
/* 공유 RX 명령 슬롯 보호: 현재 구조는 한 번에 한 명령만 보관 */
k_spinlock_key_t key = k_spin_lock(&ble_cmd_lock);
if (ble_cmd_pending)
{
@@ -171,8 +167,8 @@ static void ble_cmd_work_handler(struct k_work *work)
}
/* 일반 스레드 문맥 명령 처리 */
DBG_CORE("[BLE RX] worker dispatch len=%u\r\n", local_len);
dr_parser(local_buf, local_len);
//DBG_CORE("[BLE RX] worker dispatch len=%u\r\n", local_len);
ble_cmd_dispatch(local_buf, local_len);
/* 명령 처리 완료 표시 */
key = k_spin_lock(&ble_cmd_lock);
@@ -200,28 +196,13 @@ static void adv_stop_work_handler(struct k_work *work)
*============================================================================*/
static void ble_rx_handler(const uint8_t *data, uint16_t len)
{
/* RX 데이터 앞부분 로그 */
DBG_CORE("[BLE RX] %u bytes:", len);
for (uint16_t i = 0; i < len && i < 32; i++)
{
DBG_CORE(" %02X", data[i]);
}
if (len > 32)
{
DBG_CORE(" ...");
}
DBG_CORE("\r\n");
if (len > BLE_CMD_MAX_LEN)
{
DBG_ERR("[BLE RX] drop: len=%u exceeds %d\r\n", len, BLE_CMD_MAX_LEN);
return;
}
/*
* 이미 긴 명령 하나를 처리 중이면 새 명령은 받지 않는다.
* 지금은 안정성 우선이라 "큐에 여러 개 쌓기"보다 "한 번에 하나"가 더 안전하다.
*/
/* 공유 RX 명령 슬롯 보호: 현재 구조는 한 번에 한 명령만 보관 */
k_spinlock_key_t key = k_spin_lock(&ble_cmd_lock);
if (ble_cmd_pending)
{
@@ -237,7 +218,6 @@ static void ble_rx_handler(const uint8_t *data, uint16_t len)
k_spin_unlock(&ble_cmd_lock, key);
/* 명령 처리 work 예약 */
DBG_CORE("[BLE RX] queued len=%u\r\n", len);
int err = k_work_submit_to_queue(&ble_cmd_work_q, &ble_cmd_work);
if (err < 0)
{
@@ -253,10 +233,6 @@ static void ble_rx_handler(const uint8_t *data, uint16_t len)
/*==============================================================================
* 전원 유지 (POWER_HOLD) 제어
*============================================================================*/
/* 전원 유지 핀(P0.08) 초기화 - 아직 래치하지 않음
* 버튼을 물리적으로 누르고 있는 동안만 전원 유지됨
* 2초 후 main_s()에서 래치(HIGH) 설정 */
static void boot_context_detect(void)
{
/* 부팅 원인 확인 */
@@ -264,8 +240,7 @@ static void boot_context_detect(void)
/* DFU 완료 직후 리셋인지 확인 */
#if NRF_POWER_HAS_GPREGRET
dfu_reset_resume_request =
(nrf_power_gpregret_get(NRF_POWER, DFU_RESUME_GPREGRET_REG) == DFU_RESUME_MAGIC);
dfu_reset_resume_request = (nrf_power_gpregret_get(NRF_POWER, DFU_RESUME_GPREGRET_REG) == DFU_RESUME_MAGIC);
if (dfu_reset_resume_request)
{
nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, 0U);
@@ -325,7 +300,6 @@ static void power_control_handler(on_off_cont_t device_power_st, const char *rea
else
{
gpio_pin_set_dt(&power_hold, 1); /* P0.08 HIGH → 전원 유지 */
DBG_PRINTF("[PWR] ON reason=%s\r\n", reason ? reason : "unknown");
}
}
@@ -335,7 +309,7 @@ static void power_control_handler(on_off_cont_t device_power_st, const char *rea
static void gpio_init(void)
{
gpio_pin_configure_dt(&power_btn, GPIO_INPUT); /* 전원 버튼(P1.08) 입력 설정 */
DBG_PRINTF("[GPIO] OK (BTN=%d)\r\n", gpio_pin_get_dt(&power_btn));
DBG_PRINTF("[INIT] HW - GPIO OK (POWER BTN=%d)\r\n", gpio_pin_get_dt(&power_btn));
}
@@ -380,7 +354,7 @@ static void load_default_config(void)
m_reset_status = 1;
bond_data_delete = true;
DBG_CORE("[CFG] Default (S/N=%s)\r\n", SERIAL_NO);
DBG_CORE("[INIT] Default (S/N=%s)\r\n", SERIAL_NO);
}
/*==============================================================================
@@ -390,7 +364,7 @@ static void load_default_config(void)
static void t_power_off_timeout_handler(struct k_timer *timer)
{
ARG_UNUSED(timer);
DBG_ERR("[PWR] Off timeout reason=%s\r\n", power_off_pending_reason);
DBG_ERR("[PWR] OFF timeout reason=%s\r\n", power_off_pending_reason);
led_set_state(LED_STATE_OFF);
power_control_handler(OFF, "off-timeout");
}
@@ -402,14 +376,7 @@ static void t_power_off_timeout_handler(struct k_timer *timer)
static void power_off_schedule(const char *reason, led_state_t led_state)
{
power_off_pending_reason = reason ? reason : "unknown";
DBG_ERR("[PWR] OFF scheduled reason=%s delay_ms=%u device_on=%u btn=%d cnt=%u suspended=%u dfu_adv=%u\r\n",
power_off_pending_reason,
POWER_OFF_DELAY,
device_on ? 1U : 0U,
gpio_pin_get_dt(&power_btn),
cnt_s,
power_btn_suspended ? 1U : 0U,
ble_dfu_advertising_is_enabled() ? 1U : 0U);
DBG_ERR("[PWR] OFF scheduled reason = %s\r\n", power_off_pending_reason);
led_set_state(led_state);
k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT);
}
@@ -445,12 +412,7 @@ void device_power_keep_on(void)
void power_button_suspend(bool suspend)
{
/*
* mbb?처럼 측정 시간이 길 때 전원 버튼 상태머신이 끼어들지 않게 잠깐 멈춘다.
*
* 이 함수는 "부팅을 막는 함수"가 아니다.
* 이미 켜진 뒤, 특정 커맨드 처리 중에만 버튼 폴링 판단을 잠깐 쉬게 하는 역할이다.
*/
/* 측정 시간이 긴 커맨드 처리 중에만 버튼 폴링 판단 잠시 멈춤 */
power_btn_suspended = suspend;
cnt_s = 0;
}
@@ -460,15 +422,15 @@ void power_button_suspend(bool suspend)
*
* [부팅 시퀀스] (device_on == false)
* 1. 버튼 누름 → 전원 공급 → MCU 부팅 → 5ms마다 폴링 시작
* 2. cnt_s < 400 (2초 미만)에서 놓으면 → 전원 OFF (래치 안 됨)
* 3. cnt_s == 400 (2초) 도달 → P0.08 래치 ON + 녹색 LED 깜빡임
* 2. cnt_s < 200 (2초 미만)에서 놓으면 → 전원 OFF (래치 안 됨)
* 3. cnt_s == 200 (2초) 도달 → P0.08 래치 ON + 녹색 LED 깜빡임
* 4. 버튼 놓으면 → 깜빡임 유지, 폴링 계속
*
* [전원 OFF 시퀀스] (device_on == true)
* 1. 버튼 2초 이상 누름 → sleep_mode_enter() → 전원 차단
* 2. 버튼 2초 미만 놓으면 → 카운터 리셋, 무시
*============================================================================*/
#define BOOT_THRESHOLD 200 /* 5ms x 400 = 2초 */
#define BOOT_THRESHOLD 200 /* 5ms x 400 = 2초 -> 초기화 시간 고려 체감상 2초에 맞춤 */
static void main_s(struct k_timer *timer)
{
@@ -498,37 +460,31 @@ static void main_s(struct k_timer *timer)
if (cnt_s == BOOT_THRESHOLD) /* 2초 도달: 래치 + 부팅 완료 */
{
device_on = true;
cnt_s = 0; /* 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가
* cnt_s >= 400 조건을 즉시 만족하여 전원 OFF됨 */
cnt_s = 0; /* 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가 cnt_s >= 200 조건을 즉시 만족하여 전원 OFF됨 */
power_control_handler(ON, "button-2s-latch");
led_set_state(LED_STATE_ADVERTISING);
k_work_submit(&adv_start_work);
battery_timer_start();
m_reset_status = 1;
DBG_PRINTF("[BTN] 2s -> Power latched, LED blink\r\n");
DBG_PRINTF("[BOOT] Complete, device ON\r\n");
DBG_PRINTF("[DEV] device_on=%d\r\n", device_on);
}
}
}
/* 전원 OFF 시퀀스 (ON → OFF) */
else
{
if (!boot_btn_released) /* 부팅 시 눌렀던 버튼을 아직 안 놓음 → 대기 */
if (!boot_btn_released) /* 부팅 시 눌렀던 버튼을 아직 안 놓음 → 대기 */
{
if (!button_pressed)
{
boot_btn_released = true;
DBG_PRINTF("[BTN] Boot button released\r\n");
}
}
else if (button_pressed) /* 버튼 새로 누르고 있음 */
else if (button_pressed) /* 버튼 새로 누르고 있음 */
{
cnt_s++;
if (cnt_s >= BOOT_THRESHOLD) /* 2초 이상 → 전원 OFF */
if (cnt_s >= BOOT_THRESHOLD) /* 2초 이상 → 전원 OFF */
{
DBG_PRINTF("[BTN] 2s long press -> Power OFF\r\n");
battery_timer_stop();
k_work_submit(&adv_stop_work);
device_on = false;
@@ -538,7 +494,7 @@ static void main_s(struct k_timer *timer)
return;
}
}
else /* 버튼 놓음 → 카운터 리셋 */
else /* 버튼 놓음 → 카운터 리셋 */
{
if (cnt_s > 0)
{
@@ -603,17 +559,17 @@ static enum mgmt_cb_return dfu_status_cb(uint32_t event,
{
case MGMT_EVT_OP_IMG_MGMT_DFU_STARTED:
case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK:
/* DFU 업로드 중에는 초록 LED를 계속 켬 */
/* DFU 업로드 중에는 초록 LED ON */
dfu_led_active = true;
led_ble_solid();
break;
case MGMT_EVT_OP_IMG_MGMT_DFU_PENDING:
/* 업로드 완료 후 리셋 전까지 초록 LED 유지 */
/* 업로드 완료 후 리셋 전까지 초록 LED ON */
dfu_led_active = true;
led_ble_solid();
/* DFU 리셋 후 버튼 없이 전원 복구 */
/* DFU 리셋 후 버튼 없이 전원 ON */
#if NRF_POWER_HAS_GPREGRET
nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, DFU_RESUME_MAGIC);
#endif
@@ -785,7 +741,7 @@ int main(void)
/* 리셋/복귀 컨텍스트 */
boot_context_detect();
/*── Phase 1: 하드웨어 기본 초기화 ──*/
/* 하드웨어 기본 초기화 */
power_hold_init();
cnt_s = 0;
@@ -796,10 +752,10 @@ int main(void)
DBG_CORE("\r\n========================================\r\n");
DBG_CORE(" TEST BUILD %s (Zephyr)\r\n", FIRMWARE_VERSION);
DBG_CORE(" BUILD TAG: TEST-ADV-UNIT-001\r\n");
DBG_CORE(" BUILD TAG: TEST-ADV-UNIT-002\r\n");
DBG_CORE("========================================\r\n");
DBG_CORE("[1] HW Init\r\n");
DBG_CORE("[INIT] HW Init\r\n");
/* 기본 하드웨어/센서 초기화 */
gpio_init();
timers_init();
@@ -810,13 +766,13 @@ int main(void)
battery_timer_init();
imu_init();
piezo_config_init();
DBG_CORE(" gpio/timer/config/led/batt/imu/temp/piezo-cfg OK\r\n");
DBG_CORE("[INIT] HW - gpio/timer/config/led/batt/imu/temp/piezo-cfg OK\r\n");
/*── Phase 2: BLE 스택 + NUS ──*/
DBG_CORE("[2] BLE Init\r\n");
/* BLE 스택 + NUS */
DBG_CORE("[INIT] BLE Init\r\n");
if (ble_service_init(ble_rx_handler) == 0)
{
DBG_CORE(" ble/nus OK\r\n");
DBG_CORE("[INIT] BLE - ble/nus OK\r\n");
/* 소프트 리셋 후 서비스 복구 */
resume_device_after_soft_reset();
/* BLE 준비까지 확인한 뒤 DFU test 이미지를 확정 */
@@ -824,15 +780,12 @@ int main(void)
}
else
{
DBG_ERR(" ble FAIL\r\n");
DBG_ERR("[INIT] BLE - ble/nus FAIL\r\n");
}
/*── Phase 3: FDS/NVS ──*/
/*── Phase 4: 애플리케이션 (TODO) ──*/
DBG_CORE("\r\n========================================\r\n");
DBG_CORE("========================================\r\n");
DBG_CORE(" READY [%s]\r\n", SERIAL_NO);
DBG_CORE("========================================\r\n\r\n");
DBG_CORE("========================================\r\n");
/* 전원 버튼 상태머신 시작 (부팅 시 버튼이 눌려있는 상태) */
timers_start();