Skip to content

Calibration Interpolation

Calibration is performed at specific frequency points, but you might want to measure at different frequencies later. The NanoVNA-H firmware uses linear interpolation to estimate calibration data between stored points, allowing you to change your sweep range without full recalibration.

The firmware sets the interpolation flag when your current sweep does not exactly match the calibration:

static bool needInterpolate(freq_t start, freq_t stop, uint16_t points) {
return start != cal_frequency0 || stop != cal_frequency1 || points != cal_sweep_points;
}

Interpolation is triggered when any of these differ from calibration:

  • Start frequency
  • Stop frequency
  • Number of sweep points

For each measurement frequency, the firmware finds the two nearest calibration points and linearly interpolates between them.

Given a measurement frequency f, find where it falls in the calibration range:

static void cal_interpolate(int idx, freq_t f, float data[CAL_TYPE_COUNT][2]) {
int eterm;
uint16_t src_points = cal_sweep_points - 1;
// Direct copy if index provided (no interpolation needed)
if (idx >= 0)
goto copy_point;
// Boundary cases: use first or last point
if (f <= cal_frequency0) {
idx = 0;
goto copy_point;
}
if (f >= cal_frequency1) {
idx = src_points;
goto copy_point;
}

The firmware calculates which calibration points bracket the measurement frequency and the interpolation factor k:

// Calculate position in calibration span
freq_t span = cal_frequency1 - cal_frequency0;
idx = (uint64_t)(f - cal_frequency0) * (uint64_t)src_points / span;
// Calculate exact frequencies of bracketing calibration points
uint64_t v = (uint64_t)span * idx + src_points/2;
freq_t src_f0 = cal_frequency0 + (v ) / src_points;
freq_t src_f1 = cal_frequency0 + (v + span) / src_points;
freq_t delta = src_f1 - src_f0;
// If exactly on a calibration point, no interpolation needed
if (f == src_f0) goto copy_point;
// Calculate interpolation factor (0.0 to 1.0)
float k = (delta == 0) ? 0.0f : (float)(f - src_f0) / delta;

A critical detail: the NanoVNA switches between direct output and harmonic modes at certain frequencies. Interpolating across this boundary causes errors because the error terms change discontinuously.

// Avoid glitch between frequencies in different harmonic modes
uint32_t hf0 = si5351_get_harmonic_lvl(src_f0);
if (hf0 != si5351_get_harmonic_lvl(src_f1)) {
// f is in prev harmonic, need extrapolate from prev 2 points
if (hf0 == si5351_get_harmonic_lvl(f)) {
if (idx < 1) goto copy_point;
// Use two points from same harmonic region
// ... extrapolation code ...
}
// f is in next harmonic, extrapolate from next 2 points
else {
if (idx >= src_points - 1) goto copy_point;
// ... extrapolation code ...
}
}

Finally, linear interpolation is applied to each error term:

// Linear interpolation for each error term
for (eterm = 0; eterm < CAL_TYPE_COUNT; eterm++) {
// Get calibration values at bracketing points
float r0 = cal_data[eterm][idx][0]; // Real part at point idx
float i0 = cal_data[eterm][idx][1]; // Imag part at point idx
float r1 = cal_data[eterm][idx+1][0]; // Real part at point idx+1
float i1 = cal_data[eterm][idx+1][1]; // Imag part at point idx+1
// Interpolate: value = v0 + k * (v1 - v0)
data[eterm][0] = r0 + k * (r1 - r0);
data[eterm][1] = i0 + k * (i1 - i0);
}

Linear interpolation introduces error, especially where calibration data changes rapidly.

When error terms vary smoothly with frequency (typical at HF):

  • Interpolation error is small
  • Even 10:1 interpolation ratios work well
  • Results nearly as good as direct calibration

When error terms change quickly (near resonances, harmonic transitions):

  • Interpolation error can be significant
  • Points near transitions may be inaccurate
  • Consider recalibrating for critical measurements

For linear interpolation, the maximum error depends on the second derivative of the calibration data:

Error_max <= (delta_f)^2 / 8 * |d^2(cal_data)/df^2|

In practice:

  • At HF (< 30 MHz): Negligible error
  • At VHF (30-300 MHz): Small error
  • Near 290 MHz boundary: Potentially significant error
  • At UHF (> 300 MHz): Moderate error, especially if few cal points

When your sweep exactly matches calibration, the firmware skips interpolation entirely by passing the point index directly:

// In sweep loop
interpolation_idx = mask & SWEEP_USE_INTERPOLATION ? -1 : p_sweep;
// Later
if (mask & SWEEP_APPLY_CALIBRATION)
cal_interpolate(interpolation_idx, frequency, c_data);

If interpolation_idx >= 0, the function directly copies calibration data without any calculation:

copy_point:
for (eterm = 0; eterm < CAL_TYPE_COUNT; eterm++) {
data[eterm][0] = cal_data[eterm][idx][0];
data[eterm][1] = cal_data[eterm][idx][1];
}
  1. Calibrate at your measurement frequencies: Match start, stop, and points exactly
  2. Use more calibration points: More points means shorter interpolation distances
  3. Avoid sweeping across 290 MHz: Or calibrate specifically for that range
  1. Calibrate wide, measure narrow: Calibrate 50 kHz to 900 MHz, then zoom in as needed
  2. Accept interpolation at HF: The error is usually negligible
  3. Recalibrate for critical UHF work: Especially near the harmonic threshold
ConditionInterpolationAccuracy
Sweep matches calibrationNone neededBest
Sweep subset of calibrationLinear interpolationVery good
Sweep wider than calibrationEdge extrapolationDegraded at edges
Sweep crosses harmonic boundarySpecial handlingAcceptable
Very few calibration pointsLong-range interpolationMay be poor

Now that you understand calibration, explore the Architecture section to see how the hardware implements these measurements.