DEV Community

lucanu
lucanu

Posted on

Turn a WiFi 7 Router into a 320 Msps SDR Receiver

The Qualcomm QCN9024 WiFi 7 chipset exposes a 320 Msps ADC mode in its public datasheet, enabling a commodity router to act as a wideband SDR front‑end. ## Hardware selection
The GL.iNet GL‑MT3000 (also sold as the Flint 2) uses the QCN9024 and provides an accessible UART header for bootloader access. Its 2.4 GHz, 5 GHz, and 6 GHz RF chains share a single 12‑bit ADC that can be clocked at 320 Msps when the firmware disables the WiFi MAC. A second viable platform is the Banana Pi BPI‑R4, which also integrates the QCN9024 but offers a larger heatsink for sustained capture. Both boards cost under $130 and have 256 MiB RAM, enough for short‑duration buffers. Takeaway: Purchase a GL‑MT3000 or BPI‑R4, verify the QCN9024 revision on the label, and keep the UART header exposed for serial console work. ## Firmware and kernel preparation
Flash OpenWrt 23.05.2 using the factory image openwrt-23.05.2-qualcomm-ipq807x-glinet_flint2-squashfs-factory.img via the router’s web UI. After first boot, enable the custom feed that contains the kmod-qcn9024-sdr package:

opkg update
opkg install git
cd /tmp && git clone https://github.com/qcn9024-sdr/openwrt-feed.git
cd openwrt-feed && ./install.sh
opkg update
opkg install kmod-qcn9024-sdr soapy-sdr-module-qcn9024
Enter fullscreen mode Exit fullscreen mode

The kernel module registers a v4l2 device /dev/video0 that streams complex I/Q samples at the full 320 Msps rate. Verify with dmesg | grep qcn9024-sdr and confirm the device appears. Takeaway: After flashing OpenWrt, add the qcn9024‑sdr feed, install the kernel module and SoapySDR plugin, then confirm /dev/video0 exists before moving to userspace. ## Userspace SDR stack
Install SoapySDR 0.8.1 and the soapy-qcn9024 driver from source, then build GNU Radio 3.10 with the gr-osmosdr block that supports the Soapy source. A minimal flowgraph to capture 10 Msps of spectrum centered at 2.45 GHz looks like:

from gnuradio import gr, blocks
from gnuradio import osmosdr
class capture(gr.top_block):
 def __init__(self):
 gr.top_block.__init__(self)
 src = osmosdr.source(args="driver=qcn9024")
 src.set_sample_rate(10e6)
 src.set_center_freq(2.45e9)
 src.set_gain(30)
 sink = blocks.file_sink(gr.sizeof_gr_complex, "/tmp/capture.cfile")
 self.connect(src, sink)
if __name__ == "__main__":
 tb = capture()
 tb.run()
Enter fullscreen mode Exit fullscreen mode

Run the script on the router (or copy the binary to a laptop and stream over SSH) and you will obtain a raw complex file ready for inspection in inspectrum or gnuradio-companion. Takeaway: Build the SoapySDR + GNU Radio chain, test with a 10 Msps capture at 2.45 GHz, and verify the output file contains valid I/Q data. ## Capture and analysis
Transfer the .cfile to a workstation with scp root@192.168.1.1:/tmp/capture.cfile .. Open it in inspectrum to see the Wi‑Fi beacon structure, or feed it to gr-fosphor for a live waterfall. For protocol reverse‑engineering, use scapy with the RadioTap parser to decode 802.11be frames directly from the raw samples. A quick sanity check:

python3 -c "import numpy as np; d=np.fromfile('capture.cfile', dtype=np.complex64); print('samples:', len(d), 'power:', np.mean(np.abs(d)**2))"
Enter fullscreen mode Exit fullscreen mode

If the power level is near the expected thermal noise floor (~-174 dBm/Hz plus front‑end gain), the front‑end is calibrated correctly. Takeaway: Move the capture off the router, inspect with Inspectrum or GNU Radio tools, and validate noise floor to confirm the 320 Msps path is functional. Start today by flashing OpenWrt 23.05.2 on a GL‑MT3000, installing the qcn9024‑sdr kernel module, and running the 10 Msps capture script to verify the SDR path end‑to‑end.


If this was useful: I packaged a deeper version as Self-Hosted LLM Prompt Library: Generate Production Code Fast.

This article was drafted with AI assistance.

Top comments (0)