blog

Building an APRS IGate with a Pi Zero 2 W and SA818 VHF Module

A from-scratch APRS receive IGate using a Raspberry Pi Zero 2 W, an SA818 VHF transceiver module, Direwolf, and a custom Python gateway that decodes packets and forwards them to a web tracker.

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

ComponentRole
Raspberry Pi Zero 2 WCompute (quad-core ARM Cortex-A53, 512MB RAM)
SA818 VHF module2m radio transceiver (144-148 MHz)
USB C-Media sound cardAudio capture (SA818 AF → mic input)
External 2m antennaRF 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,115200 removed from cmdline.txt)
  • Disabled Bluetooth (dtoverlay=disable-bt in config.txt) to free the PL011 UART for the SA818
  • Disabled serial-getty@ttyS0 and hciuart services

This gives us /dev/serial0ttyAMA0 (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

← back to index