500 lines
16 KiB
C
500 lines
16 KiB
C
/*******************************************************************************
|
|
* @file main.c
|
|
* @brief VesiScan-Basic
|
|
******************************************************************************/
|
|
|
|
#include <zephyr/kernel.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"
|
|
#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_QUEUE_DEPTH 8 // RX 명령 링버퍼 깊이(버스트 흡수)
|
|
#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
|
|
|
|
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 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 bool dfu_led_active; // DFU 업로드 중 초록 LED ON
|
|
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_CHUNK_WRITE_COMPLETE |
|
|
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를 둠
|
|
* - 연결 직후 초기화 버스트 등 명령이 몰릴 때 대비해 링버퍼(FIFO)로 흡수
|
|
* - 큐가 가득 차면(BLE_CMD_QUEUE_DEPTH) 그 때만 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 큐 보호
|
|
|
|
// RX 명령 링버퍼: 들어온 순서대로 쌓고 워커가 순차 처리 (head=꺼낼 위치, tail=넣을 위치)
|
|
static uint8_t ble_cmd_q_buf[BLE_CMD_QUEUE_DEPTH][BLE_CMD_MAX_LEN]; // 명령 데이터
|
|
static uint16_t ble_cmd_q_len[BLE_CMD_QUEUE_DEPTH]; // 각 명령 길이
|
|
static uint8_t ble_cmd_q_head; // 다음에 꺼낼 슬롯
|
|
static uint8_t ble_cmd_q_tail; // 다음에 넣을 슬롯
|
|
static uint8_t ble_cmd_q_count; // 큐에 쌓인 명령 수
|
|
|
|
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;
|
|
|
|
// 큐가 빌 때까지 head에서 하나씩 꺼내 순차 처리 (dispatch는 락 밖에서)
|
|
for (;;)
|
|
{
|
|
k_spinlock_key_t key = k_spin_lock(&ble_cmd_lock);
|
|
if (ble_cmd_q_count == 0U)
|
|
{
|
|
k_spin_unlock(&ble_cmd_lock, key);
|
|
break;
|
|
}
|
|
local_len = ble_cmd_q_len[ble_cmd_q_head];
|
|
memcpy(local_buf, ble_cmd_q_buf[ble_cmd_q_head], local_len);
|
|
ble_cmd_q_head = (uint8_t)((ble_cmd_q_head + 1U) % BLE_CMD_QUEUE_DEPTH);
|
|
ble_cmd_q_count--;
|
|
k_spin_unlock(&ble_cmd_lock, key);
|
|
|
|
if (local_len == 0U)
|
|
{
|
|
DBG_ERR("[BLE RX] worker: empty\r\n");
|
|
continue;
|
|
}
|
|
|
|
// 일반 스레드 문맥 명령 처리
|
|
//DBG_CORE("[BLE RX] worker dispatch len=%u\r\n", local_len);
|
|
ble_cmd_dispatch(local_buf, local_len);
|
|
}
|
|
}
|
|
|
|
/* 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 큐 보호: tail 슬롯에 명령 적재 (FIFO)
|
|
k_spinlock_key_t key = k_spin_lock(&ble_cmd_lock);
|
|
if (ble_cmd_q_count >= BLE_CMD_QUEUE_DEPTH)
|
|
{
|
|
k_spin_unlock(&ble_cmd_lock, key);
|
|
DBG_ERR("[BLE RX] drop: queue full\r\n");
|
|
return;
|
|
}
|
|
|
|
// 명령 버퍼 복사
|
|
memcpy(ble_cmd_q_buf[ble_cmd_q_tail], data, len);
|
|
ble_cmd_q_len[ble_cmd_q_tail] = len;
|
|
ble_cmd_q_tail = (uint8_t)((ble_cmd_q_tail + 1U) % BLE_CMD_QUEUE_DEPTH);
|
|
ble_cmd_q_count++;
|
|
k_spin_unlock(&ble_cmd_lock, key);
|
|
|
|
// 명령 처리 work 예약 (워커는 큐가 빌 때까지 처리하므로 중복 예약은 무해)
|
|
// 실패해도 항목은 큐에 유효하게 남아 있고, 다음 RX의 submit이 함께 처리하므로 롤백하지 않음
|
|
int err = k_work_submit_to_queue(&ble_cmd_work_q, &ble_cmd_work);
|
|
if (err < 0)
|
|
{
|
|
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 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);
|
|
}
|
|
|
|
/* 타이머 초기화 */
|
|
static void timers_init(void)
|
|
{
|
|
|
|
// 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);
|
|
|
|
|
|
// DFU 업로드 상태를 LED에 반영
|
|
mgmt_callback_register(&dfu_status_callback);
|
|
|
|
// DFU 명령 실패 시 LED 상태 복구
|
|
mgmt_callback_register(&smp_cmd_status_callback);
|
|
}
|
|
|
|
|
|
static void dfu_watchdog_handler(struct k_work *work)
|
|
{
|
|
ARG_UNUSED(work);
|
|
|
|
if (ble_is_connected())
|
|
{
|
|
DBG_CORE("[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:
|
|
case MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK_WRITE_COMPLETE:
|
|
dfu_led_active = true;
|
|
led_ble_solid(); // DFU 업로드 중에는 초록 LED ON
|
|
// 청크 수신/flash write 완료 때마다 무진행 감시 타이머 리셋
|
|
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 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();
|
|
|
|
// 하드웨어 기본 초기화
|
|
#if defined(CONFIG_BOOTLOADER_MCUBOOT)
|
|
dfu_confirm_pending_boot = !boot_is_img_confirmed();
|
|
#else
|
|
dfu_confirm_pending_boot = false;
|
|
#endif
|
|
power_control_latch_init(resume_without_power_button || dfu_confirm_pending_boot || dfu_reset_resume_request);
|
|
power_control_reset_state();
|
|
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");
|
|
// 기본 하드웨어/센서 초기화
|
|
power_control_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");
|
|
// 소프트 리셋 후 서비스 복구
|
|
if (resume_without_power_button || dfu_confirm_pending_boot || dfu_reset_resume_request)
|
|
{
|
|
power_control_resume_after_reset(boot_reset_reason);
|
|
}
|
|
// 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");
|
|
|
|
// 전원 버튼 상태머신 시작 (부팅 시 버튼이 눌려있는 상태)
|
|
power_control_start();
|
|
|
|
// 메인 루프 - idle
|
|
for (;;)
|
|
{
|
|
k_msleep(100);
|
|
}
|
|
|
|
return 0;
|
|
}
|