All the Space Data Link frames in virtual channel 7 have a first header pointer field of 2046, which means “idle data only”. When the payload in these frames is concatenated (there are 8792 payload bits per frame) we obtain an infinite sequence that fits the following description.

The infinite sequence is composed by a repetitions of a 511 bit PN sequence which is obtained from an LFSR with polynomial \(x^9 + x^5 + 1\). The first 9 bits in this PN sequence are ones. Note that these two conditions define the PN sequence uniquely. For the record, the beginning of the sequence is `0xff 0x83 0xdf 0x17 0x32 0x09 0x4e`

(an interesting side note is that this sequence is ubiquitous: it is found everywhere from toilets to interplanetary spacecraft)

Unexpectedly, the repetitions of the sequence are restarted every 2250752 bits, even though 2250752 is not an integer multiple of 511. In other words, to get the \(j\)-th bit in the infinite sequence, we first need to reduce \(j\) modulo 2250752 to get some \(k\) with \(0 \leq k < 2250752\), and then we reduce \(k\) modulo 511 to get an \(l\) with \(0 \leq l < 511\). The \(j\)-the in the infinite sequence is the \(l\)-th bit in the 511 bit PN (whose bits are numbered \(0,1,\ldots,510\)).

Another way to view this reset of the sequence is that after having produced 2250751 bits, the sequence is producing its 2250751-th bit (starting to count by zero), which is the 307-th bit of the 511 bit PN sequence. The 2250752-th bit is not the 308-th bit of the PN sequence, but rather the 0-th bit, and then the sequence continues by the 1-th bit of the PN, etc.

Finding the 2250752 period and checking that the sequence fits this description has been a somewhat convoluted process, with some dead ends. After sorting out all the details, I have updated the Jupyter notebook with some kind of “success story” that shows the steps that worked and can be used an example of how one could go about finding these details without previous knowledge of the structure of the data.

So what is so relevant about the number 2250752, you may ask? I was asking myself the very same question, but finally noted that 2250752 = 256 * 8792. Since 8792 bits is the size of the payload of a Space Data Link frame, this means that the sequence resets every 256 frames. In fact, what happens is that whenever the virtual channel frame counter wraps down to zero (this is an 8 bit counter), the payload of the frame starts again the PN sequence by the beginning, regardless of the fact that the previous frame in this virtual channel didn’t finish the last repetition of the PN sequence.

So why does the PN restart? That’s a good question, as having it looping indefinitely regardless of how it aligns itself inside the Space Data Link frames would perhaps be the most natural choice. It might happen that all the 256 frames are precomputed and stored in memory. This seems a bit strange, since that’s already 275kB of data (or 319kB if you count headers), and the master channel frame count is deemed to change each time that these frames are used, so the CRC, Turbo encoding, etc. can’t be precomputed.

Another good question with a more definite answer is why is it helpful to have a PN sequence in the payload of idle frames, rather than having all zeros, since the TM Space Data Link layer already has a scrambler. This is explained in the TM Synchronization and Coding blue book. The pseudorandomizer for the Space Data Link layer is a 255 bit sequence. If the data is a constant sequence of zeros (or something else with very simple structure), the scrambled symbols will produce spectral lines at 1/255 of the baudrate. This is undesirable, especially for higher baudrates.

By using a 511 bit PN in the payload of idle frames, we manage to combine two scramblers with coprime periods (ignoring for a moment the effects of frame headers and Turbo encoding). The result is that the combined sequence takes very long to repeat, so it doesn’t produce spectral lines. If we consider frame headers and Turbo encoding, the effect is more or less the same.

]]>The Earth flyby will happen in a few days, on 2020-04-10, so currently BepiColombo is quickly approaching Earth at a speed of 4km/s. Yesterday, on 2020-04-04, the spacecraft was 2 million km away from Earth, which is close enough so that Amateur DSN stations can receive the data modulation sidebands. Paul Marsh M0EYT, Jean-Luc Milette and others have been posting their reception reports on Twitter.

Paul sent me a short recording he made on 2020-04-04 at 15:16 UTC at a frequency of 8420.535MHz, so that I could see if it was possible to decode the signal. I’ve successfully decoded the frames, with very few errors. This post is a summary of my decoding.

The X-band signal from BepiColombo is very similar to that of Solar Orbiter that I decoded a couple months ago, so I’ve been able to reuse most of the software. The signal is PCM/PM/Bi-φ modulated at approximately 700kbaud and uses an r=1/2 Turbo code with a block length of 8920 bits. Thus, the only difference between BepiColombo and Solar Orbiter is the baudrate (Solar Orbiter used approximately 555.5kbaud).

The SNR in this recording of BepiColombo is lower than in the Paul’s recording of Solar Orbiter, so my decoder didn’t work: the constellation locked, but it was too noisy for the Turbo decoder to correct all the bit errors. Therefore, I have tried an alternative decoding approach that seems to work better.

The decoder can be seen in the figure below. A PLL is used to track the residual carrier, phase demodulation is done, and the signal is resampled to 10 samples per symbol. After that, there is a FIR filter whose taps are a sequence of five -1’s and five +1’s. This should be thought as a matched filter to the Manchester pulse. At the appropriate sampling points, the taps of the filter are aligned with the Manchester clock and the filter wipes-off the Manchester modulation. After this we run clock recovery with the Gardner algorithm and then detect the 64bit ASM that marks the beginning of Turbo codewords.

I’m not completely sure that it’s impossible for this decoder approach to false-lock at 1/2-symbol offset from the correct symbol clock, but so far it seems to work well.

The GNU Radio decoder flowgraph can be found here. As in the case of the Solar Orbiter decoder, it uses gr-dslwp for Turbo decoding, so the flowgraph is for GNU Radio 3.7, as gr-dswlp hasn’t been ported to 3.8 so far. Note that Turbo decoding is very CPU intensive, so it would be really difficult to run the decoder in real time. When I run it on my i7-2620M laptop, the demodulator goes through the recording pretty fast, but the Turbo decoder stays running for many minutes until it finishes processing all the frames.

The decoder can be seen running in the figure below. The spectrum of the in-phase and quadrature branches of the PLL-locked signal is plotted, showing clearly the phase-modulated data sidebands in the quadrature component. There are many bit errors, but even so the Turbo decoder is capable of correcting all of them.

The analysis of the decoded frames can be seen in this Jupyter notebook and the file with the decoded frames is here in case anyone wants to have a more detailed look.

A total of 1808 frames were decoded from Paul’s 46.5 second recording. Only one of these has incorrect CRC. The frames are TM Space Data Link frames. According to the master channel frame count field, only 8 frames were lost in my decoding process.

Four different virtual channels are used to transmit the data. The relative usage of these can be seen below.

Virtual channel ID 0: 38 frames Virtual channel ID 1: 99 frames Virtual channel ID 2: 39 frames Virtual channel ID 7: 1631 frames

The contents of the virtual channels can be seen below. Each frame is plotted as one row of the graph, with colour-coded bytes. It can be seen that the data on each virtual channel is rather different. Virtual channel 2 is perhaps the most interesting, since it seems to have very long sequences of zeros. Virtual channel 7, where the bulk of the data is sent, shows some repetitive structure that doesn’t match the frame size.

I haven’t looked at the data in any more detail yet. What I’ve been curious about are the peaks that can be seen in the quadrature branch near the carrier. In fact, there are several peaks, as it can be seen in the figure below, which shows a close-in spectrum of the PLL-locked signal.

I’ve measured the frequency of these peaks to be 12490Hz, 22088Hz, 24981Hz, and 37417Hz, accurate to 1Hz or so. The last two are then 2nd and 3rd harmonics of 12490Hz. The 12490Hz and 22088Hz tones seem to be related by the fraction 4016/2271. This numerology is perhaps too much of a guess, but since 4016 and 2271 are coprime, it supports the idea that these are ranging tones. Indeed, 12490Hz/2271 is quite close to 5.5Hz (the error is 40ppm, which could be explained by Doppler and clock drift).

So my guess is that the 2271th and 4016th harmonics of a 5.5Hz clock are used for ranging. The ranging ambiguity is given by the 5.5Hz clock, and is approximately 5.5 millions of km. Regarding ranging performance, with a loop SNR of 20dB on the 22088Hz tone (which perhaps is not possible to achieve with this recording, but certainly it is not too far off), the tracking jitter of this tone would be 0.1rad RMS, which is equivalent to 216m, so this is actually not bad at all. However note that resolving the integer ambiguities would require averaging the phase jitter on the 22088Hz tone down to significantly less than 1mrad, so the SNR needed for unambiguous ranging is certainly much more than what is present in this recording.

When researching these tones I also measured the baudrate accurately by using cyclostationary analysis. It turns out that it is 699070.5 baud (accurate to around 1 baud), so I don’t think that the baudrate is related to the frequencies of these ranging tones.

]]>The goal of this post is to discuss where the corrections arise from, the typical approximations that can be made, and how these corrections affects the calculation of range and range-rate. I didn’t find a good source in the literature where this is described in detail and in a self-contained way, so I decided to write it myself.

Before beginning, let us fix the notation. We consider an ECEF frame and an ECI frame so that the rotation matrix that takes ECEF coordinates into ECI coordinates at time \(t\) is given by \(e^{A\omega_Et}\), where \(\omega_E = 7.292115\cdot10^{-5}\,\mathrm{rad}/\mathrm{s}\) denotes Earth’s angular rate and \[A = \begin{pmatrix}0 & -1 & 0\\ 1 & 0 & 0\\ 0 & 0 & 0\end{pmatrix}.\]Note that this ignores all considerations about the irregularities of the rotation of Earth in space. We have also arranged the origin of the time coordinate conveniently so that at \(t = 0\) the axes of the ECEF frame coincide with those of the ECI frame.

We fix some time \(t\) and denote by \(x_R\) the position of the receiver in ECEF coordinates at time \(t\). Given a satellite, the *range* \(\rho\) of that satellite is defined by the fact that the time that the signal of the satellite takes to arrive to the receiver is \(\tau = \rho/c\). Now, this implies that the signal was transmitted by the satellite at time \(t – \tau\), so we denote by \(x_S\) the position of the satellite in ECEF coordinates at time \(t – \tau\).

The fact that light doesn’t travel in straight lines in ECEF coordinates implies that, in general, \(\rho\) is not equal to \(\|x_R – x_S\|\). To compute \(\rho\) we need to convert to ECI coordinates. The position of the receiver at time \(t\) in ECI coordinates is \(e^{A\omega_E t}x_R\), and the position of the satellite at time \(t-\tau\) in ECI coordinates is \(e^{A\omega_E(t-\tau)}x_S\). Since light travels in straight lines in ECI coordinates, we have\[\tag{1}\rho = \|e^{A\omega_Et}x_R – e^{A\omega_E(t-\tau)}x_S\|.\]This equation is the basis of all the correction formulas and can be found as equation (E.3.8) in RTKLIB’s manual.

We put \(\sigma = \omega_E\tau\). Since \(e^{A\omega_E t}\) is an isometry, we see that\[\tag{2}\rho = \|x_R – x_S – (e^{-A\sigma} – I)x_S\|.\]We want to do an approximation so that we can write the right hand side as \(\|x_R – x_S\|\) plus some correction. There are several ways in which this could be done, but the main idea is to do an approximation of first order in the parameter \(\sigma\), which is small. Indeed, \(\tau\) is on the order of 80 ms for MEO satellites and receivers near the surface of the Earth, so \(\sigma\) is on the order of \(6\cdot 10^{-6}\). Since \(x_R\) and \(x_S\) are on the order of 6000km and 26000km respectively, terms on the order of \(\sigma x_R\) and \(\sigma x_S\) are still metric level, which justifies the need for this correction. Terms on the order of \(\sigma^2 x_R\) and \(\sigma^2 x_S\) are on the order of 1mm or less, and can be safely ignored in most applications.

We can use the first order approximation of the exponential, \[e^{-A\sigma} – I \approx -\sigma A,\] to get\[\tag{3}\rho \approx \left\|x_R – x_S + \sigma Ax_s\right\|.\]

For vectors \(u,v\) on a real Hilbert space and a small parameter \(s\), it holds that\[\|u + s v\| = \|u\| + \frac{\langle u, v\rangle}{\|u\|}s + O(s^2).\]Applying this to (3) we get\[\tag{4}\rho \approx \|x_R – x_S\| + \frac{\sigma}{\|x_R-x_S\|} \langle x_R – x_S, Ax_S\rangle.\]It is also possible (and perhaps formally more correct) to get directly from (2) to (4) by computing the derivative of the right hand side of (2) with respect to \(\sigma\).

We know from (2) that \(\|x_R – x_S\| = \rho + O(\sigma)\), so\[\frac{\sigma}{\|x_R – x_S\|} = \frac{\sigma}{\rho} + O(\sigma^2),\] and, for an approximation of first order in \(\sigma\) it is acceptable to replace \(\sigma/\|x_R – x_S\|\) by \(\sigma/\rho = \omega_E/c\) in (4). Additionally, since \(A^T = -A\), we have \(\langle x_S, A x_S \rangle = 0\). Thus, we arrive to\[\tag{5}\rho \approx \|x_R – x_S\| + \frac{\omega_E}{c} \langle x_R, A x_S \rangle.\]This is the usual form of the Earth rotation correction for the range, and indeed can be found as (E.3.8b) in RTKLIB’s manual. To see this, write \(x_R = (x^R, y^R, z^R)^T\), \(x_S = (x^S, y^S, z^S)^T\), so that the correction is\[\tag{6}\frac{\omega_E}{c}\langle x_R, A x_S \rangle = \frac{\omega_E}{c}(y^Rx^S – x^Ry^S).\]

For the *range-rate* \(\rho’\), which is the time derivative of \(\rho\), there are several ways to obtain an approximate Earth rotation correction. The most straightforward is to compute the time derivative of (5), obtaining\[\tag{6}\rho’ \approx \langle x_R’ – x_S’, E \rangle+ \frac{\omega_E}{c} \left( \langle x’_R, A x_S \rangle + \langle x_R, A x’_S\rangle\right),\]where \(E\) denotes the line of sight vector\[E = \frac{x_R-x_S}{\|x_R – x_S\|}.\]Note that this vector is not the direction in which the signal arrives to the receiver (more on this later).

The interpretation of the first summand in the right hand side of (6) is as the projection of the relative velocity vector (in ECEF coordinates) onto \(E\). The second summand in the right hand side of (6) is the approximate Earth rotation correction. Written in coordinates it is\[\tag{7}\frac{\omega_E}{c}(y’^Rx^S – x’^Ry^S + y^Rx’^S – x^Ry’^S).\]This expression appears in (F.6.29) (note that this is a typo and should be (E.6.29)) in RTKLIB’s manual, but it seems to me that the correction used by RTKLIB has the wrong sign!

There is an alternative way to obtain (6) which perhaps makes it more clear that (6) is just an approximation of \(\rho’\) of first order in \(\sigma\). In this manner, we start again with (1) and define the vector \(N\) by\[N = \frac{x_R – e^{-A\sigma}x_S}{\|x_R – e^{-A\sigma}x_S\|}.\]Note that \(N\) is indeed a unit vector in the direction in which the signal arrives to the receiver in ECEF coordinates, since \(e^{A\omega_E t}N\) is a unit vector in the direction of the straight line in which the signal travels in ECI coordinates.

We compute the derivative of (1) as\[\begin{split}\rho’ &= \langle e^{A\omega_E t} x’_R – e^{A\omega_E(t-\tau)}x’_S, e^{A\omega_E t} N\rangle \\ &\quad + \omega_E \langle A (e^{A\omega_E t} x_R – (1-\tau’)e^{A\omega_E(t-\tau)} x_S), e^{A\omega_E t}N\rangle\\ &= \langle e^{A\omega_E t} x’_R – e^{A\omega_E (t-\tau)}x’_S, e^{A\omega_E t} N\rangle \\ &\quad + \omega_E \langle A (e^{A\omega_E t} x_R – e^{A\omega_E (t-\tau)} x_S), e^{A\omega_E t}N\rangle\ \\ &\quad + \omega_E\tau’\langle Ae^{A\omega_E(t-\tau)}x_s, e^{A\omega_E t}N \rangle.\end{split} \]Now the second summand in the last equality is zero because\[e^{A\omega_E t} x_R – e^{A\omega_E (t-\tau)} x_S = \|x_R – e^{-A\sigma} x_S\|e^{A\omega_E t} N\]and \(\langle A u, u\rangle = 0\) for all vectors \(u\). Since \(e^{A\omega_E t}\) is an isometry, we get\[\tag{8}\rho’ = \langle x’_R – e^{-A\sigma} x’_S, N\rangle + \tau’\omega_E \langle A e^{-A\sigma}x_S, N\rangle,\]which is an exact expression for the range-rate \(\rho’\).

The first summand in the right hand side of (8) can be split as\[\tag{9}\langle x’_R – e^{-A\sigma} x’_S, N\rangle = \langle x’_R – x’_S, N\rangle – \langle (e^{-A\sigma}-I)x’_S, N\rangle.\]Since we want to replace \(N\) by \(E\) in these expressions, let us compute an approximate formula for \(N-E\). First note that \(E\) can be obtained by setting \(\sigma = 0\) in the expression for \(N\). Therefore,\[N – E = \sigma \left.\frac{d}{d\sigma}\right|_{\sigma=0}\frac{x_R – e^{-A\sigma}x_S}{\|x_R-e^{-A\sigma}x_S\|} + O(\sigma^2).\]After some straightforward calculations, this yields\[\tag{10}N – E = \frac{\sigma}{\|x_R-x_S\|}(Ax_S – \langle Ax_S, E\rangle E) + O(\sigma^2).\]

We can apply this result to the first summand in the right hand side of (9) to yield the approximation of first order in \(\sigma\)\[\begin{split}\langle x’_R – x’_S, N\rangle \approx &\left( 1 – \frac{\sigma \langle Ax_S, E\rangle}{\|x_R-x_S\|}\right) \langle x’_R – x’_S, E \rangle \\ &+ \frac{\sigma}{\|x_R-x_S\|}\langle x’_R – x’_S, Ax_S\rangle.\end{split}\tag{11}\]

For the second summand in the right hand side of (9) we do a first order approximation of the exponential:\[-\langle (e^{-A\sigma}-I)x’_S, N\rangle \approx \sigma \langle Ax’_S, N\rangle.\]Since this is already a term of order one in \(\sigma\), we may replace \(N\) by \(E\) directly, obtaining\[\sigma\langle Ax’_S, N\rangle \approx \frac{\sigma}{\|x_R-x_S\|} \langle Ax’_S, x_R-x_S \rangle.\]

Putting together this last result with (9) and (11), and using \(A = -A^T\) we get\[\begin{split}\langle x’_R – e^{-A\sigma}x’_S, N \rangle \approx &\left( 1 – \frac{\sigma \langle Ax_S, E\rangle}{\|x_R-x_S\|}\right)\langle x’_R-x’_S, E\rangle \\ &+ \frac{\sigma}{\|x_R-x_S\|}(\langle x’_R, Ax_S\rangle + \langle Ax’_S, x_R\rangle).\end{split}\tag{12}\]

Now we need to take care of the second summand in the right hand side of (8), which is somewhat more delicate. Since \(\tau’ = \rho’ \tau / \rho\), we can write\[\tau’ \omega_E\langle A e^{-A\sigma}x_S, N \rangle = \rho’ \sigma \left[\frac{1}{\rho}\langle A e^{-A\sigma}x_S, N \rangle\right].\]Let us write \(C_2\) for the term in square brackets in the equation above and let us write the right hand side of (12) as \(\langle x’_R-x’_S, E\rangle + \sigma C_1\), so that\[\rho’ = \langle x’_R-x’_S,E\rangle + \sigma C_1 + \rho’ \sigma C_2 + O(\sigma^2)\]according to (8). Applying this equality to the right hand side again we get\[\begin{split}\rho’ = &\langle x’_R-x’_S,E\rangle + \sigma C_1 + \sigma C_2 \langle x’_R-x’_S,E\rangle \\ &+ \sigma^2 C_1C_2 + \rho’\sigma^2C_2^2 + C_2O(\sigma^3) + O(\sigma^2).\end{split}\]

Let us examine the order of magnitude of the summands in the right hand side of this equality. The term \(C_1\) has an order of magnitude bounded by that of \(x’_R\) and \(x’_S\). The term \(C_2\) is smaller: it can be bounded by a numeric constant (probably 3 is sufficient as this constant). Therefore, the terms \(\sigma C_1\) and \(\sigma C_2 \langle x’_R-x’_S, E \rangle\) can be grouped together as our correction of first order in \(\sigma\). All the terms in the lower row, can be subsummed under the \(O(\sigma^2)\). Hence,\[\rho’ = \langle x’_R-x’_S,E\rangle + \sigma C_1 + \sigma C_2 \langle x’_R-x’_S,E\rangle + O(\sigma^2).\]

Since the term \(C_2\) is multiplied by \(\sigma\), we can replace it by an approximation of order zero in \(\sigma\). To this end, \(\rho\) can be replaced by \(\|x_R – x_S\|\), the exponential \(e^{-A\sigma}\) can be replaced by the identity matrix by setting \(\sigma = 0\), and \(N\) can be replaced by \(E\), obtaining\[C_2 = \frac{1}{\rho}\langle A e^{-A\sigma}x_S, N \rangle \approx \frac{\langle A x_S, E\rangle}{\|x_R-x_S\|},\]so that we get the approximation\[\begin{split}\tau’ \omega_E \langle A e^{-A\sigma}x_S, N \rangle &= \rho’\sigma C_2 \\ &= \sigma \langle x’_R – x’_S, E\rangle C_2 + O(\sigma^2)\\ &\approx \frac{\sigma\langle A x_S, E\rangle}{\|x_R-x_S\|}\langle x’_R – x’_S, E\rangle.\end{split}\tag{13}\]Note that this last expression also appears in (12) with the opposite sign, so they will cancel out when (12) and (13) are added.

Indeed, using (8) to add (12) and (13), we get\[\rho’ \approx \langle x’_R-x’_S, E\rangle + \frac{\sigma}{\|x_R-x_S\|}(\langle x’_R, A x_S\rangle + \langle Ax’_S, x_R\rangle).\]Replacing \(\sigma/\|x_R-x_S\|\) by \(\omega_E/c\) as we did in the derivation of the approximation for the range correction, we get equation (6), as we wanted to show.

It is interesting to note that all the correction terms are of the form \(\omega_E\langle A u, v\rangle/c\). This can be interpreted geometrically as\[\tag{14}\langle A u, v\rangle = 2 \operatorname{area}(P_z u, P_z v),\]where \(P_z\) denotes the orthogonal projection onto the plane \(z = 0\) and \(\operatorname{area}(u,v)\) is the signed area of the triangle which has a vertex at zero and sides given by the vectors \(u\) and \(v\). The sign of the area is chosen as the orientation of the basis \(\{u,v\}\) (i.e., as the sign of the determinant of \(u\) and \(v\)). In fact, the easiest way to check (14) is to note that\[\langle A u, v\rangle = \operatorname{det}(P_z u, P_z v).\]

This geometric interpretation can be used to estimate the order of magnitude of the corrections in different geometric configurations. For the range, the correction is\[\frac{\omega_E}{c}\langle Ax_S, x_R\rangle.\]For a receiver on the Earth’s surface, this term is largest when the receiver is on the equator and the satellite is in the equatorial plane and seen at an elevation of 0º by the receiver. In this case, the vectors \(x_R\) and \(x_R-x_S\) are orthogonal.

Since we can also write this correction as\[\frac{\omega_E}{c}\langle A(x_S-x_R), x_R\rangle\]due to the fact that \(A^T = -A\), in this case the absolute value of the correction works out to be\[\frac{\omega_E}{c}\|x_S-x_R\|\|x_R\| = \frac{\omega_E}{c}R_E\sqrt{a^2 – R_E^2},\]where \(a\) is the orbital radius of the satellite (we assume a circular orbit) and \(R_E\) is the Earth’s radius. For a GPS-like MEO orbit with a period of half a sidereal day, the correction amounts to 40 metres. Hence, we see that it is critical to perform this kind of correction when doing most GNSS calculations involving the range.

For the range-rate, we have two correction terms,\[\frac{\omega_E}{c}\langle Ax_S, x’_R\rangle,\quad\mathrm{and}\quad \frac{\omega_E}{c}\langle A x’_S, x_R\rangle.\]The first term depends on the receiver velocity, but it is usually much smaller than the second one. For a GPS-like MEO orbit and a receiver at the vicinity of the Earth’s surface, \(\|x_S\|\) is approximately 4 times larger than \(\|x_R\|\). However, \(\|x’_S\| = 3874\,\mathrm{m/s}\), so in most situations \(\|x’_R\|\) is much smaller than \(\|x’_S\|\).

Regarding the second term, for a receiver on the Earth’s surface, this correction is largest when the receiver is on the equator, the satellite has an orbital inclination of 0º (I reckon that most GNSS constellations have inclinations around 50º, but I use this example for simplicity) and the satellite is at the zenith of the receiver. In this situation \(x_R\) and \(x’_S\) form right angles, so the correction amounts to\[\frac{\omega_E}{c}\|x’_S\|\|x_E\| = \frac{\omega_E}{c}R_E\sqrt{\frac{\mu}{a}},\]where \(\mu = GM_E\) is Earth’s gravitational parameter. For a MEO orbit with a period of half a sidereal day, this works out to be 6mm/s, so it is a very small correction. Therefore, the Earth rotation correction can be ignored in most GNSS calculations involving the range-rate.

]]>The 1024 bit frames are shown in the figure below, with one frame per line. The turquoise portion has been inserted to mark clearly the separation between frames before the eclipse and after the eclipse, and to align the two sequences. I haven’t tried to guess where the frames begin, so choice of the first bit of the frames is arbitrary.

We can see that there is little variation in some parts of the frame, while others change more. In the Jupyter notebook I have also split the frames in 128 bit segments in order to see clearly each individual bit.

In the document we have, in page 56 it is stated that “The basic sampling interval for LES-5 telemetry is 10.24sec”. Note that this coincides with a length of a 1024 bit frame. Therefore, I have the impression that most of the contents of the frame are real-time telemetry values from sensors, measured every 10.24 seconds.

I have done some guesswork to try to spot some fields that might make sense when interpreted as integer values and plotted against time. Of course I have no clue to what any of these really are.

The most promising are bits 191 to 198 and 703 to 707, which show sawtooth waves of different frequencies.

Another fields that have caught my eye are the following two. Bits 49 to 54 show an almost constant value just before and after the eclipse. However, ten minutes after the eclipse the value jumps up and decreases slowly.

Bits 865 to 870 have a nearly constant value before the eclipse. Just after the spacecraft comes out of the eclipse, they jump down and keep decreasing slowly.

As stated above, I don’t have any clue what any of these fields represent. The eclipse affects almost all spacecraft parameters directly or indirectly, from solar voltages to temperatures. Therefore, it is natural to see its effect on most of the telemetry channels. I’m somewhat fascinated by the sawtooth waves. They don’t seem to represent any sort of frame counter. Maybe they’re some kind of angular parameter?

]]>I have used this recording made by Scott in 2020-01-13. The GNU Radio demodulator, which is very similar to the one for LES-5, is here and the Jupyter notebook with the results is here. Below, I make a brief summary of the results.

This is the demodulator running on the signal. The SNR is somewhat better than on the LES-5 signal, and the symbols are very well separated in the constellation, so most likely there are no bit errors.

The symbols demodulated from the recording are shown below. There are slightly more than 60000 symbols, corresponding to a recording somewhat longer than 10 minutes.

As in the case of LES-5, the data is differentially encoded. However, it seems that LES-9 uses NRZ-I encoding (a 0 is encoded as a change in the symbol, a 1 is encoded as no change in the symbol) rather than differential encoding as LES-5. I have arrived to this conclusion because I think it is more likely for the data to have long strings of zeros rather than long strings of ones. Thus, I’ve performed NRZ-I decoding before analysing the bitstream any further

Below I show the autocorrelation for small lags. Interestingly, there is a strong autocorrelation at 16 bits of lag and multiples.

The figure below shows the full autocorrelation. The strong peak at a lag of 6400 bits shows that the frame size is 6400 bits, in contrast with the LES-5 size of 1024 bits.

The frames are shown below. I have laid the bitstream in rows of 6400 bits, making no attempt to find the start of the frames. As we can see, there is a lot of repeating structure. The frames look somewhat similar to the LES-5 frames I demodulated in my previous post.

The figure below shows the spectrum of a BPSK signal constructed from the demodulated symbols. This is clearly different from the sinc-like spectrum of a BPSK with random data, indicating the presence of rich structure in the data. For example, the strong peaks near +/-50Hz are most likely caused by the long strings of zeros, which under NRZ-I encoding become 0101010…

]]>A couple days ago, Scott Tilley VE7TIL discovered that LES-5 was still transmitting, and was able to receive its beacon at 236.749MHz. Scott reports that LES-5 is the oldest GEO-belt object that he knows to be still transmitting.

The beacon is modulated, rather than being a CW carrier, so Scott sent me a short recording for analysis. This post is a summary of my study.

There is an interesting document that describes some measurements regarding the in-orbit performance of the LES-5 transponder. This document describes an 800 baud BPSK beacon signal at 228.43MHz and a 100 baud BPSK telemetry system at 236.7MHz, which is what Scott received.

I have made a simple GNU Radio demodulator that outputs the BPSK symbols to a file for later analysis. You can see the decoder running in the figure below. The SNR is quite good and the BPSK constellation locks cleanly, with no or very few bit errors. It is noteworthy the narrow peak in the middle of the BPSK spectrum: more on this later.

This Jupyter notebook is then used to look at the BPSK symbols. As shown below, we have collected slightly more than 10000 symbols from the short recording.

When doing blind analysis of a bit stream, it is often helpful to look at the autocorrelation, to try to detect any possible structure. The figure below shows the autocorrelation for lags between 1 and 100 bits. We see that for lags between 1 and 6 bits the autocorrelation is high. This means that the probability that consecutive symbols are equal is larger than 1/2.

The autocorrelation shows peaks at lags which are integer multiples of 1024. The peak at 1024 is much smaller than the peak at 2048. However, a look at the bitstream shows that there is indeed a repeating structure every 1024 bits.

The figure below shows the bitstream laid out in rows of 1024 bits. The repetitive structure can be clearly seen. I have arranged things so that the largest section that repeats in all frames is at the left. This may or may not be the start of the frame.

Below we show which bits are equal in all of the frames we have collected.

The figures above suggest that the data should be interpreted as being differentially encoded. There are sections which are similar in adjacent frames, except that the 0’s have being exchanged by 1’s or vice-versa. The figure below shows the differentially-decoded bitstream. Now we see that there are relatively few changes between each of the frames. This is reasonable if each frame just contains a series of real-time values from sensors, as they won’t change much from one frame to the next one.

As we can see below, most of the bits coincide in all of the 10 frames collected.

Regarding the narrow peak that we have noticed in the middle of the spectrum, if we re-encode the bitstream as BPSK and compute its spectrum, we obtain the figure below, where the same narrow peak is also present. Therefore, the reason for this peak is really the structure of the data. It is mainly caused by the tendency of adjacent bits to coincide, which in turn is caused by the many zeros in the differentially encoded data.

With only this short recording it is difficult to conclude anything else regarding the data. It would be interesting to record the signal for an extended period of time (several hours) and then study the data.

So far, my impression is that the data is valid, so at least a good part of the onboard computer is working. It would be very interesting to decode it, as probably it can show us something about the spacecraft’s health. However, this might not be so easy, as the documentation from this very old satellite might be long gone.

]]>Roughly speaking, the conversation shifted from noting that FT8 is not so efficient in terms of EbN0 to the idea of using something like coherent BPSK with \(r=1/6\) CCSDS Turbo code, then to observing that maybe there was not enough SNR for a Costas loop to work, so a residual carrier should be used, and eventually to asking whether a residual carrier would work at all.

There are several different problems that can be framed in this context. For me, the most interesting and difficult one is how to transmit some data with the least CN0 possible. In an ideal world, you can always manage to transmit a weaker signal just by transmitting slower (thus maintaining the Eb/N0 constant). In the real world, however, there are some time-varying physical parameters of the signal that the receiver needs to track (be it phase, frequency, clock synchronization, etc.). In order to detect and track these parameters, some minimum signal power is needed at the receiver.

This means that, in practice, depending on the physical channel in question, there is a lower CN0 limit at which communication on that channel can be achieved. In many situations, designing a system that tries to approach to that limit is a hard and interesting question.

Another problem that can be posed is how to transmit some data with the least Eb/N0 possible, thus approaching the Shannon capacity of the channel. However, the people doing DVB-S2 over the wideband transponder are not doing it so bad at all in this respect. Indeed, by transmitting faster (and increasing power, to keep the Eb/N0 reasonable), the frequency drift problems become completely manageable.

In any case, if we’re going to discuss about these questions, it is important to characterize the typical frequency drift of signals through the QO-100 transponder. This post contains some brief experiments about this.

To measure the typical frequency drift on the QO-100 transponder, I have decided to record the lower CW beacon of the NB transponder using my groundstation. The beacon is transmitted at Bochum using a very stable Z3801A GPSDO as a reference. My station is currently using the DF9NP GPSDO that I measured here. This GPSDO is a VCTCXO disciplined by a uBlox GPS receiver, so its short-time performance is typical of a TCXO.

I reckon that results perhaps an order of magnitude better might be obtained with a good OCXO-based GPSDO, such as the Vectron I used here, but I wouldn’t like to devise a communications system that requires an extremely stable clock. Certainly, FT8 and other modes work well with the DF9NP GPSDO, so I think it’s best to use it instead of the Vectron for this experiment.

The CW beacon transmits a continuous carrier with a duration of 12 seconds after each message. I have made a short recording of the beacon that includes three of these carriers. The recording can be downloaded here at 1ksps in `complex64`

format. The figure below shows a waterfall of the recording in inspectrum. The frequency “wiggle” is approximately 10Hz.

I have selected 11 seconds of each of the occurrences of the carrier and processed them in this Jupyter notebook to measure the coherence time. The technique I have used is based on the following method for finding the coherence time of a carrier at baseband. If \(\{x_k\}\), \(0 \leq k < L\) is the discrete time baseband complex-valued representation of that carrier, for each decomposition \(L = MN\) we define\[P_N = \frac{1}{M}\sum_{l = 0}^{M-1} \left|\frac{1}{N}\sum_{k=0}^N x_{Nl + k}\right|^2\]as the average power after coherent accumulations of \(N\) samples are done. By plotting \(P_N\) we can find a point at which \(P_N\) starts decreasing with increasing \(N\). This indicates the time length at which coherence is lost.

Since the CW carrier is not at baseband in this recording, but rather at a frequency around -50Hz, I first detect the average frequency of each carrier using an FFT and then shift the carrier down to baseband using that frequency. The figure below shows the coherence time, as described above. Each of the traces represents one of the three carriers. The green one is the last one, which is clearly seen to drift more in the waterfall.

A serious version of this experiment would use a much longer recording and average the results over many occurrences of the 12 second carrier, rather than examining only three occurrences. However, for a ballpark estimate I think this is enough and we may say that the coherence time for a typical TCXO-based station is on the order of 50 to 100ms.

The second part of the experiment is to try to lock a PLL on the carrier at different levels of CN0. The carrier has been recorded with a high CN0, so lower values of CN0 can be simulated by adding white Gaussian noise. I have selected the last occurrence of carrier, as it is the worst one, and play it in a continuous loop in GNU Radio using this flowgraph. Interestingly, the phase and frequency discontinuity at the looping point doesn’t seem to give any problems with the PLL.

I have scaled the signal so that the carrier has amplitude one. This makes it easy to add noise to set the desired CN0. The signal is lowpass filtered to an adjustable bandwidth and locked with a PLL of adjustable bandwidth.

To aid in examining the performance of the PLL, I use a noise-free version of the signal and use the phase of the PLL running on the noisy signal to lock the noise-free signal. This shows the PLL jitter very well.

The PLL simulation can be seen in the figure below. For a high CN0 and relatively high PLL bandwidth of 5Hz, the PLL jitter (as seen on the noise-free signal) is low. The original signal (in blue in the frequency plot) looks spread in frequency, while the output of the PLL is locked.

If we reduce the CN0 to 20dB, the PLL jitter is considerable, as it can be seen both on the PLL frequency output and in the constellation of the noise-free signal. To judge the effect of the PLL jitter, I average coherently the noise-free signal (after using the output of the PLL to lock it) and compute its power. In this case it is 0.8, so we’ve lost 20% signal power because of the PLL jitter.

To try to improve things, we can reduce the bandwidth of the lowpass filter to 10Hz (anything lower will cut the signal sometimes, as it drifts outside of the passband) and reduce the PLL bandwidth to 1 or 0.5Hz. This results in the figure below, in which the situation improves and we get a signal power of 0.9.

However, reducing the bandwidth to 0.5Hz is as low as we can get. Indeed, if we increase the CN0 maintaining the bandwidth parameters, we get the figure below, which shows approximately the same PLL jitter. This means that the jitter is not caused by thermal noise due to low SNR, but rather by loop stress due to the low loop bandwidth.

If we keep a high CN0 but reduce the loop bandwidth further to 0.3Hz, we get bad results, as shown below. Reducing the bandwidth further will make the loop lose lock completely.

However, 20dB CN0 is still very high for what we have in mind. To put things into perspective, in the AMSAT-BR experiment we were playing with FT8 signals at -22dB SNR in 2.5kHz. This is a CN0 of 12dB. If we want to do somewhat better than FT8 with a residual carrier signal, we should think of a carrier power of 10dB CN0 or less, as we still need some power for the data.

The results at 10dB CN0 with a loop bandwidth of 0.5Hz can be seen below. The loop loses lock sometimes and the jitter is really bad. The signal power is around 0.6. So it seems that this is as far as we can push the system.

Phil Karn mentioned the article Residual Versus Suppressed-Carrier Coherent Communications. This paper contains some useful formulas regarding when to choose a suppressed carrier system or a residual carrier one, what is the optimal power fraction to allocate for the residual carrier, and when can a PLL or Costas loop maintain lock.

The rule for PLL lock is that the loop SNR \(\rho\) should be greater than 7dB, where\[\rho = \frac{C}{N_0 B_L}.\]Here \(B_L\) denotes the Loop bandwidth in Hz. This agrees with my experimental results. For \(B_L = 0.5\mathrm{Hz}\) we have \(\rho = 7\mathrm{dB}\) when CN0 = 10dB.

With these results in mind, I think I can justify as rather challenging the idea of trying to transmit data at very low CN0 through the QO-100 transponder. To keep things reasonable and concrete, let’s assume that we want to transmit at more or less the same net bitrate as FT8, which is 7.2 bits per second.

A -22dB in 2.5kHz SNR FT8 signal has an Eb/N0 of 3.4dB. It is perhaps arguable whether the signals estimated by WSJT-X to have -22dB SNR really have that SNR or if the real SNR is somewhat higher. However, in any case, by trying to use a better modulation and FEC, one may try to go down to 1dB Eb/N0, or even less.

A 1dB Eb/N0 signal at 7.2 bits per second has a CN0 of 9.6dB. Now this is the challenge. Clearly a residual carrier approach is not going to work, and suppressed carrier coherent BPSK won’t work either.

]]>A few weeks ago I was working with Julien Nicolas F4HVX to try to decode some of the images transmitted by AMICal Sat. Julien is an Amateur radio operator and he is helping the satellite team at Grenoble with the communications of the satellite.

This post is an account of our progress so far.

AMICal Sat has a UHF transmitter for the 70cm Amateur satellite band and an S-band transmitter for the 2.4GHz Amateur satellite band. The UHF transmitter uses 9k6 FSK and can transmit images using a protocol similar to the one used by Światowid. The S-band transmitter is a high-speed transmitter, so it is desirable to use it as the main transmitter for the high resolution scientific images taken by the payload, leaving the UHF transmitter as a backup. Julien and I have been working with the S-band transmitter, so in this post I won’t speak about the UHF transmitter.

The S-band transmitter is based on a Nordic Semiconductor nRF24L01+ 2.4GHz FSK transceiver chip (see the datasheet here). The images are sent in 1Mbaud GFSK Shockburst packets. The structure of a Shockburst packet can be seen in the figure below, taken from the nRF24L01+ datasheet.

AMICal Sat uses the 5 byte address `0x7e7e7e7e7e`

, a 32 byte payload, and a 2 byte CRC. The CRC used by ShockBurst is the one called CRC_CCITT_FALSE in this online calculator, and covers both the address and the payload. The payload of each packet consists of a chunk counter, which is a 16-bit little endian integer, followed by a 30 byte chunk of the image file.

The image file has a 512 byte header followed by the image data. The header is described by the table below (all the fields are little endian).

The image data can be sent either uncompressed or compressed with fpaq0f2.

The main difficulty we’ve been facing is decoding all the packets without any bit errors. Even though Julien has made IQ recordings of the satellite in the lab, and the SNR is more or less good, it seems extremely difficult not to lose any packets due to bit errors.

In an attempt to reduced the errors, I have tried hard to design a reliable decoder in GNU Radio. The main ideas of this decoder are as follows. First, no clock recovery is done. This is acceptable, since the packets are rather short, and it increases reliability, as it eliminates the clock recovery loop as a possible point of failure. Four different clock phases are tried in parallel and packets decodes from all of them are stored for later processing.

Second, there are two different decoding algorithms which are tried in parallel. The first one is an integrate and dump FSK demodulation algorithm. This is best explained by the diagram below, taken from this post in David Rowe VK5DGR’s blog.

Essentially, each of the FSK tones is shifted to baseband, integrated coherently for a bit period, and then power is compared. This demodulator is implemented as a hierarchical flowgraph, which is instanced for each of the four clock phases. The flowgraph is shown in the figure below. The Sync and create packed PDU block from gr-satellites is used to detect the address and extract the payload and CRC as a PDU.

The second decoding algorithm is based on FM demodulation, followed by some filtering and bit slicing.

The full decoder can be seen in the figure below. There are four instances of the integrate and dump demodulator (called FSK clock branch) and four instances of the FM-based demodulator (called NRZ clock branch). Packets decoded from any of the branches are stored in a file for later processing.

The decoder flowgraph can be found in nrf24.grc.

After decoding, the frames are processed in this Jupyter notebook. It performs CRC checking, dropping all messages with invalid CRC. The remaining messages are grouped according to their chunk counter (first 2 bytes of the payload).

Ideally, we would expect that all the frames with the same chunk counter and correct CRC are equal (they are just the same frame decoded in parallel successfully by several of the eight demodulators). However, due to the short length of the CRC-16 code, we see many corrupted frames with valid CRC. To solve this problem, for each chunk counter value, a majority voting among the frames with that particular chunk counter value and correct CRC is done to try to select the correct frame. Often, the frame is decoded correctly by several of the eight demodulators, so this majority voting approach seems to work well to discard corrupted frames.

According to the result of the majority voting, the image file is reassembled using the chunk counter of each frame. Any missing frame leaves a gap of 30 bytes of zeros in the file.

Even after all this work, we haven’t managed to decode a transmission without any errors. The number of lost frames we get is quite low (on the order of 1%), but to recover compressed images we need perfect decoding, as any gaps in the file will make the decompression algorithm fail.

The image sensor used by AMICal Sat is an Onyx EV76C664 1280×1024 pixel sparse colour CMOS sensor. This means that whereas in most sensors the colour filter array has mainly coloured pixels, in this sensor most of the pixels are white, as shown by the figure below, taken from the datasheet.

I don’t know much about image sensors, but the manufacturer claims that this gives the sensor better low-light performance, which seems reasonable and is very desirable for AMICal Sat use case. The figure below, also taken from the datasheet, helps illustrate the advantage of having many white pixels.

The sensor active area is 1408×1040 pixels, with a useful area of 1280×1024 pixels. The ADC has 14 bit depth, but often a lower depth, such as 12 or 10 bits is used.

The image sensor data can be sent as uncompressed raw ADC data or as raw ADC data compressed with fpaq0f2. We haven’t been able to decode a compressed image successfully, due to packet loss, but we have managed to put together some uncompressed images.

Raw ADC data is sent as little endian 16 bit integers scanning the full 1408×1040 sensor area. In these integers, only the 12 or 10 least significant bits are nonzero, depending on the image depth. Decoding of the raw data is done in this Jupyter notebook.

I don’t usually work with colour image data, so my background read on colour spaces and human colour perception has been quite interesting. To decode the image, first each of the R, G, B and Y (luminance, from white pixes) channels are extracted and interpolated to the full sensor size. Then, the chroma information in the RGB channels is converted to CbCr using the JPEG conversion. A translation is done in CbCr coordinates for white calibration (the appropriate translation has been determined experimentally with a calibration image). Finally, the CbCr information is taken together with the Y channel from white pixels and converted back to RGB. Note that this is a very naïve demosaicing algorithm and, specially given the low chroma resolution, gives suboptimal results.

The first raw images I have worked with are a couple of calibration images taken in the lab. These are images of a calibration sheet taken at 12 and 10 bit depth. For some reason, the raw images have already been cropped to the 1280×1024 sensor useful size.

The figure below shows the calibration sheet photographed with a regular camera, for reference.

Below we show the image of the calibration sheet rendered from a 12 bit raw image taken with the AMICal Sat camera.

The two images below have been recovered from IQ recordings that Julien made of the S-band transmitter. The packet loss is apparent as black lines or very bright colours in some parts of the images. The data is 1408 columns wide, corresponding to the full sensor size. The useless sensor area can be seen as a dark stripe in the right side of the image. These images may not seem like much, but Julien tells me that they look like the corner in AMICal Sat’s clean room, so it seems all our software is working correctly.

It is interesting to look at the useless sensor area on the right side of the image. For some reason, it is only 112 pixels wide, even though there are 128 useless pixels per row, according to the sensor datasheet. The figure below shows the histogram for the raw ADC values corresponding to these pixels. The data seems normally distributed with small variance, as if the ADC was held to a fixed voltage or left floating.

If we average the data by columns, it seems that there is a slight variation from left to right. Note that the variation is only around 3 ADC counts, while the standard deviation of the pixel values is 5.4 ADC counts, so averaging is needed to see this effect clearly. I don’t know anything about its cause.

]]>Probably the largest improvement in the demodulators is the usage of the Symbol Sync block for clock recovery. This block fixes some of the bugs in older clock recovery blocks and is more general and powerful. For BPSK I am still using a maximum likelyhood TED with a matched polyphase filter. The correct TED gain has been computed as described in this post. For FSK, I have switched from the Mueller & Müller TED to the Gardner TED. The TED gain has been computed using a Jupyter notebook which is a rewrite of the Matlab simulation written by Andy Walls.

To analyse the performance of the demodulators, I am looking at two things, the BER and the lock-in performance (how fast the demodulator loops will lock at the start of a new packet). For most applications of gr-satellites, the received signals are packet bursts (some with rather short preambles) with moderate or strong SNR. Therefore, weak signal performance is not critical, and some is sacrificed for the sake of a fast lock-in. This means high loop bandwidths that cause too much jitter at low SNR.

All the performance tests for the demodulators are available in the tests folder. The BER simulation is done with `ber.py`

. An LFSR is used to generate a periodic sequence of bits, which are modulated, passed through an AWGN channel, demodulated with the gr-satellites demodulator component, and correlated against the original LFSR to compute the number of bit errors. The script computes and plots the BER depending on Eb/N0.

The BER curve for the BPSK demodulator using the default loop parameters can be seen below. There are some large implementation losses for low SNR, mainly due to jitter in the clock recovery and Costas loops. However, for Eb/N0’s greater than 4dB the demodulator follows the theory closely. As remarked above, this is done on purpose to achieve a good balance between fast lock-in and low BER.

The BER curve for the FSK demodulator is much worse than what the theory for non-coherent FSK states. The reason for this is the demodulation algorithm used in gr-satellites: the FSK signal is FM-demodulated using the Quadrature Demodulator block (which approximates the frequency of the signal by computing the phase difference between adjacent samples) and then some filtering and clock recovery is done with the output of this block. This gives suboptimal results.

A much better way to demodulate non-coherent FSK is to shift each of the tones to baseband, accumulate coherently for a symbol period, take the power, and compare to see which tone gives the largest power (basically, what is shown by David Rowe here). However, this approach has the disadvantage that the FSK deviation needs to be known or estimated from the signal and that it can’t work with the audio or discriminator output of a conventional FM receiver.

There are still many people using conventional FM radios, and more importantly, most of the recordings of FSK satellites out there are of FM demodulated audio, rather than IQ RF. Also, the deviation of most FSK satellites is unknown and would have to be estimated on the fly or measured for each satellite. Therefore, I have decided to settle on the current demodulator even though it gives worse performance. I might add the other type of demodulator as an option in the future.

In the FSK demodulator, after quadrature demodulation, there is a square (integrate and dump) pulse filter, and a DC block to remove any offset due to frequency error. Then there is an optional AGC to adjust the signal to unit amplitude, and then the Symbol Sync block using the Gardner TED. The AGC is needed if the deviation is not known. It is always on if using a real input signal (which comes from an FM demodulator), since the gain of the FM demodulator is unknown virtually always. It defaults to off if using an IQ signal, since we have control of the gain of the FM demodulator. However, this requires that the deviation is specified correctly (or at least approximately), either in the SatYAML file or with the `--deviation`

command line parameter. Otherwise, the AGC can be enabled with `--use_agc`

.

The lock-in performance of the demodulators is tested with `lockin.py`

. This generates a few short packets and dumps all the internal signals to files, so that they can be analysed and plotted later. The analysis is done in this notebook for BPSK and this notebook for FSK. There you can see how the different demodulator loops lock in at the start of each packet.

I have used this lock-in simulation to find some loop parameters that work well with most of the recordings in satellite-recordings. This is tested in the `test.sh`

script. In some cases, it might be necessary to adjust the parameters slightly to obtain good decodes. For FSK, these parameters are the clock recovery bandwidth `--clk_bw`

and clock recovery frequency error limit `--clk_limit`

. BPSK has in addition the FLL bandwidth `--fll_bw`

, and Costas loop bandwidth `--costas_bw`

. It is also possible to disable the FLL with `--disable_fll`

.

To aid in adjusting the parameters, it is possible to dump the internal signals of the demodulators to files by using the `--dump_path`

parameter, which will create a few files in the indicated path. Then these can be plotted with Numpy.

As an example, let us examine the AU02 recording in satellite-recordings. If we run

gr_satellites AU02 --wavfile ~/satellite-recordings/au02.wav \ --samp_rate 48e3 --dump_path /tmp/fsk

we don’t get a decode. We can plot the output of the clock recovery with

import numpy as np import matplotlib.pyplot as plt x = np.fromfile('/tmp/fsk/clock_recovery_out.f32', dtype = 'float32') plt.plot(x, '.') plt.show()

We get the plot below, which shows that the clock recovery has failed, but only slightly. It looks like there is just a clock cycle slip mid-packet.

We increase the default clock bandwidth of 0.06 slightly to 0.1 by using `--clk_bw 0.1`

and then we see a decoded packet. We plot again the output of the clock recovery, seeing that this time the clock recovery has worked correctly and there are probably no bit errors.

With this alpha, the release of gr-satellites 3.0.0 seems now quite near, since almost all the functionality I wanted is already working. In practise, it might still take a month or two until it’s ready for release. Most of the work that still needs to be done is documentation. I want to write some documents explaining the architecture from the perspective of the user (since the amount of blocks and functionality might be overwhelming). Essentially this amounts to rewriting the README file completely (which is already very long) and adding new markdown files. I also want to provide some example flowgraphs showcasing the functionality (intended for people wanting to reuse gr-satellites blocks in their own decoders).

When all this is ready, the next branch will be moved to master, and a v3-beta version will be released. If everything goes well with the beta, it will become v3.0.0, which will be merged into maint-3.8, and the development of the v2.*.* series will stop.

]]>One of the new features of the Symbol Sync block is the ability to specify the gain of the timing error detector (TED) used in the clock recovery feedback loop. All the other blocks assumed unity gain, which simply causes the loop filter taps to be wrong. However, the TED gain needs to be calculated beforehand either by analysis or simulation, as it depends on the choice of TED, samples per symbol, pulse shaping, SNR and other.

While Andy shows how to use the Symbol Sync block as a direct replacement for the Polyphase Clock Sync block in his slides, he leaves the TED gain as one, since that is what the Polyphase Clock Sync block uses. In replacing the Polyphase Clock Sync block by Symbol Sync in gr-satellites, I wanted to use the correct TED gain, but I didn’t found anyone having computed it before. This post shows my approach at simulating the TED gain for polyphase matched filter with maximum likelyhood detector.

Andy gave an Octave simulation for the Müller and Muller, and Gardner TEDs that accompanies an example flowgraph for Symbol Sync. After reading the code involved in a polyphase matched filter with the Symbol Sync block, I decided to follow a different approach. The construction of the polyphase filter is a bit involved, especially the normalization of the derivative filter taps, which I find a bit odd. To avoid making mistakes and getting an implementation which doesn’t match GNU Radio, I decided to use GNU Radio for the simulation instead of implementing a standalone simulation.

To perform the simulation I need to use some GNU Radio classes that aren’t available on the API, so the source code of GNU Radio is needed to build the simulation, which is written in C++.

To explain my simulation, I first need to explain some approximations and simplifications that can be made with TED. The case I am interested in is a polyphase matched filter for a PSK signal of amplitude one. The TED I am using is the complex `ted_signal_times_slope_ml`

, which can be described by the formula\[\frac{1}{2}\operatorname{Re} x_n\overline{x’_n},\]where \(x_n\) denotes the signal after matched filtering and \(x’_n\) denotes the derivative of the signal after matched filtering (obtained via convolution with the derivative filter).

I don’t understand the motivation for the factor 1/2 in the formula above, since the real `ted_signal_times_slope_ml`

detector uses just \[x_n x’_n.\]Therefore, the results obtained here can be applied to the real case, but the gain should be multiplied by two.

Now, the approximation goes as follows. We are interested in the derivative of\[\varepsilon(t) = \operatorname{Re} x(t)\overline{x'(t)}\]at an instant \(t = t_0\) aligned with the symbol clock. We have\[\varepsilon'(t_0) = |x'(t_0)|^2 + \operatorname{Re} x(t_0)\overline{x^{\prime\prime}(t_0)}.\]

Now denote by \(r(s)\) the raised cosine pulse, with the normalization that \(r(0) = 1\) and \(r(n) = 0\) if and only if \(n \in \mathbb{Z}\setminus\{0\}\). Then\[x(t_0 + sT) = \sum_{k \in \mathbb{Z}} a_k r(s-k),\]where \(T\) is the symbol period and \(a_k\) are the PSK symbols, which we assume to be independent random variables such that \(\mathbb{E}[a_k] = 0\) and \(|a_k| = 1\).

Thus,\[x'(t_0) = \frac{1}{T}\sum_{k\in\mathbb{Z}}a_k r'(-k).\]Since \(r'(0) = 0\), we see that the reason why \(x'(t_0)\) is non-zero in general is because of inter-symbol interference from symbols \(a_k\) with \(k \neq 0\). We decide to disregard this kind of inter-symbol interference and drop out the term \(|x'(t_0)|^2\) from the expression for \(\varepsilon'(t_0)\). This is our approximation. If following a more precise approach, we would compute\[\mathbb{E}[|x'(t_0)|]|^2 = \frac{1}{T^2}\sum_{k\in\mathbb{Z}} r'(-k)^2,\]using the fact that the variance of \(a_k\) is one.

After the approximation, we are left with\[\operatorname{Re} x(t_0)\overline{x^{\prime\prime}(t_0)} = \frac{1}{T^2}\operatorname{Re} \sum_{k\in\mathbb{Z}} a_0 \overline{a_k} r^{\prime\prime}(-k).\]Taking the expectation,\[\mathbb{E}[\operatorname{Re} x(t_0)\overline{x^{\prime\prime}(t_0)}] = \frac{1}{T^2}r^{\prime\prime}(0),\]since \(a_k\) are independent. This is the same that we would obtain if we compute \(\varepsilon'(t_0)\) for \(a_0 = 1\) and \(a_k = 0\), \(k \neq 0\), so that our signal \(x(t)\) is a raised cosine pulse adequately scaled to the symbol period \(T\).

This motivates our simulation, which is based in running a raised cosine pulse through the TED. Disregarding the \(|x'(t_0)|^2\) term produced by inter-symbol interference, this should give the same results as passing a raised cosine filtered PSK signal with random data and averaging over all the symbols.

Since we are using a polyphase matched filter, we first generate a root raised cosine pulse signal, then apply the polyphase filter to generate the match-filtered signal and derivative, and then use the maximum likelyhood detector to compute the clock phase error.

In order to compute the detector S-curve, we generate the input root raised cosine pulse at different clock phases by following a polyphase approach. A root raised cosine pulse at `100 * sps`

samples per symbol is generated and then decimated to `sps`

by taking one out of every 100 samples at each offset. Here `sps`

stands for the simulation samples per symbol.

The C++ simulation can be seen here. It uses the `timing_error_detector`

detector and `interpolating_resampler_ccf`

classes, which are used behind the scenes by the `symbol_sync_cc`

class. These classes are not exported on the API, so the GNU Radio sources are required to build the simulation against the sources of these classes.

The simulation computes the TED gain at a clock phase of zero by approximating the derivative of the TED error by subtracting the errors at two close clock phases.

The following Jupyter notebook shows the output of the simulation, including the output of the matched filter, derivative filter and TED error. As a result, we get the figure below, which shows that the TED gain in units of 1/sample follows closely the curve `0.5/sps`

.

In fact, the TED gain equals `0.5/sps`

when the samples per symbol is of the form \(k/2\), for \(k \in \mathbb{Z}\), but is different when the samples per symbol is of the form \((2k+1)/4\), for \(k \in \mathbb{Z}\).

The conclusion is that, except when `sps`

is small, the function `0.5/sps`

is a good approximation for the TED gain. This approximation is what I’m using now in the gr-satellites next branch. Note that the factor `0.5`

comes directly from the factor 1/2 in the definition of the complex maximum likelyhood detector. For the real case, `1/sps`

should be used instead, since the real detector doesn’t include the factor 1/2.

**Update 2020-02-29:** I have realised that the TED gain in the Symbol Sync block should be specified in units of 1/symbol instead of 1/sample (which is what the last slide in Andy’s presentation seems to indicate). The reason for this is that the TED error, and hence the loop filter update, is computed only once per symbol, regardless of the input samples per symbol. Therefore, 0.5 instead of `0.5/sps`

should be used as TED gain for the conditions described in this post.