Low latency decoder for LilacSat-1

LilacSat-1 is one of the QB50 project cubesats. It will be released tomorrow from the ISS. The most interesting aspect of this satellite is that it has an Amateur Radio transponder with an FM uplink on the 2m band and a Codec2 1300bps digital voice downlink on the 70cm band. It is the first time that an Amateur satellite really uses digital voice, as previous tests have only used an analog FM repeater to relay D-STAR and similar digital voice modes. LilacSat-1 however implements a Codec2 encoder in software using its ARM processor. I have talked about LilacSat-1 Codec2 downlink already in this blog. Here I present a low latency decoder for the digital voice downlink that I have recently included in gr-satellites.

LilacSat-1 downlink uses 9k6 BPSK with a CCSDS convolutional code and CCSDS scrambler. It transmits 116 byte packets which are marked with the CCSDS 32 bit syncword. A typical packet looks like this (syncword not included):

0000: c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 e1 5c 5e 
0010: af ab eb 21 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 
0020: c0 c0 c0 c0 c0 41 54 a8 9f df d6 b1 c0 c0 c0 c0 
0030: c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 01 12 88 
0040: 8d de d5 b1 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 
0050: c0 c0 c0 c0 c0 77 30 b8 e5 54 c3 21 c0 c0 c0 c0 
0060: c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 f7 70 88 
0070: e5 74 c3 61

We see that, if we include the syncword, the 120 byte packet can be divided into 5 chunks of 24 bytes. The last 7 bytes of each chunk are a Codec2 frame. The math adds up perfectly: a Codec2 frame is 52 bits long (here it is packed into 7 bytes) and encodes 40ms of voice. Since LilacSat-1 packets are transmitted at 4800bps and a Codec2 frame is transmitted every 24 bytes, we get a Codec2 frame precisely every 40ms.

The remaining bytes in the packet are a KISS stream that is used to transmit telemetry. In the packet above we have the stream idling, so it consists of c0 bytes only. Every once in a while, a telemetry packet is transmitted and we get a packet where the KISS stream bytes are filled in with the telemetry data, such as this one:

0000: c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 ba 49 5f 
0010: f7 7c c5 f1 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 c0 
0020: c0 c0 c0 c0 c0 e2 19 5b f7 4c dd f1 c0 f4 54 3b 
0030: 00 1a a8 0f 04 01 07 04 10 cd 7a bb a9 12 99 4a 
0040: a5 54 4d 71 22 7a 4c 41 82 1c 60 e7 4a 1b 4b c1 
0050: 80 3a 86 ca f7 f3 88 75 e5 55 c5 f1 d9 50 c1 ad 
0060: a0 8d 9d 53 5b a1 40 28 6f c6 35 4d 71 f2 aa 76 
0070: c7 5f dd 71

Here you can even see the start of the telemetry packet, which is the f4 byte located at 0x2d.

When I analysed the Codec2 downlink in a previous post, I remarked that the fact that Reed-Solomon is not used is a good idea to avoid latency. A 120 byte packet takes 200ms to transmit at 4800bps. To do Reed-Solomon decoding we would need to wait to receive the whole packet before processing any data, thus introducing more that 200ms of latency, which is probably too much in this application. On the other hand, a single Codec2 frame encodes only 40ms of voice, so if we process the Codec2 frames as soon as they are available in the receiver, we get much better latency.

Unfortunately, the decoder that the satellite team has published in gr-lilacsat doesn’t do this. As you can see in the image below, after Viterbi decoding, the syncword is searched and the whole packet is assembled into a PDU. The “LilacSat-1 Frame Depack” then extracts the Codec2 frames from the PDU and passes them on to the decoder. The problem with this approach is that the whole packet must be received in order for the PDU to be produced, so this is introducing 200ms of latency.

Deframer from gr-lilacsat

I have developed a decoder for gr-satellites that processes the Codec2 frames as soon as possible. The relevant part of the flowgraph can be seen below.

Deframer from gr-satellites

After Viterbi decoding is done, the syncword is searched and marked with a tag. The tag is used to reset the Additive scrambler that implements CCSDS descrambling. Then the custom “LilacSat-1 demuxer” block keeps track of the syncword tags and outputs Codec2 frames and KISS bytes as soon as possible. The output of this block is done with PDUs to allow mixing easily the outputs from the two Viterbi decoder branches. When no valid syncword is found, the demuxer outputs nothing.

I have decided to do Codec2 decoding outside of GNU Radio. The Codec2 frames are sent by UDP. These can be received and decoded with the c2dec command line tool from FreeDV:

nc -ulp 7000 | ./c2dec 1300 - - |  play -t raw -r 8000 -e signed-integer -b 16 -c 1 -

(See this post for more information about how to use the FreeDV command line tools).

The lilacsat1.grc flowgraph is already included in gr-satellites. It is important to note that the “Additive descrambler” block in GNU Radio has a bug that prevents it from working properly. This bug has been fixed in the git repository, but it is still present in all stable releases. Therefore, this decoder needs the git version of GNU Radio to run correctly. The flowgraph can be tested with the lilacsat1.wav sample recording in satellite-recordings.


  1. Great article Daniel!

    When I start the Low Latency decoder in GNU it immediately stops and outputs:

    gr::log :DEBUG: correlate_access_code_tag_bb0 – Access code: 1acffc1d
    gr::log :DEBUG: correlate_access_code_tag_bb0 – Mask: ffffffff

    I have no data going to the UDP source and am not connecting to the UDP sink but I expect it should stay running?
    Any ideas!

  2. OK, sorry, my misunderstanding, it was due to ‘No Gui’ set in the top block. However I still do not get packets to the low latency decoder. I am using the replay_lilacsat1_rx connected to the low latency flowgraph then outside GNUR via netcat, c2dec etc.

    The replay_lilacsat1_rx UDP sink is set to “complex” however the UDP source in the low latency decoder is set to “short”. Could this be the problem? Do I need to change the stream with some other modifying block? Thanks for your help.

    1. Briefly speaking, the replay_lilacsat1_rx UDP output is not compatible with the decoder in gr-satellites. I suggest you first try the WAV recordings in satellite-recordings (this is included as a submodule/folder inside gr-satellites). These WAV files can be played into the decoder using wav_48kHz from gr-frontends (you must use the -f option when running wav_48kHz to specify the input WAV file). There are two LilacSat-1 WAV recordings in satellite-recordings: lilacsat1.wav is a recording of the engineering model that contains digital voice and telemetry; lilacsat1-image.wav is a recording of the flight model (in space) that contains a complete image and telemetry.

  3. Thank You Daniel for you help, I now have everything working during live satellite passes! The decoder works very well. The reduced latency is very noticeable and full duplex voice is much less distracting. Now to try live satellite image downloads!
    Thanks Again.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.