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/sys/printk.h>
#include <zephyr/dfu/mcuboot.h>
#include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
#include <hal/nrf_power.h>
#include "main.h"
@@ -31,6 +32,10 @@ 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 IMG_MGMT_ID_STATE 0U
#define IMG_MGMT_ID_UPLOAD 1U
/*
* BLE 명령 처리 스택
* - piezo sweep
@@ -85,9 +90,42 @@ static bool power_btn_suspended; /* 측정 중 전원 버튼 상태
* 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 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를 둔다.
@@ -224,6 +262,18 @@ 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 |
@@ -242,28 +292,40 @@ static void power_hold_init(void)
{
#if defined(CONFIG_BOOTLOADER_MCUBOOT)
/* DFU 이미지 확인 전 전원 유지 */
bool dfu_confirm_pending = !boot_is_img_confirmed();
dfu_confirm_pending_boot = !boot_is_img_confirmed();
#else
bool dfu_confirm_pending = false;
dfu_confirm_pending_boot = false;
#endif
/* 리셋 복귀/DFU 확인 대기 시 즉시 래치 */
gpio_pin_configure_dt(&power_hold,
(resume_without_power_button || dfu_confirm_pending) ? GPIO_OUTPUT_ACTIVE
: GPIO_OUTPUT_INACTIVE);
(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)
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 → 전원 유지 */
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)
{
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);
power_control_handler(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 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)
{
led_set_state(LED_STATE_POWER_ON);
DBG_PRINTF("[SYS] Sleep\r\n");
k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT);
sleep_mode_enter_reason("sleep_mode_enter");
}
void device_power_off(void)
{
led_set_state(LED_STATE_POWER_OFF);
k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT);
power_off_schedule("device_power_off", 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)
@@ -383,12 +471,19 @@ static void main_s(struct k_timer *timer)
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 */
{
//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;
}
else /* 버튼 계속 누르고 있음 */
@@ -400,7 +495,7 @@ static void main_s(struct k_timer *timer)
device_on = true;
cnt_s = 0; /* 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가
* cnt_s >= 400 조건을 즉시 만족하여 전원 OFF됨 */
power_control_handler(ON);
power_control_handler(ON, "button-2s-latch");
led_set_state(LED_STATE_ADVERTISING);
k_work_submit(&adv_start_work);
battery_timer_start();
@@ -433,7 +528,7 @@ static void main_s(struct k_timer *timer)
device_on = false;
boot_btn_released = false;
cnt_s = 0;
sleep_mode_enter();
sleep_mode_enter_reason("button-long-press");
return;
}
}
@@ -469,6 +564,12 @@ static void timers_init(void)
/* 전원 제어 타이머 */
power_timer_init();
/* DFU 업로드 상태를 LED에 반영 */
mgmt_callback_register(&dfu_status_callback);
/* DFU 명령 실패 시 LED 상태 복구 */
mgmt_callback_register(&smp_cmd_status_callback);
}
/* 전원 버튼 폴링 시작 (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);
}
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)
{
/* 일반 버튼 부팅이면 skip */
if (!resume_without_power_button)
if (!resume_without_power_button &&
!dfu_confirm_pending_boot &&
!dfu_reset_resume_request)
{
return;
}
@@ -492,7 +686,7 @@ static void resume_device_after_soft_reset(void)
m_reset_status = 1;
/* 정상 ON 상태 서비스 재시작 */
power_control_handler(ON);
power_control_handler(ON, "resume-after-reset");
led_set_state(LED_STATE_ADVERTISING);
battery_timer_start();
k_work_submit(&adv_start_work);
@@ -619,6 +813,8 @@ int main(void)
DBG_CORE(" ble/nus OK\r\n");
/* 소프트 리셋 후 서비스 복구 */
resume_device_after_soft_reset();
/* BLE 준비까지 확인한 뒤 DFU test 이미지를 확정 */
confirm_running_image();
}
else
{