From 728f5dab769531e2051b4c319658d28cdbdf9897 Mon Sep 17 00:00:00 2001 From: jhchun Date: Thu, 11 Jun 2026 17:49:20 +0900 Subject: [PATCH] =?UTF-8?q?ADC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=83=98?= =?UTF-8?q?=ED=94=8C=20=EA=B0=84=20=EA=B0=84=EA=B2=A9=20=EC=A4=84=EC=9D=B4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plan/adc_zero_capture_notes.md | 121 ++++++++++++++++++++++++++++++++ src/drivers/echo_adc/echo_adc.c | 99 ++++++++++++++++++++++---- 2 files changed, 205 insertions(+), 15 deletions(-) create mode 100644 plan/adc_zero_capture_notes.md diff --git a/plan/adc_zero_capture_notes.md b/plan/adc_zero_capture_notes.md new file mode 100644 index 0000000..b5bf1b8 --- /dev/null +++ b/plan/adc_zero_capture_notes.md @@ -0,0 +1,121 @@ +# ADC 캡처 데이터가 전부 0으로 나온 상황 정리 + +작성일: 2026-06-11 + +## 배경 + +ADC echo 파형이 그래프 앞쪽으로 압축되어 보이는 문제가 있었다. + +원인 후보로 `src/drivers/echo_adc/echo_adc.c`에서 2바이트 샘플마다 `nrfx_spim_xfer()`를 한 번씩 호출하는 구조가 지목되었다. 이 구조는 샘플마다 드라이버 호출/대기/CS 처리 오버헤드가 붙기 때문에 실제 샘플 간격이 예상보다 커질 수 있다. + +그래서 샘플 간격을 줄이기 위해, 기존 레퍼런스 펌웨어처럼 SPIM 레지스터를 직접 만지는 hot loop 방식으로 바꾸는 실험을 했다. + +## 현재 정상 동작 기준 + +현재 정상 동작하는 기준 코드는 샘플 1개마다 아래 경로를 사용한다. + +```c +nrfx_spim_xfer_desc_t xfer = NRFX_SPIM_XFER_RX(buf, len); +nrfx_spim_xfer(&echo_spim, &xfer, 0); +``` + +그리고 `echo_adc_init()`에서는 CS 핀을 SPIM의 hardware SS 핀으로 넘긴다. + +```c +nrfx_spim_config_t config = NRFX_SPIM_DEFAULT_CONFIG( + gpio_abs_pin(&echo_sclk), + NRF_SPIM_PIN_NOT_CONNECTED, + gpio_abs_pin(&echo_miso), + gpio_abs_pin(&echo_cs)); +``` + +실패한 실험 뒤에는 이 정상 동작 기준으로 원복했다. + +## 실패 실험 1: SPIM SS를 끊고 CS를 GPIO로 수동 제어 + +`echo_adc_init()`에서 SPIM이 CS 핀을 소유하지 않도록 바꿨다. + +```c +NRF_SPIM_PIN_NOT_CONNECTED +``` + +즉 원래 있던 아래 설정을 제거했다. + +```c +gpio_abs_pin(&echo_cs) +``` + +그 뒤 CS를 GPIO 출력으로 직접 설정하고, `nrfx_spim_xfer()`와 direct capture loop 양쪽에서 수동으로 CS를 내리고 올렸다. + +```c +cs_port->OUTCLR = cs_mask; +nrfx_spim_xfer(&echo_spim, &xfer, 0); +cs_port->OUTSET = cs_mask; +``` + +캡처 루프도 아래처럼 SPIM 레지스터 직접 접근 방식으로 바꿨다. + +```c +spim->RXD.MAXCNT = 2U; + +for (...) { + cs_port->OUTCLR = cs_mask; + spim->RXD.PTR = (uint32_t)&raw_capture[i * 2U]; + spim->EVENTS_END = 0; + spim->TASKS_START = 1; + while (!spim->EVENTS_END) { + } + cs_port->OUTSET = cs_mask; +} +``` + +결과/위험: + +- ADC 데이터가 전부 0으로 나올 수 있다. +- 가능한 원인은 CS/SPIM 소유권 불일치, RX-only direct start가 `nrfx_spim_xfer()`와 다르게 동작하는 문제, 또는 nrfx가 내부적으로 준비하던 SPIM 상태가 빠진 문제다. +- 이 단순한 "SS 끊기 + direct loop" 패치는 그대로 쓰면 안 된다. + +## 실패 실험 2: Hardware SS를 유지한 채 같은 CS를 수동 토글 + +`echo_cs`는 계속 SPIM hardware SS 핀으로 설정해둔 상태에서, `echo_adc_capture()` 내부만 direct register loop로 바꿨다. + +예시: + +```c +NRF_SPIM_Type *spim = echo_spim.p_reg; +uint32_t cs_mask = BIT(echo_cs.pin); + +spim->RXD.MAXCNT = 2U; + +for (...) { + NRF_P0->OUTCLR = cs_mask; + spim->RXD.PTR = (uint32_t)&raw_capture[i * 2U]; + spim->EVENTS_END = 0; + spim->TASKS_START = 1; + while (!spim->EVENTS_END) { + } + NRF_P0->OUTSET = cs_mask; +} +``` + +결과/위험: + +- 이 경우도 ADC 데이터가 전부 0으로 나올 수 있다. +- 이유는 hardware SS가 켜진 상태에서 펌웨어가 같은 CS 핀을 직접 `NRF_P0->OUTCLR/OUTSET`으로 건드리기 때문이다. +- 즉 SPIM hardware SS 제어와 수동 GPIO CS 제어가 같은 핀에서 겹쳐 ADC 프레임 타이밍이 깨질 수 있다. +- hardware SS를 켠 상태에서 같은 CS 핀을 수동으로 토글하면 안 된다. + +## 핵심 정리 + +레퍼런스 펌웨어의 direct SPIM loop를 이 Zephyr/nrfx 포팅 코드에 내부 루프만 바꿔서 그대로 넣으면 안 된다. + +더 빠른 캡처 경로가 필요하면, 아래를 포함한 별도 low-level SPIM 경로로 제대로 구성해야 한다. + +- CS 소유권을 명확히 정하기: hardware SS 또는 manual GPIO 중 하나만 사용 +- SPIM PSEL 상태 확인 +- SCLK 생성을 보장하기 위한 RXD/TXD 설정 확인 +- `RXD.MAXCNT = 2` +- `EVENTS_END` 클리어 및 polling +- CS 타이밍 검증 + +그 전까지는 정상 동작이 확인된 `nrfx_spim_xfer()` 경로를 유지한다. diff --git a/src/drivers/echo_adc/echo_adc.c b/src/drivers/echo_adc/echo_adc.c index a507890..4716ce2 100644 --- a/src/drivers/echo_adc/echo_adc.c +++ b/src/drivers/echo_adc/echo_adc.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "echo_adc.h" @@ -30,6 +31,11 @@ static nrfx_spim_t echo_spim = NRFX_SPIM_INSTANCE(NRF_SPIM3); static bool echo_adc_initialized; static uint8_t raw_capture[ECHO_ADC_MAX_SAMPLES * 2]; +static NRF_GPIO_Type *gpio_reg_for_spec(const struct gpio_dt_spec *spec) +{ + return (spec->port == DEVICE_DT_GET(DT_NODELABEL(gpio1))) ? NRF_P1 : NRF_P0; +} + static uint32_t gpio_abs_pin(const struct gpio_dt_spec *spec) { uint32_t port = (spec->port == DEVICE_DT_GET(DT_NODELABEL(gpio1))) ? 1U : 0U; @@ -50,6 +56,79 @@ static int echo_adc_transfer_rx(uint8_t *buf, size_t len) return 0; } +static void echo_adc_unpack_samples(uint16_t *samples, uint16_t num_samples) +{ + for (uint16_t i = 0; i < num_samples; i++) + { + uint16_t raw16 = ((uint16_t)raw_capture[i * 2U] << 8) | + (uint16_t)raw_capture[i * 2U + 1U]; + samples[i] = (raw16 >> 1) & 0x0FFF; + } +} + +static bool echo_adc_samples_all_zero(const uint16_t *samples, uint16_t num_samples) +{ + for (uint16_t i = 0; i < num_samples; i++) + { + if (samples[i] != 0U) + { + return false; + } + } + + return true; +} + +static int echo_adc_capture_slow(uint16_t *samples, uint16_t num_samples) +{ + for (uint16_t i = 0; i < num_samples; i++) + { + int err = echo_adc_transfer_rx(&raw_capture[i * 2U], 2U); + if (err) + { + return err; + } + } + + echo_adc_unpack_samples(samples, num_samples); + return 0; +} + +static int echo_adc_capture_fast(uint16_t *samples, uint16_t num_samples) +{ + NRF_SPIM_Type *spim = echo_spim.p_reg; + NRF_GPIO_Type *cs_port = gpio_reg_for_spec(&echo_cs); + uint32_t cs_mask = BIT(echo_cs.pin); + + nrf_spim_enable(spim); + spim->RXD.MAXCNT = 2U; + + for (uint16_t i = 0; i < num_samples; i++) + { + cs_port->OUTCLR = cs_mask; + spim->RXD.PTR = (uint32_t)&raw_capture[i * 2U]; + spim->EVENTS_END = 0; + spim->TASKS_START = 1; + + uint32_t timeout = 10000U; + while (!spim->EVENTS_END && (timeout > 0U)) + { + timeout--; + } + + cs_port->OUTSET = cs_mask; + + if (timeout == 0U) + { + DBG_PRINTF("[ECHO] fast capture timeout\r\n"); + return -ETIMEDOUT; + } + } + + echo_adc_unpack_samples(samples, num_samples); + return 0; +} + int echo_adc_init(void) { if (echo_adc_initialized || nrfx_spim_init_check(&echo_spim)) @@ -124,22 +203,12 @@ int echo_adc_capture(uint16_t *samples, uint16_t num_samples) return err; } - for (uint16_t i = 0; i < num_samples; i++) + err = echo_adc_capture_fast(samples, num_samples); + if ((err == 0) && !echo_adc_samples_all_zero(samples, num_samples)) { - /* - * Keep the framing that matched the old firmware behavior: - * one 16-clock / 2-byte SPI frame per sample. - */ - err = echo_adc_transfer_rx(&raw_capture[i * 2U], 2U); - if (err) - { - return err; - } - - uint16_t raw16 = ((uint16_t)raw_capture[i * 2U] << 8) | - (uint16_t)raw_capture[i * 2U + 1U]; - samples[i] = (raw16 >> 1) & 0x0FFF; + return 0; } - return 0; + DBG_PRINTF("[ECHO] fast capture fallback err=%d\r\n", err); + return echo_adc_capture_slow(samples, num_samples); }