Decoding MOVE-II

MOVE-II is a cubesat from Technical University of Munich that was launched in December 2018. It transmits telemetry in the 145 MHz amateur satellite band using a protocol that uses CCSDS LDPC codewords. Back in the day, there was a GNU Radio out-of-tree module developed by the satellite team to decode this satellite. Given the additional effort required to support LDPC decoding for just this satellite and since there was already a GNU Radio decoder available, I never added a decoder for MOVE-II to gr-satellites.

Fast forward 5 years, and MOVE-II is still active, but apparently its GNU Radio out-of-tree module has bit rotten. The Gitlab repository where this was hosted (I believe it was a self-hosted Gitlab) has disappeared, and while it was originally developed for GNU Radio 3.7, it was never ported to newer GNU Radio versions. Some days ago, some amateurs including Scott Chapman K4KDR and Bob Mattaliano N6RFM started doing some experiments to try to get a decoder for MOVE-II working.

Seeing this, I decided to revisit the situation and try to add a decoder for MOVE-II to gr-satellites. Since this satellite was launched, I have been dealing with CCSDS LDPC for the Artemis Orion, made my own LDPC decoder, and participated in fixing the GNU Radio in-tree LDPC decoder. Therefore, most of the heavy lifting seemed to be already done.

I have now added an example decoder flowgraph for MOVE-II to gr-satellites. Here I describe the details of this example, and why it is only an example instead of a fully supported decoder as the ones that exist for other satellites.

The specifications for the MOVE-II beacon, as well as the flowgraph that Scott was working on, already give all the information about the physical layer. The signal is 12.5 kbaud BPSK. The coding is CCSDS LDPC r = 1/2, k = 1024. There is synchronous CCSDS scrambling (this is not mentioned in the specifications, but is present in Scott’s flowgraph). The syncword preceding each LDPC codeword is 0x49E0DCC7. This is not the standard CCSDS codeword for r = 1/2 LDPC, since in fact the CCSDS codeword is 64 bits long.

In contrast to most cubesats, MOVE-II has a data link layer that allows it to transmit frames of a size that is independent of the FEC codeword size. The data link protocol is called NANOLINK and is described in this paper. Its frame structure is somewhat similar to CCSDS Proximity-1, and in fact the paper cites it as one of their inspirations.

The paper doesn’t give all the details regarding the NANOLINK protocol, but the information there is enough to figure out the remaining details by looking at some beacons from MOVE-II. As in Proximity-1, frames are sent asynchronously by injecting them in a bitstream that is chopped up into LDPC codewords for its transmission. Whenever there are no frames to transmit, the bitstream repeats an idle sequence. Unlike Proximity-1, which defines a 32-bit idle sequence, MOVE-II uses 0xCC as idle sequence. The frames are detected in the bitstream by their synchronization word, which appears at the start of each frame. NANOLINK uses the same synchronization word as Proximity-1: 0xFAF320.

NANOLINK frame structure (taken from this paper)

After the syncword, the NANOLINK frames contain a 3-byte header that includes a length field, which is required because frames have variable length, as in Proximity-1. The value of the length field does not count the 3-byte header, but it counts the rest of the frame, including the CRC-16 (an alternative explanation is that it does not count the CRC-16, it counts the 3-byte header, and the length field value is the length minus one, which is a typical thing in CCSDS). The MOVE-II beacons I have seen don’t have extension headers.

The CRC-16 is the typical CRC-16 CCITT-ZERO, which is used for instance in the CCSDS TM Synchronization and Channel coding, but not in Proximity-1 (which uses a CRC-32). It covers all the frame (except the syncword), including the 3-byte header.

The way in which the NANOLINK protocol is used in MOVE-II is that to transmit a beacon, the BPSK transmitter switches on and starts transmitting a 010101 sequence followed by some CCSDS codewords that contain only 0xCC idle data. This gives time to the receiver loops to lock. At some point, a NANOLINK frame begins. This is not aligned to the beginning of an LDPC codeword. Since the beacon frames are longer than the 128 information bytes in an LDPC codeword, the frame spans several codewords. After this, there are some more LDPC codewords that only contain 0xCC idle data, perhaps intended to flush the decoder, and finally the BPSK transmitter switches off.

Here is the example flowgraph to decode MOVE-II. I have tested it with this recording made by Jan van Gils PE0SAT.

MOVE-II GNU Radio decoder flowgraph

Initially I wanted to use the GNU Radio in-tree LDPC decoder for this. However, when trying to use it I realized that it is not possible. Even though we have fixed the bugs it had (this is what prevented me from using it for the Artemis mission), there are two problems when trying to use it for CCSDS LDPC:

  • The decoder doesn’t support puncturing. The CCSDS LDPC r = 1/2, k = 1024 code is actually a (2560, 1024) code, of which the last 512 bits are not transmitted. This is not a major problem, since it is easy to append 512 zeros at the end of each codeword.
  • The CCSDS LDPC codes are systematic, and the first k bits of the codeword are the information bits. This is the showstopper, because the GNU Radio in-tree LDPC decoder has its own different notion of which are the systematic bits. The function that decides which are the systematic bits is here. According to some comments it is based on a construction in a book by Richardson and Urbanke that builds a lower complexity encoder (i.e., not completely dense generator matrix) from the parity generator matrix at the cost of allowing the systematic bits to be in positions other than the first ones. There is no way to override this behaviour without modifying the code (which I have occasionally done for testing).

After realizing that the in-tree LDPC decoder wouldn’t work for this without code changes, I decided to use gr-ldpc-toolbox, the out-of-tree module that wraps as GNU Radio blocks the LDPC encoders and decoders in my Rust ldpc-toolbox library. Unfortunately, this means that I cannot easily add the MOVE-II decoder as a fully supported decoder in gr-satellites, because it would require making gr-satellites depend on gr-ldpc-toolbox. This would complicate packaging gr-satellites for multiple platforms (we would need a Conda package for gr-ldpc-toolbox, which needs the Rust toolchain to build, etc.). The work needed doesn’t seem worth the effort just for this relatively old satellite. The few people that are interested in MOVE-II can build gr-ldpc-toolbox from source and use the example flowgraph.

Another issue that I found was descrambling. Here it needs to be done with soft symbols, but the Additive Scrambler block only supports hard symbols. In my deep space flowgraphs I’m using a block from gr-dslwp for this. However, I didn’t want the example to depend on gr-dslwp as well, so I have done a workaround with the current Additive Scrambler block. Additionally, I have sent a pull request to GNU Radio to add support for handling soft symbols in the Additive Scrambler block, so this kind of workaround will not be needed in the future.

The decoder flowgraph uses the BPSK demodulator from gr-satellites. It then performs detection of the 32-bit syncword marking the start of each LDPC codeword. After this, descrambling and LDPC decoding is done following the remarks above. Finally, there is a NANOLINK Transport block that I have added to gr-satellites. This is a Python block that extracts NANOLINK frames from a bitstream and checks their CRC. Its output is PDUs with a NANOLINK frame with header but without CRC-16.

The following figure shows the GNU Radio flowgraph decoding one of the MOVE-II beacons. The syncword correlations are plotted, so we can see where each LDPC codeword begins. The NANOLINK frame only occupies 3 codewords around the middle of the transmission. The rest only carry the idle sequence. We can see that the transmitter stops in the middle of an LDPC codeword.

MOVE-II beacon plotted by the decoder

The next figure shows the waterfall corresponding to the same transmission. There is a CW carrier before the BPSK burst. This corresponds to a CW beacon that alternates with the BPSK telemetry, although in this case it is transmitting a long dah just by chance. The beginning of the transmission has a 0101 sequence before the LDPC codewords start, which causes two spectral lines.

MOVE-II beacon waterfall

These are the contents of the decoded NANOLINK frame.

0000: 00 10 a0 ee 3a 33 99 c8 7e ca 81 65 fd 00 00 00
0010: 32 73 6d 61 72 64 00 00 01 00 66 72 61 6d 5f 72
0020: 65 61 01 00 63 64 68 00 00 00 00 00 01 00 65 70
0030: 73 00 00 00 00 00 01 00 62 65 61 63 6f 6e 00 00
0040: 01 fe 72 65 73 71 00 00 00 00 01 fe 74 72 63 74
0050: 72 6c 00 00 01 00 73 68 00 00 00 00 00 00 01 00
0060: 05 05 d2 04 00 00 00 00 00 00 00 00 00 00 00 00
0070: 04 00 00 00 00 00 00 00 10 ff 00 00 00 00 40 00
0080: 20 51 7c 9d 13 00 77 00 c0 04 00 20 05 00 00 00
0090: 00 00 00 00 06 00 00 55 00 8a 00 08 00 b2 00 6a
00a0: 01 02 00 08 00 b7 01 02 00 02 00 c0 01 d8 00 5a
00b0: 00 05 03 15 00 59 03 0e 00 2b 03 10 00 7e 03 01
00c0: 00 05 00 07 00 31 03 11 00 eb 02 01 00 04 00 01
00d0: 00 01 00 01 00 01 00 01 00 01 00 bb 01 01 00 01
00e0: 00 02 00 01 00 01 00 66 01 02 00 01 00 01 00 04
00f0: 01 01 00 02 03 4f 00 58 03 05 00 4d 03 02 00 57
0100: 03 e2 02 60 02 a1 02 5e 02 a2 02

As concluding remarks, I think that what has happened with the original MOVE-II GNU Radio decoder illustrates well why it is important that university satellite teams work together with the open source community to upstream their code into a well-maintained long-lived project. Otherwise, the code quickly becomes unmaintained and breaks. The main mission of the satellite finishes, the students that wrote the code have finished their studies and moved on, but the satellite is still working and transmitting. This happens in so many projects. Moreover, the code can often useful for other projects, so it is a pity that it dies unmaintained. Here I have used an LDPC decoder that I developed for something else, and only have needed to write a few lines of Python to handle the NANOLINK protocol.


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.