Build a UHF SDR transceiver: Raspberry Pi 5 + bladeRF 2.0 micro

This guide turns a Raspberry Pi 5 and a Nuand bladeRF 2.0 micro into a networked VHF/UHF transceiver for the SDR-SLC client on your Mac. When you’re done, the Pi sits next to the antenna, and the Mac anywhere on your home network finds it automatically. You get a waterfall, FM/CW/SSB receive, and transmit, plus DMR, YSF and D-STAR voice through a vocoder service on the same Pi.

Everything here was done on a fresh Pi, in this order, and every command below is the one that was actually run. Where something went wrong along the way, the fix is in the step where you’ll need it.

Time: about an hour, most of it waiting for downloads and compiles.

What you need

ItemNotes
Raspberry Pi 5 (4 GB or more)With the Active Cooler. The radio keeps the CPU busy continuously.
Official 27 W USB-C power supply (5 V / 5 A)Not optional. See Power below.
microSD card, 32 GB or more (A2), or an NVMe drive
Nuand bladeRF 2.0 micro xA4 or xA9With a good USB 3 cable.
Ethernet cableWi-Fi works, but the IQ stream is continuous UDP and Ethernet is far more reliable.
Antennas, or a 50 Ω dummy loadOne for RX1, one (or the dummy load) for TX1.
A Mac with the SDR-SLC client 1.2From the Mac App Store.
Optional: DVMEGA DVstick30 or ThumbDVHardware AMBE vocoder. Not needed, because the software vocoder works without it.

Licence. The bladeRF transmits from 47 MHz to 6 GHz. Only transmit on frequencies and at powers your amateur licence allows, and use a dummy load until you’ve seen the transmitter behave.

First, power

The Pi 5 only allows its USB ports their full current (1.6 A shared across all four) when it detects the official 5 A supply. With anything weaker it limits them to 600 mA. That’s enough for the Pi alone, but not for a bladeRF that is transmitting. The bladeRF browns out and drops off the USB bus, which looks like a software crash.

Don’t power an external power amplifier through the bladeRF’s bias tee. The bias tee is meant for a small LNA or Nuand’s own amplifier modules. A 5 W amplifier for 2 m or 70 cm draws amps, usually at 12 to 13.8 V. Fed through the bias tee, all of that current comes out of the Pi’s USB port, and the whole station goes down at once. That is exactly what happened while writing this guide. Give the amplifier its own supply.

If you do use the bias tee for a small amplifier or LNA, give the bladeRF its own 5 V supply through its DC barrel jack. The board switches over to it automatically, and the Pi’s USB port no longer has to carry the load.

Step 1: Raspberry Pi OS

Flash the card

  1. On your Mac, install Raspberry Pi Imager from raspberrypi.com/software.
  2. Choose Raspberry Pi 5, then Raspberry Pi OS (other) → Raspberry Pi OS Lite (64-bit). Lite has no desktop. The radio doesn’t need one, and it leaves more CPU for the IQ stream. This guide used the October 2026 release, based on Debian 13 “trixie”.
  3. When Imager offers to customise the settings, set:
    • a hostname, e.g. slc-uhf;
    • your username and password;
    • your time zone;
    • Enable SSH (with a password, or your Mac’s public key).
  4. Write the card, put it in the Pi, connect Ethernet and the bladeRF (to one of the blue USB 3 ports), and power up.

First login

From Terminal on your Mac:

ssh <user>@slc-uhf.local
sudo apt update && sudo apt full-upgrade -y
sudo reboot

Tip: locale warnings. If every login prints setlocale: LC_CTYPE: cannot change locale (UTF-8), that’s macOS Terminal sending a locale name Linux doesn’t know. It’s harmless, but it clutters your logs. Stop the Pi from accepting it:

sudo sed -i 's/^AcceptEnv LANG LC_\*/#&/' /etc/ssh/sshd_config
sudo systemctl restart ssh

Check

uname -m                    # aarch64
grep PRETTY /etc/os-release # Debian GNU/Linux 13 (trixie)
vcgencmd get_throttled      # throttled=0x0
lsusb | grep -i nuand       # ... Nuand LLC bladeRF 2.0 micro

throttled=0x0 means no under-voltage or overheating so far. If you see anything else, fix the power supply or cooling before going further.

Step 2: bladeRF drivers

Install the library and tools

Debian ships them, so there’s nothing to compile:

sudo apt install -y bladerf libbladerf-dev

Give the board to the plugdev group

This step is easy to miss. Debian 13’s bladeRF rule only gives access to the person logged in at the Pi’s own keyboard and screen. An SSH session doesn’t count, and neither does a system service. Without the rule below, bladeRF-cli reports insufficient permissions, and the radio service can’t open the board.

echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2cf0", MODE="0660", GROUP="plugdev"' \
  | sudo tee /etc/udev/rules.d/99-sdr-slc-bladerf.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --subsystem-match=usb --attr-match=idVendor=2cf0

Check

bladeRF-cli -e info
bladeRF-cli -e version

You should see your board, USB speed: SuperSpeed, and the versions:

  Board:                    Nuand bladeRF 2.0 (bladerf2)
  FPGA size:                301 KLE
  FPGA loaded:              yes
  USB speed:                SuperSpeed

  libbladeRF version:         2.5.0-0.2024.05-1
  Firmware version:           2.4.0-git-a3d5c55f
  FPGA version:               0.15.3 (configured from SPI flash)

FPGA size tells you which board you have: 301 KLE is an xA9, 49 KLE is an xA4.

USB speed: SuperSpeed confirms the board is on a USB 3 port. If it says HighSpeed, move it to a blue port or try another cable. USB 2 can’t carry the wider sample rates.

Only if FPGA loaded says no

Many boards load their FPGA image from their own flash. If yours doesn’t, put the image where libbladeRF looks for it. For an xA9:

sudo mkdir -p /usr/share/Nuand/bladeRF
cd /usr/share/Nuand/bladeRF
sudo wget -O hostedxA9.rbf https://www.nuand.com/fpga/hostedxA9-latest.rbf

For an xA4, replace xA9 with xA4 in both places on the wget line. Then run bladeRF-cli -e version again and check that there’s no version-mismatch warning.

Step 3: the radio service, sdr-slc-bladerfd

This is the program that turns the bladeRF into an SDR-SLC radio on your network. Source code: github.com/pa2ix/sdr-slc-bladerfd.

Build and test

sudo apt install -y git build-essential pkg-config libavahi-client-dev avahi-daemon python3
mkdir -p ~/src && cd ~/src
git clone https://github.com/pa2ix/sdr-slc-bladerfd
cd sdr-slc-bladerfd
make
make check 2>&1 | grep -E 'checks:|passed|FAIL'

make check runs the full conformance suite against a simulated board. It takes a minute or two, and every line should report 0 FAIL or n/n checks passed.

Ask the real board what it can do

./sdr-slc-bladerfd --print-capabilities > ~/caps.json

The log lines it prints are the interesting part:

INFO [RADIO] opened bladeRF ... fpga=0.15.3(A9) fw=2.4.0 lib=2.5.0
INFO [RADIO] rx: 70.000-6000.000 MHz, 520834-61440000 Sps, gain -15.0..60.0 dB ...
INFO [RADIO] tx: 47.000-6000.000 MHz, 520834-61440000 Sps, gain -23.0..66.0 dB ...

Install as a service

The service runs as its own user, slc, which you create first:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin slc
sudo make install

Create the transmit key

Transmit is locked until a key exists, so nobody else on your network can key your transmitter. Generate one key per client machine:

sudo sdr-slc-bladerfd keygen --key-id macbook
  key_id : macbook
  psk    : 3f9c…   (64 hexadecimal characters)

The secret is shown only once. Put it in your password manager. You’ll enter the key ID and the secret in the client.

Then let the service read the key file. Don’t skip this, or the service refuses to start with cannot read key file: Permission denied:

sudo chown root:slc /etc/sdr-slc/bladerfd.keys
sudo chmod 640 /etc/sdr-slc/bladerfd.keys

Start it

sudo systemctl daemon-reload
sudo systemctl enable --now sdr-slc-bladerfd
journalctl -u sdr-slc-bladerfd -n 30 --no-pager

Look for these lines:

INFO [SLC] tx requires psk-hmac-sha256 authentication; 1 key loaded from /etc/sdr-slc/bladerfd.keys
INFO [RADIO] opened bladeRF serial=... fpga=0.15.3(A9) fw=2.4.0 lib=2.5.0
INFO [SLC] SLC-6107C0 ready: ... control tcp/4620, vita rx udp/4621 tx udp/4622
INFO [MDNS] published "SLC-6107C0" as _sdr-slc._tcp.local. on port 4620

SLC-6107C0 is your radio’s name on the network. The last six characters come from your board’s serial number, so yours will differ.

Step 4: the vocoder service, sdr-slc-vocoderd

DMR, YSF and D-STAR use the AMBE family of voice codecs. The client hands that work to a vocoder service on the network. It uses a DVSI AMBE-3000 chip (DVstick30, ThumbDV) when one is plugged in, and the open-source mbelib-neo when not. Source code: github.com/pa2ix/sdr-slc-vocoderd.

sudo apt install -y cmake libjson-c-dev
cd ~/src
git clone --recursive https://github.com/pa2ix/sdr-slc-vocoderd
cd sdr-slc-vocoderd
make mbelib
make
make test 2>&1 | tail -3
sudo make install
sudo systemctl daemon-reload
sudo systemctl enable --now sdr-slc-vocoderd
journalctl -u sdr-slc-vocoderd -n 15 --no-pager
  • --recursive matters, because it fetches mbelib-neo along with the vocoder.
  • make mbelib builds a pinned mbelib-neo into the project folder, so nothing extra is installed system-wide.
  • make test takes about 30 seconds and ends with all tests passed.

Without a DVstick, the log says:

vocoder: no AMBE-3000 found; using mbelib-neo 2.4.0 for all streams (plug one in at any time, it is picked up automatically)

That’s fine. You can plug a stick in later, without restarting anything.

Both services listening?

ss -ltnup | grep -E '4620|4621|4622|4630'
PortService
TCP 4620radio control
UDP 4621receive IQ stream (VITA-49)
UDP 4622transmit IQ stream (VITA-49)
TCP 4630vocoder control

If you run a firewall on the Pi, open these ports to your home network only. Never forward them from the Internet. Anyone who can reach a transmitter can try to key it.

Step 5: receive with the client

  1. On the Pi, follow the radio’s log live: journalctl -u sdr-slc-bladerfd -f
  2. In the SDR-SLC client on your Mac, click Discover. Two devices appear: the radio (SLC-xxxxxx) and the vocoder (SLC-VOCODER-slc-uhf).
  3. Select the radio, enter the key ID and secret from step 3 in its authentication settings, and click Connect.
  4. Tune to a signal you know is there, such as your local repeater’s output, and set the RF gain to around 30 to 40 dB.

You should see the signal in the spectrum and waterfall, and hear it. Changing the RF gain should visibly move the noise floor.

Step 6: transmit

Out of the box the service radiates nothing. It accepts and counts transmit packets, but the DAC (the part that actually produces the transmit signal) stays off. Connect an antenna or dummy load to TX1, then arm it:

sudo sed -i 's/^SLC_ARGS=-v$/SLC_ARGS=-v --tx-rf --tx-persistent-stream/' /etc/sdr-slc/bladerfd.conf
grep ^SLC_ARGS /etc/sdr-slc/bladerfd.conf
sudo systemctl restart sdr-slc-bladerfd
  • --tx-rf arms the transmitter.
  • --tx-persistent-stream keeps the transmit path open between overs, at minimum gain while idle. Without it, the bladeRF can stall after the first over and send a carrier with no audio.

Test without the client

With a handheld listening on 433.500 MHz:

cd ~/src/sdr-slc-bladerfd
read -s -p "PSK: " SLC_PSK; echo; export SLC_PSK SLC_KEY_ID=macbook
./tools/tx-tone.py --host localhost --freq 433500000 --mode nfm --gain 20
  • read -s keeps your secret off the screen and out of your shell history.
  • Transmit gain starts at the board’s minimum, so --gain is needed before anything is audible. 20 is a low level, fine for a handheld in the same room.

You should hear a steady tone. The tool ends by printing the service’s packet count, which should show nothing discarded.

Then a real over

Key up from the client on a simplex frequency, with your handheld listening, first in FM and then in DMR or YSF. Your audio should come through clean from the first syllable to the end of the over.

Keeping it up to date

cd ~/src/sdr-slc-bladerfd && git pull && make && sudo make install && sudo systemctl restart sdr-slc-bladerfd
cd ~/src/sdr-slc-vocoderd && git pull --recurse-submodules && make && sudo make install && sudo systemctl restart sdr-slc-vocoderd

make install keeps your existing /etc/sdr-slc/bladerfd.conf, so your settings survive updates.

Troubleshooting

SymptomCause and fix
bladeRF-cli: could not open it due to insufficient permissionsThe udev rule from step 2 is missing, or hasn’t been applied. Add it and re-trigger udev, or unplug and replug the board.
Service won’t start: cannot read key file … Permission deniedRun the chown root:slc / chmod 640 lines from step 3.
Client: transmit refused, unauthorizedThe key ID or secret in the client doesn’t match /etc/sdr-slc/bladerfd.keys. To start over, delete the line, run keygen again, fix the permissions, and restart.
Carrier on the air but no audio, from the second over on--tx-persistent-stream is missing from SLC_ARGS.
The whole station resets when you transmit or switch on an amplifierPower. Use the 27 W supply, never power a PA from the bias tee, and give the bladeRF its own 5 V supply if you use the bias tee at all. Check vcgencmd get_throttled.
Log: rx gain 40.0 dB requested, RFIC reports 60.0 dB at startupLeft over from the board’s previous session. It clears once the client tunes and sets the gain. If it stays at your working frequency, make tools/gain-probe && ./tools/gain-probe 438025000 shows what the chip actually delivers there.
The radio doesn’t appear in DiscoverCheck that avahi-daemon is running on the Pi (systemctl status avahi-daemon) and that the Mac and the Pi are on the same network segment. Discovery uses mDNS, which doesn’t cross routers or VLANs.
USB speed: HighSpeedThe board is on a USB 2 port or cable. Use a blue port and a USB 3 cable.

What’s next

The station in this guide runs at the bladeRF’s own output level, a few milliwatts. The next step is a proper 2 m / 70 cm front end: amplifiers with their own supply, receive/transmit switching, and a sequencer that switches the relay and amplifier in the right order around each over. That’s a post of its own.

Questions or corrections: the source code for both services is on GitHub, and issues are welcome there.

73, Ivo PA2IX