ADC 데이터 샘플 간 간격 줄이기
This commit is contained in:
@@ -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()` 경로를 유지한다.
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <zephyr/devicetree.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <nrfx_spim.h>
|
||||
#include <hal/nrf_spim.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
DBG_PRINTF("[ECHO] fast capture fallback err=%d\r\n", err);
|
||||
return echo_adc_capture_slow(samples, num_samples);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user