/*******************************************************************************
 * @file    dr_adc121s051.c
 * @brief   ADC121S051 12-bit ADC Driver for nRF52840
 *          For 1.2MHz Piezo Echo Envelope Detection
 * @author  Charles KWON
 * @date    2025-12-15
 * 
 * @details Hardware SPI (nrfx_spim, SPIM2) implementation for ADC121S051.
 *          Replaces bit-bang SPI to eliminate __disable_irq() and prevent
 *          SoftDevice assertion failures during BLE connection events.
 *          Optimized for reading envelope-detected echo signals.
 *          
 *          ADC121S051 Serial Interface:
 *          
 *          CS    ────────┐                              ┌────────
 *                (HIGH)  └──────────────────────────────┘ (HIGH)
 *
 *          SCLK  ────────┐  ┌──┐  ┌──┐       ┌──┐  ┌──┐  ────────
 *                (HIGH)  └──┘  └──┘  └── ... ┘  └──┘  └──┘ (HIGH)
 *                         1    2    3        14   15   16
 *
 *          SDATA -------<Z2 ><Z1 ><Z0 ><D11>...<D1 ><D0 >------
 *                        |<- 3 zeros ->|<-- 12 data bits -->|
 *
 *          - CS idle HIGH, SCLK idle HIGH
 *          - CS falling edge: starts conversion, samples VIN
 *          - Data clocked out on SCLK falling edge
 *          - MSB first, straight binary output
 * 
 * @note    WARNING: Never hardcode pin numbers!
 *          Hardcoding may save a developer's time momentarily,
 *          but it will also shorten their lifespan.
 ******************************************************************************/

#include "dr_adc121s051.h"
#include "dr_util.h"
#include "nrfx_spim.h"
#include "nrf52840.h"
#include "nrf_gpio.h"
#include "nrf_delay.h"
#include <string.h>    /* memset */

/* BLE connection state from main.c */
extern volatile bool data_tx_in_progress;
extern volatile bool ble_connection_st;

/*==============================================================================
 * EXTERNAL PIEZO BURST FUNCTIONS
 *============================================================================*/
extern void dr_piezo_burst_sw(uint8_t cycles);
extern void dr_piezo_burst_sw_18mhz(uint8_t cycles);
extern void dr_piezo_burst_sw_20mhz(uint8_t cycles);
extern void dr_piezo_burst_sw_17mhz(uint8_t cycles);
extern void dr_piezo_burst_sw_22mhz(uint8_t cycles);
extern void dr_piezo_burst_sw_19mhz(uint8_t cycles);
extern void dr_piezo_power_off(void);

#include "parser.h"

/*==============================================================================
 * DEBUG CONFIGURATION
 *============================================================================*/
#define DEBUG_ADC
#ifdef DEBUG_ADC
#include "nrf_log.h"
#define ADC_LOG(...)    NRF_LOG_INFO(__VA_ARGS__)
#else
#define ADC_LOG(...)
#endif

/*==============================================================================
 * PRIVATE DEFINES
 *============================================================================*/

/* Extract pin number from NRF_GPIO_PIN_MAP (lower 5 bits) */
#define DR_PIN_NUM(pin)    ((pin) & 0x1F)
#define DR_PIN_PORT(pin)   (((pin) >> 5) & 0x01)

/*==============================================================================
 * PRIVATE VARIABLES
 *============================================================================*/

static bool     m_initialized = false;
static uint32_t m_vref_mv = DR_ADC_VREF_MV;

/* Hardware SPI instance (SPIM3 - supports up to 32MHz, 16MHz for ADC121S051) */
static nrfx_spim_t m_spim = NRFX_SPIM_INSTANCE(3);

/* Echo capture buffer (module-level for external access) */
static uint16_t m_echo_buffer[DR_ADC_ECHO_SAMPLES_MAX];

/*==============================================================================
 * PRIVATE FUNCTIONS
 *============================================================================*/

/**
 * @brief Perform one ADC121S051 conversion via hardware SPI (SPIM2).
 * @return 12-bit ADC value (0–4095)
 *
 * @details ADC121S051 serial frame (16 bits, MSB first):
 *          Bit 15-13: Leading zeros (Z2, Z1, Z0)
 *          Bit 12-1:  Data bits D11-D0
 *          Bit 0:     Don't care
 *          Extract: (raw16 >> 1) & 0x0FFF
 *
 *          SPI Mode 2 (CPOL=1, CPHA=0):
 *          - SCLK idle HIGH
 *          - Data valid on SCLK falling edge (sampled by SPIM on falling edge)
 *          - CS falling edge starts ADC conversion
 */
static uint16_t spim_read_raw(void)
{
    volatile uint8_t rx_buf[2] = {0, 0};
    NRF_SPIM_Type *spim = m_spim.p_reg;

    nrf_gpio_pin_clear(DR_ADC_PIN_CS);   /* CS LOW */
    spim->RXD.PTR    = (uint32_t)rx_buf;
    spim->RXD.MAXCNT = 2;
    spim->EVENTS_END = 0;
    spim->TASKS_START = 1;
    while (!spim->EVENTS_END) {}
    nrf_gpio_pin_set(DR_ADC_PIN_CS);     /* CS HIGH */

    uint16_t raw16 = ((uint16_t)rx_buf[0] << 8) | rx_buf[1];
    return (raw16 >> 1) & 0x0FFF;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - INITIALIZATION
 *============================================================================*/

dr_adc_err_t dr_adc_init(void)
{
    nrfx_err_t err;

    ADC_LOG("ADC121S051 init (HW SPI, SPIM3)...");

    nrfx_spim_config_t config = NRFX_SPIM_DEFAULT_CONFIG;
    config.sck_pin        = DR_ADC_PIN_SCLK;       /* P0.14 */
    config.mosi_pin       = NRFX_SPIM_PIN_NOT_USED;/* not used (read-only ADC) */
    config.miso_pin       = DR_ADC_PIN_SDATA;      /* P0.15 */
    config.ss_pin         = DR_ADC_PIN_CS;         /* P0.19 */
    config.frequency      = NRF_SPIM_FREQ_16M;
    config.mode           = NRF_SPIM_MODE_3;       /* CPOL=1 (idle HIGH), CPHA=1 (sample on rising - ADC presents on falling, stable on rising) */
    config.bit_order      = NRF_SPIM_BIT_ORDER_MSB_FIRST;
    config.ss_active_high = false;                 /* CS active LOW */

    err = nrfx_spim_init(&m_spim, &config, NULL, NULL);
    ADC_LOG("SPIM3 init result: 0x%08X (%s)", err, (err == NRFX_SUCCESS) ? "OK" : "FAIL");
    if (err != NRFX_SUCCESS)
    {
        return DR_ADC_ERR_NOT_INIT;
    }

    /* Wait for ADC power stabilization */
    nrf_delay_us(10);

    /* Dummy read to wake up ADC and clear stale conversion data */
    (void)spim_read_raw();

    m_initialized = true;

    ADC_LOG("ADC121S051 ready (VREF=%dmV, HW SPI)", m_vref_mv);

    return DR_ADC_OK;
}

void dr_adc_uninit(void)
{
    if (!m_initialized) return;

    nrfx_spim_uninit(&m_spim);

    m_initialized = false;

    ADC_LOG("ADC121S051 uninitialized");
}

bool dr_adc_is_initialized(void)
{
    return m_initialized;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - BASIC READ
 *============================================================================*/

dr_adc_err_t dr_adc_read_raw(uint16_t *raw_value)
{
    if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
    if (raw_value == NULL) return DR_ADC_ERR_INVALID_PARAM;

    *raw_value = spim_read_raw();

    return DR_ADC_OK;
}

dr_adc_err_t dr_adc_read(dr_adc_result_t *result)
{
    if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
    if (result == NULL) return DR_ADC_ERR_INVALID_PARAM;
    
    dr_adc_err_t err = dr_adc_read_raw(&result->raw);
    if (err != DR_ADC_OK) return err;
    
    result->voltage_mv = dr_adc_raw_to_mv(result->raw);
    
    return DR_ADC_OK;
}

dr_adc_err_t dr_adc_read_averaged(dr_adc_result_t *result, uint16_t num_samples)
{
    if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
    if (result == NULL) return DR_ADC_ERR_INVALID_PARAM;
    if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
        return DR_ADC_ERR_INVALID_PARAM;
    
    uint32_t sum = 0;
    uint16_t raw;
    
    for (uint16_t i = 0; i < num_samples; i++)
    {
        dr_adc_err_t err = dr_adc_read_raw(&raw);
        if (err != DR_ADC_OK) return err;
        sum += raw;
    }
    
    result->raw = (uint16_t)(sum / num_samples);
    result->voltage_mv = dr_adc_raw_to_mv(result->raw);
    
    return DR_ADC_OK;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - ECHO DETECTION
 *============================================================================*/

dr_adc_err_t dr_adc_capture_echo(uint16_t *buffer, uint16_t num_samples)
{
    volatile uint8_t rx_buf[2];
    uint32_t cs_mask = (1UL << DR_PIN_NUM(DR_ADC_PIN_CS));
    uint16_t raw16;
    uint16_t i;
    NRF_SPIM_Type *spim = m_spim.p_reg;

    if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
    if (buffer == NULL) return DR_ADC_ERR_INVALID_PARAM;
    if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
        return DR_ADC_ERR_INVALID_PARAM;

    spim->RXD.MAXCNT = 2;

    for (i = 0; i < num_samples; i++)
    {
        NRF_P0->OUTCLR   = cs_mask;
        spim->RXD.PTR    = (uint32_t)rx_buf;
        spim->EVENTS_END  = 0;
        spim->TASKS_START = 1;
        while (!spim->EVENTS_END) {}
        NRF_P0->OUTSET   = cs_mask;

        raw16 = ((uint16_t)rx_buf[0] << 8) | rx_buf[1];
        buffer[i] = (raw16 >> 1) & 0x0FFF;
    }

    return DR_ADC_OK;
}

dr_adc_err_t dr_adc_measure_echo(dr_adc_echo_t *echo, const dr_adc_echo_config_t *config)
{
    if (!m_initialized) return DR_ADC_ERR_NOT_INIT;
    if (echo == NULL) return DR_ADC_ERR_INVALID_PARAM;

    /* Use default config if not provided */
    dr_adc_echo_config_t cfg;
    if (config != NULL)
    {
        cfg = *config;
    }
    else
    {
        cfg.num_samples = DR_ADC_ECHO_SAMPLES_DEFAULT;
        cfg.threshold_raw = 100;    /* ~80mV threshold */
        cfg.delay_us = 0;
    }

    /* Validate config */
    if (cfg.num_samples == 0 || cfg.num_samples > DR_ADC_ECHO_SAMPLES_MAX)
        return DR_ADC_ERR_INVALID_PARAM;

    /* Use module-level buffer (16KB too large for stack) */

    /* Optional delay before capture */
    if (cfg.delay_us > 0)
    {
        nrf_delay_us(cfg.delay_us);
    }

    /* Capture echo samples into module-level buffer */
    dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, cfg.num_samples);
    if (err != DR_ADC_OK) return err;

    /* Analyze captured data */
    return dr_adc_analyze_echo(m_echo_buffer, cfg.num_samples, echo, cfg.threshold_raw);
}

dr_adc_err_t dr_adc_burst_and_capture(uint8_t cycles, uint16_t delay_us,
                                       uint16_t num_samples, dr_adc_echo_t *echo)
{
    (void)cycles;   /* Not used - ADC test only */

    if (echo == NULL) return DR_ADC_ERR_INVALID_PARAM;
    if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
        return DR_ADC_ERR_INVALID_PARAM;

    /* Initialize echo structure */
    echo->peak_raw = 0;
    echo->peak_mv = 0;
    echo->peak_index = 0;
    echo->peak_time_us = 0;
    echo->baseline_raw = 0;
    echo->num_samples = 0;

    /* Initialize ADC if not ready */
    if (!m_initialized)
    {
        dr_adc_err_t init_err = dr_adc_init();
        if (init_err != DR_ADC_OK) return init_err;
    }

    /* Settling time for ADC power */
    nrf_delay_us(100);

    /* Optional delay before capture */
    if (delay_us > 0)
    {
        nrf_delay_us(delay_us);
    }

    /* Capture ADC samples into module-level buffer */
    dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, num_samples);
    if (err != DR_ADC_OK) return err;

    /* Analyze captured data */
    return dr_adc_analyze_echo(m_echo_buffer, num_samples, echo, 100);
}

const uint16_t* dr_adc_get_echo_buffer(void)
{
    return m_echo_buffer;
}

dr_adc_err_t dr_adc_analyze_echo(const uint16_t *buffer, uint16_t num_samples,
                                  dr_adc_echo_t *echo, uint16_t threshold)
{
    if (buffer == NULL || echo == NULL) return DR_ADC_ERR_INVALID_PARAM;
    if (num_samples == 0) return DR_ADC_ERR_INVALID_PARAM;
    
    /* Calculate baseline from first few samples */
    uint16_t baseline_samples = (num_samples > 8) ? 8 : num_samples;
    echo->baseline_raw = dr_adc_calc_baseline(buffer, baseline_samples);
    
    /* Find peak */
    dr_adc_find_peak(buffer, num_samples, &echo->peak_raw, &echo->peak_index);
    
    /* Convert to voltage and time */
    echo->peak_mv = dr_adc_raw_to_mv(echo->peak_raw);

    /* peak_time_us: sample_index * 0.116us (8.6MHz sample rate) */
    echo->peak_time_us = (uint32_t)((float)echo->peak_index * DR_ADC_SAMPLE_INTERVAL_US);
    echo->num_samples = num_samples;
    
    /* Check if valid echo detected */
    if (echo->peak_raw < threshold || 
        echo->peak_raw <= echo->baseline_raw)
    {
        return DR_ADC_ERR_NO_ECHO;
    }
    
    return DR_ADC_OK;
}

void dr_adc_find_peak(const uint16_t *buffer, uint16_t num_samples,
                      uint16_t *peak_value, uint16_t *peak_index)
{
    /*
     * Posterior wall detection for bladder measurement
     * - Skip initial decay zone (index 0-29) where transducer coupling causes high values
     * - Search in range [30, 70] where posterior wall echo is expected
     * - Ignore values > 1500 (likely noise/saturation)
     */
    #define SEARCH_START    30
    #define SEARCH_END      70
    #define MAX_VALID_VALUE 1500  /* Values above this are likely noise */

    uint16_t max_val = 0;
    uint16_t max_idx = 0;

    uint16_t start = (num_samples > SEARCH_START) ? SEARCH_START : 0;
    uint16_t end = (num_samples > SEARCH_END) ? SEARCH_END : num_samples;

    for (uint16_t i = start; i < end; i++)
    {
        uint16_t val = buffer[i];
        /* Skip abnormally high values (noise/saturation) */
        if (val > MAX_VALID_VALUE) continue;

        if (val > max_val)
        {
            max_val = val;
            max_idx = i;
        }
    }

    if (peak_value != NULL) *peak_value = max_val;
    if (peak_index != NULL) *peak_index = max_idx;
}

uint16_t dr_adc_calc_baseline(const uint16_t *buffer, uint16_t num_samples)
{
    if (buffer == NULL || num_samples == 0) return 0;
    
    uint32_t sum = 0;
    for (uint16_t i = 0; i < num_samples; i++)
    {
        sum += buffer[i];
    }
    
    return (uint16_t)(sum / num_samples);
}

/*==============================================================================
 * PUBLIC FUNCTIONS - UTILITY
 *============================================================================*/

uint32_t dr_adc_raw_to_mv(uint16_t raw_value)
{
    /* V_mv = (raw * VREF_mv) / 4096 */
    return ((uint32_t)raw_value * m_vref_mv) / (DR_ADC_MAX_VALUE + 1);
}

void dr_adc_set_vref(uint32_t vref_mv)
{
    m_vref_mv = vref_mv;
    ADC_LOG("VREF set to %d mV", vref_mv);
}

uint32_t dr_adc_get_vref(void)
{
    return m_vref_mv;
}

/*==============================================================================
 * PUBLIC FUNCTIONS - DEBUG
 *============================================================================*/

bool dr_adc_test(void)
{
    if (!m_initialized)
    {
        ADC_LOG("Test FAIL: not initialized");
        return false;
    }
    
    uint16_t raw;
    dr_adc_err_t err = dr_adc_read_raw(&raw);
    
    if (err != DR_ADC_OK)
    {
        ADC_LOG("Test FAIL: read error %d", err);
        return false;
    }
    
    if (raw > DR_ADC_MAX_VALUE)
    {
        ADC_LOG("Test FAIL: invalid value %d", raw);
        return false;
    }
    
    ADC_LOG("Test PASS: raw=%d (%dmV)", raw, dr_adc_raw_to_mv(raw));
    return true;
}

void dr_adc_print_buffer(const uint16_t *buffer, uint16_t num_samples)
{
#ifdef DEBUG_ADC
    if (buffer == NULL || num_samples == 0) return;

    ADC_LOG("Echo buffer (%d samples):", num_samples);

    for (uint16_t i = 0; i < num_samples; i++)
    {
        ADC_LOG("[%3d] %4d (%4dmV)", i, buffer[i], dr_adc_raw_to_mv(buffer[i]));
    }
#else
    (void)buffer;
    (void)num_samples;
#endif
}

/*==============================================================================
 * BLE TRANSMISSION
 *============================================================================*/

/* External BLE NUS functions and variables from main.c */
extern void dr_binary_tx_safe(uint8_t const *ble_bin_buff, uint16_t length);
extern void dr_sd_delay_ms(uint32_t ms);

/* Platform interface (log function) */
#include "parser.h"

/*==============================================================================
 * INTEGRATED BURST + CAPTURE + TRANSMIT
 *============================================================================*/

/* External piezo burst function */
extern void dr_piezo_burst_sw(uint8_t cycles);

/* BLE packet constants */
#define BLE_MTU_SIZE            240
#define BLE_FIRST_HEADER_LEN    14      /* "rec:" + header */
#define BLE_CONT_HEADER_LEN     6       /* "red:" + packet_index */
#define BLE_FIRST_DATA_LEN      (BLE_MTU_SIZE - BLE_FIRST_HEADER_LEN)
#define BLE_CONT_DATA_LEN       (BLE_MTU_SIZE - BLE_CONT_HEADER_LEN)
#define BLE_PACKET_DELAY_MS     100     /* Inter-packet delay - allow BLE TX buffer to drain */
#define BLE_FIRST_PACKET_DELAY  5       /* Minimal delay */

/* Protocol version markers (OR'd with packet count in reb: header) */
#define MEC_VERSION_MARKER      0xD000  /* vD: raa only (no ree) - ree causes packet loss */
#define MAA_VERSION_MARKER      0xF000  /* vF: maa? hardcoded 8-channel debug */

/*==============================================================================
 * PIEZO CHANNEL SELECTION
 *============================================================================*/

/* Piezo MUX pins (8ch) */
#define DR_PIEZO_EN_MUXA    NRF_GPIO_PIN_MAP(0, 21)  /**< MUXA Enable */
#define DR_PIEZO_EN_MUXB    NRF_GPIO_PIN_MAP(0, 23)  /**< MUXB Enable */
#define DR_PIEZO_MUX_SEL1   NRF_GPIO_PIN_MAP(0, 28)  /**< MUX Select 1 */
#define DR_PIEZO_MUX_SEL0   NRF_GPIO_PIN_MAP(1, 10)  /**< MUX Select 0 */

 /*
 * Channel mapping (8ch)              jhChun 26.01.29

 *         | EN MUXA | EN MUXB |  SEL 0  |  SEL 1
 *   ----------------------------------------------
 *   CH A0 |    1    |    0    |    0    |    0
 *   CH A1 |    1    |    0    |    0    |    1
 *   CH A2 |    1    |    0    |    1    |    0
 *   CH A3 |    1    |    0    |    1    |    1
 *   ----------------------------------------------
 *   CH B3 |    0    |    1    |    0    |    0
 *   CH B2 |    0    |    1    |    0    |    1
 *   CH B1 |    0    |    1    |    1    |    0
 *   CH B0 |    0    |    1    |    1    |    1
 */

/* dr_piezo_select_channel is defined in dr_piezo.c */
extern void dr_piezo_select_channel(uint8_t channel);

/*==============================================================================
 * INTEGRATED BURST + CAPTURE + TRANSMIT
 *============================================================================*/

/**
 * @brief Integrated burst + capture + BLE transmit (16-bit raw data)
 *
 * PROTOCOL v3: Small header packet + separate data packets with delays
 * (Large first packets were being dropped by BLE stack)
 *
 * Response format:
 *   Packet 1 (reb:): header only (14 bytes)
 *   Packet 2~N (red:): pkt_idx(2) + raw_data(up to 234 bytes = 117 samples)
 *   Final (ree:): total_packets(2)
 *
 * With 140 samples: 280 bytes = reb:(14) + red:(6+234) + red:(6+46) + ree:(6)
 */
dr_adc_err_t dr_adc_burst_capture_transmit(uint8_t freq_option, uint16_t delay_us,
                                            uint16_t num_samples, uint8_t cycles,
                                            uint16_t averaging, uint8_t piezo_ch,
                                            uint8_t *ble_buffer, uint8_t skip_raa)
{
    if (ble_buffer == NULL) return DR_ADC_ERR_INVALID_PARAM;
    if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
        return DR_ADC_ERR_INVALID_PARAM;
    if (freq_option > 9) freq_option = 0;       /* Invalid -> default 1.8MHz */
    if (cycles < 3 || cycles > 7) cycles = 5;   /* Valid range: 3~7, default 5 */
    if (averaging == 0) averaging = 1;          /* Minimum 1 */
    if (averaging > 1000) averaging = 1000;     /* Maximum 1000 */
    if (piezo_ch >= MAA_NUM_CHANNELS) piezo_ch = 0;  /* 채널 범위 검증 */

    dr_adc_echo_t echo;
    echo.peak_raw = 0;
    echo.peak_mv = 0;
    echo.peak_index = 0;
    echo.peak_time_us = 0;
    echo.baseline_raw = 0;
    echo.num_samples = 0;

    /* Accumulator buffer for averaging (32-bit to prevent overflow) */
    static uint32_t accum_buffer[DR_ADC_ECHO_SAMPLES_MAX];

    /* CRITICAL: Clear entire accumulator buffer at start of each mec call */
    memset(accum_buffer, 0, sizeof(accum_buffer));

    if (!m_initialized)
    {
        dr_adc_err_t init_err = dr_adc_init();
        if (init_err != DR_ADC_OK) {
            return init_err;
        }
    }

    /* Settling time for ADC power */
    nrf_delay_us(100);

    /*--- Step 2: Select piezo channel ---*/
    dr_piezo_select_channel(piezo_ch);

    /*--- Step 3~5: Burst + Delay + Capture ---*/
    if (averaging == 1) {
        /*=== SINGLE MEASUREMENT (no averaging) - direct path ===*/

        /* Execute piezo burst based on frequency option */
        switch (freq_option) {
            case 0:
            default:
                dr_piezo_burst_sw_18mhz(cycles);  /* 1.8MHz (default) */
                break;
            case 1:
                dr_piezo_burst_sw(cycles);        /* 2.1MHz */
                break;
            case 2:
                dr_piezo_burst_sw_20mhz(cycles);  /* 2.0MHz */
                break;
            case 3:
                dr_piezo_burst_sw_17mhz(cycles);  /* 1.7MHz */
                break;
            case 4:
                dr_piezo_burst_sw_22mhz(cycles);  /* 2.2MHz */
                break;
            case 9:
                dr_piezo_burst_sw_19mhz(cycles);  /* 1.9MHz */
                break;
        }

        /* Delay before capture (configurable, default 20us) */
        if (delay_us > 0) {
            nrf_delay_us(delay_us);
        }

        /* Capture ADC samples directly to m_echo_buffer */
        dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, num_samples);
        if (err != DR_ADC_OK) return err;

        //ADC_LOG("mec: single measurement (no averaging)");
    }
    else {
        /*=== MULTIPLE MEASUREMENTS (with averaging) ===*/
        /* Note: accum_buffer already cleared by memset at function start */

        for (uint16_t avg_iter = 0; avg_iter < averaging; avg_iter++) {
            /* Wait for previous echo to decay before next measurement
             * 1ms = ~77cm round-trip decay time (sound speed 1.54mm/us)
             * Skip delay on first iteration */
            if (avg_iter > 0) {
                nrf_delay_us(500);  /* 500us between measurements */
            }

            /* Re-select piezo channel before each burst
             * (burst functions may modify P1 port state) */
            dr_piezo_select_channel(piezo_ch);

            /* Execute piezo burst based on frequency option */
            switch (freq_option) {
                case 0:
                default:
                    dr_piezo_burst_sw_18mhz(cycles);  /* 1.8MHz (default) */
                    break;
                case 1:
                    dr_piezo_burst_sw(cycles);        /* 2.1MHz */
                    break;
                case 2:
                    dr_piezo_burst_sw_20mhz(cycles);  /* 2.0MHz */
                    break;
                case 3:
                    dr_piezo_burst_sw_17mhz(cycles);  /* 1.7MHz */
                    break;
                case 4:
                    dr_piezo_burst_sw_22mhz(cycles);  /* 2.2MHz */
                    break;
                case 9:
                    dr_piezo_burst_sw_19mhz(cycles);  /* 1.9MHz */
                    break;
            }

            /* Delay before capture (configurable, default 20us) */
            if (delay_us > 0) {
                nrf_delay_us(delay_us);
            }

            /* Capture ADC samples */
            dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, num_samples);
            if (err != DR_ADC_OK) return err;

            /* Accumulate samples */
            for (uint16_t i = 0; i < num_samples; i++) {
                accum_buffer[i] += m_echo_buffer[i];
            }
        }

        /* Calculate average and store back to m_echo_buffer */
        for (uint16_t i = 0; i < num_samples; i++) {
            m_echo_buffer[i] = (uint16_t)(accum_buffer[i] / averaging);
        }

        ADC_LOG("mec: averaged %u measurements", averaging);
    }

    /*--- Step 6: Analyze averaged data ---*/
    dr_adc_analyze_echo(m_echo_buffer, num_samples, &echo, 100);

    /*--- Step 6: Transmit via BLE - PROTOCOL v3 ---*/
    /* Packet 1: reb:(4) + header(10) = 14 bytes ONLY (no data) */
    /* Packet 2~N: red:(4) + pkt_idx(2) + data(up to 234 bytes) */
    /* Final: ree:(4) + total_pkts(2) = 6 bytes */

    uint16_t total_data_bytes = echo.num_samples * 2;
    uint16_t cont_pkt_data_cap = BLE_MTU_SIZE - BLE_CONT_HEADER_LEN;    /* 234 bytes */

    /* Calculate data packets needed (header packet doesn't carry data) */
    uint16_t data_packets = (total_data_bytes + cont_pkt_data_cap - 1) / cont_pkt_data_cap;

    /* Version marker: select based on skip_raa (0=mec, 1=maa) */
    uint16_t version_marker = skip_raa ? MAA_VERSION_MARKER : MEC_VERSION_MARKER;
    uint16_t pkts_with_ver = data_packets | version_marker;

    ADC_LOG("mec v6: samples=%u data=%u bytes, packets=%u", echo.num_samples, total_data_bytes, data_packets);

    /* Packet 1: reb: header ONLY (14 bytes) - small packet won't be dropped */
    ble_buffer[0] = 'r';
    ble_buffer[1] = 'e';
    ble_buffer[2] = 'b';
    ble_buffer[3] = ':';
    ble_buffer[4] = (uint8_t)(pkts_with_ver & 0xFF);
    ble_buffer[5] = (uint8_t)(pkts_with_ver >> 8);
    ble_buffer[6] = (uint8_t)(echo.peak_raw & 0xFF);
    ble_buffer[7] = (uint8_t)(echo.peak_raw >> 8);
    ble_buffer[8] = (uint8_t)(echo.peak_index & 0xFF);
    ble_buffer[9] = (uint8_t)(echo.peak_index >> 8);
    ble_buffer[10] = (uint8_t)(echo.baseline_raw & 0xFF);
    ble_buffer[11] = (uint8_t)(echo.baseline_raw >> 8);
    ble_buffer[12] = (uint8_t)(echo.num_samples & 0xFF);
    ble_buffer[13] = (uint8_t)(echo.num_samples >> 8);

    dr_binary_tx_safe(ble_buffer, 7);  /* Send header only: 14 bytes = 7 words */
    dr_sd_delay_ms(BLE_PACKET_DELAY_MS);  /* Wait for BLE stack */

    /* Data packets (red:) - starting from pkt index 0 */
    uint16_t src_idx = 0;
    for (uint16_t pkt = 0; pkt < data_packets; pkt++) {
        ble_buffer[0] = 'r';
        ble_buffer[1] = 'e';
        ble_buffer[2] = 'd';
        ble_buffer[3] = ':';
        ble_buffer[4] = (uint8_t)(pkt & 0xFF);
        ble_buffer[5] = (uint8_t)(pkt >> 8);

        uint16_t dst_idx = 6;
        uint16_t bytes_this_pkt = 0;
        while (src_idx < echo.num_samples && bytes_this_pkt < cont_pkt_data_cap) {
            uint16_t sample = m_echo_buffer[src_idx++] & 0x0FFF;
            ble_buffer[dst_idx++] = (uint8_t)(sample & 0xFF);
            ble_buffer[dst_idx++] = (uint8_t)(sample >> 8);
            bytes_this_pkt += 2;
        }

        dr_binary_tx_safe(ble_buffer, dst_idx / 2);  /* bytes to words */
        dr_sd_delay_ms(BLE_PACKET_DELAY_MS);  /* Inter-packet delay */
    }

    /* vD: Send raa: only - ree: causes subsequent packet loss */
    /* DrBench now saves channel data when receiving raa: */
    /* skip_raa: 0=send raa (mec), 1=skip raa (maa - caller sends final raa) */
    if (!skip_raa) {
        ble_buffer[0] = 'r';
        ble_buffer[1] = 'a';
        ble_buffer[2] = 'a';
        ble_buffer[3] = ':';
        ble_buffer[4] = 0x00;
        ble_buffer[5] = 0x00;
        dr_binary_tx_safe(ble_buffer, 3);  /* 6 bytes = 3 words */
    }

    ADC_LOG("mec v6: complete - reb + red*%u + skip_raa=%u (%u samples)", data_packets, skip_raa, echo.num_samples);

    return DR_ADC_OK;
}


/*==============================================================================
 * CHANNEL CAPTURE FUNCTIONS (maa? command support)
 *============================================================================*/

dr_adc_err_t dr_adc_capture_channel_only(uint8_t freq_option, uint16_t delay_us,
                                          uint16_t num_samples, uint8_t cycles,
                                          uint16_t averaging, uint8_t piezo_ch,
                                          dr_maa_channel_t *out_channel)
{
    if (out_channel == NULL) return DR_ADC_ERR_INVALID_PARAM;
    if (num_samples == 0 || num_samples > MAA_SAMPLES_MAX)
        return DR_ADC_ERR_INVALID_PARAM;
    if (freq_option > 3) freq_option = 0;
    if (cycles < 3 || cycles > 7) cycles = 5;
    if (averaging == 0) averaging = 1;
    if (averaging > 1000) averaging = 1000;
    if (piezo_ch > (MAA_NUM_CHANNELS - 1)) piezo_ch = 0;

    /* Accumulator buffer for averaging */
    static uint32_t accum_buffer[MAA_SAMPLES_MAX];
    memset(accum_buffer, 0, num_samples * sizeof(uint32_t));

    if (!m_initialized)
    {
        dr_adc_err_t init_err = dr_adc_init();
        if (init_err != DR_ADC_OK) return init_err;
    }

    //nrf_delay_us(100);

    /* 채널 선택(MUX) : 1.3ms */
    dr_piezo_select_channel(piezo_ch);

    /* 채널당 반복 측정 횟수만큼 */
    for (uint16_t avg_iter = 0; avg_iter < averaging; avg_iter++)
    {
        /* TX 펄스 출력(Burst) */
        switch (freq_option) {
            case 0:
            default:
                dr_piezo_burst_sw_18mhz(cycles);
                break;
            case 1:
                dr_piezo_burst_sw(cycles); /* 2.1MHz */
                break;
            case 2:
                dr_piezo_burst_sw_20mhz(cycles);
                break;
            case 3:
                dr_piezo_burst_sw_17mhz(cycles);
                break;
        }

        /* TX 펄스 출력 후 ADC 시작 시까지 대기 시간 */
        if (delay_us > 0)
        {
            nrf_delay_us(delay_us);
        }

        /* 측정 ADC 샘플 수만큼 캡처 */
        dr_adc_err_t err = dr_adc_capture_echo(m_echo_buffer, num_samples);

        if (err != DR_ADC_OK) return err;

        /* Accumulate */
        for (uint16_t i = 0; i < num_samples; i++)
        {
            accum_buffer[i] += m_echo_buffer[i];
        }
    }

    /* Calculate average and copy to output */
    for (uint16_t i = 0; i < num_samples; i++)
    {
        out_channel->samples[i] = (uint16_t)(accum_buffer[i] / averaging);
    }

    out_channel->num_samples = num_samples;

    /* Analyze echo data */
    dr_adc_echo_t echo;
    dr_adc_analyze_echo(out_channel->samples, num_samples, &echo, 100);
    out_channel->peak_raw = echo.peak_raw;
    out_channel->peak_index = echo.peak_index;
    out_channel->baseline_raw = echo.baseline_raw;

    /* peak, index, baseline 확인용 로그 */
    //if (g_plat.log) g_plat.log("CH%u: peak=%u index=%u baseline=%u\r\n", piezo_ch, out_channel->peak_raw, out_channel->peak_index, out_channel->baseline_raw);

    return DR_ADC_OK;
}


dr_adc_err_t dr_adc_transmit_channel(const dr_maa_channel_t *ch_data,
                                      uint8_t *ble_buffer)
{
    /* Debug BEFORE null check to see if we even enter the function */
    dr_ble_debug(0x00FF, 0xAAAA);  /* Function called marker */

    if (ch_data == NULL || ble_buffer == NULL) {
        dr_ble_debug(0x00FE, (ch_data == NULL ? 0x0001 : 0) | (ble_buffer == NULL ? 0x0002 : 0));
        return DR_ADC_ERR_INVALID_PARAM;
    }

    dr_ble_debug(0x0100, ch_data->num_samples);  /* Function entry - params valid */

    uint16_t total_data_bytes = ch_data->num_samples * 2;
    uint16_t cont_pkt_data_cap = BLE_MTU_SIZE - BLE_CONT_HEADER_LEN;  /* 234 bytes */
    uint16_t data_packets = (total_data_bytes + cont_pkt_data_cap - 1) / cont_pkt_data_cap;
    uint16_t total_packets = 1 + data_packets;

    /* Version marker: MAA_VERSION_MARKER | total_packets */
    uint16_t pkt_with_ver = total_packets | MAA_VERSION_MARKER;

    dr_ble_debug(0x0101, total_packets);  /* Before reb: */

    /* Packet 1: reb: header (14 bytes) */
    ble_buffer[0] = 'r';
    ble_buffer[1] = 'e';
    ble_buffer[2] = 'b';
    ble_buffer[3] = ':';
    ble_buffer[4] = (uint8_t)(pkt_with_ver & 0xFF);
    ble_buffer[5] = (uint8_t)(pkt_with_ver >> 8);
    ble_buffer[6] = (uint8_t)(ch_data->peak_raw & 0xFF);
    ble_buffer[7] = (uint8_t)(ch_data->peak_raw >> 8);
    ble_buffer[8] = (uint8_t)(ch_data->peak_index & 0xFF);
    ble_buffer[9] = (uint8_t)(ch_data->peak_index >> 8);
    ble_buffer[10] = (uint8_t)(ch_data->baseline_raw & 0xFF);
    ble_buffer[11] = (uint8_t)(ch_data->baseline_raw >> 8);
    ble_buffer[12] = (uint8_t)(ch_data->num_samples & 0xFF);
    ble_buffer[13] = (uint8_t)(ch_data->num_samples >> 8);

    /* Send reb: header */
    /* Use dr_binary_tx_safe like mec? does, with dr_sd_delay_ms for SoftDevice */
    dr_binary_tx_safe(ble_buffer, 7);
    dr_sd_delay_ms(BLE_PACKET_DELAY_MS);

    dr_ble_debug(0x0102, data_packets);  /* After reb: */

    /* Data packets (red:) */
    uint16_t src_idx = 0;
    for (uint16_t pkt = 0; pkt < data_packets; pkt++) {
        ble_buffer[0] = 'r';
        ble_buffer[1] = 'e';
        ble_buffer[2] = 'd';
        ble_buffer[3] = ':';
        ble_buffer[4] = (uint8_t)(pkt & 0xFF);
        ble_buffer[5] = (uint8_t)(pkt >> 8);

        uint16_t dst_idx = 6;
        uint16_t bytes_this_pkt = 0;
        while (src_idx < ch_data->num_samples && bytes_this_pkt < cont_pkt_data_cap) {
            uint16_t sample = ch_data->samples[src_idx++] & 0x0FFF;
            ble_buffer[dst_idx++] = (uint8_t)(sample & 0xFF);
            ble_buffer[dst_idx++] = (uint8_t)(sample >> 8);
            bytes_this_pkt += 2;
        }

        dr_binary_tx_safe(ble_buffer, dst_idx / 2);
        dr_sd_delay_ms(BLE_PACKET_DELAY_MS);

        dr_ble_debug(0x0103, pkt);  /* red: packet sent */
    }

    dr_ble_debug(0x0104, 0);  /* Before ree: */

    /* Final packet: ree: */
    ble_buffer[0] = 'r';
    ble_buffer[1] = 'e';
    ble_buffer[2] = 'e';
    ble_buffer[3] = ':';
    ble_buffer[4] = (uint8_t)(total_packets & 0xFF);
    ble_buffer[5] = (uint8_t)(total_packets >> 8);
    dr_binary_tx_safe(ble_buffer, 3);

    dr_ble_debug(0x010F, 0);  /* Function complete */

    return DR_ADC_OK;
}


/*==============================================================================
 * DELTA COMPRESSION FUNCTIONS
 *============================================================================*/

dr_adc_err_t dr_adc_delta_compress(const uint16_t *samples, uint16_t num_samples,
                                    uint8_t *out_buffer, uint16_t *out_size)
{
    if (samples == NULL || out_buffer == NULL || out_size == NULL)
        return DR_ADC_ERR_INVALID_PARAM;
    if (num_samples == 0) {
        *out_size = 0;
        return DR_ADC_OK;
    }

    uint16_t idx = 0;

    /* First sample: 16-bit raw value */
    out_buffer[idx++] = (uint8_t)(samples[0] & 0xFF);
    out_buffer[idx++] = (uint8_t)(samples[0] >> 8);

    /* Delta encode remaining samples */
    for (uint16_t i = 1; i < num_samples; i++) {
        int16_t delta = (int16_t)samples[i] - (int16_t)samples[i-1];

        if (delta >= -127 && delta <= 127) {
            /* Normal delta: fits in signed 8-bit */
            out_buffer[idx++] = (int8_t)delta;
        } else {
            /* Out of range: escape + 16-bit raw value */
            out_buffer[idx++] = DELTA_ESCAPE_BYTE;
            out_buffer[idx++] = (uint8_t)(samples[i] & 0xFF);
            out_buffer[idx++] = (uint8_t)(samples[i] >> 8);
        }
    }

    *out_size = idx;
    return DR_ADC_OK;
}


dr_adc_err_t dr_adc_transmit_channel_delta(const dr_maa_channel_t *ch_data,
                                            uint8_t *ble_buffer)
{
    if (ch_data == NULL || ble_buffer == NULL) return DR_ADC_ERR_INVALID_PARAM;

    /* Compress data first */
    static uint8_t delta_buffer[400];  /* Worst case: 140*3 = 420 bytes */
    uint16_t compressed_size = 0;

    dr_adc_err_t err = dr_adc_delta_compress(ch_data->samples, ch_data->num_samples,
                                              delta_buffer, &compressed_size);
    if (err != DR_ADC_OK) return err;

    ADC_LOG("maa delta: %u samples -> %u bytes (%.0f%%)",
            ch_data->num_samples, compressed_size,
            100.0f * compressed_size / (ch_data->num_samples * 2));

    uint16_t cont_pkt_data_cap = BLE_MTU_SIZE - BLE_CONT_HEADER_LEN;  /* 234 bytes */
    uint16_t data_packets = (compressed_size + cont_pkt_data_cap - 1) / cont_pkt_data_cap;
    uint16_t total_packets = 1 + data_packets;

    /* Version marker for delta mode: 0xC000 | total_packets */
    uint16_t pkt_with_ver = total_packets | 0xC000;  /* v2 delta marker */

    /* Packet 1: rdb: header (16 bytes) - includes compressed_size */
    ble_buffer[0] = 'r';
    ble_buffer[1] = 'd';
    ble_buffer[2] = 'b';
    ble_buffer[3] = ':';
    ble_buffer[4] = (uint8_t)(pkt_with_ver & 0xFF);
    ble_buffer[5] = (uint8_t)(pkt_with_ver >> 8);
    ble_buffer[6] = (uint8_t)(ch_data->peak_raw & 0xFF);
    ble_buffer[7] = (uint8_t)(ch_data->peak_raw >> 8);
    ble_buffer[8] = (uint8_t)(ch_data->peak_index & 0xFF);
    ble_buffer[9] = (uint8_t)(ch_data->peak_index >> 8);
    ble_buffer[10] = (uint8_t)(ch_data->baseline_raw & 0xFF);
    ble_buffer[11] = (uint8_t)(ch_data->baseline_raw >> 8);
    ble_buffer[12] = (uint8_t)(ch_data->num_samples & 0xFF);
    ble_buffer[13] = (uint8_t)(ch_data->num_samples >> 8);
    ble_buffer[14] = (uint8_t)(compressed_size & 0xFF);
    ble_buffer[15] = (uint8_t)(compressed_size >> 8);

    dr_binary_tx_safe(ble_buffer, 8);  /* 16 bytes = 8 words */
    dr_sd_delay_ms(BLE_PACKET_DELAY_MS);

    /* Data packets (rdd:) */
    uint16_t src_idx = 0;
    for (uint16_t pkt = 0; pkt < data_packets; pkt++)
    {
        ble_buffer[0] = 'r';
        ble_buffer[1] = 'd';
        ble_buffer[2] = 'd';
        ble_buffer[3] = ':';
        ble_buffer[4] = (uint8_t)(pkt & 0xFF);
        ble_buffer[5] = (uint8_t)(pkt >> 8);

        uint16_t dst_idx = 6;
        uint16_t bytes_this_pkt = 0;
        while (src_idx < compressed_size && bytes_this_pkt < cont_pkt_data_cap)
        {
            ble_buffer[dst_idx++] = delta_buffer[src_idx++];
            bytes_this_pkt++;
        }

        /* Pad to word boundary if needed */
        if (dst_idx & 1)
        {
            ble_buffer[dst_idx++] = 0;
        }

        dr_binary_tx_safe(ble_buffer, dst_idx / 2);
        dr_sd_delay_ms(BLE_PACKET_DELAY_MS);
    }

    /* Final packet: rde: */
    ble_buffer[0] = 'r';
    ble_buffer[1] = 'd';
    ble_buffer[2] = 'e';
    ble_buffer[3] = ':';
    ble_buffer[4] = (uint8_t)(total_packets & 0xFF);
    ble_buffer[5] = (uint8_t)(total_packets >> 8);
    dr_binary_tx_safe(ble_buffer, 3);
    dr_sd_delay_ms(BLE_PACKET_DELAY_MS);

    return DR_ADC_OK;
}


/*==============================================================================
 * ASYNC MAA - Non-blocking 8-channel capture
 *
 * State machine driven by BLE TX complete events (BLE_NUS_EVT_TX_RDY)
 * This prevents blocking the main loop and allows SoftDevice to process events.
 *============================================================================*/

/* Global async context */
static maa_async_ctx_t g_maa_ctx = { .state = MAA_ASYNC_IDLE };

/* Version marker for async MAA */
#define MAA_ASYNC_VERSION_MARKER    0xC000  /* vC: async 8-channel + raa delay fix + cleanup */

/**
 * @brief Capture one channel (internal helper)
 */
static dr_adc_err_t maa_async_capture_channel(uint8_t ch)
{
    if (ch > (MAA_NUM_CHANNELS - 1)) return DR_ADC_ERR_INVALID_PARAM;

    dr_adc_err_t err = dr_adc_capture_channel_only(
        g_maa_ctx.freq_option,
        g_maa_ctx.delay_us,
        g_maa_ctx.num_samples,
        g_maa_ctx.cycles,
        g_maa_ctx.averaging,
        ch,
        &g_maa_ctx.channels[ch]
    );

    return err;
}

/**
 * @brief Send reb: header for current channel
 */
static void maa_async_send_header(void)
{
    dr_maa_channel_t *ch = &g_maa_ctx.channels[g_maa_ctx.current_ch];
    uint8_t *buf = g_maa_ctx.ble_buffer;

    uint16_t total_data_bytes = ch->num_samples * 2;
    uint16_t cont_pkt_data_cap = BLE_MTU_SIZE - BLE_CONT_HEADER_LEN;
    g_maa_ctx.data_packets = (total_data_bytes + cont_pkt_data_cap - 1) / cont_pkt_data_cap;
    g_maa_ctx.total_packets = 1 + g_maa_ctx.data_packets;

    uint16_t pkts_with_ver = g_maa_ctx.total_packets | MAA_ASYNC_VERSION_MARKER;

    /* reb: header (14 bytes) */
    buf[0] = 'r'; buf[1] = 'e'; buf[2] = 'b'; buf[3] = ':';
    buf[4] = (uint8_t)(pkts_with_ver & 0xFF);
    buf[5] = (uint8_t)(pkts_with_ver >> 8);
    buf[6] = (uint8_t)(ch->peak_raw & 0xFF);
    buf[7] = (uint8_t)(ch->peak_raw >> 8);
    buf[8] = (uint8_t)(ch->peak_index & 0xFF);
    buf[9] = (uint8_t)(ch->peak_index >> 8);
    buf[10] = (uint8_t)(ch->baseline_raw & 0xFF);
    buf[11] = (uint8_t)(ch->baseline_raw >> 8);
    buf[12] = (uint8_t)(ch->num_samples & 0xFF);
    buf[13] = (uint8_t)(ch->num_samples >> 8);

    dr_binary_tx_safe(buf, 7);  /* 14 bytes = 7 words */

    /* 50ms BLE 대기 시간을 활용하여 raw 덤프 로그 (캡처 시간에 영향 없음) 
    if (g_plat.log)
    {
        if (g_maa_ctx.current_ch > 0) g_plat.log("\r\n");
        g_plat.log("CH%u raw (%u samples):\r\n", g_maa_ctx.current_ch, ch->num_samples);
        for (uint16_t i = 0; i < ch->num_samples; i++)
        {
            g_plat.log("%u ", ch->samples[i]);
            if ((i + 1) % 16 == 0) g_plat.log("\r\n");
        }
        if (ch->num_samples % 16 != 0) g_plat.log("\r\n");
    }*/

    dr_sd_delay_ms(50);  /* Allow BLE stack to process TX */

    g_maa_ctx.current_pkt = 0;
    g_maa_ctx.data_offset = 0;
    g_maa_ctx.state = MAA_ASYNC_TX_DATA;
}

/**
 * @brief Send next red: data packet
 * @return true if more packets to send, false if done with current channel
 */
static bool maa_async_send_data_packet(void)
{
    dr_maa_channel_t *ch = &g_maa_ctx.channels[g_maa_ctx.current_ch];
    uint8_t *buf = g_maa_ctx.ble_buffer;
    uint16_t total_data_bytes = ch->num_samples * 2;

    if (g_maa_ctx.data_offset >= total_data_bytes)
    {
        return false;  /* All data sent */
    }

    uint16_t pkt_idx = g_maa_ctx.current_pkt;
    uint16_t remaining = total_data_bytes - g_maa_ctx.data_offset;
    uint16_t chunk_size = (remaining > BLE_CONT_DATA_LEN) ? BLE_CONT_DATA_LEN : remaining;

    /* red: packet header */
    buf[0] = 'r'; buf[1] = 'e'; buf[2] = 'd'; buf[3] = ':';
    buf[4] = (uint8_t)(pkt_idx & 0xFF);
    buf[5] = (uint8_t)(pkt_idx >> 8);

    /* Copy sample data */
    uint16_t src_sample_idx = g_maa_ctx.data_offset / 2;
    uint16_t dst_idx = 6;
    for (uint16_t i = 0; i < chunk_size / 2 && src_sample_idx < ch->num_samples; i++, src_sample_idx++)
    {
        uint16_t sample = ch->samples[src_sample_idx];
        buf[dst_idx++] = (uint8_t)(sample & 0xFF);
        buf[dst_idx++] = (uint8_t)(sample >> 8);
    }

    dr_binary_tx_safe(buf, dst_idx / 2);
    dr_sd_delay_ms(50);  /* Allow BLE stack to process TX */

    g_maa_ctx.data_offset += chunk_size;
    g_maa_ctx.current_pkt++;

    //ADC_LOG("maa_async: CH%u red:%u (%u/%u bytes)", g_maa_ctx.current_ch, pkt_idx, g_maa_ctx.data_offset, total_data_bytes);

    return (g_maa_ctx.data_offset < total_data_bytes);
}

/**
 * @brief Send raa: completion marker
 */
static void maa_async_send_completion(uint16_t status)
{
    uint8_t *buf = g_maa_ctx.ble_buffer;

    /* Wait for previous TX to complete before sending raa: */
    dr_sd_delay_ms(50);

    buf[0] = 'r'; buf[1] = 'a'; buf[2] = 'a'; buf[3] = ':';
    buf[4] = (uint8_t)(status & 0xFF);
    buf[5] = (uint8_t)(status >> 8);

    dr_binary_tx_safe(buf, 3);

    //if (g_plat.log) g_plat.log("-------------------------------------------------------------------------------------\r\n");

    /* 캡처 완료 → Piezo TX/RX 전원 OFF */
    dr_piezo_power_off();

    g_maa_ctx.state = MAA_ASYNC_IDLE;
    //ADC_LOG("maa_async: complete, status=0x%04X", status);

    /* 완료 콜백 호출 (mbb? 등에서 센서 측정 체인 트리거용) */
    if (g_maa_ctx.on_complete_cb)
    {
        void (*cb)(void) = g_maa_ctx.on_complete_cb;
        g_maa_ctx.on_complete_cb = NULL;  /* 1회성: 재호출 방지 */
        cb();
    }
}

/*==============================================================================
 * PUBLIC ASYNC API
 *============================================================================*/

dr_adc_err_t maa_async_start(uint8_t freq_option, uint16_t delay_us, uint16_t num_samples, uint8_t cycles, uint16_t averaging, uint8_t *ble_buffer)
{
    dr_adc_err_t err;
    uint8_t ch;

    if (g_maa_ctx.state != MAA_ASYNC_IDLE)
    {
        ADC_LOG("maa_async_start: busy");
        return DR_ADC_ERR_NOT_INIT;  /* Already running */
    }

    if (ble_buffer == NULL) return DR_ADC_ERR_INVALID_PARAM;
    if (num_samples == 0 || num_samples > DR_ADC_ECHO_SAMPLES_MAX)
        return DR_ADC_ERR_INVALID_PARAM;

    /* Initialize context */
    g_maa_ctx.freq_option = freq_option;
    g_maa_ctx.delay_us = delay_us;
    g_maa_ctx.num_samples = num_samples;
    g_maa_ctx.cycles = (cycles < 3 || cycles > 7) ? 5 : cycles;
    g_maa_ctx.averaging = (averaging == 0) ? 1 : ((averaging > 1000) ? 1000 : averaging);
    g_maa_ctx.ble_buffer = ble_buffer;
    g_maa_ctx.current_ch = 0;
    g_maa_ctx.current_pkt = 0;
    g_maa_ctx.data_offset = 0;
    g_maa_ctx.on_complete_cb = NULL;  /* 기본: 콜백 없음 (maa?) */

    /* 전채널 캡처: BLE 전송 없이 6채널 전부 캡처 완료 후 TX 시작 */
    g_maa_ctx.state = MAA_ASYNC_CAPTURING;

    for (ch = 0; ch < MAA_NUM_CHANNELS; ch++)
    {
        err = maa_async_capture_channel(ch);
        if (err != DR_ADC_OK)
        {
            if (g_plat.log) g_plat.log("[maa] maa_async_start: CH%u capture failed (%d)", ch, err);
            maa_async_send_completion(0xFFF0 | ch);
            return err;
        }
    }

    /* peak 로그와 raw 덤프 사이 구분 */
    //if (g_plat.log) g_plat.log("\r\n");

    /* 캡처 완료 → Piezo TX/RX 전원 OFF (BLE 전송 중 불필요) */
    dr_piezo_power_off();

    /* Send CH0 header - this will trigger TX_RDY for subsequent packets */
    maa_async_send_header();

    return DR_ADC_OK;
}

bool maa_async_on_tx_ready(void)
{
    if (g_maa_ctx.state == MAA_ASYNC_IDLE)
    {
        return false;
    }

    switch (g_maa_ctx.state) {
        case MAA_ASYNC_TX_DATA:
            /* Send next data packet */
            if (!maa_async_send_data_packet())
            {
                /* Current channel done, move to next */
                g_maa_ctx.current_ch++;

                if (g_maa_ctx.current_ch >= MAA_NUM_CHANNELS)
                {
                    /* All channels done */
                    g_maa_ctx.state = MAA_ASYNC_COMPLETE;
                    maa_async_send_completion(0x0000);
                    return false;
                }
                else
                {
                    /* 이미 전채널 캡처됨, 바로 헤더 전송 */
                    g_maa_ctx.state = MAA_ASYNC_CAPTURING;
                    maa_async_send_header();
                }
            }
            return true;

        case MAA_ASYNC_TX_HEADER:
            /* Header sent, start sending data */
            g_maa_ctx.state = MAA_ASYNC_TX_DATA;
            maa_async_send_data_packet();
            return true;

        case MAA_ASYNC_CAPTURING:
            /* Shouldn't happen - capture is synchronous */
            return true;

        case MAA_ASYNC_COMPLETE:
        case MAA_ASYNC_IDLE:
        default:
            return false;
    }
}

bool maa_async_is_busy(void)
{
    return (g_maa_ctx.state != MAA_ASYNC_IDLE);
}

void maa_async_set_pre_capture_all(bool on)
{
    g_maa_ctx.pre_capture_all = on;
}

maa_async_state_t maa_async_get_state(void)
{
    return g_maa_ctx.state;
}

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_on_complete(void (*cb)(void))
{
    g_maa_ctx.on_complete_cb = cb;
}


