Compare commits
3 Commits
master
..
1d11758162
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d11758162 | |||
| a6d1efc089 | |||
| 5eb6b9cd3c |
Vendored
-12
@@ -24,18 +24,6 @@
|
|||||||
"${workspaceFolder}\\tools\\flash_preserve_settings.ps1"
|
"${workspaceFolder}\\tools\\flash_preserve_settings.ps1"
|
||||||
],
|
],
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Flash + Lock (full erase + APPROTECT)",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "powershell",
|
|
||||||
"args": [
|
|
||||||
"-ExecutionPolicy",
|
|
||||||
"Bypass",
|
|
||||||
"-File",
|
|
||||||
"${workspaceFolder}\\tools\\flash_full_erase_lock.ps1"
|
|
||||||
],
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-8
@@ -10,9 +10,6 @@ target_include_directories(app PRIVATE
|
|||||||
src/command
|
src/command
|
||||||
src/command/handlers
|
src/command/handlers
|
||||||
src/ble
|
src/ble
|
||||||
src/power
|
|
||||||
src/dfu
|
|
||||||
src/nvs
|
|
||||||
src/drivers/battery
|
src/drivers/battery
|
||||||
src/drivers/echo_adc
|
src/drivers/echo_adc
|
||||||
src/drivers/led
|
src/drivers/led
|
||||||
@@ -23,20 +20,17 @@ target_include_directories(app PRIVATE
|
|||||||
|
|
||||||
target_sources(app PRIVATE
|
target_sources(app PRIVATE
|
||||||
src/main.c
|
src/main.c
|
||||||
src/nvs/app_nvs.c
|
src/app_nvs.c
|
||||||
src/command/parser.c
|
src/command/parser.c
|
||||||
src/command/ble_cmd_queue.c
|
|
||||||
src/command/cmd_common.c
|
src/command/cmd_common.c
|
||||||
src/command/cmd_table.c
|
src/command/cmd_table.c
|
||||||
src/command/handlers/cmd_device.c
|
src/command/handlers/cmd_device.c
|
||||||
src/command/handlers/cmd_info.c
|
src/command/handlers/cmd_info.c
|
||||||
src/command/handlers/cmd_piezo.c
|
src/command/handlers/cmd_piezo.c
|
||||||
src/command/handlers/cmd_sensor.c
|
src/command/handlers/cmd_sensor.c
|
||||||
src/power/power_control.c
|
src/power_control.c
|
||||||
src/dfu/dfu_manager.c
|
|
||||||
src/measurement/piezo_measure.c
|
src/measurement/piezo_measure.c
|
||||||
src/ble/ble_service.c
|
src/ble/ble_service.c
|
||||||
src/ble/ble_tx_power.c
|
|
||||||
src/drivers/battery/battery_adc.c
|
src/drivers/battery/battery_adc.c
|
||||||
src/drivers/echo_adc/echo_adc.c
|
src/drivers/echo_adc/echo_adc.c
|
||||||
src/drivers/led/led_control.c
|
src/drivers/led/led_control.c
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
source "Kconfig.zephyr"
|
|
||||||
|
|
||||||
menu "VesiScan application options"
|
|
||||||
|
|
||||||
config VESIS_DEBUG_PRINT
|
|
||||||
bool "Enable VesiScan debug print output"
|
|
||||||
default y
|
|
||||||
help
|
|
||||||
Enable DBG_PRINTF, DBG_CORE, and DBG_ERR output through printk().
|
|
||||||
Disable this option to compile out these project debug prints.
|
|
||||||
|
|
||||||
endmenu
|
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
CONFIG_BT_CTLR_TX_PWR_PLUS_8=y
|
CONFIG_BT_CTLR_TX_PWR_PLUS_4=y
|
||||||
CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y
|
CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y
|
||||||
CONFIG_BT_CTLR_CONN_RSSI=y
|
|
||||||
|
|||||||
+22
-11
@@ -1,35 +1,46 @@
|
|||||||
app:
|
|
||||||
address: 0xc200
|
|
||||||
region: flash_primary
|
|
||||||
size: 0x78e00
|
|
||||||
mcuboot:
|
mcuboot:
|
||||||
address: 0x0
|
address: 0x0
|
||||||
region: flash_primary
|
region: flash_primary
|
||||||
size: 0xc000
|
size: 0xc000 # 48 KiB
|
||||||
|
|
||||||
mcuboot_pad:
|
mcuboot_pad:
|
||||||
address: 0xc000
|
address: 0xc000
|
||||||
region: flash_primary
|
region: flash_primary
|
||||||
size: 0x200
|
size: 0x200 # 512 B
|
||||||
|
|
||||||
|
app:
|
||||||
|
address: 0xc200
|
||||||
|
region: flash_primary
|
||||||
|
size: 0x4fe00 # 319.5 KiB = 327,168 B
|
||||||
|
|
||||||
mcuboot_primary:
|
mcuboot_primary:
|
||||||
address: 0xc000
|
address: 0xc000
|
||||||
orig_span: &id001
|
orig_span: &id001
|
||||||
- mcuboot_pad
|
- mcuboot_pad
|
||||||
- app
|
- app
|
||||||
region: flash_primary
|
region: flash_primary
|
||||||
size: 0x79000
|
size: 0x50000 # 320 KiB
|
||||||
span: *id001
|
span: *id001
|
||||||
|
|
||||||
mcuboot_primary_app:
|
mcuboot_primary_app:
|
||||||
address: 0xc200
|
address: 0xc200
|
||||||
orig_span: &id002
|
orig_span: &id002
|
||||||
- app
|
- app
|
||||||
region: flash_primary
|
region: flash_primary
|
||||||
size: 0x78e00
|
size: 0x4fe00 # same as app
|
||||||
span: *id002
|
span: *id002
|
||||||
|
|
||||||
mcuboot_secondary:
|
mcuboot_secondary:
|
||||||
address: 0x85000
|
address: 0x5c000
|
||||||
region: flash_primary
|
region: flash_primary
|
||||||
size: 0x79000
|
size: 0x50000 # 320 KiB
|
||||||
|
|
||||||
|
data_storage:
|
||||||
|
address: 0xac000
|
||||||
|
region: flash_primary
|
||||||
|
size: 0x52000 # 328 KiB
|
||||||
|
|
||||||
settings_storage:
|
settings_storage:
|
||||||
address: 0xfe000
|
address: 0xfe000
|
||||||
region: flash_primary
|
region: flash_primary
|
||||||
size: 0x2000
|
size: 0x2000 # 8 KiB
|
||||||
@@ -5,8 +5,6 @@
|
|||||||
CONFIG_GPIO=y
|
CONFIG_GPIO=y
|
||||||
|
|
||||||
# Debug Message (RTT)
|
# Debug Message (RTT)
|
||||||
# Build-time switch for project DBG_PRINTF/DBG_CORE/DBG_ERR macros.
|
|
||||||
CONFIG_VESIS_DEBUG_PRINT=n
|
|
||||||
# Zephyr logging subsystem enable.
|
# Zephyr logging subsystem enable.
|
||||||
CONFIG_LOG=y
|
CONFIG_LOG=y
|
||||||
# Print logs immediately when log API is called.
|
# Print logs immediately when log API is called.
|
||||||
@@ -94,8 +92,6 @@ CONFIG_BT_SMP_SC_ONLY=y
|
|||||||
CONFIG_BT_SETTINGS=y
|
CONFIG_BT_SETTINGS=y
|
||||||
# Maximum number of stored bonded peers.
|
# Maximum number of stored bonded peers.
|
||||||
CONFIG_BT_MAX_PAIRED=20
|
CONFIG_BT_MAX_PAIRED=20
|
||||||
# Production secure boot: disable flash patching so only signed flash code can run.
|
|
||||||
CONFIG_DISABLE_FLASH_PATCH=y
|
|
||||||
|
|
||||||
# ADC (battery)
|
# ADC (battery)
|
||||||
# Enable Zephyr ADC driver for battery voltage measurement.
|
# Enable Zephyr ADC driver for battery voltage measurement.
|
||||||
@@ -125,7 +121,7 @@ CONFIG_BOOTLOADER_MCUBOOT=y
|
|||||||
# Enable zcbor library used by mcumgr/SMP/DFU CBOR payloads.
|
# Enable zcbor library used by mcumgr/SMP/DFU CBOR payloads.
|
||||||
CONFIG_ZCBOR=y
|
CONFIG_ZCBOR=y
|
||||||
# Version string used by MCUboot imgtool signing and versioned DFU zip naming.
|
# Version string used by MCUboot imgtool signing and versioned DFU zip naming.
|
||||||
CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="0.0.0+0"
|
CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="0.0.2+26"
|
||||||
|
|
||||||
# Internal flash
|
# Internal flash
|
||||||
# Enable internal flash driver.
|
# Enable internal flash driver.
|
||||||
@@ -153,7 +149,7 @@ CONFIG_MCUMGR=y
|
|||||||
# Enable mcumgr/SMP messages over BLE SMP service.
|
# Enable mcumgr/SMP messages over BLE SMP service.
|
||||||
CONFIG_MCUMGR_TRANSPORT_BT=y
|
CONFIG_MCUMGR_TRANSPORT_BT=y
|
||||||
# Require authenticated BLE connection for mcumgr read/write operations.
|
# Require authenticated BLE connection for mcumgr read/write operations.
|
||||||
CONFIG_MCUMGR_TRANSPORT_BT_PERM_RW_AUTHEN=y
|
CONFIG_MCUMGR_TRANSPORT_BT_PERM_RW_AUTHEN=n
|
||||||
# Reassemble SMP packets fragmented over BLE.
|
# Reassemble SMP packets fragmented over BLE.
|
||||||
CONFIG_MCUMGR_TRANSPORT_BT_REASSEMBLY=y
|
CONFIG_MCUMGR_TRANSPORT_BT_REASSEMBLY=y
|
||||||
# mcumgr transport receive buffer size for DFU image chunks.
|
# mcumgr transport receive buffer size for DFU image chunks.
|
||||||
@@ -182,3 +178,8 @@ CONFIG_MCUMGR_GRP_OS=y
|
|||||||
CONFIG_IMG_MANAGER=y
|
CONFIG_IMG_MANAGER=y
|
||||||
# Write uploaded image data to flash as a stream.
|
# Write uploaded image data to flash as a stream.
|
||||||
CONFIG_STREAM_FLASH=y
|
CONFIG_STREAM_FLASH=y
|
||||||
|
|
||||||
|
# Netcore HCI IPC
|
||||||
|
# nRF52840 build does not use network core HCI IPC.
|
||||||
|
#CONFIG_SB_CONFIG_NETCORE_HCI_IPC=y
|
||||||
|
#SB_CONFIG_NETCORE_HCI_IPC=y
|
||||||
|
|||||||
+228
-192
@@ -1,4 +1,4 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* @file ble_service.c
|
* @file ble_service.c
|
||||||
* @brief BLE NUS service module
|
* @brief BLE NUS service module
|
||||||
*
|
*
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/bluetooth/bluetooth.h>
|
#include <zephyr/bluetooth/bluetooth.h>
|
||||||
#include <zephyr/bluetooth/hci.h>
|
#include <zephyr/bluetooth/hci.h>
|
||||||
|
#include <zephyr/bluetooth/hci_vs.h>
|
||||||
|
#include <zephyr/sys/byteorder.h>
|
||||||
#include <zephyr/bluetooth/conn.h>
|
#include <zephyr/bluetooth/conn.h>
|
||||||
#include <zephyr/bluetooth/uuid.h>
|
#include <zephyr/bluetooth/uuid.h>
|
||||||
#include <zephyr/bluetooth/gatt.h>
|
#include <zephyr/bluetooth/gatt.h>
|
||||||
@@ -23,21 +25,40 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
#include "ble_service.h"
|
#include "ble_service.h"
|
||||||
#include "ble_tx_power.h"
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "debug_print.h"
|
#include "debug_print.h"
|
||||||
#include "led_control.h"
|
#include "led_control.h"
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(ble_svc, LOG_LEVEL_INF);
|
LOG_MODULE_REGISTER(ble_svc, LOG_LEVEL_INF);
|
||||||
|
|
||||||
/* BLE service 상태 */
|
/* Module variables */
|
||||||
static struct bt_conn *current_conn;
|
static struct bt_conn *current_conn;
|
||||||
static ble_data_rx_cb_t rx_callback;
|
static ble_data_rx_cb_t rx_callback;
|
||||||
static bool dfu_advertising_mode;
|
static bool dfu_advertising_mode;
|
||||||
static bool advertising_unlimited;
|
static bool advertising_unlimited;
|
||||||
static uint8_t conn_param_update_attempts;
|
static uint8_t conn_param_update_attempts;
|
||||||
static int64_t last_rx_conn_param_update_ms;
|
static int64_t last_rx_conn_param_update_ms;
|
||||||
static uint16_t current_notify_mtu = 23U;
|
|
||||||
|
#define BLE_TX_POWER_NORMAL_DBM 4
|
||||||
|
#define BLE_TX_POWER_STRESS_DBM 8
|
||||||
|
#define BLE_RSSI_MONITOR_ENABLED 0
|
||||||
|
#define BLE_RSSI_STRESS_DBM (-80)
|
||||||
|
#define BLE_RSSI_STRESS_COUNT 3
|
||||||
|
#define BLE_RSSI_MONITOR_INTERVAL_MS 500
|
||||||
|
#define BLE_TX_SLOW_MS 50
|
||||||
|
#define BLE_TX_CRITICAL_MS 100
|
||||||
|
#define BLE_TX_STUCK_MS 200
|
||||||
|
#define BLE_TX_SLOW_COUNT 3
|
||||||
|
#define BLE_TX_BUSY_STRESS_RETRY 4
|
||||||
|
|
||||||
|
extern int bt_hci_get_conn_handle(const struct bt_conn *conn, uint16_t *conn_handle);
|
||||||
|
|
||||||
|
static uint16_t current_conn_handle;
|
||||||
|
static bool current_conn_handle_valid;
|
||||||
|
static int8_t current_tx_power_dbm = BLE_TX_POWER_NORMAL_DBM;
|
||||||
|
static uint8_t low_rssi_count;
|
||||||
|
static uint8_t slow_tx_count;
|
||||||
|
static struct k_work_delayable tx_power_monitor_work;
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_BT_SMP)
|
#if IS_ENABLED(CONFIG_BT_SMP)
|
||||||
#define BLE_REQUIRED_SECURITY_LEVEL BT_SECURITY_L4
|
#define BLE_REQUIRED_SECURITY_LEVEL BT_SECURITY_L4
|
||||||
@@ -56,63 +77,154 @@ static struct k_work adv_restart_work;
|
|||||||
static struct k_work_delayable adv_timeout_work;
|
static struct k_work_delayable adv_timeout_work;
|
||||||
static struct k_work_delayable conn_param_update_work;
|
static struct k_work_delayable conn_param_update_work;
|
||||||
|
|
||||||
/*
|
static int ble_tx_power_write(uint8_t handle_type, uint16_t handle, int8_t dbm,
|
||||||
* Store the UATT MTU used by normal GATT/NUS notifications.
|
const char *reason)
|
||||||
* NUS notifications can carry application payload up to UATT MTU - 3 bytes.
|
|
||||||
*/
|
|
||||||
static void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx)
|
|
||||||
{
|
{
|
||||||
uint16_t uatt = bt_gatt_get_uatt_mtu(conn);
|
struct net_buf *buf;
|
||||||
|
struct net_buf *rsp;
|
||||||
|
struct bt_hci_cp_vs_write_tx_power_level *cp;
|
||||||
|
struct bt_hci_rp_vs_write_tx_power_level *rp;
|
||||||
|
int err;
|
||||||
|
|
||||||
if (uatt != 0U)
|
buf = bt_hci_cmd_alloc(K_NO_WAIT);
|
||||||
|
if (buf == NULL)
|
||||||
{
|
{
|
||||||
current_notify_mtu = uatt;
|
return -ENOBUFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp = net_buf_add(buf, sizeof(*cp));
|
||||||
|
cp->handle_type = handle_type;
|
||||||
|
cp->handle = sys_cpu_to_le16(handle);
|
||||||
|
cp->tx_power_level = dbm;
|
||||||
|
|
||||||
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL, buf, &rsp);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
DBG_ERR("[BLE] TX power set fail reason=%s err=%d\r\n", reason, err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
rp = (void *)rsp->data;
|
||||||
|
if (rp->status != 0U)
|
||||||
|
{
|
||||||
|
err = -EIO;
|
||||||
|
DBG_ERR("[BLE] TX power set rejected reason=%s status=0x%02x\r\n",
|
||||||
|
reason, rp->status);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
current_notify_mtu = (tx < rx) ? tx : rx;
|
current_tx_power_dbm = rp->selected_tx_power;
|
||||||
|
DBG_CORE("[BLE] TX power %d dBm reason=%s\r\n",
|
||||||
|
current_tx_power_dbm, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
DBG_PRINTF("[BLE] ATT MTU updated tx=%u rx=%u uatt=%u used=%u notify_payload=%u\r\n",
|
net_buf_unref(rsp);
|
||||||
tx, rx, uatt, current_notify_mtu,
|
return err;
|
||||||
(current_notify_mtu > 3U) ? (current_notify_mtu - 3U) : 0U);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int ble_set_conn_tx_power(int8_t dbm, const char *reason)
|
||||||
/* MTU Exchange 연결 직후 요청 */
|
|
||||||
static void mtu_exchange_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_exchange_params *params)
|
|
||||||
{
|
{
|
||||||
uint16_t uatt;
|
if (!current_conn_handle_valid || current_tx_power_dbm == dbm)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
ARG_UNUSED(params);
|
return ble_tx_power_write(BT_HCI_VS_LL_HANDLE_TYPE_CONN,
|
||||||
|
current_conn_handle,
|
||||||
|
dbm,
|
||||||
|
reason ? reason : "unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ble_tx_power_stress(const char *reason)
|
||||||
|
{
|
||||||
|
(void)ble_set_conn_tx_power(BLE_TX_POWER_STRESS_DBM, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ble_read_conn_rssi(int8_t *rssi)
|
||||||
|
{
|
||||||
|
struct net_buf *buf;
|
||||||
|
struct net_buf *rsp;
|
||||||
|
struct bt_hci_cp_read_rssi *cp;
|
||||||
|
struct bt_hci_rp_read_rssi *rp;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!current_conn_handle_valid || rssi == NULL)
|
||||||
|
{
|
||||||
|
return -ENOTCONN;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = bt_hci_cmd_alloc(K_NO_WAIT);
|
||||||
|
if (buf == NULL)
|
||||||
|
{
|
||||||
|
return -ENOBUFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
cp = net_buf_add(buf, sizeof(*cp));
|
||||||
|
cp->handle = sys_cpu_to_le16(current_conn_handle);
|
||||||
|
|
||||||
|
err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_RSSI, buf, &rsp);
|
||||||
if (err)
|
if (err)
|
||||||
{
|
{
|
||||||
DBG_ERR("[BLE] MTU exchange failed err=%u\r\n", err);
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
rp = (void *)rsp->data;
|
||||||
|
if (rp->status != 0U)
|
||||||
|
{
|
||||||
|
err = -EIO;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*rssi = rp->rssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
net_buf_unref(rsp);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tx_power_monitor_handler(struct k_work *work)
|
||||||
|
{
|
||||||
|
int8_t rssi = 0;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
ARG_UNUSED(work);
|
||||||
|
|
||||||
|
if (!current_conn_handle_valid)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uatt = bt_gatt_get_uatt_mtu(conn);
|
if (!BLE_RSSI_MONITOR_ENABLED)
|
||||||
if (uatt != 0U)
|
|
||||||
{
|
{
|
||||||
current_notify_mtu = uatt;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DBG_PRINTF("[BLE] MTU exchange complete uatt=%u notify_payload=%u\r\n",
|
err = ble_read_conn_rssi(&rssi);
|
||||||
uatt,
|
if (err)
|
||||||
(uatt > 3U) ? (uatt - 3U) : 0U);
|
{
|
||||||
|
ble_tx_power_stress("rssi-silence");
|
||||||
|
}
|
||||||
|
else if (rssi <= BLE_RSSI_STRESS_DBM)
|
||||||
|
{
|
||||||
|
if (low_rssi_count < UINT8_MAX)
|
||||||
|
{
|
||||||
|
low_rssi_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct bt_gatt_exchange_params mtu_exchange_params =
|
if (low_rssi_count >= BLE_RSSI_STRESS_COUNT)
|
||||||
{
|
{
|
||||||
.func = mtu_exchange_cb,
|
ble_tx_power_stress("rssi-low");
|
||||||
};
|
}
|
||||||
static struct bt_gatt_cb gatt_callbacks =
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
.att_mtu_updated = att_mtu_updated,
|
low_rssi_count = 0U;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
k_work_schedule(&tx_power_monitor_work,
|
||||||
|
K_MSEC(BLE_RSSI_MONITOR_INTERVAL_MS));
|
||||||
|
}
|
||||||
|
|
||||||
/* 로컬 BLE MAC 주소 로그 */
|
|
||||||
static void ble_log_local_identity(void)
|
static void ble_log_local_identity(void)
|
||||||
{
|
{
|
||||||
bt_addr_le_t addr = {0};
|
bt_addr_le_t addr = {0};
|
||||||
@@ -131,18 +243,15 @@ static void ble_log_local_identity(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_BT_SMP)
|
#if IS_ENABLED(CONFIG_BT_SMP)
|
||||||
/* NUS 접근 전 보안 level 확인 */
|
|
||||||
static bool ble_conn_is_secure(const struct bt_conn *conn)
|
static bool ble_conn_is_secure(const struct bt_conn *conn)
|
||||||
{
|
{
|
||||||
return (conn != NULL) && (bt_conn_get_security(conn) >= BLE_REQUIRED_SECURITY_LEVEL);
|
return (conn != NULL) && (bt_conn_get_security(conn) >= BLE_REQUIRED_SECURITY_LEVEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 6자리 ASCII passkey를 숫자로 변환 */
|
|
||||||
static int ble_passkey_to_uint(const char *passkey, unsigned int *value)
|
static int ble_passkey_to_uint(const char *passkey, unsigned int *value)
|
||||||
{
|
{
|
||||||
unsigned int parsed = 0U;
|
unsigned int parsed = 0U;
|
||||||
|
|
||||||
// 입력 포인터 방어
|
|
||||||
if ((passkey == NULL) || (value == NULL))
|
if ((passkey == NULL) || (value == NULL))
|
||||||
{
|
{
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
@@ -150,7 +259,6 @@ static int ble_passkey_to_uint(const char *passkey, unsigned int *value)
|
|||||||
|
|
||||||
for (size_t i = 0; i < PASSKEY_LENGTH; i++)
|
for (size_t i = 0; i < PASSKEY_LENGTH; i++)
|
||||||
{
|
{
|
||||||
// 숫자 6자리만 허용
|
|
||||||
if ((passkey[i] < '0') || (passkey[i] > '9'))
|
if ((passkey[i] < '0') || (passkey[i] > '9'))
|
||||||
{
|
{
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
@@ -163,7 +271,6 @@ static int ble_passkey_to_uint(const char *passkey, unsigned int *value)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 저장된 passkey 검증, 실패 시 기본값 사용 */
|
|
||||||
static int ble_security_configure(void)
|
static int ble_security_configure(void)
|
||||||
{
|
{
|
||||||
unsigned int passkey;
|
unsigned int passkey;
|
||||||
@@ -187,7 +294,6 @@ static int ble_security_configure(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Just Works/passkey pairing 요청 자동 승인 */
|
|
||||||
static void auth_pairing_confirm(struct bt_conn *conn)
|
static void auth_pairing_confirm(struct bt_conn *conn)
|
||||||
{
|
{
|
||||||
int err = bt_conn_auth_pairing_confirm(conn);
|
int err = bt_conn_auth_pairing_confirm(conn);
|
||||||
@@ -201,7 +307,6 @@ static void auth_pairing_confirm(struct bt_conn *conn)
|
|||||||
DBG_CORE("[BLE] Pairing confirmed\r\n");
|
DBG_CORE("[BLE] Pairing confirmed\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* pairing 취소 로그 */
|
|
||||||
static void auth_cancel(struct bt_conn *conn)
|
static void auth_cancel(struct bt_conn *conn)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(conn);
|
ARG_UNUSED(conn);
|
||||||
@@ -209,7 +314,6 @@ static void auth_cancel(struct bt_conn *conn)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_BT_APP_PASSKEY)
|
#if IS_ENABLED(CONFIG_BT_APP_PASSKEY)
|
||||||
/* Zephyr pairing callback에 현재 passkey 제공 */
|
|
||||||
static uint32_t auth_app_passkey(struct bt_conn *conn)
|
static uint32_t auth_app_passkey(struct bt_conn *conn)
|
||||||
{
|
{
|
||||||
unsigned int passkey;
|
unsigned int passkey;
|
||||||
@@ -226,7 +330,6 @@ static uint32_t auth_app_passkey(struct bt_conn *conn)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* pairing 처리 callback 묶음 */
|
|
||||||
static struct bt_conn_auth_cb auth_cb =
|
static struct bt_conn_auth_cb auth_cb =
|
||||||
{
|
{
|
||||||
.pairing_confirm = auth_pairing_confirm,
|
.pairing_confirm = auth_pairing_confirm,
|
||||||
@@ -236,28 +339,24 @@ static struct bt_conn_auth_cb auth_cb =
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/* pairing 성공 로그 */
|
|
||||||
static void auth_pairing_complete(struct bt_conn *conn, bool bonded)
|
static void auth_pairing_complete(struct bt_conn *conn, bool bonded)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(conn);
|
ARG_UNUSED(conn);
|
||||||
DBG_CORE("[BLE] Pairing complete bonded=%u\r\n", bonded ? 1U : 0U);
|
DBG_CORE("[BLE] Pairing complete bonded=%u\r\n", bonded ? 1U : 0U);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* pairing 실패 시 연결 종료 */
|
|
||||||
static void auth_pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
|
static void auth_pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
|
||||||
{
|
{
|
||||||
DBG_ERR("[BLE] Pairing failed reason=%u\r\n", reason);
|
DBG_ERR("[BLE] Pairing failed reason=%u\r\n", reason);
|
||||||
(void)bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
|
(void)bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* pairing 결과 callback 묶음 */
|
|
||||||
static struct bt_conn_auth_info_cb auth_info_cb =
|
static struct bt_conn_auth_info_cb auth_info_cb =
|
||||||
{
|
{
|
||||||
.pairing_complete = auth_pairing_complete,
|
.pairing_complete = auth_pairing_complete,
|
||||||
.pairing_failed = auth_pairing_failed,
|
.pairing_failed = auth_pairing_failed,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* BLE 보안 callback 등록, passkey 설정 */
|
|
||||||
static int ble_security_init(void)
|
static int ble_security_init(void)
|
||||||
{
|
{
|
||||||
int err = bt_conn_auth_cb_register(&auth_cb);
|
int err = bt_conn_auth_cb_register(&auth_cb);
|
||||||
@@ -279,7 +378,6 @@ static int ble_security_init(void)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* callback context 밖에서 advertising 재시작 */
|
|
||||||
static void adv_restart_handler(struct k_work *work)
|
static void adv_restart_handler(struct k_work *work)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(work);
|
ARG_UNUSED(work);
|
||||||
@@ -287,30 +385,26 @@ static void adv_restart_handler(struct k_work *work)
|
|||||||
led_set_state(LED_STATE_ADVERTISING);
|
led_set_state(LED_STATE_ADVERTISING);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 연결 해제 후 재광고 정책
|
|
||||||
* - 앱이 연결을 정상 종료(0x13)한 경우만 10분 타임아웃 후 자동 전원 OFF
|
|
||||||
* - 그 외(거리 이탈, 링크 타임아웃, 폰 전원 OFF, 페어링/보안 실패 등)는 재연결 대기를 위해 무제한 광고
|
|
||||||
*/
|
|
||||||
static bool ble_disconnect_reason_uses_unlimited_adv(uint8_t reason)
|
static bool ble_disconnect_reason_uses_unlimited_adv(uint8_t reason)
|
||||||
{
|
{
|
||||||
switch (reason)
|
switch (reason)
|
||||||
{
|
{
|
||||||
case BT_HCI_ERR_REMOTE_USER_TERM_CONN: // 0x13 앱이 연결을 정상 종료
|
case BT_HCI_ERR_CONN_TIMEOUT:
|
||||||
return false; // 10분 타임아웃 후 전원 OFF
|
case BT_HCI_ERR_LL_RESP_TIMEOUT:
|
||||||
|
case BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL:
|
||||||
|
case BT_HCI_ERR_CONN_FAIL_TO_ESTAB:
|
||||||
|
return true;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return true; // 그 외 전부 → 무제한 광고
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 광고 timeout 시 자동 전원 OFF 판단 */
|
|
||||||
static void adv_timeout_handler(struct k_work *work)
|
static void adv_timeout_handler(struct k_work *work)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(work);
|
ARG_UNUSED(work);
|
||||||
|
|
||||||
// 미연결 상태에서만 발동하므로 DFU 광고도 동일하게 10분 후 종료(연결 중이면 발동 안 함)
|
if (!ble_connection_st && !advertising_unlimited && !dfu_advertising_mode)
|
||||||
if (!ble_connection_st && !advertising_unlimited)
|
|
||||||
{
|
{
|
||||||
DBG_PRINTF("[BLE] Advertising timeout\r\n");
|
DBG_PRINTF("[BLE] Advertising timeout\r\n");
|
||||||
(void)ble_advertising_stop();
|
(void)ble_advertising_stop();
|
||||||
@@ -318,14 +412,14 @@ static void adv_timeout_handler(struct k_work *work)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 현재 연결 파라미터가 목표값인지 비교 */
|
|
||||||
static bool ble_conn_param_is_preferred(uint16_t interval, uint16_t latency,
|
static bool ble_conn_param_is_preferred(uint16_t interval, uint16_t latency,
|
||||||
uint16_t timeout)
|
uint16_t timeout)
|
||||||
{
|
{
|
||||||
return (interval == BLE_MIN_CONN_INTERVAL) && (latency == BLE_SLAVE_LATENCY) && (timeout == BLE_CONN_SUP_TIMEOUT);
|
return (interval == BLE_MIN_CONN_INTERVAL) &&
|
||||||
|
(latency == BLE_SLAVE_LATENCY) &&
|
||||||
|
(timeout == BLE_CONN_SUP_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 15ms interval 목표로 connection parameter update 요청 */
|
|
||||||
static int ble_request_preferred_conn_params(bool count_attempt)
|
static int ble_request_preferred_conn_params(bool count_attempt)
|
||||||
{
|
{
|
||||||
struct bt_le_conn_param conn_param =
|
struct bt_le_conn_param conn_param =
|
||||||
@@ -335,51 +429,22 @@ static int ble_request_preferred_conn_params(bool count_attempt)
|
|||||||
.latency = BLE_SLAVE_LATENCY,
|
.latency = BLE_SLAVE_LATENCY,
|
||||||
.timeout = BLE_CONN_SUP_TIMEOUT,
|
.timeout = BLE_CONN_SUP_TIMEOUT,
|
||||||
};
|
};
|
||||||
struct bt_conn_info info;
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
// 연결 없으면 요청 불가
|
|
||||||
if (current_conn == NULL)
|
if (current_conn == NULL)
|
||||||
{
|
{
|
||||||
return -ENOTCONN;
|
return -ENOTCONN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 현재 체결된 파라미터를 조회해서, 이미 목표값이면 재요청/재시도 불필요
|
|
||||||
err = bt_conn_get_info(current_conn, &info);
|
|
||||||
if ((err == 0) && (info.type == BT_CONN_TYPE_LE))
|
|
||||||
{
|
|
||||||
uint16_t cur_interval = (uint16_t)(info.le.interval_us / 1250U);
|
|
||||||
uint16_t cur_latency = info.le.latency;
|
|
||||||
uint16_t cur_timeout = info.le.timeout;
|
|
||||||
|
|
||||||
DBG_PRINTF("[BLE] Current params: interval=%u(%u.%01ums) latency=%u timeout=%u(%ums)\r\n",
|
|
||||||
cur_interval,
|
|
||||||
(cur_interval * 125U) / 100U,
|
|
||||||
((cur_interval * 125U) % 100U) / 10U,
|
|
||||||
cur_latency,
|
|
||||||
cur_timeout,
|
|
||||||
cur_timeout * 10U);
|
|
||||||
|
|
||||||
if (ble_conn_param_is_preferred(cur_interval, cur_latency, cur_timeout))
|
|
||||||
{
|
|
||||||
DBG_PRINTF("[BLE] Already at preferred params, skip request\r\n");
|
|
||||||
return -EALREADY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 예약 재시도에서만 attempt 증가
|
|
||||||
if (count_attempt)
|
if (count_attempt)
|
||||||
{
|
{
|
||||||
conn_param_update_attempts++;
|
conn_param_update_attempts++;
|
||||||
}
|
}
|
||||||
|
|
||||||
DBG_PRINTF("[BLE] Request params: interval=%u(%u.%01ums) latency=%u timeout=%u(%ums) attempt=%u\r\n",
|
DBG_PRINTF("[BLE] Request params: interval=%u latency=%u timeout=%u attempt=%u\r\n",
|
||||||
BLE_MIN_CONN_INTERVAL,
|
BLE_MIN_CONN_INTERVAL,
|
||||||
(BLE_MIN_CONN_INTERVAL * 125U) / 100U,
|
|
||||||
((BLE_MIN_CONN_INTERVAL * 125U) % 100U) / 10U,
|
|
||||||
BLE_SLAVE_LATENCY,
|
BLE_SLAVE_LATENCY,
|
||||||
BLE_CONN_SUP_TIMEOUT,
|
BLE_CONN_SUP_TIMEOUT,
|
||||||
BLE_CONN_SUP_TIMEOUT * 10U,
|
|
||||||
conn_param_update_attempts);
|
conn_param_update_attempts);
|
||||||
|
|
||||||
err = bt_conn_le_param_update(current_conn, &conn_param);
|
err = bt_conn_le_param_update(current_conn, &conn_param);
|
||||||
@@ -392,7 +457,6 @@ static int ble_request_preferred_conn_params(bool count_attempt)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* connection parameter 재시도 work */
|
|
||||||
static void conn_param_update_handler(struct k_work *work)
|
static void conn_param_update_handler(struct k_work *work)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(work);
|
ARG_UNUSED(work);
|
||||||
@@ -402,19 +466,21 @@ static void conn_param_update_handler(struct k_work *work)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// central이 계속 거부하면 최대 횟수에서 중지
|
|
||||||
if (conn_param_update_attempts >= BLE_CONN_PARAM_MAX_ATTEMPTS)
|
if (conn_param_update_attempts >= BLE_CONN_PARAM_MAX_ATTEMPTS)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ble_request_preferred_conn_params(true) == 0 && conn_param_update_attempts < BLE_CONN_PARAM_MAX_ATTEMPTS)
|
if (ble_request_preferred_conn_params(true) == 0 &&
|
||||||
|
conn_param_update_attempts < BLE_CONN_PARAM_MAX_ATTEMPTS)
|
||||||
{
|
{
|
||||||
k_work_schedule(&conn_param_update_work, K_MSEC(BLE_CONN_PARAM_RETRY_INTERVAL_MS));
|
k_work_schedule(&conn_param_update_work,
|
||||||
|
K_MSEC(BLE_CONN_PARAM_RETRY_INTERVAL_MS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Advertising scan response data */
|
/* Advertising data */
|
||||||
|
|
||||||
static const struct bt_data sd[] =
|
static const struct bt_data sd[] =
|
||||||
{
|
{
|
||||||
// 평상시 Web Bluetooth 앱이 찾을 수 있도록 NUS UUID 광고
|
// 평상시 Web Bluetooth 앱이 찾을 수 있도록 NUS UUID 광고
|
||||||
@@ -427,7 +493,7 @@ static const struct bt_data sd_dfu[] =
|
|||||||
BT_DATA_BYTES(BT_DATA_UUID128_SOME, SMP_BT_SVC_UUID_VAL),
|
BT_DATA_BYTES(BT_DATA_UUID128_SOME, SMP_BT_SVC_UUID_VAL),
|
||||||
};
|
};
|
||||||
|
|
||||||
/* BLE 연결 완료 처리 */
|
/* Connection callbacks */
|
||||||
static void connected(struct bt_conn *conn, uint8_t err)
|
static void connected(struct bt_conn *conn, uint8_t err)
|
||||||
{
|
{
|
||||||
if (err)
|
if (err)
|
||||||
@@ -439,28 +505,24 @@ static void connected(struct bt_conn *conn, uint8_t err)
|
|||||||
|
|
||||||
current_conn = bt_conn_ref(conn);
|
current_conn = bt_conn_ref(conn);
|
||||||
ble_connection_st = true;
|
ble_connection_st = true;
|
||||||
ble_tx_power_on_connected(conn); // TX power/RSSI 상태는 별도 모듈에서 관리
|
current_conn_handle_valid = (bt_hci_get_conn_handle(conn, ¤t_conn_handle) == 0);
|
||||||
|
current_tx_power_dbm = BLE_TX_POWER_NORMAL_DBM;
|
||||||
|
low_rssi_count = 0U;
|
||||||
|
slow_tx_count = 0U;
|
||||||
|
(void)ble_set_conn_tx_power(BLE_TX_POWER_NORMAL_DBM, "connected");
|
||||||
|
if (BLE_RSSI_MONITOR_ENABLED)
|
||||||
|
{
|
||||||
|
k_work_schedule(&tx_power_monitor_work, K_MSEC(BLE_RSSI_MONITOR_INTERVAL_MS));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 연결 정보 출력 */
|
||||||
char addr_str[BT_ADDR_LE_STR_LEN];
|
char addr_str[BT_ADDR_LE_STR_LEN];
|
||||||
bt_addr_le_to_str(bt_conn_get_dst(conn), addr_str, sizeof(addr_str));
|
bt_addr_le_to_str(bt_conn_get_dst(conn), addr_str, sizeof(addr_str));
|
||||||
DBG_CORE("[BLE] Peer: %s\r\n", addr_str); // 연결 정보 출력
|
DBG_CORE("[BLE] Peer: %s\r\n", addr_str);
|
||||||
|
|
||||||
|
|
||||||
err = bt_gatt_exchange_mtu(conn, &mtu_exchange_params);
|
|
||||||
if (err)
|
|
||||||
{
|
|
||||||
DBG_ERR("[BLE] MTU exchange request failed err=%d\r\n", err);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DBG_PRINTF("[BLE] MTU exchange requested\r\n");
|
|
||||||
}
|
|
||||||
conn_param_update_attempts = 0U;
|
conn_param_update_attempts = 0U;
|
||||||
last_rx_conn_param_update_ms = 0;
|
last_rx_conn_param_update_ms = 0;
|
||||||
|
|
||||||
// 연결 직후 약간 기다린 뒤 preferred parameter 요청
|
|
||||||
k_work_schedule(&conn_param_update_work, K_MSEC(BLE_CONN_PARAM_FIRST_DELAY_MS));
|
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_BT_SMP)
|
#if IS_ENABLED(CONFIG_BT_SMP)
|
||||||
err = bt_conn_set_security(conn, BLE_REQUIRED_SECURITY_LEVEL);
|
err = bt_conn_set_security(conn, BLE_REQUIRED_SECURITY_LEVEL);
|
||||||
if (err)
|
if (err)
|
||||||
@@ -476,13 +538,16 @@ static void connected(struct bt_conn *conn, uint8_t err)
|
|||||||
DBG_CORE("[BLE] Connected\r\n");
|
DBG_CORE("[BLE] Connected\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* BLE 연결 해제 처리 */
|
|
||||||
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
static void disconnected(struct bt_conn *conn, uint8_t reason)
|
||||||
{
|
{
|
||||||
k_work_cancel_delayable(&conn_param_update_work);
|
k_work_cancel_delayable(&conn_param_update_work);
|
||||||
ble_tx_power_on_disconnected();
|
k_work_cancel_delayable(&tx_power_monitor_work);
|
||||||
conn_param_update_attempts = 0U;
|
conn_param_update_attempts = 0U;
|
||||||
last_rx_conn_param_update_ms = 0;
|
last_rx_conn_param_update_ms = 0;
|
||||||
|
current_conn_handle_valid = false;
|
||||||
|
current_tx_power_dbm = BLE_TX_POWER_NORMAL_DBM;
|
||||||
|
low_rssi_count = 0U;
|
||||||
|
slow_tx_count = 0U;
|
||||||
|
|
||||||
if (current_conn)
|
if (current_conn)
|
||||||
{
|
{
|
||||||
@@ -491,16 +556,7 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ble_connection_st = false;
|
ble_connection_st = false;
|
||||||
current_notify_mtu = 23U;
|
|
||||||
if (dfu_advertising_mode)
|
|
||||||
{
|
|
||||||
// DFU 광고는 미연결 10분 타임아웃 적용
|
|
||||||
advertising_unlimited = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
advertising_unlimited = ble_disconnect_reason_uses_unlimited_adv(reason);
|
advertising_unlimited = ble_disconnect_reason_uses_unlimited_adv(reason);
|
||||||
}
|
|
||||||
DBG_CORE("[BLE] Disconnected reason=0x%02x adv=%s\r\n",
|
DBG_CORE("[BLE] Disconnected reason=0x%02x adv=%s\r\n",
|
||||||
reason,
|
reason,
|
||||||
advertising_unlimited ? "unlimited" : "10min");
|
advertising_unlimited ? "unlimited" : "10min");
|
||||||
@@ -509,7 +565,6 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
|
|||||||
k_work_submit(&adv_restart_work);
|
k_work_submit(&adv_restart_work);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* central의 connection parameter 요청 허용 */
|
|
||||||
static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
|
static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(conn);
|
ARG_UNUSED(conn);
|
||||||
@@ -518,33 +573,23 @@ static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* connection parameter 변경 결과 확인 */
|
static void le_param_updated(struct bt_conn *conn, uint16_t interval,
|
||||||
static void le_param_updated(struct bt_conn *conn, uint16_t interval, uint16_t latency, uint16_t timeout)
|
uint16_t latency, uint16_t timeout)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(conn);
|
ARG_UNUSED(conn);
|
||||||
|
|
||||||
DBG_PRINTF("[BLE] Params: interval=%u(%u.%01ums) latency=%u timeout=%u(%ums)\r\n",
|
DBG_PRINTF("[BLE] Params: interval=%d latency=%d timeout=%d\r\n",
|
||||||
interval,
|
interval, latency, timeout);
|
||||||
(interval * 125U) / 100U,
|
|
||||||
((interval * 125U) % 100U) / 10U,
|
|
||||||
latency,
|
|
||||||
timeout,
|
|
||||||
timeout * 10U);
|
|
||||||
|
|
||||||
// 목표값 도달 시 재시도 work 중지
|
|
||||||
if (ble_conn_param_is_preferred(interval, latency, timeout))
|
if (ble_conn_param_is_preferred(interval, latency, timeout))
|
||||||
{
|
{
|
||||||
k_work_cancel_delayable(&conn_param_update_work);
|
k_work_cancel_delayable(&conn_param_update_work);
|
||||||
}
|
}
|
||||||
else if (conn_param_update_attempts < BLE_CONN_PARAM_MAX_ATTEMPTS)
|
|
||||||
{
|
|
||||||
k_work_schedule(&conn_param_update_work, K_MSEC(BLE_CONN_PARAM_RETRY_INTERVAL_MS));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_BT_SMP)
|
#if IS_ENABLED(CONFIG_BT_SMP)
|
||||||
/* BLE security level 변경 결과 확인 */
|
static void security_changed(struct bt_conn *conn, bt_security_t level,
|
||||||
static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err)
|
enum bt_security_err err)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(conn);
|
ARG_UNUSED(conn);
|
||||||
|
|
||||||
@@ -569,7 +614,7 @@ BT_CONN_CB_DEFINE(conn_callbacks) =
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/* NUS RX: 보안 확인 후 app parser로 전달 */
|
/* NUS callbacks */
|
||||||
static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len)
|
static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len)
|
||||||
{
|
{
|
||||||
#if IS_ENABLED(CONFIG_BT_SMP)
|
#if IS_ENABLED(CONFIG_BT_SMP)
|
||||||
@@ -597,7 +642,6 @@ static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NUS TX 완료 callback */
|
|
||||||
static void nus_sent(struct bt_conn *conn)
|
static void nus_sent(struct bt_conn *conn)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(conn);
|
ARG_UNUSED(conn);
|
||||||
@@ -612,7 +656,7 @@ static struct bt_nus_cb nus_cb =
|
|||||||
.sent = nus_sent,
|
.sent = nus_sent,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* BLE stack, security, NUS service 초기화 */
|
/* Public functions */
|
||||||
int ble_service_init(ble_data_rx_cb_t rx_cb)
|
int ble_service_init(ble_data_rx_cb_t rx_cb)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
@@ -624,8 +668,9 @@ int ble_service_init(ble_data_rx_cb_t rx_cb)
|
|||||||
k_work_init(&adv_restart_work, adv_restart_handler);
|
k_work_init(&adv_restart_work, adv_restart_handler);
|
||||||
k_work_init_delayable(&adv_timeout_work, adv_timeout_handler);
|
k_work_init_delayable(&adv_timeout_work, adv_timeout_handler);
|
||||||
k_work_init_delayable(&conn_param_update_work, conn_param_update_handler);
|
k_work_init_delayable(&conn_param_update_work, conn_param_update_handler);
|
||||||
ble_tx_power_init();
|
|
||||||
// BLE stack 활성화
|
k_work_init_delayable(&tx_power_monitor_work, tx_power_monitor_handler);
|
||||||
|
// Enable BLE stack
|
||||||
err = bt_enable(NULL);
|
err = bt_enable(NULL);
|
||||||
if (err)
|
if (err)
|
||||||
{
|
{
|
||||||
@@ -646,7 +691,6 @@ int ble_service_init(ble_data_rx_cb_t rx_cb)
|
|||||||
|
|
||||||
ble_log_local_identity();
|
ble_log_local_identity();
|
||||||
|
|
||||||
bt_gatt_cb_register(&gatt_callbacks);
|
|
||||||
#if IS_ENABLED(CONFIG_BT_SMP)
|
#if IS_ENABLED(CONFIG_BT_SMP)
|
||||||
err = ble_security_init();
|
err = ble_security_init();
|
||||||
if (err)
|
if (err)
|
||||||
@@ -655,7 +699,7 @@ int ble_service_init(ble_data_rx_cb_t rx_cb)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// NUS service 초기화
|
// Initialize NUS
|
||||||
err = bt_nus_init(&nus_cb);
|
err = bt_nus_init(&nus_cb);
|
||||||
if (err)
|
if (err)
|
||||||
{
|
{
|
||||||
@@ -667,7 +711,6 @@ int ble_service_init(ble_data_rx_cb_t rx_cb)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 현재 모드에 맞는 advertising 시작 */
|
|
||||||
int ble_advertising_start(void)
|
int ble_advertising_start(void)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
@@ -701,10 +744,8 @@ int ble_advertising_start(void)
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
ble_tx_power_apply_advertising();
|
|
||||||
|
|
||||||
k_work_cancel_delayable(&adv_timeout_work);
|
k_work_cancel_delayable(&adv_timeout_work);
|
||||||
if (!advertising_unlimited) // DFU 광고 포함, 미연결 10분 시 전원 OFF
|
if (!advertising_unlimited && !dfu_advertising_mode)
|
||||||
{
|
{
|
||||||
k_work_schedule(&adv_timeout_work, K_MSEC(APP_ADV_DURATION * 10));
|
k_work_schedule(&adv_timeout_work, K_MSEC(APP_ADV_DURATION * 10));
|
||||||
}
|
}
|
||||||
@@ -713,7 +754,6 @@ int ble_advertising_start(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* advertising 중지, timeout work 취소 */
|
|
||||||
int ble_advertising_stop(void)
|
int ble_advertising_stop(void)
|
||||||
{
|
{
|
||||||
int err = bt_le_adv_stop();
|
int err = bt_le_adv_stop();
|
||||||
@@ -728,7 +768,6 @@ int ble_advertising_stop(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DFU 모드 진입, SMP advertising 준비 */
|
|
||||||
int ble_dfu_advertising_enable(void)
|
int ble_dfu_advertising_enable(void)
|
||||||
{
|
{
|
||||||
if (dfu_advertising_mode)
|
if (dfu_advertising_mode)
|
||||||
@@ -756,25 +795,11 @@ int ble_dfu_advertising_enable(void)
|
|||||||
return ble_advertising_start();
|
return ble_advertising_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DFU 진입용 disconnect */
|
|
||||||
bool ble_dfu_advertising_is_enabled(void)
|
bool ble_dfu_advertising_is_enabled(void)
|
||||||
{
|
{
|
||||||
return dfu_advertising_mode;
|
return dfu_advertising_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DFU watchdog: 현재 연결 강제 disconnect */
|
|
||||||
int ble_disconnect_active(void)
|
|
||||||
{
|
|
||||||
// 연결 없으면 요청 불가
|
|
||||||
if (current_conn == NULL)
|
|
||||||
{
|
|
||||||
return -ENOTCONN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bt_conn_disconnect(current_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* NUS TX: 직렬화, retry, 완료 대기 */
|
|
||||||
int ble_data_send(const uint8_t *data, uint16_t len)
|
int ble_data_send(const uint8_t *data, uint16_t len)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
@@ -798,9 +823,7 @@ int ble_data_send(const uint8_t *data, uint16_t len)
|
|||||||
k_mutex_lock(&nus_tx_lock, K_FOREVER);
|
k_mutex_lock(&nus_tx_lock, K_FOREVER);
|
||||||
|
|
||||||
// 혹시 남아 있는 완료 신호가 있으면 비우고 이번 전송 전용 상태로 만듦
|
// 혹시 남아 있는 완료 신호가 있으면 비우고 이번 전송 전용 상태로 만듦
|
||||||
while (k_sem_take(&nus_tx_done_sem, K_NO_WAIT) == 0)
|
while (k_sem_take(&nus_tx_done_sem, K_NO_WAIT) == 0) {
|
||||||
{
|
|
||||||
// stale completion 비우기
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//DBG_CORE("[NUS TX] send len=%u\r\n", len);
|
//DBG_CORE("[NUS TX] send len=%u\r\n", len);
|
||||||
@@ -808,17 +831,19 @@ int ble_data_send(const uint8_t *data, uint16_t len)
|
|||||||
for (int retry = 0; ; retry++)
|
for (int retry = 0; ; retry++)
|
||||||
{
|
{
|
||||||
err = bt_nus_send(current_conn, data, len);
|
err = bt_nus_send(current_conn, data, len);
|
||||||
// -ENOMEM만 짧게 재시도, 그 외 error는 즉시 종료
|
if (err != -ENOMEM || retry >= 20) {
|
||||||
if (err != -ENOMEM || retry >= 20)
|
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -ENOMEM : BLE TX 버퍼가 가득찬 경우: 바로 포기하지 않고 짧게 쉬었다가 다시 시도
|
/*
|
||||||
|
* -ENOMEM : BLE TX 버퍼가 가득참
|
||||||
|
* 바로 포기하지 않고 짧게 쉬었다가 다시 시도
|
||||||
|
*/
|
||||||
DBG_ERR("[NUS TX] busy, retry=%d\r\n", retry + 1);
|
DBG_ERR("[NUS TX] busy, retry=%d\r\n", retry + 1);
|
||||||
|
if ((retry + 1) >= BLE_TX_BUSY_STRESS_RETRY)
|
||||||
// busy 반복 시 TX power boost 판단
|
{
|
||||||
ble_tx_power_on_tx_busy_retry(retry + 1);
|
ble_tx_power_stress("tx-queue-busy");
|
||||||
|
}
|
||||||
k_msleep(5);
|
k_msleep(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -830,12 +855,11 @@ int ble_data_send(const uint8_t *data, uint16_t len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// bt_nus_send() 성공 후 sent 콜백이 올 때까지 기다림 (다음 패킷을 보내도 되는 시점)
|
// bt_nus_send() 성공 후 sent 콜백이 올 때까지 기다림 (다음 패킷을 보내도 되는 시점)
|
||||||
err = k_sem_take(&nus_tx_done_sem, K_MSEC(ble_tx_power_tx_stuck_ms()));
|
err = k_sem_take(&nus_tx_done_sem, K_MSEC(BLE_TX_STUCK_MS));
|
||||||
if (err)
|
if (err)
|
||||||
{
|
{
|
||||||
// sent callback 지연도 링크 stress로 처리
|
ble_tx_power_stress("tx-complete-stuck");
|
||||||
ble_tx_power_on_tx_complete_timeout();
|
err = k_sem_take(&nus_tx_done_sem, K_MSEC(1000 - BLE_TX_STUCK_MS));
|
||||||
err = k_sem_take(&nus_tx_done_sem, K_MSEC(1000 - ble_tx_power_tx_stuck_ms()));
|
|
||||||
if (err)
|
if (err)
|
||||||
{
|
{
|
||||||
DBG_ERR("[NUS TX] completion timeout (err %d)\r\n", err);
|
DBG_ERR("[NUS TX] completion timeout (err %d)\r\n", err);
|
||||||
@@ -846,22 +870,34 @@ int ble_data_send(const uint8_t *data, uint16_t len)
|
|||||||
{
|
{
|
||||||
int64_t tx_elapsed_ms = k_uptime_get() - tx_start_ms;
|
int64_t tx_elapsed_ms = k_uptime_get() - tx_start_ms;
|
||||||
|
|
||||||
// 완료 시간 기반 TX power 복구/boost 판단
|
if (tx_elapsed_ms >= BLE_TX_CRITICAL_MS)
|
||||||
ble_tx_power_on_tx_complete(tx_elapsed_ms);
|
{
|
||||||
|
ble_tx_power_stress("tx-complete-slow100");
|
||||||
|
slow_tx_count = 0U;
|
||||||
|
}
|
||||||
|
else if (tx_elapsed_ms >= BLE_TX_SLOW_MS)
|
||||||
|
{
|
||||||
|
if (slow_tx_count < UINT8_MAX)
|
||||||
|
{
|
||||||
|
slow_tx_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slow_tx_count >= BLE_TX_SLOW_COUNT)
|
||||||
|
{
|
||||||
|
ble_tx_power_stress("tx-complete-slow50");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slow_tx_count = 0U;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
k_mutex_unlock(&nus_tx_lock);
|
k_mutex_unlock(&nus_tx_lock);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* BLE 연결 상태 */
|
|
||||||
bool ble_is_connected(void)
|
bool ble_is_connected(void)
|
||||||
{
|
{
|
||||||
return (current_conn != NULL);
|
return (current_conn != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ATT MTU used to calculate notify payload size */
|
|
||||||
uint16_t ble_current_mtu(void)
|
|
||||||
{
|
|
||||||
return current_notify_mtu;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -31,10 +31,8 @@ int ble_service_init(ble_data_rx_cb_t rx_cb);
|
|||||||
int ble_advertising_start(void);
|
int ble_advertising_start(void);
|
||||||
int ble_advertising_stop(void);
|
int ble_advertising_stop(void);
|
||||||
int ble_data_send(const uint8_t *data, uint16_t len);
|
int ble_data_send(const uint8_t *data, uint16_t len);
|
||||||
uint16_t ble_current_mtu(void);
|
|
||||||
int ble_dfu_advertising_enable(void);
|
int ble_dfu_advertising_enable(void);
|
||||||
bool ble_dfu_advertising_is_enabled(void);
|
bool ble_dfu_advertising_is_enabled(void);
|
||||||
bool ble_is_connected(void);
|
bool ble_is_connected(void);
|
||||||
int ble_disconnect_active(void);
|
|
||||||
|
|
||||||
#endif /* BLE_SERVICE_H__ */
|
#endif /* BLE_SERVICE_H__ */
|
||||||
|
|||||||
@@ -1,364 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* @file ble_tx_power.c
|
|
||||||
* @brief BLE TX power and RSSI monitor helper
|
|
||||||
******************************************************************************/
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <zephyr/bluetooth/hci.h>
|
|
||||||
#include <zephyr/bluetooth/hci_vs.h>
|
|
||||||
#include <zephyr/bluetooth/conn.h>
|
|
||||||
#include <zephyr/sys/byteorder.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
#include "ble_tx_power.h"
|
|
||||||
#include "debug_print.h"
|
|
||||||
|
|
||||||
#define BLE_TX_POWER_NORMAL_DBM 4 // NORMAL: 평상시에는 전류 소모를 낮추기 위해 +4 dBm을 기본값으로 사용
|
|
||||||
#define BLE_TX_POWER_STRESS_DBM 8 // BOOST: RSSI 또는 TX 지연이 나빠지면 일시적으로 +8 dBm으로 boost
|
|
||||||
#define BLE_RSSI_MONITOR_ENABLED 1
|
|
||||||
#define BLE_RSSI_STRESS_DBM (-80)
|
|
||||||
#define BLE_RSSI_STRESS_COUNT 3
|
|
||||||
#define BLE_RSSI_MONITOR_INTERVAL_MS 500
|
|
||||||
#define BLE_TX_SLOW_MS 50
|
|
||||||
#define BLE_TX_CRITICAL_MS 100
|
|
||||||
#define BLE_TX_STUCK_MS 200
|
|
||||||
#define BLE_TX_SLOW_COUNT 3
|
|
||||||
#define BLE_TX_BUSY_STRESS_RETRY 4
|
|
||||||
#define BLE_TX_RECOVER_HOLD_MS 10000
|
|
||||||
|
|
||||||
extern int bt_hci_get_conn_handle(const struct bt_conn *conn, uint16_t *conn_handle);
|
|
||||||
|
|
||||||
/* HCI vendor command는 bt_conn 포인터 대신 controller handle을 사용 */
|
|
||||||
static uint16_t current_conn_handle;
|
|
||||||
static bool current_conn_handle_valid;
|
|
||||||
static int8_t current_tx_power_dbm = BLE_TX_POWER_NORMAL_DBM;
|
|
||||||
static uint8_t low_rssi_count;
|
|
||||||
static uint8_t slow_tx_count;
|
|
||||||
static int64_t last_stress_ms;
|
|
||||||
static struct k_work_delayable tx_power_monitor_work;
|
|
||||||
|
|
||||||
/* Nordic vendor HCI command로 advertising/connection TX power 직접 설정 */
|
|
||||||
static int ble_tx_power_write(uint8_t handle_type, uint16_t handle, int8_t dbm, const char *reason)
|
|
||||||
{
|
|
||||||
struct net_buf *buf;
|
|
||||||
struct net_buf *rsp;
|
|
||||||
struct bt_hci_cp_vs_write_tx_power_level *cp;
|
|
||||||
struct bt_hci_rp_vs_write_tx_power_level *rp;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
buf = bt_hci_cmd_alloc(K_NO_WAIT);
|
|
||||||
if (buf == NULL)
|
|
||||||
{
|
|
||||||
return -ENOBUFS;
|
|
||||||
}
|
|
||||||
|
|
||||||
cp = net_buf_add(buf, sizeof(*cp));
|
|
||||||
cp->handle_type = handle_type;
|
|
||||||
cp->handle = sys_cpu_to_le16(handle);
|
|
||||||
cp->tx_power_level = dbm;
|
|
||||||
|
|
||||||
err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL, buf, &rsp);
|
|
||||||
if (err)
|
|
||||||
{
|
|
||||||
DBG_ERR("[BLE] TX power set fail reason=%s err=%d\r\n", reason, err);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
rp = (void *)rsp->data;
|
|
||||||
if (rp->status != 0U)
|
|
||||||
{
|
|
||||||
err = -EIO;
|
|
||||||
DBG_ERR("[BLE] TX power set rejected reason=%s status=0x%02x\r\n", reason, rp->status);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (handle_type == BT_HCI_VS_LL_HANDLE_TYPE_CONN)
|
|
||||||
{
|
|
||||||
current_tx_power_dbm = rp->selected_tx_power;
|
|
||||||
}
|
|
||||||
DBG_CORE("[BLE] TX power %d dBm reason=%s\r\n", rp->selected_tx_power, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
net_buf_unref(rsp);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 연결 handle이 유효할 때만 connection TX power 변경 */
|
|
||||||
static int ble_set_conn_tx_power(int8_t dbm, const char *reason)
|
|
||||||
{
|
|
||||||
// 연결 전이거나 이미 목표 출력이면 HCI 명령을 보내지 않음
|
|
||||||
if (!current_conn_handle_valid || current_tx_power_dbm == dbm)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ble_tx_power_write(BT_HCI_VS_LL_HANDLE_TYPE_CONN, current_conn_handle, dbm, reason ? reason : "unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ble_tx_power_stress(const char *reason)
|
|
||||||
{
|
|
||||||
// stress 발생 시각을 기록해서 이후 NORMAL 복귀 시점 판단
|
|
||||||
last_stress_ms = k_uptime_get();
|
|
||||||
(void)ble_set_conn_tx_power(BLE_TX_POWER_STRESS_DBM, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 마지막 stress 이후 일정 시간 stress 없는 경우 NORMAL 출력으로 회복 */
|
|
||||||
static void ble_tx_power_try_recover(void)
|
|
||||||
{
|
|
||||||
// 이미 NORMAL이면 불필요한 HCI 명령 피함
|
|
||||||
if ((current_tx_power_dbm != BLE_TX_POWER_NORMAL_DBM) &&
|
|
||||||
((k_uptime_get() - last_stress_ms) >= BLE_TX_RECOVER_HOLD_MS))
|
|
||||||
{
|
|
||||||
(void)ble_set_conn_tx_power(BLE_TX_POWER_NORMAL_DBM, "link-recovered");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 컨트롤러에 실제 적용된 연결 TX power 확인 */
|
|
||||||
static int ble_read_conn_tx_power(int8_t *dbm)
|
|
||||||
{
|
|
||||||
struct net_buf *buf;
|
|
||||||
struct net_buf *rsp;
|
|
||||||
struct bt_hci_cp_vs_read_tx_power_level *cp;
|
|
||||||
struct bt_hci_rp_vs_read_tx_power_level *rp;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
// 연결 handle 없이는 controller에 연결 TX power 조회 불가
|
|
||||||
if (!current_conn_handle_valid || dbm == NULL)
|
|
||||||
{
|
|
||||||
return -ENOTCONN;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = bt_hci_cmd_alloc(K_NO_WAIT);
|
|
||||||
if (buf == NULL)
|
|
||||||
{
|
|
||||||
return -ENOBUFS;
|
|
||||||
}
|
|
||||||
|
|
||||||
cp = net_buf_add(buf, sizeof(*cp));
|
|
||||||
cp->handle_type = BT_HCI_VS_LL_HANDLE_TYPE_CONN;
|
|
||||||
cp->handle = sys_cpu_to_le16(current_conn_handle);
|
|
||||||
|
|
||||||
err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_TX_POWER_LEVEL, buf, &rsp);
|
|
||||||
if (err)
|
|
||||||
{
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
rp = (void *)rsp->data;
|
|
||||||
if (rp->status != 0U)
|
|
||||||
{
|
|
||||||
err = -EIO;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*dbm = rp->tx_power_level;
|
|
||||||
}
|
|
||||||
|
|
||||||
net_buf_unref(rsp);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 현재 연결의 RSSI를 읽어 링크 품질 판단에 사용 */
|
|
||||||
static int ble_read_conn_rssi(int8_t *rssi)
|
|
||||||
{
|
|
||||||
struct net_buf *buf;
|
|
||||||
struct net_buf *rsp;
|
|
||||||
struct bt_hci_cp_read_rssi *cp;
|
|
||||||
struct bt_hci_rp_read_rssi *rp;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
// RSSI 조회도 연결 handle이 있어야 가능
|
|
||||||
if (!current_conn_handle_valid || rssi == NULL)
|
|
||||||
{
|
|
||||||
return -ENOTCONN;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = bt_hci_cmd_alloc(K_NO_WAIT);
|
|
||||||
if (buf == NULL)
|
|
||||||
{
|
|
||||||
return -ENOBUFS;
|
|
||||||
}
|
|
||||||
|
|
||||||
cp = net_buf_add(buf, sizeof(*cp));
|
|
||||||
cp->handle = sys_cpu_to_le16(current_conn_handle);
|
|
||||||
|
|
||||||
err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_RSSI, buf, &rsp);
|
|
||||||
if (err)
|
|
||||||
{
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
rp = (void *)rsp->data;
|
|
||||||
if (rp->status != 0U)
|
|
||||||
{
|
|
||||||
err = -EIO;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*rssi = rp->rssi;
|
|
||||||
}
|
|
||||||
|
|
||||||
net_buf_unref(rsp);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 연결 중에는 주기적으로 RSSI를 확인하고, 필요하면 TX power boost */
|
|
||||||
static void tx_power_monitor_handler(struct k_work *work)
|
|
||||||
{
|
|
||||||
int8_t rssi = 0;
|
|
||||||
int err;
|
|
||||||
|
|
||||||
ARG_UNUSED(work);
|
|
||||||
|
|
||||||
if (!current_conn_handle_valid)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!BLE_RSSI_MONITOR_ENABLED)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ble_read_conn_rssi(&rssi);
|
|
||||||
|
|
||||||
// RSSI를 못 읽는 상황도 링크 품질 저하로 간주
|
|
||||||
if (err)
|
|
||||||
{
|
|
||||||
ble_tx_power_stress("rssi-silence");
|
|
||||||
}
|
|
||||||
else if (rssi <= BLE_RSSI_STRESS_DBM)
|
|
||||||
{
|
|
||||||
// 순간적인 RSSI 흔들림은 무시하고, 연속 low RSSI일 때만 boost
|
|
||||||
if (low_rssi_count < UINT8_MAX)
|
|
||||||
{
|
|
||||||
low_rssi_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (low_rssi_count >= BLE_RSSI_STRESS_COUNT)
|
|
||||||
{
|
|
||||||
ble_tx_power_stress("rssi-low");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// RSSI가 회복되면 연속 low RSSI 카운터 초기화
|
|
||||||
low_rssi_count = 0U;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TX가 없어도 monitor 주기마다 NORMAL 복귀 여부 확인
|
|
||||||
ble_tx_power_try_recover();
|
|
||||||
|
|
||||||
// 연결이 유지되는 동안 monitor work 계속 반복
|
|
||||||
k_work_schedule(&tx_power_monitor_work, K_MSEC(BLE_RSSI_MONITOR_INTERVAL_MS));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* RSSI monitor용 delayable work 초기화 */
|
|
||||||
void ble_tx_power_init(void)
|
|
||||||
{
|
|
||||||
k_work_init_delayable(&tx_power_monitor_work, tx_power_monitor_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 연결 직후 TX power 상태를 초기화하고 RSSI monitor를 시작 */
|
|
||||||
void ble_tx_power_on_connected(struct bt_conn *conn)
|
|
||||||
{
|
|
||||||
int8_t cur_txp = 0;
|
|
||||||
int txp_ret;
|
|
||||||
|
|
||||||
// 이후 HCI 명령에서 사용할 controller connection handle 저장
|
|
||||||
current_conn_handle_valid = (bt_hci_get_conn_handle(conn, ¤t_conn_handle) == 0);
|
|
||||||
current_tx_power_dbm = BLE_TX_POWER_NORMAL_DBM;
|
|
||||||
low_rssi_count = 0U;
|
|
||||||
slow_tx_count = 0U;
|
|
||||||
last_stress_ms = k_uptime_get();
|
|
||||||
|
|
||||||
if (current_conn_handle_valid)
|
|
||||||
{
|
|
||||||
// 새 연결은 항상 NORMAL (4 dBm) 시작
|
|
||||||
(void)ble_tx_power_write(BT_HCI_VS_LL_HANDLE_TYPE_CONN, current_conn_handle, BLE_TX_POWER_NORMAL_DBM, "connected");
|
|
||||||
}
|
|
||||||
|
|
||||||
txp_ret = ble_read_conn_tx_power(&cur_txp); // 실제 적용값
|
|
||||||
|
|
||||||
if (txp_ret == 0)
|
|
||||||
{
|
|
||||||
DBG_CORE("[BLE] TX power (current) = %d dBm\r\n", cur_txp);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DBG_CORE("[BLE] TX power read fail ret=%d\r\n", txp_ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BLE_RSSI_MONITOR_ENABLED)
|
|
||||||
{
|
|
||||||
// 연결이 유지되는 동안만 RSSI monitor
|
|
||||||
k_work_schedule(&tx_power_monitor_work, K_MSEC(BLE_RSSI_MONITOR_INTERVAL_MS));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 연결 해제 시 monitor와 링크별 상태 초기화 */
|
|
||||||
void ble_tx_power_on_disconnected(void)
|
|
||||||
{
|
|
||||||
k_work_cancel_delayable(&tx_power_monitor_work);
|
|
||||||
current_conn_handle_valid = false;
|
|
||||||
current_tx_power_dbm = BLE_TX_POWER_NORMAL_DBM;
|
|
||||||
low_rssi_count = 0U;
|
|
||||||
slow_tx_count = 0U;
|
|
||||||
last_stress_ms = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* advertising handle의 TX power를 NORMAL 값으로 설정 */
|
|
||||||
void ble_tx_power_apply_advertising(void)
|
|
||||||
{
|
|
||||||
(void)ble_tx_power_write(BT_HCI_VS_LL_HANDLE_TYPE_ADV, 0, BLE_TX_POWER_NORMAL_DBM, "adv");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ble_service.c가 TX 완료 대기 timeout을 TX power 정책값과 공유 */
|
|
||||||
uint32_t ble_tx_power_tx_stuck_ms(void)
|
|
||||||
{
|
|
||||||
return BLE_TX_STUCK_MS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* NUS TX queue가 여러 번 busy이면 링크가 불안정한 상황으로 보고 출력 boost */
|
|
||||||
void ble_tx_power_on_tx_busy_retry(uint8_t retry_count)
|
|
||||||
{
|
|
||||||
if (retry_count >= BLE_TX_BUSY_STRESS_RETRY)
|
|
||||||
{
|
|
||||||
ble_tx_power_stress("tx-queue-busy");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* sent callback이 늦게 오면 TX path가 막힌 것으로 보고 boost */
|
|
||||||
void ble_tx_power_on_tx_complete_timeout(void)
|
|
||||||
{
|
|
||||||
ble_tx_power_stress("tx-complete-stuck");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TX 완료 시간이 길어지는 것도 링크 품질 저하 신호로 사용 */
|
|
||||||
void ble_tx_power_on_tx_complete(int64_t elapsed_ms)
|
|
||||||
{
|
|
||||||
if (elapsed_ms >= BLE_TX_CRITICAL_MS)
|
|
||||||
{
|
|
||||||
// 100ms 이상이면 단발성이라도 즉시 boost
|
|
||||||
ble_tx_power_stress("tx-complete-slow100");
|
|
||||||
slow_tx_count = 0U;
|
|
||||||
}
|
|
||||||
else if (elapsed_ms >= BLE_TX_SLOW_MS)
|
|
||||||
{
|
|
||||||
// 50ms 이상은 연속 발생할 때만 boost
|
|
||||||
if (slow_tx_count < UINT8_MAX)
|
|
||||||
{
|
|
||||||
slow_tx_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (slow_tx_count >= BLE_TX_SLOW_COUNT)
|
|
||||||
{
|
|
||||||
ble_tx_power_stress("tx-complete-slow50");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 정상 속도로 전송되면 slow 카운터 지우고 복귀 가능성 확인
|
|
||||||
slow_tx_count = 0U;
|
|
||||||
ble_tx_power_try_recover();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* @file ble_tx_power.h
|
|
||||||
* @brief BLE TX power and RSSI monitor helper
|
|
||||||
******************************************************************************/
|
|
||||||
#ifndef BLE_TX_POWER_H__
|
|
||||||
#define BLE_TX_POWER_H__
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
struct bt_conn;
|
|
||||||
|
|
||||||
void ble_tx_power_init(void);
|
|
||||||
void ble_tx_power_on_connected(struct bt_conn *conn);
|
|
||||||
void ble_tx_power_on_disconnected(void);
|
|
||||||
void ble_tx_power_apply_advertising(void);
|
|
||||||
|
|
||||||
uint32_t ble_tx_power_tx_stuck_ms(void);
|
|
||||||
void ble_tx_power_on_tx_busy_retry(uint8_t retry_count);
|
|
||||||
void ble_tx_power_on_tx_complete_timeout(void);
|
|
||||||
void ble_tx_power_on_tx_complete(int64_t elapsed_ms);
|
|
||||||
|
|
||||||
#endif /* BLE_TX_POWER_H__ */
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* @file ble_cmd_queue.c
|
|
||||||
* @brief BLE RX command queue
|
|
||||||
******************************************************************************/
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
|
|
||||||
#include "ble_cmd_queue.h"
|
|
||||||
#include "debug_print.h"
|
|
||||||
#include "parser.h"
|
|
||||||
|
|
||||||
#define BLE_CMD_MAX_LEN 256
|
|
||||||
#define BLE_CMD_QUEUE_DEPTH 8 // RX command ring buffer depth
|
|
||||||
#define BLE_CMD_WORKQ_STACK_SZ 8192
|
|
||||||
#define BLE_CMD_WORKQ_PRIORITY 10
|
|
||||||
|
|
||||||
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 command worker
|
|
||||||
static struct k_spinlock ble_cmd_lock; // RX queue lock
|
|
||||||
|
|
||||||
static uint8_t ble_cmd_q_buf[BLE_CMD_QUEUE_DEPTH][BLE_CMD_MAX_LEN]; // command data
|
|
||||||
static uint16_t ble_cmd_q_len[BLE_CMD_QUEUE_DEPTH]; // command length
|
|
||||||
static uint8_t ble_cmd_q_head; // next pop slot
|
|
||||||
static uint8_t ble_cmd_q_tail; // next push slot
|
|
||||||
static uint8_t ble_cmd_q_count; // queued command count
|
|
||||||
|
|
||||||
/* Queue에 쌓인 BLE 명령을 parser로 전달 */
|
|
||||||
static void ble_cmd_work_handler(struct k_work *work)
|
|
||||||
{
|
|
||||||
ARG_UNUSED(work);
|
|
||||||
|
|
||||||
uint8_t local_buf[BLE_CMD_MAX_LEN];
|
|
||||||
uint16_t local_len;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
ble_cmd_dispatch(local_buf, local_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BLE RX 명령 처리 queue 초기화 */
|
|
||||||
void ble_cmd_queue_init(void)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BLE RX 콜백에서 호출되는 명령 enqueue 함수 */
|
|
||||||
void ble_cmd_queue_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* @file ble_cmd_queue.h
|
|
||||||
* @brief BLE RX command queue
|
|
||||||
******************************************************************************/
|
|
||||||
#ifndef BLE_CMD_QUEUE_H__
|
|
||||||
#define BLE_CMD_QUEUE_H__
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
void ble_cmd_queue_init(void);
|
|
||||||
void ble_cmd_queue_rx_handler(const uint8_t *data, uint16_t len);
|
|
||||||
|
|
||||||
#endif /* BLE_CMD_QUEUE_H__ */
|
|
||||||
+106
-201
@@ -4,8 +4,8 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/sys/reboot.h>
|
#include <zephyr/sys/reboot.h>
|
||||||
#include <zephyr/sys/util.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#include "cmd_common.h"
|
#include "cmd_common.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
@@ -23,14 +23,6 @@ static uint8_t tx_id_buf[4 + HW_NO_LENGTH + SERIAL_NO_LENGTH + SERIAL_NO_LENGTH
|
|||||||
static uint8_t tx_rim_buf[4 + 2 + (IMU_FIFO_RIM_TARGET_SAMPLES * IMU_FIFO_SAMPLE_BYTES) + 2];
|
static uint8_t tx_rim_buf[4 + 2 + (IMU_FIFO_RIM_TARGET_SAMPLES * IMU_FIFO_SAMPLE_BYTES) + 2];
|
||||||
static uint8_t g_echo_session;
|
static uint8_t g_echo_session;
|
||||||
|
|
||||||
/* ATT MTU에 따른 패킷 분할 */
|
|
||||||
#define ATT_NOTIFY_HEADER_SIZE 3U
|
|
||||||
#define REB_OVERHEAD_SIZE 10U
|
|
||||||
#define REC_OVERHEAD_SIZE 14U
|
|
||||||
#define ECHO_SAMPLE_SIZE 2U
|
|
||||||
#define RIM_OVERHEAD_SIZE 8U
|
|
||||||
#define RIC_OVERHEAD_SIZE 12U
|
|
||||||
|
|
||||||
uint8_t cmd_next_echo_session(void)
|
uint8_t cmd_next_echo_session(void)
|
||||||
{
|
{
|
||||||
return g_echo_session++;
|
return g_echo_session++;
|
||||||
@@ -194,27 +186,7 @@ int cmd_send_response_imu(const int16_t accel[3], const int16_t gyro[3])
|
|||||||
return ble_data_send(buf, 18);
|
return ble_data_send(buf, 18);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ATT MTU에 따라 패킷 분할이 필요한지 계산 */
|
void cmd_send_response_echo(uint8_t session, uint8_t channel, const uint16_t *samples, uint16_t num_samples)
|
||||||
static uint16_t calc_rec_samples_per_packet(uint16_t notify_payload_max)
|
|
||||||
{
|
|
||||||
uint16_t samples_per_pkt = 1U;
|
|
||||||
|
|
||||||
// rec:는 고정 오버헤드가 14B, chunk 크기 다시 계산
|
|
||||||
if (notify_payload_max > REC_OVERHEAD_SIZE)
|
|
||||||
{
|
|
||||||
samples_per_pkt = (uint16_t)((notify_payload_max - REC_OVERHEAD_SIZE) / ECHO_SAMPLE_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 방어코드
|
|
||||||
if (samples_per_pkt == 0U)
|
|
||||||
{
|
|
||||||
samples_per_pkt = 1U;
|
|
||||||
}
|
|
||||||
|
|
||||||
return samples_per_pkt;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cmd_send_response_echo_reb(uint8_t session, uint8_t channel, const uint16_t *samples, uint16_t num_samples)
|
|
||||||
{
|
{
|
||||||
// 정적 버퍼 사용 (한 번에 한 명령만 처리)
|
// 정적 버퍼 사용 (한 번에 한 명령만 처리)
|
||||||
uint8_t *buf = tx_echo_buf;
|
uint8_t *buf = tx_echo_buf;
|
||||||
@@ -239,87 +211,6 @@ static void cmd_send_response_echo_reb(uint8_t session, uint8_t channel, const u
|
|||||||
ble_data_send(buf, payload_len + 2);
|
ble_data_send(buf, payload_len + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* rec: packet layout:
|
|
||||||
* tag (4B): "rec:"
|
|
||||||
* session (1B): 측정 세션 번호
|
|
||||||
* channel (1B): piezo 채널 번호
|
|
||||||
* offset (2B): 전체 샘플 배열에서 시작 인덱스
|
|
||||||
* total_samples (2B): 전체 샘플 수
|
|
||||||
* chunk_samples (2B): 현재 패킷에 들어간 샘플 수
|
|
||||||
* sample data (N): Piezo ADC samples
|
|
||||||
* crc (2B): tag부터 sample data까지 CRC16
|
|
||||||
*/
|
|
||||||
static void cmd_send_response_echo_rec(uint8_t session, uint8_t channel, const uint16_t *samples, uint16_t num_samples, uint16_t samples_per_pkt)
|
|
||||||
{
|
|
||||||
uint8_t *buf = tx_echo_buf;
|
|
||||||
|
|
||||||
for (uint16_t off = 0U; off < num_samples; off = (uint16_t)(off + samples_per_pkt))
|
|
||||||
{
|
|
||||||
uint16_t remain = (uint16_t)(num_samples - off);
|
|
||||||
uint16_t chunk_samples = MIN(samples_per_pkt, remain);
|
|
||||||
|
|
||||||
buf[0] = 'r'; buf[1] = 'e'; buf[2] = 'c'; buf[3] = ':';
|
|
||||||
buf[4] = session;
|
|
||||||
buf[5] = channel;
|
|
||||||
buf[6] = (uint8_t)(off >> 8);
|
|
||||||
buf[7] = (uint8_t)(off & 0xFF);
|
|
||||||
buf[8] = (uint8_t)(num_samples >> 8);
|
|
||||||
buf[9] = (uint8_t)(num_samples & 0xFF);
|
|
||||||
buf[10] = (uint8_t)(chunk_samples >> 8);
|
|
||||||
buf[11] = (uint8_t)(chunk_samples & 0xFF);
|
|
||||||
|
|
||||||
// 마지막 chunk는 samples_per_pkt보다 작을 수 있음
|
|
||||||
for (uint16_t i = 0U; i < chunk_samples; i++)
|
|
||||||
{
|
|
||||||
buf[12 + i * 2] = (uint8_t)(samples[off + i] >> 8);
|
|
||||||
buf[13 + i * 2] = (uint8_t)(samples[off + i] & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t payload_len = (uint16_t)(REC_OVERHEAD_SIZE - 2U + (chunk_samples * ECHO_SAMPLE_SIZE));
|
|
||||||
uint16_t crc = cmd_crc16_compute(buf, payload_len);
|
|
||||||
DBG_PRINTF("[CMD] rec tx ch=%u off=%u total=%u chunk=%u len=%u\r\n",
|
|
||||||
channel, off, num_samples, chunk_samples, (uint16_t)(payload_len + 2U));
|
|
||||||
|
|
||||||
buf[payload_len] = (uint8_t)(crc & 0xFF);
|
|
||||||
buf[payload_len + 1U] = (uint8_t)(crc >> 8);
|
|
||||||
|
|
||||||
// ble_data_send()는 NUS sent 콜백을 기다리고 다음 chunk 전송
|
|
||||||
if (ble_data_send(buf, (uint16_t)(payload_len + 2U)) != 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Piezo echo ADC 응답 패킷 */
|
|
||||||
void cmd_send_response_echo(uint8_t session, uint8_t channel, const uint16_t *samples, uint16_t num_samples)
|
|
||||||
{
|
|
||||||
uint16_t notify_payload_max = 0U;
|
|
||||||
uint16_t mtu = ble_current_mtu();
|
|
||||||
uint16_t reb_len = (uint16_t)(REB_OVERHEAD_SIZE + (num_samples * ECHO_SAMPLE_SIZE));
|
|
||||||
|
|
||||||
// 협상된 ATT MTU에서 최대 payload
|
|
||||||
if (mtu > ATT_NOTIFY_HEADER_SIZE)
|
|
||||||
{
|
|
||||||
notify_payload_max = (uint16_t)(mtu - ATT_NOTIFY_HEADER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 분할이 필요 없는 경우(기존 reb: 패킷이 MTU 안에 들어가는 경우) reb: 응답
|
|
||||||
if ((notify_payload_max == 0U) || (reb_len <= notify_payload_max))
|
|
||||||
{
|
|
||||||
cmd_send_response_echo_reb(session, channel, samples, num_samples);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 기존 reb: 패킷이 현재 MTU 보다 큰 경우 rec:로 전환
|
|
||||||
uint16_t samples_per_pkt = calc_rec_samples_per_packet(notify_payload_max);
|
|
||||||
|
|
||||||
DBG_PRINTF("[CMD] rec split mtu=%u samples=%u chunk=%u\r\n", mtu, num_samples, samples_per_pkt);
|
|
||||||
|
|
||||||
cmd_send_response_echo_rec(session, channel, samples, num_samples, samples_per_pkt);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cmd_send_response_bundle(uint16_t batt_mv, const int16_t accel[3], const int16_t gyro[3], int16_t temp_cdeg)
|
void cmd_send_response_bundle(uint16_t batt_mv, const int16_t accel[3], const int16_t gyro[3], int16_t temp_cdeg)
|
||||||
{
|
{
|
||||||
uint8_t *buf = tx_bundle_buf;
|
uint8_t *buf = tx_bundle_buf;
|
||||||
@@ -349,7 +240,7 @@ void cmd_send_response_bundle(uint16_t batt_mv, const int16_t accel[3], const in
|
|||||||
ble_data_send(buf, sizeof(tx_bundle_buf));
|
ble_data_send(buf, sizeof(tx_bundle_buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cmd_send_response_rim_legacy(const uint8_t *sample_bytes, uint16_t sample_count)
|
void cmd_send_response_rim(const uint8_t *sample_bytes, uint16_t sample_count)
|
||||||
{
|
{
|
||||||
uint8_t *buf = tx_rim_buf;
|
uint8_t *buf = tx_rim_buf;
|
||||||
uint16_t payload_len;
|
uint16_t payload_len;
|
||||||
@@ -371,98 +262,9 @@ static void cmd_send_response_rim_legacy(const uint8_t *sample_bytes, uint16_t s
|
|||||||
buf[payload_len + 1U] = (uint8_t)(crc >> 8);
|
buf[payload_len + 1U] = (uint8_t)(crc >> 8);
|
||||||
|
|
||||||
DBG_PRINTF("[MTB] tx rim samples=%u len=%u\r\n", sample_count, (uint16_t)(payload_len + 2U));
|
DBG_PRINTF("[MTB] tx rim samples=%u len=%u\r\n", sample_count, (uint16_t)(payload_len + 2U));
|
||||||
|
|
||||||
ble_data_send(buf, (uint16_t)(payload_len + 2U));
|
ble_data_send(buf, (uint16_t)(payload_len + 2U));
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint16_t calc_ric_samples_per_packet(uint16_t notify_payload_max)
|
|
||||||
{
|
|
||||||
if (notify_payload_max < (RIC_OVERHEAD_SIZE + IMU_FIFO_SAMPLE_BYTES))
|
|
||||||
{
|
|
||||||
return 0U;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (uint16_t)((notify_payload_max - RIC_OVERHEAD_SIZE) / IMU_FIFO_SAMPLE_BYTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ric: packet layout:
|
|
||||||
* tag (4B): "ric:"
|
|
||||||
* offset (2B): 전체 샘플 배열에서 시작 인덱스
|
|
||||||
* total_samples (2B): 전체 IMU FIFO 샘플 수
|
|
||||||
* chunk_samples (2B): 현재 패킷에 들어간 샘플 수
|
|
||||||
* sample data (N): IMU FIFO samples
|
|
||||||
* crc (2B): tag부터 sample data까지 CRC16
|
|
||||||
*/
|
|
||||||
static void cmd_send_response_rim_ric(const uint8_t *sample_bytes, uint16_t sample_count, uint16_t samples_per_pkt)
|
|
||||||
{
|
|
||||||
uint8_t *buf = tx_rim_buf;
|
|
||||||
|
|
||||||
for (uint16_t off = 0U; off < sample_count; off = (uint16_t)(off + samples_per_pkt))
|
|
||||||
{
|
|
||||||
uint16_t remain = (uint16_t)(sample_count - off);
|
|
||||||
uint16_t chunk_samples = MIN(samples_per_pkt, remain);
|
|
||||||
|
|
||||||
buf[0] = 'r'; buf[1] = 'i'; buf[2] = 'c'; buf[3] = ':';
|
|
||||||
buf[4] = (uint8_t)(off >> 8);
|
|
||||||
buf[5] = (uint8_t)(off & 0xFF);
|
|
||||||
buf[6] = (uint8_t)(sample_count >> 8);
|
|
||||||
buf[7] = (uint8_t)(sample_count & 0xFF);
|
|
||||||
buf[8] = (uint8_t)(chunk_samples >> 8);
|
|
||||||
buf[9] = (uint8_t)(chunk_samples & 0xFF);
|
|
||||||
|
|
||||||
memcpy(&buf[10], &sample_bytes[off * IMU_FIFO_SAMPLE_BYTES], (size_t)chunk_samples * IMU_FIFO_SAMPLE_BYTES);
|
|
||||||
|
|
||||||
uint16_t payload_len = (uint16_t)(RIC_OVERHEAD_SIZE - 2U + (chunk_samples * IMU_FIFO_SAMPLE_BYTES));
|
|
||||||
uint16_t crc = cmd_crc16_compute(buf, payload_len);
|
|
||||||
DBG_PRINTF("[CMD] ric tx off=%u total=%u chunk=%u len=%u\r\n",
|
|
||||||
off, sample_count, chunk_samples, (uint16_t)(payload_len + 2U));
|
|
||||||
|
|
||||||
buf[payload_len] = (uint8_t)(crc & 0xFF);
|
|
||||||
buf[payload_len + 1U] = (uint8_t)(crc >> 8);
|
|
||||||
|
|
||||||
if (ble_data_send(buf, (uint16_t)(payload_len + 2U)) != 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void cmd_send_response_rim(const uint8_t *sample_bytes, uint16_t sample_count)
|
|
||||||
{
|
|
||||||
uint16_t notify_payload_max = 0U;
|
|
||||||
uint16_t mtu = ble_current_mtu();
|
|
||||||
|
|
||||||
if (sample_count > IMU_FIFO_RIM_TARGET_SAMPLES)
|
|
||||||
{
|
|
||||||
sample_count = IMU_FIFO_RIM_TARGET_SAMPLES;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t rim_len = (uint16_t)(RIM_OVERHEAD_SIZE + (sample_count * IMU_FIFO_SAMPLE_BYTES));
|
|
||||||
|
|
||||||
if (mtu > ATT_NOTIFY_HEADER_SIZE)
|
|
||||||
{
|
|
||||||
notify_payload_max = (uint16_t)(mtu - ATT_NOTIFY_HEADER_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((notify_payload_max == 0U) || (rim_len <= notify_payload_max))
|
|
||||||
{
|
|
||||||
cmd_send_response_rim_legacy(sample_bytes, sample_count);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t samples_per_pkt = calc_ric_samples_per_packet(notify_payload_max);
|
|
||||||
|
|
||||||
if (samples_per_pkt == 0U)
|
|
||||||
{
|
|
||||||
DBG_ERR("[CMD] ric skip mtu=%u payload=%u samples=%u\r\n", mtu, notify_payload_max, sample_count);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DBG_PRINTF("[CMD] ric split mtu=%u samples=%u chunk=%u\r\n", mtu, sample_count, samples_per_pkt);
|
|
||||||
cmd_send_response_rim_ric(sample_bytes, sample_count, samples_per_pkt);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cmd_send_response_piezo_config(const char *tag, uint16_t freq, uint16_t cycles, uint16_t avg, uint16_t delay_us, uint16_t samples)
|
void cmd_send_response_piezo_config(const char *tag, uint16_t freq, uint16_t cycles, uint16_t avg, uint16_t delay_us, uint16_t samples)
|
||||||
{
|
{
|
||||||
uint8_t *buf = tx_cfg_buf;
|
uint8_t *buf = tx_cfg_buf;
|
||||||
@@ -488,3 +290,106 @@ void cmd_send_response_piezo_config(const char *tag, uint16_t freq, uint16_t cyc
|
|||||||
|
|
||||||
ble_data_send(buf, sizeof(tx_cfg_buf));
|
ble_data_send(buf, sizeof(tx_cfg_buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void cmd_put_u16_be(uint8_t *dst, uint16_t value)
|
||||||
|
{
|
||||||
|
dst[0] = (uint8_t)(value >> 8);
|
||||||
|
dst[1] = (uint8_t)(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_put_u32_be(uint8_t *dst, uint32_t value)
|
||||||
|
{
|
||||||
|
dst[0] = (uint8_t)(value >> 24);
|
||||||
|
dst[1] = (uint8_t)(value >> 16);
|
||||||
|
dst[2] = (uint8_t)(value >> 8);
|
||||||
|
dst[3] = (uint8_t)(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cmd_put_crc(uint8_t *buf, uint16_t payload_len)
|
||||||
|
{
|
||||||
|
uint16_t crc = cmd_crc16_compute(buf, payload_len);
|
||||||
|
buf[payload_len] = (uint8_t)(crc & 0xFF);
|
||||||
|
buf[payload_len + 1U] = (uint8_t)(crc >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmd_send_response_rfq(uint32_t tick_start)
|
||||||
|
{
|
||||||
|
static uint8_t buf[10];
|
||||||
|
|
||||||
|
buf[0] = 'r'; buf[1] = 'f'; buf[2] = 'q'; buf[3] = ':';
|
||||||
|
cmd_put_u32_be(&buf[4], tick_start);
|
||||||
|
cmd_put_crc(buf, 8U);
|
||||||
|
|
||||||
|
return ble_data_send(buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmd_send_response_rfe(uint16_t status, uint8_t session, uint32_t tick_end, uint16_t total_frames)
|
||||||
|
{
|
||||||
|
static uint8_t buf[15];
|
||||||
|
|
||||||
|
buf[0] = 'r'; buf[1] = 'f'; buf[2] = 'e'; buf[3] = ':';
|
||||||
|
cmd_put_u16_be(&buf[4], status);
|
||||||
|
buf[6] = session;
|
||||||
|
cmd_put_u32_be(&buf[7], tick_end);
|
||||||
|
cmd_put_u16_be(&buf[11], total_frames);
|
||||||
|
cmd_put_crc(buf, 13U);
|
||||||
|
|
||||||
|
return ble_data_send(buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmd_send_response_rqh(const measure_queue_frame_header_t *header)
|
||||||
|
{
|
||||||
|
static uint8_t buf[14];
|
||||||
|
|
||||||
|
if (header == NULL)
|
||||||
|
{
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[0] = 'r'; buf[1] = 'q'; buf[2] = 'h'; buf[3] = ':';
|
||||||
|
buf[4] = header->session;
|
||||||
|
cmd_put_u16_be(&buf[5], header->frame_idx);
|
||||||
|
cmd_put_u32_be(&buf[7], header->tick_ms);
|
||||||
|
buf[11] = header->ch_mask;
|
||||||
|
cmd_put_crc(buf, 12U);
|
||||||
|
|
||||||
|
return ble_data_send(buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmd_send_response_rqb(uint16_t frame_idx, uint8_t channel, const uint16_t *samples, uint8_t num_samples)
|
||||||
|
{
|
||||||
|
static uint8_t buf[4 + 2 + 1 + 1 + (PIEZO_MEASURE_MAX_SAMPLES * 2) + 2];
|
||||||
|
uint16_t payload_len;
|
||||||
|
|
||||||
|
if ((samples == NULL) || (num_samples > PIEZO_MEASURE_MAX_SAMPLES))
|
||||||
|
{
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[0] = 'r'; buf[1] = 'q'; buf[2] = 'b'; buf[3] = ':';
|
||||||
|
cmd_put_u16_be(&buf[4], frame_idx);
|
||||||
|
buf[6] = channel;
|
||||||
|
buf[7] = num_samples;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < num_samples; i++)
|
||||||
|
{
|
||||||
|
cmd_put_u16_be(&buf[8U + (uint16_t)i * 2U], samples[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
payload_len = (uint16_t)(8U + ((uint16_t)num_samples * 2U));
|
||||||
|
cmd_put_crc(buf, payload_len);
|
||||||
|
|
||||||
|
return ble_data_send(buf, (uint16_t)(payload_len + 2U));
|
||||||
|
}
|
||||||
|
|
||||||
|
int cmd_send_response_rqd(uint16_t status, uint16_t sent_frames)
|
||||||
|
{
|
||||||
|
static uint8_t buf[10];
|
||||||
|
|
||||||
|
buf[0] = 'r'; buf[1] = 'q'; buf[2] = 'd'; buf[3] = ':';
|
||||||
|
cmd_put_u16_be(&buf[4], status);
|
||||||
|
cmd_put_u16_be(&buf[6], sent_frames);
|
||||||
|
cmd_put_crc(buf, 8U);
|
||||||
|
|
||||||
|
return ble_data_send(buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
#include "imu_i2c.h"
|
#include "imu_i2c.h"
|
||||||
#include "piezo_measure.h"
|
#include "piezo_measure.h"
|
||||||
|
#include "measure_queue.h"
|
||||||
|
|
||||||
void cmd_reboot_after_response(void);
|
void cmd_reboot_after_response(void);
|
||||||
uint16_t cmd_crc16_compute(const uint8_t *p_data, uint32_t size);
|
uint16_t cmd_crc16_compute(const uint8_t *p_data, uint32_t size);
|
||||||
@@ -28,5 +29,10 @@ void cmd_send_response_echo(uint8_t session, uint8_t channel, const uint16_t *sa
|
|||||||
void cmd_send_response_bundle(uint16_t batt_mv, const int16_t accel[3], const int16_t gyro[3], int16_t temp_cdeg);
|
void cmd_send_response_bundle(uint16_t batt_mv, const int16_t accel[3], const int16_t gyro[3], int16_t temp_cdeg);
|
||||||
void cmd_send_response_rim(const uint8_t *sample_bytes, uint16_t sample_count);
|
void cmd_send_response_rim(const uint8_t *sample_bytes, uint16_t sample_count);
|
||||||
void cmd_send_response_piezo_config(const char *tag, uint16_t freq, uint16_t cycles, uint16_t avg, uint16_t delay_us, uint16_t samples);
|
void cmd_send_response_piezo_config(const char *tag, uint16_t freq, uint16_t cycles, uint16_t avg, uint16_t delay_us, uint16_t samples);
|
||||||
|
int cmd_send_response_rfq(uint32_t tick_start);
|
||||||
|
int cmd_send_response_rfe(uint16_t status, uint8_t session, uint32_t tick_end, uint16_t total_frames);
|
||||||
|
int cmd_send_response_rqh(const measure_queue_frame_header_t *header);
|
||||||
|
int cmd_send_response_rqb(uint16_t frame_idx, uint8_t channel, const uint16_t *samples, uint8_t num_samples);
|
||||||
|
int cmd_send_response_rqd(uint16_t status, uint16_t sent_frames);
|
||||||
|
|
||||||
#endif /* CMD_COMMON_H__ */
|
#endif /* CMD_COMMON_H__ */
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ const cmd_entry_t cmd_table[] =
|
|||||||
{ "mtb?", cmd_mtb },
|
{ "mtb?", cmd_mtb },
|
||||||
{ "mcf?", cmd_mcf },
|
{ "mcf?", cmd_mcf },
|
||||||
{ "mcs?", cmd_mcs },
|
{ "mcs?", cmd_mcs },
|
||||||
|
{ "mfq?", cmd_mfq },
|
||||||
|
{ "mfe?", cmd_mfe },
|
||||||
|
{ "mqb?", cmd_mqb },
|
||||||
{ "mid?", cmd_mid },
|
{ "mid?", cmd_mid },
|
||||||
{ "mfv?", cmd_mfv },
|
{ "mfv?", cmd_mfv },
|
||||||
{ "mwh?", cmd_mwh },
|
{ "mwh?", cmd_mwh },
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include "debug_print.h"
|
#include "debug_print.h"
|
||||||
#include "ble_service.h"
|
#include "ble_service.h"
|
||||||
#include "cmd_device.h"
|
#include "cmd_device.h"
|
||||||
#include "led_control.h"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* msq: power off
|
* msq: power off
|
||||||
@@ -82,10 +81,8 @@ int cmd_msr(const uint8_t *data, uint8_t data_len)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
led_set_state(LED_STATE_BOND_DELETE); // 삭제 완료 확인 주황 LED ON
|
|
||||||
bond_data_delete = true;
|
bond_data_delete = true;
|
||||||
DBG_PRINTF("[CMD] msr bond data deleted\r\n");
|
DBG_PRINTF("[CMD] msr bond data deleted\r\n");
|
||||||
k_msleep(1500);
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
bond_data_delete = true;
|
bond_data_delete = true;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
#include <zephyr/sys/util.h>
|
#include <zephyr/sys/util.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#include "cmd_common.h"
|
#include "cmd_common.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
#include "imu_i2c.h"
|
#include "imu_i2c.h"
|
||||||
#include "piezo.h"
|
#include "piezo.h"
|
||||||
#include "piezo_measure.h"
|
#include "piezo_measure.h"
|
||||||
|
#include "measure_queue.h"
|
||||||
#include "cmd_piezo.h"
|
#include "cmd_piezo.h"
|
||||||
|
|
||||||
static uint8_t tx_rim_samples[IMU_FIFO_RIM_TARGET_SAMPLES * IMU_FIFO_SAMPLE_BYTES];
|
static uint8_t tx_rim_samples[IMU_FIFO_RIM_TARGET_SAMPLES * IMU_FIFO_SAMPLE_BYTES];
|
||||||
@@ -42,6 +44,7 @@ int cmd_mbb(const uint8_t *data, uint8_t data_len)
|
|||||||
int16_t gyro[3];
|
int16_t gyro[3];
|
||||||
|
|
||||||
processing = true;
|
processing = true;
|
||||||
|
power_button_suspend(true);
|
||||||
DBG_PRINTF("[MBB] cmd start\r\n");
|
DBG_PRINTF("[MBB] cmd start\r\n");
|
||||||
|
|
||||||
int batt_mv = -1;
|
int batt_mv = -1;
|
||||||
@@ -76,6 +79,7 @@ int cmd_mbb(const uint8_t *data, uint8_t data_len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
piezo_power_off();
|
piezo_power_off();
|
||||||
|
power_button_suspend(false);
|
||||||
DBG_PRINTF("[MBB] done\r\n");
|
DBG_PRINTF("[MBB] done\r\n");
|
||||||
cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태 raa: 전송
|
cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태 raa: 전송
|
||||||
DBG_PRINTF("[CMD] mbb status=0x%04X\r\n", status);
|
DBG_PRINTF("[CMD] mbb status=0x%04X\r\n", status);
|
||||||
@@ -105,6 +109,7 @@ int cmd_mtb(const uint8_t *data, uint8_t data_len)
|
|||||||
bool fifo_started = false;
|
bool fifo_started = false;
|
||||||
|
|
||||||
processing = true;
|
processing = true;
|
||||||
|
power_button_suspend(true);
|
||||||
DBG_PRINTF("[MTB] cmd start\r\n");
|
DBG_PRINTF("[MTB] cmd start\r\n");
|
||||||
|
|
||||||
int imu_ret = imu_fifo_start();
|
int imu_ret = imu_fifo_start();
|
||||||
@@ -139,13 +144,7 @@ int cmd_mtb(const uint8_t *data, uint8_t data_len)
|
|||||||
{
|
{
|
||||||
if (fifo_started)
|
if (fifo_started)
|
||||||
{
|
{
|
||||||
imu_ret = imu_fifo_wait_samples(IMU_FIFO_RIM_TARGET_SAMPLES, 500U);
|
int imu_ret = imu_fifo_read_latest(tx_rim_samples, IMU_FIFO_RIM_TARGET_SAMPLES, &rim_count);
|
||||||
if (imu_ret != 0)
|
|
||||||
{
|
|
||||||
DBG_PRINTF("[MTB] fifo wait ret=%d\r\n", imu_ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
imu_ret = imu_fifo_read_latest(tx_rim_samples, IMU_FIFO_RIM_TARGET_SAMPLES, &rim_count);
|
|
||||||
if (imu_ret != 0)
|
if (imu_ret != 0)
|
||||||
{
|
{
|
||||||
DBG_PRINTF("[MTB] fifo read fail ret=%d\r\n", imu_ret);
|
DBG_PRINTF("[MTB] fifo read fail ret=%d\r\n", imu_ret);
|
||||||
@@ -158,6 +157,7 @@ int cmd_mtb(const uint8_t *data, uint8_t data_len)
|
|||||||
DBG_PRINTF("[CMD] mtb status=0x%04X\r\n", status);
|
DBG_PRINTF("[CMD] mtb status=0x%04X\r\n", status);
|
||||||
|
|
||||||
piezo_power_off();
|
piezo_power_off();
|
||||||
|
power_button_suspend(false);
|
||||||
DBG_PRINTF("[CMD] mtb status=0x%04X rim=%u\r\n", status, rim_count);
|
DBG_PRINTF("[CMD] mtb status=0x%04X rim=%u\r\n", status, rim_count);
|
||||||
|
|
||||||
processing = false;
|
processing = false;
|
||||||
@@ -214,47 +214,138 @@ int cmd_mcs(const uint8_t *data, uint8_t data_len)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================== Test Command ===============================
|
|
||||||
/* TEST
|
static uint16_t cmd_queue_status_to_u16(int status)
|
||||||
* mim? : IMU FIFO 15 samples
|
{
|
||||||
|
if (status == 0)
|
||||||
|
{
|
||||||
|
return ECHO_STATUS_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status > 0)
|
||||||
|
{
|
||||||
|
return (uint16_t)status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint16_t)(0x8000U | ((uint16_t)(-status) & 0x7FFFU));
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t queue_tx_samples[PIEZO_NUM_CHANNELS][PIEZO_MEASURE_MAX_SAMPLES];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mfq?: start flash queue recording.
|
||||||
|
* Response: rfq: + tick_start.
|
||||||
*/
|
*/
|
||||||
int cmd_mim(const uint8_t *data, uint8_t data_len)
|
int cmd_mfq(const uint8_t *data, uint8_t data_len)
|
||||||
{
|
{
|
||||||
ARG_UNUSED(data);
|
ARG_UNUSED(data);
|
||||||
ARG_UNUSED(data_len);
|
ARG_UNUSED(data_len);
|
||||||
|
|
||||||
if (processing)
|
uint32_t tick_start = 0U;
|
||||||
|
DBG_PRINTF("[MFQ] start\r\n");
|
||||||
|
int err = measure_queue_start(&tick_start);
|
||||||
|
if (err)
|
||||||
{
|
{
|
||||||
|
DBG_ERR("[MFQ] start failed err=%d\r\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG_PRINTF("[MFQ] response rfq tick_start=%u err=%d\r\n", tick_start, err);
|
||||||
|
cmd_send_response_rfq(tick_start);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
processing = true;
|
/*
|
||||||
|
* mfe?: stop flash queue recording.
|
||||||
uint16_t rim_count = 0U;
|
* Response: rfe: + status/session/tick_end/total_frames.
|
||||||
int imu_ret = imu_fifo_start();
|
*/
|
||||||
if (imu_ret != 0)
|
int cmd_mfe(const uint8_t *data, uint8_t data_len)
|
||||||
{
|
{
|
||||||
DBG_PRINTF("[MTB] fifo start fail ret=%d\r\n", imu_ret);
|
ARG_UNUSED(data);
|
||||||
cmd_send_response_rim(tx_rim_samples, 0U);
|
ARG_UNUSED(data_len);
|
||||||
processing = false;
|
|
||||||
|
uint32_t tick_end = 0U;
|
||||||
|
uint8_t session = 0U;
|
||||||
|
DBG_PRINTF("[MFE] stop\r\n");
|
||||||
|
uint16_t total_frames = 0U;
|
||||||
|
int status = measure_queue_stop(&tick_end, &session, &total_frames);
|
||||||
|
|
||||||
|
DBG_PRINTF("[MFE] response rfe status=0x%04X session=%u tick_end=%u frames=%u\r\n",
|
||||||
|
cmd_queue_status_to_u16(status), session, tick_end, total_frames);
|
||||||
|
cmd_send_response_rfe(cmd_queue_status_to_u16(status), session, tick_end, total_frames);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
k_msleep(320); // IMU FIFO is 50Hz, 15 samples need about 300ms
|
/*
|
||||||
|
* mqb?: dump queued frames.
|
||||||
imu_ret = imu_fifo_read_latest(tx_rim_samples, IMU_FIFO_RIM_TARGET_SAMPLES, &rim_count);
|
* Response sequence: (rqh + rqb * active channels) * frames, then rqd once.
|
||||||
if (imu_ret != 0)
|
*/
|
||||||
|
int cmd_mqb(const uint8_t *data, uint8_t data_len)
|
||||||
{
|
{
|
||||||
DBG_PRINTF("[MIM] fifo read fail ret=%d\r\n", imu_ret);
|
ARG_UNUSED(data);
|
||||||
rim_count = 0U;
|
ARG_UNUSED(data_len);
|
||||||
}
|
|
||||||
|
|
||||||
cmd_send_response_rim(tx_rim_samples, rim_count); // IMU FIFO 15 samples rim: 전송
|
uint16_t total_frames = measure_queue_total_frames();
|
||||||
imu_fifo_stop();
|
uint16_t num_samples = measure_queue_num_samples();
|
||||||
processing = false;
|
DBG_PRINTF("[MQB] cmd dump total_frames=%u samples=%u\r\n", total_frames, num_samples);
|
||||||
|
uint16_t sent_frames = 0U;
|
||||||
|
uint16_t status = ECHO_STATUS_OK;
|
||||||
|
|
||||||
|
if (measure_queue_is_recording())
|
||||||
|
{
|
||||||
|
DBG_PRINTF("[MQB] reject: recording busy\r\n");
|
||||||
|
cmd_send_response_rqd(cmd_queue_status_to_u16(-EBUSY), 0U);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (uint16_t frame_idx = 0; frame_idx < total_frames; frame_idx++)
|
||||||
|
{
|
||||||
|
measure_queue_frame_header_t header;
|
||||||
|
int err = measure_queue_read_frame(frame_idx, &header, queue_tx_samples);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
DBG_ERR("[MQB] read frame failed frame=%u err=%d\r\n", frame_idx, err);
|
||||||
|
status = cmd_queue_status_to_u16(err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd_send_response_rqh(&header);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
DBG_ERR("[MQB] send rqh failed frame=%u err=%d\r\n", frame_idx, err);
|
||||||
|
status = cmd_queue_status_to_u16(err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++)
|
||||||
|
{
|
||||||
|
if ((header.ch_mask & BIT(ch)) == 0U)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd_send_response_rqb(frame_idx, ch, queue_tx_samples[ch], (uint8_t)num_samples);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
DBG_ERR("[MQB] send rqb failed frame=%u ch=%u err=%d\r\n", frame_idx, ch, err);
|
||||||
|
status = cmd_queue_status_to_u16(err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != ECHO_STATUS_OK)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sent_frames++;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG_PRINTF("[MQB] response rqd status=0x%04X sent_frames=%u\r\n", status, sent_frames);
|
||||||
|
cmd_send_response_rqd(status, sent_frames);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================== Test Command ===============================
|
||||||
/*
|
/*
|
||||||
* TEST (이전 정렬모드)
|
* TEST (이전 정렬모드)
|
||||||
* maa?: piezo echo sweep
|
* maa?: piezo echo sweep
|
||||||
@@ -272,12 +363,13 @@ int cmd_maa(const uint8_t *data, uint8_t data_len)
|
|||||||
uint8_t session = cmd_next_echo_session();
|
uint8_t session = cmd_next_echo_session();
|
||||||
|
|
||||||
processing = true;
|
processing = true;
|
||||||
|
power_button_suspend(true);
|
||||||
|
|
||||||
int status = piezo_measure_start_session();
|
int status = piezo_measure_start_session();
|
||||||
|
|
||||||
if (status == ECHO_STATUS_OK) // Piezo GPIO, power, ADC initialization/wake succeeded
|
if (status == ECHO_STATUS_OK) // Piezo GPIO, power, ADC initialization/wake succeeded
|
||||||
{
|
{
|
||||||
DBG_PRINTF("[MAA] piezo sweep start\r\n");
|
DBG_PRINTF("[MBB] piezo sweep start\r\n");
|
||||||
status = piezo_measure_sweep();
|
status = piezo_measure_sweep();
|
||||||
if (status == ECHO_STATUS_OK)
|
if (status == ECHO_STATUS_OK)
|
||||||
{
|
{
|
||||||
@@ -290,6 +382,7 @@ int cmd_maa(const uint8_t *data, uint8_t data_len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
piezo_power_off();
|
piezo_power_off();
|
||||||
|
power_button_suspend(false);
|
||||||
cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태 raa: 전송
|
cmd_send_response_u16("raa:", (uint16_t)status); // 최종 상태 raa: 전송
|
||||||
DBG_PRINTF("[CMD] maa status=0x%04X\r\n", status);
|
DBG_PRINTF("[CMD] maa status=0x%04X\r\n", status);
|
||||||
|
|
||||||
@@ -368,9 +461,11 @@ int cmd_mpc(const uint8_t *data, uint8_t data_len)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
power_button_suspend(true);
|
||||||
|
|
||||||
if (piezo_init() != 0)
|
if (piezo_init() != 0)
|
||||||
{
|
{
|
||||||
|
power_button_suspend(false);
|
||||||
cmd_send_response_u16("rpc:", 0);
|
cmd_send_response_u16("rpc:", 0);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -380,12 +475,14 @@ int cmd_mpc(const uint8_t *data, uint8_t data_len)
|
|||||||
if (piezo_select_channel((uint8_t)(piezo_ch % PIEZO_NUM_CHANNELS)) != 0)
|
if (piezo_select_channel((uint8_t)(piezo_ch % PIEZO_NUM_CHANNELS)) != 0)
|
||||||
{
|
{
|
||||||
piezo_power_off();
|
piezo_power_off();
|
||||||
|
power_button_suspend(false);
|
||||||
cmd_send_response_u16("rpc:", 0);
|
cmd_send_response_u16("rpc:", 0);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
piezo_burst_sw_freq((uint8_t)freq_option, cycles);
|
piezo_burst_sw_freq((uint8_t)freq_option, cycles);
|
||||||
piezo_power_off();
|
piezo_power_off();
|
||||||
|
power_button_suspend(false);
|
||||||
|
|
||||||
cmd_send_response_u16("rpc:", cycles);
|
cmd_send_response_u16("rpc:", cycles);
|
||||||
return 1;
|
return 1;
|
||||||
@@ -427,6 +524,7 @@ int cmd_mec(const uint8_t *data, uint8_t data_len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
processing = true;
|
processing = true;
|
||||||
|
power_button_suspend(true);
|
||||||
|
|
||||||
int status = piezo_measure_start_session();
|
int status = piezo_measure_start_session();
|
||||||
if (status == ECHO_STATUS_OK)
|
if (status == ECHO_STATUS_OK)
|
||||||
@@ -440,6 +538,7 @@ int cmd_mec(const uint8_t *data, uint8_t data_len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
piezo_power_off();
|
piezo_power_off();
|
||||||
|
power_button_suspend(false);
|
||||||
cmd_send_response_u16("raa:", (uint16_t)status);
|
cmd_send_response_u16("raa:", (uint16_t)status);
|
||||||
processing = false;
|
processing = false;
|
||||||
return 1;
|
return 1;
|
||||||
@@ -471,6 +570,7 @@ int cmd_mad(const uint8_t *data, uint8_t data_len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
processing = true;
|
processing = true;
|
||||||
|
power_button_suspend(true);
|
||||||
|
|
||||||
int status = piezo_measure_start_session();
|
int status = piezo_measure_start_session();
|
||||||
if (status == ECHO_STATUS_OK)
|
if (status == ECHO_STATUS_OK)
|
||||||
@@ -484,6 +584,7 @@ int cmd_mad(const uint8_t *data, uint8_t data_len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
piezo_power_off();
|
piezo_power_off();
|
||||||
|
power_button_suspend(false);
|
||||||
cmd_send_response_u16("raa:", (uint16_t)status);
|
cmd_send_response_u16("raa:", (uint16_t)status);
|
||||||
DBG_PRINTF("[CMD] mad status=0x%04X ch=%u samples=%u avg=%u\r\n",
|
DBG_PRINTF("[CMD] mad status=0x%04X ch=%u samples=%u avg=%u\r\n",
|
||||||
status,
|
status,
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ int cmd_mbb(const uint8_t *data, uint8_t data_len);
|
|||||||
int cmd_mtb(const uint8_t *data, uint8_t data_len);
|
int cmd_mtb(const uint8_t *data, uint8_t data_len);
|
||||||
int cmd_mcf(const uint8_t *data, uint8_t data_len);
|
int cmd_mcf(const uint8_t *data, uint8_t data_len);
|
||||||
int cmd_mcs(const uint8_t *data, uint8_t data_len);
|
int cmd_mcs(const uint8_t *data, uint8_t data_len);
|
||||||
|
int cmd_mfq(const uint8_t *data, uint8_t data_len);
|
||||||
|
int cmd_mfe(const uint8_t *data, uint8_t data_len);
|
||||||
|
int cmd_mqb(const uint8_t *data, uint8_t data_len);
|
||||||
int cmd_mim(const uint8_t *data, uint8_t data_len);
|
int cmd_mim(const uint8_t *data, uint8_t data_len);
|
||||||
|
|
||||||
#endif /* CMD_PIEZO_H__ */
|
#endif /* CMD_PIEZO_H__ */
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
#include "led_control.h"
|
#include "led_control.h"
|
||||||
#include "cmd_sensor.h"
|
#include "cmd_sensor.h"
|
||||||
|
|
||||||
|
static uint8_t tx_rim_samples[IMU_FIFO_RIM_TARGET_SAMPLES * IMU_FIFO_SAMPLE_BYTES];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* msn: battery voltage read
|
* msn: battery voltage read
|
||||||
*/
|
*/
|
||||||
@@ -31,7 +33,7 @@ int cmd_msn(const uint8_t *data, uint8_t data_len)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* 삭제 예정(mim?으로 대체)
|
||||||
* msp: IMU data direct read
|
* msp: IMU data direct read
|
||||||
*/
|
*/
|
||||||
int cmd_msp(const uint8_t *data, uint8_t data_len)
|
int cmd_msp(const uint8_t *data, uint8_t data_len)
|
||||||
@@ -39,11 +41,6 @@ int cmd_msp(const uint8_t *data, uint8_t data_len)
|
|||||||
ARG_UNUSED(data);
|
ARG_UNUSED(data);
|
||||||
ARG_UNUSED(data_len);
|
ARG_UNUSED(data_len);
|
||||||
|
|
||||||
if (processing)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int16_t accel[3], gyro[3];
|
int16_t accel[3], gyro[3];
|
||||||
|
|
||||||
int ret = imu_read(accel, gyro);
|
int ret = imu_read(accel, gyro);
|
||||||
@@ -58,6 +55,37 @@ int cmd_msp(const uint8_t *data, uint8_t data_len)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* mim? : IMU FIFO 15 samples
|
||||||
|
*/
|
||||||
|
int cmd_mim(const uint8_t *data, uint8_t data_len)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(data);
|
||||||
|
ARG_UNUSED(data_len);
|
||||||
|
|
||||||
|
uint16_t rim_count = 0U;
|
||||||
|
int imu_ret = imu_fifo_start();
|
||||||
|
if (imu_ret != 0)
|
||||||
|
{
|
||||||
|
DBG_PRINTF("[MTB] fifo start fail ret=%d\r\n", imu_ret);
|
||||||
|
cmd_send_response_rim(tx_rim_samples, 0U);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_msleep(320); // IMU FIFO is 50Hz, 15 samples need about 300ms
|
||||||
|
|
||||||
|
imu_ret = imu_fifo_read_latest(tx_rim_samples, IMU_FIFO_RIM_TARGET_SAMPLES, &rim_count);
|
||||||
|
if (imu_ret != 0)
|
||||||
|
{
|
||||||
|
DBG_PRINTF("[MIM] fifo read fail ret=%d\r\n", imu_ret);
|
||||||
|
rim_count = 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_send_response_rim(tx_rim_samples, rim_count); // IMU FIFO 15 samples rim: 전송
|
||||||
|
imu_fifo_stop();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* mst: IMU temperature direct read
|
* mst: IMU temperature direct read
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,14 +14,8 @@
|
|||||||
*/
|
*/
|
||||||
#include <zephyr/sys/printk.h>
|
#include <zephyr/sys/printk.h>
|
||||||
|
|
||||||
#if defined(CONFIG_VESIS_DEBUG_PRINT)
|
|
||||||
#define DBG_PRINTF(...) printk(__VA_ARGS__)
|
#define DBG_PRINTF(...) printk(__VA_ARGS__)
|
||||||
#define DBG_CORE(...) printk(__VA_ARGS__)
|
#define DBG_CORE(...) printk(__VA_ARGS__)
|
||||||
#define DBG_ERR(...) printk(__VA_ARGS__)
|
#define DBG_ERR(...) printk(__VA_ARGS__)
|
||||||
#else
|
|
||||||
#define DBG_PRINTF(...) do { } while (0)
|
|
||||||
#define DBG_CORE(...) printk(__VA_ARGS__)
|
|
||||||
#define DBG_ERR(...) printk(__VA_ARGS__)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* DEBUG_PRINT_H */
|
#endif /* DEBUG_PRINT_H */
|
||||||
|
|||||||
@@ -1,238 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* @file dfu_manager.c
|
|
||||||
* @brief DFU state, watchdog, and MCUboot image control
|
|
||||||
******************************************************************************/
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <zephyr/dfu/mcuboot.h>
|
|
||||||
#include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
|
|
||||||
#include <hal/nrf_power.h>
|
|
||||||
|
|
||||||
#include "main.h"
|
|
||||||
#include "dfu_manager.h"
|
|
||||||
#include "debug_print.h"
|
|
||||||
#include "led_control.h"
|
|
||||||
#include "ble_service.h"
|
|
||||||
|
|
||||||
#define DFU_STALL_TIMEOUT_SEC 30 // DFU upload stall disconnect threshold
|
|
||||||
#define IMG_MGMT_ID_STATE 0U
|
|
||||||
#define IMG_MGMT_ID_UPLOAD 1U
|
|
||||||
|
|
||||||
static bool dfu_led_active; // DFU upload green LED state
|
|
||||||
static struct k_work_delayable dfu_watchdog_work; // DFU upload stall watchdog
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* DFU watchdog와 mcumgr 콜백 초기화 */
|
|
||||||
void dfu_manager_init(void)
|
|
||||||
{
|
|
||||||
k_work_init_delayable(&dfu_watchdog_work, dfu_watchdog_handler);
|
|
||||||
mgmt_callback_register(&dfu_status_callback);
|
|
||||||
mgmt_callback_register(&smp_cmd_status_callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 현재 이미지가 confirm 대기 상태인지 확인 */
|
|
||||||
bool dfu_is_confirm_pending_boot(void)
|
|
||||||
{
|
|
||||||
#if defined(CONFIG_BOOTLOADER_MCUBOOT)
|
|
||||||
return !boot_is_img_confirmed();
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 실행 중 이미지 confirm 처리 */
|
|
||||||
void dfu_confirm_running_image(void)
|
|
||||||
{
|
|
||||||
#if defined(CONFIG_BOOTLOADER_MCUBOOT)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MCUboot swap 상태 문자열 변환 */
|
|
||||||
static const char *swap_type_to_str(int swap_type)
|
|
||||||
{
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MCUboot 이미지 상태 로그 출력 */
|
|
||||||
void dfu_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
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DFU 업로드 무진행 시 BLE 연결 강제 해제 */
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DFU 업로드 상태에 따라 LED와 watchdog 제어 */
|
|
||||||
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();
|
|
||||||
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();
|
|
||||||
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);
|
|
||||||
#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);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MGMT_CB_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DFU 명령 실패 시 LED와 resume 플래그 복구 */
|
|
||||||
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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* @file dfu_manager.h
|
|
||||||
* @brief DFU state, watchdog, and MCUboot image control
|
|
||||||
******************************************************************************/
|
|
||||||
#ifndef DFU_MANAGER_H__
|
|
||||||
#define DFU_MANAGER_H__
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#define DFU_RESUME_MAGIC 0xD5U
|
|
||||||
#define DFU_RESUME_GPREGRET_REG 1U
|
|
||||||
|
|
||||||
void dfu_manager_init(void);
|
|
||||||
bool dfu_is_confirm_pending_boot(void);
|
|
||||||
void dfu_confirm_running_image(void);
|
|
||||||
void dfu_log_mcuboot_state(void);
|
|
||||||
|
|
||||||
#endif /* DFU_MANAGER_H__ */
|
|
||||||
@@ -615,43 +615,6 @@ int imu_fifo_start(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int imu_fifo_wait_samples(uint16_t target_samples, uint32_t timeout_ms)
|
|
||||||
{
|
|
||||||
uint32_t waited_ms = 0U;
|
|
||||||
const uint32_t poll_ms = 10U;
|
|
||||||
|
|
||||||
if (target_samples == 0U)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fifo_active)
|
|
||||||
{
|
|
||||||
return -EALREADY;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (waited_ms <= timeout_ms)
|
|
||||||
{
|
|
||||||
uint16_t record_count = 0U;
|
|
||||||
int ret = imu_fifo_read_count(&record_count);
|
|
||||||
|
|
||||||
if (ret)
|
|
||||||
{
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (record_count >= target_samples)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
k_msleep(poll_ms);
|
|
||||||
waited_ms += poll_ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
return -ETIMEDOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
int imu_fifo_read_latest(uint8_t *sample_bytes,
|
int imu_fifo_read_latest(uint8_t *sample_bytes,
|
||||||
uint16_t max_samples,
|
uint16_t max_samples,
|
||||||
uint16_t *out_count)
|
uint16_t *out_count)
|
||||||
@@ -687,7 +650,9 @@ int imu_fifo_read_latest(uint8_t *sample_bytes,
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
uint16_t raw_count = record_count;
|
||||||
record_count = imu_fifo_compact_records(record_count);
|
record_count = imu_fifo_compact_records(record_count);
|
||||||
|
DBG_PRINTF("[IMU FIFO] compact %u -> %u\r\n", raw_count, record_count);
|
||||||
}
|
}
|
||||||
if (record_count > max_samples)
|
if (record_count > max_samples)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -65,13 +65,6 @@ int imu_read_temperature_cdeg(int16_t *temp_cdeg);
|
|||||||
*/
|
*/
|
||||||
int imu_fifo_start(void);
|
int imu_fifo_start(void);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Wait until the running FIFO has at least target_samples records.
|
|
||||||
*
|
|
||||||
* @return 0 if the target is reached, -ETIMEDOUT on timeout, or a negative error.
|
|
||||||
*/
|
|
||||||
int imu_fifo_wait_samples(uint16_t target_samples, uint32_t timeout_ms);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 실행 중인 FIFO에서 최신 샘플 읽기
|
* @brief 실행 중인 FIFO에서 최신 샘플 읽기
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ static const led_pattern_t m_patterns[LED_STATE_COUNT] = {
|
|||||||
[LED_STATE_ALIGN_SEARCHING] = { 1000, 1000, COLOR_ORANGE, true },
|
[LED_STATE_ALIGN_SEARCHING] = { 1000, 1000, COLOR_ORANGE, true },
|
||||||
[LED_STATE_ALIGN_COMPLETE] = { 0, 0, COLOR_GREEN, false },
|
[LED_STATE_ALIGN_COMPLETE] = { 0, 0, COLOR_GREEN, false },
|
||||||
[LED_STATE_ERROR] = { 0, 0, COLOR_ORANGE, true },
|
[LED_STATE_ERROR] = { 0, 0, COLOR_ORANGE, true },
|
||||||
[LED_STATE_BOND_DELETE] = { 0, 0, COLOR_ORANGE, false },
|
[LED_STATE_DATA_STORAGING] = { 0, 0, COLOR_GREEN, false },
|
||||||
|
[LED_STATE_DATA_ERASING] = { 200, 200, COLOR_GREEN, true },
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Module variables */
|
/* Module variables */
|
||||||
@@ -217,7 +218,6 @@ led_state_t led_get_state(void)
|
|||||||
return m_current_state;
|
return m_current_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DFU 진행 중 초록 LED ON 콜백 */
|
|
||||||
void led_ble_solid(void)
|
void led_ble_solid(void)
|
||||||
{
|
{
|
||||||
k_timer_stop(&m_led_timer);
|
k_timer_stop(&m_led_timer);
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ typedef enum
|
|||||||
LED_STATE_ALIGN_SEARCHING, // 5: Orange blink 1s/1s
|
LED_STATE_ALIGN_SEARCHING, // 5: Orange blink 1s/1s
|
||||||
LED_STATE_ALIGN_COMPLETE, // 6: Green ON
|
LED_STATE_ALIGN_COMPLETE, // 6: Green ON
|
||||||
LED_STATE_ERROR, // 7: Orange 3Hz x3 / 1s off
|
LED_STATE_ERROR, // 7: Orange 3Hz x3 / 1s off
|
||||||
LED_STATE_BOND_DELETE, // Bond Delete: Orange on
|
|
||||||
|
LED_STATE_DATA_STORAGING, // 8: Green ON
|
||||||
|
LED_STATE_DATA_ERASING, // 9: Green blink 200ms/200ms
|
||||||
LED_STATE_COUNT
|
LED_STATE_COUNT
|
||||||
} led_state_t;
|
} led_state_t;
|
||||||
|
|
||||||
|
|||||||
+619
-51
@@ -1,31 +1,58 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* @file main.c
|
* @file main.c
|
||||||
* @brief VesiScan-Basic
|
* @brief VesiScan-Basic
|
||||||
*
|
|
||||||
* - 앱 공통 전역 상태 보관
|
|
||||||
* - 부팅 직후 전원 유지 판단
|
|
||||||
* - NVS 로드 전 기본값 초기화
|
|
||||||
* - 각 모듈 초기화 순서 조립
|
|
||||||
* - main loop 유지
|
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/drivers/gpio.h>
|
||||||
#include <zephyr/sys/printk.h>
|
#include <zephyr/sys/printk.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"
|
||||||
#include "debug_print.h"
|
#include "debug_print.h"
|
||||||
#include "power_control.h"
|
#include "power_control.h"
|
||||||
#include "dfu_manager.h"
|
|
||||||
#include "led_control.h"
|
#include "led_control.h"
|
||||||
#include "ble_service.h"
|
#include "ble_service.h"
|
||||||
#include "battery_adc.h"
|
#include "battery_adc.h"
|
||||||
#include "ble_cmd_queue.h"
|
#include "parser.h"
|
||||||
#include "imu_i2c.h"
|
#include "imu_i2c.h"
|
||||||
#include "piezo_measure.h"
|
#include "piezo_measure.h"
|
||||||
|
|
||||||
LOG_MODULE_REGISTER(vesiscan, LOG_LEVEL_INF);
|
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
|
||||||
|
* - 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 300 // 5ms x 400 = 2초 -> 초기화 시간 고려 체감상 2초에 맞춤
|
||||||
|
|
||||||
|
/* 전원 제어 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 ble_connection_st = false; // BLE 연결 상태
|
||||||
volatile bool processing = false; // 센서 데이터 처리 중 플래그
|
volatile bool processing = false; // 센서 데이터 처리 중 플래그
|
||||||
bool bond_data_delete = true; // 본딩 데이터 삭제 요청
|
bool bond_data_delete = true; // 본딩 데이터 삭제 요청
|
||||||
@@ -36,23 +63,159 @@ char HW_NO[HW_NO_BUF_SIZE]; // 하드웨어 번호
|
|||||||
char m_static_passkey[PASSKEY_BUF_SIZE]; // BLE 정적 패스키
|
char m_static_passkey[PASSKEY_BUF_SIZE]; // BLE 정적 패스키
|
||||||
uint8_t m_passkey_changed; // 패스키 변경은 출고 후 1회만 허용
|
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 resume_without_power_button; // 리셋 후 버튼 없이 복귀
|
||||||
static bool dfu_confirm_pending_boot; // DFU test 이미지 확인 전 자동 복귀
|
static bool dfu_confirm_pending_boot; // DFU test 이미지 확인 전 자동 복귀
|
||||||
static bool dfu_reset_resume_request; // DFU 완료 리셋 후 자동 복귀
|
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_stop_work; // advertising 중지 work
|
||||||
|
static bool dfu_led_active; // DFU 업로드 중 초록 LED ON
|
||||||
|
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를 둠
|
||||||
|
* - 명령 하나가 끝날 때까지 다음 명령 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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)
|
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 rest (DFU 완료 후 재부팅)
|
// DFU 완료 직후 리셋인지 확인
|
||||||
// GPREGRET은 reset 후에도 유지되는 작은 상태 표시용 레지스터
|
|
||||||
#if NRF_POWER_HAS_GPREGRET
|
#if NRF_POWER_HAS_GPREGRET
|
||||||
dfu_reset_resume_request = (nrf_power_gpregret_get(NRF_POWER, DFU_RESUME_GPREGRET_REG) == DFU_RESUME_MAGIC);
|
dfu_reset_resume_request = (nrf_power_gpregret_get(NRF_POWER, DFU_RESUME_GPREGRET_REG) == DFU_RESUME_MAGIC);
|
||||||
|
|
||||||
// DFU reset 표시 확인 후 다음 부팅 오판 방지를 위해 정리
|
|
||||||
if (dfu_reset_resume_request)
|
if (dfu_reset_resume_request)
|
||||||
{
|
{
|
||||||
nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, 0U);
|
nrf_power_gpregret_set(NRF_POWER, DFU_RESUME_GPREGRET_REG, 0U);
|
||||||
@@ -61,42 +224,89 @@ static void boot_context_detect(void)
|
|||||||
dfu_reset_resume_request = false;
|
dfu_reset_resume_request = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// soft reset 후 복귀 (전원 버튼 새 부팅 X)
|
// 소프트 리셋 계열 자동 복귀
|
||||||
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;
|
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;
|
||||||
|
|
||||||
// 다음 부팅 오판 방지를 위해 RESETREAS 플래그 정리
|
|
||||||
if (boot_reset_reason != 0U)
|
if (boot_reset_reason != 0U)
|
||||||
{
|
{
|
||||||
|
// RESETREAS 플래그 정리
|
||||||
nrf_power_resetreas_clear(NRF_POWER, boot_reset_reason);
|
nrf_power_resetreas_clear(NRF_POWER, boot_reset_reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NVS 값이 없을 때 사용할 기본값 초기화 (앱 전역 상태 기본값) - NVS 값이 있는 경우 이후 NVS 값으로 덮어씀 */
|
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)
|
static void load_default_config(void)
|
||||||
{
|
{
|
||||||
// 기본 문자열 길이 계산
|
// 기본 문자열 길이 제한
|
||||||
size_t serial_len = strlen(SERIAL_NUMBER);
|
size_t serial_len = strlen(SERIAL_NUMBER);
|
||||||
size_t hw_len = strlen(HARDWARE_VERSION);
|
size_t hw_len = strlen(HARDWARE_VERSION);
|
||||||
size_t passkey_len = strlen(DEFAULT_PASSKEY);
|
size_t passkey_len = strlen(DEFAULT_PASSKEY);
|
||||||
|
|
||||||
// 버퍼 오버플로우 방지 - 기본 문자열 길이 제한 (긴 경우 잘라내기)
|
|
||||||
if (serial_len > SERIAL_NO_LENGTH)
|
if (serial_len > SERIAL_NO_LENGTH)
|
||||||
{
|
{
|
||||||
serial_len = SERIAL_NO_LENGTH;
|
serial_len = SERIAL_NO_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hw_len > HW_NO_LENGTH)
|
if (hw_len > HW_NO_LENGTH)
|
||||||
{
|
{
|
||||||
hw_len = HW_NO_LENGTH;
|
hw_len = HW_NO_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passkey_len > PASSKEY_LENGTH)
|
if (passkey_len > PASSKEY_LENGTH)
|
||||||
{
|
{
|
||||||
passkey_len = PASSKEY_LENGTH;
|
passkey_len = PASSKEY_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 복사할 때 버퍼를 먼저 0으로 채우고 복사 -> 문자열 종료 NUL 보장
|
|
||||||
// 기본 Serial Number 복사
|
|
||||||
memset(SERIAL_NO, 0, sizeof(SERIAL_NO));
|
memset(SERIAL_NO, 0, sizeof(SERIAL_NO));
|
||||||
memcpy(SERIAL_NO, SERIAL_NUMBER, serial_len);
|
memcpy(SERIAL_NO, SERIAL_NUMBER, serial_len);
|
||||||
|
|
||||||
@@ -108,54 +318,412 @@ static void load_default_config(void)
|
|||||||
memset(m_static_passkey, 0, sizeof(m_static_passkey));
|
memset(m_static_passkey, 0, sizeof(m_static_passkey));
|
||||||
memcpy(m_static_passkey, DEFAULT_PASSKEY, passkey_len);
|
memcpy(m_static_passkey, DEFAULT_PASSKEY, passkey_len);
|
||||||
|
|
||||||
m_passkey_changed = 0U; // 패스키 변경 여부 초기화
|
// NVS 로드 전 기본값은 변경 가능 상태
|
||||||
|
m_passkey_changed = 0U;
|
||||||
|
|
||||||
m_reset_status = 1; // 기본 reset 상태 코드 설정
|
/* 기본 상태값 */
|
||||||
bond_data_delete = true; // 기본 상태는 bond 삭제 완료 삭제로 초기화
|
m_reset_status = 1;
|
||||||
|
bond_data_delete = true;
|
||||||
|
|
||||||
DBG_CORE("[INIT] Default (S/N=%s)\r\n", SERIAL_NO);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 전원 버튼 상태머신 (5ms 폴링) */
|
||||||
|
#define BOOT_THRESHOLD 200 // 5ms x 400 = 2초 -> 초기화 시간 고려 체감상 2초에 맞춤
|
||||||
|
|
||||||
|
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 = 0; // 카운터 리셋: 안 하면 다음 틱에서 ON→OFF 분기가 cnt_s >= 200 조건을 즉시 만족하여 전원 OFF됨
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
boot_btn_released = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (button_pressed) // 버튼 새로 누르고 있음
|
||||||
|
{
|
||||||
|
cnt_s++;
|
||||||
|
|
||||||
|
if (cnt_s >= BOOT_THRESHOLD) // 2초 이상 → 전원 OFF
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ON
|
||||||
|
dfu_led_active = true;
|
||||||
|
led_ble_solid();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MGMT_EVT_OP_IMG_MGMT_DFU_PENDING:
|
||||||
|
// 업로드 완료 후 리셋 전까지 초록 LED ON
|
||||||
|
dfu_led_active = true;
|
||||||
|
led_ble_solid();
|
||||||
|
|
||||||
|
// DFU 리셋 후 버튼 없이 전원 ON
|
||||||
|
#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 && !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)
|
int main(void)
|
||||||
{
|
{
|
||||||
|
// 리셋/복귀 컨텍스트
|
||||||
boot_context_detect();
|
boot_context_detect();
|
||||||
|
|
||||||
#if defined(CONFIG_BOOTLOADER_MCUBOOT)
|
// 하드웨어 기본 초기화
|
||||||
dfu_confirm_pending_boot = dfu_is_confirm_pending_boot(); // MCUboot test 이미지 상태인지 확인
|
power_hold_init();
|
||||||
#else
|
cnt_s = 0;
|
||||||
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();
|
device_on = false;
|
||||||
|
boot_btn_released = false;
|
||||||
|
confirm_running_image();
|
||||||
|
|
||||||
DBG_CORE("\r\n========================================\r\n");
|
DBG_CORE("\r\n========================================\r\n");
|
||||||
DBG_CORE(" TEST BUILD %s (Zephyr)\r\n", FIRMWARE_VERSION);
|
DBG_CORE(" TEST BUILD %s (Zephyr)\r\n", FIRMWARE_VERSION);
|
||||||
DBG_CORE(" BUILD TAG: %s\r\n", CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION);
|
DBG_CORE(" BUILD TAG: TEST-003 - tx power dynamic control\r\n");
|
||||||
DBG_CORE("========================================\r\n");
|
DBG_CORE("========================================\r\n");
|
||||||
|
|
||||||
// 각 모듈 초기화
|
DBG_CORE("[INIT] HW Init\r\n");
|
||||||
power_control_init();
|
// 기본 하드웨어/센서 초기화
|
||||||
ble_cmd_queue_init();
|
gpio_init();
|
||||||
dfu_manager_init();
|
timers_init();
|
||||||
dfu_log_mcuboot_state();
|
log_mcuboot_state();
|
||||||
load_default_config();
|
load_default_config();
|
||||||
led_init();
|
led_init();
|
||||||
battery_adc_init();
|
battery_adc_init();
|
||||||
battery_timer_init();
|
battery_timer_init();
|
||||||
imu_init();
|
imu_init();
|
||||||
piezo_config_init();
|
piezo_config_init();
|
||||||
|
DBG_CORE("[INIT] HW - gpio/timer/config/led/batt/imu/temp/piezo-cfg OK\r\n");
|
||||||
|
|
||||||
// BLE Stack/NUS 초기화 (0=성공)
|
// BLE 스택 + NUS
|
||||||
if (ble_service_init(ble_cmd_queue_rx_handler) == 0) // BLE RX 콜백은 command queue로 연결
|
DBG_CORE("[INIT] BLE Init\r\n");
|
||||||
|
if (ble_service_init(ble_rx_handler) == 0)
|
||||||
{
|
{
|
||||||
// 버튼 없이 정상 ON 상태로 복구하는 경우
|
DBG_CORE("[INIT] BLE - ble/nus OK\r\n");
|
||||||
if (resume_without_power_button || dfu_confirm_pending_boot || dfu_reset_resume_request)
|
// 소프트 리셋 후 서비스 복구
|
||||||
{
|
resume_device_after_soft_reset();
|
||||||
power_control_resume_after_reset(boot_reset_reason);
|
// BLE 준비까지 확인한 뒤 DFU test 이미지를 확정
|
||||||
}
|
confirm_running_image();
|
||||||
|
|
||||||
dfu_confirm_running_image(); // BLE 준비까지 확인한 뒤 DFU test 이미지 확정(confirm)
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -166,10 +734,10 @@ int main(void)
|
|||||||
DBG_CORE(" READY [%s]\r\n", SERIAL_NO);
|
DBG_CORE(" READY [%s]\r\n", SERIAL_NO);
|
||||||
DBG_CORE("========================================\r\n");
|
DBG_CORE("========================================\r\n");
|
||||||
|
|
||||||
power_control_start(); // 전원 버튼 상태머신 시작 (부팅 시 버튼이 눌려있는 상태)
|
// 전원 버튼 상태머신 시작 (부팅 시 버튼이 눌려있는 상태)
|
||||||
|
timers_start();
|
||||||
|
|
||||||
// main loop - idle
|
// 메인 루프 - idle
|
||||||
// callback, work queue 기반 동작 유지
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
k_msleep(100);
|
k_msleep(100);
|
||||||
|
|||||||
+3
-1
@@ -15,7 +15,7 @@
|
|||||||
/*==============================================================================
|
/*==============================================================================
|
||||||
* Firmware identification : Default values, can be overridden by NVS
|
* Firmware identification : Default values, can be overridden by NVS
|
||||||
*============================================================================*/
|
*============================================================================*/
|
||||||
#define FIRMWARE_VERSION "VBTFW0200"
|
#define FIRMWARE_VERSION "TSTFW042"
|
||||||
#define HARDWARE_VERSION "VB0HW0000"
|
#define HARDWARE_VERSION "VB0HW0000"
|
||||||
#define SERIAL_NUMBER "VBT260300ZZ"
|
#define SERIAL_NUMBER "VBT260300ZZ"
|
||||||
#define DEFAULT_PASSKEY "123456"
|
#define DEFAULT_PASSKEY "123456"
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
/*==============================================================================
|
/*==============================================================================
|
||||||
* Enumerations
|
* Enumerations
|
||||||
*============================================================================*/
|
*============================================================================*/
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
OFF = 0,
|
OFF = 0,
|
||||||
@@ -60,6 +61,7 @@ typedef enum
|
|||||||
/*==============================================================================
|
/*==============================================================================
|
||||||
* Function declarations
|
* Function declarations
|
||||||
*============================================================================*/
|
*============================================================================*/
|
||||||
|
|
||||||
void sleep_mode_enter(void);
|
void sleep_mode_enter(void);
|
||||||
void sleep_mode_enter_reason(const char *reason);
|
void sleep_mode_enter_reason(const char *reason);
|
||||||
void device_power_off(void);
|
void device_power_off(void);
|
||||||
|
|||||||
@@ -0,0 +1,449 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* @file measure_queue.c
|
||||||
|
* @brief Flash-backed queued piezo measurement frames
|
||||||
|
******************************************************************************/
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/storage/flash_map.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "measure_queue.h"
|
||||||
|
#include "debug_print.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "led_control.h"
|
||||||
|
|
||||||
|
#define MEASURE_QUEUE_PERIOD_MS 100U
|
||||||
|
#define MEASURE_QUEUE_THREAD_STACK_SIZE 2048
|
||||||
|
#define MEASURE_QUEUE_THREAD_PRIORITY 7
|
||||||
|
|
||||||
|
/* data_storage flash partition handle, opened once and reused */
|
||||||
|
static const struct flash_area *queue_area;
|
||||||
|
|
||||||
|
/* Recording runs in its own thread, separate from BLE command handling */
|
||||||
|
static struct k_thread queue_thread;
|
||||||
|
static K_THREAD_STACK_DEFINE(queue_thread_stack, MEASURE_QUEUE_THREAD_STACK_SIZE);
|
||||||
|
|
||||||
|
/* Lock for shared queue state */
|
||||||
|
static struct k_mutex queue_lock;
|
||||||
|
static bool queue_lock_ready;
|
||||||
|
|
||||||
|
/* Current recording state */
|
||||||
|
static bool queue_recording; // true while the worker keeps storing frames
|
||||||
|
static bool queue_thread_running; // true while the worker thread is alive
|
||||||
|
static uint8_t queue_session; // measurement session id, incremented on each mfq?
|
||||||
|
static uint16_t queue_total_frames; // number of frames written to flash
|
||||||
|
static uint16_t queue_num_samples; // samples value latched at recording start
|
||||||
|
static uint32_t queue_tick_start; // actual recording start tick
|
||||||
|
static uint32_t queue_tick_end; // actual recording end tick
|
||||||
|
static int queue_status; // last stop status: 0=OK, negative=errno
|
||||||
|
|
||||||
|
/* Work buffers sized for one max-size frame */
|
||||||
|
static uint8_t frame_write_buf[4 + (PIEZO_NUM_CHANNELS * PIEZO_MEASURE_MAX_SAMPLES * 2)];
|
||||||
|
static uint8_t frame_read_buf[4 + (PIEZO_NUM_CHANNELS * PIEZO_MEASURE_MAX_SAMPLES * 2)];
|
||||||
|
|
||||||
|
/* Lazy init for the mutex on first use */
|
||||||
|
static void queue_init_lock_once(void)
|
||||||
|
{
|
||||||
|
if (!queue_lock_ready)
|
||||||
|
{
|
||||||
|
k_mutex_init(&queue_lock);
|
||||||
|
queue_lock_ready = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Byte size of one frame in flash
|
||||||
|
* Layout: tick_ms 4B + 6 channels * samples * uint16 2B
|
||||||
|
*/
|
||||||
|
static uint32_t queue_frame_size(uint16_t num_samples)
|
||||||
|
{
|
||||||
|
return 4U + (uint32_t)PIEZO_NUM_CHANNELS * (uint32_t)num_samples * 2U;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Max frame count that fits in the current data_storage area */
|
||||||
|
static uint16_t queue_capacity_frames(uint16_t num_samples)
|
||||||
|
{
|
||||||
|
if (queue_area == NULL)
|
||||||
|
{
|
||||||
|
return 0U;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (uint16_t)(queue_area->fa_size / queue_frame_size(num_samples));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flash format uses big endian for tick and samples */
|
||||||
|
static void put_u16_be(uint8_t *dst, uint16_t value)
|
||||||
|
{
|
||||||
|
dst[0] = (uint8_t)(value >> 8);
|
||||||
|
dst[1] = (uint8_t)(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_u32_be(uint8_t *dst, uint32_t value)
|
||||||
|
{
|
||||||
|
dst[0] = (uint8_t)(value >> 24);
|
||||||
|
dst[1] = (uint8_t)(value >> 16);
|
||||||
|
dst[2] = (uint8_t)(value >> 8);
|
||||||
|
dst[3] = (uint8_t)(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t get_u16_be(const uint8_t *src)
|
||||||
|
{
|
||||||
|
return ((uint16_t)src[0] << 8) | (uint16_t)src[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t get_u32_be(const uint8_t *src)
|
||||||
|
{
|
||||||
|
return ((uint32_t)src[0] << 24) | ((uint32_t)src[1] << 16) | ((uint32_t)src[2] << 8) | (uint32_t)src[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store the current piezo sweep result as one frame
|
||||||
|
* Order: tick_ms -> CH0 samples -> CH1 samples -> ... -> CH5 samples
|
||||||
|
*/
|
||||||
|
static int queue_write_current_frame(uint16_t frame_idx, uint32_t tick_ms)
|
||||||
|
{
|
||||||
|
uint16_t num_samples = queue_num_samples;
|
||||||
|
uint32_t frame_size = queue_frame_size(num_samples);
|
||||||
|
uint8_t *p = frame_write_buf;
|
||||||
|
|
||||||
|
put_u32_be(p, tick_ms);
|
||||||
|
p += 4;
|
||||||
|
|
||||||
|
for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++)
|
||||||
|
{
|
||||||
|
const uint16_t *samples = piezo_measure_channel_buffer(ch);
|
||||||
|
if (samples == NULL)
|
||||||
|
{
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < num_samples; i++)
|
||||||
|
{
|
||||||
|
put_u16_be(p, samples[i]);
|
||||||
|
p += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flash_area_write(queue_area, (off_t)((uint32_t)frame_idx * frame_size), frame_write_buf, frame_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Worker thread started by mfq?
|
||||||
|
* Keeps 100 ms recording separate from BLE command handling
|
||||||
|
*/
|
||||||
|
static void queue_record_thread(void *a, void *b, void *c)
|
||||||
|
{
|
||||||
|
ARG_UNUSED(a);
|
||||||
|
ARG_UNUSED(b);
|
||||||
|
ARG_UNUSED(c);
|
||||||
|
|
||||||
|
int status = piezo_measure_start_session();
|
||||||
|
|
||||||
|
while (status == ECHO_STATUS_OK)
|
||||||
|
{
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
bool should_record = queue_recording;
|
||||||
|
uint16_t frame_idx = queue_total_frames;
|
||||||
|
uint16_t capacity = queue_capacity_frames(queue_num_samples);
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
|
||||||
|
if (!should_record)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame_idx >= capacity)
|
||||||
|
{
|
||||||
|
// 저장 공간 full인 경우 측정 및 저장 자동 종료
|
||||||
|
status = -ENOSPC;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Frame timestamp shared by all six channels */
|
||||||
|
uint32_t tick = k_uptime_get_32();
|
||||||
|
status = piezo_measure_sweep_once();
|
||||||
|
if (status != ECHO_STATUS_OK)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = queue_write_current_frame(frame_idx, tick);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
status = err;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
queue_total_frames++;
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
|
||||||
|
/* Keep roughly a 100 ms period if sweep/write finishes early */
|
||||||
|
uint32_t elapsed = k_uptime_get_32() - tick;
|
||||||
|
if (elapsed < MEASURE_QUEUE_PERIOD_MS)
|
||||||
|
{
|
||||||
|
k_msleep(MEASURE_QUEUE_PERIOD_MS - elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Common cleanup for manual stop, full storage, and errors */
|
||||||
|
piezo_power_off();
|
||||||
|
power_button_suspend(false);
|
||||||
|
|
||||||
|
led_set_state(LED_STATE_OFF); // storage stopped
|
||||||
|
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
queue_tick_end = k_uptime_get_32();
|
||||||
|
queue_status = status;
|
||||||
|
queue_recording = false;
|
||||||
|
queue_thread_running = false;
|
||||||
|
processing = false;
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
|
||||||
|
DBG_PRINTF("[MFQ] record thread stop status=%d frames=%u\r\n", status, queue_total_frames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start flash-backed recording
|
||||||
|
* Flow: duplicate check -> open data_storage -> erase -> init state -> start worker
|
||||||
|
* rfq: is sent by cmd_mfq() after this function returns
|
||||||
|
*/
|
||||||
|
int measure_queue_start(uint32_t *tick_start)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
queue_init_lock_once();
|
||||||
|
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
if (queue_recording || queue_thread_running)
|
||||||
|
{
|
||||||
|
if (tick_start != NULL)
|
||||||
|
{
|
||||||
|
*tick_start = queue_tick_start;
|
||||||
|
}
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
return -EALREADY;
|
||||||
|
}
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
|
||||||
|
if (queue_area == NULL)
|
||||||
|
{
|
||||||
|
// Zephyr flash_map에서 data_storage 파티션 열기
|
||||||
|
err = flash_area_open(FIXED_PARTITION_ID(data_storage), &queue_area);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
DBG_ERR("[MFQ] data_storage open failed err=%d\r\n", err);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const piezo_config_t *cfg = piezo_config_get();
|
||||||
|
uint32_t frame_size = queue_frame_size(cfg->samples);
|
||||||
|
uint16_t capacity = queue_capacity_frames(cfg->samples);
|
||||||
|
uint32_t usable_bytes = (uint32_t)capacity * frame_size;
|
||||||
|
uint32_t erase_start = k_uptime_get_32();
|
||||||
|
|
||||||
|
DBG_PRINTF("[MFQ] data record prepare area_off=0x%08x area_size=%uB samples=%u frame_size=%uB capacity=%u frames usable=%uB\r\n",
|
||||||
|
(uint32_t)queue_area->fa_off,
|
||||||
|
(uint32_t)queue_area->fa_size,
|
||||||
|
cfg->samples,
|
||||||
|
frame_size,
|
||||||
|
capacity,
|
||||||
|
usable_bytes);
|
||||||
|
DBG_PRINTF("[MFQ] data_storage erase start size=%uB\r\n", (uint32_t)queue_area->fa_size);
|
||||||
|
|
||||||
|
led_set_state(LED_STATE_DATA_ERASING); // erasing: green LED blink
|
||||||
|
|
||||||
|
// Clear previous recording before starting a new one
|
||||||
|
err = flash_area_erase(queue_area, 0, queue_area->fa_size);
|
||||||
|
uint32_t erase_ms = k_uptime_get_32() - erase_start;
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
DBG_ERR("[MFQ] data_storage erase failed err=%d elapsed=%ums\r\n", err, erase_ms);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG_PRINTF("[MFQ] data_storage erase done elapsed=%ums\r\n", erase_ms);
|
||||||
|
|
||||||
|
led_set_state(LED_STATE_DATA_STORAGING); // storaging: green LED on
|
||||||
|
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
queue_session++; // session id
|
||||||
|
queue_total_frames = 0U; // 새로운 측정 시 프레임 카운트 리셋
|
||||||
|
queue_num_samples = cfg->samples; // latch samples at recording start
|
||||||
|
queue_tick_start = k_uptime_get_32();
|
||||||
|
queue_tick_end = queue_tick_start;
|
||||||
|
queue_status = ECHO_STATUS_OK;
|
||||||
|
queue_recording = true;
|
||||||
|
queue_thread_running = true;
|
||||||
|
processing = true;
|
||||||
|
power_button_suspend(true);
|
||||||
|
if (tick_start != NULL)
|
||||||
|
{
|
||||||
|
*tick_start = queue_tick_start;
|
||||||
|
}
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
|
||||||
|
k_thread_create(&queue_thread, queue_thread_stack, K_THREAD_STACK_SIZEOF(queue_thread_stack), queue_record_thread, NULL, NULL, NULL, MEASURE_QUEUE_THREAD_PRIORITY, 0, K_NO_WAIT);
|
||||||
|
|
||||||
|
DBG_PRINTF("[MFQ] data record start session=%u tick_start=%u samples=%u frame_size=%uB capacity=%u frames\r\n",
|
||||||
|
queue_session,
|
||||||
|
queue_tick_start,
|
||||||
|
queue_num_samples,
|
||||||
|
queue_frame_size(queue_num_samples),
|
||||||
|
queue_capacity_frames(queue_num_samples));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 측정 종료 및 저장 완료 후 상태 확인 */
|
||||||
|
int measure_queue_stop(uint32_t *tick_end, uint8_t *session, uint16_t *total_frames)
|
||||||
|
{
|
||||||
|
queue_init_lock_once();
|
||||||
|
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
bool was_running = queue_thread_running;
|
||||||
|
queue_recording = false;
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
|
||||||
|
if (was_running)
|
||||||
|
{
|
||||||
|
// rfe: 응답 시 측정 중인 경우 측정 완료될 때까지 기다림
|
||||||
|
(void)k_thread_join(&queue_thread, K_FOREVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
if (tick_end != NULL)
|
||||||
|
{
|
||||||
|
*tick_end = queue_tick_end;
|
||||||
|
}
|
||||||
|
if (session != NULL)
|
||||||
|
{
|
||||||
|
*session = queue_session;
|
||||||
|
}
|
||||||
|
if (total_frames != NULL)
|
||||||
|
{
|
||||||
|
*total_frames = queue_total_frames;
|
||||||
|
}
|
||||||
|
int status = queue_status;
|
||||||
|
uint8_t stop_session = queue_session;
|
||||||
|
uint16_t stop_frames = queue_total_frames;
|
||||||
|
uint16_t num_samples = queue_num_samples;
|
||||||
|
uint32_t frame_size = queue_frame_size(num_samples);
|
||||||
|
uint16_t capacity = queue_capacity_frames(num_samples);
|
||||||
|
uint32_t usable_bytes = (uint32_t)capacity * frame_size;
|
||||||
|
uint32_t used_bytes = (uint32_t)stop_frames * frame_size;
|
||||||
|
uint32_t remain_bytes = (used_bytes < usable_bytes) ? (usable_bytes - used_bytes) : 0U;
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
|
||||||
|
DBG_PRINTF("[MFE] data record stop status=%d session=%u frames=%u/%u samples=%u frame_size=%uB used=%uB usable=%uB remain=%uB\r\n",
|
||||||
|
status,
|
||||||
|
stop_session,
|
||||||
|
stop_frames,
|
||||||
|
capacity,
|
||||||
|
num_samples,
|
||||||
|
frame_size,
|
||||||
|
used_bytes,
|
||||||
|
usable_bytes,
|
||||||
|
remain_bytes);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool measure_queue_is_recording(void)
|
||||||
|
{
|
||||||
|
queue_init_lock_once();
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
bool recording = queue_recording;
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
return recording;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t measure_queue_session(void)
|
||||||
|
{
|
||||||
|
queue_init_lock_once();
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
uint8_t session = queue_session;
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t measure_queue_total_frames(void)
|
||||||
|
{
|
||||||
|
queue_init_lock_once();
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
uint16_t total = queue_total_frames;
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t measure_queue_num_samples(void)
|
||||||
|
{
|
||||||
|
queue_init_lock_once();
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
uint16_t num_samples = queue_num_samples;
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
return num_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
int measure_queue_read_frame(uint16_t frame_idx, measure_queue_frame_header_t *header, uint16_t samples[PIEZO_NUM_CHANNELS][PIEZO_MEASURE_MAX_SAMPLES])
|
||||||
|
{
|
||||||
|
if ((header == NULL) || (samples == NULL))
|
||||||
|
{
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue_init_lock_once();
|
||||||
|
|
||||||
|
k_mutex_lock(&queue_lock, K_FOREVER);
|
||||||
|
uint16_t total = queue_total_frames;
|
||||||
|
uint16_t num_samples = queue_num_samples;
|
||||||
|
uint8_t session = queue_session;
|
||||||
|
bool recording = queue_recording;
|
||||||
|
k_mutex_unlock(&queue_lock);
|
||||||
|
|
||||||
|
if (recording)
|
||||||
|
{
|
||||||
|
// 아직 저장 측정이 진행 중일 때 mqb? 가 오는 경우 busy 에러
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame_idx >= total)
|
||||||
|
{
|
||||||
|
return -ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queue_area == NULL)
|
||||||
|
{
|
||||||
|
int err = flash_area_open(FIXED_PARTITION_ID(data_storage), &queue_area);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t frame_size = queue_frame_size(num_samples);
|
||||||
|
int err = flash_area_read(queue_area, (off_t)((uint32_t)frame_idx * frame_size), frame_read_buf, frame_size);
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build header fields for rqh: */
|
||||||
|
header->session = session;
|
||||||
|
header->frame_idx = frame_idx;
|
||||||
|
header->tick_ms = get_u32_be(frame_read_buf);
|
||||||
|
header->ch_mask = MEASURE_QUEUE_CH_MASK_ALL;
|
||||||
|
|
||||||
|
/* Restore channel samples for rqb: */
|
||||||
|
const uint8_t *p = &frame_read_buf[4];
|
||||||
|
for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++)
|
||||||
|
{
|
||||||
|
for (uint16_t i = 0; i < num_samples; i++)
|
||||||
|
{
|
||||||
|
samples[ch][i] = get_u16_be(p);
|
||||||
|
p += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* @file measure_queue.h
|
||||||
|
* @brief Flash-backed queued piezo measurement frames
|
||||||
|
******************************************************************************/
|
||||||
|
#ifndef MEASURE_QUEUE_H__
|
||||||
|
#define MEASURE_QUEUE_H__
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "piezo_measure.h"
|
||||||
|
|
||||||
|
#define MEASURE_QUEUE_CH_MASK_ALL 0x3FU
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t session;
|
||||||
|
uint16_t frame_idx;
|
||||||
|
uint32_t tick_ms;
|
||||||
|
uint8_t ch_mask;
|
||||||
|
} measure_queue_frame_header_t;
|
||||||
|
|
||||||
|
int measure_queue_start(uint32_t *tick_start);
|
||||||
|
int measure_queue_stop(uint32_t *tick_end, uint8_t *session, uint16_t *total_frames);
|
||||||
|
bool measure_queue_is_recording(void);
|
||||||
|
uint8_t measure_queue_session(void);
|
||||||
|
uint16_t measure_queue_total_frames(void);
|
||||||
|
uint16_t measure_queue_num_samples(void);
|
||||||
|
int measure_queue_read_frame(uint16_t frame_idx,
|
||||||
|
measure_queue_frame_header_t *header,
|
||||||
|
uint16_t samples[PIEZO_NUM_CHANNELS][PIEZO_MEASURE_MAX_SAMPLES]);
|
||||||
|
|
||||||
|
#endif /* MEASURE_QUEUE_H__ */
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
/* 앱에서 저장/변경 가능한 piezo 측정 기본값 */
|
/* 앱에서 저장/변경 가능한 piezo 측정 기본값 */
|
||||||
#define PIEZO_CFG_FREQ_DEFAULT PIEZO_CFG_FREQ_2_1MHZ
|
#define PIEZO_CFG_FREQ_DEFAULT PIEZO_CFG_FREQ_2_1MHZ
|
||||||
#define PIEZO_CFG_CYCLES_DEFAULT 7
|
#define PIEZO_CFG_CYCLES_DEFAULT 3
|
||||||
#define PIEZO_CFG_DELAY_DEFAULT 10
|
#define PIEZO_CFG_DELAY_DEFAULT 10
|
||||||
#define PIEZO_CFG_SAMPLES_DEFAULT 100
|
#define PIEZO_CFG_SAMPLES_DEFAULT 100
|
||||||
#define PIEZO_CFG_AVG_DEFAULT 3
|
#define PIEZO_CFG_AVG_DEFAULT 3
|
||||||
@@ -39,10 +39,7 @@
|
|||||||
#define PIEZO_AVG_INTER_BURST_GAP_US 650
|
#define PIEZO_AVG_INTER_BURST_GAP_US 650
|
||||||
|
|
||||||
/* real capture 전에 같은 채널에서 버스트+ADC capture를 버리는 횟수(dummy) */
|
/* real capture 전에 같은 채널에서 버스트+ADC capture를 버리는 횟수(dummy) */
|
||||||
#define PIEZO_DUMMY_CAPTURE_COUNT 1
|
#define PIEZO_DUMMY_CAPTURE_COUNT 5
|
||||||
|
|
||||||
/* 채널 0 시작 전 안정을 위한 추가 딜레이 */
|
|
||||||
#define PIEZO_CH0_WARMUP_TO_REAL_SETTLE_US 3000
|
|
||||||
|
|
||||||
/* 6채널 sweep 결과 (각 채널의 평균 완료 후 최종만 저장) */
|
/* 6채널 sweep 결과 (각 채널의 평균 완료 후 최종만 저장) */
|
||||||
static uint16_t piezo_channels[PIEZO_NUM_CHANNELS][PIEZO_MEASURE_MAX_SAMPLES];
|
static uint16_t piezo_channels[PIEZO_NUM_CHANNELS][PIEZO_MEASURE_MAX_SAMPLES];
|
||||||
@@ -184,7 +181,7 @@ int piezo_measure_start_session(void)
|
|||||||
return ECHO_STATUS_ADC_INIT;
|
return ECHO_STATUS_ADC_INIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
DBG_PRINTF("[SWEEP] piezo session ready\r\n");
|
//DBG_PRINTF("[SWEEP] piezo session ready\r\n");
|
||||||
return ECHO_STATUS_OK;
|
return ECHO_STATUS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +189,7 @@ int piezo_measure_start_session(void)
|
|||||||
* 6채널 전체 sweep
|
* 6채널 전체 sweep
|
||||||
* 각 채널마다 dummy capture를 먼저 버린 뒤, cfg->avg회 real capture를 sample index별로 평균
|
* 각 채널마다 dummy capture를 먼저 버린 뒤, cfg->avg회 real capture를 sample index별로 평균
|
||||||
*/
|
*/
|
||||||
int piezo_measure_sweep(void)
|
static int piezo_measure_sweep_with_avg(uint8_t avg_override)
|
||||||
{
|
{
|
||||||
const piezo_config_t *cfg = piezo_config_get();
|
const piezo_config_t *cfg = piezo_config_get();
|
||||||
|
|
||||||
@@ -200,14 +197,15 @@ int piezo_measure_sweep(void)
|
|||||||
uint8_t cycles = cfg->cycles;
|
uint8_t cycles = cfg->cycles;
|
||||||
uint16_t capture_delay_us = cfg->delay_us;
|
uint16_t capture_delay_us = cfg->delay_us;
|
||||||
uint16_t samples = cfg->samples;
|
uint16_t samples = cfg->samples;
|
||||||
uint8_t avg = cfg->avg;
|
uint8_t avg = (avg_override > 0U) ? avg_override : cfg->avg;
|
||||||
|
|
||||||
if (capture_delay_us < 0)
|
// 너무 짧은 delay가 들어오면 burst 직후 ADC capture가 겹치므로 최소 delay 보장
|
||||||
|
if (capture_delay_us < PIEZO_BURST_TO_ADC_DELAY_US)
|
||||||
{
|
{
|
||||||
capture_delay_us = 0;
|
capture_delay_us = PIEZO_BURST_TO_ADC_DELAY_US;
|
||||||
}
|
}
|
||||||
|
|
||||||
DBG_PRINTF("[SWEEP] freq=0x%04X cycles=%u avg=%u delay_us=%u samples=%u\r\n", cfg->freq, cfg->cycles, cfg->avg, cfg->delay_us, cfg->samples);
|
//DBG_PRINTF("[SWEEP] freq=0x%04X cycles=%u avg=%u delay_us=%u samples=%u\r\n", cfg->freq, cfg->cycles, cfg->avg, cfg->delay_us, cfg->samples);
|
||||||
|
|
||||||
for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++)
|
for (uint8_t ch = 0; ch < PIEZO_NUM_CHANNELS; ch++)
|
||||||
{
|
{
|
||||||
@@ -233,8 +231,7 @@ int piezo_measure_sweep(void)
|
|||||||
|
|
||||||
if (ch == 0U) // 채널 0 시작 전에만 dummy burst + ADC capture 수행
|
if (ch == 0U) // 채널 0 시작 전에만 dummy burst + ADC capture 수행
|
||||||
{
|
{
|
||||||
// Dummy burst + ADC caputre
|
// Dummy burst - ADC caputre는 수행하지 않음
|
||||||
/*
|
|
||||||
for (uint8_t dummy = 0; dummy < PIEZO_DUMMY_CAPTURE_COUNT; dummy++)
|
for (uint8_t dummy = 0; dummy < PIEZO_DUMMY_CAPTURE_COUNT; dummy++)
|
||||||
{
|
{
|
||||||
k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US);
|
k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US);
|
||||||
@@ -255,20 +252,10 @@ int piezo_measure_sweep(void)
|
|||||||
DBG_PRINTF("[ECHO] dummy capture fail ch=%d dummy=%d err=%d\r\n", ch, dummy, err);
|
DBG_PRINTF("[ECHO] dummy capture fail ch=%d dummy=%d err=%d\r\n", ch, dummy, err);
|
||||||
return ECHO_STATUS_CAPTURE;
|
return ECHO_STATUS_CAPTURE;
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
|
|
||||||
k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US);
|
|
||||||
piezo_burst_sw_freq(freq, cycles);
|
|
||||||
k_busy_wait(capture_delay_us);
|
|
||||||
err = echo_adc_capture(echo_capture, samples);
|
|
||||||
if (err)
|
|
||||||
{
|
|
||||||
//DBG_PRINTF("[ECHO] dummy capture fail ch=%d dummy=%d err=%d\r\n", ch, dummy, err);
|
|
||||||
return ECHO_STATUS_CAPTURE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 마지막 dummy capture와 첫 real capture 사이에도 같은 회복 시간 + 추가 안정화
|
// 마지막 dummy capture와 첫 real capture 사이에도 같은 회복 시간
|
||||||
k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US + PIEZO_CH0_WARMUP_TO_REAL_SETTLE_US);
|
k_busy_wait(PIEZO_AVG_INTER_BURST_GAP_US + 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 여기부터 실제 평균에 들어갈 capture
|
// 여기부터 실제 평균에 들어갈 capture
|
||||||
@@ -288,7 +275,6 @@ int piezo_measure_sweep(void)
|
|||||||
|
|
||||||
// IRQ 잠금 해제
|
// IRQ 잠금 해제
|
||||||
irq_unlock(key);
|
irq_unlock(key);
|
||||||
|
|
||||||
if (err)
|
if (err)
|
||||||
{
|
{
|
||||||
DBG_PRINTF("[ECHO] capture fail ch=%d avg=%d err=%d\r\n", ch, a, err);
|
DBG_PRINTF("[ECHO] capture fail ch=%d avg=%d err=%d\r\n", ch, a, err);
|
||||||
@@ -315,6 +301,16 @@ int piezo_measure_sweep(void)
|
|||||||
return ECHO_STATUS_OK;
|
return ECHO_STATUS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int piezo_measure_sweep(void)
|
||||||
|
{
|
||||||
|
return piezo_measure_sweep_with_avg(0U);
|
||||||
|
}
|
||||||
|
|
||||||
|
int piezo_measure_sweep_once(void)
|
||||||
|
{
|
||||||
|
return piezo_measure_sweep_with_avg(1U);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 테스트용
|
* 테스트용
|
||||||
* 단일 채널 burst + echo capture
|
* 단일 채널 burst + echo capture
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ int piezo_config_set(const piezo_config_t *cfg);
|
|||||||
|
|
||||||
int piezo_measure_start_session(void);
|
int piezo_measure_start_session(void);
|
||||||
int piezo_measure_sweep(void);
|
int piezo_measure_sweep(void);
|
||||||
|
int piezo_measure_sweep_once(void);
|
||||||
int piezo_measure_single_capture(uint8_t freq, uint8_t cycles, uint16_t delay_us, uint16_t num_samples, uint16_t averaging, uint8_t channel);
|
int piezo_measure_single_capture(uint8_t freq, uint8_t cycles, uint16_t delay_us, uint16_t num_samples, uint16_t averaging, uint8_t channel);
|
||||||
int piezo_measure_adc_only_capture(uint16_t num_samples, uint16_t averaging, uint8_t channel);
|
int piezo_measure_adc_only_capture(uint16_t num_samples, uint16_t averaging, uint8_t channel);
|
||||||
|
|
||||||
|
|||||||
@@ -1,382 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* @file power_control.c
|
|
||||||
* @brief Device power sequence control
|
|
||||||
******************************************************************************/
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
#include <zephyr/drivers/gpio.h>
|
|
||||||
#include <zephyr/sys/reboot.h>
|
|
||||||
#if IS_ENABLED(CONFIG_BT_SMP)
|
|
||||||
#include <zephyr/bluetooth/bluetooth.h>
|
|
||||||
#include <zephyr/bluetooth/conn.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "main.h"
|
|
||||||
#include "power_control.h"
|
|
||||||
#include "debug_print.h"
|
|
||||||
#include "led_control.h"
|
|
||||||
#include "ble_service.h"
|
|
||||||
#include "battery_adc.h"
|
|
||||||
|
|
||||||
#define POWER_HOLD_NODE DT_NODELABEL(pwr_hold) // 전원 래치
|
|
||||||
#define POWER_BTN_NODE DT_NODELABEL(button_check) // 전원 버튼
|
|
||||||
#define POWER_LOOP_INTERVAL 20 // 전원 시퀀스 타이머 간격(ms)
|
|
||||||
#define POWER_ON_THRESHOLD 350 // power button hold threshold(off -> on)
|
|
||||||
#define POWER_OFF_THRESHOLD 500 // power button hold threshold(on -> off)
|
|
||||||
#define BOND_RESET_THRESHOLD (15000 / POWER_ON_DELAY) // 15s long press bond reset
|
|
||||||
|
|
||||||
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); // 전원 버튼 입력
|
|
||||||
|
|
||||||
static struct k_timer m_power_on_delay_timer; // 전원 버튼 상태머신 타이머
|
|
||||||
static struct k_timer m_power_off_delay_timer; // 전원 OFF 지연 타이머
|
|
||||||
static struct k_timer m_power_timer; // 전원 시퀀스 반복 타이머
|
|
||||||
|
|
||||||
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 uint16_t cnt_s; // power button hold counter
|
|
||||||
static bool device_on; // power latch state
|
|
||||||
static bool boot_btn_released; // 부팅 후 버튼을 놓았는지 여부
|
|
||||||
static bool power_btn_suspended; // 측정 중 버튼 상태머신 일시 정지
|
|
||||||
static const char *power_off_pending_reason = "none"; // 예약된 전원 OFF 사유
|
|
||||||
static bool bond_reset_pending; // 본드 삭제 work 중복 예약 방지
|
|
||||||
static uint8_t p_order; // 전원 시퀀스 단계
|
|
||||||
static bool lock_check = false; // power sequence lock flag
|
|
||||||
|
|
||||||
/* 시스템 워크에서 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);
|
|
||||||
k_msleep(1500);
|
|
||||||
sys_reboot(SYS_REBOOT_COLD);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 부팅 직후 전원 래치를 유지할지 결정 */
|
|
||||||
void power_control_latch_init(bool keep_on)
|
|
||||||
{
|
|
||||||
gpio_pin_configure_dt(&power_hold, keep_on ? GPIO_OUTPUT_ACTIVE : GPIO_OUTPUT_INACTIVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 전원 버튼 상태머신 초기값 */
|
|
||||||
void power_control_reset_state(void)
|
|
||||||
{
|
|
||||||
cnt_s = 0;
|
|
||||||
device_on = false;
|
|
||||||
boot_btn_released = false;
|
|
||||||
power_btn_suspended = false;
|
|
||||||
bond_reset_pending = false;
|
|
||||||
power_off_pending_reason = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* POWER_HOLD 핀으로 실제 전원 래치 ON/OFF 제어 */
|
|
||||||
static void power_control_handler(on_off_cont_t device_power_st, const char *reason)
|
|
||||||
{
|
|
||||||
ARG_UNUSED(reason);
|
|
||||||
|
|
||||||
if (device_power_st == OFF)
|
|
||||||
{
|
|
||||||
gpio_pin_set_dt(&power_hold, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gpio_pin_set_dt(&power_hold, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 전원 버튼 GPIO 초기화 */
|
|
||||||
static void power_gpio_init(void)
|
|
||||||
{
|
|
||||||
gpio_pin_configure_dt(&power_btn, GPIO_INPUT);
|
|
||||||
DBG_PRINTF("[INIT] HW - GPIO OK (POWER BTN=%d)\r\n", gpio_pin_get_dt(&power_btn));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 전원 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* sleep/전원 OFF: LED 표시 후 지연 시간 뒤 전원 차단 */
|
|
||||||
static void power_off_schedule(const char *reason, led_state_t led_state)
|
|
||||||
{
|
|
||||||
power_off_pending_reason = reason ? reason : "unknown";
|
|
||||||
led_set_state(led_state);
|
|
||||||
k_timer_start(&m_power_off_delay_timer, K_MSEC(POWER_OFF_DELAY), K_NO_WAIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 사유를 포함한 sleep 모드 진입 요청 */
|
|
||||||
void sleep_mode_enter_reason(const char *reason)
|
|
||||||
{
|
|
||||||
power_off_schedule(reason, LED_STATE_POWER_ON);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 기본 sleep 모드 진입 요청 */
|
|
||||||
void sleep_mode_enter(void)
|
|
||||||
{
|
|
||||||
sleep_mode_enter_reason("sleep_mode_enter");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 기본 전원 OFF 요청 */
|
|
||||||
void device_power_off(void)
|
|
||||||
{
|
|
||||||
device_power_off_reason("device_power_off");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 사유를 포함한 전원 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);
|
|
||||||
|
|
||||||
// 버튼 입력은 눌림 상태가 1
|
|
||||||
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 상태: 버튼을 충분히 누르면 전원 래치
|
|
||||||
if (!device_on)
|
|
||||||
{
|
|
||||||
if (!button_pressed) // 2초 미만으로 놓으면 전원 유지 실패 처리
|
|
||||||
{
|
|
||||||
power_control_handler(OFF, "boot-button-released-before-latch");
|
|
||||||
cnt_s = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cnt_s++;
|
|
||||||
|
|
||||||
if (cnt_s == POWER_ON_THRESHOLD) // 임계 시간 도달 시 전원 ON 확정
|
|
||||||
{
|
|
||||||
device_on = true;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 전원 ON 상태: 부팅 때 누른 버튼을 먼저 놓아야 새 입력으로 봄
|
|
||||||
if (!boot_btn_released)
|
|
||||||
{
|
|
||||||
if (!button_pressed) // 부팅 때 누른 버튼을 놓으면 이후 새 입력으로 판단
|
|
||||||
{
|
|
||||||
boot_btn_released = true;
|
|
||||||
cnt_s = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cnt_s++;
|
|
||||||
|
|
||||||
if ((cnt_s >= BOND_RESET_THRESHOLD) && !bond_reset_pending) // 부팅 버튼 15초 유지 시 본드 삭제
|
|
||||||
{
|
|
||||||
bond_reset_pending = true;
|
|
||||||
k_work_submit(&bond_reset_work);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (button_pressed) // ON 상태에서 새로 길게 누르면 전원 OFF
|
|
||||||
{
|
|
||||||
cnt_s++;
|
|
||||||
|
|
||||||
if (cnt_s >= POWER_OFF_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 power_loop_expiry(struct k_timer *timer)
|
|
||||||
{
|
|
||||||
power_loop(timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 슬립 진입 전 처리 중 플래그 정리 */
|
|
||||||
int device_sleep_mode(void)
|
|
||||||
{
|
|
||||||
k_msleep(2);
|
|
||||||
DBG_PRINTF("Device_Sleep_Mode OK!\r\n");
|
|
||||||
k_msleep(10);
|
|
||||||
processing = false;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 최초 전원 활성화 시퀀스 시작 */
|
|
||||||
int device_activated(void)
|
|
||||||
{
|
|
||||||
p_order = 0;
|
|
||||||
lock_check = true;
|
|
||||||
power_timer_start();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 전원 활성화 단계 처리 - 필요 시 다음 타이머 예약 */
|
|
||||||
void power_loop(struct k_timer *timer)
|
|
||||||
{
|
|
||||||
power_timer_stop();
|
|
||||||
|
|
||||||
// 센서 초기화는 각 측정 함수에서 필요 시 수행
|
|
||||||
p_order = 2;
|
|
||||||
|
|
||||||
if (p_order < 2)
|
|
||||||
{
|
|
||||||
p_order++;
|
|
||||||
power_timer_start();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DBG_PRINTF("[PWR] Device Activated OK!\r\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 소프트 리셋/복귀 후 전원 시퀀스 다시 시작 */
|
|
||||||
int device_reactivated(void)
|
|
||||||
{
|
|
||||||
// 필요 시 복귀 전용 초기화 추가
|
|
||||||
k_msleep(10);
|
|
||||||
lock_check = true;
|
|
||||||
p_order = 0;
|
|
||||||
power_timer_start();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 전원 시퀀스 타이머 1회 예약 */
|
|
||||||
void power_timer_start(void)
|
|
||||||
{
|
|
||||||
k_timer_start(&m_power_timer, K_MSEC(POWER_LOOP_INTERVAL), K_NO_WAIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 예약된 전원 시퀀스 타이머 중지 */
|
|
||||||
void power_timer_stop(void)
|
|
||||||
{
|
|
||||||
k_timer_stop(&m_power_timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 전원 시퀀스 타이머 콜백 등록 */
|
|
||||||
void power_timer_init(void)
|
|
||||||
{
|
|
||||||
k_timer_init(&m_power_timer, power_loop_expiry, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 전원 관련 타이머/work 초기화 */
|
|
||||||
void power_control_init(void)
|
|
||||||
{
|
|
||||||
power_gpio_init();
|
|
||||||
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);
|
|
||||||
k_work_init(&adv_start_work, adv_start_work_handler);
|
|
||||||
k_work_init(&adv_stop_work, adv_stop_work_handler);
|
|
||||||
k_work_init(&bond_reset_work, bond_reset_work_handler);
|
|
||||||
power_timer_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 전원 버튼 상태머신 시작 */
|
|
||||||
void power_control_start(void)
|
|
||||||
{
|
|
||||||
k_timer_start(&m_power_on_delay_timer, K_MSEC(POWER_ON_DELAY), K_NO_WAIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* soft reset/DFU reset/test image 부팅 시 버튼 없이 정상 ON 상태로 복구 */
|
|
||||||
void power_control_resume_after_reset(uint32_t reset_reason)
|
|
||||||
{
|
|
||||||
device_on = true;
|
|
||||||
cnt_s = 0;
|
|
||||||
boot_btn_released = (gpio_pin_get_dt(&power_btn) != 1);
|
|
||||||
m_reset_status = 1;
|
|
||||||
|
|
||||||
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", reset_reason);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* @file power_control.c
|
||||||
|
* @brief Device power sequence control
|
||||||
|
*
|
||||||
|
* Power-up sequence state machine with k_timer (single-shot 20ms intervals)
|
||||||
|
******************************************************************************/
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include "main.h"
|
||||||
|
#include "power_control.h"
|
||||||
|
#include "debug_print.h"
|
||||||
|
|
||||||
|
#define POWER_LOOP_INTERVAL 20 /* ms */
|
||||||
|
|
||||||
|
static struct k_timer m_power_timer;
|
||||||
|
static uint8_t p_order;
|
||||||
|
static bool lock_check = false;
|
||||||
|
|
||||||
|
extern volatile bool processing;
|
||||||
|
|
||||||
|
static void power_loop_expiry(struct k_timer *timer)
|
||||||
|
{
|
||||||
|
power_loop(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
int device_sleep_mode(void)
|
||||||
|
{
|
||||||
|
k_msleep(2);
|
||||||
|
DBG_PRINTF("Device_Sleep_Mode OK!\r\n");
|
||||||
|
k_msleep(10);
|
||||||
|
processing = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int device_activated(void)
|
||||||
|
{
|
||||||
|
p_order = 0;
|
||||||
|
lock_check = true;
|
||||||
|
power_timer_start();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void power_loop(struct k_timer *timer)
|
||||||
|
{
|
||||||
|
power_timer_stop();
|
||||||
|
|
||||||
|
// Sensor init not needed - imu_read_direct() handles it per measurement
|
||||||
|
p_order = 2;
|
||||||
|
|
||||||
|
if (p_order < 2)
|
||||||
|
{
|
||||||
|
p_order++;
|
||||||
|
power_timer_start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DBG_PRINTF("[PWR] Device Activated OK!\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int device_reactivated(void)
|
||||||
|
{
|
||||||
|
// sw_i2c_init_once() will be added in Stage 3
|
||||||
|
k_msleep(10);
|
||||||
|
lock_check = true;
|
||||||
|
p_order = 0;
|
||||||
|
power_timer_start();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void power_timer_start(void)
|
||||||
|
{
|
||||||
|
k_timer_start(&m_power_timer, K_MSEC(POWER_LOOP_INTERVAL), K_NO_WAIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void power_timer_stop(void)
|
||||||
|
{
|
||||||
|
k_timer_stop(&m_power_timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void power_timer_init(void)
|
||||||
|
{
|
||||||
|
k_timer_init(&m_power_timer, power_loop_expiry, NULL);
|
||||||
|
}
|
||||||
@@ -5,17 +5,8 @@
|
|||||||
#ifndef POWER_CONTROL_H__
|
#ifndef POWER_CONTROL_H__
|
||||||
#define POWER_CONTROL_H__
|
#define POWER_CONTROL_H__
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <zephyr/kernel.h>
|
|
||||||
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
|
||||||
void power_control_latch_init(bool keep_on);
|
|
||||||
void power_control_reset_state(void);
|
|
||||||
void power_control_init(void);
|
|
||||||
void power_control_start(void);
|
|
||||||
void power_control_resume_after_reset(uint32_t reset_reason);
|
|
||||||
int device_sleep_mode(void);
|
int device_sleep_mode(void);
|
||||||
int device_activated(void);
|
int device_activated(void);
|
||||||
int device_reactivated(void);
|
int device_reactivated(void);
|
||||||
@@ -75,32 +75,3 @@ if(EXISTS "${APP_PRJ_CONF}")
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Production HEX file name based on src/main.h FIRMWARE_VERSION.
|
|
||||||
set(APP_MAIN_H "${APP_DIR}/src/main.h")
|
|
||||||
if(EXISTS "${APP_MAIN_H}")
|
|
||||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${APP_MAIN_H}")
|
|
||||||
file(READ "${APP_MAIN_H}" APP_MAIN_H_TEXT)
|
|
||||||
|
|
||||||
if(APP_MAIN_H_TEXT MATCHES "#define[ \t]+FIRMWARE_VERSION[ \t]+\"([^\"]+)\"")
|
|
||||||
set(FW_HEX_VERSION "${CMAKE_MATCH_1}")
|
|
||||||
string(REGEX REPLACE "[^A-Za-z0-9_.+-]" "_" FW_HEX_VERSION "${FW_HEX_VERSION}")
|
|
||||||
|
|
||||||
set(FW_HEX_SOURCE "${CMAKE_BINARY_DIR}/merged.hex")
|
|
||||||
set(FW_HEX_RELEASE_DIR "${APP_DIR}/release")
|
|
||||||
set(FW_HEX_VERSIONED "${FW_HEX_RELEASE_DIR}/VesiScan-Basic_${FW_HEX_VERSION}.hex")
|
|
||||||
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT "${FW_HEX_VERSIONED}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${FW_HEX_RELEASE_DIR}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FW_HEX_SOURCE}" "${FW_HEX_VERSIONED}"
|
|
||||||
DEPENDS "${FW_HEX_SOURCE}"
|
|
||||||
COMMENT "Generating firmware-versioned HEX ${FW_HEX_VERSIONED}"
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
|
|
||||||
add_custom_target(firmware_versioned_hex ALL
|
|
||||||
DEPENDS "${FW_HEX_VERSIONED}"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -2,5 +2,3 @@
|
|||||||
# Re-enable NETCORE_HCI_IPC only for nRF5340 sysbuild configurations.
|
# Re-enable NETCORE_HCI_IPC only for nRF5340 sysbuild configurations.
|
||||||
|
|
||||||
SB_CONFIG_BOOTLOADER_MCUBOOT=y
|
SB_CONFIG_BOOTLOADER_MCUBOOT=y
|
||||||
SB_CONFIG_BOOT_SIGNATURE_TYPE_ECDSA_P256=y
|
|
||||||
SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${APPLICATION_CONFIG_DIR}/keys/mcuboot_private.pem"
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
CONFIG_BT_CTLR_TX_PWR_PLUS_8=y
|
CONFIG_BT_CTLR_TX_PWR_PLUS_4=y
|
||||||
CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y
|
CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y
|
||||||
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
|
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251
|
||||||
|
|||||||
@@ -1,20 +1 @@
|
|||||||
CONFIG_GPIO_HOGS=y
|
CONFIG_GPIO_HOGS=y
|
||||||
|
|
||||||
# Keep MCUboot LOG enabled to satisfy MCUboot default Kconfig choices,
|
|
||||||
# but disable console/log output backends.
|
|
||||||
CONFIG_LOG=y
|
|
||||||
CONFIG_LOG_DEFAULT_LEVEL=0
|
|
||||||
CONFIG_MCUBOOT_LOG_LEVEL_OFF=y
|
|
||||||
|
|
||||||
CONFIG_USE_SEGGER_RTT=y
|
|
||||||
CONFIG_CONSOLE=n
|
|
||||||
CONFIG_RTT_CONSOLE=n
|
|
||||||
CONFIG_LOG_BACKEND_RTT=n
|
|
||||||
CONFIG_LOG_BACKEND_SHOW_COLOR=n
|
|
||||||
|
|
||||||
CONFIG_LOG_BACKEND_UART=n
|
|
||||||
CONFIG_UART_CONSOLE=n
|
|
||||||
|
|
||||||
# security boot
|
|
||||||
CONFIG_REBOOT=y
|
|
||||||
CONFIG_DISABLE_FLASH_PATCH=y
|
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal enabledelayedexpansion
|
|
||||||
|
|
||||||
REM ==============================================================
|
|
||||||
REM Build + Flash + APPROTECT lock (PRODUCTION ONLY)
|
|
||||||
REM - Rebuilds so build\merged.hex is up to date, then flashes.
|
|
||||||
REM - Do NOT run on development boards (locking needs --recover).
|
|
||||||
REM ==============================================================
|
|
||||||
|
|
||||||
set "REPO=%~dp0.."
|
|
||||||
set "BUILDDIR=%REPO%\build"
|
|
||||||
set "HEX=%BUILDDIR%\merged.hex"
|
|
||||||
|
|
||||||
REM cmake from the nRF Connect toolchain, fallback to PATH
|
|
||||||
set "CMAKE=C:\ncs\toolchains\fd21892d0f\opt\bin\cmake.exe"
|
|
||||||
if not exist "%CMAKE%" set "CMAKE=cmake"
|
|
||||||
|
|
||||||
echo [FLASH] FULL ERASE + APPROTECT LOCK mode (production)
|
|
||||||
echo [FLASH] This wipes application flash, NVS/settings, and BLE bonds.
|
|
||||||
echo [FLASH] After locking, SWD debug read/write is disabled.
|
|
||||||
echo [FLASH] To unlock later: nrfjprog --recover (mass-erases the chip).
|
|
||||||
echo [FLASH] DFU(SMP) over BLE still works after locking.
|
|
||||||
echo.
|
|
||||||
|
|
||||||
REM --- Step 1: build (updates merged.hex) ---
|
|
||||||
echo [FLASH] Step 1/4: build (update merged.hex)
|
|
||||||
"%CMAKE%" --build "%BUILDDIR%"
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo [FLASH] Build FAILED. Aborting.
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
if not exist "%HEX%" (
|
|
||||||
echo [FLASH] merged.hex not found: %HEX%
|
|
||||||
echo [FLASH] Build the project first.
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
echo [FLASH] Using: %HEX%
|
|
||||||
echo.
|
|
||||||
|
|
||||||
REM --- Step 2: chip erase + program + verify ---
|
|
||||||
echo [FLASH] Step 2/4: chip erase + program + verify
|
|
||||||
nrfjprog --program "%HEX%" --chiperase --verify --reset
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo [FLASH] Program FAILED. Aborting before lock.
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
REM --- Step 3: enable APPROTECT ---
|
|
||||||
echo [FLASH] Step 3/4: enabling APPROTECT (readback protection)
|
|
||||||
nrfjprog --rbp ALL
|
|
||||||
|
|
||||||
REM --- Step 4: pin reset to apply protection ---
|
|
||||||
echo [FLASH] Step 4/4: pin reset
|
|
||||||
nrfjprog --pinreset
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo [FLASH] Done. POWER-CYCLE the device to fully latch APPROTECT.
|
|
||||||
echo [FLASH] Verify lock: nrfjprog --memrd 0x0 should now FAIL.
|
|
||||||
endlocal
|
|
||||||
pause
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
param(
|
|
||||||
[string]$Hex = "build\merged.hex"
|
|
||||||
)
|
|
||||||
|
|
||||||
Set-StrictMode -Version Latest
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
|
|
||||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
|
||||||
$HexPath = Join-Path $RepoRoot $Hex
|
|
||||||
|
|
||||||
if (-not (Test-Path -LiteralPath $HexPath)) {
|
|
||||||
throw "Hex file not found: $HexPath. Build the project first."
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "[FLASH] FULL ERASE + APPROTECT LOCK mode (production)"
|
|
||||||
Write-Host "[FLASH] This wipes application flash, NVS/settings, and BLE bonds."
|
|
||||||
Write-Host "[FLASH] After locking, SWD debug read/write is disabled permanently."
|
|
||||||
Write-Host "[FLASH] To unlock later you MUST run 'nrfjprog --recover', which mass-erases the chip."
|
|
||||||
Write-Host "[FLASH] DFU(SMP) over BLE still works after locking."
|
|
||||||
Write-Host "[FLASH] Programming: $HexPath"
|
|
||||||
|
|
||||||
# 1) 전체 지우고 서명된 펌웨어 프로그램
|
|
||||||
Write-Host "[FLASH] Step 1/3: chip erase + program + verify"
|
|
||||||
nrfjprog --program $HexPath --chiperase --verify --reset
|
|
||||||
|
|
||||||
# 2) UICR.APPROTECT 잠금 (SWD 디버그 포트 차단)
|
|
||||||
Write-Host "[FLASH] Step 2/3: enabling APPROTECT (readback protection)"
|
|
||||||
nrfjprog --rbp ALL
|
|
||||||
|
|
||||||
# 3) APPROTECT는 파워사이클/핀리셋 후 실제 적용됨
|
|
||||||
Write-Host "[FLASH] Step 3/3: pin reset to apply protection"
|
|
||||||
nrfjprog --pinreset
|
|
||||||
|
|
||||||
Write-Host "[FLASH] Done. POWER-CYCLE the device to fully latch APPROTECT."
|
|
||||||
Write-Host "[FLASH] Verify lock: 'nrfjprog --memrd 0x0' should now FAIL."
|
|
||||||
Reference in New Issue
Block a user