/******************************************************************************* * @file main.c * @brief VesiScan-Basic ******************************************************************************/ #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_BT_SMP) #include #include #endif #include "main.h" #include "debug_print.h" #include "power_control.h" #include "led_control.h" #include "ble_service.h" #include "battery_adc.h" #include "parser.h" #include "imu_i2c.h" #include "piezo_measure.h" LOG_MODULE_REGISTER(vesiscan, LOG_LEVEL_INF); #define BLE_CMD_MAX_LEN 256 #define BLE_CMD_WORKQ_STACK_SZ 8192 #define DFU_RESUME_MAGIC 0xD5U #define DFU_RESUME_GPREGRET_REG 1U #define DFU_STALL_TIMEOUT_SEC 30 // DFU 업로드 무진행 시 강제 disconnect 임계 #define IMG_MGMT_ID_STATE 0U #define IMG_MGMT_ID_UPLOAD 1U /* * BLE 명령 처리 스택 * - piezo sweep * - ADC capture * - BLE 응답 패킷 조립 * - 디버깅 여유분 포함 */ #define BLE_CMD_WORKQ_PRIORITY 10 /* Devicetree GPIO Settings */ #define POWER_HOLD_NODE DT_NODELABEL(pwr_hold) // 전원 래치 #define POWER_BTN_NODE DT_NODELABEL(button_check) // 전원 버튼 /* 전원 버튼 상태머신 (5ms 폴링) */ #define BOOT_THRESHOLD 350 // 5ms x 400 = 2초 -> 초기화 시간 고려 체감상 2초에 맞춤 #define BOND_RESET_THRESHOLD (15000 / POWER_ON_DELAY) // 전원 ON 상태에서 15초 롱프레스 → 본드 삭제 /* 전원 제어 GPIO */ static const struct gpio_dt_spec power_hold = GPIO_DT_SPEC_GET(POWER_HOLD_NODE, gpios); static const struct gpio_dt_spec power_btn = GPIO_DT_SPEC_GET(POWER_BTN_NODE, gpios); /* Timer */ static struct k_timer m_power_on_delay_timer; // 전원 버튼 폴링용 (5ms 싱글샷) static struct k_timer m_power_off_delay_timer; // 전원 OFF 지연용 (3초 후 전원 차단) /* 전역 변수 */ volatile bool ble_connection_st = false; // BLE 연결 상태 volatile bool processing = false; // 센서 데이터 처리 중 플래그 bool bond_data_delete = true; // 본딩 데이터 삭제 요청 uint32_t m_life_cycle = 0; // 디바이스 수명 카운터 uint8_t m_reset_status = 1; // 리셋 상태 코드 char SERIAL_NO[SERIAL_NO_BUF_SIZE]; // 시리얼 번호 char HW_NO[HW_NO_BUF_SIZE]; // 하드웨어 번호 char m_static_passkey[PASSKEY_BUF_SIZE]; // BLE 정적 패스키 uint8_t m_passkey_changed; // 패스키 변경은 출고 후 1회만 허용 static uint16_t cnt_s; // 전원 버튼 폴링 카운터 (5ms 단위) static bool device_on = false; // 디바이스 전원 상태 (래치 완료 여부) static bool boot_btn_released = false; // 부팅 후 버튼 놓았는지 여부 static bool power_btn_suspended; // 측정 중 전원 버튼 상태머신 일시 정지 /* * BLE advertising 제어용 워크 아이템 * - main_s()에서 k_work_submit()으로 예약하여 사용 */ static bool resume_without_power_button; // 리셋 후 버튼 없이 복귀 static bool dfu_confirm_pending_boot; // DFU test 이미지 확인 전 자동 복귀 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 struct k_work bond_reset_work; // 전원 버튼 15초 롱프레스 본드 삭제 work static bool dfu_led_active; // DFU 업로드 중 초록 LED ON static const char *power_off_pending_reason = "none"; static bool bond_reset_pending; // 본드 삭제 work 중복 예약 방지 static struct k_work_delayable dfu_watchdog_work; // DFU 업로드 무진행 감시 work static void dfu_watchdog_handler(struct k_work *work); static enum mgmt_cb_return dfu_status_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc, uint16_t *group, bool *abort_more, void *data, size_t data_size); static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc, uint16_t *group, bool *abort_more, void *data, size_t data_size); static struct mgmt_callback dfu_status_callback = { .callback = dfu_status_cb, .event_id = MGMT_EVT_OP_IMG_MGMT_DFU_STARTED | MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK | MGMT_EVT_OP_IMG_MGMT_DFU_PENDING | MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED, }; static struct mgmt_callback smp_cmd_status_callback = { .callback = smp_cmd_status_cb, .event_id = MGMT_EVT_OP_CMD_DONE, }; /* * 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); static struct k_work ble_cmd_work; // BLE 명령 처리 work static struct k_spinlock ble_cmd_lock; // RX 공유 버퍼 보호 static uint8_t ble_cmd_buf[BLE_CMD_MAX_LEN];// RX 명령 복사 버퍼 static uint16_t ble_cmd_len; // RX 명령 길이 static bool ble_cmd_pending; // 처리 대기 명령 있음 static void ble_cmd_work_handler(struct k_work *work) { ARG_UNUSED(work); // work queue 로컬 복사본 uint8_t local_buf[BLE_CMD_MAX_LEN]; uint16_t local_len = 0U; // 공유 RX 명령 슬롯 보호: 현재 구조는 한 번에 한 명령만 보관 k_spinlock_key_t key = k_spin_lock(&ble_cmd_lock); if (ble_cmd_pending) { local_len = ble_cmd_len; memcpy(local_buf, ble_cmd_buf, local_len); } k_spin_unlock(&ble_cmd_lock, key); if (local_len == 0U) { DBG_ERR("[BLE RX] worker: empty\r\n"); return; } // 일반 스레드 문맥 명령 처리 //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); ble_cmd_pending = false; ble_cmd_len = 0U; k_spin_unlock(&ble_cmd_lock, key); } /* 시스템 워크큐에서 BLE advertising 시작 */ static void adv_start_work_handler(struct k_work *work) { ARG_UNUSED(work); ble_advertising_start(); } /* 시스템 워크큐에서 BLE advertising 중지 */ static void adv_stop_work_handler(struct k_work *work) { ARG_UNUSED(work); ble_advertising_stop(); } /* 전원 버튼 15초 롱프레스: BLE 본드 삭제 후 리부트 */ static void bond_reset_work_handler(struct k_work *work) { ARG_UNUSED(work); DBG_ERR("[BTN] 15s long press -> BLE bond reset\r\n"); battery_timer_stop(); ble_advertising_stop(); #if IS_ENABLED(CONFIG_BT_SMP) int err = bt_unpair(BT_ID_DEFAULT, NULL); if (err) { DBG_ERR("[BTN] bt_unpair failed err=%d\r\n", err); } else { bond_data_delete = true; DBG_PRINTF("[BTN] BLE bond data deleted\r\n"); } #else bond_data_delete = true; DBG_PRINTF("[BTN] BLE bond delete skipped (BT_SMP disabled)\r\n"); #endif led_set_state(LED_STATE_BOND_DELETE); // 삭제 완료 확인 주황 LED ON k_msleep(1500); sys_reboot(SYS_REBOOT_COLD); } /* BLE RX 콜백 */ static void ble_rx_handler(const uint8_t *data, uint16_t len) { 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) { k_spin_unlock(&ble_cmd_lock, key); DBG_ERR("[BLE RX] drop: command busy\r\n"); return; } // 명령 버퍼 복사 memcpy(ble_cmd_buf, data, len); ble_cmd_len = len; ble_cmd_pending = true; k_spin_unlock(&ble_cmd_lock, key); // 명령 처리 work 예약 int err = k_work_submit_to_queue(&ble_cmd_work_q, &ble_cmd_work); if (err < 0) { // 예약 실패 시 pending 복구 key = k_spin_lock(&ble_cmd_lock); ble_cmd_pending = false; ble_cmd_len = 0U; k_spin_unlock(&ble_cmd_lock, key); DBG_ERR("[BLE RX] queue submit fail err=%d\r\n", err); } } /* 전원 유지 (POWER_HOLD) 제어 */ static void boot_context_detect(void) { // 부팅 원인 확인 boot_reset_reason = nrf_power_resetreas_get(NRF_POWER); // DFU 완료 직후 리셋인지 확인 #if NRF_POWER_HAS_GPREGRET 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); } #else dfu_reset_resume_request = false; #endif // 소프트 리셋 계열 자동 복귀 resume_without_power_button = (boot_reset_reason & (NRF_POWER_RESETREAS_SREQ_MASK | NRF_POWER_RESETREAS_DOG_MASK | NRF_POWER_RESETREAS_LOCKUP_MASK | NRF_POWER_RESETREAS_RESETPIN_MASK)) != 0U; if (boot_reset_reason != 0U) { // RESETREAS 플래그 정리 nrf_power_resetreas_clear(NRF_POWER, boot_reset_reason); } } static void power_hold_init(void) { #if defined(CONFIG_BOOTLOADER_MCUBOOT) // DFU 이미지 확인 전 전원 유지 dfu_confirm_pending_boot = !boot_is_img_confirmed(); #else dfu_confirm_pending_boot = false; #endif // 리셋 복귀/DFU 확인 대기 시 즉시 래치 gpio_pin_configure_dt(&power_hold, (resume_without_power_button || dfu_confirm_pending_boot || dfu_reset_resume_request) ? GPIO_OUTPUT_ACTIVE : GPIO_OUTPUT_INACTIVE); } /* 전원 ON/OFF 제어 - P0.08 핀으로 물리적 전원 래치/해제 */ static void power_control_handler(on_off_cont_t device_power_st, const char *reason) { if (device_power_st == OFF) { DBG_ERR("[PWR] OFF latch release reason=%s pending=%s device_on=%u btn=%d cnt=%u suspended=%u dfu_adv=%u dfu_confirm=%u dfu_resume=%u\r\n", reason ? reason : "unknown", power_off_pending_reason, device_on ? 1U : 0U, gpio_pin_get_dt(&power_btn), cnt_s, power_btn_suspended ? 1U : 0U, ble_dfu_advertising_is_enabled() ? 1U : 0U, dfu_confirm_pending_boot ? 1U : 0U, dfu_reset_resume_request ? 1U : 0U); gpio_pin_set_dt(&power_hold, 0); // P0.08 LOW → 전원 래치 해제 → 전원 차단 } else { gpio_pin_set_dt(&power_hold, 1); // P0.08 HIGH → 전원 유지 } } /* GPIO 초기화 */ static void gpio_init(void) { gpio_pin_configure_dt(&power_btn, GPIO_INPUT); // 전원 버튼(P1.08) 입력 설정 DBG_PRINTF("[INIT] HW - GPIO OK (POWER BTN=%d)\r\n", gpio_pin_get_dt(&power_btn)); } /* 기본 설정값 로드 */ static void load_default_config(void) { // 기본 문자열 길이 제한 size_t serial_len = strlen(SERIAL_NUMBER); size_t hw_len = strlen(HARDWARE_VERSION); size_t passkey_len = strlen(DEFAULT_PASSKEY); if (serial_len > SERIAL_NO_LENGTH) { serial_len = SERIAL_NO_LENGTH; } if (hw_len > HW_NO_LENGTH) { hw_len = HW_NO_LENGTH; } if (passkey_len > PASSKEY_LENGTH) { passkey_len = PASSKEY_LENGTH; } memset(SERIAL_NO, 0, sizeof(SERIAL_NO)); memcpy(SERIAL_NO, SERIAL_NUMBER, serial_len); // 기본 HW Version 복사 memset(HW_NO, 0, sizeof(HW_NO)); memcpy(HW_NO, HARDWARE_VERSION, hw_len); // 기본 BLE 패스키 복사 memset(m_static_passkey, 0, sizeof(m_static_passkey)); memcpy(m_static_passkey, DEFAULT_PASSKEY, passkey_len); // NVS 로드 전 기본값은 변경 가능 상태 m_passkey_changed = 0U; // 기본 상태값 m_reset_status = 1; bond_data_delete = true; DBG_CORE("[INIT] Default (S/N=%s)\r\n", SERIAL_NO); } /* 전원 OFF 타임아웃 콜백 */ 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); led_set_state(LED_STATE_OFF); power_control_handler(OFF, "off-timeout"); } /* 슬립 모드 / 전원 OFF: LED 표시 후 3초 뒤 전원 차단 */ 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\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); } void sleep_mode_enter_reason(const char *reason) { power_off_schedule(reason, LED_STATE_POWER_ON); } void sleep_mode_enter(void) { sleep_mode_enter_reason("sleep_mode_enter"); } void device_power_off(void) { device_power_off_reason("device_power_off"); } void device_power_off_reason(const char *reason) { power_off_schedule(reason, LED_STATE_POWER_OFF); } void device_power_keep_on(void) { k_timer_stop(&m_power_off_delay_timer); device_on = true; boot_btn_released = (gpio_pin_get_dt(&power_btn) != 1); cnt_s = 0; power_control_handler(ON, "keep-on"); } void power_button_suspend(bool suspend) { // 측정 시간이 긴 커맨드 처리 중에만 버튼 폴링 판단 잠시 멈춤 power_btn_suspended = suspend; cnt_s = 0; } static void main_s(struct k_timer *timer) { ARG_UNUSED(timer); bool button_pressed = (gpio_pin_get_dt(&power_btn) == 1); if (power_btn_suspended) { k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT); return; } // 부팅 시퀀스 (OFF → ON) if (!device_on) { if (!button_pressed) // 버튼 놓음 → 2초 미만이면 전원 OFF { //DBG_PRINTF("[BTN] Short press (%d) -> OFF\r\n", cnt_s); power_control_handler(OFF, "boot-button-released-before-latch"); cnt_s = 0; } else // 버튼 계속 누르고 있음 { cnt_s++; if (cnt_s == BOOT_THRESHOLD) // 2초 도달: 래치 + 부팅 완료 { device_on = true; // cnt_s는 리셋하지 않음: 부팅 누름을 놓기 전(!boot_btn_released)까지 15초 본드 삭제 카운트를 연속 유지(놓는 순간 아래 분기에서 0으로 리셋) 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; } } } /* 전원 OFF 시퀀스 (ON → OFF) */ else { if (!boot_btn_released) // 부팅 때 누른 버튼을 아직 안 놓음 { if (!button_pressed) // 놓음 → 이후부터 새 누름을 전원 OFF로 인식 { boot_btn_released = true; cnt_s = 0; } else // 전원 ON부터 계속 유지 중 → 15초 넘으면 본드 삭제 + 리부트 { cnt_s++; if ((cnt_s >= BOND_RESET_THRESHOLD) && !bond_reset_pending) { bond_reset_pending = true; k_work_submit(&bond_reset_work); return; } } } else if (button_pressed) // 켜진 뒤 새로 누름 → 2초 이상이면 자동 전원 OFF { cnt_s++; if (cnt_s >= BOOT_THRESHOLD) { battery_timer_stop(); k_work_submit(&adv_stop_work); device_on = false; boot_btn_released = false; cnt_s = 0; sleep_mode_enter_reason("button-long-press"); return; } } else // 버튼 놓음 → 카운터 리셋 { if (cnt_s > 0) { DBG_PRINTF("[BTN] Short press (%d) -> ignored\r\n", cnt_s); } cnt_s = 0; } } k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT); } /* 타이머 초기화 / 시작 */ static void timers_init(void) { // 전원 상태머신 타이머 k_timer_init(&m_power_on_delay_timer, main_s, NULL); k_timer_init(&m_power_off_delay_timer, t_power_off_timeout_handler, NULL); // BLE advertising work k_work_init(&adv_start_work, adv_start_work_handler); k_work_init(&adv_stop_work, adv_stop_work_handler); // 전원 버튼 15초 롱프레스 본드 삭제 work k_work_init(&bond_reset_work, bond_reset_work_handler); // DFU 업로드 무진행 감시 work k_work_init_delayable(&dfu_watchdog_work, dfu_watchdog_handler); // BLE 명령 처리 work queue k_work_init(&ble_cmd_work, ble_cmd_work_handler); k_work_queue_start(&ble_cmd_work_q, ble_cmd_workq_stack, K_THREAD_STACK_SIZEOF(ble_cmd_workq_stack), BLE_CMD_WORKQ_PRIORITY, NULL); // 전원 제어 타이머 power_timer_init(); // DFU 업로드 상태를 LED에 반영 mgmt_callback_register(&dfu_status_callback); // DFU 명령 실패 시 LED 상태 복구 mgmt_callback_register(&smp_cmd_status_callback); } /* 전원 버튼 폴링 시작 (5ms 후 main_s 콜백) */ static void timers_start(void) { k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT); } /* DFU 업로드가 멈춘 채(청크 무진행) 링크만 살아있는 경우, 연결을 강제로 끊어 * SMP 재광고(10분 타임아웃)로 전환한다. 초록 LED 무한 점등/무한 대기 방지. */ static void dfu_watchdog_handler(struct k_work *work) { ARG_UNUSED(work); if (ble_is_connected()) { DBG_PRINTF("[DFU] upload stalled (%ds), forcing disconnect\r\n", DFU_STALL_TIMEOUT_SEC); (void)ble_disconnect_active(); } } static enum mgmt_cb_return dfu_status_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc, uint16_t *group, bool *abort_more, void *data, size_t data_size) { ARG_UNUSED(prev_status); ARG_UNUSED(rc); ARG_UNUSED(group); ARG_UNUSED(abort_more); ARG_UNUSED(data); ARG_UNUSED(data_size); switch (event) { case MGMT_EVT_OP_IMG_MGMT_DFU_STARTED: case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK: dfu_led_active = true; led_ble_solid(); // DFU 업로드 중에는 초록 LED ON // 청크가 올 때마다 무진행 감시 타이머 리셋 k_work_reschedule(&dfu_watchdog_work, K_SECONDS(DFU_STALL_TIMEOUT_SEC)); break; case MGMT_EVT_OP_IMG_MGMT_DFU_PENDING: dfu_led_active = true; led_ble_solid(); // 업로드 완료 후 리셋 전까지 초록 LED ON k_work_cancel_delayable(&dfu_watchdog_work); // 업로드 완료: 감시 종료 #if NRF_POWER_HAS_GPREGRET nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, DFU_RESUME_MAGIC); // DFU 리셋 후 버튼 없이 전원 ON #endif break; case MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED: k_work_cancel_delayable(&dfu_watchdog_work); // 중단/실패: 감시 종료 if (dfu_led_active) { dfu_led_active = false; led_set_state(LED_STATE_ADVERTISING); // 실패나 중단 시 일반 광고 LED로 복귀 } break; default: break; } return MGMT_CB_OK; } static enum mgmt_cb_return smp_cmd_status_cb(uint32_t event, enum mgmt_cb_return prev_status, int32_t *rc, uint16_t *group, bool *abort_more, void *data, size_t data_size) { ARG_UNUSED(event); ARG_UNUSED(prev_status); ARG_UNUSED(rc); ARG_UNUSED(group); ARG_UNUSED(abort_more); if (data == NULL || data_size != sizeof(struct mgmt_evt_op_cmd_arg)) { return MGMT_CB_OK; } const struct mgmt_evt_op_cmd_arg *cmd = data; // 이미지 업로드/test 명령 실패 시 DFU LED 고정 해제 if (cmd->group == MGMT_GROUP_ID_IMAGE && (cmd->id == IMG_MGMT_ID_UPLOAD || cmd->id == IMG_MGMT_ID_STATE) && cmd->err != MGMT_ERR_EOK) { DBG_ERR("[DFU] command failed id=%u err=%d\r\n", cmd->id, cmd->err); dfu_led_active = false; led_set_state(LED_STATE_ADVERTISING); // 실패 후 리셋 복구 표식이 남지 않게 정리 #if NRF_POWER_HAS_GPREGRET nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, 0U); #endif } return MGMT_CB_OK; } static void resume_device_after_soft_reset(void) { // 일반 버튼 부팅이면 skip if (!resume_without_power_button && !dfu_confirm_pending_boot && !dfu_reset_resume_request) { return; } // 리셋 복귀나 DFU test 부팅은 버튼 없이도 ON 상태로 복구 device_on = true; cnt_s = 0; boot_btn_released = (gpio_pin_get_dt(&power_btn) != 1); m_reset_status = 1; // 정상 ON 상태 서비스 재시작 power_control_handler(ON, "resume-after-reset"); led_set_state(LED_STATE_ADVERTISING); battery_timer_start(); k_work_submit(&adv_start_work); DBG_CORE("[BOOT] Resume after resetreas=0x%08x\r\n", boot_reset_reason); } static void confirm_running_image(void) { #if defined(CONFIG_BOOTLOADER_MCUBOOT) // 기본 초기화와 BLE 준비가 끝난 뒤 MCUboot revert 방지 if (!boot_is_img_confirmed()) { int err = boot_write_img_confirmed(); if (err == 0) { DBG_CORE("[DFU] Running image confirmed\r\n"); } else { DBG_ERR("[DFU] Image confirm failed (err %d)\r\n", err); } } else { DBG_CORE("[DFU] Running image already confirmed\r\n"); } #endif } static const char *swap_type_to_str(int swap_type) { // MCUboot swap 상태 문자열 switch (swap_type) { case BOOT_SWAP_TYPE_NONE: return "none"; case BOOT_SWAP_TYPE_TEST: return "test"; case BOOT_SWAP_TYPE_PERM: return "perm"; case BOOT_SWAP_TYPE_REVERT: return "revert"; case BOOT_SWAP_TYPE_FAIL: return "fail"; default: return "unknown"; } } static void log_mcuboot_state(void) { #if defined(CONFIG_BOOTLOADER_MCUBOOT) // 현재 부팅 슬롯/교체 상태 struct mcuboot_img_header header; uint8_t active_slot = boot_fetch_active_slot(); int swap_type = mcuboot_swap_type(); int err = boot_read_bank_header(active_slot, &header, sizeof(header)); DBG_CORE("[DFU] active_slot=%u swap_type=%s(%d) confirmed=%d\r\n", active_slot, swap_type_to_str(swap_type), swap_type, boot_is_img_confirmed()); if (err == 0) { /* 실행 이미지 버전 */ DBG_CORE("[DFU] image version %u.%u.%u+%u size=0x%x\r\n", header.h.v1.sem_ver.major, header.h.v1.sem_ver.minor, header.h.v1.sem_ver.revision, header.h.v1.sem_ver.build_num, header.h.v1.image_size); } else { DBG_ERR("[DFU] image header read failed (slot %u, err %d)\r\n", active_slot, err); } #endif } /* 메인 함수 */ int main(void) { // 리셋/복귀 컨텍스트 boot_context_detect(); // 하드웨어 기본 초기화 power_hold_init(); cnt_s = 0; // 버튼 부팅 기본 상태 device_on = false; boot_btn_released = false; confirm_running_image(); DBG_CORE("\r\n========================================\r\n"); DBG_CORE(" TEST BUILD %s (Zephyr)\r\n", FIRMWARE_VERSION); DBG_CORE(" BUILD TAG: %s\r\n", CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION); DBG_CORE("========================================\r\n"); DBG_CORE("[INIT] HW Init\r\n"); // 기본 하드웨어/센서 초기화 gpio_init(); timers_init(); log_mcuboot_state(); load_default_config(); led_init(); battery_adc_init(); battery_timer_init(); imu_init(); piezo_config_init(); DBG_CORE("[INIT] HW - gpio/timer/config/led/batt/imu/temp/piezo-cfg OK\r\n"); // BLE 스택 + NUS DBG_CORE("[INIT] BLE Init\r\n"); if (ble_service_init(ble_rx_handler) == 0) { DBG_CORE("[INIT] BLE - ble/nus OK\r\n"); // 소프트 리셋 후 서비스 복구 resume_device_after_soft_reset(); // BLE 준비까지 확인한 뒤 DFU test 이미지를 확정 confirm_running_image(); } else { DBG_ERR("[INIT] BLE - ble/nus FAIL\r\n"); } DBG_CORE("========================================\r\n"); DBG_CORE(" READY [%s]\r\n", SERIAL_NO); DBG_CORE("========================================\r\n"); // 전원 버튼 상태머신 시작 (부팅 시 버튼이 눌려있는 상태) timers_start(); // 메인 루프 - idle for (;;) { k_msleep(100); } return 0; }