Software defined KISS modem
I’ve kept working on my SDR framework in Rust called RustRadio, that I’ve blogged about twice before. I’ve been adding a little bit here, a little bit there, with one of my goals being to control a whole AX.25 stack.
As seen in the diagram in this post, we need:
- Applications, client and server — I’ve made those.
- AX.25 connected mode stack (OSI layer 4, basically) — The kernel’s sucks, so I made that too.
- A modem (OSI layer 1-2), turning digital packets into analog radio — The topic of this post.
The job of the modem
Applications talk in terms of streams. AX.25 implementation turns that into individual data frames. The most common protocol for sending and receiving frames is KISS.
I’ve not been happy with the existing KISS modems for a few reasons. The main one is that they just convert between packets and audio. I don’t want audio, I want I/Q signals suitable for SDRs.
On the transmit side it’s less of a problem for regular 1200bps AX.25, since either the radio will turn audio into a FM-modulated signal, or if using an SDR it’s trivial to add the audio-to-I/Q step.
On transmit you do have to trigger PTT, though. You can do VOX, but it’s not optimal.
But on the receive side it’s a completely different matter. Once it’s audio, the information about the RF signal strength is gone. It makes it impossible to work on more advanced reception strategies such as whole packet clock recovery, or soft decoding. Soft decoding would allow things like “CRC doesn’t match, but this one bit had a very low RF signal strength, so if flipping that bit fixes the CRC, then that’s probably correct.
Once you have a pluggable KISS modem you can also innovate on making the modem better. A simple example is to just run the same modem in multiple copies, thereby increasing the bandwidth (both in the Hz sense and the bps sense).
Since SDRs are not bound to audio as a communication medium, they can also be changed to use more efficient modulations. Wouldn’t it be cool to build a QAM modulation scheme, with LDPC and “real” soft decoding?
Yes, an SDR based modem does have two main challenges:
- Power. SDRs don’t transmit at high power, so you need to get it through a power amplifier.
- Duplex. Most TX-capable SDRs have two antenna ports. One for TX, one for RX. You’ll need to have two antennas, or figure out a safe way to transmit on the same antenna without destroying the RX port.
For the duplex problem, the cheap and simple solution is to use frequencies on different bands, and put a band pass filter on the receive port, thus blocking the transmitted power. SDR outputs are not clean, so you’ll need a filter on the transmit path too anyway. In other words, you can just use a diplexer.
It gets harder if RX and TX need to be on the same band, or worse, the same exact frequency. Repeaters tend to use cavity filters. But that’s a bit bulky for my use cases. And in any case don’t work if the frequency is exactly the same.
More likely a better use case here is to use half duplex, with a relay switching from RX to TX and back. But you need to synchronize it so that there’s no race condition that accidentally plows 10W into your receive port, even for a split second.
But that’s a problem for the future. For now I’m just using two antennas.
How to run the modem
I’ve implemented it. It works. It’s less that 250 lines of Rust, and the actual transmitter and receiver is really easy to follow. Well… to me at least.
In order to not introduce too many things at a time, here’s how to use the regular Linux kernel stack with my new bell202 modem. Bell202 is the standard and most used amateur radio data mode. Often just referred to as “1200bps packet”.
Build and start the modem:
$ git clone https://github.com/ThomasHabets/rustradio
[…]
$ cd rustradio
$ cargo build --release -F soapysdr,fast-math,nix --example bell202
[…]
$ ./target/release/examples/bell202 \
-d 'driver=uhd' \
--tty ./mydev.link \
--freq 144.800m \
--ogain 0.5
Create Linux AX.25 config:
$ sudo apt install libax25
$ echo 'radio1 M0XXX-1 9600 255 5 My test' | sudo tee -a /etc/ax25/axports
Attach the kernel to the modem:
$ sudo kissattach ./mydev.link radio1
AX.25 port radio1 bound to device ax0
$ sudo kissparms -c 1 -p radio1
Now use it as normal:
$ axcall M0XXX-2
[…]