DFU
This commit is contained in:
+215
-19
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user