Nexmon CSI in practice: 80 MHz / 256 subcarriers on Raspberry Pi, Pi 5 compatibility and safe rollback
By Wu Jun · Updated 2026-10-29 · 7 min read
Nexmon CSI is maintained by SEEMOO Lab at TU Darmstadt [1]. It is not an ordinary userspace packet sniffer: it patches Broadcom Wi-Fi firmware so the radio emits per-frame CSI to a UDP socket. A peer-reviewed paper describes the platform and its extraction model [2].
That gives useful high-bandwidth measurements, with an equally clear cost: the chip, firmware and OS must match; the patch changes the network interface; and a failed installation can temporarily remove Wi-Fi. Use a dedicated experiment SD card and test machine, not the only Raspberry Pi that carries your home network.
0. Bottom line
| Need | Is Nexmon a fit? |
|---|---|
| Fixed-channel, 80 MHz, 256 complex CSI values | Yes, after checking the exact chip and OS path |
| Research across channels, bandwidths, frame types and transmitters | Yes; more flexible than a consumer router UI |
| Use the same radio for daily Wi-Fi and 24/7 CSI capture | No; capture takes the interface away from NetworkManager |
| Operate only over the Pi's own Wi-Fi, with no backup image | Do not start; a failure can remove remote access |
| You already own an MT6000 and need long-running home coverage | Prefer this project's tested mt76 router route |
1. Supported chips and Raspberry Pi models
The official matrix lists four primary chips [1]:
| Chip | Firmware directory | Typical device | Bandwidth |
|---|---|---|---|
bcm43455c0 |
7_45_189 |
Raspberry Pi 3B+ / 4B / 5 | 20 / 40 / 80 MHz |
bcm4339 |
6_37_34_43 |
Nexus 5 | 20 / 40 / 80 MHz |
bcm4358 |
7_112_300_14_sta |
Nexus 6P | 20 / 40 / 80 MHz |
bcm4366c0 |
10_10_122_20 |
Some ASUS RT-AC86U devices | 20 / 40 / 80 MHz |
"Raspberry Pi 5" does not mean "any Raspberry Pi OS image compiles unchanged." The maintainer's recent guide says:
- Pi 5 must switch to a non-16K-page kernel or the Nexmon build fails;
- the new path uses
Makefile.rpiand no longer needs a modifiedbrcmfmac; nexutilmust be built withUSE_VENDOR_CMD=1, otherwise the driver rejects the ioctls [3].
Record these before touching the system:
cat /proc/device-tree/model
uname -a
getconf PAGESIZE
iw dev
sudo dmesg | grep -i -E 'brcmfmac|cyfmac|firmware'
If getconf PAGESIZE returns 16384 on a Pi 5, switch to kernel=kernel8.img as required by the upstream guide.
Do not make that change on the only card without understanding how to recover it.
2. Prerequisites
- A dedicated SD card: image the whole card first and keep a second bootable card;
- Ethernet or a serial console:
Makefile.rpi unmanagedisconnectswlan0from NetworkManager; - Stable power: compilation and long captures need a reliable supply;
- A transmitter on the same channel: a phone, ESP32 or second Wi-Fi interface;
- A pinned 5 GHz channel: disable automatic selection and avoid DFS/congested primary channels;
- A local experiment log: chip, firmware directory, OS, kernel, page size, channel, bandwidth, spatial streams and capture time.
The upstream Pi 5 path installs historical compatibility packages and may temporarily use an archived Debian source [3]. That is not a hardened production-server recipe. Treat the card as a short-lived experiment: do not keep SSH private keys, password stores, family photos or the only copy of any data on it. Restore the official firmware after the experiment.
3. Two installation paths
Path A: older Raspberry Pi OS / legacy upstream flow
The official README documents modified brcmfmac drivers and patch builds for Pi 3B+/4B on kernels 4.19, 5.4 and
5.10. It depends heavily on those kernel versions. Do not copy those commands onto a Pi 5 or a current OS.
Path B: current Raspberry Pi OS / Pi 5
For current Raspberry Pi OS and Pi 5, use the maintainer's Makefile.rpi guide [3]. The core
sequence is below. Do not treat the block as a complete one-command installer: Pi 5 armhf compatibility
packages and the Python 2.7 build dependency must be installed according to the linked guide.
# Raspberry Pi 5 only: switch to a non-16K-page kernel, then reboot
grep -qxF 'kernel=kernel8.img' /boot/firmware/config.txt || \
echo 'kernel=kernel8.img' | sudo tee -a /boot/firmware/config.txt
sudo reboot
sudo apt update
sudo apt full-upgrade
sudo apt install git libgmp3-dev gawk qpdf bison flex make \
autoconf libtool texinfo xxd libnl-3-dev libnl-genl-3-dev \
bc libssl-dev tcpdump
git clone --depth=1 https://github.com/seemoo-lab/nexmon.git
cd nexmon
source setup_env.sh
make
# The modern driver path requires vendor commands
cd "$NEXMON_ROOT/utilities/nexutil"
sudo -E make install USE_VENDOR_CMD=1
sudo setcap cap_net_admin+ep /usr/bin/nexutil
cd "$NEXMON_ROOT/patches/bcm43455c0/7_45_189"
git clone --depth=1 https://github.com/seemoo-lab/nexmon_csi.git
cd nexmon_csi
make -f Makefile.rpi install-firmware
make -f Makefile.rpi unmanage
make -f Makefile.rpi reload-full
After unmanage, do not expect wlan0 to keep SSH alive. Generate extractor parameters with the upstream
makecsiparams, then configure CSI and monitor mode:
# The fields are illustrative; replace MAC, frame type and channel for your test
SENDER_MAC="${SENDER_MAC:?set the test transmitter MAC first}"
CSI_PARAMS="$(makecsiparams -c 44/80 -C 1 -N 1 -m "$SENDER_MAC" -b 0x88)"
sudo nexutil -Iwlan0 -s500 -b -l34 -v"$CSI_PARAMS"
sudo nexutil -m1
sudo tcpdump -i wlan0 dst port 5500 -w csi-10s.pcap
The command has no built-in example MAC; it fails immediately unless SENDER_MAC is set. Always copy the current
parameter syntax from the upstream README. The repository has introduced backward-incompatible changes; magic values
and configuration strings in old blog posts can be stale.
4. UDP packet and CSI format
The current README says each matching Wi-Fi frame produces one UDP packet per configured core/spatial stream,
normally on port 5500. The payload layout is:
uint16 magic = 0x1111
uint8 source_mac[6]
uint16 wifi_sequence
uint16 core_and_spatial_stream
uint16 chanspec
uint16 chip_version
... CSI samples
For bcm43455c0 and bcm4339, CSI is interleaved int16 I / int16 Q. The bcm4358 and bcm4366c0 use different
packed floating-point formats. A 20 / 40 / 80 MHz extraction contains 64 / 128 / 256 complex slots respectively,
at four bytes per slot. Those slots include guard, null and pilot positions; do not treat all 256 as useful
information subcarriers.
An 80 MHz receiver can also receive a frame transmitted in a 20 MHz-wide portion of the channel. Most of the CSI slots being empty does not necessarily mean capture is broken; check the transmitter's actual bandwidth and frame type. Remove guard/null positions and report the ratio of usable subcarriers.
5. Capture settings and data budget
- Pin the channel and bandwidth; scan the channel before capturing;
- use MAC and frame-type filters to reject irrelevant frames;
- keep a compliant transmitter busy with UDP/TCP traffic;
- capture 10 seconds and parse it before extending to 10 minutes;
- record the
makecsiparamsarguments and thenexutil -kchanspec.
A rough budget: at about 100 matching frames/s, one 80 MHz stream carries about
100 × 256 × 4 = 102.4 KB/s of CSI before Ethernet/IP/UDP and pcapng overhead. Actual size depends on matched
frame rate, cores and spatial streams. This project's MT6000 capture is 58 MB for 10 minutes and eight-chain records,
which is a useful order-of-magnitude reference.
6. Parsing and quality checks
| Tool | Best use |
|---|---|
CSIKit [4] |
multi-format parsing, plots and CSV/JSON export |
csiread [5] |
Cython parsing with faster batch and live-capture examples |
After a capture, verify:
| Check | Pass condition |
|---|---|
| UDP traffic | port 5500 receives a sustained stream |
| pcap readability | tcpdump -r csi-10s.pcap returns packets |
| magic / chip format | matches current Nexmon release and chip |
| bandwidth | 80 MHz configuration exposes the expected 256 slots |
| usable subcarriers | frame bandwidth and zero-slot ratio are recorded |
| timeline | report inter-packet p95 and maximum gap |
| filtering | transmitter MAC and frame type match the configuration |
7. Failure guide
| Symptom | Cause | Check first |
|---|---|---|
| build fails on Pi 5 | still using a 16K-page kernel | getconf PAGESIZE, kernel=kernel8.img |
nexutil commands are rejected |
built without vendor-command support | rebuild with USE_VENDOR_CMD=1 |
| no UDP packets | no traffic or wrong MAC/frame filter | ping/iperf, tcpdump, extractor parameters |
| bad chanspec / interface down | radio down or another service changed the channel | nexutil -k, stop wpa_supplicant |
| most 80 MHz slots are empty | a 20 MHz frame or transmitter not using full width | inspect transmitter bandwidth and frame type |
| Wi-Fi remains missing after rollback | firmware alternative or driver not restored | sudo update-alternatives --config cyfmac43455-sdio.bin |
| capture stops after a few minutes | power, heat, distance or interference | stable supply, temperature, pinned channel |
8. Safe rollback
Restore the normal firmware when the experiment ends:
make -f Makefile.rpi restore-wifi
If rollback fails, boot from the spare SD card or restore the card image with a reader. Never combine experiment, remote administration and the only copy of important data on one card.
9. Nexmon vs ESP32 vs MT6000
| Item | Nexmon + Pi | ESP32 CSI | MT6000 + mt76 CSI |
|---|---|---|---|
| Typical bandwidth | 20–80 MHz | 20 MHz, some 40 MHz | HE80 in this project |
| Typical slots/subcarriers | 64 / 128 / 256 slots | 30–64 measured subcarriers | 256 complex slots |
| Channel/bandwidth flexibility | high | medium | medium |
| Long-running deployment | needs a maintained experiment host | low power, single room | natural fit for an existing router |
| Main risk | firmware, kernel and network changes | sampling rate and interference | driver patch and router performance tuning |
Continue with the Raspberry Pi role guide, ESP32 CSI introduction, MT6000 reproduction guide or the project's 10-minute dataset.
References and data sources
Every external claim carries a source and an evidence grade (grading scheme). Grade E marks first-party, reproducible measurements from this project.
- Grade C · official docs
seemoo-lab/nexmon_csi — CSI extraction on various Broadcom Wi-Fi chips
SEEMOO Lab, TU Darmstadt
github.com/seemoo-lab/nexmon_csi · verified: GitHub API 2026-10-29 (stars 479, last push 2026-09-30; README lists bcm43455c0 on Raspberry Pi 3B+/4B/5) - Grade B · peer-reviewed
Free Your CSI: A Channel State Information Extraction Platform For Modern Wi-Fi Chipsets
F. Gringoli, M. Schulz, J. Link, M. Hollick · Proceedings of the 13th ACM Workshop on Wireless Network Testbeds, Experimental Evaluation & Characterization · 2019
doi:10.1145/3349623.3355477 · citations 334 · verified: Crossref API 2026-10-29 (334 citations; DOI and venue resolved) · Peer-reviewed description of the Nexmon CSI platform, its firmware patch and per-frame CSI extraction. - Grade C · official docs
Raspberry Pi 5 and recent Raspberry Pi OS kernel versions — Makefile.rpi guide
SEEMOO Lab, TU Darmstadt · 2026
github.com/seemoo-lab/nexmon_csi/discussions/395 · verified: Official maintainer discussion fetched 2026-10-29; documents Pi 5 kernel=kernel8.img, USE_VENDOR_CMD=1, capture and restore-wifi · The maintainer's current Raspberry Pi route; use a dedicated experiment SD card because it changes firmware, drivers and system packages. - Grade C · official docs
Gi-z/CSIKit — Python CSI parsing and visualisation
G. Zhang
github.com/Gi-z/CSIKit · verified: GitHub API 2026-10-11 (stars 307, MIT, last push 2025-02-19) · Parses many formats: Atheros / Intel / Nexmon / ESP32 / FeitCSI / PicoScenes. - Grade C · official docs
citysu/csiread — fast Cython CSI parser
S. Zhu
github.com/citysu/csiread · verified: GitHub API 2026-10-11 (stars 142, MIT, last push 2024-10-12)