Building SD Images¶
openwifi boots from an SD card running one of three base operating systems, and you can build any of them from scratch:
- ADI Kuiper: a Debian/Ubuntu-like image (the classic openwifi environment, and what the
fosdem.shdemo and most app notes assume). - OpenWrt: a router-style image with the LuCI web UI, with openwifi packaged as a kernel module.
- Buildroot: a compact image, for the MicroPhase ANTSDR boards only. See Buildroot below.
You may not need to build anything
Prebuilt images exist for Kuiper and OpenWrt. If you just want a working board, flash a prebuilt image as in Getting Started (Kuiper) or the OpenWrt quick start below. Build from scratch when you need a custom kernel, a new board, or an image you control end to end.
The builds below assume you understand the boot chain and device tree. For the driver/dev loop see Software Development Workflow.
Which one should you build?¶
| ADI Kuiper | OpenWrt | Buildroot | |
|---|---|---|---|
| Feels like | A small Debian/Ubuntu box | A Wi-Fi router (LuCI web UI) | A minimal BusyBox system |
| Best for | Research, the app-note workflows, full apt tooling | Router use cases | A small, fast-booting image on ANTSDR boards |
| Build needs | Vivado 2022.2 + Vitis | Docker only (no Vivado) | The prebuilt board system_top.xsa (no Vivado) |
| openwifi tools | Built on the board | Packaged into the image (in $PATH) |
Packaged into the image, under /root/openwifi |
ADI Kuiper: build from scratch¶
Prerequisites¶
- Vivado 2022.2 with Vitis installed (you need
.../Vitis, notVitis_HLS). - An SD card of 16 GB or more.
-
Host packages:
sudo apt install flex bison libssl-dev device-tree-compiler u-boot-tools -y -
The usual environment variables (
XILINX_DIR,OPENWIFI_HW_IMG_DIR,BOARD_NAME) set as in Environment Setup. SDCARD_DIR: the mount point that contains the card'sBOOTandrootfspartitions (the last argument ofupdate_sdcard.shbelow).
1. Flash the ADI Kuiper base image¶
Download the "December 13, 2023 release (2022_r2)" (image_2023-12-13-ADI-Kuiper-full.zip) from the ADI Kuiper page and extract the .img. Flash it:
# Check the real sector count first: fdisk -l 2023-12-13-ADI-Kuiper-full.img
sudo dd bs=512 count=24182784 if=2023-12-13-ADI-Kuiper-full.img of=/dev/your_sdcard_dev
2. Edit the rootfs config files¶
Mount the card's BOOT and rootfs partitions on your PC and make these edits.
Add a static eth0 to rootfs/etc/network/interfaces:
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 192.168.10.122
gateway 192.168.10.1
netmask 255.255.255.0
network 192.168.10.0
broadcast 192.168.10.255
Enable IP forwarding in rootfs/etc/sysctl.conf:
net.ipv4.ip_forward=1
Speed up shutdown in rootfs/etc/systemd/system.conf:
DefaultTimeoutStopSec=2s
Copy the udev rule that names the network device:
cp openwifi/kernel_boot/10-network-device.rules rootfs/etc/udev/rules.d/
3. Run update_sdcard.sh¶
From openwifi/user_space, run update_sdcard.sh with the mount point that holds BOOT and rootfs as its last argument:
cd openwifi/user_space
./update_sdcard.sh $OPENWIFI_HW_IMG_DIR $XILINX_DIR $SDCARD_DIR
It builds and copies onto the card:
- the kernel image:
adi-linux-64/arch/arm64/boot/Image(64-bit) oradi-linux/arch/arm/boot/uImage(32-bit) - the device tree:
kernel_boot/boards/zcu102_fmcs2/system.dtb(64-bit) orkernel_boot/boards/$BOARD_NAME/devicetree.dtb(32-bit) BOOT.BIN: fromkernel_boot/boards/$BOARD_NAME/output_boot_bin/BOOT.BIN- the openwifi driver, and the
user_space+webserverfiles.
(See Boot, Kernel & Device Tree for how those three artifacts are built.)
4. Configure the board files and first boot¶
Still on your PC, configure the BOOT partition for the specific board you have:
- Copy everything from
BOOT/openwifi/<board_name>/into the root of theBOOTpartition. - Delete
rootfs/root/kernel_modulesif it exists. - Delete
rootfs/etc/network/interfaces.newif it exists.
Then insert the card, set the board to SD-boot mode, connect antennas, and power on. Wire Ethernet to a PC at static IP 192.168.10.1 and log in. The ADI base image's initial password is analog (a prebuilt openwifi card would already be openwifi):
ssh root@192.168.10.122
passwd # change it to openwifi
If login fails, see Troubleshooting. Enlarge the root partition (only needed if your SD card is larger than 16 GB) and reboot:
raspi-config --expand-rootfs
reboot now
5. Give the board internet (needed for the next step)¶
The on-board package installs need internet, routed through your PC. On the PC:
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -o <internet_nic> -j MASQUERADE
sudo ip route add 192.168.13.0/24 via 192.168.10.122 dev <board_nic>
Then confirm connectivity on the board:
route add default gw 192.168.10.1
ping <some_host_you_know>
Resolve any connectivity problem before continuing. (To make forwarding persistent on the PC, uncomment net.ipv4.ip_forward=1 in /etc/sysctl.conf.)
6. Install tools and build the on-board utilities¶
In the board's ssh session (set the clock first with date -s if needed, for example date -s "2026-08-16 12:00"):
sudo apt update
chmod +x /root/openwifi/*.sh
# DHCP server
sudo apt-get -y install isc-dhcp-server
cp /root/openwifi/dhcpd.conf /etc/dhcp/dhcpd.conf
# useful tools
sudo apt-get -y install hostapd tcpdump webfs iperf iperf3 libpcap-dev bridge-utils
# build the on-board tools
sudo apt-get -y install libnl-3-dev libnl-genl-3-dev
cd /root/openwifi/sdrctl_src && make clean && make && cp sdrctl ../
cd /root/openwifi/side_ch_ctl_src/ && gcc -o side_ch_ctl side_ch_ctl.c && cp side_ch_ctl ../
cd /root/openwifi/inject_80211/ && make clean && make && cd ..
7. Run openwifi¶
/root/openwifi/setup_once.sh # once per new board (reboots)
cd /root/openwifi
./wgd.sh # "./wgd.sh 1" enables experimental 11n A-MPDU aggregation
ifconfig sdr0 up
iwlist sdr0 scan
./fosdem.sh # "./fosdem-11ag.sh" forces legacy 11a/g mode
Connect a phone or laptop to the "openwifi" SSID. You should get a 192.168.13.x address, and browsing to 192.168.13.1 shows the on-board webserver page. A few things to know (same as the prebuilt-image flow):
- The demo defaults to channel 44 (5 GHz). For a 2.4 GHz-only client, edit
hostapd-openwifi.confon the board and re-runfosdem.sh. - The Xilinx Viterbi decoder halts after ~2 hours (evaluation license). Reload the FPGA or power-cycle to recover.
- The ADRV9361-Z7035 has very low 5 GHz TX power: keep nodes close on that board.
See Getting Started → Start the access point for more on the bring-up, and Research Features to start capturing CSI.
Faster paths
- Prebuilt img: flash the openwifi prebuilt
.img(dd bs=512 count=31116288 …) and skip to step 4. - Move a working card to a new board: re-do the "configure the board files" step for the new
board_nameon an existing card.
OpenWrt¶
OpenWrt packages openwifi as a kernel module and gives you the LuCI web UI. The build needs only Docker (no Vivado).
Board support matrix¶
| Board | Supported | Tested |
|---|---|---|
zc706_fmcs2, zed_fmcs2, adrv9364z7020, adrv9361z7035 |
✅ | ✅ |
zcu102_fmcs2 |
✅ | ✅ ⚠️ (fails on some boards, see Troubleshooting) |
zc702_fmcs2, antsdr, e310v2, antsdr_e200, sdrpi, neptunesdr |
✅ | (untested) |
OpenWrt quick start (prebuilt image)¶
This is the OpenWrt equivalent of the fosdem.sh demo.
-
Download the prebuilt image for your board from the image folder, then unzip and flash (example for
adrv9364z7020):cd ~/Downloads && gunzip openwrt-zynq-generic-analog_devices_zynq-adrv9364-squashfs-sdcard.img.gz sudo dd if=~/Downloads/openwrt-zynq-generic-analog_devices_zynq-adrv9364-squashfs-sdcard.img of=/dev/your_sdcard_dev status=progressCheck the target device
On a PC,
/dev/mmcblk0is often the PC's own internal eMMC, not the SD card. Runlsblkbefore and after inserting the card and use the device that appeared. Picking the wrongof=target overwrites that disk. -
Boot the board. After about a minute an
openwrt-openwifiSSID appears on 2.4 GHz channel 1. Connecting gives you an IP but no internet yet. -
Give the board (and its clients) internet through your PC. Connect Ethernet, and the board assigns your PC
192.168.10.1. The script ships in the openwifi repo next to the OpenWrt build instructions, underdoc/img_build_instruction/openwrt/. Find your interface names withip addr, then run the script below. Its first argument is the PC's internet-facing interface, the second is the board-facing one:./give_board_internet_access.sh wlan0 eth0 -
Reach LuCI at
http://192.168.10.122(http://openwrt.lanshould work too) from the PC, orhttp://192.168.13.1from a device on theopenwrt-openwifiSSID. There is no password by default. Set one for any real use. Network → Wireless is where you configure the radio:
Research config, not a deployment config
The default OpenWrt network setup with the openwifi package is meant for research. For deployment, move eth0 into the wan zone as a DHCP client (not a DHCP server) and assign the wireless network to lan.
Build an OpenWrt image with openwifi¶
Prerequisite: Docker on a Linux host (Windows untested). Vivado is not required.
-
Clone the OpenWrt source with openwifi support (branch
openwrt-openwifi_v25.12.5= OpenWrt v25.12.5, Linux 6.12, mac80211 v6.18):git clone --branch openwrt-openwifi_v25.12.5 https://github.com/open-sdr/openwrt-openwifi.git -
Build the container (OpenWrt's Docker guide):
docker build --rm --tag openwrt:debian_12 --file ./Dockerfile ./openwrt-openwifi -
Start the container (drops you into an interactive shell in the mounted source):
./start_docker_openwrt_build.sh -
Update package feeds (pulls in the openwifi packages feed):
./scripts/feeds update ./scripts/feeds install -a -
Configure the build, either from a provided default:
cp configs/adrv9364z7020_defconfig .config…or manually with
make menuconfig, selecting your Architecture (zynq or zynqmp), your Board, and the openwifi kernel module under Kernel Modules → Wireless Drivers → Openwifi kernel package:

Handy extras: Network → SSH → openssh-sftp-server (enables
scpto the board) and Utilities → Editors → nano. -
Build, keeping the job count low (about 3) to avoid dependency-ordering errors (retry with fewer jobs if it fails):
make -j3 V=sc -
Flash the resulting image with the same
ddprocedure as the quick start. The build places the image underbin/targets/in the build tree (for the zynq target,bin/targets/zynq/generic/). Exit the container withCtrl+Dfirst.
Building for every board at once
doc/img_build_instruction/openwrt/build_images.sh in the openwifi repo repeats steps 5 and 6 for every *_defconfig in openwrt-openwifi/configs/, or only the boards passed as arguments, producing images under ./output_images. Each board is built by doc/img_build_instruction/openwrt/build_image_for_board.sh, which you can also run directly to build a single board.
OpenWrt tips¶
- Userspace tools are pre-installed. The openwifi package puts all
user_spacefiles under/root/openwifi(so the app-note scripts work), and installs the compiled tools (sdrctl,inject_80211,analyze_80211,side_ch_ctl) into/usr/bin, so they're in$PATH. - Kernel modules are packed in. No manual copying is needed, and
insmod side_chworks directly. - SSH uses mDNS:
ssh root@openwrt.lan, no password by default. - The app-note IQ and CSI workflows work on OpenWrt with minor differences (for example
insmod side_ch iq_len_init=4095, thenside_ch_ctland the host-side Python display scripts as usual).
Debugging the openwifi package¶
To iterate on the openwifi source without rebuilding from git each time, mount local sources into the container. Add to the docker run line in start_docker_openwrt_build.sh:
--volume "$(pwd)/openwifi:/openwifi" \
then point the package feed at a local checkout by editing OpenWrt's feeds.conf.default to replace the git openwifi feed with:
src-link openwifi /openwrt-openwifi-packages-feed
You can also bind-mount the OpenWrt tree under /workdir so paths printed in the container are copy-pasteable on the host. OpenWrt-specific issues (including the ZCU102 UART/SODIMM problem) are collected in Troubleshooting → OpenWrt-specific.
Buildroot: compact images for ANTSDR boards¶
A third, Buildroot-based image workflow builds a much smaller SD-card image (about 169 MB, versus the multi-gigabyte ADI Kuiper image). It is an additional path alongside Kuiper and OpenWrt, not a replacement, and it targets only the MicroPhase ANTSDR boards:
board_name argument |
Hardware |
|---|---|
antsdr_e200 |
ANTSDR-E200 |
antsdr |
ANTSDR-E310/ANT |
e310v2 |
ANTSDR-E310V2 |
Prerequisites: the buildroot git submodule (git submodule update --init buildroot), and the matching board's system_top.xsa. By default the build script looks for it under ../openwifi-hw-img/boards/<board>/sdk/system_top.xsa, or set the OPENWIFI_XSA or OPENWIFI_HW_IMG_DIR environment variable to point elsewhere.
Build with:
./buildroot-build.sh <board> build
The Linux kernel, modules, and ext4 root filesystem are shared across all three boards and built once. Only U-Boot, the device tree, and the FPGA bitstream stay board-specific. Login is root / openwifi, and eth0 is a static 192.168.10.122 as in the other workflows.
Runtime FPGA handling differs slightly from Kuiper and OpenWrt. Since U-Boot already configures the FPGA at boot, ./wgd.sh on a Buildroot image reuses that configuration by default instead of reloading a local bitstream file. Set OPENWIFI_RELOAD_FPGA=1 before running ./wgd.sh to force a reload through Linux FPGA Manager for development.
Full details are in doc/img_build_instruction/buildroot/README.md in the openwifi repo.
Related pages¶
- Getting Started: flashing a prebuilt image and first bring-up.
- Boot, Kernel & Device Tree: how the kernel, device tree, and
BOOT.BINthat these builds install are produced. - Software Development Workflow: driver rebuilds and the live reload loop.
- Troubleshooting: boot and OpenWrt-specific issues.