I/Q Quadrature and the Two-Channel Phase Detector
A microwave interferometer compares a reference wave with a reflected wave to measure distance or detect movement. But here’s the problem: if you only measure amplitude, you can’t tell whether two waves are slightly in-phase or slightly out-of-phase—both look like partial cancellation. You need to track the phase angle continuously.
The solution, borrowed from radar and radio astronomy in the 1960s, is quadrature sampling: split the signal into two channels 90° apart (cosine and sine components), giving you both magnitude and phase as a complex number. The in-phase channel (I) measures cos(φ), the quadrature channel (Q) measures sin(φ), and together they define a point on the unit circle. Phase difference becomes atan2(Q, I).
Here’s Rust computing phase shift from I/Q samples:
fn phase_diff(i1: f64, q1: f64, i2: f64, q2: f64) -> f64 {
let phase1 = q1.atan2(i1);
let phase2 = q2.atan2(i2);
let mut diff = phase2 - phase1;
if diff > std::f64::consts::PI { diff -= 2.0 * std::f64::consts::PI; }
if diff < -std::f64::consts::PI { diff += 2.0 * std::f64::consts::PI; }
diff
}
fn main() {
let (i_ref, q_ref) = (0.707, 0.707); // 45° reference
let (i_sig, q_sig) = (0.0, 1.0); // 90° signal
println!("Phase shift: {:.2}° ({:.4} rad)",
phase_diff(i_ref, q_ref, i_sig, q_sig).to_degrees(),
phase_diff(i_ref, q_ref, i_sig, q_sig));
}
Haskell treats I/Q pairs as native complex numbers:
import Data.Complex
phaseDiff :: Complex Double -> Complex Double -> Double
phaseDiff z1 z2 = phase (z2 / z1)
main :: IO ()
main = do
let ref = 0.707 :+ 0.707
sig = 0.0 :+ 1.0
putStrLn $ "Phase shift: " ++ show (phaseDiff ref sig * 180 / pi) ++ "°"
Both output 45.00°—the signal leads the reference by 45°. In a slotted line or standing wave interferometer, this phase difference maps directly to distance: λ/8 of movement at 10 GHz is 3.75 mm. Quadrature detection turns wave interference into geometry you can compute.