#learningPacketRadio

Building an AX.25 Messenger App with C#, a TNC, and a Bit of Madness

1,046 words, 6 minutes read time.

YT MD-UV390 Digital Dual Band VHF UHF DMR Radio Waterproof Dustproof IP67 Walkie Talkie

It started with a simple idea — or at least it seemed simple. I was chatting with AI about ways to send text messages from device to device using nothing but the speakers and microphones already built into our phones or laptops. The idea was to modulate the data into frequencies just outside the human hearing range and transmit them. AI made it sound easy — encode, send, decode. But once I actually started trying to do it, it quickly became clear: it wasn’t simple at all. It wasn’t even close.

That failure wasn’t the end — it was the beginning. Because during that process, I remembered that I already owned something built for this kind of thing: a Mobilinkd TNC3. It connects via Bluetooth or USB, speaks the AX.25 protocol over KISS, and communicates via AFSK. And so, a new plan was born — use existing amateur radio tech to send messages between devices. But I didn’t just want to use the TNC — I wanted to understand what it was doing. That meant learning AX.25, digging into KISS, and writing everything from scratch in C#.

The Stack: Tools and Hardware

For this build, I used:

  • C# as the language of choice.
  • A Mobilinkd TNC3, connected via USB.
  • A TYT MD-UV390 radio, operating in analog mode.
  • Cables to connect the TNC to the Radio.

Why AX.25?

The Mobilinkd TNC3 operates using the AX.25 protocol layered over KISS. If you want to send messages through this hardware, you have to understand those protocols. AX.25, originally developed for packet radio in amateur radio use, is efficient, compact, and battle-tested.

I dove into it because the hardware required it — but what I found was something elegant. The structure is simple enough to understand but flexible enough to do real work. That said, actually implementing it in C# was another story.

Writing the AX.25 Messenger

The goal of the app was simple: send and receive short text messages over radio using the Mobilinkd TNC3 and AX.25 protocol.

The project is open source. You can find the code here:

👉 GitHub Repository: AX25Messenger

Let’s walk through some of the key components.

1. Encoding an AX.25 Frame

Here’s what the code looks like when we build the raw AX.25 frame. We start with destination and source callsigns, then add control and protocol fields, and finally append the actual payload.

public byte[] CreateAX25Frame(string destination, string source, byte[] payload){    var frame = new List<byte>();    // Encode callsigns    frame.AddRange(EncodeCallsign(destination, false));    frame.AddRange(EncodeCallsign(source, true));    // Control field (0x03 = UI frame)    frame.Add(0x03);    // Protocol ID (0xF0 = no layer 3 protocol)    frame.Add(0xF0);    // Add payload    frame.AddRange(payload);    return frame.ToArray();}

This code constructs the complete AX.25 frame for a UI (Unnumbered Information) packet. The EncodeCallsign method handles shifting and bit manipulation, making it compatible with the standard.

2. Interfacing with the TNC over KISS

KISS is a framing protocol that wraps the AX.25 data for transmission over a serial port. Here’s how we encapsulate an AX.25 frame into a KISS frame.

public byte[] WrapKISSFrame(byte[] ax25Frame){    var kissFrame = new List<byte>();    kissFrame.Add(0xC0); // Start delimiter    kissFrame.Add(0x00); // Command byte: TNC Data frame    foreach (var b in ax25Frame)    {        if (b == 0xC0) { kissFrame.Add(0xDB); kissFrame.Add(0xDC); }        else if (b == 0xDB) { kissFrame.Add(0xDB); kissFrame.Add(0xDD); }        else { kissFrame.Add(b); }    }    kissFrame.Add(0xC0); // End delimiter    return kissFrame.ToArray();}

This makes the AX.25 message digestible by the TNC.

3. Sending the Data

Once the KISS frame is ready, it’s sent out the serial port like this:

_serialPort.Write(kissFrame, 0, kissFrame.Length);

Where _serialPort is an instance of SerialPort in .NET. Simple, fast, and effective.

The Frustrations of Learning While Building

There were many moments during this project that made me want to throw the whole setup out the window. Trying to learn AX.25 and KISS while building an app around them is painful. The documentation is scattered, and much of what’s out there is old, inconsistent, or assumes a Linux environment.

It didn’t help that most of the time, you can’t even tell if your code is broken or if the radio just didn’t key up fast enough. Debugging means digging into the bits of a failed frame, then trying again and hoping for a clean transmission.

And then there’s AI — which was extremely helpful in doing early code research, mocking up skeletons, and pointing me toward technical docs. But it also hallucinated like crazy when it came to niche protocols. AI tends to guess at how it should be coded when it doesn’t have enough training data, and this project was definitely in that category. I had to train the AI by teaching it the protocol myself before it became useful.

What Didn’t Work

One major limitation of this build: I wasn’t able to test incoming AX.25 messages yet. That part of the project is still under construction. The receive pipeline, decoding the AX.25 frame, and confirming messages are cleanly parsed is a work in progress. It’s the next big hurdle.

Conclusion: A Love Letter to Low-Level Protocols

This whole build was a reminder that sometimes the best learning comes from the weirdest places. I didn’t plan to learn how AX.25 worked. I definitely didn’t plan to write a whole C# interface to a hardware modem. But here we are.

This was a project of persistence, curiosity, and a refusal to let a good idea die. If you’re into coding, radios, or just learning things the hard way, I can’t recommend it enough.

Follow the project on GitHub: AX25Messenger.

And if you enjoyed this write-up or want to follow along as I dive deeper into protocols, microcontrollers, or strange networking ideas, be sure to subscribe to the newsletter.

D. Bryan King

Sources

Disclaimer:

The views and opinions expressed in this post are solely those of the author. The information provided is based on personal research, experience, and understanding of the subject matter at the time of writing. Readers should consult relevant experts or authorities for specific guidance related to their unique situations.

Related Posts

#AFSKModem #AIInRadioProjects #AmateurRadio #amateurRadioTNC #APRSMessaging #AX25 #AX25CCode #AX25Examples #AX25ForProgrammers #AX25LearnByCoding #AX25RawFrames #AX25WithMobilinkd #AX25CMessenger #AX25DigitalMode #AX25Modem #AX25ParserC_ #AX25Protocol #ax25TestSetup #buildingAX25Apps #CAFSKDecoding #CAndKissProtocol #CAndPacketRadio #CAX25 #CHamToolkit #CProjectAX25 #CRadioApp #digitalModeCCode #digitalRadioMessage #hamRadioAndProgramming #hamRadioCExamples #hamRadioCoding #hamRadioCodingTutorial #hamRadioProgramming #hamRadioProtocol #kissAndAfsk #kissCProtocol #kissDecoderTutorial #kissInterface #KISSProtocol #kissProtocolC_ #kissProtocolExample #kissTnc #learnAX25 #learningPacketRadio #MobilinkdTNC3 #packetRadio #packetRadioDev #packetRadioOverAudio #radioMessageApp #radioTextOverAfsk #radioToRadioMessaging #realTimeRadioMessaging #serialRadioCommunication #TNC3AndKiss #TNC3HardwareGuide

Ham Radio Coding Workspace

Client Info

Server: https://mastodon.social
Version: 2025.04
Repository: https://github.com/cyevgeniy/lmst