2026-03-06
TL;DR: My homebrew TNC has successfully sent data to
the APRS network. This required some investigation of the electrical
requirements for my radio’s microphone input. I also built an external
VOX circuit that is suitable for use with direwolf and
APRSdroid.
The major parts of the TNC at the moment are:
(1) is easy enough. The Nano has a USB mini-B socket, and when you
plug it into a USB host it appears as a virtual serial port. Data that
comes into the Nano’s USB port get translated into UART frames and sent
to the serial input as if they’d come in on pin D0.
Similarly, serial data going out as UART on pin D1 gets
translated to USB and sent to the host. This slightly silly abstraction
lets arbitrary KISS clients on the host side send data to the TNC
firmware in the same way that ham operators did forty years ago, except
with more layers.
I talked a fair amount about (2) last time, so I won’t repeat that here. (3) is a method for converting parallel digital data into serial analog signals. For a good overview of how this network does that, see this video. The Falstad simulation below may also help your understanding. The general idea is that some digital system can set several bits high or low, and these get mixed together according to voltage division rules such that the first bit adds half the total potential, the second bit adds a quarter, the third adds an eighth, and so on up to arbitrary precision. I decided on five bits of precision, so five digital pins are connected to this DAC. To generate a sine wave of a particular frequency, I use a lookup table and a special wrapping counter arrangement to efficiently step through the necessary outputs. All this ends up producing a chunky but usable wave.
Ls to toggle the digital inputs. If the simulator
doesn’t load for you, you can try on
the simulator site or view a
screenshot.
(4) took the longest because of my relative unfamiliarity with analog electronics and lack of documentation for my radio’s input. I had to do some mild reverse engineering to observe the signal coming out of my laptop and going into the radio microphone to understand what it needs. Eventually I figured out that the radio has pullup resistor on the microphone line such that it stays at about 3 V relative to its internal ground when unloaded. Apparently the proper approach is to load it with roughly 2 kΩ to ground, which pulls it to about 1.5 V. (This in particular took me a long time to understand.) Then you’re supposed to AC couple your signal in with the expectation that it stays in the range of 0–3 V at the microphone input. This means that the signal before AC coupling must be no more than 3 V peak to peak, and in practice to avoid clipping I limited my input to about 1.5 V.
The DAC output nominally swings between 0 and 4.84 V, so at first I thought this was a matter of scaling the voltage down with a divider. The output impedance of the R-2R network is equal to R, which I chose to be 10 kΩ. This is too weak to drive the microphone, so I buffer it with an LM358 opamp. The output of the buffer connects an RC low-pass filter to smooth out the sharp steps from the DAC. Then the signal continues to the AC coupling capacitor, which connects to the load resistor and finally the microphone input.
I thought this would be enough, but it wasn’t. It turns out that the LM358 has some limitation in its output when it comes close to its rail voltages, so the setup I’ve described so far will clip the signal on the negative part of the wave. To resolve this, I added a second lower-impedance voltage divider to the output of the DAC that raises the signal into the opamp’s safe range. This, at last, produces a well-conditioned signal for input to the radio.
The TNC now works pretty consistently on the air. Failures generally
only occur when I accidentally pull something out of the breadboard.
I’ve got to make a proper board for this project, but I’m glad I waited
so I could work out the signal conditioning problems. I made several
transmissions that were accepted by the local APRS network and showed up
on aprs.fi, so I consider that to be a success. As
currently built, the TNC is polite with respect to PTT speed but has no
way to listen
for other stations that might be transmitting. I think this probably
resulted in a few instances of my station “stomping” on others during
testing. Once I build the receiver, I’ll add a mechanism to avoid this
like any well-designed APRS station.
I couldn’t get the TNC to work with APRSdroid after admittedly only a few minutes of testing. The app definitely showed successful USB communication with the TNC, so I guess I either have something configured wrong or the app is sending data that my firmware doesn’t expect. I would like to eventually figure this out because of the AFSK decoding issues I’ve had with my USB audio adapter (see below), which rules out VOX operation without a higher-quality adapter.
I didn’t get nearly as far on the receiver as I wanted this week, but I did make a little progress by learning how other systems solve this problem. A simple receiver would run the input signal through two bandpass filters, each tuned for the mark and space frequencies. Then, the filter outputs would run into a comparator to determine the observed tone. Some systems also apply automatic gain control to the filter outputs before comparison for de-emphasis, since some (most?) HTs adjust signal frequencies before transmission to optimize for voice. This can all be done in the analog domain in principle, with a digital system doing clock recovery, symbol logic, and frame parsing, but as usual an all-digital solution is more flexible. All you have to do in that case is connect the receiver to an ADC and do math on the sampled values.
The Nano runs at 16 MHz, so for a sample rate of 8000 Hz, that’s 2000 cycles per sample. I think most instructions take between one and three cycles to execute, so we’re looking at maybe 800 or so instructions. That probably rules out FIR bandpass filters because—from what I can tell—a suitable filter would need a few dozen taps. That’s a lot of multiply-adds per sample just to get the first part done. Analog filtering doesn’t look so bad here, but it does increase part count.
I’m undecided on the question of analog versus digital for this problem. Now that I have slightly more familiarity with opamp audio circuits, maybe I could build active filters for the mark and space frequencies to achieve analog filtering with fewer components. A purely digital solution would be a good challenge too. I might try both in parallel and see which one yields first.
Anyway, once I have the comparator output, I can apply the complicated but tractable logic of mapping to bits and frames. When a valid AX.25 frame arrives, the firmware will pack it into an HDLC frame and send it to the KISS host over the serial port.
In parallel with the TNC, I also spent some time designing and testing an external VOX circuit. My radio, like most, has an internal VOX circuit already, which means that it can be configured to activate the transmitter when a certain microphone input level is reached. This internal circuit is designed for use with speech and not data, so while it starts the transmitter relatively quickly, it leaves it on for far too long after the packet has been sent. The best solution is to directly control the PTT input to the radio like my TNC does (and like most do), but failing that you can use VOX if the on/off delays are short enough. Ideally the delay would be less than about 50 ms.
I took a swing at an external VOX circuit last time around on this project, but it didn’t work, and I got distracted by the TNC anyway. But when the TNC was giving me trouble this week, I suddenly didn’t mind wandering back to the VOX circuit. I settled on a pretty common design that uses a puny half-wave rectifier1 followed by an envelope detector and a comparator. My radio’s PTT input floats at around 1.5 V, and pulling it to ground activates the transmitter, so I fed the comparator output into an open-drain MOSFET. One of the comparator’s inputs is connected to a potentiometer, which allows me to adjust the sensitivity of the circuit. I’d like to also add a potentiometer to the envelope detector to be able to adjust the turn-off delay, but I don’t think that’s as important in this case.
I have been using this design on a breadboard with
direwolf to run a digipeater and IGate for the last day or
so. It seems to work fine, so at some point I will probably convert the
breadboard version into a PCB and maybe add an enclosure. I also tried
this circuit with APRSdroid and a USB-to-TRRS adapter, and it worked
well there too. Receiving didn’t work in that case, though, and
eventually I figured out that it was simply due to the quality of the
adapter’s input audio being quite poor. I verified this by recording
some audio through the adapter on my phone and laptop. I guess I’ll have
to buy a better adapter or come up with some other arrangement if I want
to run mobile with my VOX circuit.
The first stage is actually a precision rectifier, which probably isn’t strictly necessary but increases the sensitivity of the circuit for low-amplitude inputs. The LM358 has two opamps in the same package, so it cost me nothing to use it.↩︎