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으로 동적 제어
This commit is contained in:
2026-06-15 15:39:41 +09:00
parent ad9546b934
commit 26c6d035f0
@@ -142,11 +142,35 @@
#define MIN_CONN_INTERVAL MSEC_TO_UNITS(15, UNIT_1_25_MS) /* Min connection interval: 15ms */ #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 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 SLAVE_LATENCY 0 /* Slave latency: 0 (respond every connection event) */
#define CONN_SUP_TIMEOUT MSEC_TO_UNITS(4000, UNIT_10_MS) /* Connection supervision timeout: 4s */ #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 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 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 */ #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 * System Constants
*============================================================================*/ *============================================================================*/
@@ -172,6 +196,7 @@ BLE_ADVERTISING_DEF(m_advertising); /* Advertising module instanc
APP_TIMER_DEF(m_power_on_delay_timer_id); /* Power button polling timer (5ms single-shot, main_s callback) */ 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_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_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 * Static Variables
@@ -183,6 +208,17 @@ static pm_peer_id_t m_peer_to_be_deleted = PM_PEER_ID_INVALID;
static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID; /* Current BLE connection handle */ 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 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 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 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 uint16_t m_tx_len = 0; /* Data length to transmit */
@@ -228,7 +264,8 @@ volatile bool ble_connection_st; /* BLE connection state (1=co
volatile bool data_tx_in_progress = false; /* Binary TX in progress flag */ volatile bool data_tx_in_progress = false; /* Binary TX in progress flag */
/* -- BLE TX async retry state -- */ /* -- BLE TX async retry state -- */
#define BLE_TX_PENDING_QUEUE_SIZE 8U #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 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 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_head = 0; /* Next packet to retry */
@@ -394,6 +431,7 @@ static void load_flash_config(void)
static void main_s(void * p_context); /* Power button state machine (forward declaration) */ 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 t_power_off_timeout_handler(void * p_context); /* Power-off timeout */
static void PM_s(void * p_context); /* Peer Manager timer */ 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 * @brief Power-off timeout callback
@@ -440,6 +478,7 @@ static void PM_s(void * p_context)
* - m_power_on_delay_timer: power button polling (5ms single-shot, main_s callback) * - 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_power_off_delay_timer: power-off delay (3s single-shot)
* - m_PM_timer: Peer Manager disconnect (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) * - main_timer: main event loop (10ms single-shot, main_loop callback)
* - battery_timer: battery monitoring (5s repeating) * - battery_timer: battery monitoring (5s repeating)
* - power_timer: power sequence (20ms single-shot, power_loop callback) * - power_timer: power sequence (20ms single-shot, power_loop callback)
@@ -452,6 +491,7 @@ static void timers_init(void)
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_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_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_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(); main_timer_init();
battery_timer_init(); battery_timer_init();
@@ -512,7 +552,7 @@ static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event)
* @brief Initialize GAP (Generic Access Profile) parameters * @brief Initialize GAP (Generic Access Profile) parameters
* *
* 1) Set device name to SERIAL_NO -> shown during BLE scan * 1) Set device name to SERIAL_NO -> shown during BLE scan
* 2) Configure connection parameters (20~75ms interval, slave latency 0, supervision timeout 4s) * 2) Configure connection parameters (15ms interval, slave latency 0, EMC supervision timeout 10s)
* 3) Set static passkey (when FEATURE_STATIC_PASSKEY enabled) * 3) Set static passkey (when FEATURE_STATIC_PASSKEY enabled)
*/ */
static void gap_params_init(void) static void gap_params_init(void)
@@ -564,6 +604,11 @@ extern void maa_async_abort(void);
static volatile uint8_t pending_cmd_buf[BLE_NUS_MAX_DATA_LEN] = {0}; static volatile uint8_t pending_cmd_buf[BLE_NUS_MAX_DATA_LEN] = {0};
static volatile uint8_t pending_cmd_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) static bool ble_tx_pending_has_data(void)
{ {
return (s_tx_pending_count > 0); return (s_tx_pending_count > 0);
@@ -625,6 +670,7 @@ static bool ble_retry_pending_tx(void)
if (err == NRF_SUCCESS) if (err == NRF_SUCCESS)
{ {
ble_link_stress_on_hvn_send();
ble_tx_pending_pop(); ble_tx_pending_pop();
freed_slot = true; freed_slot = true;
continue; continue;
@@ -1057,6 +1103,359 @@ static void peer_manager_init(void)
} }
#endif #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 * BLE Event Handler
* Handles all BLE events from the SoftDevice. * Handles all BLE events from the SoftDevice.
@@ -1067,13 +1466,14 @@ static void peer_manager_init(void)
* *
* Key events handled: * Key events handled:
* - DISCONNECTED: connection lost -> device sleep, state reset * - DISCONNECTED: connection lost -> device sleep, state reset
* - CONNECTED: connection established -> assign QWR handle, set TX power +8dBm * - CONNECTED: connection established -> assign QWR handle, start EMC link monitor
* - PHY_UPDATE_REQUEST: keep link on 1M PHY * - PHY_UPDATE_REQUEST: keep link on 1M PHY
* - RSSI_CHANGED: RSSI input for dynamic TX power boost/restore
* - TIMEOUT: connection/GATT timeout -> force disconnect * - TIMEOUT: connection/GATT timeout -> force disconnect
* - SEC_PARAMS_REQUEST: security parameter request (reject if security unused) * - SEC_PARAMS_REQUEST: security parameter request (reject if security unused)
* - PASSKEY_DISPLAY: display passkey (debug log) * - PASSKEY_DISPLAY: display passkey (debug log)
* - AUTH_KEY_REQUEST: respond with static passkey * - AUTH_KEY_REQUEST: respond with static passkey
* - HVN_TX_COMPLETE: TX complete -> clear transmission flag * - 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) static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{ {
@@ -1109,6 +1509,8 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
DBG_PRINTF("[BLE] Disconnected (reason 0x%02X)%s\r\n", DBG_PRINTF("[BLE] Disconnected (reason 0x%02X)%s\r\n",
disc_reason, unintended_disc ? " [UNINTENDED]" : ""); disc_reason, unintended_disc ? " [UNINTENDED]" : "");
ble_rssi_monitor_stop(); /* EMC: stop RSSI/HVN stress timer before invalidating handle */
ble_connection_st = 0; ble_connection_st = 0;
pending_cmd_len = 0; // Clear pending command buffer pending_cmd_len = 0; // Clear pending command buffer
m_conn_handle = BLE_CONN_HANDLE_INVALID; m_conn_handle = BLE_CONN_HANDLE_INVALID;
@@ -1152,7 +1554,7 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle); err_code = nrf_ble_qwr_conn_handle_assign(&m_qwr, m_conn_handle);
APP_ERROR_CHECK(err_code); APP_ERROR_CHECK(err_code);
sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_CONN, m_conn_handle, 4); 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 */ led_set_state(LED_STATE_OFF); /* Connection complete -> LED OFF */
@@ -1170,6 +1572,10 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
} }
break; 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_GATTC_EVT_TIMEOUT:
case BLE_GATTS_EVT_TIMEOUT: case BLE_GATTS_EVT_TIMEOUT:
DBG_PRINTF("[BLE] GATT Timeout -> disconnect\r\n"); DBG_PRINTF("[BLE] GATT Timeout -> disconnect\r\n");
@@ -1222,6 +1628,7 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
#endif #endif
case BLE_GATTS_EVT_HVN_TX_COMPLETE: 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_in_progress = false;
m_tx_complete_pending = false; /* Notify waiting functions of TX completion */ m_tx_complete_pending = false; /* Notify waiting functions of TX completion */
break; break;
@@ -1424,11 +1831,11 @@ void data_tx_handler(char const *p_data_to_send)
if (err_code == NRF_SUCCESS) if (err_code == NRF_SUCCESS)
{ {
// OK ble_link_stress_on_hvn_send(); /* EMC: start HVN latency stopwatch */
} }
else if (err_code == NRF_ERROR_RESOURCES) else if (err_code == NRF_ERROR_RESOURCES)
{ {
// Retry later /* 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) else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND)
{ {
@@ -1607,6 +2014,7 @@ uint32_t dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length)
return NRF_ERROR_NO_MEM; return NRF_ERROR_NO_MEM;
} }
ble_link_stress_check_pending_depth();
return NRF_ERROR_RESOURCES; return NRF_ERROR_RESOURCES;
} }
@@ -1615,6 +2023,7 @@ uint32_t dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length)
if (err_code == NRF_SUCCESS) if (err_code == NRF_SUCCESS)
{ {
ble_link_stress_on_hvn_send(); /* EMC: start HVN latency stopwatch */
return NRF_SUCCESS; return NRF_SUCCESS;
} }
else if (err_code == NRF_ERROR_RESOURCES) else if (err_code == NRF_ERROR_RESOURCES)
@@ -1625,6 +2034,7 @@ uint32_t dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length)
return NRF_ERROR_NO_MEM; return NRF_ERROR_NO_MEM;
} }
ble_link_stress_check_pending_depth();
return NRF_ERROR_RESOURCES; return NRF_ERROR_RESOURCES;
} }
else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND) else if (err_code == NRF_ERROR_INVALID_STATE || err_code == NRF_ERROR_NOT_FOUND)