This commit is contained in:
2026-06-19 14:21:23 +09:00
parent 945e0a1cff
commit b64b4d08a2
+215 -19
View File
@@ -16,6 +16,7 @@
#include <zephyr/drivers/gpio.h> #include <zephyr/drivers/gpio.h>
#include <zephyr/sys/printk.h> #include <zephyr/sys/printk.h>
#include <zephyr/dfu/mcuboot.h> #include <zephyr/dfu/mcuboot.h>
#include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
#include <hal/nrf_power.h> #include <hal/nrf_power.h>
#include "main.h" #include "main.h"
@@ -31,6 +32,10 @@ LOG_MODULE_REGISTER(vesiscan, LOG_LEVEL_INF);
#define BLE_CMD_MAX_LEN 256 #define BLE_CMD_MAX_LEN 256
#define BLE_CMD_WORKQ_STACK_SZ 8192 #define BLE_CMD_WORKQ_STACK_SZ 8192
#define DFU_RESUME_MAGIC 0xD5U
#define DFU_RESUME_GPREGRET_REG 1U
#define IMG_MGMT_ID_STATE 0U
#define IMG_MGMT_ID_UPLOAD 1U
/* /*
* BLE 명령 처리 스택 * BLE 명령 처리 스택
* - piezo sweep * - piezo sweep
@@ -85,9 +90,42 @@ static bool power_btn_suspended; /* 측정 중 전원 버튼 상태
* main_s() 타이머 콜백에서 k_work_submit()으로 예약하여 사용한다. * main_s() 타이머 콜백에서 k_work_submit()으로 예약하여 사용한다.
*/ */
static bool resume_without_power_button; /* 리셋 후 버튼 없이 복귀 */ 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 uint32_t boot_reset_reason; /* RESETREAS 원본 값 */
static struct k_work adv_start_work; /* advertising 시작 work */ static struct k_work adv_start_work; /* advertising 시작 work */
static struct k_work adv_stop_work; /* advertising 중지 work */ static struct k_work adv_stop_work; /* advertising 중지 work */
static bool dfu_led_active; /* DFU 업로드 중 초록 LED 고정 */
static const char *power_off_pending_reason = "none";
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를 둔다. * BLE RX 콜백 안에서 무거운 일을 바로 처리하지 않기 위해 별도 work queue를 둔다.
@@ -224,6 +262,18 @@ static void boot_context_detect(void)
/* 부팅 원인 확인 */ /* 부팅 원인 확인 */
boot_reset_reason = nrf_power_resetreas_get(NRF_POWER); 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 = resume_without_power_button =
(boot_reset_reason & (NRF_POWER_RESETREAS_SREQ_MASK | (boot_reset_reason & (NRF_POWER_RESETREAS_SREQ_MASK |
@@ -242,28 +292,40 @@ static void power_hold_init(void)
{ {
#if defined(CONFIG_BOOTLOADER_MCUBOOT) #if defined(CONFIG_BOOTLOADER_MCUBOOT)
/* DFU 이미지 확인 전 전원 유지 */ /* DFU 이미지 확인 전 전원 유지 */
bool dfu_confirm_pending = !boot_is_img_confirmed(); dfu_confirm_pending_boot = !boot_is_img_confirmed();
#else #else
bool dfu_confirm_pending = false; dfu_confirm_pending_boot = false;
#endif #endif
/* 리셋 복귀/DFU 확인 대기 시 즉시 래치 */ /* 리셋 복귀/DFU 확인 대기 시 즉시 래치 */
gpio_pin_configure_dt(&power_hold, gpio_pin_configure_dt(&power_hold,
(resume_without_power_button || dfu_confirm_pending) ? GPIO_OUTPUT_ACTIVE (resume_without_power_button ||
: GPIO_OUTPUT_INACTIVE); dfu_confirm_pending_boot ||
dfu_reset_resume_request) ? GPIO_OUTPUT_ACTIVE
: GPIO_OUTPUT_INACTIVE);
} }
/* 전원 ON/OFF 제어 - P0.08 핀으로 물리적 전원 래치/해제 */ /* 전원 ON/OFF 제어 - P0.08 핀으로 물리적 전원 래치/해제 */
static void power_control_handler(on_off_cont_t device_power_st) static void power_control_handler(on_off_cont_t device_power_st, const char *reason)
{ {
if (device_power_st == OFF) 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 → 전원 래치 해제 → 전원 차단 */ gpio_pin_set_dt(&power_hold, 0); /* P0.08 LOW → 전원 래치 해제 → 전원 차단 */
} }
else else
{ {
gpio_pin_set_dt(&power_hold, 1); /* P0.08 HIGH → 전원 유지 */ gpio_pin_set_dt(&power_hold, 1); /* P0.08 HIGH → 전원 유지 */
DBG_PRINTF("[PWR] ON\r\n"); DBG_PRINTF("[PWR] ON reason=%s\r\n", reason ? reason : "unknown");
} }
} }
@@ -328,26 +390,52 @@ static void load_default_config(void)
static void t_power_off_timeout_handler(struct k_timer *timer) static void t_power_off_timeout_handler(struct k_timer *timer)
{ {
ARG_UNUSED(timer); ARG_UNUSED(timer);
DBG_PRINTF("[PWR] Off timeout\r\n"); DBG_ERR("[PWR] Off timeout reason=%s\r\n", power_off_pending_reason);
led_set_state(LED_STATE_OFF); led_set_state(LED_STATE_OFF);
power_control_handler(OFF); power_control_handler(OFF, "off-timeout");
} }
/*============================================================================== /*==============================================================================
* 슬립 모드 / 전원 OFF * 슬립 모드 / 전원 OFF
* LED로 사용자에게 알린 후 3초 뒤 전원 차단 * 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 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);
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) void sleep_mode_enter(void)
{ {
led_set_state(LED_STATE_POWER_ON); sleep_mode_enter_reason("sleep_mode_enter");
DBG_PRINTF("[SYS] Sleep\r\n");
k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT);
} }
void device_power_off(void) void device_power_off(void)
{ {
led_set_state(LED_STATE_POWER_OFF); power_off_schedule("device_power_off", LED_STATE_POWER_OFF);
k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT); }
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) void power_button_suspend(bool suspend)
@@ -383,12 +471,19 @@ static void main_s(struct k_timer *timer)
bool button_pressed = (gpio_pin_get_dt(&power_btn) == 1); bool button_pressed = (gpio_pin_get_dt(&power_btn) == 1);
if (!device_on) /* ── 부팅 시퀀스 (OFF → ON) ── */ 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 */ if (!button_pressed) /* 버튼 놓음 → 2초 미만이면 전원 OFF */
{ {
//DBG_PRINTF("[BTN] Short press (%d) -> OFF\r\n", cnt_s); //DBG_PRINTF("[BTN] Short press (%d) -> OFF\r\n", cnt_s);
power_control_handler(OFF); power_control_handler(OFF, "boot-button-released-before-latch");
cnt_s = 0; cnt_s = 0;
} }
else /* 버튼 계속 누르고 있음 */ else /* 버튼 계속 누르고 있음 */
@@ -400,7 +495,7 @@ static void main_s(struct k_timer *timer)
device_on = true; device_on = true;
cnt_s = 0; /* 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가 cnt_s = 0; /* 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가
* cnt_s >= 400 조건을 즉시 만족하여 전원 OFF됨 */ * cnt_s >= 400 조건을 즉시 만족하여 전원 OFF됨 */
power_control_handler(ON); power_control_handler(ON, "button-2s-latch");
led_set_state(LED_STATE_ADVERTISING); led_set_state(LED_STATE_ADVERTISING);
k_work_submit(&adv_start_work); k_work_submit(&adv_start_work);
battery_timer_start(); battery_timer_start();
@@ -433,7 +528,7 @@ static void main_s(struct k_timer *timer)
device_on = false; device_on = false;
boot_btn_released = false; boot_btn_released = false;
cnt_s = 0; cnt_s = 0;
sleep_mode_enter(); sleep_mode_enter_reason("button-long-press");
return; return;
} }
} }
@@ -469,6 +564,12 @@ static void timers_init(void)
/* 전원 제어 타이머 */ /* 전원 제어 타이머 */
power_timer_init(); power_timer_init();
/* DFU 업로드 상태를 LED에 반영 */
mgmt_callback_register(&dfu_status_callback);
/* DFU 명령 실패 시 LED 상태 복구 */
mgmt_callback_register(&smp_cmd_status_callback);
} }
/* 전원 버튼 폴링 시작 (5ms 후 main_s 콜백) */ /* 전원 버튼 폴링 시작 (5ms 후 main_s 콜백) */
@@ -477,10 +578,103 @@ static void timers_start(void)
k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT); k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT);
} }
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를 계속 켬 */
dfu_led_active = true;
led_ble_solid();
break;
case MGMT_EVT_OP_IMG_MGMT_DFU_PENDING:
/* 업로드 완료 후 리셋 전까지 초록 LED 유지 */
dfu_led_active = true;
led_ble_solid();
/* DFU 리셋 후 버튼 없이 전원 복구 */
#if NRF_POWER_HAS_GPREGRET
nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, DFU_RESUME_MAGIC);
#endif
break;
case MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED:
/* 실패나 중단 시 일반 광고 LED로 복귀 */
if (dfu_led_active)
{
dfu_led_active = false;
led_set_state(LED_STATE_ADVERTISING);
}
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) static void resume_device_after_soft_reset(void)
{ {
/* 일반 버튼 부팅이면 skip */ /* 일반 버튼 부팅이면 skip */
if (!resume_without_power_button) if (!resume_without_power_button &&
!dfu_confirm_pending_boot &&
!dfu_reset_resume_request)
{ {
return; return;
} }
@@ -492,7 +686,7 @@ static void resume_device_after_soft_reset(void)
m_reset_status = 1; m_reset_status = 1;
/* 정상 ON 상태 서비스 재시작 */ /* 정상 ON 상태 서비스 재시작 */
power_control_handler(ON); power_control_handler(ON, "resume-after-reset");
led_set_state(LED_STATE_ADVERTISING); led_set_state(LED_STATE_ADVERTISING);
battery_timer_start(); battery_timer_start();
k_work_submit(&adv_start_work); k_work_submit(&adv_start_work);
@@ -619,6 +813,8 @@ int main(void)
DBG_CORE(" ble/nus OK\r\n"); DBG_CORE(" ble/nus OK\r\n");
/* 소프트 리셋 후 서비스 복구 */ /* 소프트 리셋 후 서비스 복구 */
resume_device_after_soft_reset(); resume_device_after_soft_reset();
/* BLE 준비까지 확인한 뒤 DFU test 이미지를 확정 */
confirm_running_image();
} }
else else
{ {