- Flash Memory Piezo 측정 파라미터 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1584 lines
55 KiB
C
1584 lines
55 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 Software SPI (bit-bang) implementation for ADC121S051.
|
|
* 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 "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
|
|
*============================================================================*/
|
|
#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)
|
|
|
|
/* SPI bit masks for direct register access */
|
|
#define CS_MASK (1UL << DR_PIN_NUM(DR_ADC_PIN_CS))
|
|
#define SCLK_MASK (1UL << DR_PIN_NUM(DR_ADC_PIN_SCLK))
|
|
#define SDATA_MASK (1UL << DR_PIN_NUM(DR_ADC_PIN_SDATA))
|
|
|
|
/* Get port register based on pin */
|
|
#define DR_GET_PORT(pin) (DR_PIN_PORT(pin) ? NRF_P1 : NRF_P0)
|
|
|
|
/* ADC timing constants */
|
|
#define SCLK_CYCLES 16 /**< Clock cycles per conversion */
|
|
|
|
/*==============================================================================
|
|
* PRIVATE VARIABLES
|
|
*============================================================================*/
|
|
|
|
static bool m_initialized = false;
|
|
static uint32_t m_vref_mv = DR_ADC_VREF_MV;
|
|
|
|
/* Echo capture buffer (module-level for external access) */
|
|
static uint16_t m_echo_buffer[DR_ADC_ECHO_SAMPLES_MAX];
|
|
|
|
/* Port registers for fast access (currently unused - using direct NRF_P0) */
|
|
/* static NRF_GPIO_Type *m_port_cs; */
|
|
/* static NRF_GPIO_Type *m_port_sclk; */
|
|
/* static NRF_GPIO_Type *m_port_sdata; */
|
|
|
|
/*==============================================================================
|
|
* PRIVATE FUNCTION PROTOTYPES
|
|
*============================================================================*/
|
|
|
|
static void adc_gpio_init(void);
|
|
/* adc_transfer is always_inline, no prototype needed */
|
|
|
|
/*==============================================================================
|
|
* INLINE GPIO FUNCTIONS (for speed)
|
|
*============================================================================*/
|
|
|
|
/* cs_low() unused - using ADC_TRANSFER_16BITS macro with direct register access
|
|
static inline void cs_low(void)
|
|
{
|
|
NRF_P0->OUTCLR = CS_MASK;
|
|
}
|
|
*/
|
|
|
|
static inline void cs_high(void)
|
|
{
|
|
NRF_P0->OUTSET = CS_MASK;
|
|
}
|
|
|
|
/* sclk_low() unused - using direct register access in macro
|
|
static inline void sclk_low(void)
|
|
{
|
|
NRF_P0->OUTCLR = SCLK_MASK;
|
|
}
|
|
*/
|
|
|
|
static inline void sclk_high(void)
|
|
{
|
|
NRF_P0->OUTSET = SCLK_MASK;
|
|
}
|
|
|
|
/* read_sdata() unused - using direct register access in macro
|
|
static inline uint32_t read_sdata(void)
|
|
{
|
|
return (NRF_P0->IN & SDATA_MASK) ? 1 : 0;
|
|
}
|
|
*/
|
|
|
|
/*==============================================================================
|
|
* PRIVATE FUNCTIONS
|
|
*============================================================================*/
|
|
|
|
/**
|
|
* @brief Initialize GPIO pins for ADC
|
|
*/
|
|
static void adc_gpio_init(void)
|
|
{
|
|
/* Port pointers removed - using direct NRF_P0 access for speed */
|
|
|
|
/* CS: Output, HIGH (idle) */
|
|
nrf_gpio_cfg_output(DR_ADC_PIN_CS);
|
|
nrf_gpio_pin_set(DR_ADC_PIN_CS);
|
|
|
|
/* SCLK: Output, HIGH (idle) - per Figure 4 */
|
|
nrf_gpio_cfg_output(DR_ADC_PIN_SCLK);
|
|
nrf_gpio_pin_set(DR_ADC_PIN_SCLK);
|
|
|
|
/* SDATA: Input (ADC has push-pull output) */
|
|
nrf_gpio_cfg_input(DR_ADC_PIN_SDATA, NRF_GPIO_PIN_NOPULL);
|
|
|
|
ADC_LOG("ADC GPIO: CS=P%d.%02d SCLK=P%d.%02d SDATA=P%d.%02d",
|
|
DR_PIN_PORT(DR_ADC_PIN_CS), DR_PIN_NUM(DR_ADC_PIN_CS),
|
|
DR_PIN_PORT(DR_ADC_PIN_SCLK), DR_PIN_NUM(DR_ADC_PIN_SCLK),
|
|
DR_PIN_PORT(DR_ADC_PIN_SDATA), DR_PIN_NUM(DR_ADC_PIN_SDATA));
|
|
}
|
|
|
|
/**
|
|
* @brief Perform one ADC conversion (16 clock cycles)
|
|
* @return Raw 16-bit data (includes leading zeros)
|
|
*
|
|
* @details Timing optimized for maximum speed (~500ksps).
|
|
* At 64MHz CPU: 1 NOP = 15.625ns
|
|
*
|
|
* ADC specs (@ 3.3V):
|
|
* - tACC (data access time): 40ns max
|
|
* - tH (hold time): 7ns min
|
|
* - fSCLK: 4-10 MHz
|
|
*
|
|
* MUST be inlined to eliminate function call overhead for consistent timing.
|
|
*/
|
|
/*
|
|
* ADC_TRANSFER_BIT macro - one SCLK cycle for bit-bang SPI
|
|
* Captures data on falling edge, processes on rising edge
|
|
*
|
|
* Target: 10MHz (100ns cycle) = 50ns LOW + 50ns HIGH
|
|
* ADC121S051 timing requirements:
|
|
* - tCL (SCLK low time): 40ns min
|
|
* - tCH (SCLK high time): 40ns min
|
|
* - tACC (data access): 40ns max (data valid after SCLK falling)
|
|
*
|
|
* LOW phase: OUTCLR(~15ns) + NOP(~16ns) + IN read(~30ns) = ~61ns (meets tCL, tACC)
|
|
* HIGH phase: OUTSET(~15ns) + shift/OR(~40ns) = ~55ns (meets tCH)
|
|
* Total: ~116ns (~8.6MHz)
|
|
*
|
|
* Note: Removing HIGH phase NOP to reduce cycle time
|
|
*/
|
|
#define ADC_TRANSFER_BIT(data, in_reg) \
|
|
do { \
|
|
NRF_P0->OUTCLR = SCLK_MASK; \
|
|
__NOP(); \
|
|
in_reg = NRF_P0->IN; \
|
|
NRF_P0->OUTSET = SCLK_MASK; \
|
|
data = (data << 1) | ((in_reg & SDATA_MASK) ? 1 : 0); \
|
|
} while(0)
|
|
|
|
#define ADC_TRANSFER_BIT_LAST(data, in_reg) \
|
|
do { \
|
|
NRF_P0->OUTCLR = SCLK_MASK; \
|
|
__NOP(); \
|
|
in_reg = NRF_P0->IN; \
|
|
NRF_P0->OUTSET = SCLK_MASK; \
|
|
data = (data << 1) | ((in_reg & SDATA_MASK) ? 1 : 0); \
|
|
} while(0)
|
|
|
|
/*
|
|
* ADC_TRANSFER_16BITS macro - Complete 16-bit transfer
|
|
* Use this instead of adc_transfer() function for consistent first bit timing.
|
|
* Variables data and in_reg MUST be declared and initialized before calling.
|
|
*
|
|
* IMPORTANT: Call ADC_PREHEAT(data, in_reg) before cs_low() to warm up pipeline.
|
|
*/
|
|
#define ADC_TRANSFER_16BITS(data, in_reg) \
|
|
do { \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 15 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 14 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 13 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 12 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 11 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 10 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 9 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 8 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 7 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 6 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 5 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 4 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 3 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 2 */ \
|
|
ADC_TRANSFER_BIT(data, in_reg); /* Bit 1 */ \
|
|
ADC_TRANSFER_BIT_LAST(data, in_reg); /* Bit 0 */ \
|
|
NRF_P0->OUTSET = CS_MASK; \
|
|
__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); \
|
|
__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); \
|
|
__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); \
|
|
__NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); __NOP(); \
|
|
} while(0)
|
|
|
|
/*
|
|
* ADC_PREHEAT - Warm up instruction pipeline before timing-critical section
|
|
* This prevents the first SCLK cycle from being longer than others.
|
|
*
|
|
* CRITICAL: Must perform ACTUAL SCLK toggle to prime the GPIO write path!
|
|
* CS is still HIGH (idle), so this dummy toggle doesn't affect ADC.
|
|
*
|
|
* Sequence:
|
|
* 1. Dummy SCLK LOW/HIGH cycle (exact same code as ADC_TRANSFER_BIT)
|
|
* 2. SCLK remains HIGH (ready for cs_low)
|
|
*/
|
|
#define ADC_PREHEAT(data, in_reg) \
|
|
do { \
|
|
/* Dummy SCLK cycle - CS still HIGH, so ADC ignores this */ \
|
|
/* Must match ADC_TRANSFER_BIT timing exactly */ \
|
|
NRF_P0->OUTCLR = SCLK_MASK; \
|
|
__NOP(); \
|
|
in_reg = NRF_P0->IN; \
|
|
NRF_P0->OUTSET = SCLK_MASK; \
|
|
data = (data << 1) | ((in_reg & SDATA_MASK) ? 1 : 0); \
|
|
/* Second dummy cycle for better pipeline warm-up */ \
|
|
NRF_P0->OUTCLR = SCLK_MASK; \
|
|
__NOP(); \
|
|
in_reg = NRF_P0->IN; \
|
|
NRF_P0->OUTSET = SCLK_MASK; \
|
|
data = (data << 1) | ((in_reg & SDATA_MASK) ? 1 : 0); \
|
|
/* Reset data after priming */ \
|
|
data = 0; \
|
|
} while(0)
|
|
|
|
/* adc_transfer() function replaced by ADC_TRANSFER_16BITS macro for speed
|
|
* Kept here for reference but commented out to eliminate warning
|
|
*/
|
|
#if 0
|
|
/**
|
|
* @brief Perform one ADC conversion (16 clock cycles)
|
|
* @param data Pre-initialized to 0 by caller
|
|
* @param in_reg Pre-declared register variable by caller
|
|
* @return Raw 16-bit data (includes leading zeros)
|
|
*/
|
|
__attribute__((always_inline))
|
|
static inline uint16_t adc_transfer(uint16_t data, register uint32_t in_reg)
|
|
{
|
|
/* Implementation moved to ADC_TRANSFER_16BITS macro */
|
|
return data;
|
|
}
|
|
#endif
|
|
|
|
/*==============================================================================
|
|
* PUBLIC FUNCTIONS - INITIALIZATION
|
|
*============================================================================*/
|
|
|
|
dr_adc_err_t dr_adc_init(void)
|
|
{
|
|
ADC_LOG("ADC121S051 init...");
|
|
|
|
/* Initialize GPIO */
|
|
adc_gpio_init();
|
|
|
|
/* Ensure idle state (SCLK idle HIGH per Figure 4) */
|
|
cs_high();
|
|
sclk_high();
|
|
|
|
/* Wait for power stabilization */
|
|
nrf_delay_us(10);
|
|
|
|
/* Dummy read to wake up and clear stale data */
|
|
{
|
|
uint16_t dummy_data = 0;
|
|
register uint32_t dummy_reg = 0;
|
|
ADC_PREHEAT(dummy_data, dummy_reg);
|
|
NRF_P0->OUTCLR = CS_MASK;
|
|
__NOP(); __NOP(); __NOP(); __NOP(); /* tSU: CS setup time */
|
|
ADC_TRANSFER_16BITS(dummy_data, dummy_reg);
|
|
}
|
|
|
|
/* Quiet time between conversions */
|
|
__NOP(); __NOP(); __NOP(); __NOP();
|
|
|
|
m_initialized = true;
|
|
|
|
ADC_LOG("ADC121S051 ready (VREF=%dmV)", m_vref_mv);
|
|
|
|
return DR_ADC_OK;
|
|
}
|
|
|
|
void dr_adc_uninit(void)
|
|
{
|
|
if (!m_initialized) return;
|
|
|
|
cs_high();
|
|
sclk_high(); /* Idle HIGH per Figure 4 */
|
|
|
|
nrf_gpio_cfg_default(DR_ADC_PIN_CS);
|
|
nrf_gpio_cfg_default(DR_ADC_PIN_SCLK);
|
|
nrf_gpio_cfg_default(DR_ADC_PIN_SDATA);
|
|
|
|
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;
|
|
|
|
uint16_t data = 0;
|
|
register uint32_t in_reg = 0;
|
|
|
|
/* Critical section for precise timing */
|
|
__disable_irq();
|
|
|
|
/* Preheat pipeline before timing-critical section */
|
|
ADC_PREHEAT(data, in_reg);
|
|
|
|
/* CS LOW - starts conversion, samples VIN on this edge */
|
|
NRF_P0->OUTCLR = CS_MASK;
|
|
__NOP(); /* Match timing with other SCLK cycles */
|
|
|
|
/* Transfer 16 bits + CS HIGH (macro for consistent timing) */
|
|
ADC_TRANSFER_16BITS(data, in_reg);
|
|
|
|
__enable_irq();
|
|
|
|
/*
|
|
* Extract 12-bit data from 16-bit frame:
|
|
* Bit 15-13: Leading zeros (Z2, Z1, Z0) - must be 0
|
|
* Bit 12-1: Data bits (D11-D0)
|
|
* Bit 0: Don't care (trailing)
|
|
*
|
|
* Result = (data >> 1) & 0x0FFF
|
|
*/
|
|
|
|
/* Verify leading zeros (upper 3 bits should be 0) */
|
|
if ((data & 0xE000) != 0)
|
|
{
|
|
ADC_LOG("ADC frame error: leading zeros invalid (0x%04X)", data);
|
|
}
|
|
|
|
*raw_value = (data >> 1) & 0x0FFF;
|
|
|
|
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)
|
|
{
|
|
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;
|
|
|
|
uint16_t data = 0;
|
|
register uint32_t in_reg = 0;
|
|
|
|
/* Disable interrupts for continuous high-speed capture */
|
|
__disable_irq();
|
|
|
|
/* Preheat pipeline once before the loop */
|
|
ADC_PREHEAT(data, in_reg);
|
|
|
|
for (uint16_t i = 0; i < num_samples; i++)
|
|
{
|
|
data = 0; /* Reset for each sample */
|
|
|
|
/* CS LOW - starts conversion, samples VIN */
|
|
NRF_P0->OUTCLR = CS_MASK;
|
|
__NOP(); /* Match timing with other SCLK cycles */
|
|
|
|
/* Transfer 16 bits + CS HIGH (macro for consistent timing) */
|
|
ADC_TRANSFER_16BITS(data, in_reg);
|
|
|
|
/* Store 12-bit value */
|
|
buffer[i] = (data >> 1) & 0x0FFF;
|
|
}
|
|
|
|
__enable_irq();
|
|
|
|
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);
|
|
|
|
/*==============================================================================
|
|
* 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;
|
|
}
|
|
|
|
|
|
/*==============================================================================
|
|
* 4-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);
|
|
|
|
/* Select piezo channel */
|
|
dr_piezo_select_channel(piezo_ch);
|
|
|
|
/* Capture with averaging */
|
|
for (uint16_t avg_iter = 0; avg_iter < averaging; avg_iter++) {
|
|
if (avg_iter > 0) {
|
|
nrf_delay_us(500); /* Echo decay time */
|
|
}
|
|
|
|
dr_piezo_select_channel(piezo_ch);
|
|
|
|
/* Execute piezo burst */
|
|
switch (freq_option) {
|
|
case 0:
|
|
default:
|
|
dr_piezo_burst_sw_18mhz(cycles);
|
|
break;
|
|
case 1:
|
|
dr_piezo_burst_sw(cycles);
|
|
break;
|
|
case 2:
|
|
dr_piezo_burst_sw_20mhz(cycles);
|
|
break;
|
|
case 3:
|
|
dr_piezo_burst_sw_17mhz(cycles);
|
|
break;
|
|
}
|
|
|
|
if (delay_us > 0) {
|
|
nrf_delay_us(delay_us);
|
|
}
|
|
|
|
/* Capture to internal buffer */
|
|
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;
|
|
|
|
ADC_LOG("maa capture CH%u: peak=%u idx=%u baseline=%u",
|
|
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;
|
|
|
|
if (g_plat.log) g_plat.log("[maa] capturing CH%u\r\n", ch);
|
|
|
|
return 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]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @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 */
|
|
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;
|
|
|
|
ADC_LOG("maa_async: CH%u reb: sent (pkts=%u)", g_maa_ctx.current_ch, g_maa_ctx.total_packets);
|
|
}
|
|
|
|
/**
|
|
* @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);
|
|
|
|
/* 자동으로 켰으면 완료 후 전원 OFF */
|
|
if (g_maa_ctx.auto_powered) {
|
|
if (g_plat.log) g_plat.log("[Cmd_maa] TX/RX Active -> Sleep\r\n");
|
|
dr_piezo_power_off();
|
|
g_maa_ctx.auto_powered = false;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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?) */
|
|
|
|
ADC_LOG("maa_async_start: freq=%u delay=%u samples=%u cycles=%u avg=%u",
|
|
freq_option, delay_us, num_samples, g_maa_ctx.cycles, g_maa_ctx.averaging);
|
|
|
|
/* Capture CH0 */
|
|
g_maa_ctx.state = MAA_ASYNC_CAPTURING;
|
|
dr_adc_err_t err = maa_async_capture_channel(0);
|
|
if (err != DR_ADC_OK) {
|
|
ADC_LOG("maa_async_start: CH0 capture failed (%d)", err);
|
|
maa_async_send_completion(0xFFF0);
|
|
return err;
|
|
}
|
|
|
|
/* 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 {
|
|
/* Capture next channel */
|
|
g_maa_ctx.state = MAA_ASYNC_CAPTURING;
|
|
dr_adc_err_t err = maa_async_capture_channel(g_maa_ctx.current_ch);
|
|
if (err != DR_ADC_OK) {
|
|
ADC_LOG("maa_async: CH%u capture failed", g_maa_ctx.current_ch);
|
|
maa_async_send_completion(0xFFF0 | g_maa_ctx.current_ch);
|
|
return false;
|
|
}
|
|
/* Send header for new channel */
|
|
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);
|
|
}
|
|
|
|
maa_async_state_t maa_async_get_state(void)
|
|
{
|
|
return g_maa_ctx.state;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void maa_async_set_auto_power(bool on)
|
|
{
|
|
g_maa_ctx.auto_powered = on;
|
|
}
|
|
|
|
void maa_async_set_on_complete(void (*cb)(void))
|
|
{
|
|
g_maa_ctx.on_complete_cb = cb;
|
|
}
|
|
|