- main.c: BLE disconnected -> maa_async_abort() 함수 호출 - dr_adc121s051c: maa_async_abort() 함수에서 상태를 IDLE로 초기화 및 정리
1411 lines
49 KiB
C
1411 lines
49 KiB
C
/*******************************************************************************
|
||
* @file dr_adc121s051.c
|
||
* @brief ADC121S051 12-bit ADC Driver for nRF52840
|
||
* For 1.2MHz Piezo Echo Envelope Detection
|
||
* @author Charles KWON
|
||
* @date 2025-12-15
|
||
*
|
||
* @details Hardware SPI (nrfx_spim, SPIM2) implementation for ADC121S051.
|
||
* Replaces bit-bang SPI to eliminate __disable_irq() and prevent
|
||
* SoftDevice assertion failures during BLE connection events.
|
||
* Optimized for reading envelope-detected echo signals.
|
||
*
|
||
* ADC121S051 Serial Interface:
|
||
*
|
||
* CS ────────┐ ┌────────
|
||
* (HIGH) └──────────────────────────────┘ (HIGH)
|
||
*
|
||
* SCLK ────────┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ────────
|
||
* (HIGH) └──┘ └──┘ └── ... ┘ └──┘ └──┘ (HIGH)
|
||
* 1 2 3 14 15 16
|
||
*
|
||
* SDATA -------<Z2 ><Z1 ><Z0 ><D11>...<D1 ><D0 >------
|
||
* |<- 3 zeros ->|<-- 12 data bits -->|
|
||
*
|
||
* - CS idle HIGH, SCLK idle HIGH
|
||
* - CS falling edge: starts conversion, samples VIN
|
||
* - Data clocked out on SCLK falling edge
|
||
* - MSB first, straight binary output
|
||
*
|
||
* @note WARNING: Never hardcode pin numbers!
|
||
* Hardcoding may save a developer's time momentarily,
|
||
* but it will also shorten their lifespan.
|
||
******************************************************************************/
|
||
|
||
#include "dr_adc121s051.h"
|
||
#include "dr_util.h"
|
||
#include "nrfx_spim.h"
|
||
#include "nrf52840.h"
|
||
#include "nrf_gpio.h"
|
||
#include "nrf_delay.h"
|
||
#include <string.h> /* memset */
|
||
|
||
/* BLE connection state from main.c */
|
||
extern volatile bool data_tx_in_progress;
|
||
extern volatile bool ble_connection_st;
|
||
|
||
/*==============================================================================
|
||
* EXTERNAL PIEZO BURST FUNCTIONS
|
||
*============================================================================*/
|
||
extern void dr_piezo_burst_sw(uint8_t cycles);
|
||
extern void dr_piezo_burst_sw_18mhz(uint8_t cycles);
|
||
extern void dr_piezo_burst_sw_20mhz(uint8_t cycles);
|
||
extern void dr_piezo_burst_sw_17mhz(uint8_t cycles);
|
||
extern void dr_piezo_burst_sw_22mhz(uint8_t cycles);
|
||
extern void dr_piezo_burst_sw_19mhz(uint8_t cycles);
|
||
extern void dr_piezo_power_off(void);
|
||
|
||
#include "parser.h"
|
||
|
||
/*==============================================================================
|
||
* DEBUG CONFIGURATION
|
||
*============================================================================*/
|
||
#define DEBUG_ADC
|
||
#ifdef DEBUG_ADC
|
||
#include "nrf_log.h"
|
||
#define ADC_LOG(...) NRF_LOG_INFO(__VA_ARGS__)
|
||
#else
|
||
#define ADC_LOG(...)
|
||
#endif
|
||
|
||
/*==============================================================================
|
||
* PRIVATE DEFINES
|
||
*============================================================================*/
|
||
|
||
/* Extract pin number from NRF_GPIO_PIN_MAP (lower 5 bits) */
|
||
#define DR_PIN_NUM(pin) ((pin) & 0x1F)
|
||
#define DR_PIN_PORT(pin) (((pin) >> 5) & 0x01)
|
||
|
||
/*==============================================================================
|
||
* PRIVATE VARIABLES
|
||
*============================================================================*/
|
||
|
||
static bool m_initialized = false;
|
||
static uint32_t m_vref_mv = DR_ADC_VREF_MV;
|
||
|
||
/* Hardware SPI instance (SPIM3 - supports up to 32MHz, 16MHz for ADC121S051) */
|
||
static nrfx_spim_t m_spim = NRFX_SPIM_INSTANCE(3);
|
||
|
||
/* Echo capture buffer (module-level for external access) */
|
||
static uint16_t m_echo_buffer[DR_ADC_ECHO_SAMPLES_MAX];
|
||
|
||
/*==============================================================================
|
||
* PRIVATE FUNCTIONS
|
||
*============================================================================*/
|
||
|
||
/**
|
||
* @brief Perform one ADC121S051 conversion via hardware SPI (SPIM2).
|
||
* @return 12-bit ADC value (0–4095)
|
||
*
|
||
* @details ADC121S051 serial frame (16 bits, MSB first):
|
||
* Bit 15-13: Leading zeros (Z2, Z1, Z0)
|
||
* Bit 12-1: Data bits D11-D0
|
||
* Bit 0: Don't care
|
||
* Extract: (raw16 >> 1) & 0x0FFF
|
||
*
|
||
* SPI Mode 2 (CPOL=1, CPHA=0):
|
||
* - SCLK idle HIGH
|
||
* - Data valid on SCLK falling edge (sampled by SPIM on falling edge)
|
||
* - CS falling edge starts ADC conversion
|
||
*/
|
||
static uint16_t spim_read_raw(void)
|
||
{
|
||
volatile uint8_t rx_buf[2] = {0, 0};
|
||
NRF_SPIM_Type *spim = m_spim.p_reg;
|
||
|
||
nrf_gpio_pin_clear(DR_ADC_PIN_CS); /* CS LOW */
|
||
spim->RXD.PTR = (uint32_t)rx_buf;
|
||
spim->RXD.MAXCNT = 2;
|
||
spim->EVENTS_END = 0;
|
||
spim->TASKS_START = 1;
|
||
while (!spim->EVENTS_END) {}
|
||
nrf_gpio_pin_set(DR_ADC_PIN_CS); /* CS HIGH */
|
||
|
||
uint16_t raw16 = ((uint16_t)rx_buf[0] << 8) | rx_buf[1];
|
||
return (raw16 >> 1) & 0x0FFF;
|
||
}
|
||
|
||
/*==============================================================================
|
||
* PUBLIC FUNCTIONS - INITIALIZATION
|
||
*============================================================================*/
|
||
|
||
dr_adc_err_t dr_adc_init(void)
|
||
{
|
||
nrfx_err_t err;
|
||
|
||
ADC_LOG("ADC121S051 init (HW SPI, SPIM3)...");
|
||
|
||
nrfx_spim_config_t config = NRFX_SPIM_DEFAULT_CONFIG;
|
||
config.sck_pin = DR_ADC_PIN_SCLK; /* P0.14 */
|
||
config.mosi_pin = NRFX_SPIM_PIN_NOT_USED;/* not used (read-only ADC) */
|
||
config.miso_pin = DR_ADC_PIN_SDATA; /* P0.15 */
|
||
config.ss_pin = DR_ADC_PIN_CS; /* P0.19 */
|
||
config.frequency = NRF_SPIM_FREQ_16M;
|
||
config.mode = NRF_SPIM_MODE_3; /* CPOL=1 (idle HIGH), CPHA=1 (sample on rising - ADC presents on falling, stable on rising) */
|
||
config.bit_order = NRF_SPIM_BIT_ORDER_MSB_FIRST;
|
||
config.ss_active_high = false; /* CS active LOW */
|
||
|
||
err = nrfx_spim_init(&m_spim, &config, NULL, NULL);
|
||
ADC_LOG("SPIM3 init result: 0x%08X (%s)", err, (err == NRFX_SUCCESS) ? "OK" : "FAIL");
|
||
if (err != NRFX_SUCCESS)
|
||
{
|
||
return DR_ADC_ERR_NOT_INIT;
|
||
}
|
||
|
||
/* Wait for ADC power stabilization */
|
||
nrf_delay_us(10);
|
||
|
||
/* Dummy read to wake up ADC and clear stale conversion data */
|
||
(void)spim_read_raw();
|
||
|
||
m_initialized = true;
|
||
|
||
ADC_LOG("ADC121S051 ready (VREF=%dmV, HW SPI)", m_vref_mv);
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
void dr_adc_uninit(void)
|
||
{
|
||
if (!m_initialized) return;
|
||
|
||
nrfx_spim_uninit(&m_spim);
|
||
|
||
m_initialized = false;
|
||
|
||
ADC_LOG("ADC121S051 uninitialized");
|
||
}
|
||
|
||
bool dr_adc_is_initialized(void)
|
||
{
|
||
return m_initialized;
|
||
}
|
||
|
||
/*==============================================================================
|
||
* PUBLIC FUNCTIONS - BASIC READ
|
||
*============================================================================*/
|
||
|
||
dr_adc_err_t dr_adc_read_raw(uint16_t *raw_value)
|
||
{
|
||
if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
|
||
if (raw_value == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
*raw_value = spim_read_raw();
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
dr_adc_err_t dr_adc_read(dr_adc_result_t *result)
|
||
{
|
||
if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
|
||
if (result == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
dr_adc_err_t err = dr_adc_read_raw(&result->raw);
|
||
if (err != DR_ADC_OK) return err;
|
||
|
||
result->voltage_mv = dr_adc_raw_to_mv(result->raw);
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
dr_adc_err_t dr_adc_read_averaged(dr_adc_result_t *result, uint16_t num_samples)
|
||
{
|
||
if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
|
||
if (result == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
|
||
return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
uint32_t sum = 0;
|
||
uint16_t raw;
|
||
|
||
for (uint16_t i = 0; i < num_samples; i++)
|
||
{
|
||
dr_adc_err_t err = dr_adc_read_raw(&raw);
|
||
if (err != DR_ADC_OK) return err;
|
||
sum += raw;
|
||
}
|
||
|
||
result->raw = (uint16_t)(sum / num_samples);
|
||
result->voltage_mv = dr_adc_raw_to_mv(result->raw);
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
/*==============================================================================
|
||
* PUBLIC FUNCTIONS - ECHO DETECTION
|
||
*============================================================================*/
|
||
|
||
dr_adc_err_t dr_adc_capture_echo(uint16_t *buffer, uint16_t num_samples)
|
||
{
|
||
volatile uint8_t rx_buf[2];
|
||
uint32_t cs_mask = (1UL << DR_PIN_NUM(DR_ADC_PIN_CS));
|
||
uint16_t raw16;
|
||
uint16_t i;
|
||
NRF_SPIM_Type *spim = m_spim.p_reg;
|
||
|
||
if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
|
||
if (buffer == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
|
||
return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
spim->RXD.MAXCNT = 2;
|
||
|
||
for (i = 0; i < num_samples; i++)
|
||
{
|
||
NRF_P0->OUTCLR = cs_mask;
|
||
spim->RXD.PTR = (uint32_t)rx_buf;
|
||
spim->EVENTS_END = 0;
|
||
spim->TASKS_START = 1;
|
||
while (!spim->EVENTS_END) {}
|
||
NRF_P0->OUTSET = cs_mask;
|
||
|
||
raw16 = ((uint16_t)rx_buf[0] << 8) | rx_buf[1];
|
||
buffer[i] = (raw16 >> 1) & 0x0FFF;
|
||
}
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
dr_adc_err_t dr_adc_measure_echo(dr_adc_echo_t *echo, const dr_adc_echo_config_t *config)
|
||
{
|
||
if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
|
||
if (echo == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
/* Use default config if not provided */
|
||
dr_adc_echo_config_t cfg;
|
||
if (config != NULL)
|
||
{
|
||
cfg = *config;
|
||
}
|
||
else
|
||
{
|
||
cfg.num_samples = DR_ADC_ECHO_SAMPLES_DEFAULT;
|
||
cfg.threshold_raw = 100; /* ~80mV threshold */
|
||
cfg.delay_us = 0;
|
||
}
|
||
|
||
/* Validate config */
|
||
if (cfg.num_samples == 0 || cfg.num_samples > DR_ADC_ECHO_SAMPLES_MAX)
|
||
return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
/* Use module-level buffer (16KB too large for stack) */
|
||
|
||
/* Optional delay before capture */
|
||
if (cfg.delay_us > 0)
|
||
{
|
||
nrf_delay_us(cfg.delay_us);
|
||
}
|
||
|
||
/* Capture echo samples into module-level buffer */
|
||
dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, cfg.num_samples);
|
||
if (err != DR_ADC_OK) return err;
|
||
|
||
/* Analyze captured data */
|
||
return dr_adc_analyze_echo(m_echo_buffer, cfg.num_samples, echo, cfg.threshold_raw);
|
||
}
|
||
|
||
dr_adc_err_t dr_adc_burst_and_capture(uint8_t cycles, uint16_t delay_us,
|
||
uint16_t num_samples, dr_adc_echo_t *echo)
|
||
{
|
||
(void)cycles; /* Not used - ADC test only */
|
||
|
||
if (echo == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
|
||
return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
/* Initialize echo structure */
|
||
echo->peak_raw = 0;
|
||
echo->peak_mv = 0;
|
||
echo->peak_index = 0;
|
||
echo->peak_time_us = 0;
|
||
echo->baseline_raw = 0;
|
||
echo->num_samples = 0;
|
||
|
||
/* Initialize ADC if not ready */
|
||
if (!m_initialized)
|
||
{
|
||
dr_adc_err_t init_err = dr_adc_init();
|
||
if (init_err != DR_ADC_OK) return init_err;
|
||
}
|
||
|
||
/* Settling time for ADC power */
|
||
nrf_delay_us(100);
|
||
|
||
/* Optional delay before capture */
|
||
if (delay_us > 0)
|
||
{
|
||
nrf_delay_us(delay_us);
|
||
}
|
||
|
||
/* Capture ADC samples into module-level buffer */
|
||
dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, num_samples);
|
||
if (err != DR_ADC_OK) return err;
|
||
|
||
/* Analyze captured data */
|
||
return dr_adc_analyze_echo(m_echo_buffer, num_samples, echo, 100);
|
||
}
|
||
|
||
const uint16_t* dr_adc_get_echo_buffer(void)
|
||
{
|
||
return m_echo_buffer;
|
||
}
|
||
|
||
dr_adc_err_t dr_adc_analyze_echo(const uint16_t *buffer, uint16_t num_samples,
|
||
dr_adc_echo_t *echo, uint16_t threshold)
|
||
{
|
||
if (buffer == NULL || echo == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
if (num_samples == 0) return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
/* Calculate baseline from first few samples */
|
||
uint16_t baseline_samples = (num_samples > 8) ? 8 : num_samples;
|
||
echo->baseline_raw = dr_adc_calc_baseline(buffer, baseline_samples);
|
||
|
||
/* Find peak */
|
||
dr_adc_find_peak(buffer, num_samples, &echo->peak_raw, &echo->peak_index);
|
||
|
||
/* Convert to voltage and time */
|
||
echo->peak_mv = dr_adc_raw_to_mv(echo->peak_raw);
|
||
|
||
/* peak_time_us: sample_index * 0.116us (8.6MHz sample rate) */
|
||
echo->peak_time_us = (uint32_t)((float)echo->peak_index * DR_ADC_SAMPLE_INTERVAL_US);
|
||
echo->num_samples = num_samples;
|
||
|
||
/* Check if valid echo detected */
|
||
if (echo->peak_raw < threshold ||
|
||
echo->peak_raw <= echo->baseline_raw)
|
||
{
|
||
return DR_ADC_ERR_NO_ECHO;
|
||
}
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
void dr_adc_find_peak(const uint16_t *buffer, uint16_t num_samples,
|
||
uint16_t *peak_value, uint16_t *peak_index)
|
||
{
|
||
/*
|
||
* Posterior wall detection for bladder measurement
|
||
* - Skip initial decay zone (index 0-29) where transducer coupling causes high values
|
||
* - Search in range [30, 70] where posterior wall echo is expected
|
||
* - Ignore values > 1500 (likely noise/saturation)
|
||
*/
|
||
#define SEARCH_START 30
|
||
#define SEARCH_END 70
|
||
#define MAX_VALID_VALUE 1500 /* Values above this are likely noise */
|
||
|
||
uint16_t max_val = 0;
|
||
uint16_t max_idx = 0;
|
||
|
||
uint16_t start = (num_samples > SEARCH_START) ? SEARCH_START : 0;
|
||
uint16_t end = (num_samples > SEARCH_END) ? SEARCH_END : num_samples;
|
||
|
||
for (uint16_t i = start; i < end; i++)
|
||
{
|
||
uint16_t val = buffer[i];
|
||
/* Skip abnormally high values (noise/saturation) */
|
||
if (val > MAX_VALID_VALUE) continue;
|
||
|
||
if (val > max_val)
|
||
{
|
||
max_val = val;
|
||
max_idx = i;
|
||
}
|
||
}
|
||
|
||
if (peak_value != NULL) *peak_value = max_val;
|
||
if (peak_index != NULL) *peak_index = max_idx;
|
||
}
|
||
|
||
uint16_t dr_adc_calc_baseline(const uint16_t *buffer, uint16_t num_samples)
|
||
{
|
||
if (buffer == NULL || num_samples == 0) return 0;
|
||
|
||
uint32_t sum = 0;
|
||
for (uint16_t i = 0; i < num_samples; i++)
|
||
{
|
||
sum += buffer[i];
|
||
}
|
||
|
||
return (uint16_t)(sum / num_samples);
|
||
}
|
||
|
||
/*==============================================================================
|
||
* PUBLIC FUNCTIONS - UTILITY
|
||
*============================================================================*/
|
||
|
||
uint32_t dr_adc_raw_to_mv(uint16_t raw_value)
|
||
{
|
||
/* V_mv = (raw * VREF_mv) / 4096 */
|
||
return ((uint32_t)raw_value * m_vref_mv) / (DR_ADC_MAX_VALUE + 1);
|
||
}
|
||
|
||
void dr_adc_set_vref(uint32_t vref_mv)
|
||
{
|
||
m_vref_mv = vref_mv;
|
||
ADC_LOG("VREF set to %d mV", vref_mv);
|
||
}
|
||
|
||
uint32_t dr_adc_get_vref(void)
|
||
{
|
||
return m_vref_mv;
|
||
}
|
||
|
||
/*==============================================================================
|
||
* PUBLIC FUNCTIONS - DEBUG
|
||
*============================================================================*/
|
||
|
||
bool dr_adc_test(void)
|
||
{
|
||
if (!m_initialized)
|
||
{
|
||
ADC_LOG("Test FAIL: not initialized");
|
||
return false;
|
||
}
|
||
|
||
uint16_t raw;
|
||
dr_adc_err_t err = dr_adc_read_raw(&raw);
|
||
|
||
if (err != DR_ADC_OK)
|
||
{
|
||
ADC_LOG("Test FAIL: read error %d", err);
|
||
return false;
|
||
}
|
||
|
||
if (raw > DR_ADC_MAX_VALUE)
|
||
{
|
||
ADC_LOG("Test FAIL: invalid value %d", raw);
|
||
return false;
|
||
}
|
||
|
||
ADC_LOG("Test PASS: raw=%d (%dmV)", raw, dr_adc_raw_to_mv(raw));
|
||
return true;
|
||
}
|
||
|
||
void dr_adc_print_buffer(const uint16_t *buffer, uint16_t num_samples)
|
||
{
|
||
#ifdef DEBUG_ADC
|
||
if (buffer == NULL || num_samples == 0) return;
|
||
|
||
ADC_LOG("Echo buffer (%d samples):", num_samples);
|
||
|
||
for (uint16_t i = 0; i < num_samples; i++)
|
||
{
|
||
ADC_LOG("[%3d] %4d (%4dmV)", i, buffer[i], dr_adc_raw_to_mv(buffer[i]));
|
||
}
|
||
#else
|
||
(void)buffer;
|
||
(void)num_samples;
|
||
#endif
|
||
}
|
||
|
||
/*==============================================================================
|
||
* BLE TRANSMISSION
|
||
*============================================================================*/
|
||
|
||
/* External BLE NUS functions and variables from main.c */
|
||
extern void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length);
|
||
extern void dr_sd_delay_ms(uint32_t ms);
|
||
|
||
/* Platform interface (log function) */
|
||
#include "parser.h"
|
||
|
||
/*==============================================================================
|
||
* INTEGRATED BURST + CAPTURE + TRANSMIT
|
||
*============================================================================*/
|
||
|
||
/* External piezo burst function */
|
||
extern void dr_piezo_burst_sw(uint8_t cycles);
|
||
|
||
/* BLE packet constants */
|
||
#define BLE_MTU_SIZE 240
|
||
#define BLE_FIRST_HEADER_LEN 14 /* "rec:" + header */
|
||
#define BLE_CONT_HEADER_LEN 6 /* "red:" + packet_index */
|
||
#define BLE_FIRST_DATA_LEN (BLE_MTU_SIZE - BLE_FIRST_HEADER_LEN)
|
||
#define BLE_CONT_DATA_LEN (BLE_MTU_SIZE - BLE_CONT_HEADER_LEN)
|
||
#define BLE_PACKET_DELAY_MS 100 /* Inter-packet delay - allow BLE TX buffer to drain */
|
||
#define BLE_FIRST_PACKET_DELAY 5 /* Minimal delay */
|
||
|
||
/* Protocol version markers (OR'd with packet count in reb: header) */
|
||
#define MEC_VERSION_MARKER 0xD000 /* vD: raa only (no ree) - ree causes packet loss */
|
||
#define MAA_VERSION_MARKER 0xF000 /* vF: maa? hardcoded 8-channel debug */
|
||
|
||
/*==============================================================================
|
||
* PIEZO CHANNEL SELECTION
|
||
*============================================================================*/
|
||
|
||
/* Piezo MUX pins (8ch) */
|
||
#define DR_PIEZO_EN_MUXA NRF_GPIO_PIN_MAP(0, 21) /**< MUXA Enable */
|
||
#define DR_PIEZO_EN_MUXB NRF_GPIO_PIN_MAP(0, 23) /**< MUXB Enable */
|
||
#define DR_PIEZO_MUX_SEL1 NRF_GPIO_PIN_MAP(0, 28) /**< MUX Select 1 */
|
||
#define DR_PIEZO_MUX_SEL0 NRF_GPIO_PIN_MAP(1, 10) /**< MUX Select 0 */
|
||
|
||
/*
|
||
* Channel mapping (8ch) jhChun 26.01.29
|
||
|
||
* | EN MUXA | EN MUXB | SEL 0 | SEL 1
|
||
* ----------------------------------------------
|
||
* CH A0 | 1 | 0 | 0 | 0
|
||
* CH A1 | 1 | 0 | 0 | 1
|
||
* CH A2 | 1 | 0 | 1 | 0
|
||
* CH A3 | 1 | 0 | 1 | 1
|
||
* ----------------------------------------------
|
||
* CH B3 | 0 | 1 | 0 | 0
|
||
* CH B2 | 0 | 1 | 0 | 1
|
||
* CH B1 | 0 | 1 | 1 | 0
|
||
* CH B0 | 0 | 1 | 1 | 1
|
||
*/
|
||
|
||
/* dr_piezo_select_channel is defined in dr_piezo.c */
|
||
extern void dr_piezo_select_channel(uint8_t channel);
|
||
|
||
/*==============================================================================
|
||
* INTEGRATED BURST + CAPTURE + TRANSMIT
|
||
*============================================================================*/
|
||
|
||
/**
|
||
* @brief Integrated burst + capture + BLE transmit (16-bit raw data)
|
||
*
|
||
* PROTOCOL v3: Small header packet + separate data packets with delays
|
||
* (Large first packets were being dropped by BLE stack)
|
||
*
|
||
* Response format:
|
||
* Packet 1 (reb:): header only (14 bytes)
|
||
* Packet 2~N (red:): pkt_idx(2) + raw_data(up to 234 bytes = 117 samples)
|
||
* Final (ree:): total_packets(2)
|
||
*
|
||
* With 140 samples: 280 bytes = reb:(14) + red:(6+234) + red:(6+46) + ree:(6)
|
||
*/
|
||
dr_adc_err_t dr_adc_burst_capture_transmit(uint8_t freq_option, uint16_t delay_us,
|
||
uint16_t num_samples, uint8_t cycles,
|
||
uint16_t averaging, uint8_t piezo_ch,
|
||
uint8_t *ble_buffer, uint8_t skip_raa)
|
||
{
|
||
if (ble_buffer == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
|
||
return DR_ADC_ERR_INVALID_PARAM;
|
||
if (freq_option > 9) freq_option = 0; /* Invalid -> default 1.8MHz */
|
||
if (cycles < 3 || cycles > 7) cycles = 5; /* Valid range: 3~7, default 5 */
|
||
if (averaging == 0) averaging = 1; /* Minimum 1 */
|
||
if (averaging > 1000) averaging = 1000; /* Maximum 1000 */
|
||
if (piezo_ch >= MAA_NUM_CHANNELS) piezo_ch = 0; /* 채널 범위 검증 */
|
||
|
||
dr_adc_echo_t echo;
|
||
echo.peak_raw = 0;
|
||
echo.peak_mv = 0;
|
||
echo.peak_index = 0;
|
||
echo.peak_time_us = 0;
|
||
echo.baseline_raw = 0;
|
||
echo.num_samples = 0;
|
||
|
||
/* Accumulator buffer for averaging (32-bit to prevent overflow) */
|
||
static uint32_t accum_buffer[DR_ADC_ECHO_SAMPLES_MAX];
|
||
|
||
/* CRITICAL: Clear entire accumulator buffer at start of each mec call */
|
||
memset(accum_buffer, 0, sizeof(accum_buffer));
|
||
|
||
if (!m_initialized)
|
||
{
|
||
dr_adc_err_t init_err = dr_adc_init();
|
||
if (init_err != DR_ADC_OK) {
|
||
return init_err;
|
||
}
|
||
}
|
||
|
||
/* Settling time for ADC power */
|
||
nrf_delay_us(100);
|
||
|
||
/*--- Step 2: Select piezo channel ---*/
|
||
dr_piezo_select_channel(piezo_ch);
|
||
|
||
/*--- Step 3~5: Burst + Delay + Capture ---*/
|
||
if (averaging == 1) {
|
||
/*=== SINGLE MEASUREMENT (no averaging) - direct path ===*/
|
||
|
||
/* Execute piezo burst based on frequency option */
|
||
switch (freq_option) {
|
||
case 0:
|
||
default:
|
||
dr_piezo_burst_sw_18mhz(cycles); /* 1.8MHz (default) */
|
||
break;
|
||
case 1:
|
||
dr_piezo_burst_sw(cycles); /* 2.1MHz */
|
||
break;
|
||
case 2:
|
||
dr_piezo_burst_sw_20mhz(cycles); /* 2.0MHz */
|
||
break;
|
||
case 3:
|
||
dr_piezo_burst_sw_17mhz(cycles); /* 1.7MHz */
|
||
break;
|
||
case 4:
|
||
dr_piezo_burst_sw_22mhz(cycles); /* 2.2MHz */
|
||
break;
|
||
case 9:
|
||
dr_piezo_burst_sw_19mhz(cycles); /* 1.9MHz */
|
||
break;
|
||
}
|
||
|
||
/* Delay before capture (configurable, default 20us) */
|
||
if (delay_us > 0) {
|
||
nrf_delay_us(delay_us);
|
||
}
|
||
|
||
/* Capture ADC samples directly to m_echo_buffer */
|
||
dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, num_samples);
|
||
if (err != DR_ADC_OK) return err;
|
||
|
||
//ADC_LOG("mec: single measurement (no averaging)");
|
||
}
|
||
else {
|
||
/*=== MULTIPLE MEASUREMENTS (with averaging) ===*/
|
||
/* Note: accum_buffer already cleared by memset at function start */
|
||
|
||
for (uint16_t avg_iter = 0; avg_iter < averaging; avg_iter++) {
|
||
/* Wait for previous echo to decay before next measurement
|
||
* 1ms = ~77cm round-trip decay time (sound speed 1.54mm/us)
|
||
* Skip delay on first iteration */
|
||
if (avg_iter > 0) {
|
||
nrf_delay_us(500); /* 500us between measurements */
|
||
}
|
||
|
||
/* Re-select piezo channel before each burst
|
||
* (burst functions may modify P1 port state) */
|
||
dr_piezo_select_channel(piezo_ch);
|
||
|
||
/* Execute piezo burst based on frequency option */
|
||
switch (freq_option) {
|
||
case 0:
|
||
default:
|
||
dr_piezo_burst_sw_18mhz(cycles); /* 1.8MHz (default) */
|
||
break;
|
||
case 1:
|
||
dr_piezo_burst_sw(cycles); /* 2.1MHz */
|
||
break;
|
||
case 2:
|
||
dr_piezo_burst_sw_20mhz(cycles); /* 2.0MHz */
|
||
break;
|
||
case 3:
|
||
dr_piezo_burst_sw_17mhz(cycles); /* 1.7MHz */
|
||
break;
|
||
case 4:
|
||
dr_piezo_burst_sw_22mhz(cycles); /* 2.2MHz */
|
||
break;
|
||
case 9:
|
||
dr_piezo_burst_sw_19mhz(cycles); /* 1.9MHz */
|
||
break;
|
||
}
|
||
|
||
/* Delay before capture (configurable, default 20us) */
|
||
if (delay_us > 0) {
|
||
nrf_delay_us(delay_us);
|
||
}
|
||
|
||
/* Capture ADC samples */
|
||
dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, num_samples);
|
||
if (err != DR_ADC_OK) return err;
|
||
|
||
/* Accumulate samples */
|
||
for (uint16_t i = 0; i < num_samples; i++) {
|
||
accum_buffer[i] += m_echo_buffer[i];
|
||
}
|
||
}
|
||
|
||
/* Calculate average and store back to m_echo_buffer */
|
||
for (uint16_t i = 0; i < num_samples; i++) {
|
||
m_echo_buffer[i] = (uint16_t)(accum_buffer[i] / averaging);
|
||
}
|
||
|
||
ADC_LOG("mec: averaged %u measurements", averaging);
|
||
}
|
||
|
||
/*--- Step 6: Analyze averaged data ---*/
|
||
dr_adc_analyze_echo(m_echo_buffer, num_samples, &echo, 100);
|
||
|
||
/*--- Step 6: Transmit via BLE - PROTOCOL v3 ---*/
|
||
/* Packet 1: reb:(4) + header(10) = 14 bytes ONLY (no data) */
|
||
/* Packet 2~N: red:(4) + pkt_idx(2) + data(up to 234 bytes) */
|
||
/* Final: ree:(4) + total_pkts(2) = 6 bytes */
|
||
|
||
uint16_t total_data_bytes = echo.num_samples * 2;
|
||
uint16_t cont_pkt_data_cap = BLE_MTU_SIZE - BLE_CONT_HEADER_LEN; /* 234 bytes */
|
||
|
||
/* Calculate data packets needed (header packet doesn't carry data) */
|
||
uint16_t data_packets = (total_data_bytes + cont_pkt_data_cap - 1) / cont_pkt_data_cap;
|
||
|
||
/* Version marker: select based on skip_raa (0=mec, 1=maa) */
|
||
uint16_t version_marker = skip_raa ? MAA_VERSION_MARKER : MEC_VERSION_MARKER;
|
||
uint16_t pkts_with_ver = data_packets | version_marker;
|
||
|
||
ADC_LOG("mec v6: samples=%u data=%u bytes, packets=%u", echo.num_samples, total_data_bytes, data_packets);
|
||
|
||
/* Packet 1: reb: header ONLY (14 bytes) - small packet won't be dropped */
|
||
ble_buffer[0] = 'r';
|
||
ble_buffer[1] = 'e';
|
||
ble_buffer[2] = 'b';
|
||
ble_buffer[3] = ':';
|
||
ble_buffer[4] = (uint8_t)(pkts_with_ver & 0xFF);
|
||
ble_buffer[5] = (uint8_t)(pkts_with_ver >> 8);
|
||
ble_buffer[6] = (uint8_t)(echo.peak_raw & 0xFF);
|
||
ble_buffer[7] = (uint8_t)(echo.peak_raw >> 8);
|
||
ble_buffer[8] = (uint8_t)(echo.peak_index & 0xFF);
|
||
ble_buffer[9] = (uint8_t)(echo.peak_index >> 8);
|
||
ble_buffer[10] = (uint8_t)(echo.baseline_raw & 0xFF);
|
||
ble_buffer[11] = (uint8_t)(echo.baseline_raw >> 8);
|
||
ble_buffer[12] = (uint8_t)(echo.num_samples & 0xFF);
|
||
ble_buffer[13] = (uint8_t)(echo.num_samples >> 8);
|
||
|
||
dr_binary_tx_safe(ble_buffer, 7); /* Send header only: 14 bytes = 7 words */
|
||
dr_sd_delay_ms(BLE_PACKET_DELAY_MS); /* Wait for BLE stack */
|
||
|
||
/* Data packets (red:) - starting from pkt index 0 */
|
||
uint16_t src_idx = 0;
|
||
for (uint16_t pkt = 0; pkt < data_packets; pkt++) {
|
||
ble_buffer[0] = 'r';
|
||
ble_buffer[1] = 'e';
|
||
ble_buffer[2] = 'd';
|
||
ble_buffer[3] = ':';
|
||
ble_buffer[4] = (uint8_t)(pkt & 0xFF);
|
||
ble_buffer[5] = (uint8_t)(pkt >> 8);
|
||
|
||
uint16_t dst_idx = 6;
|
||
uint16_t bytes_this_pkt = 0;
|
||
while (src_idx < echo.num_samples && bytes_this_pkt < cont_pkt_data_cap) {
|
||
uint16_t sample = m_echo_buffer[src_idx++] & 0x0FFF;
|
||
ble_buffer[dst_idx++] = (uint8_t)(sample & 0xFF);
|
||
ble_buffer[dst_idx++] = (uint8_t)(sample >> 8);
|
||
bytes_this_pkt += 2;
|
||
}
|
||
|
||
dr_binary_tx_safe(ble_buffer, dst_idx / 2); /* bytes to words */
|
||
dr_sd_delay_ms(BLE_PACKET_DELAY_MS); /* Inter-packet delay */
|
||
}
|
||
|
||
/* vD: Send raa: only - ree: causes subsequent packet loss */
|
||
/* DrBench now saves channel data when receiving raa: */
|
||
/* skip_raa: 0=send raa (mec), 1=skip raa (maa - caller sends final raa) */
|
||
if (!skip_raa) {
|
||
ble_buffer[0] = 'r';
|
||
ble_buffer[1] = 'a';
|
||
ble_buffer[2] = 'a';
|
||
ble_buffer[3] = ':';
|
||
ble_buffer[4] = 0x00;
|
||
ble_buffer[5] = 0x00;
|
||
dr_binary_tx_safe(ble_buffer, 3); /* 6 bytes = 3 words */
|
||
}
|
||
|
||
ADC_LOG("mec v6: complete - reb + red*%u + skip_raa=%u (%u samples)", data_packets, skip_raa, echo.num_samples);
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
|
||
/*==============================================================================
|
||
* CHANNEL CAPTURE FUNCTIONS (maa? command support)
|
||
*============================================================================*/
|
||
|
||
dr_adc_err_t dr_adc_capture_channel_only(uint8_t freq_option, uint16_t delay_us,
|
||
uint16_t num_samples, uint8_t cycles,
|
||
uint16_t averaging, uint8_t piezo_ch,
|
||
dr_maa_channel_t *out_channel)
|
||
{
|
||
if (out_channel == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
if (num_samples == 0 || num_samples > MAA_SAMPLES_MAX)
|
||
return DR_ADC_ERR_INVALID_PARAM;
|
||
if (freq_option > 3) freq_option = 0;
|
||
if (cycles < 3 || cycles > 7) cycles = 5;
|
||
if (averaging == 0) averaging = 1;
|
||
if (averaging > 1000) averaging = 1000;
|
||
if (piezo_ch > (MAA_NUM_CHANNELS - 1)) piezo_ch = 0;
|
||
|
||
/* Accumulator buffer for averaging */
|
||
static uint32_t accum_buffer[MAA_SAMPLES_MAX];
|
||
memset(accum_buffer, 0, num_samples * sizeof(uint32_t));
|
||
|
||
if (!m_initialized)
|
||
{
|
||
dr_adc_err_t init_err = dr_adc_init();
|
||
if (init_err != DR_ADC_OK) return init_err;
|
||
}
|
||
|
||
//nrf_delay_us(100);
|
||
|
||
/* 채널 선택(MUX) : 1.3ms */
|
||
dr_piezo_select_channel(piezo_ch);
|
||
|
||
/* 채널당 반복 측정 횟수만큼 */
|
||
for (uint16_t avg_iter = 0; avg_iter < averaging; avg_iter++)
|
||
{
|
||
/* TX 펄스 출력(Burst) */
|
||
switch (freq_option) {
|
||
case 0:
|
||
default:
|
||
dr_piezo_burst_sw_18mhz(cycles);
|
||
break;
|
||
case 1:
|
||
dr_piezo_burst_sw(cycles); /* 2.1MHz */
|
||
break;
|
||
case 2:
|
||
dr_piezo_burst_sw_20mhz(cycles);
|
||
break;
|
||
case 3:
|
||
dr_piezo_burst_sw_17mhz(cycles);
|
||
break;
|
||
}
|
||
|
||
/* TX 펄스 출력 후 ADC 시작 시까지 대기 시간 */
|
||
if (delay_us > 0)
|
||
{
|
||
nrf_delay_us(delay_us);
|
||
}
|
||
|
||
/* 측정 ADC 샘플 수만큼 캡처 */
|
||
dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, num_samples);
|
||
|
||
if (err != DR_ADC_OK) return err;
|
||
|
||
/* Accumulate */
|
||
for (uint16_t i = 0; i < num_samples; i++)
|
||
{
|
||
accum_buffer[i] += m_echo_buffer[i];
|
||
}
|
||
}
|
||
|
||
/* Calculate average and copy to output */
|
||
for (uint16_t i = 0; i < num_samples; i++)
|
||
{
|
||
out_channel->samples[i] = (uint16_t)(accum_buffer[i] / averaging);
|
||
}
|
||
|
||
out_channel->num_samples = num_samples;
|
||
|
||
/* Analyze echo data */
|
||
dr_adc_echo_t echo;
|
||
dr_adc_analyze_echo(out_channel->samples, num_samples, &echo, 100);
|
||
out_channel->peak_raw = echo.peak_raw;
|
||
out_channel->peak_index = echo.peak_index;
|
||
out_channel->baseline_raw = echo.baseline_raw;
|
||
|
||
/* peak, index, baseline 확인용 로그 */
|
||
//if (g_plat.log) g_plat.log("CH%u: peak=%u index=%u baseline=%u\r\n", piezo_ch, out_channel->peak_raw, out_channel->peak_index, out_channel->baseline_raw);
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
|
||
dr_adc_err_t dr_adc_transmit_channel(const dr_maa_channel_t *ch_data,
|
||
uint8_t *ble_buffer)
|
||
{
|
||
/* Debug BEFORE null check to see if we even enter the function */
|
||
dr_ble_debug(0x00FF, 0xAAAA); /* Function called marker */
|
||
|
||
if (ch_data == NULL || ble_buffer == NULL) {
|
||
dr_ble_debug(0x00FE, (ch_data == NULL ? 0x0001 : 0) | (ble_buffer == NULL ? 0x0002 : 0));
|
||
return DR_ADC_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
dr_ble_debug(0x0100, ch_data->num_samples); /* Function entry - params valid */
|
||
|
||
uint16_t total_data_bytes = ch_data->num_samples * 2;
|
||
uint16_t cont_pkt_data_cap = BLE_MTU_SIZE - BLE_CONT_HEADER_LEN; /* 234 bytes */
|
||
uint16_t data_packets = (total_data_bytes + cont_pkt_data_cap - 1) / cont_pkt_data_cap;
|
||
uint16_t total_packets = 1 + data_packets;
|
||
|
||
/* Version marker: MAA_VERSION_MARKER | total_packets */
|
||
uint16_t pkt_with_ver = total_packets | MAA_VERSION_MARKER;
|
||
|
||
dr_ble_debug(0x0101, total_packets); /* Before reb: */
|
||
|
||
/* Packet 1: reb: header (14 bytes) */
|
||
ble_buffer[0] = 'r';
|
||
ble_buffer[1] = 'e';
|
||
ble_buffer[2] = 'b';
|
||
ble_buffer[3] = ':';
|
||
ble_buffer[4] = (uint8_t)(pkt_with_ver & 0xFF);
|
||
ble_buffer[5] = (uint8_t)(pkt_with_ver >> 8);
|
||
ble_buffer[6] = (uint8_t)(ch_data->peak_raw & 0xFF);
|
||
ble_buffer[7] = (uint8_t)(ch_data->peak_raw >> 8);
|
||
ble_buffer[8] = (uint8_t)(ch_data->peak_index & 0xFF);
|
||
ble_buffer[9] = (uint8_t)(ch_data->peak_index >> 8);
|
||
ble_buffer[10] = (uint8_t)(ch_data->baseline_raw & 0xFF);
|
||
ble_buffer[11] = (uint8_t)(ch_data->baseline_raw >> 8);
|
||
ble_buffer[12] = (uint8_t)(ch_data->num_samples & 0xFF);
|
||
ble_buffer[13] = (uint8_t)(ch_data->num_samples >> 8);
|
||
|
||
/* Send reb: header */
|
||
/* Use dr_binary_tx_safe like mec? does, with dr_sd_delay_ms for SoftDevice */
|
||
dr_binary_tx_safe(ble_buffer, 7);
|
||
dr_sd_delay_ms(BLE_PACKET_DELAY_MS);
|
||
|
||
dr_ble_debug(0x0102, data_packets); /* After reb: */
|
||
|
||
/* Data packets (red:) */
|
||
uint16_t src_idx = 0;
|
||
for (uint16_t pkt = 0; pkt < data_packets; pkt++) {
|
||
ble_buffer[0] = 'r';
|
||
ble_buffer[1] = 'e';
|
||
ble_buffer[2] = 'd';
|
||
ble_buffer[3] = ':';
|
||
ble_buffer[4] = (uint8_t)(pkt & 0xFF);
|
||
ble_buffer[5] = (uint8_t)(pkt >> 8);
|
||
|
||
uint16_t dst_idx = 6;
|
||
uint16_t bytes_this_pkt = 0;
|
||
while (src_idx < ch_data->num_samples && bytes_this_pkt < cont_pkt_data_cap) {
|
||
uint16_t sample = ch_data->samples[src_idx++] & 0x0FFF;
|
||
ble_buffer[dst_idx++] = (uint8_t)(sample & 0xFF);
|
||
ble_buffer[dst_idx++] = (uint8_t)(sample >> 8);
|
||
bytes_this_pkt += 2;
|
||
}
|
||
|
||
dr_binary_tx_safe(ble_buffer, dst_idx / 2);
|
||
dr_sd_delay_ms(BLE_PACKET_DELAY_MS);
|
||
|
||
dr_ble_debug(0x0103, pkt); /* red: packet sent */
|
||
}
|
||
|
||
dr_ble_debug(0x0104, 0); /* Before ree: */
|
||
|
||
/* Final packet: ree: */
|
||
ble_buffer[0] = 'r';
|
||
ble_buffer[1] = 'e';
|
||
ble_buffer[2] = 'e';
|
||
ble_buffer[3] = ':';
|
||
ble_buffer[4] = (uint8_t)(total_packets & 0xFF);
|
||
ble_buffer[5] = (uint8_t)(total_packets >> 8);
|
||
dr_binary_tx_safe(ble_buffer, 3);
|
||
|
||
dr_ble_debug(0x010F, 0); /* Function complete */
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
|
||
/*==============================================================================
|
||
* DELTA COMPRESSION FUNCTIONS
|
||
*============================================================================*/
|
||
|
||
dr_adc_err_t dr_adc_delta_compress(const uint16_t *samples, uint16_t num_samples,
|
||
uint8_t *out_buffer, uint16_t *out_size)
|
||
{
|
||
if (samples == NULL || out_buffer == NULL || out_size == NULL)
|
||
return DR_ADC_ERR_INVALID_PARAM;
|
||
if (num_samples == 0) {
|
||
*out_size = 0;
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
uint16_t idx = 0;
|
||
|
||
/* First sample: 16-bit raw value */
|
||
out_buffer[idx++] = (uint8_t)(samples[0] & 0xFF);
|
||
out_buffer[idx++] = (uint8_t)(samples[0] >> 8);
|
||
|
||
/* Delta encode remaining samples */
|
||
for (uint16_t i = 1; i < num_samples; i++) {
|
||
int16_t delta = (int16_t)samples[i] - (int16_t)samples[i-1];
|
||
|
||
if (delta >= -127 && delta <= 127) {
|
||
/* Normal delta: fits in signed 8-bit */
|
||
out_buffer[idx++] = (int8_t)delta;
|
||
} else {
|
||
/* Out of range: escape + 16-bit raw value */
|
||
out_buffer[idx++] = DELTA_ESCAPE_BYTE;
|
||
out_buffer[idx++] = (uint8_t)(samples[i] & 0xFF);
|
||
out_buffer[idx++] = (uint8_t)(samples[i] >> 8);
|
||
}
|
||
}
|
||
|
||
*out_size = idx;
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
|
||
dr_adc_err_t dr_adc_transmit_channel_delta(const dr_maa_channel_t *ch_data,
|
||
uint8_t *ble_buffer)
|
||
{
|
||
if (ch_data == NULL || ble_buffer == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
/* Compress data first */
|
||
static uint8_t delta_buffer[400]; /* Worst case: 140*3 = 420 bytes */
|
||
uint16_t compressed_size = 0;
|
||
|
||
dr_adc_err_t err = dr_adc_delta_compress(ch_data->samples, ch_data->num_samples,
|
||
delta_buffer, &compressed_size);
|
||
if (err != DR_ADC_OK) return err;
|
||
|
||
ADC_LOG("maa delta: %u samples -> %u bytes (%.0f%%)",
|
||
ch_data->num_samples, compressed_size,
|
||
100.0f * compressed_size / (ch_data->num_samples * 2));
|
||
|
||
uint16_t cont_pkt_data_cap = BLE_MTU_SIZE - BLE_CONT_HEADER_LEN; /* 234 bytes */
|
||
uint16_t data_packets = (compressed_size + cont_pkt_data_cap - 1) / cont_pkt_data_cap;
|
||
uint16_t total_packets = 1 + data_packets;
|
||
|
||
/* Version marker for delta mode: 0xC000 | total_packets */
|
||
uint16_t pkt_with_ver = total_packets | 0xC000; /* v2 delta marker */
|
||
|
||
/* Packet 1: rdb: header (16 bytes) - includes compressed_size */
|
||
ble_buffer[0] = 'r';
|
||
ble_buffer[1] = 'd';
|
||
ble_buffer[2] = 'b';
|
||
ble_buffer[3] = ':';
|
||
ble_buffer[4] = (uint8_t)(pkt_with_ver & 0xFF);
|
||
ble_buffer[5] = (uint8_t)(pkt_with_ver >> 8);
|
||
ble_buffer[6] = (uint8_t)(ch_data->peak_raw & 0xFF);
|
||
ble_buffer[7] = (uint8_t)(ch_data->peak_raw >> 8);
|
||
ble_buffer[8] = (uint8_t)(ch_data->peak_index & 0xFF);
|
||
ble_buffer[9] = (uint8_t)(ch_data->peak_index >> 8);
|
||
ble_buffer[10] = (uint8_t)(ch_data->baseline_raw & 0xFF);
|
||
ble_buffer[11] = (uint8_t)(ch_data->baseline_raw >> 8);
|
||
ble_buffer[12] = (uint8_t)(ch_data->num_samples & 0xFF);
|
||
ble_buffer[13] = (uint8_t)(ch_data->num_samples >> 8);
|
||
ble_buffer[14] = (uint8_t)(compressed_size & 0xFF);
|
||
ble_buffer[15] = (uint8_t)(compressed_size >> 8);
|
||
|
||
dr_binary_tx_safe(ble_buffer, 8); /* 16 bytes = 8 words */
|
||
dr_sd_delay_ms(BLE_PACKET_DELAY_MS);
|
||
|
||
/* Data packets (rdd:) */
|
||
uint16_t src_idx = 0;
|
||
for (uint16_t pkt = 0; pkt < data_packets; pkt++)
|
||
{
|
||
ble_buffer[0] = 'r';
|
||
ble_buffer[1] = 'd';
|
||
ble_buffer[2] = 'd';
|
||
ble_buffer[3] = ':';
|
||
ble_buffer[4] = (uint8_t)(pkt & 0xFF);
|
||
ble_buffer[5] = (uint8_t)(pkt >> 8);
|
||
|
||
uint16_t dst_idx = 6;
|
||
uint16_t bytes_this_pkt = 0;
|
||
while (src_idx < compressed_size && bytes_this_pkt < cont_pkt_data_cap)
|
||
{
|
||
ble_buffer[dst_idx++] = delta_buffer[src_idx++];
|
||
bytes_this_pkt++;
|
||
}
|
||
|
||
/* Pad to word boundary if needed */
|
||
if (dst_idx & 1)
|
||
{
|
||
ble_buffer[dst_idx++] = 0;
|
||
}
|
||
|
||
dr_binary_tx_safe(ble_buffer, dst_idx / 2);
|
||
dr_sd_delay_ms(BLE_PACKET_DELAY_MS);
|
||
}
|
||
|
||
/* Final packet: rde: */
|
||
ble_buffer[0] = 'r';
|
||
ble_buffer[1] = 'd';
|
||
ble_buffer[2] = 'e';
|
||
ble_buffer[3] = ':';
|
||
ble_buffer[4] = (uint8_t)(total_packets & 0xFF);
|
||
ble_buffer[5] = (uint8_t)(total_packets >> 8);
|
||
dr_binary_tx_safe(ble_buffer, 3);
|
||
dr_sd_delay_ms(BLE_PACKET_DELAY_MS);
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
|
||
/*==============================================================================
|
||
* ASYNC MAA - Non-blocking 8-channel capture
|
||
*
|
||
* State machine driven by BLE TX complete events (BLE_NUS_EVT_TX_RDY)
|
||
* This prevents blocking the main loop and allows SoftDevice to process events.
|
||
*============================================================================*/
|
||
|
||
/* Global async context */
|
||
static maa_async_ctx_t g_maa_ctx = { .state = MAA_ASYNC_IDLE };
|
||
|
||
/* Version marker for async MAA */
|
||
#define MAA_ASYNC_VERSION_MARKER 0xC000 /* vC: async 8-channel + raa delay fix + cleanup */
|
||
|
||
/**
|
||
* @brief Capture one channel (internal helper)
|
||
*/
|
||
static dr_adc_err_t maa_async_capture_channel(uint8_t ch)
|
||
{
|
||
if (ch > (MAA_NUM_CHANNELS - 1)) return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
dr_adc_err_t err = dr_adc_capture_channel_only(
|
||
g_maa_ctx.freq_option,
|
||
g_maa_ctx.delay_us,
|
||
g_maa_ctx.num_samples,
|
||
g_maa_ctx.cycles,
|
||
g_maa_ctx.averaging,
|
||
ch,
|
||
&g_maa_ctx.channels[ch]
|
||
);
|
||
|
||
return err;
|
||
}
|
||
|
||
/**
|
||
* @brief Send reb: header for current channel
|
||
*/
|
||
static void maa_async_send_header(void)
|
||
{
|
||
dr_maa_channel_t *ch = &g_maa_ctx.channels[g_maa_ctx.current_ch];
|
||
uint8_t *buf = g_maa_ctx.ble_buffer;
|
||
|
||
uint16_t total_data_bytes = ch->num_samples * 2;
|
||
uint16_t cont_pkt_data_cap = BLE_MTU_SIZE - BLE_CONT_HEADER_LEN;
|
||
g_maa_ctx.data_packets = (total_data_bytes + cont_pkt_data_cap - 1) / cont_pkt_data_cap;
|
||
g_maa_ctx.total_packets = 1 + g_maa_ctx.data_packets;
|
||
|
||
uint16_t pkts_with_ver = g_maa_ctx.total_packets | MAA_ASYNC_VERSION_MARKER;
|
||
|
||
/* reb: header (14 bytes) */
|
||
buf[0] = 'r'; buf[1] = 'e'; buf[2] = 'b'; buf[3] = ':';
|
||
buf[4] = (uint8_t)(pkts_with_ver & 0xFF);
|
||
buf[5] = (uint8_t)(pkts_with_ver >> 8);
|
||
buf[6] = (uint8_t)(ch->peak_raw & 0xFF);
|
||
buf[7] = (uint8_t)(ch->peak_raw >> 8);
|
||
buf[8] = (uint8_t)(ch->peak_index & 0xFF);
|
||
buf[9] = (uint8_t)(ch->peak_index >> 8);
|
||
buf[10] = (uint8_t)(ch->baseline_raw & 0xFF);
|
||
buf[11] = (uint8_t)(ch->baseline_raw >> 8);
|
||
buf[12] = (uint8_t)(ch->num_samples & 0xFF);
|
||
buf[13] = (uint8_t)(ch->num_samples >> 8);
|
||
|
||
dr_binary_tx_safe(buf, 7); /* 14 bytes = 7 words */
|
||
|
||
/* 50ms BLE 대기 시간을 활용하여 raw 덤프 로그 (캡처 시간에 영향 없음)
|
||
if (g_plat.log)
|
||
{
|
||
if (g_maa_ctx.current_ch > 0) g_plat.log("\r\n");
|
||
g_plat.log("CH%u raw (%u samples):\r\n", g_maa_ctx.current_ch, ch->num_samples);
|
||
for (uint16_t i = 0; i < ch->num_samples; i++)
|
||
{
|
||
g_plat.log("%u ", ch->samples[i]);
|
||
if ((i + 1) % 16 == 0) g_plat.log("\r\n");
|
||
}
|
||
if (ch->num_samples % 16 != 0) g_plat.log("\r\n");
|
||
}*/
|
||
|
||
dr_sd_delay_ms(50); /* Allow BLE stack to process TX */
|
||
|
||
g_maa_ctx.current_pkt = 0;
|
||
g_maa_ctx.data_offset = 0;
|
||
g_maa_ctx.state = MAA_ASYNC_TX_DATA;
|
||
}
|
||
|
||
/**
|
||
* @brief Send next red: data packet
|
||
* @return true if more packets to send, false if done with current channel
|
||
*/
|
||
static bool maa_async_send_data_packet(void)
|
||
{
|
||
dr_maa_channel_t *ch = &g_maa_ctx.channels[g_maa_ctx.current_ch];
|
||
uint8_t *buf = g_maa_ctx.ble_buffer;
|
||
uint16_t total_data_bytes = ch->num_samples * 2;
|
||
|
||
if (g_maa_ctx.data_offset >= total_data_bytes)
|
||
{
|
||
return false; /* All data sent */
|
||
}
|
||
|
||
uint16_t pkt_idx = g_maa_ctx.current_pkt;
|
||
uint16_t remaining = total_data_bytes - g_maa_ctx.data_offset;
|
||
uint16_t chunk_size = (remaining > BLE_CONT_DATA_LEN) ? BLE_CONT_DATA_LEN : remaining;
|
||
|
||
/* red: packet header */
|
||
buf[0] = 'r'; buf[1] = 'e'; buf[2] = 'd'; buf[3] = ':';
|
||
buf[4] = (uint8_t)(pkt_idx & 0xFF);
|
||
buf[5] = (uint8_t)(pkt_idx >> 8);
|
||
|
||
/* Copy sample data */
|
||
uint16_t src_sample_idx = g_maa_ctx.data_offset / 2;
|
||
uint16_t dst_idx = 6;
|
||
for (uint16_t i = 0; i < chunk_size / 2 && src_sample_idx < ch->num_samples; i++, src_sample_idx++)
|
||
{
|
||
uint16_t sample = ch->samples[src_sample_idx];
|
||
buf[dst_idx++] = (uint8_t)(sample & 0xFF);
|
||
buf[dst_idx++] = (uint8_t)(sample >> 8);
|
||
}
|
||
|
||
dr_binary_tx_safe(buf, dst_idx / 2);
|
||
dr_sd_delay_ms(50); /* Allow BLE stack to process TX */
|
||
|
||
g_maa_ctx.data_offset += chunk_size;
|
||
g_maa_ctx.current_pkt++;
|
||
|
||
//ADC_LOG("maa_async: CH%u red:%u (%u/%u bytes)", g_maa_ctx.current_ch, pkt_idx, g_maa_ctx.data_offset, total_data_bytes);
|
||
|
||
return (g_maa_ctx.data_offset < total_data_bytes);
|
||
}
|
||
|
||
/**
|
||
* @brief Send raa: completion marker
|
||
*/
|
||
static void maa_async_send_completion(uint16_t status)
|
||
{
|
||
uint8_t *buf = g_maa_ctx.ble_buffer;
|
||
|
||
/* Wait for previous TX to complete before sending raa: */
|
||
dr_sd_delay_ms(50);
|
||
|
||
buf[0] = 'r'; buf[1] = 'a'; buf[2] = 'a'; buf[3] = ':';
|
||
buf[4] = (uint8_t)(status & 0xFF);
|
||
buf[5] = (uint8_t)(status >> 8);
|
||
|
||
dr_binary_tx_safe(buf, 3);
|
||
|
||
//if (g_plat.log) g_plat.log("-------------------------------------------------------------------------------------\r\n");
|
||
|
||
/* 캡처 완료 → Piezo TX/RX 전원 OFF */
|
||
dr_piezo_power_off();
|
||
|
||
g_maa_ctx.state = MAA_ASYNC_IDLE;
|
||
//ADC_LOG("maa_async: complete, status=0x%04X", status);
|
||
|
||
/* 완료 콜백 호출 (mbb? 등에서 센서 측정 체인 트리거용) */
|
||
if (g_maa_ctx.on_complete_cb)
|
||
{
|
||
void (*cb)(void) = g_maa_ctx.on_complete_cb;
|
||
g_maa_ctx.on_complete_cb = NULL; /* 1회성: 재호출 방지 */
|
||
cb();
|
||
}
|
||
}
|
||
|
||
/*==============================================================================
|
||
* PUBLIC ASYNC API
|
||
*============================================================================*/
|
||
|
||
dr_adc_err_t maa_async_start(uint8_t freq_option, uint16_t delay_us, uint16_t num_samples, uint8_t cycles, uint16_t averaging, uint8_t *ble_buffer)
|
||
{
|
||
dr_adc_err_t err;
|
||
uint8_t ch;
|
||
|
||
if (g_maa_ctx.state != MAA_ASYNC_IDLE)
|
||
{
|
||
ADC_LOG("maa_async_start: busy");
|
||
return DR_ADC_ERR_NOT_INIT; /* Already running */
|
||
}
|
||
|
||
if (ble_buffer == NULL) return DR_ADC_ERR_INVALID_PARAM;
|
||
if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
|
||
return DR_ADC_ERR_INVALID_PARAM;
|
||
|
||
/* Initialize context */
|
||
g_maa_ctx.freq_option = freq_option;
|
||
g_maa_ctx.delay_us = delay_us;
|
||
g_maa_ctx.num_samples = num_samples;
|
||
g_maa_ctx.cycles = (cycles < 3 || cycles > 7) ? 5 : cycles;
|
||
g_maa_ctx.averaging = (averaging == 0) ? 1 : ((averaging > 1000) ? 1000 : averaging);
|
||
g_maa_ctx.ble_buffer = ble_buffer;
|
||
g_maa_ctx.current_ch = 0;
|
||
g_maa_ctx.current_pkt = 0;
|
||
g_maa_ctx.data_offset = 0;
|
||
g_maa_ctx.on_complete_cb = NULL; /* 기본: 콜백 없음 (maa?) */
|
||
|
||
/* 전채널 캡처: BLE 전송 없이 6채널 전부 캡처 완료 후 TX 시작 */
|
||
g_maa_ctx.state = MAA_ASYNC_CAPTURING;
|
||
|
||
for (ch = 0; ch < MAA_NUM_CHANNELS; ch++)
|
||
{
|
||
err = maa_async_capture_channel(ch);
|
||
if (err != DR_ADC_OK)
|
||
{
|
||
if (g_plat.log) g_plat.log("[maa] maa_async_start: CH%u capture failed (%d)", ch, err);
|
||
maa_async_send_completion(0xFFF0 | ch);
|
||
return err;
|
||
}
|
||
}
|
||
|
||
/* peak 로그와 raw 덤프 사이 구분 */
|
||
//if (g_plat.log) g_plat.log("\r\n");
|
||
|
||
/* 캡처 완료 → Piezo TX/RX 전원 OFF (BLE 전송 중 불필요) */
|
||
dr_piezo_power_off();
|
||
|
||
/* Send CH0 header - this will trigger TX_RDY for subsequent packets */
|
||
maa_async_send_header();
|
||
|
||
return DR_ADC_OK;
|
||
}
|
||
|
||
bool maa_async_on_tx_ready(void)
|
||
{
|
||
if (g_maa_ctx.state == MAA_ASYNC_IDLE)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
switch (g_maa_ctx.state) {
|
||
case MAA_ASYNC_TX_DATA:
|
||
/* Send next data packet */
|
||
if (!maa_async_send_data_packet())
|
||
{
|
||
/* Current channel done, move to next */
|
||
g_maa_ctx.current_ch++;
|
||
|
||
if (g_maa_ctx.current_ch >= MAA_NUM_CHANNELS)
|
||
{
|
||
/* All channels done */
|
||
g_maa_ctx.state = MAA_ASYNC_COMPLETE;
|
||
maa_async_send_completion(0x0000);
|
||
return false;
|
||
}
|
||
else
|
||
{
|
||
/* 이미 전채널 캡처됨, 바로 헤더 전송 */
|
||
g_maa_ctx.state = MAA_ASYNC_CAPTURING;
|
||
maa_async_send_header();
|
||
}
|
||
}
|
||
return true;
|
||
|
||
case MAA_ASYNC_TX_HEADER:
|
||
/* Header sent, start sending data */
|
||
g_maa_ctx.state = MAA_ASYNC_TX_DATA;
|
||
maa_async_send_data_packet();
|
||
return true;
|
||
|
||
case MAA_ASYNC_CAPTURING:
|
||
/* Shouldn't happen - capture is synchronous */
|
||
return true;
|
||
|
||
case MAA_ASYNC_COMPLETE:
|
||
case MAA_ASYNC_IDLE:
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool maa_async_is_busy(void)
|
||
{
|
||
return (g_maa_ctx.state != MAA_ASYNC_IDLE);
|
||
}
|
||
|
||
void maa_async_set_pre_capture_all(bool on)
|
||
{
|
||
g_maa_ctx.pre_capture_all = on;
|
||
}
|
||
|
||
maa_async_state_t maa_async_get_state(void)
|
||
{
|
||
return g_maa_ctx.state;
|
||
}
|
||
|
||
/*==============================================================================
|
||
* 비동기 측정 상태 IDLE로 초기화 - BLE 연결이 끊어지는 경우 먹통 현상 방지
|
||
*============================================================================*/
|
||
void maa_async_abort(void)
|
||
{
|
||
if (g_maa_ctx.state != MAA_ASYNC_IDLE)
|
||
{
|
||
ADC_LOG("maa_async_abort: aborting from state %d", g_maa_ctx.state);
|
||
g_maa_ctx.state = MAA_ASYNC_IDLE; // 측정 상태 IDLE로 초기화
|
||
g_maa_ctx.on_complete_cb = NULL; // abort 후 콜백 호출 방지
|
||
dr_piezo_power_off(); // 전체 측정 중간에 끊기는 경우 Piezo TX/RX Sleep
|
||
}
|
||
}
|
||
|
||
void maa_async_set_on_complete(void (*cb)(void))
|
||
{
|
||
g_maa_ctx.on_complete_cb = cb;
|
||
}
|
||
|
||
|