Compare commits

..

14 Commits

Author SHA1 Message Date
jh.chun ab74582568 IMU FIFO 동시 진입 방지 및 MCLK ready timeout 추가
- IMU FIFO capture가 이미 활성화되어 있는 상태에서 msp?/mim? 등이 들어오는 경우 요청 무시
- MCLK_RDY 대기에 timeout 추가해 IMU/I2C 상태가 꼬였을 때 메인 루프가 멈추지 않도록 함
2026-07-08 14:51:29 +09:00
jh.chun b75c99b125 IMU FIFO 커맨드 mim? 추가 2026-07-06 12:04:44 +09:00
jh.chun 763abb0fcc Piezo 측정 파라미터 공용 헤더 추가 및 최대 샘플 수 118개로 변경
- reb: 패킷에 채널 정보 2바이트가 추가되어 단일 패킷 최대 샘플 수를 119개에서 118개로 조정
2026-06-26 17:40:05 +09:00
jh.chun 80d7086d09 bootloader DFU 배치 파일
- DFU를 통한 bootloader 반영을 위해 생성
- 현재 nrfutil 버전에서는 App + bootloader를 한 zip 파일에 넣을 수 없어 개별 생성
2026-06-26 17:34:40 +09:00
jh.chun 39cf6068f7 주석 정리 2026-06-26 17:28:52 +09:00
jh.chun 0d719751c4 DFU activation 후 App 부팅 시 전원 OFF 처리 추가 2026-06-26 17:22:56 +09:00
jh.chun f3110c696f B-Mode Test용 커맨드 mab? 추가
- 6채널 단발 측정
- 측정 시작 시 초록색 LED ON, 측정 종료 시 초록색 LED OFF
2026-06-22 12:28:38 +09:00
jh.chun 08a4bbef5d DFU 진입 후 업데이트 대기 시간 2분 → 1분 2026-06-19 18:04:10 +09:00
jh.chun 873ae0fb83 정렬모드 종료 시 IMU FIFO 비활성화 2026-06-18 16:22:26 +09:00
jh.chun 593f1d1c5b 정렬모드 중에는 IMU FIFO OFF 하지 않도록 수정
- IMU 샘플 15개 보장
2026-06-18 13:57:49 +09:00
jh.chun cda370b69d mtb? 정렬모드 중 IMU 주기 온도 읽기 비활성화
- 정렬모드(mtb?)일 때 FIFO가 활성화 상태, FW 내부 60초 주기 온도 점검을 위해 온도 레지스터 접근 시 오류 발생 가능
2026-06-16 10:40:44 +09:00
jh.chun 228f6da4a3 IMU ACC FSR 4g 통일
- 기존에는 direct read 8g, FIFO 4g
2026-06-16 10:33:35 +09:00
jh.chun 25befa26b1 Ver120 업데이트: EMC 대비 2026-06-15 15:40:10 +09:00
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
24 changed files with 837 additions and 66 deletions
@@ -64,6 +64,7 @@ static volatile bool m_flash_write_done;
#define SCHED_QUEUE_SIZE 32 /**< Maximum number of events in the scheduler queue. */ #define SCHED_QUEUE_SIZE 32 /**< Maximum number of events in the scheduler queue. */
#define SCHED_EVENT_DATA_SIZE NRF_DFU_SCHED_EVENT_DATA_SIZE /**< Maximum app_scheduler event size. */ #define SCHED_EVENT_DATA_SIZE NRF_DFU_SCHED_EVENT_DATA_SIZE /**< Maximum app_scheduler event size. */
#define POST_DFU_APP_BOOT_GPREGRET2_MASK 0x02U
#if !(defined(NRF_BL_DFU_ENTER_METHOD_BUTTON) && \ #if !(defined(NRF_BL_DFU_ENTER_METHOD_BUTTON) && \
defined(NRF_BL_DFU_ENTER_METHOD_PINRESET) && \ defined(NRF_BL_DFU_ENTER_METHOD_PINRESET) && \
@@ -470,6 +471,7 @@ ret_code_t nrf_bootloader_init(nrf_dfu_observer_t observer)
break; break;
case ACTIVATION_SUCCESS: case ACTIVATION_SUCCESS:
nrf_power_gpregret2_set(nrf_power_gpregret2_get() | POST_DFU_APP_BOOT_GPREGRET2_MASK);
bootloader_reset(true); bootloader_reset(true);
NRF_LOG_ERROR("Unreachable"); NRF_LOG_ERROR("Unreachable");
return NRF_ERROR_INTERNAL; // Should not reach this. return NRF_ERROR_INTERNAL; // Should not reach this.
@@ -50,6 +50,8 @@ extern int imu_read_direct(void);
extern int imu_read_temperature_x100(uint16_t *temp_x100, float *temp_c); extern int imu_read_temperature_x100(uint16_t *temp_x100, float *temp_c);
extern int imu_fifo_capture_start(void); extern int imu_fifo_capture_start(void);
extern int imu_fifo_capture_stop_and_send_rim(void); extern int imu_fifo_capture_stop_and_send_rim(void);
extern int imu_fifo_capture_wait_samples_and_send_rim(uint16_t target_samples, uint32_t timeout_ms);
extern void imu_fifo_capture_disable(void);
extern void battery_timer_stop(void); extern void battery_timer_stop(void);
extern void main_timer_start(void); extern void main_timer_start(void);
extern void hw_i2c_init_once(void); extern void hw_i2c_init_once(void);
@@ -41,6 +41,7 @@ static const CmdEntry m_cmd_table[] = {
{ "msn?", true, Cmd_msn }, { "msn?", true, Cmd_msn },
{ "mst?", true, Cmd_mst }, { "mst?", true, Cmd_mst },
{ "msp?", true, Cmd_msp }, { "msp?", true, Cmd_msp },
{ "mim?", true, Cmd_mim },
/* D. Piezo ultrasound */ /* D. Piezo ultrasound */
{ "mpa?", true, Cmd_mpa }, { "mpa?", true, Cmd_mpa },
@@ -49,6 +50,7 @@ static const CmdEntry m_cmd_table[] = {
{ "mec?", true, Cmd_mec }, { "mec?", true, Cmd_mec },
{ "maa?", true, Cmd_maa }, { "maa?", true, Cmd_maa },
{ "mbb?", true, Cmd_mbb }, { "mbb?", true, Cmd_mbb },
{ "mab?", true, Cmd_mab }, // B-Mode Test
{ "mtb?", true, Cmd_mtb }, { "mtb?", true, Cmd_mtb },
{ "mcf?", true, Cmd_mcf }, { "mcf?", true, Cmd_mcf },
{ "mcs?", true, Cmd_mcs }, { "mcs?", true, Cmd_mcs },
@@ -12,6 +12,7 @@
#include "cmd_device.h" #include "cmd_device.h"
#include "fstorage.h" #include "fstorage.h"
#include "led_control.h" #include "led_control.h"
#include "app_raw.h"
/*============================================================================== /*==============================================================================
* msq? -> rsq: Device power OFF * msq? -> rsq: Device power OFF
@@ -172,6 +173,14 @@ int Cmd_mls(const ParsedCmd *cmd)
} }
led_set_state((led_state_t)state); led_set_state((led_state_t)state);
if ((led_state_t)state == LED_STATE_OFF)
{
if (imu_fifo_capture_is_active())
{
imu_fifo_capture_disable();
}
}
dr_ble_return_1("rls:", state); dr_ble_return_1("rls:", state);
return 1; return 1;
} }
@@ -16,12 +16,19 @@
#include "cmd_sensor.h" /* all_sensors() */ #include "cmd_sensor.h" /* all_sensors() */
#include "dr_piezo.h" #include "dr_piezo.h"
#include "dr_adc121s051.h" #include "dr_adc121s051.h"
#include "led_control.h"
#include "../../measurement/piezo/piezo_config.h"
static void mtb_send_rim_after_piezo(void) static void mtb_send_rim_after_piezo(void)
{ {
send_imu_rim_fifo(); send_imu_rim_fifo();
} }
static void mab_led_off_after_piezo(void)
{
led_set_state(LED_STATE_OFF);
}
/*------------------------------------------------------------------------------ /*------------------------------------------------------------------------------
* Internal clamp helpers for persisted piezo configuration * Internal clamp helpers for persisted piezo configuration
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
@@ -58,48 +65,48 @@ static uint8_t clamp_piezo_freq_option(uint16_t raw_freq)
static uint8_t clamp_piezo_cycles(uint16_t cycles) static uint8_t clamp_piezo_cycles(uint16_t cycles)
{ {
if (cycles < 3) if (cycles < PIEZO_CONFIG_CYCLES_MIN)
{ {
return 3; return (uint8_t)PIEZO_CONFIG_CYCLES_MIN;
} }
if (cycles > 7) if (cycles > PIEZO_CONFIG_CYCLES_MAX)
{ {
return 7; return (uint8_t)PIEZO_CONFIG_CYCLES_MAX;
} }
return (uint8_t)cycles; return (uint8_t)cycles;
} }
static uint16_t clamp_piezo_averaging(uint16_t averaging) static uint16_t clamp_piezo_averaging(uint16_t averaging)
{ {
if (averaging < 1) if (averaging < PIEZO_CONFIG_AVERAGING_MIN)
{ {
return 1; return PIEZO_CONFIG_AVERAGING_MIN;
} }
if (averaging > 10) if (averaging > PIEZO_CONFIG_AVERAGING_MAX)
{ {
return 10; return PIEZO_CONFIG_AVERAGING_MAX;
} }
return averaging; return averaging;
} }
static uint16_t clamp_piezo_delay_us(uint16_t delay_us) static uint16_t clamp_piezo_delay_us(uint16_t delay_us)
{ {
if (delay_us > 50) if (delay_us > PIEZO_CONFIG_DELAY_US_MAX)
{ {
return 50; return PIEZO_CONFIG_DELAY_US_MAX;
} }
return delay_us; return delay_us;
} }
static uint16_t clamp_piezo_num_samples(uint16_t num_samples) static uint16_t clamp_piezo_num_samples(uint16_t num_samples)
{ {
if (num_samples < 80) if (num_samples < PIEZO_CONFIG_NUM_SAMPLES_MIN)
{ {
return 80; return PIEZO_CONFIG_NUM_SAMPLES_MIN;
} }
if (num_samples > 119) if (num_samples > PIEZO_CONFIG_NUM_SAMPLES_MAX)
{ {
return 119; return PIEZO_CONFIG_NUM_SAMPLES_MAX;
} }
return num_samples; return num_samples;
} }
@@ -158,8 +165,8 @@ int Cmd_mpb(const ParsedCmd *cmd)
*============================================================================*/ *============================================================================*/
int Cmd_mpc(const ParsedCmd *cmd) int Cmd_mpc(const ParsedCmd *cmd)
{ {
uint16_t cycles = 5; uint16_t cycles = DR_PIEZO_DEFAULT_CYCLES;
uint16_t freq_option = 1; uint16_t freq_option = PIEZO_CONFIG_FREQ_OPTION_DEFAULT;
uint16_t piezo_ch = 0; uint16_t piezo_ch = 0;
(void)dr_get_u16(cmd, 0, &cycles); (void)dr_get_u16(cmd, 0, &cycles);
@@ -171,7 +178,7 @@ int Cmd_mpc(const ParsedCmd *cmd)
piezo_ch = 0; piezo_ch = 0;
} }
if (cycles < 3 || cycles > 7) if (cycles < PIEZO_CONFIG_CYCLES_MIN || cycles > PIEZO_CONFIG_CYCLES_MAX)
{ {
dr_ble_return_1("rpc:", 2); dr_ble_return_1("rpc:", 2);
return 1; return 1;
@@ -218,7 +225,7 @@ int Cmd_mec(const ParsedCmd *cmd)
uint16_t freq_option = 0; uint16_t freq_option = 0;
uint16_t delay_us = 20; uint16_t delay_us = 20;
uint16_t num_samples = 140; uint16_t num_samples = 140;
uint16_t cycles = 5; uint16_t cycles = DR_PIEZO_DEFAULT_CYCLES;
uint16_t averaging = 1; uint16_t averaging = 1;
uint16_t piezo_ch = 0; uint16_t piezo_ch = 0;
@@ -414,6 +421,57 @@ int Cmd_mtb(const ParsedCmd *cmd)
return 1; return 1;
} }
/*==============================================================================
* mab? -> reb:+raa: 6-channel asynchronous full capture + GR LED
*
* Request: [TAG 4B "mab?"] [CRC 2B]
* Response: per channel reb: [num_samples 2B] [raw_data...]
* final raa: [status 2B]
* Error: raa: + 0xFFFE (previous capture in progress)
* raa: + (0xFF00|err) (start failed)
*
* For B-Mode Test
*============================================================================*/
int Cmd_mab(const ParsedCmd *cmd)
{
dr_adc_err_t err;
(void)cmd;
if (maa_async_is_busy())
{
dr_ble_return_1("raa:", 0xFFFE);
return 1;
}
led_set_state(LED_STATE_ALIGN_COMPLETE);
err = maa_async_start(
m_config.piezo_freq_option,
m_config.piezo_delay_us,
m_config.piezo_num_samples,
m_config.piezo_cycles,
m_config.piezo_averaging,
ble_bin_buffer
);
if (err != DR_ADC_OK)
{
if (g_plat.log)
{
g_plat.log("[Cmd_mab] start failed err=%d\r\n", err);
}
single_format_data(ble_bin_buffer, "raa:", (uint16_t)(0xFF00 | err));
dr_binary_tx_safe(ble_bin_buffer, 3);
dr_piezo_power_off();
led_set_state(LED_STATE_OFF);
return 1;
}
maa_async_set_on_complete(mab_led_off_after_piezo);
return 1;
}
/*============================================================================== /*==============================================================================
* mcf? -> rcf: Read piezo parameters from FDS * mcf? -> rcf: Read piezo parameters from FDS
* *
@@ -12,6 +12,7 @@ int Cmd_mpc(const ParsedCmd *cmd); /* mpc? -> rpc: burst generation */
int Cmd_mec(const ParsedCmd *cmd); /* mec? -> reb:+raa: single-channel capture */ int Cmd_mec(const ParsedCmd *cmd); /* mec? -> reb:+raa: single-channel capture */
int Cmd_maa(const ParsedCmd *cmd); /* maa? -> reb:+raa: 6-channel async capture */ int Cmd_maa(const ParsedCmd *cmd); /* maa? -> reb:+raa: 6-channel async capture */
int Cmd_mbb(const ParsedCmd *cmd); /* mbb? -> rbb:+reb:+raa: sensors + capture */ int Cmd_mbb(const ParsedCmd *cmd); /* mbb? -> rbb:+reb:+raa: sensors + capture */
int Cmd_mab(const ParsedCmd *cmd); /* mab? -> reb:+raa: 6-channel async capture + green LED */
int Cmd_mtb(const ParsedCmd *cmd); /* mtb? -> reb:+raa:+rim: piezo + IMU FIFO (no rbb:) */ int Cmd_mtb(const ParsedCmd *cmd); /* mtb? -> reb:+raa:+rim: piezo + IMU FIFO (no rbb:) */
int Cmd_mcf(const ParsedCmd *cmd); /* mcf? -> rcf: read piezo parameters */ int Cmd_mcf(const ParsedCmd *cmd); /* mcf? -> rcf: read piezo parameters */
int Cmd_mcs(const ParsedCmd *cmd); /* mcs? -> rcs: write piezo parameters */ int Cmd_mcs(const ParsedCmd *cmd); /* mcs? -> rcs: write piezo parameters */
@@ -4,11 +4,13 @@
* msn? -> rsn: battery ADC measurement * msn? -> rsn: battery ADC measurement
* mst? -> rso: IMU die temperature reading * mst? -> rso: IMU die temperature reading
* msp? -> rsp: IMU 6-axis single read * msp? -> rsp: IMU 6-axis single read
* mim? -> rim: IMU FIFO 15-sample read
* all_sensors() bulk-measurement helper used by the mbb? handler * all_sensors() bulk-measurement helper used by the mbb? handler
*============================================================================*/ *============================================================================*/
#include "cmd_common.h" #include "cmd_common.h"
#include "cmd_sensor.h" #include "cmd_sensor.h"
#include "app_raw.h"
/*============================================================================== /*==============================================================================
* msn? -> rsn: Battery level ADC measurement * msn? -> rsn: Battery level ADC measurement
@@ -47,6 +49,7 @@ int Cmd_mst(const ParsedCmd *cmd)
/*============================================================================== /*==============================================================================
* msp? -> rsp: IMU 6-axis single read * msp? -> rsp: IMU 6-axis single read
* mim? -> rim: IMU FIFO 15-sample read
* *
* Request: [TAG 4B "msp?"] [CRC 2B] * Request: [TAG 4B "msp?"] [CRC 2B]
* Response: rsp: + accel(xyz) + gyro(xyz) (transmitted inside imu_read_direct) * Response: rsp: + accel(xyz) + gyro(xyz) (transmitted inside imu_read_direct)
@@ -56,11 +59,49 @@ int Cmd_mst(const ParsedCmd *cmd)
int Cmd_msp(const ParsedCmd *cmd) int Cmd_msp(const ParsedCmd *cmd)
{ {
(void)cmd; (void)cmd;
if (imu_fifo_capture_is_active())
{
return 1; // already owned by mtb?/mim?, ignore duplicate request
}
hw_i2c_init_once(); hw_i2c_init_once();
imu_read_direct(); imu_read_direct();
return 1; return 1;
} }
/*==============================================================================
* mim? -> rim: IMU FIFO-only capture
*
* Request: [TAG 4B "mim?"] [CRC 2B]
* Response: rim: [total_sample_count u16 BE]
* [samples: 12B each ax,ay,az,gx,gy,gz ...]
*
* Uses the same FIFO/rim path as mtb? but without piezo reb:/raa: packets.
* At 50 Hz, 15 samples are about 300 ms; timeout leaves margin for startup.
*============================================================================*/
int Cmd_mim(const ParsedCmd *cmd)
{
int rc;
(void)cmd;
if (imu_fifo_capture_is_active())
{
return 1; // already owned by mtb?/mim?, ignore duplicate request
}
rc = imu_fifo_capture_start();
if (rc != 0)
{
(void)imu_fifo_capture_stop_and_send_rim();
imu_fifo_capture_disable();
return 1;
}
(void)imu_fifo_capture_wait_samples_and_send_rim(15, 500);
return 1;
}
/*============================================================================== /*==============================================================================
* all_sensors() - Bulk-measurement helper for the mbb? handler * all_sensors() - Bulk-measurement helper for the mbb? handler
* *
@@ -9,6 +9,7 @@
int Cmd_msn(const ParsedCmd *cmd); /* msn? -> rsn: battery ADC measurement */ int Cmd_msn(const ParsedCmd *cmd); /* msn? -> rsn: battery ADC measurement */
int Cmd_mst(const ParsedCmd *cmd); /* mst? -> rso: IMU die temperature */ int Cmd_mst(const ParsedCmd *cmd); /* mst? -> rso: IMU die temperature */
int Cmd_msp(const ParsedCmd *cmd); /* msp? -> rsp: IMU 6-axis single read */ int Cmd_msp(const ParsedCmd *cmd); /* msp? -> rsp: IMU 6-axis single read */
int Cmd_mim(const ParsedCmd *cmd); /* mim? -> rim: IMU FIFO 15 samples */
/* Helper for the mbb? handler: sequentially measures battery / IMU / temperature, then emits a single rbb: response. /* Helper for the mbb? handler: sequentially measures battery / IMU / temperature, then emits a single rbb: response.
* Called from Cmd_mbb() in cmd_piezo.c. */ * Called from Cmd_mbb() in cmd_piezo.c. */
void all_sensors(void); void all_sensors(void);
@@ -0,0 +1,66 @@
@echo off
chcp 437
setlocal EnableExtensions
cd /d "%~dp0"
set "APP_HEX=medithings_bladder_patch_0001.hex"
set "BOOT_HEX=medithings_bladder_patch_bootloader.hex"
set "OUT_APP_ZIP=medithings_bladder_patch_dfu.zip"
set "OUT_BOOT_ZIP=medithings_bladder_patch_bootloader_dfu.zip"
REM Bootloader DFU requires a version greater than the currently installed bootloader_version.
REM cpd/cpd_eraseALL currently install bootloader-version 1, so use 2 for this update.
set "APP_VERSION=1"
set "BOOTLOADER_VERSION=2"
set "HW_VERSION=52"
set "SD_REQ=0x0100"
set "SD_ID=0x0100"
echo [1/4] Copying application HEX...
copy /Y "..\pca10056\s140\arm5_no_packs\_build\nrf52840_xxaa.hex" "%APP_HEX%"
if errorlevel 1 goto fail
echo [2/4] Copying bootloader HEX...
copy /Y "..\..\..\dfu\secure_bootloader\pca10056_s140_ble\arm5_no_packs\_build\nrf52840_xxaa_s140.hex" "%BOOT_HEX%"
if errorlevel 1 goto fail
echo [3/4] Generating application DFU package...
nrfutil pkg generate ^
--application "%APP_HEX%" ^
--application-version %APP_VERSION% ^
--hw-version %HW_VERSION% ^
--sd-req %SD_REQ% ^
--sd-id %SD_ID% ^
--key-file private.key ^
"%OUT_APP_ZIP%"
if errorlevel 1 goto fail
echo [4/4] Generating bootloader DFU package...
nrfutil pkg generate ^
--bootloader "%BOOT_HEX%" ^
--bootloader-version %BOOTLOADER_VERSION% ^
--hw-version %HW_VERSION% ^
--sd-req %SD_REQ% ^
--key-file private.key ^
"%OUT_BOOT_ZIP%"
if errorlevel 1 goto fail
echo.
echo Done:
echo %OUT_APP_ZIP%
echo %OUT_BOOT_ZIP%
echo Bootloader version: %BOOTLOADER_VERSION%
echo.
echo Update order for both app and bootloader changes:
echo 1. Send %OUT_BOOT_ZIP%
echo 2. Power on again if the device powers off
echo 3. Send %OUT_APP_ZIP%
pause
exit /b 0
:fail
echo.
echo ERROR: Failed to generate DFU package(s).
pause
exit /b 1
@@ -40,6 +40,7 @@
#include "nrf_pwr_mgmt.h" #include "nrf_pwr_mgmt.h"
#include "main.h" #include "main.h"
#include "debug_print.h" #include "debug_print.h"
#include "../../measurement/piezo/piezo_config.h"
/* FDS record identifiers */ /* FDS record identifiers */
@@ -107,11 +108,11 @@ void fds_default_value_set(void)
m_config.life_cycle = 0; m_config.life_cycle = 0;
/* Piezo measurement parameter defaults */ /* Piezo measurement parameter defaults */
m_config.piezo_freq_option = 1; /* 2.1 MHz */ m_config.piezo_freq_option = PIEZO_CONFIG_FREQ_OPTION_DEFAULT; /* 2.1 MHz */
m_config.piezo_delay_us = 10; /* 10 us after burst */ m_config.piezo_delay_us = PIEZO_CONFIG_DELAY_US_DEFAULT; /* 10 us after burst */
m_config.piezo_num_samples = 100; /* 100 samples */ m_config.piezo_num_samples = PIEZO_CONFIG_NUM_SAMPLES_DEFAULT; /* 100 samples */
m_config.piezo_cycles = 3; /* 7 cycles */ m_config.piezo_cycles = PIEZO_CONFIG_CYCLES_DEFAULT; /* 3 cycles */
m_config.piezo_averaging = 3; /* 3x averaging */ m_config.piezo_averaging = PIEZO_CONFIG_AVERAGING_DEFAULT; /* 3x averaging */
/* Factory provisioning — default: not provisioned */ /* Factory provisioning — default: not provisioned */
m_config.factory_provisioned = 0; m_config.factory_provisioned = 0;
@@ -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
*============================================================================*/ *============================================================================*/
@@ -157,6 +181,7 @@
#define POWER_OFF_DELAY 3000 /* Power-off delay: 3s after LED indication */ #define POWER_OFF_DELAY 3000 /* Power-off delay: 3s after LED indication */
#define POWER_RESET_DELAY 2000 /* Reset delay: 2s */ #define POWER_RESET_DELAY 2000 /* Reset delay: 2s */
#define LED_NUM 24 /* LED pin number */ #define LED_NUM 24 /* LED pin number */
#define POST_DFU_APP_BOOT_GPREGRET2_MASK 0x02U /* Bootloader marker after DFU activation */
/*============================================================================== /*==============================================================================
* BLE Instances (statically allocated via SoftDevice macros) * BLE Instances (statically allocated via SoftDevice macros)
@@ -172,6 +197,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 +209,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 */
@@ -194,7 +231,7 @@ static char * roles_str[] = {"INVALID_ROLE", "CENTRAL", "PERIPHERAL"};
/*============================================================================== /*==============================================================================
* Global Variables * Global Variables
* IEC 62304 §5.5.3: all global buffers zero-initialized for deterministic startup * IEC 62304 5.5.3: all global buffers zero-initialized for deterministic startup
*============================================================================*/ *============================================================================*/
volatile uint8_t Sj_type; /* Command type identifier */ volatile uint8_t Sj_type; /* Command type identifier */
bool power_off_duble_prohibit = false; /* Power-off double prevention flag */ bool power_off_duble_prohibit = false; /* Power-off double prevention flag */
@@ -228,7 +265,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 +432,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 +479,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 +492,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 +553,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 +605,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 +671,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 +1104,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 +1467,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 +1510,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;
@@ -1143,7 +1546,7 @@ static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
DBG_PRINTF("[BLE] Connected\r\n"); DBG_PRINTF("[BLE] Connected\r\n");
#if BLE_DEV_MODE #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). */ /* 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; ble_connection_st = BLE_CONNECTED_ST;
battery_timer_start(); battery_timer_start();
#endif #endif
@@ -1152,7 +1555,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 +1573,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 +1629,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 +1832,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 +2015,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 +2024,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 +2035,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)
@@ -1666,6 +2077,7 @@ static void main_s(void * p_context)
bool button_released = nrf_gpio_pin_read(POWER_BUTTON); bool button_released = nrf_gpio_pin_read(POWER_BUTTON);
/* ---- Running phase: post-boot button polling ---- */ /* ---- Running phase: post-boot button polling ---- */
if (booted) if (booted)
{ {
@@ -1710,7 +2122,7 @@ static void main_s(void * p_context)
go_device_power_off = true; go_device_power_off = true;
main_timer_start(); main_timer_start();
} }
else if (cnt_s > 250 || (m_reset_status == 2)) /* 250 x 5ms = 1.25s + α */ else if (cnt_s > 250 || (m_reset_status == 2)) /* 250 x 5ms = 1.25s + ¥á */
{ {
DBG_PRINTF("[BTN] Boot (cnt=%d)\r\n", cnt_s); DBG_PRINTF("[BTN] Boot (cnt=%d)\r\n", cnt_s);
device_reset = false; device_reset = false;
@@ -1739,7 +2151,7 @@ static void main_s(void * p_context)
cnt_s++; cnt_s++;
device_reset = false; device_reset = false;
if (cnt_s == 250) /* 250 x 5ms = 1.25s + α */ if (cnt_s == 250) /* 250 x 5ms = 1.25s + ¥á */
{ {
led_set_state(LED_STATE_POWER_ON); led_set_state(LED_STATE_POWER_ON);
DBG_PRINTF("[BTN] 2.0s\r\n"); DBG_PRINTF("[BTN] 2.0s\r\n");
@@ -1760,7 +2172,7 @@ int main(void)
{ {
bool erase_bonds_local = false; bool erase_bonds_local = false;
/*────────────────────────────────────────────────────────────── /*--------------------------------------------------------------------------
* Phase 1: Basic hardware init (BLE-independent) * Phase 1: Basic hardware init (BLE-independent)
* *
* - Power self-latch (P0.8 HIGH) * - Power self-latch (P0.8 HIGH)
@@ -1769,9 +2181,20 @@ int main(void)
* - App timers (power polling, battery, main loop) * - App timers (power polling, battery, main loop)
* - Default config (serial number, passkey) * - Default config (serial number, passkey)
* - Buttons/LEDs * - Buttons/LEDs
**/ *--------------------------------------------------------------------------*/
power_hold_init(); power_hold_init();
if ((NRF_POWER->GPREGRET2 & POST_DFU_APP_BOOT_GPREGRET2_MASK) != 0)
{
NRF_POWER->GPREGRET2 &= ~POST_DFU_APP_BOOT_GPREGRET2_MASK;
NRF_P0->OUTCLR = (1UL << 8);
NRF_P0->DIRSET = (1UL << 8);
while (true)
{
__WFE();
}
}
if (power_off_duble_prohibit) if (power_off_duble_prohibit)
{ {
return 0; return 0;
@@ -1781,7 +2204,7 @@ int main(void)
log_init(); log_init();
DBG_PRINTF("\r\n========================================\r\n"); DBG_PRINTF("\r\n========================================\r\n");
DBG_PRINTF(" Medithings v1.17\r\n"); DBG_PRINTF(" Medithings VesiScan-Basic\r\n");
DBG_PRINTF("========================================\r\n"); DBG_PRINTF("========================================\r\n");
DBG_PRINTF("[1] HW Init\r\n"); DBG_PRINTF("[1] HW Init\r\n");
@@ -1795,33 +2218,33 @@ int main(void)
#endif #endif
DBG_PRINTF(" gpio/timer/config/btn OK\r\n"); DBG_PRINTF(" gpio/timer/config/btn OK\r\n");
/*────────────────────────────────────────────────────────────── /*--------------------------------------------------------------------------
* Phase 2: BLE stack init * Phase 2: BLE stack init
* *
* - Power management module (WFE/sleep when idle) * - Power management module (WFE/sleep when idle)
* - SoftDevice S140 (BLE 5.0 protocol stack) * - SoftDevice S140 (BLE 5.0 protocol stack)
* - DC-DC converter enable (required after SoftDevice) * - DC-DC converter enable (required after SoftDevice)
**/ *--------------------------------------------------------------------------*/
DBG_PRINTF("[2] BLE Stack\r\n"); DBG_PRINTF("[2] BLE Stack\r\n");
power_management_init(); power_management_init();
ble_stack_init(); ble_stack_init();
APP_ERROR_CHECK(sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE)); APP_ERROR_CHECK(sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE));
DBG_PRINTF(" pwr/stack/dcdc OK\r\n"); DBG_PRINTF(" pwr/stack/dcdc OK\r\n");
/*────────────────────────────────────────────────────────────── /*--------------------------------------------------------------------------
* Phase 3: Internal flash config (must init after BLE stack) * Phase 3: Internal flash config (must init after BLE stack)
* *
* - FDS (Flash Data Storage) init * - FDS (Flash Data Storage) init
* - Read stored config from flash (serial, passkey, piezo, etc.) * - Read stored config from flash (serial, passkey, piezo, etc.)
* - Overwrite defaults with flash values * - Overwrite defaults with flash values
**/ *--------------------------------------------------------------------------*/
DBG_PRINTF("[3] FDS\r\n"); DBG_PRINTF("[3] FDS\r\n");
fs_storage_init(); fs_storage_init();
config_load(); config_load();
load_flash_config(); load_flash_config();
DBG_PRINTF(" fds OK\r\n"); DBG_PRINTF(" fds OK\r\n");
/*────────────────────────────────────────────────────────────── /*--------------------------------------------------------------------------
* Phase 4: BLE protocol setup * Phase 4: BLE protocol setup
* *
* - GAP: device name (serial number), connection params, passkey * - GAP: device name (serial number), connection params, passkey
@@ -1830,7 +2253,7 @@ int main(void)
* - Advertising: advertising data (name, UUID) * - Advertising: advertising data (name, UUID)
* - Connection parameter negotiation module * - Connection parameter negotiation module
* - Security: Peer Manager (bonding/passkey) * - Security: Peer Manager (bonding/passkey)
**/ *--------------------------------------------------------------------------*/
DBG_PRINTF("[4] BLE Protocol\r\n"); DBG_PRINTF("[4] BLE Protocol\r\n");
gap_params_init(); gap_params_init();
gatt_init(); gatt_init();
@@ -1865,14 +2288,14 @@ int main(void)
DBG_PRINTF(" fds defaults saved\r\n"); DBG_PRINTF(" fds defaults saved\r\n");
} }
/*────────────────────────────────────────────────────────────── /*--------------------------------------------------------------------------
* Phase 5: Application init * Phase 5: Application init
* *
* - Command parser: process BLE received commands (log, BLE TX, CRC) * - Command parser: process BLE received commands (log, BLE TX, CRC)
* - Piezo driver: GPIO/Timer/PPI setup for ultrasound measurement * - Piezo driver: GPIO/Timer/PPI setup for ultrasound measurement
* - IMU (ICM42670P) is NOT initialized here * - IMU (ICM42670P) is NOT initialized here
* -> imu_read_direct() self-configures/reads/sleeps on each msp? command * -> imu_read_direct() self-configures/reads/sleeps on each msp? command
**/ *--------------------------------------------------------------------------*/
DBG_PRINTF("[5] App\r\n"); DBG_PRINTF("[5] App\r\n");
g_plat.log = log_printf; g_plat.log = log_printf;
g_plat.tx_bin = dr_binary_tx_safe; g_plat.tx_bin = dr_binary_tx_safe;
@@ -1882,9 +2305,9 @@ int main(void)
dr_piezo_init(); dr_piezo_init();
DBG_PRINTF(" parser/piezo OK\r\n"); DBG_PRINTF(" parser/piezo OK\r\n");
/*────────────────────────────────────────────────────────────── /*--------------------------------------------------------------------------
* Boot complete -> start power button state machine * Boot complete -> start power button state machine
**/ *--------------------------------------------------------------------------*/
DBG_PRINTF("\r\n========================================\r\n"); DBG_PRINTF("\r\n========================================\r\n");
DBG_PRINTF(" READY [%s]\r\n", SERIAL_NO); DBG_PRINTF(" READY [%s]\r\n", SERIAL_NO);
DBG_PRINTF("========================================\r\n\r\n"); DBG_PRINTF("========================================\r\n\r\n");
@@ -48,9 +48,15 @@
* - VBTFW0118 260601 jhChun * - VBTFW0118 260601 jhChun
* : Added channel information(2B) to the ADC raw data response packet (reb:). * : Added channel information(2B) to the ADC raw data response packet (reb:).
* : Replaced TMP235 temperature with IMU register direct-read temperature in the rbb: response. * : Replaced TMP235 temperature with IMU register direct-read temperature in the rbb: response.
* - VBTFW0119 260615 jhChun : Replaced all TMP235 temperature paths with IMU register direct-read temperature and removed legacy TMP235 build references. * - VBTFW0119 260615 jhChun
* : Replaced all TMP235 temperature paths with IMU register direct-read temperature and removed legacy TMP235 build references.
* : Unify both IMU direct-read and FIFO outputs as raw data in datasheet axis convention.
* : Added mim? command (rim:, IMU FIFO-only 15 samples at 50 Hz).
* - VBTFW0120 260615 jhChun : Add EMC mitigation logic for BLE link stability: extended supervision timeout and dynamic TX power control based on RSSI, RSSI silence, HVN latency, and TX queue congestion.
* - VBTFW0121 260703 jhChun : Add mim? cammand.
* - VBTFW0122 260708 jhChun : Prevent IMU FIFO reentry and add MCLK ready timeout.
------------------------------------------------------------------------- */ ------------------------------------------------------------------------- */
#define FIRMWARE_VERSION "VBTFW0119" #define FIRMWARE_VERSION "VBTFW0122"
/*============================================================================== /*==============================================================================
* Data Length Constants * Data Length Constants
@@ -30,6 +30,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "nrf_gpio.h" #include "nrf_gpio.h"
#include "../piezo/piezo_config.h"
/*============================================================================== /*==============================================================================
* PIN CONFIGURATION * PIN CONFIGURATION
@@ -73,8 +74,8 @@
*============================================================================*/ *============================================================================*/
#define DR_ADC_SCLK_MHZ 8.6f /**< SPI bit-bang SCLK frequency */ #define DR_ADC_SCLK_MHZ 8.6f /**< SPI bit-bang SCLK frequency */
#define DR_ADC_CLOCKS_PER_SAMPLE 16 /**< ADC121S051: 16 SCLK per sample */ #define DR_ADC_CLOCKS_PER_SAMPLE 16 /**< ADC121S051: 16 SCLK per sample */
#define DR_ADC_ECHO_SAMPLES_MAX 119 /**< Maximum samples */ #define DR_ADC_ECHO_SAMPLES_MAX PIEZO_CONFIG_NUM_SAMPLES_MAX /**< Maximum samples */
#define DR_ADC_ECHO_SAMPLES_DEFAULT 100 /**< Default samples */ #define DR_ADC_ECHO_SAMPLES_DEFAULT PIEZO_CONFIG_NUM_SAMPLES_DEFAULT /**< Default samples */
#define DR_ADC_SAMPLE_INTERVAL_US 1.86f /**< 16 / 8.6MHz = 1.86us per sample */ #define DR_ADC_SAMPLE_INTERVAL_US 1.86f /**< 16 / 8.6MHz = 1.86us per sample */
#define DR_ADC_SOUND_SPEED_MM_US 1.54f /**< Sound speed in tissue (mm/us) */ #define DR_ADC_SOUND_SPEED_MM_US 1.54f /**< Sound speed in tissue (mm/us) */
@@ -183,6 +183,11 @@ void battery_event_handler(nrf_drv_saadc_evt_t const * p_event)
low_battery_check = false; low_battery_check = false;
safety_batt_mv = batt_lvl_in_milli_volt_1; safety_batt_mv = batt_lvl_in_milli_volt_1;
if (imu_fifo_capture_is_active())
{
return;
}
if (imu_read_temperature_x100(NULL, &imu_temp_c) != 0) if (imu_read_temperature_x100(NULL, &imu_temp_c) != 0)
{ {
DBG_PRINTF("[SAFETY] IMU temp read failed\r\n"); DBG_PRINTF("[SAFETY] IMU temp read failed\r\n");
@@ -101,6 +101,7 @@ int inv_imu_switch_on_mclk(struct inv_imu_device *s)
int status = 0; int status = 0;
uint8_t data; uint8_t data;
struct inv_imu_transport *t = (struct inv_imu_transport *)s; struct inv_imu_transport *t = (struct inv_imu_transport *)s;
uint64_t start;
/* set IDLE bit only if it is not set yet */ /* set IDLE bit only if it is not set yet */
if (t->need_mclk_cnt == 0) { if (t->need_mclk_cnt == 0) {
@@ -112,9 +113,23 @@ int inv_imu_switch_on_mclk(struct inv_imu_device *s)
if (status) if (status)
return status; return status;
start = inv_imu_get_time_us();
/* Check if MCLK is ready */ /* Check if MCLK is ready */
do { do {
status = inv_imu_read_reg(s, MCLK_RDY, 1, &data); status = inv_imu_read_reg(s, MCLK_RDY, 1, &data);
/* Bound the MCLK wait so a bad IMU/I2C state cannot hang the main loop. */
if ((inv_imu_get_time_us() - start) >= 50000U)
{
status = 0;
if (inv_imu_read_reg(s, PWR_MGMT0, 1, &data) == 0)
{
data &= ~PWR_MGMT0_IDLE_MASK;
(void)inv_imu_write_reg(s, PWR_MGMT0, 1, &data);
}
return INV_ERROR_TIMEOUT;
}
} while ((status != 0) || !(data & MCLK_RDY_MCLK_RDY_MASK)); } while ((status != 0) || !(data & MCLK_RDY_MCLK_RDY_MASK));
} else { } else {
@@ -385,7 +385,7 @@ void imu_callback(inv_imu_sensor_event_t *event)
* Flow: * Flow:
* 1) Check TWI initialization (first call only) * 1) Check TWI initialization (first call only)
* 2) Gyro config: +/-2000dps, 100Hz ODR (GYRO_CONFIG0 = 0x09) * 2) Gyro config: +/-2000dps, 100Hz ODR (GYRO_CONFIG0 = 0x09)
* 3) Accel config: +/-4g, 100Hz ODR (ACCEL_CONFIG0 = 0x29) * 3) Accel config: +/-4g, 100Hz ODR (ACCEL_CONFIG0 = 0x49)
* 4) Power ON: accel+gyro low-noise mode (PWR_MGMT0 = 0x0F) * 4) Power ON: accel+gyro low-noise mode (PWR_MGMT0 = 0x0F)
* 5) Wait 80ms (gyro startup: min 45ms + margin) * 5) Wait 80ms (gyro startup: min 45ms + margin)
* 6) Read 14 consecutive bytes from TEMP_DATA1 (0x09) (temp 2 + accel 6 + gyro 6) * 6) Read 14 consecutive bytes from TEMP_DATA1 (0x09) (temp 2 + accel 6 + gyro 6)
@@ -407,6 +407,7 @@ extern const nrfx_twi_t m_twi_icm42670;
#define IMU_TEMP_AVG_SAMPLES 4U #define IMU_TEMP_AVG_SAMPLES 4U
static bool s_direct_twi_ready = false; static bool s_direct_twi_ready = false;
static bool s_fifo_capture_active = false;
static void imu_direct_twi_init_once(void) static void imu_direct_twi_init_once(void)
{ {
@@ -447,9 +448,9 @@ int imu_read_direct(void)
icm42670_twi_tx(IMU_I2C_ADDR, gyro_cfg, 2, false); icm42670_twi_tx(IMU_I2C_ADDR, gyro_cfg, 2, false);
} }
/* Accel config: ACCEL_CONFIG0(0x21) = 0x29 -> +/-4g FSR, 100Hz ODR */ /* Accel config: ACCEL_CONFIG0(0x21) = 0x49 -> +/-4g FSR, 100Hz ODR */
{ {
uint8_t accel_cfg[2] = { 0x21, 0x29 }; uint8_t accel_cfg[2] = { 0x21, 0x49 };
icm42670_twi_tx(IMU_I2C_ADDR, accel_cfg, 2, false); icm42670_twi_tx(IMU_I2C_ADDR, accel_cfg, 2, false);
} }
@@ -569,6 +570,11 @@ int imu_read_temperature_x100(uint16_t *temp_x100, float *temp_c)
return -1; return -1;
} }
if (s_fifo_capture_active)
{
return -4;
}
imu_direct_twi_init_once(); imu_direct_twi_init_once();
/* Power ON briefly so the temperature register is refreshed before reading. */ /* Power ON briefly so the temperature register is refreshed before reading. */
@@ -619,6 +625,11 @@ int imu_read_temperature_x100(uint16_t *temp_x100, float *temp_c)
return 0; return 0;
} }
bool imu_fifo_capture_is_active(void)
{
return s_fifo_capture_active;
}
/* -------------------------------------------------------------------------------------- /* --------------------------------------------------------------------------------------
* mtb? FIFO capture support * mtb? FIFO capture support
* *
@@ -642,8 +653,6 @@ int imu_read_temperature_x100(uint16_t *temp_x100, float *temp_c)
#define RIM_MAX_SAMPLE_BYTES (BLE_NUS_MAX_DATA_LEN - 2 - RIM_PACKET_HEADER_BYTES) #define RIM_MAX_SAMPLE_BYTES (BLE_NUS_MAX_DATA_LEN - 2 - RIM_PACKET_HEADER_BYTES)
#define RIM_SAMPLES_PER_PACKET (RIM_MAX_SAMPLE_BYTES / RIM_SAMPLE_SIZE_BYTES) #define RIM_SAMPLES_PER_PACKET (RIM_MAX_SAMPLE_BYTES / RIM_SAMPLE_SIZE_BYTES)
static bool s_fifo_capture_active = false;
static void imu_serif_make(struct inv_imu_serif *serif) static void imu_serif_make(struct inv_imu_serif *serif)
{ {
serif->context = 0; serif->context = 0;
@@ -675,6 +684,14 @@ static void imu_fifo_power_off(void)
s_fifo_capture_active = false; s_fifo_capture_active = false;
} }
void imu_fifo_capture_disable(void)
{
if (s_fifo_capture_active)
{
imu_fifo_power_off();
}
}
int imu_fifo_capture_start(void) int imu_fifo_capture_start(void)
{ {
int rc; int rc;
@@ -693,8 +710,8 @@ int imu_fifo_capture_start(void)
rc |= inv_imu_set_accel_fsr(&icm_driver, ACCEL_CONFIG0_FS_SEL_4g); rc |= inv_imu_set_accel_fsr(&icm_driver, ACCEL_CONFIG0_FS_SEL_4g);
rc |= inv_imu_set_gyro_fsr(&icm_driver, GYRO_CONFIG0_FS_SEL_500dps); rc |= inv_imu_set_gyro_fsr(&icm_driver, GYRO_CONFIG0_FS_SEL_500dps);
rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_50_HZ); rc |= inv_imu_set_accel_frequency(&icm_driver, ACCEL_CONFIG0_ODR_50_HZ); // FIFO ODR Accel 50Hz Setting
rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_50_HZ); rc |= inv_imu_set_gyro_frequency(&icm_driver, GYRO_CONFIG0_ODR_50_HZ); // FIFO ODR Gyro 50Hz Setting
rc |= inv_imu_set_accel_ln_bw(&icm_driver, IMU_FIFO_MTB_ACCEL_LN_BW); rc |= inv_imu_set_accel_ln_bw(&icm_driver, IMU_FIFO_MTB_ACCEL_LN_BW);
rc |= inv_imu_set_gyro_ln_bw(&icm_driver, IMU_FIFO_MTB_GYRO_LN_BW); rc |= inv_imu_set_gyro_ln_bw(&icm_driver, IMU_FIFO_MTB_GYRO_LN_BW);
rc |= inv_imu_disable_high_resolution_fifo(&icm_driver); rc |= inv_imu_disable_high_resolution_fifo(&icm_driver);
@@ -707,8 +724,9 @@ int imu_fifo_capture_start(void)
rc |= inv_imu_write_reg(&icm_driver, FIFO_CONFIG1, 1, &fifo_cfg1); rc |= inv_imu_write_reg(&icm_driver, FIFO_CONFIG1, 1, &fifo_cfg1);
} }
rc |= inv_imu_reset_fifo(&icm_driver); rc |= inv_imu_reset_fifo(&icm_driver);
rc |= inv_imu_enable_accel_low_noise_mode(&icm_driver); rc |= inv_imu_enable_accel_low_noise_mode(&icm_driver); // FIFO Accel Low Noise Mode
rc |= inv_imu_enable_gyro_low_noise_mode(&icm_driver); //rc |= inv_imu_enable_accel_low_power_mode(&icm_driver); // FIFO Accel Low Power Mode TEST
rc |= inv_imu_enable_gyro_low_noise_mode(&icm_driver); // FIFO Gyro Low Noise Mode
dr_sd_delay_ms(IMU_FIFO_ENABLE_SETTLE_MS); dr_sd_delay_ms(IMU_FIFO_ENABLE_SETTLE_MS);
rc |= inv_imu_reset_fifo(&icm_driver); rc |= inv_imu_reset_fifo(&icm_driver);
@@ -961,9 +979,55 @@ int imu_fifo_capture_stop_and_send_rim(void)
{ {
DBG_PRINTF("[IMU FIFO] drain fail %d\r\n", rc); DBG_PRINTF("[IMU FIFO] drain fail %d\r\n", rc);
imu_fifo_send_rim_packets(0); imu_fifo_send_rim_packets(0);
imu_fifo_power_off();
return rc; return rc;
} }
imu_fifo_send_rim_packets(packet_count); imu_fifo_send_rim_packets(packet_count);
return 0; return 0;
} }
int imu_fifo_capture_wait_samples_and_send_rim(uint16_t target_samples, uint32_t timeout_ms)
{
int rc = 0;
uint8_t count_raw[2] = {0};
uint16_t packet_count = 0;
uint32_t waited_ms = 0;
const uint32_t poll_ms = 10U;
if (target_samples == 0U)
{
return imu_fifo_capture_stop_and_send_rim();
}
if (!s_fifo_capture_active)
{
imu_fifo_send_rim_packets(0);
return -1;
}
while (waited_ms <= timeout_ms)
{
rc = inv_imu_read_reg(&icm_driver, FIFO_COUNTH, 2, count_raw);
if (rc != 0)
{
DBG_PRINTF("[IMU FIFO] count read fail %d\r\n", rc);
imu_fifo_send_rim_packets(0);
imu_fifo_power_off();
return rc;
}
packet_count = (uint16_t)count_raw[0] | ((uint16_t)count_raw[1] << 8);
if (packet_count > target_samples)
{
break;
}
dr_sd_delay_ms(poll_ms);
waited_ms += poll_ms;
}
rc = imu_fifo_capture_stop_and_send_rim();
imu_fifo_capture_disable();
return rc;
}
@@ -21,6 +21,7 @@
#ifndef _APP_RAW_H_ #ifndef _APP_RAW_H_
#define _APP_RAW_H_ #define _APP_RAW_H_
#include <stdbool.h>
#include "sdk_config.h" #include "sdk_config.h"
#include <stdint.h> #include <stdint.h>
@@ -105,6 +106,11 @@ int imu_read_direct(void);
*/ */
int imu_read_temperature_x100(uint16_t *temp_x100, float *temp_c); int imu_read_temperature_x100(uint16_t *temp_x100, float *temp_c);
/**
* \brief Return true while IMU FIFO capture is active.
*/
bool imu_fifo_capture_is_active(void);
/** /**
* \brief Start IMU internal FIFO capture for mtb? test flow. * \brief Start IMU internal FIFO capture for mtb? test flow.
* Configures accel/gyro 100 Hz and flushes FIFO before capture. * Configures accel/gyro 100 Hz and flushes FIFO before capture.
@@ -116,6 +122,18 @@ int imu_fifo_capture_start(void);
*/ */
int imu_fifo_capture_stop_and_send_rim(void); int imu_fifo_capture_stop_and_send_rim(void);
/**
* \brief Wait until at least target_samples FIFO records are available, then drain
* FIFO and send rim: packets.
* \return 0=success, negative=error
*/
int imu_fifo_capture_wait_samples_and_send_rim(uint16_t target_samples, uint32_t timeout_ms);
/**
* \brief Stop IMU FIFO capture without sending rim: packets.
*/
void imu_fifo_capture_disable(void);
/* /*
* mtb? / rim: binary layout (every BLE fragment) * mtb? / rim: binary layout (every BLE fragment)
* [ 'r' 'i' 'm' ':' ] [ total_sample_count u16 BE ] [ 12 * total_sample_count bytes ... ] * [ 'r' 'i' 'm' ':' ] [ total_sample_count u16 BE ] [ 12 * total_sample_count bytes ... ]
@@ -32,6 +32,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include "nrf_gpio.h" #include "nrf_gpio.h"
#include "piezo_config.h"
/*============================================================================== /*==============================================================================
* Power control pin (+/-20V DC/DC converter) * Power control pin (+/-20V DC/DC converter)
@@ -64,9 +65,9 @@
* Configuration * Configuration
*============================================================================*/ *============================================================================*/
#define DR_PIEZO_FREQ_HZ 2100000 /**< Target frequency (set PIEZO_FREQ_MHZ in .c) */ #define DR_PIEZO_FREQ_HZ 2100000 /**< Target frequency (set PIEZO_FREQ_MHZ in .c) */
#define DR_PIEZO_DEFAULT_CYCLES 5 /**< Default burst cycles */ #define DR_PIEZO_DEFAULT_CYCLES PIEZO_CONFIG_CYCLES_DEFAULT /**< Default burst cycles */
#define DR_PIEZO_MIN_CYCLES 3 #define DR_PIEZO_MIN_CYCLES PIEZO_CONFIG_CYCLES_MIN
#define DR_PIEZO_MAX_CYCLES 7 #define DR_PIEZO_MAX_CYCLES PIEZO_CONFIG_CYCLES_MAX
#define DR_PIEZO_MUX_SETTLING_US 1300 /**< MUX settling delay (us) */ #define DR_PIEZO_MUX_SETTLING_US 1300 /**< MUX settling delay (us) */
/*============================================================================== /*==============================================================================
@@ -0,0 +1,29 @@
/*==============================================================================
* piezo_config.h - Shared piezo measurement parameter limits/defaults
*============================================================================*/
#ifndef PIEZO_CONFIG_H
#define PIEZO_CONFIG_H
/* Frequency option protocol:
* 0=1.8MHz, 1=2.1MHz, 2=2.0MHz, 3=1.7MHz, 4=2.2MHz, 9=1.9MHz
*/
#define PIEZO_CONFIG_FREQ_OPTION_DEFAULT 1U
#define PIEZO_CONFIG_CYCLES_MIN 3U
#define PIEZO_CONFIG_CYCLES_MAX 7U
#define PIEZO_CONFIG_CYCLES_DEFAULT 7U
#define PIEZO_CONFIG_AVERAGING_MIN 1U
#define PIEZO_CONFIG_AVERAGING_MAX 10U
#define PIEZO_CONFIG_AVERAGING_DEFAULT 10U
#define PIEZO_CONFIG_DELAY_US_MIN 0U
#define PIEZO_CONFIG_DELAY_US_MAX 50U
#define PIEZO_CONFIG_DELAY_US_DEFAULT 10U
#define PIEZO_CONFIG_NUM_SAMPLES_MIN 80U
#define PIEZO_CONFIG_NUM_SAMPLES_MAX 118U
#define PIEZO_CONFIG_NUM_SAMPLES_DEFAULT 100U
#endif /* PIEZO_CONFIG_H */
@@ -32,6 +32,8 @@ NOTICE: This file has been modified by Nordic Semiconductor ASA.
#include "system_nrf52_approtect.h" #include "system_nrf52_approtect.h"
#define __SYSTEM_CLOCK_64M (64000000UL) #define __SYSTEM_CLOCK_64M (64000000UL)
#define POWER_HOLD_PIN 8UL
#define POWER_HOLD_PIN_MASK (1UL << POWER_HOLD_PIN)
#if defined ( __CC_ARM ) #if defined ( __CC_ARM )
@@ -89,6 +91,15 @@ void SystemCoreClockUpdate(void)
void SystemInit(void) void SystemInit(void)
{ {
/* Keep the external power latch asserted immediately after reset. */
NRF_P0->OUTSET = POWER_HOLD_PIN_MASK;
NRF_P0->DIRSET = POWER_HOLD_PIN_MASK;
NRF_P0->PIN_CNF[POWER_HOLD_PIN] =
(GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
(GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos) |
(GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) |
(GPIO_PIN_CNF_DRIVE_H0H1 << GPIO_PIN_CNF_DRIVE_Pos) |
(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
/* Enable SWO trace functionality. If ENABLE_SWO is not defined, SWO pin will be used as GPIO (see Product /* Enable SWO trace functionality. If ENABLE_SWO is not defined, SWO pin will be used as GPIO (see Product
Specification to see which one). */ Specification to see which one). */
#if defined (ENABLE_SWO) && defined(CLOCK_TRACECONFIG_TRACEMUX_Pos) #if defined (ENABLE_SWO) && defined(CLOCK_TRACECONFIG_TRACEMUX_Pos)
+2
View File
@@ -76,6 +76,8 @@ static void dfu_observer(nrf_dfu_evt_type_t evt_type)
break; break;
case NRF_DFU_EVT_DFU_STARTED: case NRF_DFU_EVT_DFU_STARTED:
break; break;
case NRF_DFU_EVT_DFU_COMPLETED:
break;
default: default:
break; break;
} }
@@ -32,6 +32,8 @@ NOTICE: This file has been modified by Nordic Semiconductor ASA.
#include "system_nrf52_approtect.h" #include "system_nrf52_approtect.h"
#define __SYSTEM_CLOCK_64M (64000000UL) #define __SYSTEM_CLOCK_64M (64000000UL)
#define POWER_HOLD_PIN 8UL
#define POWER_HOLD_PIN_MASK (1UL << POWER_HOLD_PIN)
#if defined ( __CC_ARM ) #if defined ( __CC_ARM )
@@ -89,6 +91,15 @@ void SystemCoreClockUpdate(void)
void SystemInit(void) void SystemInit(void)
{ {
/* Keep the external power latch asserted immediately after reset. */
NRF_P0->OUTSET = POWER_HOLD_PIN_MASK;
NRF_P0->DIRSET = POWER_HOLD_PIN_MASK;
NRF_P0->PIN_CNF[POWER_HOLD_PIN] =
(GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) |
(GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos) |
(GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) |
(GPIO_PIN_CNF_DRIVE_H0H1 << GPIO_PIN_CNF_DRIVE_Pos) |
(GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos);
/* Enable SWO trace functionality. If ENABLE_SWO is not defined, SWO pin will be used as GPIO (see Product /* Enable SWO trace functionality. If ENABLE_SWO is not defined, SWO pin will be used as GPIO (see Product
Specification to see which one). */ Specification to see which one). */
#if defined (ENABLE_SWO) && defined(CLOCK_TRACECONFIG_TRACEMUX_Pos) #if defined (ENABLE_SWO) && defined(CLOCK_TRACECONFIG_TRACEMUX_Pos)
@@ -73,7 +73,7 @@
<LExpSel>0</LExpSel> <LExpSel>0</LExpSel>
</OPTXL> </OPTXL>
<OPTFL> <OPTFL>
<tvExp>0</tvExp> <tvExp>1</tvExp>
<tvExpOptDlg>0</tvExpOptDlg> <tvExpOptDlg>0</tvExpOptDlg>
<IsCurrentTarget>1</IsCurrentTarget> <IsCurrentTarget>1</IsCurrentTarget>
</OPTFL> </OPTFL>
@@ -141,6 +141,7 @@
// <i> to immediately transfer a new application if it wishes. // <i> to immediately transfer a new application if it wishes.
#ifndef NRF_BL_DFU_CONTINUATION_TIMEOUT_MS #ifndef NRF_BL_DFU_CONTINUATION_TIMEOUT_MS
/* Time to wait for an expected DFU update immediately after entering DFU mode from the application. (10 seconds) */
#define NRF_BL_DFU_CONTINUATION_TIMEOUT_MS 10000 #define NRF_BL_DFU_CONTINUATION_TIMEOUT_MS 10000
#endif #endif
@@ -150,7 +151,8 @@
// <i> If 0, no inactivity timer will be used. Values 1-99 are invalid. // <i> If 0, no inactivity timer will be used. Values 1-99 are invalid.
#ifndef NRF_BL_DFU_INACTIVITY_TIMEOUT_MS #ifndef NRF_BL_DFU_INACTIVITY_TIMEOUT_MS
#define NRF_BL_DFU_INACTIVITY_TIMEOUT_MS 120000 /* Time to stay in DFU mode after the last DFU activity before returning to the application. (60 seconds) */
#define NRF_BL_DFU_INACTIVITY_TIMEOUT_MS 60000
#endif #endif
// </h> // </h>