Files
firmware-test/pc_firm/dr_adc121s051/dr_adc121s051.c
jhChun 1aa6585725 BLE 연결이 끊어지는 경우 비동기 측정 상태로 인한 먹통 현상 방지
- main.c: BLE disconnected -> maa_async_abort() 함수 호출
- dr_adc121s051c: maa_async_abort() 함수에서 상태를 IDLE로 초기화 및 정리
2026-03-24 18:18:52 +09:00

1411 lines
49 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*******************************************************************************
* @file 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 (04095)
*
* @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;
}