Signal Processing Pipeline
The NanoVNA-H converts RF measurements to complex S-parameters through a clever signal processing pipeline. This page traces the path from the audio codec through DFT processing to the final gamma (reflection coefficient) calculation.
Pipeline Overview
Section titled “Pipeline Overview”flowchart TB
subgraph HARDWARE[Hardware Layer]
MIX[Mixer Output<br/>IF at ~12 kHz]
CODEC[TLV320AIC3204<br/>192 kHz Stereo ADC]
DMA[DMA Transfer<br/>Double Buffer]
end
subgraph DSP[DSP Layer]
ACC[Accumulator<br/>Sin/Cos Correlation]
GAMMA[Gamma Calculation<br/>Complex Division]
end
subgraph CAL[Calibration Layer]
ERROR[Error Term<br/>Application]
RESULT[Calibrated<br/>S-Parameter]
end
MIX --> CODEC
CODEC --> DMA
DMA --> ACC
ACC --> GAMMA
GAMMA --> ERROR
ERROR --> RESULT Audio Codec Configuration
Section titled “Audio Codec Configuration”The TLV320AIC3204 audio codec is configured for high-speed stereo sampling in tlv320aic3204.c:
// Key configuration parameters#define AUDIO_ADC_FREQ 192000 // 192 kHz sample rate#define AUDIO_SAMPLES_COUNT 48 // Samples per measurement#define FREQUENCY_IF_K 12 // 12 kHz IF frequencyThe codec provides:
- Stereo input: Left = Reference signal, Right = Sample signal
- 192 kHz sample rate: Captures ~16 samples per IF cycle at 12 kHz
- 24-bit resolution: Provides excellent dynamic range
- Programmable gain: Adjusts sensitivity automatically
DMA Double Buffering
Section titled “DMA Double Buffering”Audio samples arrive via DMA (Direct Memory Access) without CPU intervention:
// Double buffer for continuous samplingstatic audio_sample_t rx_buffer[AUDIO_BUFFER_LEN * 2];
void i2s_lld_serve_rx_interrupt(uint32_t flags) { uint16_t count = AUDIO_BUFFER_LEN; // Select buffer half based on DMA flags audio_sample_t *p = (flags & STM32_DMA_ISR_TCIF) ? rx_buffer + AUDIO_BUFFER_LEN : rx_buffer;
if (wait_count >= config._bandwidth + 2) reset_dsp_accumerator(); // First buffer after freq change: discard else dsp_process(p, count); // Process the samples
--wait_count;}The double-buffer scheme allows:
- DMA fills one half while CPU processes the other
- No gaps in sampling
- Deterministic timing for DSP operations
The DFT: Sin/Cos Correlation
Section titled “The DFT: Sin/Cos Correlation”The core of the measurement is a single-bin DFT (Discrete Fourier Transform). Rather than computing a full FFT, the firmware correlates the input with precomputed sine and cosine tables at exactly the IF frequency:
Sin/Cos Lookup Table
Section titled “Sin/Cos Lookup Table”For 192 kHz sample rate and 12 kHz IF with 48 samples:
// Precomputed sin/cos values (scaled to 16-bit integer)static const int16_t sincos_tbl[48][2] = { { 6393, 32138}, { 27246, 18205}, { 32138,-6393}, { 18205,-27246}, {-6393,-32138}, {-27246,-18205}, {-32138, 6393}, {-18205, 27246}, // ... repeats for 3 complete cycles};Each entry contains {sin(n*omega), cos(n*omega)} where:
omega = 2*pi*IF/sample_rate = 2*pi*12000/192000 = pi/8- Values scaled by 32700 for 16-bit fixed-point math
DSP Processing (Cortex-M0 Version)
Section titled “DSP Processing (Cortex-M0 Version)”On the F072 (no hardware DSP), the correlation uses software multiplication:
void dsp_process(audio_sample_t *capture, size_t length) { int32_t samp_s = 0, samp_c = 0; // Sample sin/cos accumulators int32_t ref_s = 0, ref_c = 0; // Reference sin/cos accumulators
for (uint32_t i = 0; i < length; i += 2) { int16_t ref = capture[i+0]; // Reference channel (left) int16_t smp = capture[i+1]; // Sample channel (right) int32_t sin = sincos_tbl[i/2][0]; int32_t cos = sincos_tbl[i/2][1];
samp_s += (smp * sin) / 16; // Accumulate sample * sin samp_c += (smp * cos) / 16; // Accumulate sample * cos ref_s += (ref * sin) / 16; // Accumulate reference * sin ref_c += (ref * cos) / 16; // Accumulate reference * cos }
// Add to global accumulators (multiple buffers averaged) acc_samp_s += samp_s; acc_samp_c += samp_c; acc_ref_s += ref_s; acc_ref_c += ref_c;}DSP Processing (Cortex-M4 Version)
Section titled “DSP Processing (Cortex-M4 Version)”On the F303, hardware DSP instructions accelerate the math:
void dsp_process(audio_sample_t *capture, size_t length) { for (uint32_t i = 0; i < length/2; i++) { int32_t sc = ((int32_t *)sincos_tbl)[i]; // Load sin+cos as packed int32_t sr = ((int32_t *)capture)[i]; // Load ref+samp as packed
// SIMD multiply-accumulate instructions acc_samp_s = __smlaltb(acc_samp_s, sr, sc); // samp_s += smp * sin acc_samp_c = __smlaltt(acc_samp_c, sr, sc); // samp_c += smp * cos acc_ref_s = __smlalbb(acc_ref_s, sr, sc); // ref_s += ref * sin acc_ref_c = __smlalbt(acc_ref_c, sr, sc); // ref_c += ref * cos }}The __smlaltb family are ARM SIMD instructions that multiply 16-bit halves of 32-bit words and accumulate to 64-bit, processing two multiplications in one instruction.
Gamma Calculation
Section titled “Gamma Calculation”After accumulating samples across multiple buffers (determined by bandwidth setting), the complex reflection coefficient is calculated:
void calculate_gamma(float *gamma) { // Reference: complex number (ref_c + j*ref_s) // Sample: complex number (samp_c + j*samp_s) // Gamma = Sample / Reference
measure_t rs_rc = (measure_t) acc_ref_s / acc_ref_c; measure_t sc_rc = (measure_t) acc_samp_c / acc_ref_c; measure_t ss_rc = (measure_t) acc_samp_s / acc_ref_c;
measure_t rr = rs_rc * rs_rc + 1.0f;
gamma[0] = (sc_rc + ss_rc * rs_rc) / rr; // Real part gamma[1] = (ss_rc - sc_rc * rs_rc) / rr; // Imaginary part}The formula implements complex division:
gamma = (samp_c + j*samp_s) / (ref_c + j*ref_s)Using the identity for complex division and normalizing by ref_c to avoid overflow.
Bandwidth and Averaging
Section titled “Bandwidth and Averaging”The bandwidth setting controls how many DMA buffers are averaged before calculating gamma:
// Bandwidth settings (number of buffers to average)#define BANDWIDTH_4000 (1 - 1) // Single buffer: 4 kHz BW#define BANDWIDTH_1000 (4 - 1) // 4 buffers: 1 kHz BW#define BANDWIDTH_100 (40 - 1) // 40 buffers: 100 Hz BW#define BANDWIDTH_30 (132 - 1) // 132 buffers: ~30 Hz BWMore averaging:
- Reduces noise (averaging N samples improves SNR by sqrt(N))
- Slows sweep (more time per point)
- Increases dynamic range (can see weaker signals)
Sweep Timing
Section titled “Sweep Timing”The sweep function coordinates the entire measurement cycle:
static bool sweep(bool break_on_operation, uint16_t mask) { for (; p_sweep < sweep_points; p_sweep++) { freq_t frequency = getFrequency(p_sweep);
// Set new frequency, get settling delay delay = set_frequency(frequency);
// CH0: Reflection measurement if (mask & SWEEP_CH0_MEASURE) { tlv320aic3204_select(0); // Select CH0 input DSP_START(delay + st_delay); // Start accumulation // ... calibration lookup while waiting ... DSP_WAIT; // Wait for buffers (*sample_func)(&data[0]); // Calculate gamma apply_CH0_error_term(data, c_data); // Apply calibration }
// CH1: Transmission measurement if (mask & SWEEP_CH1_MEASURE) { tlv320aic3204_select(1); // Select CH1 input DSP_START(delay); DSP_WAIT; (*sample_func)(&data[2]); apply_CH1_error_term(data, c_data); }
// Store results measured[0][p_sweep][0] = data[0]; // S11 real measured[0][p_sweep][1] = data[1]; // S11 imag measured[1][p_sweep][0] = data[2]; // S21 real measured[1][p_sweep][1] = data[3]; // S21 imag }}Timing Constraints
Section titled “Timing Constraints”| Operation | Typical Time | Notes |
|---|---|---|
| Frequency change (same band) | 100-300 us | PLL relock |
| Frequency change (band switch) | 5 ms | Full PLL reset |
| Buffer accumulation | 250 us per buffer | 48 samples at 192 kHz |
| Gamma calculation | < 50 us | Floating point division |
| Calibration correction | < 20 us | Complex multiply/divide |
| Channel switch | 400 us | Codec input MUX settling |
Summary
Section titled “Summary”The signal pipeline converts RF reflections and transmissions into calibrated S-parameters through:
- Mixing: RF to audio IF (hardware)
- Sampling: 192 kHz stereo ADC (TLV320AIC3204)
- DMA: Double-buffered transfer (no CPU overhead)
- DFT: Sin/cos correlation at IF frequency
- Averaging: Multiple buffers for noise reduction
- Gamma: Complex division (sample/reference)
- Calibration: Error term correction
This approach achieves >70 dB dynamic range with a simple, low-cost audio codec by leveraging coherent detection at a known IF frequency.
Next Steps
Section titled “Next Steps”Learn how the RF signals are generated in Frequency Synthesis.