Background
This project started as an APRS decoder running on a Raspberry Pi Pico W — bare-metal C code doing AFSK1200 demodulation with DMA double-buffering and correlation-based detection, ported from multimon-ng. It worked, but the Pico's limitations (no OS, limited networking, manual everything) made it hard to extend.
The new direction: move to a Pi Zero 2 W running Linux, use Direwolf as the software TNC, and keep the same SA818 VHF module as the radio frontend. The result is a much more capable system that decodes APRS, gates packets to APRS-IS, and feeds a custom web-based tracker.
Hardware
| Component | Role |
|---|---|
| Raspberry Pi Zero 2 W | Compute (quad-core ARM Cortex-A53, 512MB RAM) |
| SA818 VHF module | 2m radio transceiver (144-148 MHz) |
| USB C-Media sound card | Audio capture (SA818 AF → mic input) |
| External 2m antenna | RF receive |
SA818 Wiring
The SA818 connects to the Pi via two interfaces: UART for control commands, and analog audio out to the USB sound card for packet demodulation.
SA818 Pin Pi Zero 2 W
───────── ───────────
VCC Pin 2 (5V)
GND Pin 6 (GND)
TXD Pin 10 (GPIO 15 / RXD)
RXD Pin 8 (GPIO 14 / TXD)
PTT Pin 11 (GPIO 17) — HIGH for RX
PD Pin 13 (GPIO 27) — HIGH to enable
AF OUT USB sound card mic input (via coupling cap)
The SA818 uses 3.3V logic on its UART — safe to connect directly to the Pi's GPIO. The AF output goes through a ~100nF coupling capacitor to the sound card's mic input to block DC bias.
OS and Initial Setup
The Pi is running Debian 13 (Trixie) aarch64 with the Raspberry Pi kernel. Headless, no desktop. Key configuration changes:
- Disabled serial console (
console=serial0,115200removed from cmdline.txt) - Disabled Bluetooth (
dtoverlay=disable-btin config.txt) to free the PL011 UART for the SA818 - Disabled
serial-getty@ttyS0andhciuartservices
This gives us /dev/serial0 → ttyAMA0 (the full PL011 UART) on GPIO 14/15 for reliable serial communication with the SA818.
Software Stack
Direwolf
Direwolf is a software TNC (Terminal Node Controller) that handles all the signal processing: AFSK1200 demodulation, HDLC framing, AX.25 decoding. It reads audio from the USB sound card and exposes decoded packets via a KISS TCP port.
# /home/baz/aprs/direwolf.conf
ADEVICE plughw:0,0
ACHANNELS 1
CHANNEL 0
MYCALL K0BAZ-10
MODEM 1200
KISSPORT 8001
AGWPORT 8000
LOGDIR /home/baz/aprs/logs
SA818 Control Script
A simple Python script configures the radio module over serial — sets frequency, volume, and filters:
#!/usr/bin/env python3
import serial, sys, time
def send_cmd(ser, cmd):
ser.write((cmd + '\r\n').encode())
time.sleep(0.5)
resp = ser.read(ser.in_waiting).decode(errors='ignore').strip()
print(f'> {cmd}\n< {resp}')
return resp
def init(freq='144.3900', squelch=0, volume=6):
with serial.Serial('/dev/serial0', 9600, timeout=2) as ser:
time.sleep(1)
ser.reset_input_buffer()
send_cmd(ser, 'AT+DMOCONNECT')
send_cmd(ser, f'AT+DMOSETGROUP=0,{freq},{freq},{squelch:04d},0,0000')
send_cmd(ser, f'AT+DMOSETVOLUME={volume}')
send_cmd(ser, 'AT+SETFILTER=1,1,0')
The SA818 responds with +DMOCONNECT:0 on success. The GPIO pins (PD and PTT) must be held HIGH for the module to power on and stay in receive mode.
APRS Gateway
The gateway script connects to Direwolf's KISS TCP port, decodes AX.25 frames, parses APRS position data (including Mic-E encoding), and POSTs packets to a remote web server:
Direwolf (KISS TCP :8001) → aprs_gateway.py → HTTPS POST → ewok.me/aprs/packet
Mic-E decoding was the trickiest part. The position is encoded in the destination address field of the AX.25 frame (latitude in the callsign bytes, longitude in the info field), with speed and course packed into three bytes. The gateway extracts all of this and sends clean lat/lon coordinates to the server.
Web Tracker
The server is a Flask app with a SQLite database. It receives packets via HTTP POST, stores them, and serves a web UI showing recent packets, station list, and status. It also gates packets to APRS-IS.
The web UI renders Mic-E packets with emoji symbols (🚗 for cars, 🏍️ for motorcycles, etc.) and strips the binary encoding that would otherwise display as garbage characters. Message packets show the addressee and content cleanly.
Systemd Services
Everything auto-starts on boot with proper dependency ordering:
sa818-gpio (holds PD/PTT HIGH)
→ sa818-init (configures frequency)
→ direwolf (audio decode)
→ aprs-gateway (KISS → web POST)
The GPIO service uses gpioset which naturally holds the process open to maintain pin state — perfect for a systemd simple service.
Audio Level Challenges
Getting the audio levels right was the biggest debugging effort. Key lessons:
- The USB sound card's mic input shows a consistent ~5000 peak from bias voltage even with no signal — you need to compare plugged vs unplugged to know if audio is actually flowing
- A Baofeng's speaker output is extremely hot for a mic input — needed the capture gain at only 5-10%
- The SA818's AF output is much quieter — needs 80-100% gain
- Disable Auto Gain Control on the sound card for consistent levels
- Direwolf is surprisingly tolerant of non-ideal levels — it decoded packets at audio levels from -15 to -6 dBFS
Results
With a Baofeng HT feeding audio to the sound card (temporary setup while the SA818 antenna connection is being sorted), the system successfully decodes APRS packets from stations across the Colorado Springs area. Packets are decoded, positions extracted (including Mic-E), and forwarded to the web tracker in real-time.
Stations heard through the WA6IFI-12 digipeater include mobile stations, weather stations, and portable IGates — all decoded and displayed with clean formatting on the web UI.
What's Next
- Fix the SA818 antenna/RF connection to replace the Baofeng with the dedicated module
- Add transmit capability for two-way IGate operation
- Add a map view to the web tracker using the decoded positions
- Design a PCB to integrate the Pi Zero 2 W, SA818, and sound card into a single compact unit (KiCad schematic already started)
73 de K0BAZ