Theory of Operation¶
This document aims to explain the core mathematical concepts used in the Rx Phase Calibration tutorial.
Note that in order to maintain sample-level accuracy, the computations explained below are performed in the complex time-domain on baseband IQ data, and can be divided into four main steps:
- Step 1: Receive IQ data with respect to desired reference receive channel
- Step 2: Assess the time delay and phase offset between desired receive channels
- Step 3: Generate a complex correction phasor to compensate for the phase offset between receive channels
- Step 4: Apply the correction phasor to receive channels to align them in phase
Step 1: Data Acquisition¶
NOTE: If you are not familiar with IQ (in-phase and quadrature) data, or simply need a refresher, a whitepaper on the concept can be found here.
The received IQ data can be represented as follows in the complex time-domain:
where \(s_{{test}_{chan}}(t)\) represents each receive channel to be phase calibrated against the chosen reference channel \(s_{{ref}_{chan}}(t)\) (RX Channel 0 by default), \(\tau\) represents the time delay (samples), and \(\theta\) the constant phase offset (radians) of \(s_{{test}_{chan}}(t)\) with respect to \(s_{{ref}_{chan}}(t)\). \(A(t)\) represents the time-varying amplitude of each signal, whereas \(\phi(t)\) represents the angular frequency (rad/sec).
The DC bias is now removed from each signal by subtracting its mean. This is done in order to ensure accuracy in our IQ data:
where \(\bar{s}_{{test}_{chan}}(t)\) is the mean, and \(\hat{s}_{{test}_{chan}}(t)\) is the resulting signal with the DC bias removed.
Step 2: Phase Correlation¶
With the DC bias of each signal accounted for, we can now determine the time delay \(\tau\) (seconds) between each test signal and the chosen reference signal. This is done by computing the cross-correlation \(R\) of each sampled test signal with respect to the reference signal:
where \(*\) denotes the complex conjugate, and \(N\) is the number of samples. If the test channel is delayed by \(\tau\) relative to the reference channel, then the two signals align best when the test signal is shifted back by that delay. In samples, the peak location is
where \(f_s\) is the sample rate in Hz.
Using the SOAPY_SDR_HAS_TIME flag when receiving data as shown in the Rx Phase Calibration Tutorial source code should ensure time-alignment across receive channels, resulting in:
for \(N\) number of samples with respect to the reference signal \(s_{{ref}_{chan}}[n]\).
NOTE: if \(d\) is non-zero, the current channel pair is not time-aligned. In this case, please do not proceed unless you intend on accounting for this delay, as time-alignment is required in order for the phase calibration to be accurate.
Once time-alignment between receive channels is confirmed, the phase offset between each pair of test and reference channels can be found by taking \(100~\mu s\) worth of overlapping, time-aligned samples, and computing their dot product. Only then can we find each respective phase offset \({\theta}_{{{test,ref}}_{chan}}\) (radians):
Step 3: Phasor Generation¶
The previous step estimates phase offsets between pairs of test and reference channels. If channel \(i\) has phase offset \(\phi_i\) relative to the chosen reference channel, then the measured phase from each test channel to reference channel can be modeled as
where \(\phi_{ref} = 0\).
This leaves one unknown phase per remaining channel. We can represent this relationship using a system of linear equations, where each row in matrix \(\mathbf{A}\) represents each valid test and reference channel pair:
Since there are more pair measurements than unknown channel phases, the equations are likely to be overdetermined. Using the least-squares method, we find the set of channel phases \(\hat{\phi}\) that best fits all pairwise measurements:
The returned calibration values are complex unit phasors, not phase angles:
The negative sign is intentional. If a test channel is measured as leading the reference by \(\hat{\phi}_{test,ref}\), multiplying its samples by \(e^{-j\hat{\phi}_{test,ref}}\) rotates that channel back toward the reference phase. The reference channel therefore receives the identity phasor \(e^{-j0}=1\).
Step 4: Phasor Application¶
The per-channel correction phasors \(c_{test,ref}\) computed in the previous section are then programmed into the receive datapaths for each test channel. This correction gets applied on top of the phasor correction \(p_{existing}\) currently stored on that receive datapath:
where \(c_{test,ref} = e^{-j\hat{\phi}_{test,ref}}\) is the per-channel correction computed in the previous section, and \(p_{new}\) is the resulting phasor applied to each channel.
Note that if this is the first time calibrating a channel, the existing phasor correction will simply be the identity phasor \(e^{-j0}=1\), leaving us with:
Applying the phasor corrections incrementally in this way makes calibration cumulative; each calibration pass adjusts the phasor already programmed for that channel rather than replacing it with only the latest estimate.