I’ve heard that some Atheros WiFi cards support spectral measurements—particularly the ones using the Linux ath9k driver. In our lab at UCLA we have USB dongles that use the ath9k_htc driver and wanted to give it a try.
As it turns out the ath9k_htc driver uses some code from ath9k but is actually a different driver. Fortunately, also the ath9k_htc got patched to support spectrum scanning in November 2014. This patch is part of Linux starting from version 4.3-rc4.
An up-to-date kernel will show the interface to configure spectral scanning in debugfs.
root@virt:/sys/kernel/debug/ieee80211/phy0/ath9k_htc# ls base_eeprom queue spectral_count spectral_scan_ctl tgt_tx_stats debug recv spectral_fft_period spectral_short_repeat xmit modal_eeprom skb_rx spectral_period tgt_int_stats phy_err slot spectral_scan0 tgt_rx_stats
To me it looks like the spectrum scanning functionality was reverse enigneered, i.e., the driver can perform measurements, but there is no official documentation about the data format and how to interpret the measurements.
When implementing my measurements script I looked at the two projects referenced in the Wiki. One project is by Lorenzo Bianconi, while the another project is by Simon Wunderlich. Both projects agree in how the power should be calculated, but they differ in how the individual measurements should be mapped to frequencies.
The relevant part from Bianconi:
#define SPECTRAL_HT20_NUM_BINS 56 freq = fft_data->freq - 10.0 + ((20.0 * i) / SPECTRAL_HT20_NUM_BINS);
and from Wunderlich:
#define SPECTRAL_HT20_NUM_BINS 56 freq = result->sample.ht20.freq - (22.0 * SPECTRAL_HT20_NUM_BINS / 64.0) / 2 + (22.0 * (i + 0.5) / 64.0);
Both don’t make much sense to me since I think Atheros uses functionality that the chip already provides. When using 802.11a or 802.11g the 20MHz spectrum is divided into 64 orthogonal subcarriers with a 64 point FFT. I would expect that the measurements correspond to bins of said FFT and, thus, should be mapped to subcarrier frequencies.
54 (48 data + four pilots) of the 64 subcarriers are actually used. The rest are DC and nine guards. Currently, I assume that the measurements correspond to the utilized subcarriers plus the inner most guard carriers, i.e. carriers -28,..,-1 and 1,..,28. Consequently, my mapping looks like this
if i < 28: freq = center_freq - (20.0 / 64) * (28 - i) else: freq = center_freq + (20.0 / 64) * (i - 27)
As a proof-of-concept I implemented a python script that plots the spectral measurements using matplotlib. It’s available as a gist. It logs in on a remote host, performs the measurement, downloads the data, outputs the raw measurements in CSV format, and plots min (blue), max (green), and average (red) power levels.
The screenshot shows some bluetooth transmissions on the lower part of the spectrum (the spikes) and WiFi on the upper part. Everything on top of a lot of WiFi background noise.
I made a small video to show how it looks like. Sometimes parts of the specturm are not measured. This might be a bug in the driver or in my code; I should have a more detailed look somewhen.