Bit-bang SPI(8MHz)에서 HW SPI(SPIM3, 16MHz)로 변경
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,9 @@
|
||||
* @author Charles KWON
|
||||
* @date 2025-12-15
|
||||
*
|
||||
* @details Software SPI (bit-bang) implementation for ADC121S051.
|
||||
* @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:
|
||||
@@ -32,6 +34,8 @@
|
||||
|
||||
#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 */
|
||||
@@ -72,17 +76,6 @@ extern void dr_piezo_power_off(void);
|
||||
#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
|
||||
*============================================================================*/
|
||||
@@ -90,247 +83,85 @@ extern void dr_piezo_power_off(void);
|
||||
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];
|
||||
|
||||
/* 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
|
||||
* @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 void adc_gpio_init(void)
|
||||
static uint16_t spim_read_raw(void)
|
||||
{
|
||||
/* Port pointers removed - using direct NRF_P0 access for speed */
|
||||
volatile uint8_t rx_buf[2] = {0, 0};
|
||||
NRF_SPIM_Type *spim = m_spim.p_reg;
|
||||
|
||||
/* CS: Output, HIGH (idle) */
|
||||
nrf_gpio_cfg_output(DR_ADC_PIN_CS);
|
||||
nrf_gpio_pin_set(DR_ADC_PIN_CS);
|
||||
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 */
|
||||
|
||||
/* 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));
|
||||
uint16_t raw16 = ((uint16_t)rx_buf[0] << 8) | rx_buf[1];
|
||||
return (raw16 >> 1) & 0x0FFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
nrfx_err_t err;
|
||||
|
||||
/* Dummy read to wake up and clear stale data */
|
||||
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)
|
||||
{
|
||||
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);
|
||||
return DR_ADC_ERR_NOT_INIT;
|
||||
}
|
||||
|
||||
/* Quiet time between conversions */
|
||||
__NOP(); __NOP(); __NOP(); __NOP();
|
||||
|
||||
/* 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)", m_vref_mv);
|
||||
|
||||
|
||||
ADC_LOG("ADC121S051 ready (VREF=%dmV, HW SPI)", m_vref_mv);
|
||||
|
||||
return DR_ADC_OK;
|
||||
}
|
||||
|
||||
@@ -338,15 +169,10 @@ 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);
|
||||
|
||||
nrfx_spim_uninit(&m_spim);
|
||||
|
||||
m_initialized = false;
|
||||
|
||||
|
||||
ADC_LOG("ADC121S051 uninitialized");
|
||||
}
|
||||
|
||||
@@ -364,40 +190,7 @@ 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;
|
||||
*raw_value = spim_read_raw();
|
||||
|
||||
return DR_ADC_OK;
|
||||
}
|
||||
@@ -444,37 +237,32 @@ dr_adc_err_t dr_adc_read_averaged(dr_adc_result_t *result, uint16_t num_samples)
|
||||
|
||||
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;
|
||||
|
||||
uint16_t data = 0;
|
||||
register uint32_t in_reg = 0;
|
||||
spim->RXD.MAXCNT = 2;
|
||||
|
||||
/* 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++)
|
||||
for (i = 0; i < num_samples; i++)
|
||||
{
|
||||
data = 0; /* Reset for each sample */
|
||||
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;
|
||||
|
||||
/* 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;
|
||||
raw16 = ((uint16_t)rx_buf[0] << 8) | rx_buf[1];
|
||||
buffer[i] = (raw16 >> 1) & 0x0FFF;
|
||||
}
|
||||
|
||||
__enable_irq();
|
||||
|
||||
return DR_ADC_OK;
|
||||
}
|
||||
|
||||
@@ -1485,6 +1273,9 @@ 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");
|
||||
@@ -1510,16 +1301,25 @@ dr_adc_err_t maa_async_start(uint8_t freq_option, uint16_t delay_us,
|
||||
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 */
|
||||
/* 전채널 캡처: BLE 전송 없이 6채널 전부 캡처 완료 후 TX 시작 */
|
||||
g_maa_ctx.state = MAA_ASYNC_CAPTURING;
|
||||
dr_adc_err_t err = maa_async_capture_channel(0);
|
||||
|
||||
if (err != DR_ADC_OK)
|
||||
for (ch = 0; ch < MAA_NUM_CHANNELS; ch++)
|
||||
{
|
||||
ADC_LOG("maa_async_start: CH0 capture failed (%d)", err);
|
||||
maa_async_send_completion(0xFFF0);
|
||||
return err;
|
||||
err = maa_async_capture_channel(ch);
|
||||
if (err != DR_ADC_OK)
|
||||
{
|
||||
//ADC_LOG("maa_async_start: CH%u capture failed (%d)", ch, err);
|
||||
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;
|
||||
}
|
||||
}
|
||||
//ADC_LOG("maa_async_start: all channels captured, starting TX");
|
||||
if (g_plat.log) g_plat.log("[maa] maa_async_start: all channels captured, starting TX");
|
||||
|
||||
/* 캡처 완료 → 피에조 전원 OFF (BLE 전송 중 불필요) */
|
||||
dr_piezo_power_off();
|
||||
g_maa_ctx.auto_powered = false; /* 이미 껐으므로 완료 후 중복 OFF 방지 */
|
||||
|
||||
/* Send CH0 header - this will trigger TX_RDY for subsequent packets */
|
||||
maa_async_send_header();
|
||||
@@ -1551,17 +1351,8 @@ bool maa_async_on_tx_ready(void)
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1589,6 +1380,11 @@ 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;
|
||||
@@ -1612,3 +1408,4 @@ void maa_async_set_on_complete(void (*cb)(void))
|
||||
g_maa_ctx.on_complete_cb = cb;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -455,6 +455,7 @@ typedef struct {
|
||||
uint16_t total_packets; /**< Total packets for current channel */
|
||||
uint16_t data_packets; /**< Data packets for current channel */
|
||||
bool auto_powered; /**< true: 자동 전원 ON → 완료 후 OFF */
|
||||
bool pre_capture_all; /**< true: 전채널 캡처 완료 후 일괄 전송 (mbb용) */
|
||||
void (*on_complete_cb)(void); /**< 비동기 캡처 완료 후 호출될 콜백 (NULL이면 미사용) */
|
||||
} maa_async_ctx_t;
|
||||
|
||||
@@ -507,6 +508,8 @@ void maa_async_abort(void);
|
||||
* @brief 자동 전원 플래그 설정 (완료 후 자동 power off)
|
||||
*/
|
||||
void maa_async_set_auto_power(bool on);
|
||||
void maa_async_set_pre_capture_all(bool on);
|
||||
|
||||
|
||||
/**
|
||||
* @brief 비동기 캡처 완료 콜백 설정
|
||||
|
||||
149
pc_firm/parser.c
149
pc_firm/parser.c
@@ -408,6 +408,8 @@ static int Cmd_mpc(const ParsedCmd *cmd); /* mpc? 피에조 버스트 발생 (
|
||||
static int Cmd_mec(const ParsedCmd *cmd); /* mec? 피에조 버스트 + 에코 캡처 (16비트 원시) */
|
||||
static int Cmd_maa(const ParsedCmd *cmd); /* maa? 6채널 전체 캡처 (비동기 전송) */
|
||||
static int Cmd_mbb(const ParsedCmd *cmd); /* mbb? 6채널 캡처 + 센서 측정 (배터리 + 온도 + IMU) */
|
||||
static int Cmd_mcf(const ParsedCmd *cmd); /* mcf? 피에조 파라미터 읽기 (FDS) */
|
||||
static int Cmd_mcs(const ParsedCmd *cmd); /* mcs? 피에조 파라미터 쓰기 (FDS) */
|
||||
|
||||
|
||||
/* ---- 명령 테이블 ---- */
|
||||
@@ -451,6 +453,8 @@ static CmdEntry g_cmd_table[] = {
|
||||
{ "mec?", true, Cmd_mec },
|
||||
{ "maa?", true, Cmd_maa },
|
||||
{ "mbb?", true, Cmd_mbb },
|
||||
{ "mcf?", true, Cmd_mcf },
|
||||
{ "mcs?", true, Cmd_mcs },
|
||||
};
|
||||
|
||||
/* 명령 테이블 엔트리 수 (컴파일 타임 계산) */
|
||||
@@ -838,12 +842,10 @@ static int Cmd_mec(const ParsedCmd *cmd)
|
||||
uint16_t averaging = 1; /* 기본 1 (평균화 없음), 최대 1000 */
|
||||
uint16_t piezo_ch = 0; /* 기본 채널 0 (유효: 0~7) */
|
||||
|
||||
/* 피에조 전원이 꺼져 있으면 자동으로 켜기 */
|
||||
bool auto_powered = false;
|
||||
/* 피에조 전원이 꺼져 있으면 켜기 */
|
||||
if (!dr_piezo_is_power_on()) {
|
||||
if (g_plat.log) g_plat.log("[Cmd_mec] TX/RX Sleep -> Active\r\n");
|
||||
dr_piezo_system_init();
|
||||
auto_powered = true;
|
||||
}
|
||||
|
||||
/* 6개 파라미터 순서대로 추출 (데이터 부족 시 기본값 유지) */
|
||||
@@ -884,13 +886,20 @@ static int Cmd_mec(const ParsedCmd *cmd)
|
||||
|
||||
if (g_plat.log && g_log_enable) {
|
||||
g_plat.log("[Cmd_mec] result=%d\r\n", err);
|
||||
|
||||
/* raw 데이터 덤프 */
|
||||
const uint16_t *buf = dr_adc_get_echo_buffer();
|
||||
g_plat.log("[mec] CH%u raw (%u samples):\r\n", piezo_ch, num_samples);
|
||||
for (uint16_t i = 0; i < num_samples; i++) {
|
||||
g_plat.log("%u ", buf[i]);
|
||||
if ((i + 1) % 16 == 0) g_plat.log("\r\n");
|
||||
}
|
||||
if (num_samples % 16 != 0) g_plat.log("\r\n");
|
||||
}
|
||||
|
||||
/* 자동으로 켰으면 완료 후 전원 OFF */
|
||||
if (auto_powered) {
|
||||
if (g_plat.log) g_plat.log("[Cmd_mec] TX/RX Active -> Sleep\r\n");
|
||||
dr_piezo_power_off();
|
||||
}
|
||||
/* 측정 완료 후 무조건 전원 OFF */
|
||||
if (g_plat.log) g_plat.log("[Cmd_mec] TX/RX Active -> Sleep\r\n");
|
||||
dr_piezo_power_off();
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -1007,14 +1016,11 @@ static int Cmd_maa(const ParsedCmd *cmd)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 피에조 전원이 꺼져 있으면 자동으로 켜기 */
|
||||
bool auto_powered = false;
|
||||
bool pwr = dr_piezo_is_power_on();
|
||||
if (g_plat.log) g_plat.log("[Cmd_maa] piezo_power=%u\r\n", pwr);
|
||||
if (!pwr) {
|
||||
/* 피에조 전원: OFF일 경우 ON → 완료 후 OFF */
|
||||
if (!dr_piezo_is_power_on())
|
||||
{
|
||||
if (g_plat.log) g_plat.log("[Cmd_maa] TX/RX Sleep -> Active\r\n");
|
||||
dr_piezo_system_init();
|
||||
auto_powered = true;
|
||||
}
|
||||
|
||||
/*=======================================================================
|
||||
@@ -1038,12 +1044,13 @@ static int Cmd_maa(const ParsedCmd *cmd)
|
||||
if (g_plat.log) g_plat.log("[Cmd_maa] start failed err=%d\r\n", err);
|
||||
single_format_data(ble_bin_buffer, "raa:", (uint16_t)(0xFF00 | err));
|
||||
dr_binary_tx_safe(ble_bin_buffer, 3);
|
||||
if (auto_powered) dr_piezo_power_off();
|
||||
dr_piezo_power_off();
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 자동 전원 플래그 설정 → 비동기 완료 시 자동 OFF */
|
||||
maa_async_set_auto_power(auto_powered);
|
||||
/* 측정 완료 시 무조건 전원 OFF */
|
||||
//maa_async_set_auto_power(true);
|
||||
//dr_piezo_power_off();
|
||||
|
||||
/* 즉시 반환 → 비동기 전송 진행 중 */
|
||||
/* 전체 완료 시 상태 머신이 "raa:" 응답 + 자동 power off 처리 */
|
||||
@@ -1086,7 +1093,7 @@ static void all_sensors(void)
|
||||
dr_sd_delay_ms(50); /* SAADC 콜백 완료 대기 */
|
||||
|
||||
info4 = false;
|
||||
|
||||
|
||||
/* rbb: 패킷 조립 및 전송 : [TAG 4B] [배터리 2B] [IMU 12B] [온도 2B] = 20바이트 = 10워드 */
|
||||
uint8_t *buf = ble_bin_buffer;
|
||||
|
||||
@@ -1094,7 +1101,8 @@ static void all_sensors(void)
|
||||
buf[4] = (uint8_t)(info_batt & 0xFF);
|
||||
buf[5] = (uint8_t)(info_batt >> 8);
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
buf[6 + i * 2] = (uint8_t)(info_imu[i] & 0xFF);
|
||||
buf[6 + i * 2 + 1] = (uint8_t)(info_imu[i] >> 8);
|
||||
}
|
||||
@@ -1119,11 +1127,18 @@ static void all_sensors(void)
|
||||
* 2) 각 채널(CH0~CH5): reb: [헤더] → red: [데이터...]
|
||||
* 3) 캡처 완료: raa: [상태]
|
||||
*/
|
||||
|
||||
static int Cmd_mbb(const ParsedCmd *cmd)
|
||||
{
|
||||
dr_adc_err_t err;
|
||||
|
||||
/* 파라미터 5개 수신 시 FDS에 저장 — 디버그 중 비활성화
|
||||
if (g_plat.log) g_plat.log("[Cmd_mbb] params updated: freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n",
|
||||
m_config.piezo_freq_option, m_config.piezo_cycles,
|
||||
m_config.piezo_averaging, m_config.piezo_delay_us,
|
||||
m_config.piezo_num_samples);
|
||||
|
||||
// 파라미터 5개 수신 시 FDS에 저장 — 디버그 중 비활성화
|
||||
/*
|
||||
if (cmd->data_len >= 10) {
|
||||
uint16_t freq, cycles, averaging, delay_us, num_samples;
|
||||
dr_get_u16(cmd, 0, &freq);
|
||||
@@ -1138,23 +1153,19 @@ static int Cmd_mbb(const ParsedCmd *cmd)
|
||||
m_config.piezo_delay_us = delay_us;
|
||||
m_config.piezo_num_samples = num_samples;
|
||||
config_save();
|
||||
|
||||
if (g_plat.log) g_plat.log("[Cmd_mbb] params updated: freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n",
|
||||
m_config.piezo_freq_option, m_config.piezo_cycles,
|
||||
m_config.piezo_averaging, m_config.piezo_delay_us,
|
||||
m_config.piezo_num_samples);
|
||||
}
|
||||
*/
|
||||
}*/
|
||||
|
||||
all_sensors();
|
||||
|
||||
dr_sd_delay_ms(20); /* SoftDevice 이벤트 정리 후 캡처 시작 */
|
||||
|
||||
if (maa_async_is_busy())
|
||||
{
|
||||
dr_ble_return_1("raa:", 0xFFFE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 피에조 전원: OFF일 경우 ON → 완료 후 OFF */
|
||||
/* 피에조 전원: OFF일 경우 ON */
|
||||
if (!dr_piezo_is_power_on())
|
||||
{
|
||||
if (g_plat.log) g_plat.log("[Cmd_mbb] TX/RX Sleep -> Active\r\n");
|
||||
@@ -1166,6 +1177,9 @@ static int Cmd_mbb(const ParsedCmd *cmd)
|
||||
m_config.piezo_averaging, m_config.piezo_delay_us,
|
||||
m_config.piezo_num_samples);
|
||||
|
||||
/* 전채널 선캡처 모드: 모든 채널 캡처 완료 후 BLE 전송 */
|
||||
maa_async_set_pre_capture_all(true);
|
||||
|
||||
/* 비동기 6채널 캡처 시작 (m_config 파라미터 사용) */
|
||||
err = maa_async_start(
|
||||
m_config.piezo_freq_option,
|
||||
@@ -1185,8 +1199,81 @@ static int Cmd_mbb(const ParsedCmd *cmd)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 완료 시 무조건 전원 OFF (전체 측정 사이클) */
|
||||
maa_async_set_auto_power(true);
|
||||
/* 완료 시 자동 전원 OFF */
|
||||
//maa_async_set_auto_power(true);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief mcf? - 피에조 파라미터 읽기 (FDS)
|
||||
*
|
||||
* 응답: rcf: [freq(2)] [cycles(2)] [avg(2)] [delay_us(2)] [num_samples(2)] = 14바이트 = 7워드
|
||||
*/
|
||||
static int Cmd_mcf(const ParsedCmd *cmd)
|
||||
{
|
||||
(void)cmd;
|
||||
|
||||
uint8_t *buf = ble_bin_buffer;
|
||||
buf[0] = 'r'; buf[1] = 'c'; buf[2] = 'f'; buf[3] = ':';
|
||||
buf[4] = m_config.piezo_freq_option; buf[5] = 0;
|
||||
buf[6] = m_config.piezo_cycles; buf[7] = 0;
|
||||
buf[8] = (uint8_t)(m_config.piezo_averaging & 0xFF); buf[9] = (uint8_t)(m_config.piezo_averaging >> 8);
|
||||
buf[10] = (uint8_t)(m_config.piezo_delay_us & 0xFF); buf[11] = (uint8_t)(m_config.piezo_delay_us >> 8);
|
||||
buf[12] = (uint8_t)(m_config.piezo_num_samples & 0xFF); buf[13] = (uint8_t)(m_config.piezo_num_samples >> 8);
|
||||
dr_binary_tx_safe(buf, 7); /* 14바이트 = 7워드 */
|
||||
|
||||
if (g_plat.log) g_plat.log("[Cmd_mcf] freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n",
|
||||
m_config.piezo_freq_option, m_config.piezo_cycles,
|
||||
m_config.piezo_averaging, m_config.piezo_delay_us,
|
||||
m_config.piezo_num_samples);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief mcs? - 피에조 파라미터 쓰기 (FDS)
|
||||
*
|
||||
* 파라미터: [freq(2)] [cycles(2)] [avg(2)] [delay_us(2)] [num_samples(2)] = 10바이트
|
||||
* 응답: rcs: [저장된 5개 값] = 14바이트 = 7워드
|
||||
*/
|
||||
static int Cmd_mcs(const ParsedCmd *cmd)
|
||||
{
|
||||
if (cmd->data_len < 10) {
|
||||
if (g_plat.log) g_plat.log("[Cmd_mcs] missing params (data_len=%u)\r\n", cmd->data_len);
|
||||
dr_ble_return_1("rcs:", 0xFFFF);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint16_t freq, cycles, averaging, delay_us, num_samples;
|
||||
dr_get_u16(cmd, 0, &freq);
|
||||
dr_get_u16(cmd, 1, &cycles);
|
||||
dr_get_u16(cmd, 2, &averaging);
|
||||
dr_get_u16(cmd, 3, &delay_us);
|
||||
dr_get_u16(cmd, 4, &num_samples);
|
||||
|
||||
m_config.piezo_freq_option = (uint8_t)freq;
|
||||
m_config.piezo_cycles = (uint8_t)cycles;
|
||||
m_config.piezo_averaging = averaging;
|
||||
m_config.piezo_delay_us = delay_us;
|
||||
m_config.piezo_num_samples = num_samples;
|
||||
config_save();
|
||||
|
||||
if (g_plat.log) g_plat.log("[Cmd_mcs] saved: freq=%u cyc=%u avg=%u delay=%u samples=%u\r\n",
|
||||
m_config.piezo_freq_option, m_config.piezo_cycles,
|
||||
m_config.piezo_averaging, m_config.piezo_delay_us,
|
||||
m_config.piezo_num_samples);
|
||||
|
||||
uint8_t *buf = ble_bin_buffer;
|
||||
buf[0] = 'r'; buf[1] = 'c'; buf[2] = 's'; buf[3] = ':';
|
||||
buf[4] = m_config.piezo_freq_option; buf[5] = 0;
|
||||
buf[6] = m_config.piezo_cycles; buf[7] = 0;
|
||||
buf[8] = (uint8_t)(m_config.piezo_averaging & 0xFF); buf[9] = (uint8_t)(m_config.piezo_averaging >> 8);
|
||||
buf[10] = (uint8_t)(m_config.piezo_delay_us & 0xFF); buf[11] = (uint8_t)(m_config.piezo_delay_us >> 8);
|
||||
buf[12] = (uint8_t)(m_config.piezo_num_samples & 0xFF); buf[13] = (uint8_t)(m_config.piezo_num_samples >> 8);
|
||||
dr_binary_tx_safe(buf, 7); /* 14바이트 = 7워드 */
|
||||
|
||||
return 1;
|
||||
}
|
||||
@@ -1319,8 +1406,6 @@ static int Cmd_mqz(const ParsedCmd *cmd)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 2026-03-17: mxz?, myz? 삭제 */
|
||||
|
||||
/*==============================================================================
|
||||
* IMU: 6축 원시 데이터 (단발 읽기)
|
||||
*============================================================================*/
|
||||
|
||||
Reference in New Issue
Block a user