I wrote this code to help NOAA, the operator of the spacecraft, close coverage gaps with additional groundstations around the world. They didn't have the budget to buy the necessary hardware decoders, so I volunteered to do it in software to demonstrate the powerful digital signal processing that can now be done on garden-variety PCs.
The demodulator takes three options:
-d|--debug to specify the debug level;
-f|--full-frame to specify full frame output, including sync and RS parities
(normally only the data is output)
-m|--no-mmx to disable the automatic use of MMX, if present (x86 machines
only)
-s|--sample-rate to specify the input sample rate (default 9600 Hz).
-o|--no-offset disables repeated decoding attempts with increasing
subcarrier phase offsets on frames that do not initially decode
-v|--no-mmx-vd disables the MMX version of the Viterbi decoder
without totally disabling the use of MMX instructions.
The program as supplied simply demodulates the data and sends it to standard output; comments in the source indicate where code can be added to further format the data and send it over the Internet to a central collection point as desired.
Note that acedemod expects its input at baseband. It assumes that a receiver with a PLL is tracking the residual carrier on the spacecraft downlink and downconverting the signal to audio with the carrier at zero frequency. A to-do project is to implement such a receiver in DSP that can be run in a UNIX pipe ahead of acedemod, thus allowing the RF equipment to consist merely of a fixed-frequency downconverter that shifts the composite telemetry to within the passband of a standard PC sound card's A/D converter.
gensig [-a amplitude] [-f subcarrier freq] [-e ebno] [-s samplerate]
where the amplitude is the peak signal output amplitude in sample units (32767 being full scale for signed 16-bit samples), the subcarrier frequency defaults to 996 Hz (and probably shouldn't be changed), and the ebno is specified in decibels (default 10 dB, a very strong signal).
The coding used here will operate pretty solidly down to EbNo = 3dB, and will start breathing hard below that. At about 2.6-2.7 dB, you'll start to see a few RS blocks that cannot be corrected. Below 2.5 dB, lots of blocks are lost. This steep error curve is typical of systems with strong FEC.
I had already implemented a Viterbi decoder for the CCSDS k=7 r=1/2 polynomials, but the CCSDS convention for the polarity and order of the two symbols for each input bit was different. This was a relatively straightforward change.
The Reed-Solomon decoder took more work. The CCSDS standard calls for a "dual basis" representation of the 8-bit symbols. I implemented this with a pair of 256-byte lookup tables, one to convert from dual-basis to conventional representation before decoding, and an inverse table to convert back to dual-basis after decoding. The CCSDS standard also specifies a palindromic generator polynomial; this by itself was relatively easy to accomodate. A bigger problem was that the roots of the CCSDS generator polynomial are not consecutive, and in my decoder I had assumed they always would be. I made the necessary generalizations to support this.
The sync vector correlator is quite compute intensive, especially on the initial acquisition where a full 16 second window must be searched. Version 2.0 of acedemod automatically uses the Intel MultiMedia eXtensions (MMX) instructions, if available, to speed this operation substantially.
Version 3.0 of acedemod includes a Viterbi decoder that uses MMX instructions, if available. The speedup is approximately 3x on the Intel Pentium-II and somewhat less on MMX-enabled Pentiums and AMD K-6s.
If at least one of the RS blocks decodes successfully, the demodulator assumes that it synchronized correctly and it proceeds to search for the trailing sync vector at the end of the next frame using the same narrow search used to find the vector that ended the current frame. But if none of the RS blocks decoded, the demodulator assumes this was due to a loss of synchronization so it repeats the full 16-second sync vector search procedure. This is by far the most CPU-intensive part of the demodulator. Version 2.0 of the demodulator uses the Intel MMX (Multi-Media eXtensions) instructions, if available and enabled, to speed up this step.
Since the sync vector correlator can only estimate subcarrier phase to the nearest A/D sample, this technique is especially beneficial with low A/D sampling rates. It can be disabled with the -o or --no-offset command line option.
Sometimes some but not all of the RS blocks in the frame will decode. When this happens, the RS decoder can tell you where it found and corrected errors in the blocks that did decode (it cannot tell you anything about the blocks that didn't decode, though). But with this information, and the knowledge that a RS decoder can correct up to twice as many errors if you can tell it in advance where the errors are, you can try telling the RS decoder to try the failed block(s) again, this time marking as erasures the symbols corresponding to those that were successfully corrected in the adjacent blocks.
This works often enough to give you a few tenths of a dB of improvement in Eb/No performance, but you have to be careful. Every time you erase RS symbols before decoding a block, you increase the chances of the decoder succeeding but you also increase its chances of making an undetected error! For this reason I have not released the iterative decoding stuff until I can further characterize its undetected error rate.
Another form of iterative decoding involves repeating the Viterbi decoder step. It's known that a Viterbi decoder is less likely to make errors when decoding in the vicinity of data it already knows (such as the first or last few bits of a frame with known starting and terminal encoder states). We can apply the same principle to RS code blocks that fail to decode when they're straddled by RS blocks that did decode. We simply use the decoded RS data (which is highly reliable because of the redundancy in that code) to "pin" the Viterbi decoder in a second run over the data that didn't decode. In this case, I actually perform 248 separate runs of the Viterbi decoder, one for each byte in the failed RS frame. I used the same feature I had already added to the Viterbi decoder to handle the non-zero starting and terminal encoder states in the first decoding pass on the frame.
Surprisingly enough, this technique seemed to help little, if at all. The Viterbi decoder almost never "changed its mind" when given firm knowledge of the adjacent data. The one exception occurred occasionally in the very last byte of the frame. It turned out that I had made a fencepost error in my original Viterbi decoding pass (the terminal state was shifted off by one bit), but I was giving it the correct terminal state in the redecoding pass. Funny how FEC can sometimes be so strong that it even corrects for programming errors!
This is somewhat consistent with the literature, which gives a much smaller EbNo improvement (.1-.2 dB) for the iterated Viterbi decoding with state pinning than for the iterated Reed-Solomon coding.
Copyright 1999 Phil Karn, KA9Q
This software may be used under the terms of the GNU Public License.
Updated: 19 June 2006