The structure of the telemetry frames can be seen in the following image, taken from Mike's web.

The telemetry frame is sent as the PDU in an LTU frame. The frame starts with an 18 bit syncword and a CRC-14. These don't make much sense when the telemetry frame is sent inside a single LTU frame, since the start of the frame is already clear and the PDU of an LTU frame is protected by a CRC-13. My guess is that the syncword and CRC-14 are there in case the telemetry frame is fragmented and sent inside several LTU frames (or perhaps when it is sent over another different link layer). Or maybe for aggregation, where several telemetry frames are sent inside the same LTU frame PDU. So far, I haven't seen any of these things being used.

The Frame Content ID fields identify the type of frame. The S-NET team has only shared the details for EPS telemetry frames (FCID Major 9, FCID Sub 0) and ADCS telemetry frames (FCID Major 0, FCID Sub 0).

After this, there are 6 bits of flags. The multiframe flag hints at the possibility of performing fragmentation or aggregation. The optional timestamp is given as a little-endian 32bit integer, in units of 0.5s since 2000-01-01 00:00:00UTC (a rather weird timestamp format). The "Is Time Tagged" flag indicates whether the optional timestamp is included (not all packets use it). I don't know the purpose of the "Time Tagged Setting" flag.

The data length field contains the length in bytes of the user data. Again, this field doesn't make a lot of sense when the telemetry frame is sent inside a single LTU frame, as there is already a length field in the LTU header.

The S-NET team has shared the format for the ADCS and EPS beacons. This can be seen in the S-NET telemetry pyconstruct definitions.

An ADCS frame looks like this. It is interesting that the current position of the satellite is included with the ADCS telemetry. The name of the fields includes SGP4, so this probably indicates that the position is calculated using TLEs rather than being obtained with GPS as other small satellites do.

Container: header = Container: CRC = 14448 FCIDMajor = 0 FCIDSub = 0 Urgent = False FutureUse = False CheckCRC = True Multiframe = False TimeTaggedSetting = True TimeTagged = True DataLength = 57 TimeTag = 2018-03-06 22:34:22 telemetry = Container: iModeChkListThisStepActive = 1 iAttDetFinalState = 3 iSensorArrayAvailStatusGA = 15 iSensorArrayAvailStatusMFSA = 3 iSensorArrayAvailStatusSUSEA = 0 iActArrayAvailStatusRWA = 0 iActArrayAvailStatusMATA = 0 AttDetMfsDistCorrMode = 4 AttDetSuseDistCorrMode = 4 flags = Container: AttDetTrackIGRFDeltaB = False AttDetSuseAlbedoTracking = False SUSE1AlbedoFlag = True SUSE2AlbedoFlag = True SUSE3AlbedoFlag = False SUSE4AlbedoFlag = False SUSE5AlbedoFlag = False SUSE6AlbedoFlag = False AttDetAutoVirtualizeMFSA = True AttDetAutoVirtualizeSUSEA = False AttDetNarrowVectors = False AttDetMismatchingVectors = False omegaXOptimal_SAT = -0.161538461538 omegaYOptimal_SAT = -0.211538461538 omegaZOptimal_SAT = 0.265384615385 magXOptimal_SAT = -16570.0 magYOptimal_SAT = -20980.0 magZOptimal_SAT = 20230.0 sunXOptimal_SAT = -0.30425 sunYOptimal_SAT = -0.40390625 sunZOptimal_SAT = 0.86271875 dCtrlTorqueRWAx_SAT_lr = 0.0 dCtrlTorqueRWAy_SAT_lr = 0.0 dCtrlTorqueRWAz_SAT_lr = 0.0 dCtrlMagMomentMATAx_SAT_lr = 0.244094488189 dCtrlMagMomentMATAy_SAT_lr = 0.133858267717 dCtrlMagMomentMATAz_SAT_lr = 1.97637795276 iReadTorqueRWx_MFR = 0.0 iReadTorqueRWy_MFR = 0.0 iReadTorqueRWz_MFR = 0.0 iReadRotSpeedRWx_MFR = 0 iReadRotSpeedRWy_MFR = 0 iReadRotSpeedRWz_MFR = 0 SGP4LatXPEF = 42.4338028169 SGP4LongYPEF = 19.4802259887 SGP4AltPEF = 568.0 AttitudeErrorAngle = 0.0 TargetData_Distance = 64000 TargetData_ControlIsActive = 0

I haven't seen an EPS frame yet, which I still need to test my EPS frame parser. Most of the frames transmitted by S-NET are not ADCS or EPS frames. For instance, the FCID Major 48, FCID Sub 1 message type is seen a lot. Unfortunately, we don't have the information for the format of these messages. You can see the full decode of one of the recordings in this gist.

]]>When I implemented this FEC decoder, for simplicity I used a hard decision Viterbi decoder, since my main concern was to get all the system working. I always intended to replace this by a soft decision Viterbi decoder, but it seems that I forgot completely about it.

Now, while thinking about integrating gr-aausat (my AAUSAT-4 decoder OOT module) into gr-satellites and adding a soft Viterbi decoder for AAUSAT-4, I have remembered about this. While the decoder for AAUSAT-4 will have to wait, since I have found a bug in the GNU Radio Viterbi decoder that makes it segfault, I have already added a soft Viterbi AO-40 FEC decoder to the FUNcube decoders in gr-satellites.

Recall that the AO-40 FEC decoder works as described by the figure below.

My implementation of the hard decision decoder was as follows (click to view the figure in larger size):

Hard symbols come into the "Synchronize frame" block, that searches the distributed syncword and forms a PDU when it finds it. The PDU elements are also hard symbols. The "Deinterleaver" block implements a matrix deinterleaving with the hard symbols and then transforms them to soft symbols (just -1.0 and 1.0) before passing the PDU to the "FEC Async Decoder", since the Viterbi decoder always expects soft symbols.

With this in mind, doing soft Viterbi decoding is just a matter of sending soft symbols instead of hard symbols into the synchronizer, and also working with soft symbols in the deinterleaver. The new soft decoder is shown below.

The deinterleaver is the same as before, but working with floats instead of bytes. Since the synchronizer has access to soft symbols, it could use them to perform the correlation of the soft symbol stream against the syncword and detect the syncword by the correlation peak. However, I have decided to make it work as the previous version: it only looks at the sign of the soft symbols to detect the syncword. I don't think that this difference is very important, since the main task of the synchronizer is to avoid sending too much PDUs into the decoder by reducing the number of false positives.

Looking only at the sign of the soft symbols in the synchronizer makes it easier to choose the threshold, since it can be specified as the maximum number of bit errors to tolerate in the syncword.

]]>I had completely forgotten this satellite, but the other day I saw that Andy UZ7HO had added support for PolyITAN-2-SAU to his SoundModem. I asked Andy for some help, since I suspected that the coding wasn't exactly standard G3RUH AX.25.

The trick is that this satellite uses two "layers" of NRZI encoding. The relevant part of the decoder is shown below. The BPSK symbols come in from the left of the figure and the AX.25 packets exit by the right. A standard G3RUH AX.25 decoder wouldn't have the extra NRZI decoder on the right.

Note that NRZI decoding and G3RUH descrambling commute, since the G3RUH polynomial has an odd number of terms. Therefore, the decoder can also be organized in a different way, with both NRZI decoders at one side (either the input or output) of the descrambler.

Having two NRZI decoders in chain is a really funny concept, so it almost seems as some kind of mistake from the satellite team (most QB50 satellites use standard BPSK or FSK AX.25 packet radio for compatibility). In fact, if we write an NRZI decoder as , where is the input sequence, is the output sequence and the operations are performed on the finite field , then the effect of two NRZI decoders in chain can be written as

which is a rather strange form of differential decoder.

Thanks to Andy for giving me the clue about the extra NRZI decoder, as I would have had a hard time in finding it by myself (although, in retrospective, it is not that difficult to guess it by looking at the descrambled stream and seeing how HDLC 0x7e flags can be obtained from it). I have now added a decoder for PolyITAN-2-SAU to gr-satellites.

]]>

Although I already had a BCH decoder in C that Walter Frese kindly sent me, I have decided to implement my own in Python. The problem with several FEC decoders is that their licensing is not very clear. They originally come from textbook examples that get passed on and modified. For instance, several of the decoders available online for BCH and other codes are based on Robert Morelos-Zaragoza's implementations, whose licence prohibits commercial use. Thus, to keep licensing clean, I implemented my own decoder. In doing so, I have also learned the details of the decoding algorithm.

My BCH decoder has been made with simplicity in mind. The code is brief and simple to understand. Its performance (in terms of decoding speed) could be optimized, but performance is not very important for low bitrate telemetry decoding. The decoder implementation can be found here.

S-NET uses three related BCH codes: BCH(15,11,3), BCH(15,7,5) and BCH(15,5,7). These are cyclic codes over that are constructed with the help of the finite field . The only difference between the three codes is the choice of the generator polynomial.

BCH decoding is very well explained in the Wikipedia BCH page, so I've followed that closely. The decoding procedure uses arithmetic in . I have implemented multiplication and inversion by using exponential and logarithm tables, which have been calculated in Sage.

The received word is represented as the polynomial . Note that here the first element of the codeword represents the independent term of the polynomial. This is the convention used in S-NET, but the mapping between codewords and polynomials can also be done in the opposite order.

Syndrome calculation is easy to do by using the exponential table. Since the syndromes are , they can be computed by summing up some powers of . Here is the primitive element of that we have used to construct our exponential and logarithm tables. Note that, from the point of view of the decoder, the only difference between the three codes BCH(15,11,3), BCH(15,7,5) and BCH(15,5,7) is the number of syndromes that are calculated (2, 4 and 6 respectively).

The next step in the decoding algorithm is to calculate the coefficients of the error locator polynomial. I have used the Peterson–Gorenstein–Zierler algorithm, as described in Wikipedia. This involves solving a linear system with coefficients in . The matrix of this system is a square matrix of size 1, 2 or 3, depending on the number of syndromes.

To solve this system, I use Cramer's rule. The determinants are computed by Sarrus' rule. Since the matrix is small, it is not difficult to write all the calculations explicitly by hand. This step could be optimized, but is is easy and clear to do it in this way.

After having computed the coefficients of the error locator polynomial , I calculate its roots by brute-force search, by computing for . This is done again by using the exponential table. This step can be optimized by using Chien search, but this is not so important, because we only have to try 15 possible roots and has degree at most 3, so even the naïve computation is fast.

Since the roots of indicate the positions of the errors, the final decoding step is to flip the erroneous bits.

]]>After some really helpful communication with the S-NET team, in particular with Walter Frese, and some exchanges of ideas with Andrei Kopanchuk UZ7HO, who was also working to add an S-NET decoder to his soundmodem, I have finally added a basic S-NET decoder to gr-satellites.

From the very start, Walter Frese from the S-NET team has been very helpful and has provided Andy UZ7HO and me with all the information we needed. He even did a worked example for me on how to parse the packet header. This has made our lives a lot easier, since S-NET has some quirks with the endianness and some bugs in the implementation of CRC's, so we probably wouldn't have succeeded without Walter's help. Many thanks to Walter and the rest of the S-NET team.

S-NET uses the CMX469 FFSK modem chip to transmit on 70cm. This chip is capable of generating 1k2, 2k4 or 4k8 AFSK, ready to be sent to an FM modulator. S-NET uses the 1k2 configuration, which uses tones at 1200 and 1800Hz, with the lower tone representing the bit 1 and the higher tone representing the bit 0. Note that the tone frequencies are different from the tones at 1200 and 2400Hz of the Bell 202 modem, used in 1k2 AFSK packet radio.

A packet sent by S-NET starts with 24 bits consisting of a repetition of the pattern 01. This is used for clock synchronization in the receiver. After this preamble, the 6 character callsign is transmitted in ASCII. As DK3WN shows, the callsigns for each of the satellites S-NET A, S-NET B, S-NET C and S-NET D are DP0TBB, DP0TBC, DP0TBD and DP0TBE respectively. After the callsign, the 32-bit syncword `0x20F3FA13`

is sent to allow byte-synchronization in the receiver.

There is a quirk in the way that the callsign and syncword are transmitted: each byte is sent least-significant bit first. In this way, the syncword that is really sent over the air is `0x04CF5FC8`

. It is also a bit strange to send the syncword after the callsign, and this almost seems like an afterthought in the protocol. My gr-satellites decoder uses the syncword `0x04CF5FC8`

to detect the start of a packet and ignores the callsign.

After the syncword, the packet header (which is called LTU frame header) is sent. This header consists of 70 bits, but FEC and interleaving is used, so a total of 210 bits are transmitted for the header. For transmission, the 70 bit header is first encoded as 14 BCH(15,5,3) codewords. Then, the codewords are sent interleaved, so the order of transmission is as follows: the first bit of the first codeword, the first bit of the second codeword, ..., the first bit of the 14th codeword, the second bit of the first codeword, etc.

There is also an endianness quirk in how the 70 bit header is split into 14 chunks of 5 bits and systematically encoded into each of the BCH codewords. The 5 last bits of each BCH codeword are used to store each 5 bit chunk of the header. However, the order in which these 5 bits are written is inverted (from right to left). In other words, the first bit of the header is stored in the last bit of the first BCH codeword, the second bit of the header is stored in the second to last bit of the first BCH codeword, and so on.

The 70 bit header is divided in fields according to the following construct `BitStruct`

, which is taken from the gr-satellites decoder.

LTUFrameHeader = BitStruct( 'SrcId' / BitsInteger(7), 'DstId' / BitsInteger(7), 'FrCntTx' / BitsInteger(4), 'FrCntRx' / BitsInteger(4), 'SNR' / BitsInteger(4), 'AiTypeSrc' / BitsInteger(4), 'AiTypeDst' / BitsInteger(4), 'DfcId' / BitsInteger(2), 'Caller' / Flag, 'Arq' / Flag, 'PduTypeId' / Flag, 'BchRq' / Flag, 'Hailing' / Flag, 'UdFl1' / Flag, 'PduLength' / BitsInteger(10), 'CRC13' / BitsInteger(13), 'CRC5' / BitsInteger(5), Padding(2) )

The last 2 bits of padding are actually not included in the header. They are only used because a `BitStruct`

must have a length which is a multiple of 8 bits. The `SrcId`

field identifies the spacecraft and transmitter: 0 is S-NET A transmitter 0, 1 is SNET-A transmitter 1, 2 is S-NET B transmitter 0, etc. The `PduLength`

field indicates the length of the (uncoded) PDU (or payload of the packet) in bytes. The `CRC13`

is a CRC-13BBC of the (uncoded) PDU and the `CRC5`

is a CRC-5ITU of the 70 bit header.

The CRC-5ITU is implemented in a peculiar way. The CRC is computed over the 65 bits comprising the header without the CRC5 field, followed by the sequence 1011011, which is used to pad the data to a multiple of 8 bits. Interestingly, the bytes are processed in reverse (from the last byte to the first byte), and within each byte the most significant bit is processed first. The CRC computation code is as follows.

crc = 0x1F for bit in bits: crc <<= 1 if (crc >> 5) != bit: crc ^= 0x15 # CRC polynomial crc &= 0x1F

I'm not an expert on CRC's, but I don't see how this code implements polynomial division, and it doesn't resemble any of the usual algorithms I know for CRC computation. In particular, note that if `bits`

equals five ones followed by an arbitrary number of zeros, then the resulting CRC is always zero.

There is also a bug in the way that the CRC is implemented in the satellite: input byte number 4 is overwritten with the contents of input byte number 3. The gr-satellites decoder mimics this bug to get the same result. The team has said that they will correct this bug in a future software update.

After the FEC encoded and interleaved header is sent, the PDU is transmitted by blocks. Each block also uses FEC and interleaving and consists of 16 codewords of 15 bits. The interleaving is done in the same way as for the header: first we send the first bit of each codeword, then the second bit of each codeword and so on. The contents of each 15 bit codeword depend on the value of the `AiTypeSrc`

, which indicates the code used for the codewords:

- 0. Uncoded. All the 15 bits represent data.
- 1. BCH(15,11). The last 11 bits represent data.
- 2. BCH(15, 7). The last 7 bits represent data.
- 3. BCH(15, 5). The last 5 bits represent data.

In contrast to the header, the data is written in the usual way (from left to right) in the last bits of each codeword.

Note that since there are 16 codewords by block, each block transmits a whole number of data bytes. However, in the stream of bits that we obtain by concatenating all the last bits in each codeword (according to the value of `AiTypeSrc`

), the bytes are stored in least-significant-bit first format (another endianness quirk).

The complete data is recovered by concatenating the data bytes extracted from each of the blocks. The data is padded with 0xDB bytes at the end to get a whole number of data blocks.

After extracting the data and dropping the 0xDB padding bytes, the CRC13 must be checked. The implementation of CRC13 is similar to the implementation of CRC5 above. However, there is a bug, so the actual code is equivalent to this:

crc = 0x1FFF for bit in bits: crc <<= 1 if crc & 0x2000 or bit: # BUG crc ^= 0x1CF5 # CRC polynomial crc &= 0x1FFF

Currently I haven't implemented any additional processing for the PDU, but there are some details about its format in Mike's webpage.

The only thing that is missing from the gr-satellites decoder right now is BCH codeword decoding. However, the decoder is able to extract the PDU and check CRC's, provided there are no bit errors. I have a working implementation of a BCH decoder, which was sent by Walter Frese, but I've preferred to release the decoder earlier, before I have some spare time to integrate this BCH decoder.

I have decoded a sample recording of S-NET A that Mike sent me. The results are in this gist, and a brief excerpt of this recording can be found in satellite-recordings.

]]>

The most challenging part for processing this VLBI experiment is that the delta-velocity is quite high (around 11km/s). In comparison, in GNSS with MEO satellites, the maximum velocity projected onto the line of sight is around 800m/s. This very high delta-velocity causes several problems. The most important is that the delta-range changes roughly 740m during each of the FFT integrations I was doing (with samples). There are two main consequences of this. First, the measurements need to be time-tagged properly. Even mistaking the time tag by as little as samples causes a large bias. Second, during the correlation via FFT, the correlation peak is moving. This causes the correlation peak to become smeared, making it more difficult to locate the correlation peak accurately. To put things in perspective, 740m is roughly 0.6 samples.

The solution to the first problem is to be careful and time-tag adequately all measurements. In my previous study I had been too careless about it, introducing biases due to improper tagging. Proper time-tagging is done as follows. Note that when we do an FFT correlation, the result of this correlation has to be tagged with the sample in the middle of the FFT interval. In fact, as we have already seen, the correlation peak moves 0.6 samples during the FFT interval. Therefore, the FFT correlation computes the average position of the correlation peak. This corresponds to the position of the peak at the middle of the FFT interval, since the velocity at which the peak travels is almost constant. Since we are doing FFT correlations for the samples , we should time-tag these with the samples .

This time tagging is adequate for the delta-range and phase measurements. However, delta-frequency and delta-velocity are computed by subtracting consecutive phase measurements (discrete differentiation). In other words, the difference between the phase at sample and the phase at sample approximates the frequency at sample (which is the midpoint of and ). Therefore, the delta-frequency and delta-velocity measurements should be time-tagged with the samples .

When doing the time-tags in this manner, we obtain the following residuals against the TLEs.

Compare with the residuals shown in the previous post, where everything was time-tagged with the samples .

We see that the delta-range residual has decreased by roughly 500m. However, the delta-velocity now shows a small linear drift. It is difficult to judge whether this is a problem with the inaccuracy of the TLEs or some additional bias that the signal processing is introducing.

One solution to the second problem (the fact that the correlation peak moves during the correlation interval) is to resample one of the two signals according to the delta-velocity to try to keep the correlation peak in the same place during each correlation interval. In this way, the correlation peak doesn't get smeared and the peak can be located more accurately. The drawback of this approach is that most useful applications of this idea require arbitrary resampling (or interpolation) of one of the signals.

In the algorithm I have designed, I am using sinc interpolation with the second signal (the one corresponding to the Harbin groundstation). I use the phase measurement obtained before to calculate how the Harbin signal should be resampled to keep the correlation peak in place during each correlation interval. This allows longer correlation intervals and makes peak location more accurate. The details of the algorithm are shown in the Jupyter notebook.

Another thing I've noted is that the peak estimation algorithm I used was not adequate. This peak estimation algorithm was used to get subsample resolution in the location of the correlation peak. The idea is that the sample with the largest correlation and its two adjacent samples are sent to the peak estimation algorithm. This then locates the peak by assuming that the shape of the peak is an isosceles triangle. It fits the triangle to the three data points and gets the position of the vertex (fitting is only done implicitly).

This idea is good for GNSS signals, which use rectangular shaping, so the correlation peak is in fact shaped as an isosceles triangle. However, for smooth pulse shaping, such as the Gaussian pulse shaping used in LilacSat-2, the correlation peak is a parabola. Therefore, I have designed a new peak estimation algorithm that fits a parabola through the three data points and gets the position of the vertex. The result obtained with this algorithm is only slightly different to the one that the triangle method gives, but keep in mind that slightly different here can mean a difference of 100 or 200m, since the distance between samples is roughly 1200m.

I have also played with the idea of sending more than three points into the peak estimator, and performing a least-squares fit for a parabola using all the points. However, the results are very similar to those obtained using only three points.

With the resampling algorithm and the parabola peak estimator, the following improved results for the delta-range can be obtained. Here I am using a coherent integration interval of roughly 1 second. The length of the interval doesn't seem to be very critical, since the SNR is already quite good.

Note that the measurement noise is now around 300m. This is a noticeable improvement.

]]>Since then, I have tried to get in contact with the satellite team to see if they could give me any additional information about TY-2 and its companion TY-6 (which uses the same format). Finally, the satellite team have answered me, giving me some details and confirming me that they use the AX100. This has allowed me to finish the decoder. An updated decoder is now available in gr-satellites. Thanks to BI1AEM for his help. Here I look at the specific details of the format used by TY-2.

The important thing to note is that the AX100 supports several modes:

- Raw, which is only intended for BER testing.
- ASM, which uses a 32 bit syncword, followed by a length byte and then the payload. The whole packet is sent G3RUH scrambled, including the syncword. This is the mode used by most satellites.
- HDLC.
- HDLC + Viterbi FEC, which presumably sends the HDLC bits into a CCSDS k=7, r=1/2 convolutional encoder.
- ASM + Golay, which uses a 32 bit syncword, followed by 3 bytes which consist of a length field encoded with a Golay (24,12) code. The payload is optionally scrambled with the CCSDS (synchronous) scrambler.
- HDLC + AX.25

The payload is optionally encoded with the CCSDS (255,223) Reed-Solomon code. This is usually done in most satellites. The syncword used in the ASM and ASM + Golay modes is the same one (`0x930B51DE`

), but note that it is sent G3RUH scrambled in the ASM mode and unscrambled in the ASM + Golay mode. In view of this, it is clear that TY-2 is using the ASM + Golay mode. However, I hadn't read the AX100 manual in detail before, and all the AX100 satellites I had seen used the ASM mode.

After considering the possibility that TY-2 uses the ASM + Golay mode, the rest of the work is trying to decode the Golay encoded length field and then checking if the packet uses Reed-Solomon. Since the older NanoCom U482C transceiver uses a Golay encoded length field, with a little luck, the AX100 would use the same code.

I tried my Golay decoder, which is included in gr-satellites for the U482C, and it worked. After that, I used the CCSDS descrambler with the payload following the Golay field. BI1AEM had told me that valid satellite packets start by `EB 90`

. After descrambling the payload, I obtained a packet starting with a valid CSP header followed by these bytes. An interesting fact is that the U482C includes some flags in the Golay field to indicate whether scrambling, Viterbi and Reed-Solomon are used. The AX100 doesn't seem to use these flags, since none of the corresponding bits are set in the TY-2 packets.

Note that the 3 byte Golay field explains why I wasn't able to descramble the packets before. I tried using the CCSDS descrambler, but since it is synchronous, descrambling fails unless you start descrambling at the correct byte. Since the Golay field is not scrambled, descrambling must start after this field, and I hadn't considered this possibility.

After descrambling, I verified that the packets used the common CCSDS Reed-Solomon code. Since the format used by the AX100 in ASM + Golay mode is almost the same as the one used by the U482C, the U482C decoder block from gr-satellites can be used with the correct settings. Therefore, I have not bothered to include a new block or modify the AX100 block to support this mode.

]]>

VLBI stands for very-long-baseline interferometry and it is a new concept for most Amateurs, since this is the first time that VLBI is used in Amateur radio. Interferometry, in general, involves measuring some property about a radio (or optical) source using two or more receivers in different locations and then comparing the signals recorded at each receiver. The "baseline" refers to the distance (or the line segment) between the two receivers. Here, very-long refers to distances larger than a few kilometres, and usually hundreds or thousands of kilometres. This distinction is important for two reasons: first, using a long baseline gives more accuracy, as we will shortly see; second, interferometry requires synchronization between all receivers, and this has been historically quite difficult to achieve between distant receivers (today it is relatively easy using GPS).

For an example about how VLBI works, let us see the usual example in radio astronomy. Imagine a very distant radio source and two receivers on Earth. The radio wave from the source propagates in wave fronts which are almost planes, perpendicular to the direction of propagation. Therefore, the signal arrives to each receiver at different times. The

difference between the arrival times depends on the direction to the source with respect to the baseline (more precisely, on the angle between the direction to the source and the baseline), and also on the baseline length. In this way, the location of the source on the sky can be measured and tracked. Longer baselines give larger differences of arrival times, allowing a more precise measurement.

The case of LilacSat-2 is different to the situation described above, since it is a LEO satellite, not a very distant object. Therefore, instead of measuring its position in the sky, VLBI measures something called delta-range. This is just the difference of distances between the satellite and each of the groundstations. Another important measurement is delta-velocity. This is just the difference of velocities seen by each of the groundstations, or the time derivative of the delta-range. However, it is considered a different measurement because it is normally computed from phase measurements, rather than using times of arrival, as delta-range does. Using several delta-range or delta-velocity observations, either from different pairs of groundstations or from the same pair at different times, the position and velocity of the satellite can be estimated, and orbit determination can be done.

In fact, the main goal for the Harbin Amateur VLBI program is to perform orbit determination for the future DSLWP lunar mission (which I've talked about in a previous post). Since DSLWP will orbit outside the coverage of NORAD radars normally used for tracking near Earth objects, Amateur orbit determination via VLBI will be important for the mission.

The signal processing I have done with this experiment's recording is described in my Amateur VLBI Jupyter notebook. Most of the techniques I'm using come from GNSS signal processing, since it is quite similar to this kind of VLBI.

The recording was made during a southbound pass of LilacSat-2 over China. It is quite short (around 30 seconds), since the common window when LilacSat-2 is visible from both groundstations is short, due to the long baseline in comparison with LilacSat-2's orbit height.

A simple way to analyse the results of these measurements is to compare them with the values computed by using NORAD TLEs. The difference obtained is called residual, and it should be zero except for measurement errors (both in the VLBI measurements and in the TLEs, which are not very precise ephemeris), noise and so on. My results so far are quite good, as seen in the plots below.

There is a bias in both delta-range and delta-velocity. This is probably partly because of some bias in the signal processing that can be corrected with some effort. Other than that, the measurement noise is really good. The delta-range noise is between 500 and 1000m, and the delta-velocity noise is around 0.25m/s.

The VLBI measurements are done using LilacSat-2's 4k8 GFSK signal at 437.225MHz, which transmits telemetry packets. By doing a correlation of the signal received in each groundstation, the difference in times of arrival, and hence the delta-range can be calculated. A packet from this 4k8 GFSK signal is shown in the image below, taken from a post I wrote back in 2015.

As you can see in the image, the 4k8 GFSK packets have a long preamble and postamble which consists of some periodic bits (which is what produces the spectral lines). This is not good for correlation, since a signal of short period will have closely spaced self-correlation peaks, producing an ambiguity in the delta-range measurement. A good signal for correlation is either aperiodic or has a long period. Usually, a PRN, such as a Gold code is used in GNSS because of this reason. The body of the 4k8 GFSK packet is scrambled with a CCSDS scrambler, so it provides a random-like signal that is good for performing correlation. Therefore, a valid measurement is only done while the body of a 4k8 GFSK packet is transmitted.

Although the signal processing techniques can be similar to those used in GNSS, some of the parameters are really different, giving new challenges for effective processing. The bits of the 4k8 signal are roughly 200 times longer than GPS chips (which have a frequency of 1.023MHz). In principle, this gives 200 times more noise in the delta-range measurement. The velocity of a LEO satellite is roughly 10 larger than that of a MEO GNSS satellite. This makes it more difficult using long integration times for the correlation (at least in open loop, as I'm doing here). The wavelength of the 70cm signal of LilacSat-2 is 3.6 times larger than that of an L-band GNSS satellite, giving 3.6 times more noise in the phase measurements (which is rather good in comparison with the large noise of delta-range measurements). However, since the frequency of the 4k8 GFSK signal is not precise, this gives an error when converting phase measurements to units of meters (this can contribute some bias to the delta-velocity shown above). It would be interesting to know how large is the error in frequency of the 4k8 GFSK signal.

I still have to study carefully the biases I've observed in my calculations, and I also have some ideas to improve my signal processing. However, I've decided to release the calculations I've done so far and write this post now, given the interest that this topic has sparked recently. Stay tuned for a future post with improved computations.

]]>The Outernet LDPC codes follow what I call the "identity scheme". This is different from the staircase and triangle schemes introduced in the RFC. The identity scheme already appeared in the literature, but it did not make it into the RFC. See, for instance, the report by Roca and Neumann Design, Evaluation and Comparison of Four Large Block FEC Codecs, LDPC, LDGM, LDGM Staircase and LDGM Triangle, plus a Reed-Solomon Small Block FEC Codec, especially Section 2, where it is called "LDGM".

I also commented that erasure decoding for an LDPC code (or any other linear code) amounts to solving a linear system. This can be done using any algebraic method, such as Gaussian elimination. However, the simple decoding algorithm used in Outernet is as follows: try to find an equation with only one unknown, solve for that unknown, and repeat until the system is solved. Clearly this algorithm can fail even if the system can be solved (see my previous post for some examples and formal results). I will refer to this algorithm as iterative decoding, as it is done in the RFC.

With these two things in mind, I wondered about the performance of the LDPC codes used in Outernet and the iterative decoding algorithm. I've done some simulations and here I present my results.

The notation I use in this post is the same that I used in the previous post. I won't introduce it again, so I refer the reader to the previous post to consult the notation.

I have done my simulations using Sage, since it provides a simple way of doing linear algebra over the field GF(2). I have used the Python code from free-outernet to generate the parity check matrices according to the RFC. To generate random numbers, I am using the most significant bits of the output of the Lehmer PRNG, as the RFC indicates, not the least significant bits (which are less random), as Outernet does. I guess that this difference doesn't impact the results much.

To perform the simulations, I fix a value of and . They are chosen to yield a rate close to , as Outernet does. Then I generate the parity check matrix for the scheme to be tested (identity, staircase or triangle). Given a probability of erasure , whether each of the symbols is erased or not is given by independent random variables distributed as a Bernoulli with parameter . With this in mind, I generate the list of erased symbols randomly.

The naïve way to do this is, for each symbol, to generate a sample from a Bernoulli distribution with parameter and decide whether the symbol is erased or not. However, this algorithm needs a lot of random samples. There is a smarter way to do it which uses much fewer random samples when is small (which is the case in this kind of simulations). Note that the number of erased symbols is distributed as a Binomial of parameters , . We generate by taking a sample from this distribution. Then, we choose the erased symbols as follows: each of the symbols is chosen randomly from the set (where each choice is equiprobable). If two of the erased symbols are chosen equal, then we choose again for the second one until we obtain a symbol that hasn't been chosen already. These collisions are improbable when is small.

Given the list of erased symbols, we write the linear system as , where is the vector of unknowns corresponding to the erased symbols. Note that the matrix is obtained from by taking the columns corresponding to the erased symbols. As seen in the previous post, we know that the system has a solution. We are interested in whether it has a single solution, since erasure decoding is possible if and only if the solution is unique. This happens if and only if , or equivalently, the . Finding the rank of the matrix over the field GF(2) is very easy to do with Sage.

To check if iterative decoding would succeed with this list of erasures, we look at the matrix again and proceed as follows. We look for a row of where only one entry is . We remove that row and the column where the appeared. We continue in this manner until we get stuck or we obtain a matrix whose entries are all zeros. If we get stuck, iterative decoding is not possible. If we obtain the zero matrix, iterative decoding is possible. Note that this procedure mimics the iterative decoding algorithm, but we don't explicitly solve for the unknowns, since we are not interested in their value.

For each value of , we do trials, generating a list of erasures and checking whether erasure decoding is possible and whether iterative decoding would succeed. With this, we approximate the probability of decoding given an erasure rate . I have simulated the cases , and , , for . These are typical values that one could see used in Outernet. The computations take many hours to run. The results are shown below.

We observe the following. First, the relative performance of the six codes/algorithms studied is very similar in both graphs. Second, for the identity and staircase schemes, iterative decoding is as good as full algebraic decoding. This is a small surprise for me. However, for the triangle scheme, the difference between iterative decoding and algebraic decoding is significant. This is intuitive, since the identity and staircase matrices are rather sparse, but the triangle matrix has many ones.

The triangle scheme gives a much better performance than the other two schemes, but algebraic decoding is needed to take advantage of that improvement. If iterative decoding is done, the performance is actually worse.

Another interesting aspect is how the identity and staircase schemes compare. For high erasure rate , the staircase scheme is better. However, for the identity scheme performs slightly better, and almost as well as the triangle scheme with algebraic decoding.

The Sage/Jupyter notebook used for the simulations in this post can be found here.

]]>In my post I explained that the LDPC code used in Outernet followed RFC5170, and I wondered whether it used the staircase scheme or the triangle scheme. I also commented that erasure decoding with an LDPC code (or any other linear code, actually) was mathematically equivalent to solving a linear system where the unknowns correspond to the erased symbols. I observed that the decoding function looked very different from this mathematical procedure, but should do more or less the same thing. Now I have read the decoder implementation carefully and I have the answer to both questions.

Recall that an LDPC code following RFC5170 is an linear code over a field whose parity check matrix is of the form

where is and is . The matrix is generated pseudorandomly and its entries are 0's and 1's. The algorithm that generates ensures that every row of has at least ones. Also, every column of has at least ones, and usually most columns have exactly ones. Here is a parameter (Outernet uses ). The matrix is

for the staircase scheme and

for the triangle scheme.

Also recall the mathematical interpretation for erasure decoding that I gave in my post. A codeword is transmitted and we receive , where each element is either an unknown if the -th symbol was erased or otherwise. Then the equation is a (non-homogeneous) linear system for the unknowns . Decoding amounts to solving this system. The message can be decoded correctly if the system has a unique solution.

The decoding algorithm by George Hopkins works basically as follows:

- Take the smallest index such that is not an unknown (i.e., the first parity check symbol which was not erased).
- Consider the -th equation of the system . Look at the set of symbols with that appear in this equation with coefficient one. If exactly one of these symbols is unknown, go to step 3. Else, go to step 4.
- Precisely one symbol in is unknown, say, . Compute as . Henceforth, is regarded as a known symbol. Go to step 1.
- Take the next such that is not an unknown and go to step 2.

Decoding fails if at some point you loop over all known symbols , without ever getting into step 3, as the algorithm would then enter an infinite loop without making any further progress. If this does not happen, then all unknowns with are eventually solved by step 3, and so the message gets decoded.

From step 3 in the algorithm we note something quite interesting. The matrix is neither the staircase nor the triangle matrices given above. In fact, is the identity matrix. Indeed, note that only the parity check symbol appears in the equation that we use to compute the unknown . I wonder whether taking is a wise choice, i.e., whether there is any noticeable performance difference between this simpler code and the staircase and triangle codes as described in RFC5170.

The decoding algorithm above is best explained in the following manner. We look at the system . If one of the equations has only one unknown with non-zero coefficient, then we solve for that unknown trivially. By repeating this step, eventually we either solve the system or get stuck. The RFC mentions this algorithm, citing an article by Zyablov and Pinsker.

It is quite clear that this algorithm, while being simple and computationally fast, is not optimal, in the sense that it may fail even if the system has a single solution. For instance, consider a linear system where the matrix is

Then is invertible, but all of its rows have more than one , so the system cannot be solved by this algorithm. A system such as this one can arise when writing the system for one of the codes we are considering here.

Therefore, the LDPC decoder in free-outernet could work better if it used a proper algorithm to solve the system , such as Gaussian elimination. Note that this decoder was written by George by reverse-engineering the closed-source binary in the official receiver, so the official receiver also uses the same oversimplified algorithm.

**Update:** I have just realised that the matrix given above is only invertible when the characteristic of the field is not . Over fields of characteristic , which is the usual case in applications, is not invertible, so the system does not have a single solution. In fact, fact, we can prove the following lemma.

**Lemma.** Assume that is a field of characteristic , the matrix has at most two ones in each row and that . If the system that arises when considering erasure decoding has a single solution, then this solution can be found by the simplified algorithm described above.

**Proof:** We proceed by induction on the number of symbols , , that were erased. For there is nothing to prove. Assume that the lemma is proved for erasures. Let us prove the lemma for . Since , solving the system is equivalent to solving , where , is the vector of known parity check symbols and is the matrix formed by the rows of that correspond to the known parity check symbols. We write this system as , where is the matrix formed by the columns of that correspond to unknown symbols in . The system has a single solution if and only if has rank , since there are unknowns.

Note that is a submatrix of and it has columns. If one of the rows of has exactly one 1, then the simplified algorithm can solve an unknown using this row. We obtain a problem with erasures and we can apply the inductive step. Otherwise, each of the rows of has either zero ones or two ones. Since the characteristic of is , then the sum of the columns of is the zero vector, so the rank of is not and the system does not have a single solution. This proves the lemma.

Note that the lemma is no longer true when there are more than two ones in some of the rows of , as the matrix

(which has determinant ) shows. To which extent the matrix can have at most two ones on each row depends on the pseudorandom generation algorithm. An easy application of the pigeonhole principle shows that if the rate is greater than , then there must be some rows with at least three ones. This is the case for Outernet, which uses a rate of approximately and . ]]>