1200 Baud Packet Radio Details

Lessons Learned

When I started working on my OpenTracker project, I discovered that there is very little documentation available on the low-level aspects of 1200 baud packet radio. I learned a lot of lessons the hard way during the course of the project, and I thought I'd write a quick summary here. If you'd rather skip all this and have something else handle the low-level details for your project, check out my Packet Shooter firmware.

Physical Layer

1200 baud packet uses Bell 202 AFSK. AFSK is audio frequency shift keying, which means the signal is modulated using two audio tones, as opposed to regular FSK, where the radio frequency carrier itself is shifted in frequency. Using FSK generally requires a DC-coupled connection directly to the radio's discriminator. AFSK has the advantage of working through a regular audio path, which makes it well suited for use with radios designed for voice.

Audio Tones

Bell 202 uses a tone of 1200 hz for mark and 2200 hz for space. This is about as far as most packet documentation goes, and unfortunately it's a bit misleading in this case. Packet uses NRZI (non-return to zero inverted) encoding, which means that a 0 is encoded as a change in tone, and a 1 is encoded as no change in tone.

It is also worth noting that the tones must be continuous phase - when you shift from one tone to another, there can't be any jump in phase. For example, if you're sending a 1200 hz tone and the waveform is at its peak when you switch to 2200 hz, the waveform is still at its peak - it can't start back at zero, or any other point. This makes it impossible to generate proper AFSK using something like the Basic Stamp's audio tone function.

Bit Order

Bytes are sent least-significant bit first.

Bit Stuffing

Because a 1 is represented by no change in tone, sending a long string of 1s would result in an unchanging signal, which in turn would cause the receiver to drift out of sync with the transmitter. Bit stuffing prevents this. With the exception of the flag sequence, which we'll get to in a minute, any run of five 1s has a 0 inserted after the fifth 1. This stuffed 0 must be removed by the receiver.

Be warned: The description of bit stuffing in version 2.0 of the AX.25 specification (paragraph 2.2.6) is WRONG. The 0 is inserted after the fifth 1, not the first 1.

Data Link Layer

The AX.25 protocol defines the packet format. AX.25 is based on the High Level Data Link Control protocol, or HDLC. Frames start and end with the binary sequence 01111110 - the HDLC 'flag'. This sequence is not bit stuffed, so this is the only time you'll see a run of six 1s. The contents of the AX.25 frame are well-documented, so I won't go into much detail here.

Frame Check Sequence

One detail of the AX.25 format that deserves attention is the Frame Check Sequence (FCS) checksum. This is a two-byte checksum added to the end of every frame. It's generated using the CRC-CCITT polynomial, and is sent low-byte first.

The CRC-CCITT algorithm has plenty of published code examples, but the one I needed, and had trouble finding, was the algorithm for calculating the FCS one bit at a time, rather than a byte at a time. That algorithm is as follows:

Start with the 16-bit FCS set to 0xffff. For each data bit sent, shift the FCS value right one bit. If the bit that was shifted off (formerly bit 1) was not equal to the bit being sent, exclusive-OR the FCS value with 0x8408. After the last data bit, take the ones complement (inverse) of the FCS value and send it low-byte first.