Denys Inhul

theory: radar using wifi csi

date: May 23 2026

WiFi CSI

As I’ve been exploring the landscape of radars and what’s possible to build myself, I also started seeing a lot of diy projects and research papers using wifi modules as the base RF module. That makes a lot of sense: the wifi routers we all have at home have a couple different antennas, they have internal RF circuits already set up, can transfer gigabits of data per second, and are super cheap (relatively speaking). $40 gets you something that can operate at 5 GHz range with 100s of MHz of bandwidth with all of the circuitry included and soldered. pretty neat.

Now, all of those projects use something called WiFi CSI. That is some kind of internal signal representation that you can get your hands on if you use special drivers and wifi module combinations to expose that data to the outside. Or if you buy special wifi modules that can give you access to that “raw” data. For example, raspberry pi and esp32 can give you that without much hassle.

what is CSI?

CSI = Channel State Information. For each OFDM subcarrier (and each TX-RX antenna pair), the receiver module estimates a complex number H = amplitude · e^(jφ) that says “this frequency was attenuated by X and phase-shifted by Y on its way here.” Wi-Fi already computes this internally for equalization, beamforming, and rate selection; CSI sensing just exposes that matrix to userspace so you can watch it change as people, hands, or objects perturb the multipath in the room.

what is a subcarrier?

Wi-Fi splits its channel (e.g. 20 MHz) into many narrow parallel frequency slots, each ~312.5 kHz wide. Each slot is a “subcarrier” carrying its own data. 20 MHz → 64 subcarriers (52 usable for 802.11n, 56 for 802.11ac), 80 MHz → 256, etc.

what is OFDM?

OFDM = Orthogonal Frequency Division Multiplexing, the scheme that uses those subcarriers in parallel. “Orthogonal” because the subcarrier frequencies are spaced so each one’s peak lines up with the others’ zeros, letting them overlap in frequency without interfering.

Practically: for 64 subcarriers -> 64 complex inputs (our data). TX performs IFFT: those N complex inputs map to N time samples = superposition of N perfectly-spaced sinusoids. FFT at the other end pulls them back out cleanly. That is possible only if subcarrier frequency spacing equals 1/T (T = 1 symbol = 1 OFDM transmission of the 64 time samples); then, over one symbol, the integral of sin(2πf_i·t) · sin(2πf_j·t) is exactly zero for i≠j. Off by a hair in spacing and you get inter-carrier interference where one subcarrier influences and contributes to the next.

what does CSI expose?

When TX transmits a signal in an environment, that signal gets perturbed. The medium itself changes the signal: the noise in the TX and RX modules, slight differences in temperature and clock signal. One of the biggest contributors is signal multipath. All the walls, furniture, moving objects, etc. perturb the signal even further and then reflect and refract it. At the scale of a room or a building of ~10s of meters vs the speed of light, all those signals and reflections get combined into a single RX reading and overlap on top of each other, so whatever the RX receives is greatly perturbed by the environment.

So that’s what CSI estimates: those perturbations between TX and RX. Every OFDM transmission starts with a so-called LTF = Long Training Field, a specific sequence of data that is standardized, so RX knows exactly what the intended waveform of TX’s LTF is, and hence can estimate a complex number H that describes the perturbation to each subcarrier.

And that is what is used for learning about the environment around the RX. We do not do frequency chirps, we do not have high-fidelity propagation delay between TX and RX signal, we simply have this H that describes an estimate (magnitude and phase) of perturbation of each OFDM symbol from any given TX. And yet, one can still use the evolution of those perturbations and changes in multipath and doppler effects to learn about what’s going on in the environment.

Here is a quick conceptual demo showing the multipath of the signal between TX and RX and why we need to estimate H to compensate for all this signal perturbation:

0.64
0.00
propagation ray-bundled wavefront, max 4 bounces
RX recording colored arrivals sum below

the TX-RX pipeline

what does the TX-RX pipeline look like, step by step?

  1. Take your bit stream, chop into groups, map each group to a complex number via QAM (e.g., 16-QAM: 4 bits → one point in a 4×4 grid in the complex plane).
  2. Assign one complex number X[k] to each of N subcarriers. This is your “frequency-domain symbol.”
  3. IFFT(X) → N time-domain samples x[n]. This is what gets sent: a wideband waveform that is the sum of all N subcarriers, each modulated by its X[k].
  4. Prepend a cyclic prefix (copy of the tail) to absorb multipath.
  5. DAC, upconvert to RF, transmit.
  6. RX downconverts, removes cyclic prefix, FFT → recovers Y[k] per subcarrier.
  7. Divide by channel estimate H[k] (equalize), demap QAM back to bits.

how does the LTF work, and how does RX compute H from it?

In 802.11n, the L-LTF is 8 µs, two repetitions of a 64-sample sequence. The transmitted values X[k] ∈ {+1, -1} across the 52 used subcarriers are fixed in the standard (chosen for low peak-to-average ratio), so every receiver knows them. RX takes FFT of the received LTF samples → Y[k] (complex), then H[k] = Y[k] / X[k]. Since X is ±1, division is a sign flip. Two repetitions are averaged to reduce noise. There’s also HT-LTF for MIMO channel estimation, 4 µs per stream.

how accurate is that estimation?

Limited by thermal noise, RX hardware imperfections (carrier frequency offset, sampling clock offset, IQ imbalance), and AGC. Absolute phase is garbage on commodity chips, though.

H[k] = |H[k]| · e^(jφ[k]). The amplitude |H[k]| is meaningful and stable. φ[k] is corrupted by CFO (TX/RX oscillator offset adds a time-varying rotation across packets), SFO (ADC clock drift adds a phase ramp across subcarriers), and PLL random initial phase (resets per packet). Absolute phase varies wildly from packet to packet even in a static channel.

What is usable: within a single packet the SFO ramp is deterministic (e^(j·2π·k·δ)), so phase differences between subcarriers are stable and carry real channel information. Between packets, the PLL reset kills those.

how often can you sample CSI, and is faster always better?

CSI is estimated per packet from the preamble, so sampling rate equals packet arrival rate. Each packet has ~40 µs of preamble + headers regardless of payload; smallest useful packet ~100 µs on the air. Theoretical max ~10 000 packets/s on a single link, but MAC contention and interrupt overhead cut that in practice. Commodity CSI tools: 100-1000 Hz comfortably, 2-3 kHz pushing it. Faster isn’t always better: human motion is <20 Hz, breathing <0.5 Hz. 100-1000 Hz is plenty and gives better SNR per sample than chasing 10 kHz.

how many channels are there in 2.4 GHz Wi-Fi and how are they separated?

14 channels, spaced 5 MHz apart, centers from 2412 MHz (ch 1) to 2484 MHz (ch 14, Japan only). Each channel is 20 MHz wide so adjacent channels overlap heavily. The non-overlapping set is 1, 6, 11 (US), 25 MHz apart. Three APs on 1/6/11 in the same room can genuinely transmit in parallel and roughly triple aggregate capacity; any other channel choice creates partial overlap that hurts both you and your neighbors. 5 GHz has ~25 non-overlapping 20 MHz channels, which is why dual-band became standard.

do devices on the same channel take turns, and how does an RX tell different TXs apart?

Yes, via CSMA/CA: each device listens before transmitting, waits a random backoff if the channel is busy, and backs off exponentially after collisions. Polite chaos, not central coordination; works to ~30-50 devices per channel and degrades sharply beyond. An RX decodes one packet at a time on the channel it’s tuned to; collisions and sub-threshold packets are dropped. Every 802.11 header carries the source MAC in cleartext, so per-packet CSI is automatically tagged with which TX sent it. That’s how CSI sensing isolates one device’s channel from another’s.

the demo: end to end

The widget below runs the whole thing twice, side by side. The left column is the calibration pass: the known LTF preamble goes through the channel and the receiver divides it back out to estimate Ĥ[k], the CSI. The right column is a data symbol through that same channel, decoded using the Ĥ[k] measured on the left: input bits → QPSK symbols → IFFT waveform → channel-corrupted received waveform → FFT → equalize → output bits. Bit errors at the bottom turn red. Three sliders drive it:

  • multipath α (0 to 1): strength of a single echo relative to the direct path, so H[k] = 1 + α·exp(jθₖ). At α=0 the channel is flat (H[k]=1, all four dots stacked at (1,0)) and nothing distorts. Crank it up and the dots fan onto a circle of radius α around (1,0), each subcarrier picking up its own gain; near α=1 some subcarriers nearly cancel and Y/H there amplifies noise.
  • noise σ: additive Gaussian noise injected at the receiver, scaled relative to signal amplitude.
  • carrier offset δ (in units of 1/T): the orthogonality knob. At δ=0 each subcarrier lands in its own bin. At δ≠0 the spectrum shifts, subcarriers leak into each other.

The orthogonality tab strips it down to two subcarriers and plots the integral of their product, so you can watch δ≠0 break the math directly.

OFDM end-to-end pipeline + subcarrier orthogonality

0.50 0.03 +0.00