Protocol Commonalities
An engineer's guided tour of the building blocks behind the digital modes
A commentary companion to the Rafe native protocol specifications.
The protocols/ directory documents ~40 modes one at a time — FT8, LoRa, DAB, P25, NAVTEX, TETRA, DVB-S2, and the rest. This document is the other axis. It assumes you can read the individual specs but haven't necessarily internalised why they keep reaching for the same dozen ideas, and it explains those ideas the way you'd want them explained if you were about to implement one: what the part does, how it fits with its neighbours, what you gain and give up, and the things that bite you in code.
We build up from the easiest thing you can put on the air and add sophistication one layer at a time. So we start at the antenna — with switching a carrier on and off — and only much later reach the finite-field algebra and belief propagation that the low-SNR modes depend on. If you read start to finish, each section leans on the one before it. If you're here for one topic, the headings and the periodic table at the end will get you there.
A note on audience: mid-level engineer. You know what a Fourier transform and a probability are; you don't need "a bit is a 0 or a 1." (If you do want "a bit is a 0 or a 1," start with the Beginner's Tour and come back. And if at any point you want the machinery derived rather than described — how Viterbi, Reed–Solomon or belief propagation actually work, with worked numbers — that's The Mathematics of the Digital Modes, the intermediate guide.) Where maths clarifies, it's here (\(\oplus\) is XOR / addition in \(\mathrm{GF}(2)\)); where a sentence does the job, maths stays out of the way.
Part 1 — Getting bits onto a carrier
Everything starts here: how do you make a radio wave mean something? You vary one of its three knobs — amplitude, frequency, or phase — in step with your bits. The modes differ mostly in which knob and how cleverly. We'll turn the knobs in order of difficulty.
1.1 On-off keying (OOK) — the simplest thing that works
Switch the carrier on for a 1, off for a 0. That's it. The receiver doesn't even need to know the exact frequency or phase — it just measures envelope (how much energy is here right now) and compares to a threshold.
Why you'd use it. It's almost free. The transmitter is a switch; the receiver is a diode and a comparator. This is why every $2 door sensor, tyre-pressure monitor, and weather station on 433 MHz uses it (Rafe decodes these in the rtl_433-style ISM engine), and why 1920s-era Feld Hell paints text by keying the carrier on for each black pixel of a 7×7 font.
What you give up. Amplitude is the least robust thing to modulate — fading, interference, and AGC all attack it directly. And a long run of 0s is just silence: the receiver has nothing to keep its timing locked to, and its threshold (usually the midpoint between the observed on- and off-levels) drifts.
Implementation notes. The one subtlety that matters is the slicer. A single fixed threshold chatters when the signal hovers near it, so use a Schmitt trigger — two thresholds with a hysteresis band (Rafe's ISM slicer uses midpoint \(\pm 10\%\) of the observed range). And decide packet boundaries by a reset gap (a silence longer than any intra-packet gap), but let the device decoder re-split on its own reset, because one global gap can't fit every device.
1.2 Frequency-shift keying (FSK) — the workhorse
Now put the bit in frequency: send tone A for a 1, tone B for a 0. The carrier is always on at full amplitude — it just moves.
Why it's the workhorse. Constant envelope is the gift that keeps giving. You can run the power amplifier flat-out in saturation (cheap, efficient, no linearity requirement), and fading that would wreck an OOK signal barely dents an FSK one because the ratio of the two tones' energies is what matters, not their absolute level. This is why pagers (POCSAG, 2-FSK) and the entire teleprinter tradition (RTTY, Baudot over mark/space tones) are FSK.
What you give up. Bandwidth. Two well-separated tones occupy more spectrum than one amplitude-keyed carrier for the same bit rate.
Implementation notes. Two ways to demodulate, and you'll meet both: a filterbank (one bandpass filter per tone, pick the loudest) or an FM discriminator (differentiate the unwrapped phase, \(\frac{d}{dt}\arg x(t)\), to recover instantaneous frequency, then slice). The discriminator is what Rafe's AIS and FSK-ISM receivers use — one line of numpy, np.diff(np.unwrap(np.angle(iq))).
1.3 M-ary FSK — more tones, and a weak-signal superpower
Use \(M\) tones instead of two, and each symbol now carries \(\log_2 M\) bits. But the more interesting reason to add tones shows up at the weak-signal extreme.
The insight that powers WSJT-X. If you make each tone a symbol and demodulate with an FFT (one bin per tone), you're integrating all of the symbol's energy into a single narrow bin. Narrow the bin (longer, slower symbols) and the noise in it shrinks while the signal stays put. Push this hard and you can decode tones below the noise floor. That's the whole idea behind FT8 (8 tones), and it's taken to the extreme by JT65 and Q65 with a remarkable 65 tones. The many-tone design isn't about bit rate — it's about spending lots of time and bandwidth to buy sensitivity.
Tradeoff. Bandwidth and latency grow with \(M\) and with symbol length. A 65-FSK, one-symbol-per-second mode is gloriously robust and glacially slow — perfect for bouncing a callsign off the moon, useless for voice.
Implementation notes. The demod is an FFT and an argmax per symbol; the hard part is timing and frequency alignment so the tones land in bins (that's what sync, §Part 2, is for). MFSK text modes (Olivia/Contestia, DominoEX and Thor) live here too.
1.4 Keeping the phase continuous — CPFSK, MSK, GMSK, GFSK
Naïve FSK snaps between two oscillators, and the discontinuity splatters energy into neighbouring channels. The fix is to make the frequency change smoothly so the phase stays continuous, and — one step further — to pre-filter the frequency pulse with a Gaussian so even the corners are rounded.
Why you care. Spectral cleanliness. Continuous phase plus Gaussian shaping (GMSK, GFSK) concentrates the energy in-band, so you can pack channels tighter and stay legal on spectral masks. AIS uses GMSK at 9600 baud for exactly this; FT8/JS8 shape their FSK into GFSK (\(BT=2.0\)) so 8 tones in 50 Hz don't interfere with the neighbours.
Tradeoff. The Gaussian filter smears each symbol into its neighbours — inter-symbol interference (ISI) — and the \(BT\) product (bandwidth × symbol time) is the knob: smaller \(BT\) = cleaner spectrum, more ISI. You're trading spectral occupancy for detectability.
Implementation notes. Generate it in the phase domain: build the instantaneous frequency (data × Gaussian pulse), integrate to phase (np.cumsum), take exp(j·phase). The magic constant you'll see is \(\pi\sqrt{2/\ln 2}\approx 5.336\) — the Gaussian's relationship between \(BT\) and its time-domain width.
1.5 Chirp spread spectrum (LoRa) — FSK smeared for processing gain
Instead of a steady tone, sweep the frequency linearly across the whole channel — a chirp — and encode the symbol in the chirp's cyclic starting frequency. See LoRa PHY.
Why it's clever. At the receiver you multiply by a conjugate reference chirp ("de-chirp"), which collapses the whole swept symbol back into a single tone whose FFT bin is the symbol. Spreading the symbol over a wide bandwidth and then coherently collapsing it gives processing gain — like M-FSK's sensitivity trick, but with one symbol filling the channel instead of many narrow tones. LoRa decodes 10–20 dB below the noise with a dollar of silicon.
Tradeoff. Low data rate for the bandwidth, and it's a proprietary-ish PHY. The spreading factor \(\mathrm{SF}\) trades rate against range.
Implementation notes. The whole demod is: multiply by the down-chirp, FFT, argmax. Everything hard (CFO/STO alignment) is about making that argmax land in the right bin — see the LoRa modem sync notes.
1.6 Phase-shift keying (PSK) — spend bandwidth on efficiency instead
Put the bits in phase: 0→0°, 1→180° (BPSK), or four phases for 2 bits/symbol (QPSK). Phase is more bandwidth-efficient than frequency — you get more bits per hertz — which is why the higher-throughput and satellite modes live here.
The catch, and the universal fix. The receiver has no absolute phase reference — it can't tell 0° from 180° without help. Two solutions recur: send a known preamble and track phase with a Costas loop (§7), or encode the data differentially so only changes in phase matter (DBPSK). PSK31 uses differential BPSK (and is a lovely minimal example); RDS rides a differential-BPSK subcarrier on the FM broadcast signal; DVB-S uses coherent QPSK with full carrier recovery.
Tradeoff. More sensitive to phase noise and frequency error than FSK, and you usually need a linear-ish amplifier. Differential coding sidesteps the reference problem but doubles errors (one slip corrupts two symbols).
1.7 The practical PSK variants — π/4-DQPSK, DQPSK, OQPSK
Real systems tweak QPSK to solve a specific hardware problem, and the tweaks are worth recognising because the reason is always physical:
- π/4-DQPSK (TETRA): rotate the constellation by π/4 each symbol so the trajectory never passes through the origin. Zero-crossings mean deep amplitude dips, which a nonlinear amplifier turns into spectral regrowth — so avoiding them keeps a cheap PA clean. Differential, so no phase reference needed.
- DQPSK (DAB): each OFDM subcarrier is differentially QPSK-keyed against the previous symbol, so you never need a per-carrier phase estimate — a big simplification with hundreds of carriers.
- OQPSK (Meteor LRPT): offset the Q arm by half a symbol so the phase can only change by ±90° at a time, never 180°, again capping the envelope swing.
The lesson for a mid-level engineer: constellations are chosen against the amplifier and the oscillator, not just against noise.
1.8 QAM and APSK — squeeze the most bits per hertz
Vary amplitude and phase, packing many points into the constellation (16, 32, 64…). Maximum spectral efficiency; used where SNR is high and spectrum is precious. DVB-S2 uses APSK (points on concentric rings — QAM reshaped so a saturating satellite amplifier doesn't distort the outer points).
Tradeoff. Every doubling of constellation size costs a few dB of required SNR and demands a genuinely linear PA and clean LO. This is the top of the modulation complexity ladder — great on a fibre-fed satellite uplink, hopeless on a fading HF channel.
1.9 OFDM — when the channel itself is the enemy
Everything above assumes a well-behaved channel. On HF or in a city, multipath (echoes arriving at different delays) smears symbols into each other. OFDM's answer: don't fight it — split the data across hundreds of parallel narrowband subcarriers (via an IFFT), so each subcarrier's symbols are long and slow relative to the echo delay, and add a cyclic prefix (a copy of the symbol's tail pasted on the front) so that multipath becomes a harmless per-subcarrier phase twist instead of ISI. Used by DAB, HamDRM/DSSTV, the native Mercury HF modem, and FreeDV.
Benefits. Turns a nasty frequency-selective channel into a set of easy flat ones, and the FFT makes it computationally cheap.
Tradeoffs. High peak-to-average power ratio (many carriers occasionally add in phase → big peaks → amplifier headroom wasted), and it's very sensitive to frequency offset and timing (a small CFO destroys subcarrier orthogonality). OFDM buys multipath immunity with sync precision — which is why OFDM modes spend so much on pilots and phase references.
1.10 Two refinements that apply to all of the above
Gray coding. However you lay out your tones or constellation points, assign the bit patterns so that neighbouring symbols differ by only one bit. The most likely error is mistaking a symbol for its neighbour, and Gray coding makes that cost one bit error instead of several. Nearly free, always worth it — LoRa, FT8, and every QAM mode do it.
Pulse shaping (and the matched-filter identity). A rectangular symbol has infinite bandwidth; you shape it with a root-raised-cosine (RRC) filter (for PSK/QAM) or a Gaussian (for FSK) to bound the spectrum while keeping zero ISI at the sampling instants (the Nyquist criterion). The trick worth remembering: you split the RRC half-and-half between transmitter and receiver, and the receiver's half is also the matched filter that maximises SNR. One filter, two jobs. That identity — shaping the transmit pulse and correlating against the reference are the same operation — recurs everywhere (M17 at \(\beta=0.5\), DVB, LRPT at \(\beta=0.6\)).
Part 2 — Finding the signal in the noise (synchronisation)
You cannot demodulate what you haven't found. Before any of Part 1 works, the receiver has to answer: where does the frame start, and what are the time and frequency offsets? Every mode answers by embedding a known reference for the receiver to correlate against — the peak of the correlation is the alignment.
The zoo of references, and what each is good at:
- Preambles — constant or alternating runs (LoRa's up-chirps, AIS's
0101…) that let the receiver settle its AGC and timing loops before real data starts. - Sync words — a fixed pattern with a sharp autocorrelation: P25's
0x5575F5FF77FF, the CCSDS attached sync marker0x1ACFFC1Dused by LRPT, the HDLC0x7Eflag. You find them by sliding a correlator and thresholding. - Costas arrays — FT8's 7×7 Costas array is the elegant one: a pattern of tone-vs-time points whose 2-D autocorrelation is a perfect thumbtack, so a single correlation pins time and frequency at once. That's why FT8 needs no separate frequency-search — the sync does double duty.
- Sync vectors — WSPR threads a fixed 162-bit sync sequence through the data (one sync bit per symbol), so acquisition and demodulation happen together.
- Training sequences — TETRA drops a known pattern in the middle of the burst so the equaliser can lock mid-slot.
The gotcha worth internalising. Your sync must key on something the data cannot imitate. NAVTEX is the cautionary tale: its constant-ratio code makes every valid symbol have the same weight, so a weight/energy detector locks onto noise and data rotations indiscriminately. The fix is to correlate specifically against the phasing pattern the standard reserves for sync. Whenever your acquisition is "flaky," ask whether the thing you're correlating against is actually unique.
Frame hierarchies. Above one frame, modes nest time structure so different traffic runs at different rates: TETRA's slot → frame(×4) → multiframe(×18); CCSDS's CADU → VCDU → packet; the WSJT-X 15-second slot that clocks the whole QSO. When you implement a decoder, you build these as nested state machines, outer sync gating inner sync.
Part 3 — Did we receive it correctly? (error detection)
Before spending effort correcting errors, it's cheap and almost foolproof to detect them. That's the cyclic redundancy check: treat the message as a big polynomial, divide by a fixed generator polynomial, and send the remainder. Recompute on receive; if the remainder doesn't match, the frame is corrupt — drop it, or (in ARQ modes) ask for a resend. A good CRC catches essentially all real-world error patterns for a handful of bits of overhead. Its job is also to stop the FEC from silently "correcting" a badly-hit frame to the wrong codeword and handing you plausible garbage.
The polynomials you'll meet — and the trap. They're a family, named by their generator:
| CRC | Polynomial | Where |
|---|---|---|
| CRC-8 | 0x07 |
loracss, ISM devices |
| CRC-12 (JS8) | 0xC06, final XOR 42 |
JS8 |
| CRC-12 (Q65) | 0xF01 |
Q65 |
| CRC-14 | 0x2757 |
FT8/FT4 |
| CRC-16-CCITT | 0x1021 |
DAB FIB, and many |
| CRC-16 / X.25 | 0x8408 |
AIS, APRS (AX.25 FCS) |
| CRC-16 (ardopcf) | 0x8810 |
ARDOP — deliberately not CCITT |
| CRC-24 | 0xFFF409 |
ADS-B |
The trap that costs everyone an afternoon: 0x1021 and 0x8408 are the same polynomial, one bit-reversed from the other, because AX.25 clocks bits LSB-first. And ARDOP's 0x8810 proves "CRC-16" is a family, not a value — always pin down the exact polynomial, the initial value, the final XOR, and the bit order before you trust an implementation. A CRC that's "almost right" passes your loopback test and fails on air. (The fully pinned-down parameter table — init, final XOR, bit order for every mode — is in Appendix B.3.)
Part 4 — Fixing errors without asking again (forward error correction)
Detection tells you a frame is broken; FEC lets you repair it in place, by adding structured redundancy the receiver can reason about. This is the deepest part of the stack, so we build it up from the simplest idea. The throughline: every FEC scheme trades rate (overhead) for coding gain (dB of sensitivity), and they differ in what error patterns they're good against and how much they cost to decode.
4.1 The core idea: distance
Send extra bits so that valid codewords are "far apart" in Hamming distance. If the nearest codeword to what you received is \(d\) flips away and the next-nearest is further, you can correct up to \(\lfloor (d-1)/2 \rfloor\) errors by rounding to the nearest codeword. Repetition (send each bit three times, majority-vote) is the trivial case — robust but brutally inefficient. Everything below is a cleverer way to buy distance cheaply.
4.2 Parity and Hamming codes — distance for almost free
One parity bit gives distance 2 (detect one error). A Hamming code arranges several parity bits so that the failing checks, read as a binary number, name the position of the single bad bit — correction with a few bits of overhead. Rafe uses Hamming(8,4)/(7,4) in the LoRa header and Hamming(15,11)/(10,6) in P25. Great for the header of a packet (small, needs light protection); too weak alone for a deep-fade payload.
4.3 The finite-field detour (you need this for the strong codes)
The powerful block codes (BCH, Reed-Solomon) do arithmetic in a finite field \(\mathrm{GF}(2^m)\) — the \(2^m\) bit-patterns of width \(m\), added by XOR and multiplied modulo a primitive polynomial. Pick a primitive element \(\alpha\); then every nonzero element is some power \(\alpha^i\), so multiplication is just adding exponents mod \(2^m-1\). In code you precompute two tables — alpha_to[i] = \(\alpha^i\) and its inverse log[] — and every field multiply becomes two lookups and an add. That's the entire trick; don't let the algebra intimidate you. The fields in the catalogue:
| Field | Primitive poly | Used by |
|---|---|---|
| \(\mathrm{GF}(2^6)\) | \(x^6+x+1\) (0x43) |
JT65, Q65, P25 NID |
| \(\mathrm{GF}(2^8)\) | \(x^8+x^4+x^3+x^2+1\) (0x11D) |
DAB, DVB-S, LRPT, ARDOP |
4.4 BCH and cyclic codes — algebra that points at the error
A cyclic code is generated by multiplying your message polynomial by a fixed generator \(g(x)\). BCH chooses \(g(x)\) so that a designed set of \(\alpha^i\) are roots, which guarantees a correction power \(t\). Decoding is a fixed pipeline — compute syndromes, run Berlekamp-Massey to get the error-locator polynomial, Chien search for its roots (the error positions), flip them. POCSAG wraps each word in BCH(31,21) (generator 0x769, corrects 2); P25's Network ID uses BCH(63,16,23) so the NAC survives a hammered header. Once you've written the syndrome/BM/Chien loop once, it serves every BCH and RS code you'll ever touch.
4.5 Reed-Solomon — the burst specialist
RS is BCH's non-binary sibling: the symbols are field elements, and it corrects \(t=(n-k)/2\) symbol errors. Because a burst of bit errors usually lands inside just a few symbols, RS is the natural defence against bursts — scratches on a disc, fades on a downlink. It's everywhere the channel is bursty: JT65 (RS(63,12) — the code basically is the mode), DVB-S (RS(204,188)), LRPT and CCSDS (RS(255,223)), DAB, ARDOP. Same decoder as BCH plus a Forney step to compute the symbol values.
4.6 Convolutional codes — a sliding window, and soft decisions
Instead of chopping into blocks, run the data through a shift register and emit XOR taps: each output depends on the last \(K\) bits (the constraint length). The beauty is the decoder — the Viterbi algorithm finds the most-likely transmitted sequence by a dynamic program over the encoder's state trellis, and crucially it accepts soft inputs (likelihoods), which is worth ~2 dB over hard bits. WSPR and JT4 use a very long \(K=32\) code for deep gain; DVB-S and LRPT use the classic \(K=7\); M17 uses \(K=5\).
Puncturing is the practical trick: design one low-rate "mother" code, then delete some output bits per a fixed pattern to raise the rate on demand (the decoder inserts erasures where bits were dropped). One encoder yields a whole rate family — DVB-S (1/2 … 7/8), TETRA's RCPC, M17. When you need to trade coding strength for throughput on the fly, you puncture.
4.7 LDPC — near-Shannon, and why the modern modes all use it
A low-density parity-check code is defined by a sparse parity-check matrix — a bipartite Tanner graph of bit-nodes and check-nodes. Decoding is belief propagation: likelihood messages flow back and forth along the edges, each check nudging its bits toward consistency, iterating until the parities are satisfied. With enough iterations LDPC gets within a fraction of a dB of the Shannon limit — which is why every mode designed since ~2010 uses it: FT8/FT4 (174,91), FST4, JS8, DVB-S2, FreeDV 700D. Q65 uses a non-binary LDPC-like code (QRA) over GF(64).
Implementation notes. You store the graph as two adjacency tables (which checks touch each bit — Mn — and vice-versa — Nm); these are the biggest constants in the whole catalogue and are printed in full in each spec. The math is tanh/atanh per edge, or the cheaper min-sum approximation. You must feed it soft LLRs from the demapper — hard-in LDPC throws away most of the point.
4.8 Concatenation — stacking codes for the best of both
The strongest links concatenate an inner code good against random errors (Viterbi or LDPC) with an outer code good against the residual bursts the inner decoder spits out (Reed-Solomon or BCH), separated by an interleaver. Decode inner-first, hand hard symbols outward, let RS mop up. This is the structure that ran Voyager and still runs your satellite TV:
| System | Inner | Outer |
|---|---|---|
| DVB-S | punctured conv \(K=7\) | RS(204,188) |
| LRPT / CCSDS | conv \(K=7\) Viterbi | RS(255,223) |
| DVB-S2 | LDPC | BCH |
4.9 Choosing a code — the cheat sheet
| Family | Best against | Rate flexibility | Decode cost | Soft-in? | Typical use |
|---|---|---|---|---|---|
| Hamming | 1 error | fixed | trivial | no | headers |
| BCH | few random | fixed | low | no | short critical fields |
| Reed-Solomon | bursts | shorten freely | moderate | (usually hard) | outer code, storage |
| Convolutional | random, streaming | puncture | moderate (Viterbi) | yes | inner code, legacy |
| LDPC | random, near-limit | designed | high (iterative) | yes | modern everything |
Part 5 — Conditioning the bitstream
Two transforms sit between the coder and the modulator. Neither adds redundancy; each reshapes the bits so the channel and the receiver cooperate. They're small but skipping them breaks things in ways that are maddening to debug.
5.1 Interleaving — turn bursts into the random errors your FEC expects
Convolutional and LDPC codes are designed against independent errors, but real channels deliver bursts (a fade, a click, a collision). An interleaver permutes the coded bits before transmission so that, after de-interleaving at the receiver, a burst is scattered into isolated errors each code can handle. It's a pure permutation — the art is in choosing one that spreads well and costs little latency:
- Block interleaver — write a matrix by rows, read by columns (JT65's 7×9). Simple; costs a full block of latency.
- Convolutional (Forney) interleaver — staggered delay lines; DVB-S gets the same spread at a fraction of the latency.
- Diagonal — LoRa spreads coded nibbles diagonally across chirps, coupling time and frequency diversity.
- Time diversity — NAVTEX sends every character twice, the copies separated by a fixed delay, so a burst can't hit both. The "interleaver" is just a delay, and it's what gives the detection-only constant-ratio code its correction power.
The universal tradeoff: deeper interleaving spreads bursts better but adds latency (you can't decode until the de-interleaver fills), which is why real-time voice interleaves shallowly and file/image modes interleave deep.
5.2 Scrambling / whitening — DC balance and something to clock off
A long run of identical bits is two problems at once: the receiver's timing recovery has no transitions to lock to, and the spectrum grows a DC or tonal spike. Fix both by XORing the stream with a pseudo-random sequence from a linear-feedback shift register, and XOR again on receive. It's deterministic and carries no secret — this is not encryption.
| Mode | LFSR polynomial | Seed |
|---|---|---|
| DVB-S | \(x^{15}+x^{14}+1\) | 0o251 |
| DAB | \(x^9+x^5+1\) | 0x1FF |
| LRPT / CCSDS | \(x^8+x^7+x^5+x^3+1\) | 0xFF |
| LoRa | 8-bit, taps 7/5/4/3 | 0xFF |
Two flavours: additive (XOR a free-running PRBS — needs frame sync to align, the common case) and self-synchronising (the PRBS is driven by the data — no alignment needed but errors multiply). TETRA does something neat: its scrambler seed is the network colour code, so a receiver that descrambles cleanly has simultaneously confirmed it's on the right network — sync and identity in one step.
Don't confuse it with encryption. Meshtastic's payload is AES-CTR encrypted — also "XOR with a keystream," but the keystream is cryptographic and keyed. Whitening conditions the spectrum in public; encryption hides content with a secret. Recognising which one you're looking at is the whole of crypto hygiene at this layer (§8.3).
Part 6 — Squeezing the message small (source coding)
Every bit you don't spend on the payload is a bit the FEC can spend on reliability, so the front of the chain compresses. Four approaches, by increasing cleverness:
- Fixed-alphabet packing — when the message space is small and known, enumerate it and send the index. The WSJT-X 77-bit payload crams a whole QSO (two callsigns + report) into 77 bits by field-typing everything; M17 packs callsigns into base-40; AIS bit-packs then 6-bit-armours for the NMEA layer. Densest option, but zero internal redundancy — one bit error changes an unrelated field, which is why these modes wrap the packed payload in heavy FEC.
- Variable-length (varicode) — for open-ended text, give frequent characters short codes. PSK31's varicode is self-delimiting (no code contains
00, so00is the gap) — elegant because it needs no separate character framing. MFSK, DominoEX, Thor reuse the idea. - Dictionary/entropy — JS8 adds a Huffman table and a 262k-word dictionary so common words cost a few symbols.
- Vocoders — you don't compress speech by enumerating it, you model it. All the voice modes send vocal-tract model parameters: linear prediction (an order-10 all-pole filter, via Levinson-Durbin, transmitted as line-spectral frequencies for robustness) plus a pitch/voicing excitation. M17 and FreeDV carry Codec2; P25 carries IMBE; the native RVQ-Voice pairs that LPC front-end with a neural-codec-style residual vector quantiser. Same front-end, different back-ends — a good doc to read once and reuse.
Part 7 — Putting the receiver together
We've met the receiver pieces in passing; here's how they compose. A demodulator is a small pipeline of standard loops, and different modes just wire the same loops differently:
- AGC — normalise amplitude so the slicer/decision thresholds mean something.
- Matched filter — the pulse-shaping filter again (§1.10), maximising SNR at the sample.
- Symbol-timing recovery — find the best instant to sample. The Gardner detector (\(e=\Re\{\overline{x_{\text{mid}}}(x_{\text{cur}}-x_{\text{prev}})\}\)) is popular because it's independent of carrier phase; LRPT and DVB use it.
- Carrier recovery — a Costas loop derotates residual frequency/phase for coherent PSK (LRPT, DVB).
- FM discriminator — differentiate unwrapped phase for FSK/GMSK (AIS, FSK ISM).
- Envelope / Hilbert — recover AM/OOK (APT's subcarrier, OOK ISM).
The composition tells you the mode's character. An AIS receiver is AGC → matched filter → FM-discriminate → clock recovery — no carrier loop, because FSK doesn't need one. An LRPT receiver is AGC → RRC → Gardner → Costas — the full coherent chain. FT8 throws the loops away entirely and does a 2-D FFT correlation against the Costas array, because its rigid 15-second slot timing removes the need to track anything. When you design a receiver, you're choosing which of these loops you can afford to omit.
And keep it soft. The single biggest lever on FEC performance is handing the decoder log-likelihood ratios, not hard bits — the demapper computes, per coded bit, \(\mathrm{LLR}=\log\frac{P(b=0)}{P(b=1)}\), and the Viterbi/LDPC stage works in that domain. Hard-slicing before the decoder throws away ~2 dB. If your modern mode "almost works," check that soft information survives all the way to the parity checks.
Part 8 — Above the physical layer
Once bits are flowing reliably, the classic data-link and network machinery reappears — the same ideas you'd find in any protocol stack, just riding a radio:
- Framing — HDLC's flag
0x7E+ bit-stuffing (insert a0after five1s so the flag is unique in the stream) + FCS carries both AIS and APRS (AX.25). - Reassembly — CCSDS space packets span several transfer frames on LRPT; Meshtastic fragments over LoRa.
- ARQ — ARDOP and Mercury add send/wait/retransmit state machines with sequence numbers, turning a lossy PHY into a reliable link. The RS-BA1 remote-control transport does its own sequence/retransmit over three UDP streams.
- Addressing — AX.25 callsign+SSID, AIS MMSI, Meshtastic node IDs, P25 NAC: identity fields, in-band, protected by the same FEC as the payload.
8.3 Security vs. obfuscation — know which one you have
Two things share a shape but not a purpose:
- Encryption — Meshtastic uses AES-128/256 in CTR mode (nonce = packet-id ∥ sender ∥ block counter), a keyed keystream XORed with the payload. Transparent with the key, secure without it.
- Obfuscation — RS-BA1 "protects" its login password with a fixed byte-substitution table. No secret, trivially reversible — a speed bump, not a lock.
Same XOR-shaped operation, opposite security properties. The habit worth building: when you see a keystream, ask where's the key, and is it secret? — that one question separates real protection from theatre.
Appendix A — The periodic table of the modes
Each row is a mode; each column a stage from this document. Read a column to see who shares a technique; read a row for one mode's pipeline. — = stage omitted.
The table names the techniques; Appendix B breaks every distinct entry down — the typical algorithm, what it buys, what it costs, and the load-bearing constants.
| Mode | Modulation | Sync ref | Error check | FEC | Interleave | Scramble | Source coding |
|---|---|---|---|---|---|---|---|
| FT8/FT4 | 8/4-GFSK | Costas 7×7/4×4 | CRC-14 | LDPC(174,91) | yes | — | 77-bit pack |
| JS8 | 8-GFSK | Costas | CRC-12 | LDPC(174,87) | yes | — | Huffman+JSC |
| WSPR | 4-FSK | 162-bit vector | — | conv \(K=32\) | yes | — | fixed pack |
| JT4 | 4-FSK | sync vector | — | conv \(K=32\) | yes | — | 72-bit pack |
| JT65 | 65-FSK | 126-bit sync | — | RS(63,12) | 7×9 | — | 72-bit pack |
| FST4 | 4-GFSK | sync symbols | CRC-24 | LDPC(240,101) | yes | — | 77-bit pack |
| Q65 | 65-FSK | sync | CRC-12 | QRA65/GF(64) | yes | — | 77-bit pack |
| PSK31 | DBPSK | — | — | — | — | — | varicode |
| RTTY | 2-FSK | — | — | — | — | — | Baudot |
| MFSK/Domino/Thor | MFSK / IFK+ | — | — | conv (some) | yes | — | varicode |
| Olivia/Contestia | MFSK | — | — | Walsh block | yes | LFSR | varicode |
| MT63 | OFDM/DBPSK ×64 | phase-ref symbol | — | Walsh(64,6) | symbol block | — | 6-bit pack |
| LoRa | CSS chirp | up-chirp | CRC-16 | Hamming(8,4) | diagonal | LFSR | — |
| Meshtastic | (LoRa) | (LoRa) | (LoRa) | (LoRa) | (LoRa) | AES-CTR | protobuf |
| M17 | 4-FSK (RRC) | sync words | CRC-16 | conv \(K=5\)+Golay | QPP | randomiser | base-40/Codec2 |
| P25 | C4FM | frame sync+NID | Golay/BCH | Golay+Ham+BCH(63,16) | yes | — | IMBE |
| TETRA | π/4-DQPSK | training seq | CRC-16 | RCPC+RM(30,14) | block | colour-code | — |
| DAB | OFDM/DQPSK | null+phase ref | CRC-16 | RS+punct conv | time | LFSR | — |
| DVB-S | QPSK (RRC) | TS 0x47 cadence |
(RS) | RS(204,188)+conv | Forney | LFSR | MPEG-TS |
| DVB-S2 | QPSK/APSK | SOF+PLSC | (BCH) | BCH+LDPC | bit | LFSR | MPEG-TS |
| HamDRM | OFDM | FAC/pilots | RS | MLC conv+RS | cell map | — | image/MOT |
| Mercury | OFDM | preamble | CRC+ARQ | LDPC | yes | — | packet |
| ARDOP | 4FSK/PSK/QAM | preamble | CRC 0x8810 |
RS | yes | — | packet |
| AIS | GMSK 9600 | 0101+0x7E |
CRC 0x8408 |
— | — | — | 6-bit armour |
| APRS | AFSK 1200 | 0x7E (NRZI) |
CRC 0x8408 |
— | — | — | AX.25 |
| POCSAG | 2-FSK | preamble+sync | (BCH) | BCH(31,21) | — | — | numeric/alpha |
| ADS-B | PPM | preamble | CRC-24 | — | — | — | field pack |
| NAVTEX | 2-FSK (SITOR) | phasing | constant-ratio | CCIR-476 4/7 | DX/RX | — | — |
| Marine DSC | 2-FSK/AFSK | phasing | B-count + XOR ECC | time diversity | DX/RX | — | BCD MMSI pack |
| RDS | DBPSK 57kHz | offset words | syndrome | (26,16) cyclic | — | — | field pack |
| NOAA APT | AM subcarrier | sync A/B | — | — | — | — | — |
| Meteor LRPT | OQPSK | ASM 0x1ACFFC1D |
(RS) | RS(255,223)+conv | depth-4 | LFSR | — |
| Inmarsat-C | BPSK 1200 | UW 0xB1CB4E |
EGC checksum | conv \(K=7\) | block 64-col | LFSR | EGC pack |
| RVQ-Voice | audio modem | preamble | CRC | conv | — | — | LPC/LSF+RVQ |
| Feld Hell | OOK | — | — | — | — | — | raster font |
| CW / Morse | OOK (keyed tone) | tone track + gate | duration structure | — | — | — | Morse (varicode) |
| WEFAX | FM subcarrier | line clock (free-run) | — | — | — | — | analog raster |
| ACARS/AERO | (rides AERO/VHF) | SOH/STX + UW | CRC-16 0x8408 |
conv \(K=7\) (AERO) | block 64 (AERO) | LFSR (AERO) | ARINC-618 fields |
| NOAA HRPT | BPSK + Manchester | 60-bit frame sync | — | — | — | — | 10-bit AVHRR words |
| AHRPT | OQPSK | ASM 0x1ACFFC1D |
(RS) | RS(255,223)+conv | depth-4 | LFSR | CCSDS/AVHRR |
| xRIT | (CCSDS link layer) | packet seq-flags | (link RS) | (link RS+conv) | — | — | header chain + JPEG |
| RS-BA1 | UDP transport | handshake | seq/retransmit | ARQ | — | passcode | — |
Appendix B — Technique reference: the periodic table's columns explained
This appendix unpacks the four "how it works" columns of Appendix A — Modulation, Sync ref, Error check, FEC — one entry per distinct technique, deduplicated. Each gives the typical algorithm, the benefit that earns its place, and the tradeoff you accept. Section numbers point back to the narrative treatment in Parts 1–4.
B.1 Modulation column
Amplitude
- OOK (on-off keying). Algo: carrier on for
1, off for0; RX rectifies to an envelope and slices against a threshold (with hysteresis to stop chatter). Benefit: the cheapest link that exists — a switch to transmit, a diode to receive. Tradeoff: amplitude is the least robust dimension (fading/AGC/interference hit it directly) and long0-runs starve timing recovery. Modes: ISM, Feld Hell. (§1.1)
Frequency
- 2-FSK. Algo: two tones for mark/space; RX runs two bandpass filters and compares energies, or FM-discriminates (differentiate unwrapped phase) and slices. Benefit: constant envelope — run the PA in saturation; fading barely matters because the tone ratio carries the bit. Tradeoff: ~2× the bandwidth of a phase-keyed carrier at the same rate. Modes: POCSAG, RTTY, NAVTEX. (§1.2)
- AFSK (Bell 202, 1200 baud). Algo: 2-FSK whose tones (1200/2200 Hz) sit in the audio band and drive an ordinary FM voice radio's mic — the RF modulation is the radio's FM. Benefit: works through any unmodified voice transceiver. Tradeoff: double-modulation (AFSK-into-FM) wastes deviation and SNR versus native FSK. Modes: APRS. (§1.2)
- M-FSK (4-, 8-FSK). Algo: \(M\) tones carry \(\log_2 M\) bits/symbol; demod is an FFT (one bin per tone) with an argmax per symbol. Benefit: each tone's energy integrates into one narrow bin, so long slow tones buy sensitivity — the basis of weak-signal work. Tradeoff: bandwidth and latency grow with \(M\) and symbol length. Modes: WSPR/JT4/FT4 (4), FT8/JS8 (8). (§1.3)
- 65-FSK. Algo: M-FSK taken to 64 data tones + sync, ~1 symbol/second, FFT-demodulated. Benefit: extreme sensitivity, and a natural match to a 64-ary RS/QRA code (one tone = one field symbol). Tradeoff: glacially slow and wide. Modes: JT65, Q65. (§1.3)
- GFSK / GMSK. Algo: FSK with the frequency pulse pre-filtered by a Gaussian and the phase kept continuous — generated in the phase domain (integrate frequency, take \(e^{j\varphi}\)); GMSK is the minimum-shift (\(h=0.5\)) case. Benefit: tightly bounded, splatter-free spectrum, so channels pack close. Tradeoff: the Gaussian smears symbols (ISI), traded via the \(BT\) product. Modes: AIS (GMSK 9600 bit/s, \(BT=0.4\)), FT8/JS8 (GFSK \(BT=2.0\)), FT4 (\(BT=1.0\)), FST4 (\(BT=2.0\)). (§1.4)
- MFSK / IFK+. Algo: many-tone FSK for text — with a split worth knowing. MFSK16/32, Olivia and Contestia key the absolute Gray-coded tone; DominoEX and Thor key the increment between consecutive tones (IFK+:
tone = (prev + 2 + nibble) mod 18). Benefit: incremental keying is differential-in-frequency — drift and imperfect tuning cancel out of the difference, and the forced minimum step of +2 guarantees a transition every symbol (self-clocking) and keeps consecutive symbols off the same tone (multipath echo protection). Tradeoff: one tone error corrupts two increments, and two of the 18 tone steps are forbidden, wasting a little alphabet. Modes: DominoEX, Thor (IFK+); Olivia, Contestia, MFSK16/32 (absolute). (§1.3–1.4) - CSS (chirp spread spectrum). Algo: each symbol is a full-band linear chirp whose cyclic start frequency encodes \(\mathrm{SF}\) bits; RX multiplies by a conjugate reference chirp ("de-chirp") to collapse it to a tone, then FFT-argmax. Benefit: large processing gain — decode well below the noise floor on cheap silicon. Tradeoff: low data rate for the occupied bandwidth. Modes: LoRa, Meshtastic. (§1.5)
- C4FM / shaped 4-level FSK. Algo: four deviation levels (±1, ±3 as PAM) carry 2 bits/symbol through an ordinary FM chain, shaped by a Nyquist filter — P25's C4FM at 4800 sym/s, RRC \(\alpha=0.2\), deviations ±600/±1800 Hz; M17 at 4800 sym/s, RRC \(\beta=0.5\), ±800/±2400 Hz. RX: FM-discriminate → matched filter → 4-level slice. Benefit: constant envelope with twice the bits of 2-FSK, on existing FM hardware. Tradeoff: the inner ±1 levels sit close together — the eye is only as good as your deviation accuracy and DC-offset handling. Modes: P25, M17. (§1.2, §1.6)
Phase & amplitude
- BPSK / DBPSK. Algo: two phases (0/180°) carry one bit; differential BPSK encodes the bit as a phase change so no absolute reference is needed. Benefit: more energy-efficient than FSK per hertz; DBPSK sidesteps carrier recovery. Tradeoff: sensitive to phase noise; differential doubles errors. Modes: PSK31, RDS (57 kHz DBPSK). (§1.6)
- QPSK. Algo: four phases carry 2 bits/symbol; coherent RX recovers carrier (Costas) and timing (TED), then rotates and slices. Benefit: twice BPSK's efficiency at the same bandwidth. Tradeoff: needs genuine carrier recovery and a linear-ish PA. Modes: DVB-S. (§1.6)
- OQPSK. Algo: QPSK with the Q arm delayed half a symbol, so phase steps only ±90°, never 180°. Benefit: the envelope never collapses to zero → gentler on a saturating amplifier. Tradeoff: the offset complicates timing recovery slightly. Modes: Meteor-M LRPT. (§1.7)
- π/4-DQPSK. Algo: differential QPSK with a π/4 constellation rotation each symbol, so the trajectory never crosses the origin. Benefit: small envelope variation (amplifier-friendly) plus differential (no phase reference). Tradeoff: differential error-doubling. Modes: TETRA. (§1.7)
- DQPSK (per-subcarrier). Algo: differential QPSK applied independently on each OFDM subcarrier against the previous OFDM symbol. Benefit: no per-carrier channel estimate needed — a huge simplification with hundreds of carriers. Tradeoff: the ~3 dB penalty of differential detection, plus error doubling. Modes: DAB. (§1.7, §1.9)
- APSK / QAM. Algo: points arranged in amplitude and phase (QAM on a grid, APSK on concentric rings) carry 4–6+ bits/symbol; RX needs a coherent carrier + amplitude reference and soft-demaps to LLRs. Benefit: maximum bits per hertz. Tradeoff: each extra bit/symbol costs several dB of SNR and demands a linear PA and clean LO — APSK's rings are QAM bent to survive a nonlinear satellite amp. Modes: DVB-S2 (APSK), ARDOP high modes (QAM). (§1.8)
Multicarrier
- OFDM. Algo: map data across hundreds of orthogonal narrowband subcarriers via an IFFT and prepend a cyclic prefix; RX FFTs each symbol and equalises per subcarrier. Benefit: turns a multipath (frequency-selective) channel into many flat ones; FFT-cheap. Tradeoff: high peak-to-average ratio (PA headroom) and acute sensitivity to frequency/timing offset (needs pilots). Modes: DAB, HamDRM, Mercury, FreeDV. (§1.9)
Other
- PPM (pulse-position modulation). Algo: each bit is a pulse in one of two half-slots (early = 1, late = 0); RX correlates each half. Benefit: self-clocking (every bit has a transition) and amplitude-robust — good for a dense, bursty, uncoordinated channel. Tradeoff: spends 2× the time of straight OOK. Modes: ADS-B / Mode S. (§6.3)
- AM subcarrier. Algo: amplitude-modulate a fixed audio subcarrier with the payload, itself carried on an FM (or FM-broadcast MPX) signal; RX envelope-detects the subcarrier after FM demod. Benefit: rides an existing analog FM link with no RF-layer change. Tradeoff: AM's amplitude fragility plus the host link's noise. Modes: NOAA APT (2400 Hz), RDS (57 kHz). (§6.3)
- Audio-band passband modem. Algo: a self-contained DQPSK/FSK modem whose passband sits in the 300–3000 Hz voice channel, so it rides any SSB/FM radio like speech. Benefit: radio-agnostic — the "PHY" is just audio. Tradeoff: limited to ~2.5 kHz and the voice channel's group-delay/AGC quirks. Modes: RVQ-Voice, FreeDV, ARDOP. (§1)
- UDP transport (not a modulation). RS-BA1's row says "UDP transport" because it operates above an already-demodulated IP link — a control/streaming protocol, not a PHY. Listed for completeness. (§8)
B.2 Sync-reference column
- Preamble. Algo: a constant or alternating run (LoRa up-chirps, AIS
0101…, an OFDM null+ramp) sent before data; RX uses it to settle AGC, estimate coarse frequency, and arm timing. Two designs worth noting: POCSAG's preamble is 576 bits long on purpose — battery-saving pagers wake periodically and must still catch it; ARDOP's two-tone leader (1475 + 1525 Hz, 240 ms) phase-inverts its final symbol so the discontinuity marks the exact sync instant. Benefit: gives the tracking loops time to converge before information starts. Tradeoff: pure overhead — too short and acquisition fails, too long and throughput drops — and a preamble alone carries no frame position; it is always paired with a sync word. - Chirp preamble + sync word + SFD. Algo: LoRa's acquisition run — 8 unshifted up-chirps, a network sync word (one byte → two shifted up-chirps;
0x12default,0x2BMeshtastic), then 2.25 down-chirps. The trick: carrier offset \(c\) and timing offset \(s\) shift an up-chirp's dechirped FFT bin by \((c-s)\) but a down-chirp's by \((c+s)\), so comparing preamble and SFD bins solves both in closed form; the known sync word settles the residual half-symbol ambiguity. Benefit: full time/frequency acquisition from one cheap structure, working below the noise floor. Tradeoff: ~12 symbols of overhead per packet. Modes: LoRa, Meshtastic. - Sync word / frame sync / ASM / HDLC flag. Algo: a fixed pattern with a thumbtack autocorrelation; RX slides a correlator and declares a frame at the peak above a threshold. HDLC's
0x7Eflag + bit-stuffing guarantees uniqueness in the stream. Benefit: unambiguous frame start, cheap — and the word's value can carry information: M17 builds its 16-bit words only from outer ±3 symbols (maximum correlation energy) and uses different words to type the frame (LSF0x55F7, stream0xFF5D, EOT0x555D); POCSAG's0x7CD215D8prefixes every 544-bit batch so a receiver can join mid-stream; LRPT hunts its ASM across all four QPSK phase rotations (tolerating ≤ 4 bit errors), letting the sync word double as the phase-ambiguity resolver. Tradeoff: a fixed pattern can be imitated by data (hence bit-stuffing / high-distance words), costs overhead every frame, and the correlation threshold is a genuine tuning knob — too tight misses weak frames, too loose false-locks on data. Modes: P25 (0x5575F5FF77FF), M17, POCSAG, LRPT ASM (0x1ACFFC1D), AIS/APRS (0x7E), Inmarsat-C (24-bit UW0xB1CB4E, ≤ 3 errors tolerated, doubling as the BPSK 180° ambiguity resolver). - Implicit cadence (no sync field at all). Algo: DVB-S transmits no preamble or sync word; RX hunts the MPEG-TS byte
0x47recurring at the 204-byte RS-coded packet spacing (≥ 8 of 10 hits), anchored by the inverted0xB8that marks each 8-packet randomiser super-frame. It works because the sync bytes ride the undelayed branch of the Forney interleaver. Benefit: zero overhead. Tradeoff: only a continuous stream can sync this lazily — a burst mode gets no second chance at the cadence. Modes: DVB-S. - Costas array. Algo: a set of tone-vs-time points whose 2-D autocorrelation is an ideal thumbtack; RX correlates the received time-frequency plane (an STFT waterfall) against it. FT8 sends the 7×7 array
(3,1,4,0,6,5,2)three times — start, middle, end (symbols 0–6, 36–42, 72–78) — so drift between groups is visible; FT4 uses four different 4×4 arrays; JS8's submodes use distinct array sets, so the array doubles as a submode identifier; FST4 alternates two 8-tone words across five evenly-spread anchor groups. Benefit: pins time and frequency at once — no separate frequency search. Tradeoff: consumes non-data symbols (21 of FT8's 79 — ~27%, gladly paid at −20 dB SNR). Modes: FT8 (7×7), FT4 (4×4), JS8, FST4. - Sync vector (interleaved). Algo: a fixed pseudo-random sequence threaded through the data. WSPR and JT4 put the sync bit in the LSB of every 4-FSK symbol (
tone = 2·data + sync); JT65 dedicates tone 0 at 63 of its 126 symbol positions; Q65 pins tone 0 at 22 fixed positions of 85. RX runs a 2-D grid search over (start time, base frequency), scoring the known pattern's energy at each cell. Benefit: acquisition and demodulation proceed together, and the sync energy accumulates over the whole transmission — lock works as deep as the data decodes, and drift across a 2-minute frame is visible. Tradeoff: heavy overhead (half of WSPR's symbol bits are sync), and the 2-D search costs far more than a sliding correlator. Modes: WSPR (162-bit), JT4, JT65 (126-bit), Q65. - Training sequence (mid-burst). Algo: a known pattern placed inside the burst; RX locks timing/equalisation mid-slot where the channel estimate is freshest. Benefit: good mid-burst tracking on fading/multipath links. Tradeoff: interrupts the payload. Modes: TETRA.
- Null symbol + phase-reference symbol. Algo: coarse frame sync is an absence — a sliding energy window finds DAB's carrier-free null symbol as the energy minimum, no correlation needed; the known phase-reference symbol that follows then seeds the per-carrier DQPSK chain (every later symbol is decoded against its predecessor, so nothing needs tracking after that one anchor). Benefit: trivially robust coarse sync plus a one-symbol differential anchor. Tradeoff: a whole silent symbol plus a reference symbol of overhead per frame. Modes: DAB.
- Pilots + FAC. Algo: known pilot cells scattered through the OFDM grid let the RX interpolate the channel; a Fast Access Channel carries the parameters needed to start decoding. Benefit: coherent OFDM equalisation and self-describing frames. Tradeoff: pilots and FAC are non-payload overhead. Modes: HamDRM/DRM.
- SOF + PLSC. Algo: a Start-Of-Frame marker followed by a heavily-coded (Reed-Muller / Walsh) modulation-and-coding descriptor; RX finds SOF by correlation, then decodes PLSC to learn the MODCOD before touching the payload. Benefit: one waveform carries many modulation/rate combos, signalled robustly per frame. Tradeoff: header overhead. Modes: DVB-S2.
- Offset words (syndrome sync). Algo: each block's checkword is XORed with one of five fixed offset words (A
0x0FC, B0x198, C0x168, C′0x350, D0x1B4); by linearity, a correctly-aligned clean block's syndrome equals its offset word. RX slides a 26-bit window computing one syndrome per position — a match means bit-aligned and block-type known and checksum passed, simultaneously. Benefit: sync and block-type identification fall out of the error-detecting code for free — no separate sync pattern; arguably the most economical sync design in the catalogue. Tradeoff: ties sync to the FEC. Modes: RDS. - Sync A/B tone pulses. Algo: each scan line opens with a burst at a known rate (APT's 1040 Hz "A" / 832 Hz "B"); RX cross-correlates the demodulated line against the template to find line start. Benefit: robust line sync for an analog raster. Tradeoff: line-rate only, no bit-level protection. Modes: NOAA APT.
- Phasing pattern. Algo: a periodic reserved sequence (NAVTEX alternates its two phasing codes) that RX correlates against by minimum Hamming distance to fix the symbol boundary. Benefit: solves the constant-ratio ambiguity where a weight test cannot find the boundary. Tradeoff: needs a dedicated lead-in. Modes: NAVTEX.
- NID (network ID). Algo: immediately after frame sync, a BCH-protected field identifies the network (NAC) and data-unit type; RX uses it to confirm acquisition and route the frame. Benefit: sync + addressing + type in one protected field. Tradeoff: a coded field on every frame. Modes: P25.
- Handshake (protocol-level). Algo: an application-layer exchange (are-you-there / i-am / login+token) over an already-reliable transport establishes the session before media flows. Benefit: authenticated, negotiated setup. Tradeoff: round-trips before first data; not a PHY sync. Modes: RS-BA1.
B.3 Error-check column
- CRC (cyclic redundancy check). Algo: treat the message as a polynomial over \(\mathrm{GF}(2)\), divide by a fixed generator, transmit the remainder; RX recomputes and compares. Implemented as a bit/byte-wise LFSR or a 256-entry table. Benefit: catches essentially all real error patterns for a few bits of overhead — near-perfect detection. Tradeoff: detection only; it flags a bad frame, it doesn't fix it. Gotchas: the same polynomial appears bit-reversed (
0x1021↔︎0x8408) with bit order, and the init value and final-XOR are part of the spec — "CRC-16" is a family, so ARDOP's0x8810is legitimately different. Widths used: CRC-8 (0x07,0x1D,0xD5), CRC-12 (JS80xC06⊕ 42, Q650xF01), CRC-14 (0x2757), CRC-16 (0x1021/0x8408/0x8810/M170x5935), CRC-24 (0xFFF409, FST40x100065B). The full parameter table closes this section. (§3) - FEC-intrinsic detection — "(RS)", "(BCH)", "(Golay)". Algo: no separate CRC; the receiver detects as a side effect of decoding — if the block can't be resolved to a valid codeword (the locator search fails, or the corrected weight exceeds the code's power), the frame is flagged. Benefit: free detection, no overhead beyond the FEC already present. Tradeoff: weaker than a dedicated CRC — a badly-hit block can miscorrect to a wrong-but- valid codeword and pass silently, which is exactly why strong systems add a CRC on top of the FEC. Modes: DVB-S/S2, POCSAG, P25, LRPT. (§3–4)
- Constant-ratio weight check. Algo: every valid symbol has a fixed mark/space weight (CCIR-476: 4-of-7); RX rejects any symbol of wrong weight. Benefit: single-error detection with zero arithmetic. Tradeoff: pure detection, and it can't find the boundary (all valid symbols share a weight — see phasing, §B.2); correction comes only from time diversity. Modes: NAVTEX. (§3–4)
- Weight-count check + arithmetic checksums. Algo: Marine DSC sends each 7-bit symbol with a 3-bit count of its zero bits appended (a transmitted weight, where CCIR-476 fixes it) — a failed count marks the symbol for repair from its DX/RX time-diversity twin — and seals the whole call with an XOR of all symbol values (the error-check character); Inmarsat-C's EGC layer uses a byte-sum checksum (two's complement, so the received sum ≡ 0). Benefit: per-symbol detection that names which copy to trust, plus a whole-message seal, all for a few bits of arithmetic. Tradeoff: far weaker than a CRC against multi-bit patterns (an even number of same-position flips cancels in XOR); acceptable only because the diversity/FEC beneath has already cleaned the stream. Modes: Marine DSC, Inmarsat-C EGC. (§3)
- Cyclic syndrome. Algo: the block's cyclic-code syndrome (remainder under the generator) is zero for a clean block; a nonzero syndrome flags an error and, for a small one, names it for correction. Benefit: detection + light correction from one computation, and it doubles as the sync mechanism (offset words). Tradeoff: limited correction power. Modes: RDS (26,16). (§4.4)
- Sequence numbers / retransmit (ARQ). Algo: each packet carries a sequence number; the receiver detects loss/duplication from gaps and requests retransmission. RS-BA1 keeps a 16-bit sequence per stream, a bounded retransmit buffer, NACK packets requesting one sequence or a range (sent twice), and a 0.12 s reordering window on audio only — and when a requested frame is no longer buffered, it sends an idle at that sequence so the requester's window can advance rather than stall. Mercury's ARQ (Rafe's own design, not Mercury's wire format) runs a 16-frame window with a 16-bit selective-ACK bitmap. Benefit: makes a lossy link reliable without heavy FEC, with overhead proportional to actual loss. Tradeoff: needs a return channel and adds round-trip latency; useless one-way — which is exactly why broadcast NAVTEX uses time-diversity FEC instead. Modes: RS-BA1, ARDOP, Mercury. (§8)
The pinned-down parameters. "CRC-16" names a family, not a value — here is every CRC in the catalogue with all four parameters fixed, as verified against the implementations:
| Mode | Width | Poly | Init | Final XOR | Reflected | Covers |
|---|---|---|---|---|---|---|
| FT8/FT4 | 14 | 0x2757 |
0 | — | no | 77-bit payload (zero-padded to 82 bits) |
| JS8 | 12 | 0xC06 |
0 | ⊕ 42 | no | 72 msg + 3 frame-type bits |
| Q65 | 12 | 0xF01 |
0 | — | per-symbol bit-reversal | 78 info bits; the 2 CRC symbols are shortened away before TX |
| FST4 | 24 | 0x100065B |
0 | — | no | K−24 message bits (K = 101 FST4 / 74 FST4W) |
| LoRa payload | 16 | 0x1021 |
0 | ⊕ last 2 payload bytes | no | payload; CRC bytes left un-whitened |
| loracss | 8 | 0x07 |
0 | — | no | data bytes ("123456789" → 0xF4) |
| M17 LSF | 16 | 0x5935 |
0xFFFF |
— | no | 28-byte LSF ("123456789" → 0x772B) |
| P25 TSBK | 16 | 0x1021 |
0xFFFF |
— | no | first 8 payload bytes |
| TETRA | 16 | 0x1021 |
0xFFFF |
ones-complement FCS | no | payload; good residue 0x1D0F |
| DAB FIB | 16 | 0x1021 |
0xFFFF |
complement | no | 30 bytes per FIB (all its FIGs collectively) |
| HamDRM FAC | 8 | 0x1D |
0xFF |
inverted | no | FAC bits 0–39 |
| HamDRM packets/MOT | 16 | 0x1021 |
0xFFFF |
complement | no | data groups on reassembly |
| DVB-S2 BBHEADER | 8 | 0xD5 |
0 | — | no | 9 header bytes (a second CRC-8 replaces each TS 0x47 sync byte) |
| Mercury | 16 | MODBUS (named only) | — | — | — | frame (the spec pins the name, not the parameters) |
| ARDOP | 16 | 0x8810 |
0xFFFF |
frame type folded into low byte | no | count + data ("123456789" → 0x0E59) |
| AIS / APRS | 16 | 0x8408 (0x1021 reflected) |
0xFFFF |
0xFFFF |
yes | payload / whole frame between flags |
| ADS-B | 24 | 0xFFF409 |
0 | AP = CRC ⊕ ICAO address | no | full frame; DF17 valid ⇔ zero remainder |
| RVQ-Voice | 16 | 0x1021 |
n/s | — | — | header + payload |
Three rows are deliberately incompatible, and each teaches something. JS8's ⊕ 42 exists so that JS8 and FT8 frames — which share an LDPC family and a channel — can never validate against each other's CRC: protocol separation enforced by checksum. ARDOP folds the frame type into the CRC, binding a payload to its declared mode, so a frame decoded under the wrong mode hypothesis fails even if its bits survived. ADS-B overlays the transmitter's ICAO address onto the CRC (AP = CRC ⊕ address) — the checksum field doubles as the address channel, and "who sent this?" and "did it arrive intact?" become one computation. (And one non-CRC hiding among them: LoRa's explicit header check is five bits computed by a fixed 5×12 \(\mathrm{GF}(2)\) matrix over the header bits — a linear checksum, not polynomial division.)
B.4 FEC column
- None / repetition. Algo: send the payload raw, or send each bit \(n\) times and majority-vote. Benefit: trivial; repetition is genuinely robust. Tradeoff: no protection, or a brutal rate hit for modest gain. Modes: RTTY, PSK31, AIS, APRS carry no FEC and lean on retransmission / operator repetition. (§4.1)
- Hamming. Algo: a few parity bits positioned so the failing-check pattern (syndrome), read as a binary number, names the single bad bit. Benefit: single-error correction for very little overhead — ideal for a short header. Tradeoff: one error per block; useless against bursts. Modes: LoRa — per-nibble at rates 4/5…4/8 (the
CRsetting), but only the 7- and 8-bit variants can correct; CR 4/5 and 4/6 merely detect, and the header always gets the full (8,4) regardless of payload CR — and P25 (15,11)/(10,6). (§4.2) - Golay(23,12) / extended (24,12). Algo: a perfect binary code correcting any 3 errors in 23 bits (the extended (24,12) adds parity and detects 4), decoded by syndrome table or algebraically. Benefit: strong fixed-rate protection for small critical fields, half-rate. Tradeoff: fixed short block, 50% overhead. Modes: P25 (throughout), M17 LICH. (§4.4)
- BCH. Algo: a cyclic code whose generator forces \(2t\) consecutive powers of \(\alpha\) to be roots (guaranteeing \(t\)-error correction); decode by syndrome → Berlekamp-Massey → Chien. Benefit: designed, provable correction power in a compact block; the syndrome doubles as detection. Tradeoff: fixed rate, hard-decision (no soft-in gain), and as a binary code a burst of \(b\) bits costs \(b\) corrections — burst channels want RS instead. Modes: POCSAG (31,21, generator
0x769, \(t=2\), plus a parity bit lifting distance to 6), P25 NID (63,16, degree-47 generator, \(t=11\) — two-thirds of the NID's bits are parity, which is why the NAC survives a hammered header), DVB-S2 outer (over \(\mathrm{GF}(2^{14})\), \(t\) = 8–12, mopping up the LDPC's rare residual errors). (§4.4) - Reed-Solomon. Algo: BCH over \(\mathrm{GF}(2^m)\) with symbols as field elements; corrects \(t=(n-k)/2\) symbol errors via syndrome/BM/Chien + Forney (for values). Benefit: superb against bursts — a burst hits few symbols — and shortens/punctures freely. Tradeoff: usually hard-decision; complexity grows with the field. Quirks worth knowing: CCSDS/LRPT sets its first root at \(\alpha^{112}\) (making the generator palindromic) and transmits symbols in a dual basis, with four codewords byte-interleaved per frame; JT65's RS(63,12) corrects 25 of 63 symbols — the code essentially is the mode (Rafe decodes it hard-decision; WSJT-X's soft Franke–Taylor decoder is worth several dB more and is not ported); HamDRM's EasyPal repair blocks decode RS(255,K) erasure-only — each known-missing block costs one parity symbol instead of two; ARDOP re-verifies the syndrome after correcting and refuses anything beyond \(t\) — belt and braces against miscorrection. Modes: JT65 (63,12), DVB-S (204,188), LRPT/CCSDS (255,223), DAB+ (120,110), ARDOP (per frame type), HamDRM repair. (§4.5)
- Reed-Muller RM(30,14). Algo: a low-rate block code decoded by fast majority-logic / correlation against the codeword set. Benefit: very robust for a tiny mission-critical field (broadcast sync / access grant), with a cheap decoder. Tradeoff: very low rate — only worth it for the few most-important bits. Modes: TETRA (the AACH carries no CRC — the block code is its sole protection). (§4.4)
- Constant-ratio CCIR-476. Algo: a fixed-weight (4-of-7) code; detection by weight, correction by time diversity — each character sent twice, delayed, so a bad copy is repaired from the good one. Benefit: extremely simple, and the diversity gives real correction on a fading LF/MF path. Tradeoff: correction rides entirely on the diversity delay (latency), not the code. Modes: NAVTEX. (§4, §5.1)
- Convolutional + Viterbi. Algo: slide the data through a \(K\)-stage shift register emitting XOR taps; decode with Viterbi (most-likely path through the \(2^{K-1}\)-state trellis), ideally with soft inputs. The generator constants cluster beautifully: the classic \(K=7\) pair octal 171/133 serves DVB-S and LRPT (G2 inverted per CCSDS convention) and — in fldigi bit-layout
0x6D/0x4F— Thor and RVQ-Voice; DAB and HamDRM run \(K=7\) rate-1/4 mother codes; M17 (0x19/0x17) and TETRA (rate-1/4 mother) use \(K=5\); P25's trunking data uses a tiny 4-state rate-½ trellis. Benefit: excellent against random errors, streaming (no block boundary), and gains ~2 dB from soft decisions. Tradeoff: decoder cost doubles per unit of \(K\); errors leave the decoder in bursts (the reason outer RS codes exist); tail bits to flush the register tax every block; poor against bursts without interleaving. Modes: DVB-S/LRPT/DAB/HamDRM/Thor/RVQ-Voice (\(K=7\)), M17/TETRA (\(K=5\)), P25 data. (§4.6) - Sequential (stack) decoding. Algo: the same convolutional idea pushed to \(K=32\) (generators
0xF2D05351/0xE4613C47, both weight 15), where Viterbi's \(2^{31}\) states are impossible — the Zigangirov–Jelinek stack decoder explores the code tree best-first under the Fano metric, popping the most promising path until it reaches the end (JT4 budgets 300k pops). Benefit: the asymptotic gain of an enormous constraint length — no 64-state code approaches it — on messages small enough (50–72 bits) for tree search to stay sane. Tradeoff: decode effort is a random variable that explodes at low SNR, with no maximum-likelihood guarantee; strictly a short-message trick. Modes: WSPR, JT4. (§4.6) - Puncturing / RCPC. Algo: design one low-rate mother convolutional code, then delete output bits per a fixed pattern to raise the rate; the decoder inserts erasures at the deleted positions. Benefit: one encoder/decoder serves a whole family of rates, switchable per link condition. Tradeoff: higher punctured rates give up coding gain. Modes: DVB-S (1/2…7/8), TETRA (RCPC), M17. (§4.6)
- LDPC + belief propagation. Algo: a sparse parity-check matrix (Tanner graph); decode by iteratively passing likelihood (LLR) messages between bit- and check-nodes (sum-product, or the cheaper min-sum) until the parities hold. Benefit: within a fraction of a dB of the Shannon limit — the best practical performance, fully soft-decision. Tradeoff: iterative decode is compute-heavy; large parity tables; needs good LLRs to shine. Modes: FT8/FT4 (174,91 — every bit node degree 3, 25 BP iterations), JS8 (174,87), FST4 (240,101) and FST4W (240,74 — same length, more parity, deeper reach; 40 iterations), DVB-S2 (16200-bit short frames with staircase parity, so encoding is a linear-time accumulator), Mercury (clean-room N=1600 at rates 1/16–14/16), FreeDV 700D/E. One documented gap: Rafe's WSJT-X-family decoders run single-pass BP without the reference implementations' OSD fallback stage, giving up a little sensitivity at the very bottom of the range. (§4.7)
- QRA65 (q-ary repeat-accumulate over GF(64)). Algo: a (65,15) systematic code whose symbols are \(\mathrm{GF}(64)\) elements — 13 info + 2 CRC symbols generate 50 parity symbols, shortened to 63 for transmission. Decoding is non-binary sum-product: messages are 64-ary probability vectors, and each check node's convolution over \((\mathbb{Z}_2)^6\) is computed with a fast Walsh–Hadamard transform, up to 100 iterations. Benefit: the code's symbols are the same 64-ary objects the 65-FSK demodulator measures, so FFT bin energies feed the decoder directly with no lossy symbol-to-bit marginalisation — modulation and code speak one alphabet, excellent for EME/weak-signal work. Tradeoff: every graph edge carries a 64-value message — memory and compute per iteration dwarf a binary decoder's; niche. Modes: Q65. (§4.7)
- Walsh / Hadamard block code. Algo: map blocks of bits to rows of a Hadamard matrix (mutually orthogonal ±1 sequences); decode by a fast Walsh-Hadamard transform picking the largest correlation. Benefit: large coding gain with graceful soft-decision via a cheap transform; very robust. Tradeoff: low rate (orthogonality costs bandwidth). Modes: Olivia ([64,7,32] — one character per codeword, then sign-scrambled and spread diagonally across the MFSK symbols), Contestia ([32,6,16]). (§4.7)
- MLC (multi-level coding). Algo: protect each bit-level of a multi-bit QAM/OFDM symbol with a different code rate (strong on the vulnerable MSB, light on the robust LSB), often a punctured convolutional set. Benefit: matches protection to each bit's actual error probability — near-optimal redundancy on multilevel constellations. Tradeoff: more complex than one uniform code; rate bookkeeping. Modes: HamDRM/DRM. (§4)
- (26,16) cyclic. Algo: a shortened cyclic code whose syndrome detects and (single-bit) corrects, with the offset-word trick providing sync. Benefit: detection + light correction + sync from one small code — ideal for a low-rate broadcast data stream. Tradeoff: modest correction power (single-bit, via a precomputed syndrome table). Modes: RDS (generator
0x5B9). (§4.4) - Concatenation. Algo: cascade an inner code (Viterbi/LDPC, against random errors) and an outer code (RS/BCH, against residual bursts), separated by an interleaver; decode inner-first, hand hard symbols outward. Benefit: combines the strengths — near-capacity inner performance with a burst-cleaning outer backstop. Tradeoff: two decoders plus an interleaver's latency and more implementation surface. Modes: DVB-S (conv+RS), LRPT/CCSDS (Viterbi+RS), DVB-S2 (LDPC+BCH), DAB. (§4.8)
Appendix C — Where to go deeper
Concept → the spec that works it in full detail:
- Modulation — FSK/OOK are clearest in POCSAG and ISM; GFSK in FT8; chirp in LoRa PHY; π/4-DQPSK in TETRA; OFDM in DAB and HamDRM.
- Sync / Costas arrays — FT8; the phasing gotcha in NAVTEX.
- CRC — the polynomial family across AIS, ARDOP, ADS-B.
- FEC — Hamming in LoRa PHY; BCH in POCSAG & P25; RS/GF(64) in JT65; Viterbi/puncturing in WSPR & TETRA; LDPC in FT8, FST4, DVB-S2; concatenation in LRPT.
- Interleaving / scrambling — time diversity in NAVTEX; whitening in LoRa PHY; the CCSDS randomiser in LRPT; colour-code keying in TETRA.
- Receiver DSP — Gardner+Costas in LRPT; envelope in APT; discriminator in AIS.
- Source coding / vocoders — rvqvoice.md, M17, FreeDV, wsjtx-message-packing.
- Above the PHY — RS-BA1 transport; AES-CTR in Meshtastic.
Every constant named here is reproduced, in full and verified against the implementation, in the linked per-protocol specification.