Propeller TNC

Having had some success getting Arduinos (and bare AVR mega chips) to function as KISS TNCs, I thought I would try to achieve the same functionality using a Parallax Propeller board.

Background

I came across the QuickStart Propeller board from Parallax at my local RadioShack recently, $40 plus tax. The board has no jack for external DC, but does have a mini-USB jack for power and data, and a nice fat pin header with every Prop pin accessible. I had been curious about the Propeller for some time, and this seemed like an easy way to get started.

As a first attempt at AX.25 on the little board, I thought I would try out the "Bell 202 Modem" from the Object Exchange. I was MIGHTY disappointed to find that it is written not for the non-return-to-zero-inverted format used by AX.25 (and thus APRS), but for old school 1200b telephone modems (kinda like the title of the object). Undeterred, I found the APRS project in the OBEX. Unfortunately that one is TX-only, though the author did modify the Bell 202 code to speak genuine AX.25. I fiddled around with the assembler code unsuccessfully for a while before wandering off in dismay.

Back to square one, I decided to try porting the receive code from my previous Arduino TNC project (lucky version 13). I used Prop-GCC in order to stick as closely to the original C code as possible. The results of those adventures follow.

First, though, here are some...


Lessons learned in the course of Propeller development

    • The Sigma-Delta ADC circuit described in AppNote AN008 (and discussed in a thousand different places online), and the "ADC" object that comes with the PropellerTool software (which is based on that circuit), WILL NOT WORK ON A BREADBOARD. The sampling frequency (via the counters) is 80 MHz. That's VHF territory, where any wires long enough to be handled without tweezers will make the circuit unstable enough to be completely unreliable for ADC work. An example: with the ADC input grounded, the ADC output would wander as high as half-scale. Same thing at the other end - connected to the 3.3V rail, the ADC would read anywhere from half-scale to full-scale, sometimes giving a different value every time.

    • Of course Sigma-Delta ADC is not the only game in town. I developed my own sub-RF ADC from scratch, and it works just fine. See the notes from 2012-08-05, below.

    • The PLLDIV sub-register within the CTRx register only applies to the counter modes that are specifically labelled as PLL modes. This means that all other counter modes DO NOT USE THE PLL DIVIDER. For example, the counter mode for Sigma-Delta ADC (positive counter with feedback, mode 0b01001) runs at XTAL x 16 (usually 5 Mhz x 16 = 80 MHz). Here, a value of 0b000 for PLLDIV does not mean a "VCO / 128" clock tap (or XTAL x 16 / 128) as you might expect - the 0b000 is just a placeholder in the code samples.

    • This took two days and dozens of web sites to figure out. The datasheet seems to completely omit this little detail. Thankfully AppNote AN001 set me straight.

    • Prop-GCC does not seem to handle complex assembler routines gracefully. For example, I had no luck whatsoever trying to use the ASM routine from the "ADC" object. That code uses a couple of labels for controlling program flow. The "gas" compiler did not like these labels at all - it thought they were assembler functions, so of course it rejected them. To be fair the problem here may be my own unfamiliarity with inline assembler. I looked high and low for Prop-GCC code samples using entire ASM routines inline, but could not find any.

Some critically useful links:

Anyway, let's get back to....


Progress creating a functional TNC

2012-08-05:

I still suspect that the 1-bit ADC is causing problems, so I took a side trip through the land of Propeller ADCs. I was mighty disappointed to find that the classic Sigma-Delta ADC described in their literature in a non-starter when you build your circuits with breadboards (as described above). I had given some thought to a very different ADC design, and gave it a whirl.

The Propeller has no mixed-signal functionality - it is a digital-only device. To make it work with analog signals, you can either add some extra chips (an external ADC, perhaps), or you can work within the context of the binary nature of the Prop's inputs. In my case, all of my work with the Propeller up to today has involved using an input pin (binary!) to read the analog signal. The behavior of the pins is that a signal above (approximately) half of the supply voltage (3.3v / 2 = 1.67ish volts) is considered to be a logic "high", or a binary 1. Any signal below Vdd / 2 is considered a logic "low", or binary 0. If you take an analog signal like the audio from a VHF receiver, de-couple it from DC (to get just the AC audio signal), then bias that to Vdd/2 with a voltage divider, you are left with a DC waveform that crosses over the Propeller pin's logic threshold twice per audio waveform. The Prop reads this as a series of 0s and 1s that corresponds to the original audio. This is the "one-bit ADC" that I have been referring to (and suspicious of). The Sigma-Delta design described by Parallax also makes use of this logic threshold at Vdd/2 as an integral part of their design, albeit in a far more sophisticated way.

In trying to re-imagine an ADC for the Prop, it was clear that I would need to make use of this same behavior. Instead of biasing the de-coupled audio to Vdd/2, I thought, I could instead ramp up a bias voltage from zero to Vdd in small steps, and use the logic state change to tell me when the audio signal plus my bias voltage had reached Vdd/2. I would then know what the incoming signal was at that moment by how far up this ramp I had to climb to hit Vdd/2.

Once of my primary design concerns was that the entire ADC operation had to function well below the VHF frequency of the official Sigma-Delta design (80 MHz). I knew from working on the TX section of the Arduino-based TNC that I could easily build a four-bit waveform generator using only digital output pins. That only gives 16 discreet output voltages, but it would be a familiar place to start. That would give me 16 values-per-sample * 13200 samples/sec, or an operational frequency of just 211.2 kHz - a tiny fraction of the unstable VHF of Parallax's design. In fact, that left room for me to double the ramp-up frequency to 422.4 kHz, so that I could perform all of the ramp-up during the first half of each sample time, so that the sample would be read at more precise intervals.

Here's what all of this looks like in practice:

Schematic of the sawtooth generator circuit. Not much to it.

At first I tried to build this in such a way as to divide the incoming audio down to a smaller range of p-t-p voltages, then bias that to Vdd / 4, then add the sawtooth on top. That would avoid any input below zero volts on the ADC pin, and by scaling the sawtooth down to a range of 0 to Vdd/2 volts, would still allow the use all 4 bits of dynamic range. That turned out to require more circuit design than I was up to at the time. I suppose with an op-amp buffer stage or two I could get it to work, but the setup shown here works fine.

I have been using a 0.1uF ceramic cap for DC bypass of audio input signals for years, but today I actually listened to what it did to the incoming audio (the low frequencies were greatly attenuated). I've now replaced that with an electrolytic cap with far more capacitance. A non-polarized cap would probably be better here, but this is good enough for my purposes.

Schematic - Propeller Sawtooth Generator

The physical layout on a breadboard. I'm using pins 16-19 as the outputs that generate the waveform - I take the signal level I want to generate (0 through 15), left-shift it 16 bits (to start at pin 16), and OR that value into the OUTA register. Dead-simple to code.

Pin 21 is my ADC sensor. It flips to logic "high" for just a few CPU cycles out of each sampling period. It's hard to tell in this picture, but it is quite a bit dimmer than the four waveform pins.

Pin 23 indicates that an ADC sample is being read by the main COG. I connect this to the scope, or to my PC's sound card, to verify that the COGs are communicating to each other, and how frequently.

Pins 38 and 39 jump 3.3v and ground to the breadboard (the 3.3v bus on the breadboard is not used for this circuit).

Oscilloscope pics!

This is the "sawtooth" waveform. The climbing pattern falls within the first half (roughly) of each sample period, after which the code zeros out the waveform and waits for the next sample period. Note the non-linearities between binary values "0011" and "0100", and then an even larger gap moving from "0111" to "1000". I could probably tighten this up by using precision resistors, or hand-selecting individual resistors that are matched better, but it's working ok for now.

The scope is set to 1volt vertical and 20us horizontal per major division. The waveform runs 0 to 3.3 volts, with the center gap right at Vdd/2, around 1.67 volts.

Here, I am running the sawtooth output (above) into my sampling pin. My code stops ramping up the sawtooth once the input pins reads a logic "high". Binary value "1000" of the sawtooth (the first value after the mid-scale gap seen above) corresponds to Vdd/2, which is detected at the input as a logic "high". The code sees this, returns the sawtooth output to zero, and waits for the next sampling interval.

This is the same sawtooth pattern once an audio input signal is added. Note that the audio signal swings between +/- 0.5 volts. This drags the flat bottom of the waveform below 0 volts. As in the picture above, the code stops ramping up the sawtooth after the sum of audio plus sawtooth crosses Vdd/2, which is why the highest peaks here are below 2 volts.

Here I have switched the horizontal scan rate to better show the audio waveform (1ms per major division, 50 times slower than the previous shots). If you look very closely, you can see faint vertical lines stretching upward from the jumble of the audio signal; these vertical lines are the sawtooth ramps - they are going by so quickly at this scan rate they appear as a vertical blur at regular intervals (one "climber" per sampling period, around 120 of them in this shot). I disconnected the ADC sensor pin for this picture, so that each sawtooth would climb all the way to 3.3 volts.

This is my favorite picture in this series. This is almost precisely the mental image I had of how this design would work - I was very excited to see it appear on the scope.

Here's the same thing, but with the ADC sensor pin re-connected. All of the sawtooth "climbers" now stop when the input pin reads logic "high" near Vdd / 2 (around 1.67 volts). I added this "stop climbing" function to the code to avoid burning out the Propeller's input circuits - an audio peak at high volume (2 volts, maybe) plus 3.3 volts of un-checked sawtooth, all going into a 3.3v chip, might let the magic smoke out. I'm still a little worried about the down-swings, which as you can see dip well below zero volts.

So how does it play? I'm reading data bits over the air at a furious pace ("test4b.c" is attached below, but is very untidy). Even after all of this work, though, I still seem to be haunted by clock recovery issues.


2012-08-03:

Getting better (Test4a.c, below), but still having a lot of trouble with timing. I am not sure if the issue is one of having a crappy algorithm (always a possibility), bugs bugs bugs (I keep making dumb mistakes), or the 1-bit ADC. Since the ADC has only two phase states, I fear it may be more susceptible to large phase errors, which present themselves as jitter in the symbol detector. I spent a couple of days trying to get the standard ADC circuit working, only to discover (after much too much time) that the standard ADC cannot function reliably on a breadboard.

Some additional file attachments - a version of the test program (test4_pc.c) adapted to compile and run on a PC (I've been using TCC with great results), and a couple of WAV files with APRS traffic, down-sampled to 13.2KHz 16 bit mono. The "wav2dense.pl" script converts these to a file of ASCII ones and zeros, which are read by the PC version of the test program. All of this is to allow me to debug the DSP code without the timing and bandwidth limitations of trying to spit out serial data while keeping correct timing on the Prop at the same time.


2012-07-26:

The Prop code runs at about 20 MIPS, but actually seems a lot slower. I've had a lot of trouble with clock over-runs. I had to break the code into sections in order to get the ADC sampler routine down around 4000 clock ticks per sample. That piece runs in a separate COG. I'm using a digital input pin as a one-bit ADC - the audio input is biased to half of the regulated 3.3V rail, so downswings in the audio voltage make the pin read 0, and upswings read as 1. It works a lot better than I would have suspected. I'm clocking in bits furiously with open-squelch audio, though decode performance still has a ways to go. The code attached below (Test2.1.c) is maybe 25% functional as an RX-only KISS TNC as-is. I think my clock management code got screwed up in the transition to the multi-COG design, but that should be fixable.

I might also try re-writing this new version of the TNC code in Spin, just for kicks. No idea if it would be fast enough, but the COG management in Spin is a little more intuitive. I could probably spread the code across 3 or 4 COGS to mitigate slower program execution.


Attached Files

Link