Files
VesiScan-Basic_imu/project/ble_peripheral/ble_app_bladder_patch/main.c
T
jh.chun 26c6d035f0 EMC RS 시험 대비 supervision timeout 및 동적 TX power 제어 추가
- supervision timeout을 4초에서 10초로 변경
- RSSI/RSSI silence/HVN 지연/TX queue 적체 기반 TX power boost 추가
- 정상 시 +4 dBm, link stress 시 +8 dBm으로 동적 제어
2026-06-15 15:39:41 +09:00

2318 lines
85 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*******************************************************************************
* @file main.c
* @brief Medithings VesiScan BASIC
* @version 1.17
* @date 2025-12-09
* @author Charles KWON
*
* [System Architecture Overview]
* VesiScan BASIC is an nRF52840 (ARM Cortex-M4F, 64MHz) based BLE bladder monitoring patch device.
* Uses SoftDevice S140 (BLE 5.0) and communicates with the smartphone app via BLE NUS (Nordic UART Service) binary protocol.
*
* [Hardware Configuration]
* - MCU: nRF52840 (SoftDevice S140 v7.x)
* - IMU: ICM42670P 6-axis accel/gyro (I2C, 0x68)
* - Temperature: IMU register direct read
* - Battery: voltage divider (SAADC AIN2, 1/3 prescale)
* - Power: POWER_HOLD(P0.8) self-latch circuit, POWER_BUTTON(P1.8) input
*
* [Software Boot Sequence - main()]
* Phase 1: Basic hardware (power latch, log, GPIO, timers, config, buttons/LEDs)
* Phase 2: BLE stack (SoftDevice, power mgmt, DC-DC)
* Phase 3: FDS + config load
* Phase 4: BLE protocol (GAP, GATT, NUS, Advertising, security)
* Phase 5: Application (parser, piezo)
* Boot complete -> power button state machine (main_s) -> idle loop
*
* [Power Button State Machine - main_s() callback, 5ms interval]
* - Short press (<1.5s, cnt_s<150): power OFF
* - Medium press (1.5s~10s, cnt_s>=150): start boot sequence (sensor+BLE advertising)
* - Long press (>10s, cnt_s>1000): factory reset (passkey reset + power OFF)
*
* [Event Flow]
* 1. Receive command from app via BLE NUS -> nus_data_handler -> dr_cmd_parser
* 2. Receive command from UART -> uart_event_handle -> dr_cmd_parser
* 3. Command parser (cmd_parse.c) dispatches by tag (sta?, ssn?, ssp?, etc.)
* 4. Build binary packets from sensor data via format_data() family
* 5. Transmit over BLE with CRC16 via dr_binary_tx_safe() (max 100 retries)
*
* [Build Modes]
* BLE_DEV_MODE = 1 : No security (fast pairing, development)
* BLE_DEV_MODE = 0 : Static passkey + MITM protection (production)
******************************************************************************/
/*==============================================================================
* INCLUDES
*============================================================================*/
#include <stdint.h>
#include <string.h>
#include "nordic_common.h"
#include "nrf.h"
#include "ble_hci.h" /* BLE HCI error code definitions */
#include "ble_advdata.h" /* BLE advertising data structures */
#include "ble_advertising.h" /* BLE advertising module */
#include "ble_srv_common.h" /* BLE service common utilities */
#include "ble_conn_params.h" /* BLE connection parameter negotiation */
#include "nrf_sdh.h" /* SoftDevice handler (BLE stack management) */
#include "nrf_sdh_soc.h" /* SoftDevice SoC event handler */
#include "nrf_sdh_ble.h" /* SoftDevice BLE event handler */
#include "nrf_ble_gatt.h" /* BLE GATT module (MTU negotiation) */
#include "nrf_ble_qwr.h" /* BLE Queued Write module (long characteristic writes) */
#include "app_timer.h" /* App timer (RTC1-based soft timer) */
#include "ble_nus.h" /* Nordic UART Service (BLE bidirectional data) */
#include "app_uart.h" /* Physical UART driver (debug/factory test) */
#include "app_util_platform.h" /* Interrupt priority macros */
#include "nrf_pwr_mgmt.h" /* Power management (WFE/sleep when idle) */
#include "nrf_delay.h" /* Blocking delay (us/ms) */
#include "math.h"
#include "crc16.h" /* CRC16 computation (BLE packet integrity) */
#if defined (UART_PRESENT)
#include "nrf_uart.h"
#endif
#if defined (UARTE_PRESENT)
#include "nrf_uarte.h"
#endif
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#if BLE_DFU_ENABLED
#include "nrf_pwr_mgmt.h"
#include "ble_dfu.h"
#include "nrf_power.h"
#include "nrf_bootloader_info.h"
#include "nrf_dfu_ble_svci_bond_sharing.h"
#include "nrf_svci_async_function.h"
#include "nrf_svci_async_handler.h"
#endif
/* -- Application module headers -- */
#include "system_interface.h" /* System interface (IMU sensor I2C communication) */
#include "main.h" /* Main header (global structs, constants, extern declarations) */
#include "app_raw_main.h" /* Sensor raw data processing module */
#include "main_timer.h" /* Main event loop timer (10ms period) */
#include "power_control.h" /* Power sequence control (ON/OFF/sleep) */
#include "fds.h" /* Flash Data Storage (non-volatile config) */
#include "battery_saadc.h" /* Battery voltage measurement (SAADC) */
/* -- BLE security headers (when FEATURE_SECURE_CONNECTION enabled) -- */
#if FEATURE_SECURE_CONNECTION
#include "peer_manager.h" /* Peer Manager (bonding/pairing management) */
#include "peer_manager_handler.h" /* Peer Manager default event handler */
#include "nrf_ble_lesc.h" /* BLE LESC (LE Secure Connections) support */
#include "ble_quick_security.h" /* Quick security setup wrapper */
#include "i2c_manager.h" /* I2C bus manager (sensor communication) */
#endif
/* -- Crypto / command parser / debug -- */
#include "parser.h" /* Binary command parser (dr_cmd_parser) */
#include "cmd_table.h" /* Command table registration (cmd_table_init) */
#include "debug_print.h" /* Debug output macro (DBG_PRINTF) */
#include "fstorage.h" /* Flash Storage wrapper (FDS init/save/load) */
#include "dr_piezo.h" /* Piezo ultrasound driver */
#include "led_control.h" /* LED direct control driver */
/*==============================================================================
* Build Configuration
*============================================================================*/
#define BLE_DEV_MODE 0 /* 1: Dev mode (no security), 0: Production mode (passkey required) */
/*==============================================================================
* Hardware Pin Definitions
*============================================================================*/
#define POWER_HOLD NRF_GPIO_PIN_MAP(0,8) /* Power self-latch pin (HIGH=power maintained) */
#define POWER_BUTTON NRF_GPIO_PIN_MAP(1,8) /* Power button input pin (LOW=pressed) */
/*==============================================================================
* BLE Configuration
*============================================================================*/
#define APP_BLE_CONN_CFG_TAG 1 /* SoftDevice BLE config tag */
#define NUS_SERVICE_UUID_TYPE BLE_UUID_TYPE_VENDOR_BEGIN /* NUS UUID type (vendor-specific) */
#define APP_BLE_OBSERVER_PRIO 3 /* BLE event observer priority */
#define APP_ADV_INTERVAL 64 /* Advertising interval: 64 x 0.625ms = 40ms */
#define APP_ADV_DURATION_NORMAL 60000 /* Normal disconnect adv duration: 60000 x 10ms = 10min */
#define APP_ADV_DURATION_UNLIMITED 0 /* Unintended disconnect: unlimited advertising */
#define APP_ADV_DURATION APP_ADV_DURATION_NORMAL /* Default adv duration (changed at runtime by disconnect reason) */
#define MIN_CONN_INTERVAL MSEC_TO_UNITS(15, UNIT_1_25_MS) /* Min connection interval: 15ms */
#define MAX_CONN_INTERVAL MSEC_TO_UNITS(15, UNIT_1_25_MS) /* Max connection interval: 15ms */
#define SLAVE_LATENCY 0 /* Slave latency: 0 (respond every connection event) */
#define CONN_SUP_TIMEOUT MSEC_TO_UNITS(10000, UNIT_10_MS) /* EMC: extend supervision timeout 4s -> 10s to tolerate short RF fades */
#define FIRST_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(5000) /* Wait 5s before first param update request */
#define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(30000) /* Subsequent param update interval: 30s */
#define MAX_CONN_PARAMS_UPDATE_COUNT 3 /* Max param update attempts */
/* EMC BLE link protection:
* - Start each connection at normal +4 dBm.
* - Boost to +8 dBm when RSSI is repeatedly poor, RSSI events stop, HVN complete is slow/stuck,
* or the NUS pending queue backs up.
* - Restore to +4 dBm only after RSSI is stably good and TX is idle.
*/
#define BLE_TX_POWER_NORMAL_DBM 4 /* Normal connected TX power */
#define BLE_TX_POWER_BOOST_DBM 8 /* Temporary TX power boost during link stress */
#define BLE_TX_POWER_UNKNOWN_DBM 127 /* Sentinel to force first SoftDevice TX power apply */
#define BLE_RSSI_BAD_DBM (-80) /* Weak central signal threshold: count toward boost */
#define BLE_RSSI_GOOD_DBM (-65) /* Good central signal threshold: count toward restore */
#define BLE_RSSI_BAD_COUNT_LIMIT 3 /* Poor RSSI events needed before RSSI-based boost */
#define BLE_RSSI_GOOD_COUNT_LIMIT 10 /* Good RSSI events needed before restore */
#define BLE_RSSI_CHANGE_THRESHOLD_DBM 1 /* Request RSSI event on >=1 dB change */
#define BLE_RSSI_SKIP_COUNT 0 /* Report RSSI immediately; useful during EMC validation */
#define BLE_LINK_STRESS_TICK_MS 200 /* Periodic link-health poll interval */
#define BLE_LINK_STRESS_RSSI_SILENCE_MS 500 /* No RSSI event for this long -> suspect downlink stress */
#define BLE_LINK_STRESS_HVN_SLOW_MS 50 /* HVN complete slower than this counts as TX stress */
#define BLE_LINK_STRESS_HVN_VERY_SLOW_MS 100 /* HVN complete slower than this boosts immediately */
#define BLE_LINK_STRESS_HVN_STUCK_MS 200 /* No HVN complete after submit -> boost immediately */
#define BLE_LINK_STRESS_BOOST_COUNT 3 /* Consecutive slow HVN events before boost */
#define BLE_LINK_STRESS_RECOVERY_MS 10000 /* Clear stress counter after quiet recovery period */
/*==============================================================================
* System Constants
*============================================================================*/
#define DEAD_BEEF 0xDEADBEEF /* SDK error handler magic number */
#define UART_TX_BUF_SIZE 16384 /* UART TX FIFO buffer size (16KB) */
#define UART_RX_BUF_SIZE 512 /* UART RX FIFO buffer size */
#define POWER_ON_DELAY 5 /* Power button polling interval (5ms) */
#define POWER_OFF_DELAY 3000 /* Power-off delay: 3s after LED indication */
#define POWER_RESET_DELAY 2000 /* Reset delay: 2s */
#define LED_NUM 24 /* LED pin number */
/*==============================================================================
* BLE Instances (statically allocated via SoftDevice macros)
*============================================================================*/
BLE_NUS_DEF(m_nus, NRF_SDH_BLE_TOTAL_LINK_COUNT); /* Nordic UART Service instance */
NRF_BLE_GATT_DEF(m_gatt); /* GATT module instance (MTU negotiation) */
NRF_BLE_QWR_DEF(m_qwr); /* Queued Write instance */
BLE_ADVERTISING_DEF(m_advertising); /* Advertising module instance */
/*==============================================================================
* Timer Instances
*============================================================================*/
APP_TIMER_DEF(m_power_on_delay_timer_id); /* Power button polling timer (5ms single-shot, main_s callback) */
APP_TIMER_DEF(m_power_off_delay_timer_id); /* Power-off delay timer (physical power cut after 3s) */
APP_TIMER_DEF(m_PM_timer_id); /* Peer Manager timer (forced disconnect) */
APP_TIMER_DEF(m_link_stress_timer_id); /* EMC: BLE link stress monitor (periodic RSSI/HVN watchdog) */
/*==============================================================================
* Static Variables
*============================================================================*/
#if FEATURE_SECURE_CONNECTION
static pm_peer_id_t m_peer_to_be_deleted = PM_PEER_ID_INVALID;
#endif
static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID; /* Current BLE connection handle */
static uint16_t m_ble_nus_max_data_len = BLE_GATT_ATT_MTU_DEFAULT - 3; /* NUS max data length (MTU - overhead) */
static ble_uuid_t m_adv_uuids[] = {{BLE_UUID_NUS_SERVICE, NUS_SERVICE_UUID_TYPE}}; /* UUIDs included in advertising */
static int8_t m_ble_tx_power_dbm = BLE_TX_POWER_UNKNOWN_DBM; /* Currently applied connected TX power */
static uint8_t m_rssi_bad_count = 0; /* Consecutive poor RSSI event count */
static uint8_t m_rssi_good_count = 0; /* Consecutive good RSSI event count */
static uint8_t m_link_stress_count = 0; /* Consecutive slow-HVN stress count */
static uint32_t m_last_rssi_tick = 0; /* Last RSSI event tick for silence detection */
static uint32_t m_last_stress_tick = 0; /* Last stress tick for recovery reset */
static uint32_t m_hvn_send_tick = 0; /* Last successful notification submit tick */
static bool m_hvn_send_pending = false; /* True until BLE_GATTS_EVT_HVN_TX_COMPLETE */
static bool m_rssi_silence_latched = false; /* Prevent repeated RSSI silence logs/boosts */
static bool m_rssi_bad_latched = false; /* Prevent repeated RSSI_BAD logs/boosts */
static bool m_hvn_stuck_latched = false; /* Prevent repeated HVN_STUCK logs/boosts */
static uint8_t m_tx_buffer[BLE_NUS_MAX_DATA_LEN] = {0}; /* ASCII text transmission buffer */
static uint16_t m_tx_len = 0; /* Data length to transmit */
static volatile bool m_tx_in_progress = false; /* TX in progress flag */
static volatile bool m_tx_complete_pending = false; /* TX completion pending flag */
static uint8_t c_addr[6] = {0}; /* Connected peer BLE address */
static char * roles_str[] = {"INVALID_ROLE", "CENTRAL", "PERIPHERAL"};
/*==============================================================================
* Global Variables
* IEC 62304 §5.5.3: all global buffers zero-initialized for deterministic startup
*============================================================================*/
volatile uint8_t Sj_type; /* Command type identifier */
bool power_off_duble_prohibit = false; /* Power-off double prevention flag */
volatile bool power_state = false; /* Power state tracking */
/* -- External module flags (defined in main_timer/power_control) -- */
extern bool go_device_power_off; /* Power-off request flag */
extern bool go_sleep_mode_enter; /* Sleep mode entry request flag */
extern bool go_NVIC_SystemReset; /* System reset request flag */
extern bool ble_got_new_data; /* BLE new data received flag */
extern bool motion_data_once; /* Motion data one-shot transmit flag */
extern bool con_single; /* Single connection mode flag */
extern bool info4; /* Device info transmission complete flag */
extern uint8_t add_cycle; /* Additional measurement cycle counter */
extern bool motion_raw_data_enabled; /* Motion raw data streaming enabled */
uint16_t cnt_s; /* Power button polling counter (5ms units, 150=0.75s) */
char ble_tx_buffer[BLE_NUS_MAX_DATA_LEN] = {0}; /* BLE text transmission buffer */
uint8_t ble_bin_buffer[BLE_NUS_MAX_DATA_LEN] = {0}; /* BLE binary response buffer */
uint16_t ble_bin_buff[BLE_NUS_MAX_DATA_LEN/2] = {0}; /* BLE binary transmission buffer (word units) */
which_cmd_t cmd_type_t; /* Current command source (CMD_BLE or CMD_UART) */
bool device_status = false; /* Device active state (true=running) */
bool device_reset = true; /* Booting flag (true=not yet booted) */
#if FEATURE_SECURE_CONNECTION
bool erase_bonds; /* Bond erase flag (set by button state at boot) */
#endif
volatile bool ble_connection_st; /* BLE connection state (1=connected, 0=disconnected) */
volatile bool data_tx_in_progress = false; /* Binary TX in progress flag */
/* -- BLE TX async retry state -- */
#define BLE_TX_PENDING_QUEUE_SIZE 8U
#define BLE_TX_PENDING_BOOST_THRESHOLD 4U /* EMC: boost TX power if queued packets accumulate */
static uint8_t s_tx_pending_buf[BLE_TX_PENDING_QUEUE_SIZE][BLE_NUS_MAX_DATA_LEN] = {{0}}; /* Pending packets (with CRC) */
static uint16_t s_tx_pending_len[BLE_TX_PENDING_QUEUE_SIZE] = {0}; /* Pending packet lengths */
static volatile uint8_t s_tx_pending_head = 0; /* Next packet to retry */
static volatile uint8_t s_tx_pending_tail = 0; /* Next enqueue slot */
static volatile uint8_t s_tx_pending_count = 0; /* Queued packet count */
char m_static_passkey[PASSKEY_LENGTH] = DEFAULT_PASSKEY; /* Static passkey (6 digits, loaded from FDS) */
char SERIAL_NO[SERIAL_NO_LENGTH] = {0}; /* Serial number (used as BLE device name) */
char HW_NO[HW_NO_LENGTH] = {0}; /* Hardware number (FDS stored/read) */
bool bond_data_delete; /* Bond data delete request flag */
uint32_t m_life_cycle; /* Device life cycle counter */
uint8_t resetCount = 0; /* Communication timeout counter (reset detection) */
bool info4; /* Measurement with sensor info (battery/IMU/temp) flag */
uint8_t m_reset_status; /* Reset status code (1=normal, 2=SW reset, 5=security reset, 10=bond complete) */
/*==============================================================================
* Suppress Unused Variable Warnings
*============================================================================*/
#if FEATURE_SECURE_CONNECTION
static void __attribute__((unused)) suppress_unused_warnings(void)
{
(void)m_peer_to_be_deleted;
(void)roles_str;
(void)c_addr;
}
#endif
/*==============================================================================
* Power Management
*============================================================================*/
/**
* @brief Initialize power self-latch pin
*
* Configure P0.8 (POWER_HOLD) as output and drive HIGH to maintain the external power latch.
* When P0.8 goes LOW, physical power is cut off.
* Must be called first upon main() entry (Phase 0).
*/
static void power_hold_init(void)
{
NRF_P0->DIRSET = (1 << 8); /* Set P0.8 as output */
NRF_P0->OUTSET = (1 << 8); /* Set P0.8 HIGH -> maintain power */
}
/**
* @brief Physical power ON/OFF control
*
* Directly controls the POWER_HOLD pin for physical device power ON/OFF.
* When OFF, the power latch releases and the entire system shuts down (non-recoverable).
*/
static void power_control_handler(on_off_cont_t device_power_st)
{
if (device_power_st == OFF)
{
nrf_gpio_pin_clear(POWER_HOLD); /* P0.8 LOW -> release power latch -> power cut */
DBG_PRINTF("[PWR] OFF\r\n");
}
else if (device_power_st == ON)
{
nrf_gpio_pin_set(POWER_HOLD); /* P0.8 HIGH -> maintain power */
DBG_PRINTF("[PWR] ON\r\n");
}
}
/*==============================================================================
* GPIO Initialization
*============================================================================*/
/**
* @brief GPIO initialization
*
* Configure power button input and power hold pin only.
*/
static void gpio_init(void)
{
nrf_gpio_cfg_input(POWER_BUTTON, NRF_GPIO_PIN_NOPULL);
DBG_PRINTF("[GPIO] OK (BTN=%d)\r\n", nrf_gpio_pin_read(POWER_BUTTON));
}
/*==============================================================================
* Configuration Loading
* Load defaults before reading from FDS (Flash Data Storage).
* After FDS init, load_flash_config() overwrites with flash-stored values.
*============================================================================*/
/**
* @brief Load default configuration values
*
* Set safe defaults in global variables before reading from FDS.
* Initializes serial number, passkey, reset status, etc. to defaults.
*/
static void load_default_config(void)
{
memset(SERIAL_NO, 0, SERIAL_NO_LENGTH);
memcpy(SERIAL_NO, SERIAL_NUMBER, strlen(SERIAL_NUMBER));
memset(m_static_passkey, 0, PASSKEY_LENGTH);
memcpy(m_static_passkey, DEFAULT_PASSKEY, PASSKEY_LENGTH);
m_reset_status = 1;
bond_data_delete = 0;
DBG_PRINTF("[CFG] Default (S/N=%s)\r\n", SERIAL_NO);
}
/**
* @brief Load configuration from internal flash (FDS) into global variables
*
* Must be called after ble_stack_init() -> fs_storage_init() -> config_load().
* Copies the m_config struct read from FDS into runtime global variables.
* Empty fields (0x00 or 0xFF) are filled with defaults and m_need_save_defaults is set.
*/
static bool m_need_save_defaults = false;
static void load_flash_config(void)
{
m_need_save_defaults = false;
/* Hardware number -- fill with default if empty */
if (m_config.hw_no[0] == 0 || m_config.hw_no[0] == (char)0xFF)
{
memset(m_config.hw_no, 0, HW_NO_LENGTH);
memcpy(m_config.hw_no, HARDWARE_VERSION, strlen(HARDWARE_VERSION));
DBG_PRINTF("[CFG] HW empty, set default: %s\r\n", HARDWARE_VERSION);
m_need_save_defaults = true;
}
/* Serial number -- fill with default if empty */
if (m_config.serial_no[0] == 0 || m_config.serial_no[0] == (char)0xFF)
{
memset(m_config.serial_no, 0, SERIAL_NO_LENGTH);
memcpy(m_config.serial_no, SERIAL_NUMBER, strlen(SERIAL_NUMBER));
DBG_PRINTF("[CFG] S/N empty, set default: %s\r\n", SERIAL_NUMBER);
m_need_save_defaults = true;
}
/* Copy serial number -> BLE device name */
memset(SERIAL_NO, 0, SERIAL_NO_LENGTH);
memcpy(SERIAL_NO, m_config.serial_no, SERIAL_NO_LENGTH);
/* Copy passkey */
memset(m_static_passkey, 0, PASSKEY_LENGTH);
memcpy(m_static_passkey, m_config.static_passkey, PASSKEY_LENGTH);
/* Bond data delete flag */
bond_data_delete = m_config.bond_data_delete;
/* Reset status */
m_reset_status = m_config.reset_status;
DBG_PRINTF("[CFG] HW=%.12s S/N=%s passkey=%.6s bond=%d rst=%d\r\n",
m_config.hw_no, SERIAL_NO, m_static_passkey, bond_data_delete, m_reset_status);
DBG_PRINTF("[CFG] piezo: freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n",
m_config.piezo_freq_option, m_config.piezo_cycles,
m_config.piezo_averaging, m_config.piezo_delay_us,
m_config.piezo_num_samples);
}
/*==============================================================================
* Timer Callback Functions
*============================================================================*/
static void main_s(void * p_context); /* Power button state machine (forward declaration) */
static void t_power_off_timeout_handler(void * p_context); /* Power-off timeout */
static void PM_s(void * p_context); /* Peer Manager timer */
static void ble_link_stress_tick_handler(void * p_context); /* EMC: BLE RSSI/HVN link stress timer */
/**
* @brief Power-off timeout callback
*
* Called after POWER_OFF_DELAY (3s) elapses.
* Turns off LED and sets POWER_HOLD pin LOW for physical power cut.
* After this function, the device is completely off (non-recoverable).
*/
static void t_power_off_timeout_handler(void * p_context)
{
UNUSED_PARAMETER(p_context);
APP_ERROR_CHECK(app_timer_stop(m_power_off_delay_timer_id));
DBG_PRINTF("[PWR] Off timeout\r\n");
led_set_state(LED_STATE_OFF); /* LED OFF */
power_control_handler(OFF); /* P0.8 LOW -> power cut */
}
/**
* @brief Peer Manager timer callback
*
* Force BLE disconnect when in security-related state (m_reset_status==5).
* Used to drop the existing connection for reconnection after bond deletion.
*/
static void PM_s(void * p_context)
{
UNUSED_PARAMETER(p_context);
APP_ERROR_CHECK(app_timer_stop(m_PM_timer_id));
if (m_reset_status == 5)
{
DBG_PRINTF("[PM] Kill\r\n");
sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
}
}
/*==============================================================================
* Timer Initialization / Start Functions
*============================================================================*/
/**
* @brief Initialize all app timers (boot Phase 3)
*
* Initialize app_timer module then create each timer:
* - m_power_on_delay_timer: power button polling (5ms single-shot, main_s callback)
* - m_power_off_delay_timer: power-off delay (3s single-shot)
* - m_PM_timer: Peer Manager disconnect (single-shot)
* - m_link_stress_timer: EMC BLE link monitor (RSSI silence + HVN latency)
* - main_timer: main event loop (10ms single-shot, main_loop callback)
* - battery_timer: battery monitoring (5s repeating)
* - power_timer: power sequence (20ms single-shot, power_loop callback)
*/
static void timers_init(void)
{
ret_code_t err_code = app_timer_init();
APP_ERROR_CHECK(err_code);
APP_ERROR_CHECK(app_timer_create(&m_power_on_delay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, main_s));
APP_ERROR_CHECK(app_timer_create(&m_power_off_delay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, t_power_off_timeout_handler));
APP_ERROR_CHECK(app_timer_create(&m_PM_timer_id, APP_TIMER_MODE_SINGLE_SHOT, PM_s));
APP_ERROR_CHECK(app_timer_create(&m_link_stress_timer_id, APP_TIMER_MODE_REPEATED, ble_link_stress_tick_handler));
main_timer_init();
battery_timer_init();
power_timer_init();
#if FEATURE_PRINTF
full_timer_init();
#endif
}
/**
* @brief Start the power button polling timer
*
* Fires main_s() callback after 5ms (POWER_ON_DELAY).
* main_s() checks button state; if still pressed, re-arms the timer to drive the state machine.
*/
static void timers_start(void)
{
ret_code_t err_code;
err_code = app_timer_start(m_power_on_delay_timer_id, APP_TIMER_TICKS(POWER_ON_DELAY), NULL);
APP_ERROR_CHECK(err_code);
}
/*==============================================================================
* BLE DFU Handler
* Handles DFU (Device Firmware Update) events for firmware update over BLE.
* Logs bootloader entry preparation, success/failure events.
*============================================================================*/
#if BLE_DFU_ENABLED
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event)
{
switch (event)
{
case BLE_DFU_EVT_BOOTLOADER_ENTER_PREPARE:
DBG_PRINTF("[DFU] Prepare\r\n");
break;
case BLE_DFU_EVT_BOOTLOADER_ENTER:
DBG_PRINTF("[DFU] Enter\r\n");
break;
case BLE_DFU_EVT_BOOTLOADER_ENTER_FAILED:
DBG_PRINTF("[DFU] Failed\r\n");
break;
case BLE_DFU_EVT_RESPONSE_SEND_ERROR:
DBG_PRINTF("[DFU] Error\r\n");
APP_ERROR_CHECK(false);
break;
default:
break;
}
}
#endif
/*==============================================================================
* BLE GAP Initialization
*============================================================================*/
/**
* @brief Initialize GAP (Generic Access Profile) parameters
*
* 1) Set device name to SERIAL_NO -> shown during BLE scan
* 2) Configure connection parameters (15ms interval, slave latency 0, EMC supervision timeout 10s)
* 3) Set static passkey (when FEATURE_STATIC_PASSKEY enabled)
*/
static void gap_params_init(void)
{
uint32_t err_code;
ble_gap_conn_params_t gap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
#if FEATURE_STATIC_PASSKEY
ble_opt_t ble_opt;
#endif
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *) SERIAL_NO,
strlen(SERIAL_NO));
APP_ERROR_CHECK(err_code);
memset(&gap_conn_params, 0, sizeof(gap_conn_params));
gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
gap_conn_params.slave_latency = SLAVE_LATENCY;
gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;
err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
APP_ERROR_CHECK(err_code);
#if FEATURE_STATIC_PASSKEY
ble_opt.gap_opt.passkey.p_passkey = (const uint8_t *)m_static_passkey;
err_code = sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &ble_opt);
APP_ERROR_CHECK(err_code);
#endif
}
/*==============================================================================
* BLE Service Initialization
*============================================================================*/
static void nrf_qwr_error_handler(uint32_t nrf_error)
{
APP_ERROR_HANDLER(nrf_error);
}
/* Async MAA TX complete handler (forward declarations) */
extern bool maa_async_on_tx_ready(void);
extern bool maa_async_is_busy(void);
extern void maa_async_abort(void);
/* 2026-03-17: Prevent blocking in BLE callback -- buffer command for main loop processing */
static volatile uint8_t pending_cmd_buf[BLE_NUS_MAX_DATA_LEN] = {0};
static volatile uint8_t pending_cmd_len = 0;
static void ble_link_stress_on_hvn_send(void);
static void ble_link_stress_on_hvn_complete(void);
static void ble_link_stress_boost_now(const char * p_reason, uint32_t elapsed_ms);
static void ble_link_stress_check_pending_depth(void);
static bool ble_tx_pending_has_data(void)
{
return (s_tx_pending_count > 0);
}
static void ble_tx_pending_clear(void)
{
s_tx_pending_head = 0;
s_tx_pending_tail = 0;
s_tx_pending_count = 0;
}
static bool ble_tx_pending_enqueue(uint8_t const *p_data, uint16_t len)
{
if (s_tx_pending_count >= BLE_TX_PENDING_QUEUE_SIZE)
{
return false;
}
memcpy(s_tx_pending_buf[s_tx_pending_tail], p_data, len);
s_tx_pending_len[s_tx_pending_tail] = len;
s_tx_pending_tail = (uint8_t)((s_tx_pending_tail + 1U) % BLE_TX_PENDING_QUEUE_SIZE);
s_tx_pending_count++;
return true;
}
static void ble_tx_pending_pop(void)
{
if (s_tx_pending_count == 0)
{
return;
}
s_tx_pending_len[s_tx_pending_head] = 0;
s_tx_pending_head = (uint8_t)((s_tx_pending_head + 1U) % BLE_TX_PENDING_QUEUE_SIZE);
s_tx_pending_count--;
}
static bool ble_retry_pending_tx(void)
{
uint16_t send_len;
uint32_t err;
bool freed_slot = false;
if (!ble_tx_pending_has_data())
{
return true;
}
if (ble_connection_st != BLE_CONNECTED_ST)
{
ble_tx_pending_clear();
return false;
}
while (ble_tx_pending_has_data())
{
send_len = s_tx_pending_len[s_tx_pending_head];
err = ble_nus_data_send(&m_nus, s_tx_pending_buf[s_tx_pending_head], &send_len, m_conn_handle);
if (err == NRF_SUCCESS)
{
ble_link_stress_on_hvn_send();
ble_tx_pending_pop();
freed_slot = true;
continue;
}
if (err == NRF_ERROR_RESOURCES)
{
return freed_slot;
}
if (err == NRF_ERROR_INVALID_STATE || err == NRF_ERROR_NOT_FOUND)
{
DBG_PRINTF("[BLE TX] Pending send aborted\r\n");
ble_tx_pending_clear();
return false;
}
DBG_PRINTF("[BLE TX] Pending err:0x%X\r\n", err);
ble_tx_pending_pop();
freed_slot = true;
}
return true;
}
/**
* @brief NUS (Nordic UART Service) data receive handler
*
* Called when data is received from the smartphone app over BLE.
* - RX_DATA event: copy received data to buffer (dr_cmd_parser called in main loop)
* - TX_RDY event: BLE TX buffer has space, continue async MAA transmission
*/
static void nus_data_handler(ble_nus_evt_t * p_evt)
{
if (p_evt->type == BLE_NUS_EVT_RX_DATA)
{
/* Central may have increased connection interval; re-request fast interval (once per 30s) */
{
static uint32_t last_update_tick = 0;
uint32_t now_tick = app_timer_cnt_get();
if (last_update_tick == 0 || app_timer_cnt_diff_compute(now_tick, last_update_tick) >= APP_TIMER_TICKS(30000))
{
ble_gap_conn_params_t conn_params;
conn_params.min_conn_interval = MIN_CONN_INTERVAL;
conn_params.max_conn_interval = MAX_CONN_INTERVAL;
conn_params.slave_latency = SLAVE_LATENCY;
conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;
sd_ble_gap_conn_param_update(m_conn_handle, &conn_params);
last_update_tick = now_tick;
}
}
#if FEATURE_SECURE_CONNECTION && !BLE_DEV_MODE
if (ble_connection_st != BLE_CONNECTED_ST)
{
return; /* Not authenticated: do not buffer command */
}
#endif
cmd_type_t = CMD_BLE;
ble_got_new_data = true;
/* Only copy in callback; process in main loop */
if (p_evt->params.rx_data.length <= BLE_NUS_MAX_DATA_LEN)
{
memcpy((void *)pending_cmd_buf, p_evt->params.rx_data.p_data, p_evt->params.rx_data.length);
pending_cmd_len = p_evt->params.rx_data.length;
}
}
else if (p_evt->type == BLE_NUS_EVT_TX_RDY)
{
/* First drain any queued packet that previously hit NRF_ERROR_RESOURCES. */
if (ble_tx_pending_has_data())
{
if (!ble_retry_pending_tx())
{
return;
}
}
/* BLE TX buffer has space - continue async MAA transmission */
if (maa_async_is_busy())
{
maa_async_on_tx_ready();
}
}
}
/**
* @brief Initialize BLE services
*
* Initialize QWR (Queued Write) + NUS (Nordic UART Service) + DFU (optional).
* Register nus_data_handler as the NUS data_handler for BLE receive processing.
*/
static void services_init(void)
{
uint32_t err_code;
ble_nus_init_t nus_init;
nrf_ble_qwr_init_t qwr_init = {0};
qwr_init.error_handler = nrf_qwr_error_handler;
err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
APP_ERROR_CHECK(err_code);
memset(&nus_init, 0, sizeof(nus_init));
nus_init.data_handler = nus_data_handler;
err_code = ble_nus_init(&m_nus, &nus_init);
APP_ERROR_CHECK(err_code);
#if BLE_DFU_ENABLED
ble_dfu_buttonless_init_t dfus_init = {0};
dfus_init.evt_handler = ble_dfu_evt_handler;
err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);
#endif
}
/*==============================================================================
* BLE Connection Parameter Negotiation
*============================================================================*/
/** @brief Disconnect on connection parameter negotiation failure */
static void on_conn_params_evt(ble_conn_params_evt_t * p_evt)
{
if (p_evt->evt_type == BLE_CONN_PARAMS_EVT_FAILED)
{
DBG_PRINTF("[CONN] Param negotiation FAILED -> disconnect\r\n");
uint32_t err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_CONN_INTERVAL_UNACCEPTABLE);
APP_ERROR_CHECK(err_code);
}
}
static void conn_params_error_handler(uint32_t nrf_error)
{
APP_ERROR_HANDLER(nrf_error);
}
/**
* @brief Initialize BLE connection parameter module
*
* Wait 5s after connection -> request param update -> retry every 30s up to 3 times.
* On failure, on_conn_params_evt disconnects.
*/
static void conn_params_init(void)
{
uint32_t err_code;
ble_conn_params_init_t cp_init;
memset(&cp_init, 0, sizeof(cp_init));
cp_init.p_conn_params = NULL;
cp_init.first_conn_params_update_delay = FIRST_CONN_PARAMS_UPDATE_DELAY;
cp_init.next_conn_params_update_delay = NEXT_CONN_PARAMS_UPDATE_DELAY;
cp_init.max_conn_params_update_count = MAX_CONN_PARAMS_UPDATE_COUNT;
cp_init.start_on_notify_cccd_handle = BLE_GATT_HANDLE_INVALID;
cp_init.disconnect_on_fail = false;
cp_init.evt_handler = on_conn_params_evt;
cp_init.error_handler = conn_params_error_handler;
err_code = ble_conn_params_init(&cp_init);
APP_ERROR_CHECK(err_code);
}
/*==============================================================================
* BLE Stack Initialization
*============================================================================*/
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context);
/**
* @brief Initialize SoftDevice BLE stack
*
* 1) Request SoftDevice enable
* 2) Set BLE default config (auto-compute RAM start address)
* 3) Enable BLE
* 4) Register BLE event observer (ble_evt_handler)
*/
static void ble_stack_init(void)
{
ret_code_t err_code;
err_code = nrf_sdh_enable_request();
APP_ERROR_CHECK(err_code);
uint32_t ram_start = 0;
err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
APP_ERROR_CHECK(err_code);
err_code = nrf_sdh_ble_enable(&ram_start);
APP_ERROR_CHECK(err_code);
NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
}
/*==============================================================================
* BLE GATT (Generic Attribute Profile)
* Determines max data size per transfer via MTU negotiation.
*============================================================================*/
/** @brief GATT MTU update event handler -- updates NUS max data length */
void gatt_evt_handler(nrf_ble_gatt_t * p_gatt, nrf_ble_gatt_evt_t const * p_evt)
{
if ((m_conn_handle == p_evt->conn_handle) && (p_evt->evt_id == NRF_BLE_GATT_EVT_ATT_MTU_UPDATED))
{
m_ble_nus_max_data_len = p_evt->params.att_mtu_effective - OPCODE_LENGTH - HANDLE_LENGTH;
}
}
/** @brief Initialize GATT module -- set MTU to max size */
static void gatt_init(void)
{
ret_code_t err_code;
err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);
APP_ERROR_CHECK(err_code);
err_code = nrf_ble_gatt_att_mtu_periph_set(&m_gatt, NRF_SDH_BLE_GATT_MAX_MTU_SIZE);
APP_ERROR_CHECK(err_code);
}
/*==============================================================================
* BLE Advertising
*============================================================================*/
/**
* @brief BLE advertising event handler
*
* - FAST: advertising started (LED indication)
* - IDLE: advertising timeout -> set sleep mode entry flag
*/
static void on_adv_evt(ble_adv_evt_t ble_adv_evt)
{
switch (ble_adv_evt)
{
case BLE_ADV_EVT_FAST:
led_set_state(LED_STATE_ADVERTISING);
DBG_PRINTF("[ADV] Fast\r\n");
break;
case BLE_ADV_EVT_IDLE:
DBG_PRINTF("[ADV] Idle\r\n");
go_sleep_mode_enter = true;
main_timer_start();
break;
default:
break;
}
}
/**
* @brief Initialize BLE advertising
*
* Advertising data: full device name, NUS UUID.
* Advertising mode: Fast (40ms interval), default duration 10 min.
* May switch to unlimited advertising at runtime based on disconnect reason.
*/
static void advertising_init(void)
{
uint32_t err_code;
ble_advertising_init_t init;
memset(&init, 0, sizeof(init));
init.advdata.name_type = BLE_ADVDATA_FULL_NAME;
init.advdata.include_appearance = true;
init.advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
init.srdata.uuids_complete.uuid_cnt = sizeof(m_adv_uuids) / sizeof(m_adv_uuids[0]);
init.srdata.uuids_complete.p_uuids = m_adv_uuids;
init.config.ble_adv_fast_enabled = true;
init.config.ble_adv_fast_interval = APP_ADV_INTERVAL;
init.config.ble_adv_fast_timeout = APP_ADV_DURATION;
init.evt_handler = on_adv_evt;
err_code = ble_advertising_init(&m_advertising, &init);
APP_ERROR_CHECK(err_code);
ble_advertising_conn_cfg_tag_set(&m_advertising, APP_BLE_CONN_CFG_TAG);
}
#if FEATURE_SECURE_CONNECTION
/** @brief Delete all bonding (pairing) info from flash */
static void delete_bonds(void)
{
ret_code_t err_code;
DBG_PRINTF("[PM] Erase bonds\r\n");
err_code = pm_peers_delete();
APP_ERROR_CHECK(err_code);
}
/**
* @brief Start BLE advertising (secure mode)
*
* If erase_bonds_flag is true, delete all bond info first, then
* start advertising from PM_EVT_PEERS_DELETE_SUCCEEDED event.
* If false, start Fast mode advertising immediately.
*/
static void advertising_start(bool erase_bonds_flag)
{
if (erase_bonds_flag == true)
{
DBG_PRINTF("[BOND] Delete requested (count_before=%lu)\r\n", (unsigned long)pm_peer_count());
/* Clear bond delete request flag in flash -> prevent re-deletion on next boot */
bond_data_delete = false;
m_config.bond_data_delete = 0;
config_save();
delete_bonds(); /* After deletion, advertising starts from pm_evt_handler */
}
else
{
ret_code_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
}
}
#else
/** @brief Start BLE advertising (non-secure mode, immediate Fast advertising) */
static void advertising_start(void)
{
uint32_t err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
APP_ERROR_CHECK(err_code);
}
#endif
/*==============================================================================
* BLE Security (Peer Manager)
* LESC/static passkey MITM protection, bonding management
*============================================================================*/
#if FEATURE_SECURE_CONNECTION
/**
* @brief Peer Manager event handler
*
* Handles BLE security events:
* - CONN_SEC_SUCCEEDED: security established -> verify MITM then activate connection
* - CONN_SEC_FAILED: security failed -> retry on PIN/key missing
* - PEERS_DELETE_SUCCEEDED: bond deletion complete -> restart advertising
* - CONN_SEC_CONFIG_REQ: allow re-pairing config
* - PEER_DATA_UPDATE_SUCCEEDED: peer data updated -> save address, update reset status
*/
static void pm_evt_handler(pm_evt_t const * p_evt)
{
pm_peer_data_bonding_t peer_bonding_data;
uint32_t return_code;
ret_code_t err_code;
/* SDK default handler + security processing (SDK handler called inside ble_quick_security) */
ble_security_quick_pm_handler(p_evt);
switch (p_evt->evt_id)
{
/* Security connection succeeded */
case PM_EVT_CONN_SEC_SUCCEEDED:
{
pm_conn_sec_status_t conn_sec_status;
err_code = pm_conn_sec_status_get(p_evt->conn_handle, &conn_sec_status);
APP_ERROR_CHECK(err_code);
/* Verify MITM protection (always pass in dev mode) */
if (conn_sec_status.mitm_protected || BLE_DEV_MODE)
{
DBG_PRINTF("[PM] Secured\r\n");
ble_connection_st = 1; /* Activate connection state */
battery_timer_start(); /* Start battery monitoring */
}
else
{
/* MITM not protected -> delete peer and disconnect */
DBG_PRINTF("[PM] Sec FAIL\r\n");
err_code = pm_peer_id_get(m_conn_handle, &m_peer_to_be_deleted);
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gap_disconnect(m_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
}
}
break;
/* Security connection failed (retry/disconnect handled by ble_quick_security) */
case PM_EVT_CONN_SEC_FAILED:
DBG_PRINTF("[PM] Sec failed\r\n");
break;
/* All bonds deleted -> restart advertising only if no active connection */
case PM_EVT_PEERS_DELETE_SUCCEEDED:
DBG_PRINTF("[BOND] Deleted all (count_after=%lu)\r\n", (unsigned long)pm_peer_count());
if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
{
advertising_start(false);
}
break;
/* PM_EVT_CONN_SEC_CONFIG_REQ -> handled by ble_quick_security */
/* Peer data (bonding info) update succeeded -> save peer BLE address */
case PM_EVT_PEER_DATA_UPDATE_SUCCEEDED:
{
return_code = pm_peer_data_bonding_load(p_evt->peer_id, &peer_bonding_data);
if (return_code == NRF_SUCCESS)
{
memcpy(c_addr, peer_bonding_data.peer_ble_id.id_addr_info.addr, sizeof(c_addr));
/* Log peer ID and address when new bond is created */
if (p_evt->params.peer_data_update_succeeded.data_id == PM_PEER_DATA_ID_BONDING)
{
DBG_PRINTF("[BOND] Created peer_id=%u addr=%02X:%02X:%02X:%02X:%02X:%02X (count=%lu)\r\n",
p_evt->peer_id, c_addr[5], c_addr[4], c_addr[3], c_addr[2], c_addr[1], c_addr[0],
(unsigned long)pm_peer_count());
}
}
m_reset_status = 10; /* Bond complete status */
}
break;
default:
break;
}
}
/**
* @brief Initialize Peer Manager
*
* Initialize BLE security module and register event handler.
* Security level determined by BLE_DEV_MODE (0=passkey required, 1=no security).
*/
static void peer_manager_init(void)
{
ret_code_t err_code;
ble_security_quick_init(BLE_DEV_MODE);
err_code = pm_register(pm_evt_handler);
APP_ERROR_CHECK(err_code);
DBG_PRINTF("[PM] Init (mode=%d)\r\n", BLE_DEV_MODE);
}
#endif
/*==============================================================================
* BLE Link Stress Monitor + Dynamic TX Power Control (EMC)
*
* EMC noise can make the BLE link degrade before the connection actually drops.
* This monitor watches both directions:
* - RSSI events from the central side indicate downlink quality.
* - HVN TX complete latency and NUS pending depth indicate uplink congestion.
*
* Policy:
* 1. Use +4 dBm normally to keep RF output modest.
* 2. Boost to +8 dBm when the link shows repeated or immediate stress.
* 3. Restore to +4 dBm only after RSSI is stably good and TX is fully idle.
*============================================================================*/
static void ble_tx_power_set_dynamic(int8_t tx_power_dbm);
static uint32_t ble_link_stress_elapsed_ms(uint32_t start_tick, uint32_t end_tick)
{
return (uint32_t)((app_timer_cnt_diff_compute(end_tick, start_tick) * 1000ULL) / APP_TIMER_CLOCK_FREQ);
}
static bool ble_link_stress_rssi_recent(void)
{
if (m_last_rssi_tick == 0)
{
return false;
}
return (app_timer_cnt_diff_compute(app_timer_cnt_get(), m_last_rssi_tick) <
APP_TIMER_TICKS(BLE_LINK_STRESS_RSSI_SILENCE_MS));
}
static void ble_link_stress_try_restore(void)
{
/* Restore only when every stress source is quiet; this prevents power flapping during EMC bursts. */
if (m_ble_tx_power_dbm == BLE_TX_POWER_BOOST_DBM &&
m_link_stress_count == 0 &&
m_rssi_good_count >= BLE_RSSI_GOOD_COUNT_LIMIT &&
!m_rssi_bad_latched &&
!m_rssi_silence_latched &&
ble_link_stress_rssi_recent() &&
!ble_tx_pending_has_data() &&
!m_tx_in_progress &&
!m_hvn_send_pending)
{
ble_tx_power_set_dynamic(BLE_TX_POWER_NORMAL_DBM);
}
}
static void ble_link_stress_boost_now(const char * p_reason, uint32_t elapsed_ms)
{
m_last_stress_tick = app_timer_cnt_get();
UNUSED_PARAMETER(p_reason);
if (elapsed_ms > 0)
{
/* EMC RTT log disabled: DBG_PRINTF("[BLE] stress: %s %lums (boost now)\r\n", p_reason, (unsigned long)elapsed_ms); */
}
else
{
/* EMC RTT log disabled: DBG_PRINTF("[BLE] stress: %s (boost now)\r\n", p_reason); */
}
ble_tx_power_set_dynamic(BLE_TX_POWER_BOOST_DBM);
}
static void ble_link_stress_check_pending_depth(void)
{
/* Queue growth means SoftDevice/NUS cannot accept packets fast enough; treat it as uplink stress. */
if (s_tx_pending_count >= BLE_TX_PENDING_BOOST_THRESHOLD)
{
ble_link_stress_boost_now("TX_QUEUE_DEPTH", 0);
}
}
static void ble_link_stress_bump(const char * p_reason, uint32_t elapsed_ms)
{
m_last_stress_tick = app_timer_cnt_get();
UNUSED_PARAMETER(p_reason);
UNUSED_PARAMETER(elapsed_ms);
if (m_link_stress_count < BLE_LINK_STRESS_BOOST_COUNT)
{
m_link_stress_count++;
}
/* EMC RTT log disabled: DBG_PRINTF("[BLE] stress: %s %lums (count=%u/%u)\r\n",
p_reason, (unsigned long)elapsed_ms, m_link_stress_count, BLE_LINK_STRESS_BOOST_COUNT); */
if (m_link_stress_count >= BLE_LINK_STRESS_BOOST_COUNT)
{
ble_tx_power_set_dynamic(BLE_TX_POWER_BOOST_DBM);
}
}
static void ble_link_stress_reset(void)
{
m_link_stress_count = 0;
m_last_rssi_tick = 0;
m_last_stress_tick = 0;
m_hvn_send_tick = 0;
m_hvn_send_pending = false;
m_rssi_silence_latched = false;
m_hvn_stuck_latched = false;
}
static void ble_link_stress_on_hvn_send(void)
{
m_hvn_send_tick = app_timer_cnt_get();
m_hvn_send_pending = true;
m_hvn_stuck_latched = false;
}
static void ble_link_stress_on_hvn_complete(void)
{
uint32_t now_tick;
uint32_t elapsed_ms;
if (!m_hvn_send_pending)
{
return;
}
now_tick = app_timer_cnt_get();
elapsed_ms = ble_link_stress_elapsed_ms(m_hvn_send_tick, now_tick);
m_hvn_send_pending = false;
m_hvn_stuck_latched = false;
if (elapsed_ms >= BLE_LINK_STRESS_HVN_VERY_SLOW_MS)
{
ble_link_stress_boost_now("HVN_VERY_SLOW", elapsed_ms);
}
else if (elapsed_ms >= BLE_LINK_STRESS_HVN_SLOW_MS)
{
ble_link_stress_bump("HVN_SLOW", elapsed_ms);
}
else
{
m_link_stress_count = 0;
}
}
static void ble_link_stress_tick_handler(void * p_context)
{
uint32_t now_tick;
UNUSED_PARAMETER(p_context);
if (m_conn_handle == BLE_CONN_HANDLE_INVALID || ble_connection_st != BLE_CONNECTED_ST)
{
return;
}
now_tick = app_timer_cnt_get();
/* If RSSI events disappear, assume the central/downlink side is being disturbed. */
if (m_last_rssi_tick != 0 &&
app_timer_cnt_diff_compute(now_tick, m_last_rssi_tick) >= APP_TIMER_TICKS(BLE_LINK_STRESS_RSSI_SILENCE_MS))
{
if (!m_rssi_silence_latched)
{
m_rssi_silence_latched = true;
ble_link_stress_boost_now("RSSI_SILENCE", 0);
}
}
/* If a notification was submitted but TX complete does not arrive, boost immediately. */
if (m_hvn_send_pending &&
app_timer_cnt_diff_compute(now_tick, m_hvn_send_tick) >= APP_TIMER_TICKS(BLE_LINK_STRESS_HVN_STUCK_MS))
{
if (!m_hvn_stuck_latched)
{
uint32_t elapsed_ms = ble_link_stress_elapsed_ms(m_hvn_send_tick, now_tick);
m_hvn_stuck_latched = true;
ble_link_stress_boost_now("HVN_STUCK", elapsed_ms);
}
}
else if (m_tx_in_progress &&
m_hvn_send_tick != 0 &&
app_timer_cnt_diff_compute(now_tick, m_hvn_send_tick) >= APP_TIMER_TICKS(BLE_LINK_STRESS_HVN_STUCK_MS))
{
if (!m_hvn_stuck_latched)
{
uint32_t elapsed_ms = ble_link_stress_elapsed_ms(m_hvn_send_tick, now_tick);
m_hvn_stuck_latched = true;
ble_link_stress_boost_now("HVN_STUCK", elapsed_ms);
}
}
if (m_link_stress_count > 0 &&
m_last_stress_tick != 0 &&
app_timer_cnt_diff_compute(now_tick, m_last_stress_tick) >= APP_TIMER_TICKS(BLE_LINK_STRESS_RECOVERY_MS))
{
m_link_stress_count = 0;
/* EMC RTT log disabled: DBG_PRINTF("[BLE] stress: recovered (count=0)\r\n"); */
ble_link_stress_try_restore();
}
}
static void ble_link_stress_start(void)
{
ret_code_t err_code;
ble_link_stress_reset();
m_last_rssi_tick = app_timer_cnt_get();
err_code = app_timer_start(m_link_stress_timer_id, APP_TIMER_TICKS(BLE_LINK_STRESS_TICK_MS), NULL);
if (err_code != NRF_SUCCESS && err_code != NRF_ERROR_INVALID_STATE)
{
APP_ERROR_CHECK(err_code);
}
}
static void ble_link_stress_stop(void)
{
(void)app_timer_stop(m_link_stress_timer_id);
ble_link_stress_reset();
}
static void ble_tx_power_set_dynamic(int8_t tx_power_dbm)
{
ret_code_t err_code;
int8_t prev_tx_power_dbm = m_ble_tx_power_dbm;
if (m_conn_handle == BLE_CONN_HANDLE_INVALID || m_ble_tx_power_dbm == tx_power_dbm)
{
return;
}
err_code = sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_CONN,
m_conn_handle,
tx_power_dbm);
if (err_code == NRF_SUCCESS)
{
m_ble_tx_power_dbm = tx_power_dbm;
if (tx_power_dbm == BLE_TX_POWER_BOOST_DBM)
{
m_rssi_good_count = 0;
}
if (prev_tx_power_dbm == BLE_TX_POWER_UNKNOWN_DBM)
{
/* EMC RTT log disabled: DBG_PRINTF("[BLE] TX power -> %d dBm\r\n", tx_power_dbm); */
}
else
{
/* EMC RTT log disabled: DBG_PRINTF("[BLE] TX power %d -> %d dBm\r\n", prev_tx_power_dbm, tx_power_dbm); */
}
}
else if (err_code != NRF_ERROR_INVALID_STATE && err_code != BLE_ERROR_INVALID_CONN_HANDLE)
{
APP_ERROR_CHECK(err_code);
}
}
static void ble_rssi_state_reset(void)
{
m_rssi_bad_count = 0;
m_rssi_good_count = 0;
m_rssi_bad_latched = false;
}
static void ble_rssi_monitor_start(void)
{
ret_code_t err_code;
if (m_conn_handle == BLE_CONN_HANDLE_INVALID)
{
return;
}
ble_rssi_state_reset();
m_ble_tx_power_dbm = BLE_TX_POWER_UNKNOWN_DBM;
/* Apply normal power first; stress monitor will boost only when needed. */
ble_tx_power_set_dynamic(BLE_TX_POWER_NORMAL_DBM);
ble_link_stress_start();
err_code = sd_ble_gap_rssi_start(m_conn_handle,
BLE_RSSI_CHANGE_THRESHOLD_DBM,
BLE_RSSI_SKIP_COUNT);
if (err_code == NRF_SUCCESS)
{
/* EMC RTT log disabled: DBG_PRINTF("[BLE] RSSI monitor start (threshold=%d, skip=%d)\r\n",
BLE_RSSI_CHANGE_THRESHOLD_DBM, BLE_RSSI_SKIP_COUNT); */
}
else if (err_code != NRF_ERROR_INVALID_STATE)
{
APP_ERROR_CHECK(err_code);
}
else
{
/* EMC RTT log disabled: DBG_PRINTF("[BLE] RSSI monitor already active\r\n"); */
}
}
static void ble_rssi_monitor_stop(void)
{
if (m_conn_handle != BLE_CONN_HANDLE_INVALID)
{
(void)sd_ble_gap_rssi_stop(m_conn_handle);
}
ble_link_stress_stop();
ble_rssi_state_reset();
m_ble_tx_power_dbm = BLE_TX_POWER_UNKNOWN_DBM;
}
static void ble_rssi_update(int8_t rssi_dbm)
{
m_last_rssi_tick = app_timer_cnt_get();
if (rssi_dbm <= BLE_RSSI_BAD_DBM)
{
if (m_rssi_bad_count < BLE_RSSI_BAD_COUNT_LIMIT)
{
m_rssi_bad_count++;
}
m_rssi_good_count = 0;
if (m_rssi_bad_count >= BLE_RSSI_BAD_COUNT_LIMIT && !m_rssi_bad_latched)
{
m_rssi_bad_latched = true;
/* EMC RTT log disabled: DBG_PRINTF("[BLE] stress: RSSI_BAD %d dBm (count=%u/%u, boost now)\r\n",
rssi_dbm, m_rssi_bad_count, BLE_RSSI_BAD_COUNT_LIMIT); */
ble_tx_power_set_dynamic(BLE_TX_POWER_BOOST_DBM);
}
}
else if (rssi_dbm >= BLE_RSSI_GOOD_DBM)
{
m_rssi_silence_latched = false;
if (m_rssi_good_count < BLE_RSSI_GOOD_COUNT_LIMIT)
{
m_rssi_good_count++;
}
m_rssi_bad_count = 0;
m_rssi_bad_latched = false;
if (m_rssi_good_count >= BLE_RSSI_GOOD_COUNT_LIMIT)
{
ble_link_stress_try_restore();
}
}
else
{
m_rssi_bad_count = 0;
m_rssi_good_count = 0;
}
}
/*==============================================================================
* BLE Event Handler
* Handles all BLE events from the SoftDevice.
*============================================================================*/
/**
* @brief BLE event handler (SoftDevice observer)
*
* Key events handled:
* - DISCONNECTED: connection lost -> device sleep, state reset
* - CONNECTED: connection established -> assign QWR handle, start EMC link monitor
* - PHY_UPDATE_REQUEST: keep link on 1M PHY
* - RSSI_CHANGED: RSSI input for dynamic TX power boost/restore
* - TIMEOUT: connection/GATT timeout -> force disconnect
* - SEC_PARAMS_REQUEST: security parameter request (reject if security unused)
* - PASSKEY_DISPLAY: display passkey (debug log)
* - AUTH_KEY_REQUEST: respond with static passkey
* - HVN_TX_COMPLETE: TX complete -> clear transmission flag and measure HVN latency
*/
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
uint32_t err_code;
#if FEATURE_STATIC_PASSKEY
ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
#endif
#if FEATURE_SECURE_CONNECTION
if (!ble_security_is_dev_mode())
{
pm_handler_secure_on_connection(p_ble_evt);
}
#endif
switch (p_ble_evt->header.evt_id)
{
/* BLE connection lost */
case BLE_GAP_EVT_DISCONNECTED:
{
uint8_t disc_reason = p_ble_evt->evt.gap_evt.params.disconnected.reason;
/* Determine unintended disconnect:
* 0x08 supervision timeout, 0x22 LMP response timeout,
* 0x3D MIC failure, 0x3E connection failed to be established
* -> maintain unlimited advertising + block sleep entry
*/
bool unintended_disc = (disc_reason == BLE_HCI_CONNECTION_TIMEOUT) ||
(disc_reason == BLE_HCI_STATUS_CODE_LMP_RESPONSE_TIMEOUT) ||
(disc_reason == BLE_HCI_CONN_TERMINATED_DUE_TO_MIC_FAILURE)||
(disc_reason == BLE_HCI_CONN_FAILED_TO_BE_ESTABLISHED);
DBG_PRINTF("[BLE] Disconnected (reason 0x%02X)%s\r\n",
disc_reason, unintended_disc ? " [UNINTENDED]" : "");
ble_rssi_monitor_stop(); /* EMC: stop RSSI/HVN stress timer before invalidating handle */
ble_connection_st = 0;
pending_cmd_len = 0; // Clear pending command buffer
m_conn_handle = BLE_CONN_HANDLE_INVALID;
m_tx_in_progress = false;
maa_async_abort(); // Prevent hang caused by async measurement state
ble_tx_pending_clear(); // Clear pending TX
/* Adjust advertising duration at runtime based on disconnect reason */
(void)sd_ble_gap_adv_stop(m_advertising.adv_handle);
m_advertising.adv_modes_config.ble_adv_fast_timeout =
unintended_disc ? APP_ADV_DURATION_UNLIMITED : APP_ADV_DURATION_NORMAL;
ret_code_t adv_err = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
if (adv_err != NRF_SUCCESS && adv_err != NRF_ERROR_INVALID_STATE)
{
APP_ERROR_CHECK(adv_err);
}
/* Unintended disconnect: block sleep entry to ensure continuous advertising */
if (!unintended_disc && device_status == true)
{
if (device_sleep_mode() == 0)
{
device_status = false;
}
}
}
break;
case BLE_GAP_EVT_CONNECTED:
DBG_PRINTF("[BLE] Connected\r\n");
#if BLE_DEV_MODE
/* Dev: no passkey/SEC — allow NUS TX/RX as soon as GAP is up (prod uses PM_EVT_CONN_SEC_SUCCEEDED). */
ble_connection_st = BLE_CONNECTED_ST;
battery_timer_start();
#endif
m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
APP_ERROR_CHECK(err_code);
ble_rssi_monitor_start(); /* EMC: apply +4 dBm normal power and start dynamic boost monitor */
led_set_state(LED_STATE_OFF); /* Connection complete -> LED OFF */
break;
case BLE_GAP_EVT_PHY_UPDATE_REQUEST:
{
/* Product is certified/tested as fixed 1M PHY. */
ble_gap_phys_t const phys = {
.rx_phys = BLE_GAP_PHY_1MBPS,
.tx_phys = BLE_GAP_PHY_1MBPS,
};
err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
APP_ERROR_CHECK(err_code);
}
break;
case BLE_GAP_EVT_RSSI_CHANGED:
ble_rssi_update(p_ble_evt->evt.gap_evt.params.rssi_changed.rssi);
break;
case BLE_GATTC_EVT_TIMEOUT:
case BLE_GATTS_EVT_TIMEOUT:
DBG_PRINTF("[BLE] GATT Timeout -> disconnect\r\n");
err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gap_evt.conn_handle,
BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
APP_ERROR_CHECK(err_code);
break;
case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
#if !FEATURE_SECURE_CONNECTION
err_code = sd_ble_gap_sec_params_reply(m_conn_handle,
BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP,
NULL, NULL);
APP_ERROR_CHECK(err_code);
#else
if (ble_security_is_dev_mode()) {
err_code = sd_ble_gap_sec_params_reply(m_conn_handle,
BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP,
NULL, NULL);
APP_ERROR_CHECK(err_code);
DBG_PRINTF("DEV: Rejected security request\r\n");
}
#endif
break;
#if FEATURE_SECURE_CONNECTION
case BLE_GAP_EVT_PASSKEY_DISPLAY:
{
char passkey[PASSKEY_LENGTH + 1];
memcpy(passkey, p_ble_evt->evt.gap_evt.params.passkey_display.passkey, PASSKEY_LENGTH);
passkey[PASSKEY_LENGTH] = 0;
DBG_PRINTF("[BLE] Passkey: %s\r\n", passkey);
}
break;
case BLE_GAP_EVT_AUTH_KEY_REQUEST:
#if FEATURE_STATIC_PASSKEY
if (p_gap_evt->params.auth_key_request.key_type == BLE_GAP_AUTH_KEY_TYPE_PASSKEY)
{
err_code = sd_ble_gap_auth_key_reply(p_gap_evt->conn_handle,
BLE_GAP_AUTH_KEY_TYPE_PASSKEY,
(const uint8_t *)m_static_passkey);
APP_ERROR_CHECK(err_code);
}
#endif
break;
case BLE_GAP_EVT_LESC_DHKEY_REQUEST:
break;
#endif
case BLE_GATTS_EVT_HVN_TX_COMPLETE:
ble_link_stress_on_hvn_complete(); /* EMC: HVN latency feeds dynamic TX power control */
m_tx_in_progress = false;
m_tx_complete_pending = false; /* Notify waiting functions of TX completion */
break;
default:
break;
}
}
/*==============================================================================
* Utility Functions
*============================================================================*/
/** @brief LED init + erase_bonds default (boot Phase 5) */
static void buttons_leds_init(bool * p_erase_bonds)
{
led_init();
*p_erase_bonds = false;
}
static void log_init(void)
{
ret_code_t err_code = NRF_LOG_INIT(NULL);
APP_ERROR_CHECK(err_code);
NRF_LOG_DEFAULT_BACKENDS_INIT();
}
/**
* @brief UART receive event handler
*
* Accumulates bytes received from physical UART (1Mbps) into a buffer.
* On receiving '\n' or '\r', passes data to received_command_process().
* Used for debug and factory testing.
*/
void uart_event_handle(app_uart_evt_t * p_event)
{
static uint8_t data_array[BLE_NUS_MAX_DATA_LEN] = {0};
static uint8_t index = 0;
switch (p_event->evt_type)
{
case APP_UART_DATA_READY:
UNUSED_VARIABLE(app_uart_get(&data_array[index]));
index++;
if ((data_array[index - 1] == '\n') || (data_array[index - 1] == '\r') || (index >= m_ble_nus_max_data_len))
{
if (index > 1)
{
cmd_type_t = CMD_UART;
ble_got_new_data = true;
dr_cmd_parser(data_array, index);
}
index = 0;
}
break;
case APP_UART_COMMUNICATION_ERROR:
break;
case APP_UART_FIFO_ERROR:
APP_ERROR_HANDLER(p_event->data.error_code);
break;
default:
break;
}
}
/** @brief UART init (1Mbps, no flow control, no parity) -- currently unused (power saving) */
#if 0
static void uart_init(void)
{
uint32_t err_code;
app_uart_comm_params_t const comm_params =
{
.rx_pin_no = RX_PIN_NUMBER,
.tx_pin_no = TX_PIN_NUMBER,
.rts_pin_no = RTS_PIN_NUMBER,
.cts_pin_no = CTS_PIN_NUMBER,
.flow_control = APP_UART_FLOW_CONTROL_DISABLED,
.use_parity = false,
#if defined (UART_PRESENT)
.baud_rate = NRF_UART_BAUDRATE_1000000
#else
.baud_rate = NRF_UARTE_BAUDRATE_1000000
#endif
};
APP_UART_FIFO_INIT(&comm_params,
UART_RX_BUF_SIZE,
UART_TX_BUF_SIZE,
uart_event_handle,
APP_IRQ_PRIORITY_LOWEST,
err_code);
APP_ERROR_CHECK(err_code);
}
#endif
static void power_management_init(void)
{
ret_code_t err_code;
err_code = nrf_pwr_mgmt_init();
APP_ERROR_CHECK(err_code);
}
/**
* @brief Idle state processing
*
* Process BLE security (LESC) requests, flush logs, then
* put the CPU into low-power state via nrf_pwr_mgmt_run().
* Automatically wakes on BLE events.
*/
static void idle_state_handle(void)
{
#if FEATURE_SECURE_CONNECTION
ret_code_t err_code;
err_code = nrf_ble_lesc_request_handler();
APP_ERROR_CHECK(err_code);
#endif
if (NRF_LOG_PROCESS() == false)
{
nrf_pwr_mgmt_run();
}
}
void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
{
app_error_handler(DEAD_BEEF, line_num, p_file_name);
}
/**
* @brief Enter sleep mode
*
* Indicate to user via LED, then start 3s (POWER_OFF_DELAY) wait timer.
* On timer expiry, t_power_off_timeout_handler() cuts physical power.
*/
void sleep_mode_enter(void)
{
led_set_state(LED_STATE_POWER_OFF);
DBG_PRINTF("[SYS] Sleep\r\n");
APP_ERROR_CHECK(app_timer_start(m_power_off_delay_timer_id, APP_TIMER_TICKS(POWER_OFF_DELAY), NULL));
}
/**
* @brief Device power off
*
* Indicate to user via LED, then start 3s wait timer.
* Cuts physical power using the same mechanism as sleep_mode_enter().
*/
void device_power_off(void)
{
led_set_state(LED_STATE_POWER_OFF);
APP_ERROR_CHECK(app_timer_start(m_power_off_delay_timer_id, APP_TIMER_TICKS(POWER_OFF_DELAY), NULL));
}
/*==============================================================================
* Data Transmission Functions
* Transmit sensor data/responses to the smartphone app via BLE NUS.
* Packet structure: [data][CRC16 2 bytes]
*============================================================================*/
/**
* @brief ASCII text BLE transmission
*
* Copy string content up to '\r', append CRC16, and send via BLE.
* Returns immediately if a transmission is already in progress (m_tx_in_progress).
*/
void data_tx_handler(char const *p_data_to_send)
{
if (m_tx_in_progress)
{
return;
}
char const *p_end_char = strchr(p_data_to_send, '\r');
if (p_end_char == NULL)
{
return;
}
uint16_t data_len = p_end_char - p_data_to_send;
if (data_len > (BLE_NUS_MAX_DATA_LEN - 2))
{
return;
}
memcpy(m_tx_buffer, p_data_to_send, data_len);
uint16_t crc = crc16_compute(m_tx_buffer, data_len, NULL);
m_tx_buffer[data_len] = (uint8_t)(crc & 0xFF);
m_tx_buffer[data_len + 1] = (uint8_t)((crc >> 8) & 0xFF);
m_tx_len = data_len + 2;
if (ble_connection_st == BLE_CONNECTED_ST)
{
m_tx_in_progress = true;
uint32_t err_code = ble_nus_data_send(&m_nus, m_tx_buffer, &m_tx_len, m_conn_handle);
if (err_code == NRF_SUCCESS)
{
ble_link_stress_on_hvn_send(); /* EMC: start HVN latency stopwatch */
}
else if (err_code == NRF_ERROR_RESOURCES)
{
/* Retry later; pending queue depth will trigger boost if congestion persists. */
}
else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND)
{
ble_connection_st = BLE_DISCONNECTED_ST;
m_conn_handle = BLE_CONN_HANDLE_INVALID;
m_tx_in_progress = false;
}
else
{
DBG_PRINTF("[BLE TX] Err:0x%X\r\n", err_code); // Log instead of APP_ERROR_CHECK - jhChun 26.03.16
m_tx_in_progress = false;
//APP_ERROR_CHECK(err_code);
}
}
}
/**
* @brief Format single uint16_t value into binary packet
*
* Packet structure: [tag 4 bytes][value 2 bytes] = 6 bytes total (3 words).
* Tag is 4 ASCII chars (e.g. "rta:", "rsn:"), value stored big-endian.
*/
void single_format_data(uint8_t *buffer, const char *tag, const uint16_t value)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
buffer[5] = (uint8_t)(value & 0xFF);
buffer[4] = (uint8_t)((value >> 8) & 0xFF);
}
/**
* @brief Format uint16_t array into binary packet
*
* Packet structure: [tag 4 bytes][data0 2 bytes][data1 2 bytes]...
* Each value stored big-endian; length is array element count, not byte count.
*/
void format_data(uint8_t *buffer, const char *tag, const uint16_t *data_array, size_t length)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
for (size_t i = 0; i < length; i++)
{
buffer[4 + i * 2 + 1] = (uint8_t)(data_array[i] & 0xFF);
buffer[4 + i * 2] = (uint8_t)((data_array[i] >> 8) & 0xFF);
}
}
/**
* @brief Format uint8_t byte array into binary packet
*
* Packet structure: [tag 4 bytes][byte0][byte1]...
*/
void format_data_byte(uint8_t *buffer, const char *tag, const uint8_t *data_array, size_t length)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
for (size_t i = 0; i < length; i++)
{
buffer[4 + i] = (uint8_t)(data_array[i] & 0xFF);
}
}
/**
* @brief Format ASCII string into binary packet
*
* Packet structure: [tag 4 bytes][char0][char1]...
* Used for transmitting serial number, passkey, firmware version, etc.
*/
void ascii_format_data(uint8_t *buffer, const char *tag, const char *data_ascii, size_t length)
{
uint16_t tag1 = ((uint16_t)tag[1] << 8) | (uint16_t)tag[0];
uint16_t tag2 = ((uint16_t)tag[3] << 8) | (uint16_t)tag[2];
buffer[0] = (uint8_t)(tag1 & 0xFF);
buffer[1] = (uint8_t)((tag1 >> 8) & 0xFF);
buffer[2] = (uint8_t)(tag2 & 0xFF);
buffer[3] = (uint8_t)((tag2 >> 8) & 0xFF);
for (size_t i = 0; i < length; i++)
{
buffer[4 + i] = (uint8_t)(data_ascii[i] & 0xFF);
}
}
/**
* @brief BLE binary safe transmission (preamble for dr_binary_tx_safe)
*
* On NRF_ERROR_RESOURCES (TX queue full), stores one pending packet and retries
* only when BLE_NUS_EVT_TX_RDY is received.
* Returns gracefully on connection errors.
*
* @param ble_bin_buff Data buffer to transmit
* @param length uint16_t word count (actual bytes = length * 2)
*/
/**
* @brief Safe delay for BLE operations
*
* Uses interrupt-safe nrf_delay_ms().
* BLE stack runs on interrupts, so TX complete events are processed during delay.
*
* Note: Do NOT use __WFE() or sd_app_evt_wait() here!
* It conflicts with SoftDevice power management.
*
* @param ms Delay time in milliseconds
*/
void dr_sd_delay_ms(uint32_t ms)
{
if (ms == 0) return;
nrf_delay_ms(ms);
}
extern int SEGGER_RTT_vprintf(unsigned, const char *, va_list *);
static void log_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
SEGGER_RTT_vprintf(0, fmt, &args);
va_end(args);
}
void param_error(const char *cmd)
{
char resp_error[4];
resp_error[0] = 'r';
resp_error[1] = cmd[1];
resp_error[2] = cmd[2];
resp_error[3] = '!';
single_format_data(ble_bin_buffer, resp_error, 65533); /* err_code3 */
dr_binary_tx_safe(ble_bin_buffer, 3);
}
uint32_t dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length)
{
uint32_t err_code;
static uint8_t tx_buffer[BLE_NUS_MAX_DATA_LEN] = {0};
uint16_t send_len;
uint16_t total_len;
if (ble_connection_st == 0) return NRF_ERROR_INVALID_STATE;
if (length * sizeof(uint16_t) > (BLE_NUS_MAX_DATA_LEN - 2)) return NRF_ERROR_DATA_SIZE;
if (ble_connection_st == BLE_CONNECTED_ST)
{
memcpy(tx_buffer, ble_bin_buff, length * sizeof(uint16_t));
uint16_t crc = crc16_compute(tx_buffer, length * sizeof(uint16_t), NULL);
tx_buffer[length * sizeof(uint16_t)] = (uint8_t)(crc & 0xFF);
tx_buffer[length * sizeof(uint16_t) + 1] = (uint8_t)((crc >> 8) & 0xFF);
total_len = length * sizeof(uint16_t) + 2;
if (ble_tx_pending_has_data())
{
if (!ble_tx_pending_enqueue(tx_buffer, total_len))
{
DBG_PRINTF("[BLE TX] Pending queue full, dropping new packet\r\n");
return NRF_ERROR_NO_MEM;
}
ble_link_stress_check_pending_depth();
return NRF_ERROR_RESOURCES;
}
send_len = total_len;
err_code = ble_nus_data_send(&m_nus, tx_buffer, &send_len, m_conn_handle);
if (err_code == NRF_SUCCESS)
{
ble_link_stress_on_hvn_send(); /* EMC: start HVN latency stopwatch */
return NRF_SUCCESS;
}
else if (err_code == NRF_ERROR_RESOURCES)
{
if (!ble_tx_pending_enqueue(tx_buffer, total_len))
{
DBG_PRINTF("[BLE TX] Pending queue full, dropping new packet\r\n");
return NRF_ERROR_NO_MEM;
}
ble_link_stress_check_pending_depth();
return NRF_ERROR_RESOURCES;
}
else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND)
{
DBG_PRINTF("[BLE TX] Disconnected\r\n");
return err_code;
}
else
{
DBG_PRINTF("[BLE TX] Err:0x%X\r\n", err_code);
return err_code;
}
}
return NRF_ERROR_INVALID_STATE;
}
/*==============================================================================
* Power Button State Machine (main_s)
*
* Called repeatedly by a 5ms single-shot timer (m_power_on_delay_timer).
*
* [Boot phase] (booted == false)
* - cnt_s < 400 (< 2s): short press -> power OFF
* - cnt_s >= 400 (~2s): normal boot sequence start
* - cnt_s > 1000 (~5s): factory reset (passkey reset + power OFF)
* - m_reset_status == 2: reboot after software reset
*
* [Running phase] (booted == true)
* - Button pressed 2s (cnt_s >= 400): power OFF
* - Button released: reset counter, keep polling
*============================================================================*/
static bool booted = false;
static void main_s(void * p_context)
{
UNUSED_PARAMETER(p_context);
APP_ERROR_CHECK(app_timer_stop(m_power_on_delay_timer_id));
bool button_released = nrf_gpio_pin_read(POWER_BUTTON);
/* ---- Running phase: post-boot button polling ---- */
if (booted)
{
if (!button_released)
{
cnt_s++;
if (cnt_s == 400) /* 400 x 5ms = 2s */
{
DBG_PRINTF("[BTN] Power OFF\r\n");
led_set_state(LED_STATE_POWER_OFF);
go_device_power_off = true;
main_timer_start();
return;
}
}
else
{
cnt_s = 0;
}
timers_start();
return;
}
/* ---- Boot phase ---- */
if (button_released)
{
if ((cnt_s < 250) && (m_reset_status != 2))
{
led_set_state(LED_STATE_OFF);
power_control_handler(OFF);
cnt_s = 0;
}
else if (cnt_s >= 3000) /* 3000 x 5ms = 15s */
{
DBG_PRINTF("[BTN] Bond Delete\r\n");
power_control_handler(ON);
nrf_delay_ms(100);
bond_data_delete = true;
m_config.bond_data_delete = (uint8_t)bond_data_delete;
config_save();
nrf_delay_ms(1000);
go_device_power_off = true;
main_timer_start();
}
else if (cnt_s > 250 || (m_reset_status == 2)) /* 250 x 5ms = 1.25s + α */
{
DBG_PRINTF("[BTN] Boot (cnt=%d)\r\n", cnt_s);
device_reset = false;
power_control_handler(ON);
battery_timer_start();
#if FEATURE_SECURE_CONNECTION
/* DEV mode: bonds already deleted in ble_security_quick_init() -> prevent duplicate */
advertising_start(!ble_security_is_dev_mode() && (erase_bonds || bond_data_delete));
#else
advertising_start();
#endif
DBG_PRINTF("[BOOT] ADV started\r\n");
m_reset_status = 1;
DBG_PRINTF("[BOOT] Ready\r\n");
/* Boot complete -> switch to running phase */
booted = true;
cnt_s = 0;
timers_start();
}
}
else
{
cnt_s++;
device_reset = false;
if (cnt_s == 250) /* 250 x 5ms = 1.25s + α */
{
led_set_state(LED_STATE_POWER_ON);
DBG_PRINTF("[BTN] 2.0s\r\n");
}
timers_start();
}
}
/*==============================================================================
* Main Function
*
* Performs system initialization in phases (Phase 0~9), then enters infinite loop.
* In the loop, idle_state_handle() keeps the CPU in low-power state,
* waking on BLE/timer interrupts to process events.
*============================================================================*/
int main(void)
{
bool erase_bonds_local = false;
/*──────────────────────────────────────────────────────────────
* Phase 1: Basic hardware init (BLE-independent)
*
* - Power self-latch (P0.8 HIGH)
* - RTT log output
* - GPIO (power button input)
* - App timers (power polling, battery, main loop)
* - Default config (serial number, passkey)
* - Buttons/LEDs
*────────────────────────────────────────────────────────────*/
power_hold_init();
if (power_off_duble_prohibit)
{
return 0;
}
cnt_s = 0;
log_init();
DBG_PRINTF("\r\n========================================\r\n");
DBG_PRINTF(" Medithings v1.17\r\n");
DBG_PRINTF("========================================\r\n");
DBG_PRINTF("[1] HW Init\r\n");
gpio_init();
info4 = false;
timers_init();
load_default_config();
buttons_leds_init(&erase_bonds_local);
#if FEATURE_SECURE_CONNECTION
erase_bonds = erase_bonds_local;
#endif
DBG_PRINTF(" gpio/timer/config/btn OK\r\n");
/*──────────────────────────────────────────────────────────────
* Phase 2: BLE stack init
*
* - Power management module (WFE/sleep when idle)
* - SoftDevice S140 (BLE 5.0 protocol stack)
* - DC-DC converter enable (required after SoftDevice)
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("[2] BLE Stack\r\n");
power_management_init();
ble_stack_init();
APP_ERROR_CHECK(sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE));
DBG_PRINTF(" pwr/stack/dcdc OK\r\n");
/*──────────────────────────────────────────────────────────────
* Phase 3: Internal flash config (must init after BLE stack)
*
* - FDS (Flash Data Storage) init
* - Read stored config from flash (serial, passkey, piezo, etc.)
* - Overwrite defaults with flash values
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("[3] FDS\r\n");
fs_storage_init();
config_load();
load_flash_config();
DBG_PRINTF(" fds OK\r\n");
/*──────────────────────────────────────────────────────────────
* Phase 4: BLE protocol setup
*
* - GAP: device name (serial number), connection params, passkey
* - GATT: MTU size negotiation
* - Services: NUS (Nordic UART Service), QWR, DFU
* - Advertising: advertising data (name, UUID)
* - Connection parameter negotiation module
* - Security: Peer Manager (bonding/passkey)
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("[4] BLE Protocol\r\n");
gap_params_init();
gatt_init();
services_init();
advertising_init();
conn_params_init();
#if FEATURE_SECURE_CONNECTION
peer_manager_init();
/* Log bond state at boot time */
{
uint32_t bond_count = pm_peer_count();
DBG_PRINTF("[BOOT] Bond state: count=%lu flag_delete=%d erase_bonds=%d\r\n",
(unsigned long)bond_count, bond_data_delete, erase_bonds);
if (bond_count > 0)
{
pm_peer_id_t pid = pm_next_peer_id_get(PM_PEER_ID_INVALID);
while (pid != PM_PEER_ID_INVALID)
{
DBG_PRINTF("[BOOT] Bonded peer id=%u\r\n", pid);
pid = pm_next_peer_id_get(pid);
}
}
}
#endif
DBG_PRINTF(" gap/gatt/svc/adv/conn/sec OK\r\n");
if (m_need_save_defaults)
{
config_save();
m_need_save_defaults = false;
DBG_PRINTF(" fds defaults saved\r\n");
}
/*──────────────────────────────────────────────────────────────
* Phase 5: Application init
*
* - Command parser: process BLE received commands (log, BLE TX, CRC)
* - Piezo driver: GPIO/Timer/PPI setup for ultrasound measurement
* - IMU (ICM42670P) is NOT initialized here
* -> imu_read_direct() self-configures/reads/sleeps on each msp? command
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("[5] App\r\n");
g_plat.log = log_printf;
g_plat.tx_bin = dr_binary_tx_safe;
g_plat.crc_check = true;
g_log_enable = true;
cmd_table_init(); /* Inject command table into parser */
dr_piezo_init();
DBG_PRINTF(" parser/piezo OK\r\n");
/*──────────────────────────────────────────────────────────────
* Boot complete -> start power button state machine
*────────────────────────────────────────────────────────────*/
DBG_PRINTF("\r\n========================================\r\n");
DBG_PRINTF(" READY [%s]\r\n", SERIAL_NO);
DBG_PRINTF("========================================\r\n\r\n");
timers_start();
// MAIN LOOP
for (;;)
{
/* Process command received in BLE callback here (prevents blocking) */
if (pending_cmd_len > 0)
{
uint8_t len = pending_cmd_len;
pending_cmd_len = 0;
dr_cmd_parser((const uint8_t *)pending_cmd_buf, len);
}
idle_state_handle();
}
}