I’ll start off with a correction to my previous post. In there, we saw that not only E24 has atypically large \(BGD(\mathrm{E1}, \mathrm{E5a})\) and \(BGD(\mathrm{E1}, \mathrm{E5b})\), but also the difference \(BGD(\mathrm{E1}, \mathrm{E5a}) – BGD(\mathrm{E1}, \mathrm{E5b})\) is atypically large (around 5ns). I said that this causes different delays in the E5a and E5b signals, so users of the combined AltBOC signal may need to take it into account.

However, if we look at the DCBs for E1-E5a and E1-E5b, we will see that they are pretty similar. In fact, the DCB for E1-E5a is -36.1ns, and the DCB for E1-E5b is -35.8ns, so their difference (which gives the DCB for E5b-E5a) is only -0.25ns. Hence the difference of delays between E5a and E5b is small. In hindsight, this is to be expected. The E5a and E5b signals are really sidebands of the same AltBOC modulation, which is passed through the same power amplifier and filter chain. The response of these will vary slightly with frequency, but a large difference of delays is not to be expected unless something has gone really wrong.

This remark gives us more insight into why the BGDs for E24 are rather different. As we have said, typically we will have \(DCB(\mathrm{E1} – \mathrm{E5a}) \approx DCB(\mathrm{E1} – \mathrm{E5b})\). Now,\[\begin{split}BGD(\mathrm{E1}, \mathrm{E5a}) &- BGD(\mathrm{E1}, \mathrm{E5b}) = \\ &\frac{DCB(\mathrm{E1} – \mathrm{E5a})}{1 – \left(\frac{f_{\mathrm{E1}}}{f_{\mathrm{E5a}}}\right)^2} – \frac{DCB(\mathrm{E1} – \mathrm{E5b})}{1 – \left(\frac{f_{\mathrm{E1}}}{f_{\mathrm{E5b}}}\right)^2} \approx \\ &DCB(\mathrm{E1} – \mathrm{E5a}) \left[ \frac{1}{1 – \left(\frac{f_{\mathrm{E1}}}{f_{\mathrm{E5a}}}\right)^2} – \frac{1}{1 – \left(\frac{f_{\mathrm{E1}}}{f_{\mathrm{E5b}}}\right)^2}\right].\end{split}\]The number in square brackets is approximately 0.16, so this means that the difference between BGDs is proportional to either one of the DCBs (assuming both DCBs are very similar). We can also write this in terms of BGDs as \[\begin{split}BGD(\mathrm{E1}, \mathrm{E5a}) &- BGD(\mathrm{E1}, \mathrm{E5b}) \approx \\ &BGD(\mathrm{E1}, \mathrm{E5a}) \left[ 1 – \frac{1 – \left(\frac{f_{\mathrm{E1}}}{f_{\mathrm{E5a}}}\right)^2}{1 – \left(\frac{f_{\mathrm{E1}}}{f_{\mathrm{E5b}}}\right)^2}\right].\end{split}\]The number in square brackets is approximately -0.13.

So everything works out nicely for E24. It has a \(BGD(\mathrm{E1}, \mathrm{E5a})\) of approximately 45ns, and so the difference between BGDs must be on the order of -5.8ns. Most of the other Galileo satellites respect this proportion between the BGDs, which is a consequence of the delay between E5a and E5b being very small. If we look again at the plot of BGDs shown in the previous post, we see that the E5a BGD is usually a bit smaller than the E5b BGD.

The atypical case is E12, which has an E5a BGD slightly larger than the E5b BGD. This is caused by a significantly large difference of delays between E5a and E5b, which can be seen in the plot of DCBs.

Let’s now turn back of the topic of RTCM clock corrections. The RTCM protocol includes message type 1241 to send clock corrections to the Galileo broadcast ephemerides. More information can be found in this presentation. Basically, a clock correction is formed by polynomial coefficients \(C_0, C_1, C_2\) and a base time \(t_0\), so that the correction in metres at time \(t\) is given by \[\delta C (t) = C_0 + C_1 (t – t_0) + C_2 (t-t_0)^2.\]The correction is applied by replacing the satellite clock bias \(\Delta t_{SV}(X) (t)\) computed with the broadcast parameters by \(\Delta t_{SV}(X) (t) + c^{-1}\delta C(t)\).

An important remark is that this correction is only valid for a particular choice of ionosphere-free combination or single frequency \(X\). Apparently, the RTCM provider has the freedom to choose to what \(X\) their \(\delta C\) refers to, and this choice is implicit rather than indicated in the RTCM message. Therfeore, the RTCM message should be used to correct the appropriate broadcast clock. Whether \(\delta C\) can be scaled in order to correct another broadcast clock will be discussed below.

For this post, we will be looking only at the \(C_0\) term of the RTCM clock correction. Our main point is to investigate why the main RTCM sources send a \(C_0\) of around -5ns for E24. As shown by Bert, this happens for the Spaceopal NAVCAST, the CNES PPP-wizard caster, DLR RETICLE, and to some extent for the CAS Wuhan RTCM service (more details below), so here we’ll only be looking at the Spaceopal data.

A \(C_0\) term of around -5ns means that some broadcast satellite clock model \(\Delta t_{SV}(X)\) has a +5ns error, so that when \(\delta C\) is added to the broadcast model, the error cancels out. Therefore, it is best to start by investigating the broadcast clocks for E24 and checking if there is anything wrong with them.

To do so, we use precise clock products as our best proxy for the actual clock of the satellites. We will be using CLK files from CODE. By subtracting the broadcast \(a_{f0}\) term minus the clock obtained from CODE, we obtain a good indication of the error in the broadcast clock model. However, there is a subtlety here. The two terms we’re subtracting use different timescales: the \(a_{f0}\) term is referred to the GST timescale, which is specific to the Galileo constellation, while the CODE clock files use another UTC-ish timescale that is common to all the constellations in the clock file. Therefore, the difference between the GST and the CODE timescales will show up when computing \(a_{f0}\) minus the CODE clock. However, this timescale difference term is common for all the Galileo satellites. Another detail: Care must be taken in using the correct ionosphere-free combination or scaling things appropriately. The precise clocks refer to the E1-E5a combination, so we use \(a_{f0}(\mathrm{E1}, \mathrm{E5a})\) here.

In the figure below we show the difference between the broadcast \(a_{f0}\) term for the E1-E5a combination (which is transmitted in the F/NAV message) and the CODE CLK. We show E24 in red and the remaining Galileo satellites in grey as an indication of the timescale difference between GST and the CODE. As we see, this timescale difference waves around 2 and 4ns, but all the satellites are in the same band of approximately 1ns about this timescale difference. This means that the E24 broadcast clock for E1-E5a is correct and doesn’t show any constant error that may explain the -5ns RTCM corrections.

So what happens to the E1-E5b clock (which is transmitted in the I/NAV message)? Well, in my previous post I showed how to relate the E1-E5a and E1-E5b clocks using the difference of BGDs as\[\Delta t_{SV}(\mathrm{E1}, \mathrm{E5a}) – \Delta t_{SV}(\mathrm{E1}, \mathrm{E5b}) = BGD(\mathrm{E1}, \mathrm{E5a}) – BGD(\mathrm{E1}, \mathrm{E5b}).\]Thus, I checked for consistency in the broadcast clock models by showing that the differences\[a_{f0}(\mathrm{E1}, \mathrm{E5a}) – a_{f0}(\mathrm{E1}, \mathrm{E5b})\]and\[BGD(\mathrm{E1}, \mathrm{E5a}) – BGD(\mathrm{E1}, \mathrm{E5b})\]were similar for all the Galileo satellites. Therefore, the fact that the E1-E5a broadcast clock for E24 is correct also implies the correctness of the E1-E5b broadcast clock.

We are now pretty sure that the E24 broadcast clocks are correct and do not have any constant error that resembles 5ns. Yet the RTCM providers are sending a -5ns correction, but for what clock? As mentioned above, RTCM providers have the freedom to send corrections specific to any frequency combination or single frequency they want to. Bert has run some questions with Spaceopal and they said that their RTCM corrections refer to the E1-E5a clock. If we take this literally, then it is wrong: the E1-E5a broadcast clock has by no means a 5ns error.

So where could this -5ns value come from? Well, it is interesting to note that the difference between BGDs has this value. The figure below shows a number of different things, all of them centred around -5.5ns. In blue we show the \(C_0\) term for the Spaceopal RTCM. The only modification is the conversion from metres to ns. In orange, we show the difference between broadcast \(a_{f0}\) terms for E1-E5a and E1-E5b. In green, we show the difference between broadcast BGDs for E1-E5a and E1-E5b. Finally, in red we show the BGD difference computed by using the Wuhan DCBs.

The calculation with the DCBs should give the best approximation to the real BGD difference. The difference of BGDs included in the navigation message is pretty close to this value and in fact seems to straddle it by one least-significant bit (which represents 0.23ns). As remarked above, the difference between \(a_{f0}\) terms should be very close to the difference between BGDs. Here we see some small bias of around 0.25ns, which is interesting but can probably be ignored for the purposes of this post. Finally, the RTCM \(C_0\) term moves one or two ns up and down (just because the broadcast clock model has an error that moves in this manner), but is also centred on the BGD difference.

Other than this -5ns offset, the RTCM \(C_0\) seems correct. In fact, the figure below shows the comparison between \(C_0\) and the CODE precise clock product. To do this comparison, we take the time series formed by the broadcast \(a_{f0}\) minus the CODE clock and remove its average, as a zero-order correction for the difference between the GST and CODE timescales. We also take the value of \(-C_0\) and add the difference of BGDs to it. The minus sign is needed because \(\delta C\) is used as an additive correction to the broadcast clock, so the broadcast clock error is actually \(-\delta C\).

Note that there is still some difference between GST and CODE that we’ve left uncorrected, but in any case the two traces above look qualitatively rather similar. This is as much as can be expected, since the RTCM is a real time correction and the CODE precise clock is a final product, so it will be more accurate.

So what does the -5ns offset actually mean? Well, since this is equal to the difference of BGDs, which can be used to transform between the E1-E5a and E1-E5b broadcast clocks, it turns out that if we take the broadcast E1-E5b clock and add to it the RTCM correction, we end up with a better (or corrected) value for the E1-E5a clock. In formula:\[\Delta t_{SV}^B(\mathrm{E1}, \mathrm{E5b}) + c^{-1}\delta C = \Delta t_{SV}^C(\mathrm{E1}, \mathrm{E5a}),\]where we are marking with the superscript \(B\) the broadcast clock (as transmitted in the navigation message) and with the superscript \(C\) a corrected clock that should be more accurate, and is still referred to the GST timescale.

**Update 2020-07-06:** Originally, I made a sign mistake when deriving the formula above and wrote E5a in place of E5b and vice versa. I have now corrected this error.

The jump from the E1-E5b clock to the E1-E5a clock in the formula above is certainly weird. Users interested in staying with the same clock would find the following formulas useful:\[\Delta t_{SV}^B(\mathrm{E1}, \mathrm{E5a}) + c^{-1}\delta C – \Delta BGD= \Delta t_{SV}^C(\mathrm{E1}, \mathrm{E5a}),\]\[\Delta t_{SV}^B(\mathrm{E1}, \mathrm{E5b}) + c^{-1}\delta C – \Delta BGD= \Delta t_{SV}^C(\mathrm{E1}, \mathrm{E5b}),\]where\[\Delta BGD = BGD(\mathrm{E1}, \mathrm{E5a}) – BGD(\mathrm{E1}, \mathrm{E5b}).\]Single frequency users can subtract the appropriate BGD from both sides of these equalities to obtain their corrected clock (note that the extra terms included by \(\Delta BGD\) do not cancel out when doing so). These formulas with \(\Delta BGD\) are what galmon.eu is using now to get meaningful results for E24.

It goes without saying to say that it is important to use the formulas with the \(\Delta BGD\) term, since otherwise we end up with a satellite clock that has a -5.5ns error (roughly -1.5 metres), which is a significant problem in any application requiring precision.

So is this weird jump from the E1-E5b clock to the E1-E5a any useful? I think not. Receivers not working with E5a will only have access to the I/NAV data. Therefore, they will only have access to the E1-E5b broadcast clock model and BGD. This is all they need to work with the E1-E5b combination or single frequency on either E1 or E5b. But if they want to apply the RTCM correction and get the clock they need, they will also need knowledge of either the E1-E5a BGD or the E1-E5a broadcast clock, which is transmitted only in the F/NAV message.

Receivers working only with E5a (I reckon this is an atypical case) will only have access to the F/NAV data. As such, they have the E1-E5a broadcast clock model and BGD that they need to compute the E5a broadcast clock. However, if they want to apply the RTCM correction to get the E5a broadcast clock, then they need knowledge of either the E1-E5b BGD or the E1-E5b broadcast clock, which are in the I/NAV message.

Receivers that have access to both the I/NAV and the F/NAV message can apply the formulas above without restrictions, but what happens if the broadcast BGDs are wrong? The idea behind RTCM corrections is to protect the receiver from any problems in the broadcast data by sending appropriate corrections, so including uncorrected broadcast BGDs in the formula partially defeats this purpose.

This leads us to another ineresting topic. The RTCM protocol has also a message 1242 to send Galileo code biases. I’m not sure about the details of these messages, but I think that they can be used as DCBs, and hence as a substitute (rather than correction) for the broadcast BGDs. I don’t know whether the major RTCM providers are sending these messages (something worth checking!). If so, it might be that the biases in these messages are wrong (or let’s say “weirdly defined”) in such a way that when used together with the “weirdly defined” \(\delta C\) one obtains the appropriate clock without the need to correct with \(\Delta BGD\). This might be true only to some extent: An ionosphere-free user will not need to account for any code biases, so they will still face the \(\delta C\) weirdness problem. A single frequency user will need to throw in a correction term with a BGD computed from the RTCM code biases, so in theory it is possible that this cancels out the \(\delta C\) weirdness.

As I already mentioned above, the case of the Wuhan RTCM feed is actually more interesting. Bert has noticed that it seems that it randomly needs and doesn’t need the correction with \(\Delta BGD\). As such, it is even less useful, because one never knows how to treat it.

As a bonus, yet some more weirdness in the Galileo constellation. We have been looking at the difference in the \(a_{f0}\) terms of the broadcast clock, and saying that this must be essentially the same as \(\Delta BGD\). However, if we look at the data from the week I have studied for this series of posts, we see that there are three satellites whose \(a_{f0}\) difference goes very wrong at some point.

This doesn’t make much sense. The satellites only have one onboard clock active for all the signals (they have several onboard clocks as spares, but at any time they use the same one for all the navigation signals). In addition, there are the BGDs, which tell us the different delays between the navigation signals. The BGDs are parameters that change very slowly in time and so it is very difficult that estimation of them by the ground segment goes wrong (and in fact it doesn’t: we always see more or less the same BGDs for each satellite and frequency pair).

The estimation of the onboard clock by the ground segment might go wrong for a number of reasons. But still, the difference between \(a_{f0}\) terms should be equal to \(\Delta BGD\). There is no reason why one of the \(a_{f0}\)’s goes wrong and the other doesn’t, or that they both go wrong “in different ways”.

The remaining satellites show \(a_{f0}\) differences that are more or less constant and equal to their \(\Delta BGD\)’s. But still the differences move much more than \(\Delta BGD\).

In my opinion, the \(a_{f0}\) difference should be exactly equal to \(\Delta BGD\), except for numerical precision (\(a_{f0}\) is transmitted with a resolution of \(2^{-34}\) seconds, while the BGDs have a resolution of \(2^{-32}\) seconds). Additionally, the \(a_{f1}\) and \(a_{f2}\) terms should be exactly the same for both clock models, just as the time derivatives of the BGD are deemed too small to be considered.

The calculations and data used in this post are included in the updated version of the Jupyter notebook. The Spaceopal RTCM data was kindly provided by Bert from the protobuf data stored by the galmon network. Bert and I have been working in parallel and exchanging private communication about these topics, so some of the ideas and discoveries presented here are originally from Bert and others are mine.

]]>In this post I will explain a few basic facts about BGDs and related topics, following an approach that is perhaps different to that of the usual GNSS literature, and also study the current values for the Galileo constellation. People who know all the details about the BGDs or who just want to see a few pretty plots can skip all the first section of the post.

The go-to reference for BGDs and any other element of the Galileo Open Service navigation message is the Galileo – Open Service – Signal In Space Interface Control Document (OS SIS ICD V1.3), so here I will try to stick to the notation used there as much as possible. The relevant sections that I will be discussing are 5.1.4 and 5.1.5.

Let’s start from the basics, which will lead us to Equation 12 in the SIS ICD. The whole concept of time in a GNSS constellation is governed through the constellation time scale, which in the case of Galileo is the GST. This is a time reference that doesn’t exist physically, and which is only brought into play by speaking about how real physical clocks differ from this ideal timescale. As such, it is often called a paper clock.

GNSS signals are structured in time, in such a way that to each instant of GST (or the corresponding constellation clock, for other constellations), we have assigned a specific value (actually a voltage amplitude, since the signal is an electromagnetic wave) to each of the particular signals of each satellite in the constellation. If everything was perfect, then the satellites would transmit the corresponding signal at exactly the precise instant according to GST. But the transmissions from each satellite are driven by their own imperfect physical clocks. Thus, there is always a small offset between when the satellite is really transmitting something and when it should be transmitting it according to GST.

The way to write this mathematically is to denote by \(TOT_m\) the scheduled time of transmission according to the constellation clock (so the time when a particular signal should be transmitted if following GST exactly), and by \(TOT_c\) the time when the satellite actually transmits the signal. The two are related by\[TOT_c = TOT_m – \Delta t_{SV},\]where \(\Delta t_{SV}\) is a time-varying quantity that tells us the time offset in this specific satellite’s transmissions (this is often called satellite clock bias). The equation above is Equation 12 in the SIS ICD.

Now, signals corresponding to different frequencies might suffer different delays and end up not being transmitted simultaneously even if ideally they should. We will write\[TOT_c(f) = TOT_m(f) – \Delta t_{SV}(f)\]to express the dependence on the frequency \(f\). Note that we have a different satellite clock bias for each frequency.

GNSS receivers usually work by measuring the signals from different satellites simultaneously as they arrive to the receiver’s antenna. The receiver is able to work out the corresponding \(TOT_m\)’s from the structure of the signals, and forms quantities called pseudoranges which are defined by \[p = TOR – TOT_m,\]where \(TOR\) stands for time of reception according to the receiver clock. For the purposes of this post, we can think that the \(TOR\) is just a common quantity that is used to form the pseudoranges for all the satellites and that it is just an approximation made by the receiver about the time at which the measurement is done (and the precision of this approximation is not really important). We will write\[p(f) = TOR – TOT_m(f)\]to make the dependence on the frequency explicit.

If \(f_1\) and \(f_2\) are two different frequencies (for example, E1 and E5a), then it is possible to form the ionosphere-free combination of the pseudoranges \(p(f_1)\) and \(p(f_2)\), which is defined as\[p(f_1,f_2) = \frac{f_1^2 p(f_1) – f_2^2 p(f_2)}{f_1^2 – f_2^2}.\]Note that this is just a weighted average of \(p(f_1)\) and \(p(f_2)\). This particular choice of weights has the special property that it cancels out the ionospheric delay to a first order of approximation. As such, almost all applications that need precision use this combination. For example, it is used by the ground segment to determine the satellites orbits and clocks. So in many settings it is more natural to think of the ionosphere-free combination rather than of a single frequency.

Now we consider the following weighted averages\[TOT_m(f_1, f_2) = \frac{f_1^2 TOT_m(f_1) – f_2^2 TOT_m(f_2)}{f_1^2 – f_2^2},\]\[TOT_c(f_1, f_2) = \frac{f_1^2 TOT_c(f_1) – f_2^2 TOT_c(f_2)}{f_1^2 – f_2^2},\]and\[\Delta t_{SV}(f_1, f_2) = \frac{f_1^2 \Delta t_{SV}(f_1) – f_2^2 \Delta t_{SV}(f_2)}{f_1^2 – f_2^2}.\]For now this is just notation, but the convenience of this notation will be seen soon. A straightforward calculation shows that\[\begin{split}p(f_1,f_2) &= TOR – TOT_m(f_1,f_2)\\ &= TOR – TOT_c(f_1,f_2) – \Delta t_{SV}(f_1,f_2).\end{split}\]The quantity \(TOR – TOT_c(f_1,f_2)\) has everything a receiver needs to compute its position, or everything that the ground segment needs to compute the satellite orbit, with the advantage that all ionospheric delays have been cancelled out to first order as stated above (but this is a matter for another post). As such, the ground segment actually determines the ionosphere-free satellite clock bias \(\Delta t_{SV}(f_1,f_2)\), and this is also the satellite clock bias that the receivers using ionosphere-free combinations need for their calculations.

Note that \(\Delta t_{SV}(f_1,f_2)\) doesn’t really have a simple interpretation in physical terms, as \(\Delta t_{SV}(f_j)\) did. It is just a weighted average of clock biases that shows up when dealing with the ionosphere combination. Nevertheless, this is the clock bias that is transmitted in the navigation message, as well as the clock bias that is used in SP3 files. The bias \(\Delta t_{SV}(\mathrm{E1},\mathrm{E5a})\) is transmitted in F/NAV, while \(\Delta t_{SV}(\mathrm{E1},\mathrm{E5b})\) is transmitted in I/NAV.

Single frequency users can’t use \(\Delta t_{SV}(f_1,f_2)\) directly. A single frequency user working on \(f_1\) needs a means to obtain \(\Delta t_{SV}(f_1)\) from \(\Delta t_{SV}(f_1,f_2)\). So let us define \(BGD(f_1,f_2)\) as\[BGD(f_1,f_2) = \Delta t_{SV}(f_1,f_2) – \Delta t_{SV}(f_1).\]In this manner, users of \(f_1\) can compute the satellite clock bias they need by\[\Delta t_{SV}(f_1) = \Delta t_{SV}(f_1,f_2) – BGD(f_1,f_2),\]which is Equation 15 in the SIS ICD.

Now let us work out what \(BGD(f_1,f_2)\) actually is. Expanding the definition of \(\Delta t_{SV}(f_1,f_2)\), we get\[\tag{A}BGD(f_1, f_2) = \frac{\Delta t_{SV}(f_1) – \Delta t_{SV}(f_2)}{\left(\frac{f_1}{f_2}\right)^2 – 1}.\]Now, this is almost Equation 14 in the SIS ICD, which is\[BGD(f_1, f_2) = \frac{TR_1 – TR_2}{1 – \left(\frac{f_1}{f_2}\right)^2},\]where \(TR_1\) and \(TR_2\) are the group delays of the signals at frequencies \(f_1\) and \(f_2\).

This requires a small remark regarding group delays. In a GNSS satellite, there is an atomic clock that controls the generation of the RF signals, then these signals are delayed to some extent as they travel through the satellite payload, and then they are radiated from the antenna. Now, we don’t care about the delays of the signals per se. We only care about differences of delays. In fact, a delay that affects all the signals in the same way is indistinguishable from an atomic clock that is running a bit late. So actually we can’t measure the delays themselves, but only their differences. Under this perspective, Equation 14 makes sense, because it only depends on the difference \(TR_1 – TR_2\).

If we have two signals that should be transmitted simultaneously, so that \(TOT_m(f_1) = TOT_m(f_2)\), but are delayed differently so that they are radiated at different times \(TOT_c(f_1)\) and \(TOT_c(f_2)\), their difference of delays is\[\begin{split}TR_1 – TR_2 &= TOT_c(f_1) – TOT_c(f_2) \\ &= -\Delta t_{SV}(f_1) + \Delta t_{SV}(f_2).\end{split}\]This shows that (A) above is equivalent to Equation 14.

Another important question is what happens with single frequency users that work with \(f_2\). A simple calculation shows that the conversion they need to apply can be computed as\[\begin{split}\Delta t_{SV}(f_1,f_2) – \Delta t_{SV}(f_2) &= \frac{f_1^2}{f_1^2 – f_2^2}\left( \Delta t_{SV}(f_1) – \Delta t_{SV}(f_2) \right) \\ &= \left(\frac{f_1}{f_2}\right)^2 BGD(f_1,f_2).\end{split}\]So users of \(f_2\) compute the satellite clock bias as\[\Delta t_{SV}(f_2) = \Delta t_{SV}(f_1,f_2) – \left(\frac{f_1}{f_2}\right)^2 BGD(f_1,f_2).\]

So to summarise, we may say that \(BGD(f_1, f_2)\) is the conversion from the ionosphere-free satellite clock bias to the single frequency satellite clock biases, scaled in such a way that it can be directly applied to obtain the clock for \(f_1\) (so it needs to be scaled appropriately to obtain the clock for \(f_2\)).

Now let us pass to the study of the BGDs and related quantities for the current Galileo constellation. The work shown here can be seen in this Jupyter notebook. I have focused the analysis on GPS week 2109, which started on 2020-06-07, just because it is a recent week, but old enough so that all final GNSS products have already been released.

First we study the BGDs contained in the navigation message. The I/NAV pages contain the BGD for E1-E5b, and the F/NAV pages contain the BGD for E1-E5a. I have used the BRDM MGEX merged ephemeris products. These are loaded with georinex. Since the BGD depends only on internal delays in the satellite payload, which can change with age, temperature, etc., but are otherwise more or less constant, it varies very little.

The figure below shows the average BGDs for each satellite over the whole week 2109, and small error bars which correspond to the standard deviation (in this post all the plots will have error bars). I have ignored the eccentric satellites E14 and E18.

We see what already Bert had noted. E24 has much larger BGDs than the other satellites. Additionally, the difference between its E1-E5a and E1-E5b BGDs is much larger than for all the other satellites.

As we have seen, the BGDs are very related to the clock models \(\Delta t_{SV}\). Indeed, if we consider three frequencies \(f_1\), \(f_2\), \(f_3\), we have\[BGD(f_1, f_2) – BGD(f_1, f_3) = \Delta t_{SV}(f_1, f_2) – \Delta t_{SV}(f_1, f_3),\]since the \(\Delta t_{SV}(f_1)\) terms cancel out. As the BGDs vary slowly, we can just look at the constant term of \(\Delta t_{SV}\), which is called \(a_{f0}\) and study\[a_{f0}(f_1,f_2) – a_{f0}(f_1, f_3).\]These clock model coefficients are broadcast in the navigation message and their difference should be approximately the same as the difference between BGDs.

The figure below compares the difference between BGDs with the difference between \(a_{f0}\) terms, averaged over one week. As before, the error bars correspond to the standard deviation. There are a few satellites with large error bars on the \(a_{f0}\) differences. These correspond to problems in the consistency of the clock models for E1-E5a and E1-E5b (the difference between clock models should be almost constant, so its standard deviation should be pretty small).

Besides these large error bars for some satellites, we see good agreement between the BGD and \(a_{f0}\) differences. We also note that E24 has a relatively large difference. This actually means that there is a significant delay difference between the E5a and E5b signals, so users of the E5 AltBOC signal might need to take this into account. (**Update 2020-07-04:** This remark about the delay difference between E5a and E5b is not correct. See the next post).

So far we have only looked at broadcast ephemeris parameters, but these also have their equivalent “precise products” (in the same way that SP3 files are the precise equivalent to broadcast orbits). The DCBs, or differential code biases, are precise products that describe the observed delay differences between pairs of signals. I have used the DCBs from the Chinese Academy of Sciences in Wuhan corresponding to GPS week 2109. These are read with some ad-hoc Python code and plotted here.

I’m using pilot signals (there are also DCB’s for the C*X signals, which are the combined data+pilot signals). The nomenclature used in DCB files is the one of RINEX files, so C1C stands for the E1C pilot signal, C5Q stands for the E5a-Q pilot signal, C6C stands for the E6C pilot signal, C7Q stands for the E5b-Q pilot signal, and C8Q stands for the AltBOC E5 pilot signal.

We see that, as expected, E24 has the largest biases (also for E1-E6). Note that the sign of the BGDs is opposite to that of the DCBs just because the \(1 – (f_1/f_2)^2\) term in Equation 14 of the Galileo SIS ICD is negative, since \(f_1 > f_2\). Also, the DCBs are directly the difference of delays \(TR_1 – TR_2\), while the BGDs are scaled by \(1/(1 – (f_1/f_2)^2)\).

To check the quality of the broadcast BGDs, we can scale them appropriately and subtract them from the DCBs. The results are shown below. We note that the difference is small, and often zero belongs to the error bars, so we can say that the Galileo broadcast BGDs are in good accordance with the precise DCB products.

So in summary, I have three conclusions for this post:

- The BGDs of Galileo Open Service are accurate enough, and in good agreement with the precise DCBs.
- The satellite E24 is somewhat strange, but that’s all: its data is correct and consistent.
- The broadcast \(a_{f0}\) clock terms for I/NAV and F/NAV are often consistent, in the sense that their difference is almost constant, and very similar to the BGD difference. However, there are occasional problems that might be interesting to study in more detail.

The HF transmitter has a power of up to 600kW and can use the frequencies 5.1MHz and 8.175MHz. At those frequencies, the dish has a gain of 22dB (13º beamwidth) and 25.5dB (8.5deg) respectively, so the power that is beamed up to the ionosphere is huge. The 430MHz incoherent scatter radar is even more powerful, with up to 2MW. For an introduction to ionospheric incoherent scatter radar, see this lecture by Juha Vierinen, which explains why such huge powers are needed, due to the very weak radar return of ionospheric plasma.

A few days ago, on Wednesday 24, Chris Fallen tweeted that the Arecibo transmitter was active at 5.1MHz. According to the telescope schedule, which can be seen in the figure below (click on the image to view it in full size), there was an experiment that involved the HF transmitter on 2020-06-24 from 18:00 to 22:00 UTC, on 2020-06-25 from 17:00 to 21:00 UTC, and on 2020-06-26 from 17:00 to 21:00 UTC.

I saw Chris’ tweet a few hours too late, but I still could fire up my HF receiver (which consists of a Hermes-Lite 2 and a wire antenna of approximately 15 metres long) at around 21:00 UTC and receive the signal from Arecibo. Then I decided to record the rest of the experiment, and I also did recordings during the next days.

The project associated to this HF experiment is H3473 and its description can be read here. The description is very brief but says that this is an experiment by Fourth State Communications, which is a small company that develops an Enhanced Thermo-Scatter System for ionospheric communications. They claim up to 200Mbps at 2000 miles distance. Pretty impressive.

Since I was a bit informal in doing these recordings, I didn’t take the exact start times and on two days I started after the beginning of the experiment. In all the three days my recordings ended after the experiment stopped. These are the approximate start times for the recordings I made (I have more uncertainty in the first one, the other two should be accurate to one minute):

- 2020-06-24 21:00 UTC
- 2020-06-25 17:21 UTC
- 2020-06-26 16:51 UTC

I have published these recordings as a dataset in Zenodo, for anyone interested in having a look.

The waterfall corresponding to the three days of experiment can be seen below. I have lined up the three recordings by time of day. The resolution of this waterfall is 4.096 seconds and 61mHz per pixel. The total frequency span that is shown is 30Hz, centred on 5100005Hz. You can click on the image to view it in full size.

On the first day, the HF transmitter duty cycle was 4 minutes on and 1 minute off. On the second and third day there was a constant CW carrier interfering on the frequency, and the Arecibo signal was much weaker. The duty cycle was different: 5 minutes on and 5 minutes off.

It is interesting to see the Doppler spread on the Arecibo signal and how it changes with time. The figure below shows the power spectral density corresponding to the last transmission on 2020-06-24. We can see a stronger concentrated tone at higher frequency, which is typical in all the three days of experiment, and then weaker signal spreading downwards in frequency.

]]>First, something I find worthy of mention is that only 6 people have submitted a correct solution. It would be interesting to know the total number of people that submitted a solution, but I guess it is not much larger than that. The riddle was perhaps quite difficult depending on the approach to the problem (more on that later), but it didn’t require very advanced knowledge. Just basic knowledge of the mathematical description of Keplerian orbits (so you can do as me and look up the formulas in Wikipedia). Therefore, I would like to encourage more people to have a go with the next riddles. The next one will be published on June 30, which is Asteroid day.

My solution is now published in this Jupyter notebook. If your read the introduction of this solution, or the introduction of the solution published by NEOCC, you’ll see that after writing everything in mathematical terms, the riddle boils down to solving a constrained optimization problem.

At first, I tried to solve this problem analytically. This seems quite difficult if at all possible, and indeed I filled many pages with different approaches using Lagrange multipliers before giving up and doing it numerically with the computer.

My numerical approach actually boiled down to computing the maximum of the function\[f(E_0) = \frac{\sin E_0 + E_0 – \frac{\pi}{2}}{\sin E_0 ( 1 – (E_0 – \frac{\pi}{2}) \tan E_0))},\]where \(E_0\) denotes the eccentric anomaly at the moment when the mean anomaly is \(\pi/2\). I was quite lazy and found a numerical approximation to the maximum by evaluating \(f\) over a find grid of points (which you have to do anyway if you want to plot things to gain some insight into the problem), and picking the maximum over this grid.

Looking now at NEOCC’s solution, I see that they have made more involved calculations, since they have also introduced the true anomaly \(\theta\) and they approach the problem by using the fact that the derivative of the function to maximize must vanish at the maximum point. After some calculations, they arrive at\[\tag{4}0 = 1 + \cos \theta_0 – \sqrt{1-\varepsilon^2}\varepsilon\sin \theta_0 \frac{\sin E_0}{1 – \varepsilon \cos E_0},\]where \(\theta_0\) is the true anomaly at the moment when the mean anomaly is \(\pi/2\), and \(\varepsilon\) is the eccentricity (NEOCC uses a slightly different notation; here I’m keeping to the notation I used in my solution). This equation must be understood as an equation for \(\varepsilon\), since \(E_0\) is defined implicitly in terms of \(\varepsilon\) by Kepler’s equation\[E_0 – \varepsilon \sin E_0 = \frac{\pi}{2},\]and \(\theta_0\) can be written as an explicit function of \(\varepsilon\) and \(E_0\), since\[\tan^2 \frac{\theta_0}{2} = \frac{1+\varepsilon}{1-\varepsilon} \tan^2 \frac{E}{2}.\]

Now, NEOCC’s solution involves finding numerically a value of \(\varepsilon\) that satisfies (4) by solving for \(E_0\) and \(\theta_0\) as indicated above. This involves solving Kepler’s equation by iteration.

So drawing upon NEOCC’s idea to find the point where the derivative of the function to maximize vanishes, and trying to improve my laziness, we can follow my approach and compute and solve \[f'(E_0) = 0.\]The derivative \(f'(E_0)\) has a rather ugly expression but is straightforward to compute explicitly and only involves trigonometric functions in \(E_0\). Afterwards, your favourite root-finding numerical method can be used to find the point where the derivative vanishes. So maybe this will give an algorithm that converges to the maximum rather quickly, which is useful if we want to solve to many decimal places (actually several numerical methods for maximum finding are disguised root-finding methods for the derivative). Note that this doesn’t involve solving Kepler’s equation.

So in the end I like my approach more than NEOCC’s. By using \(E_0\) instead of \(\varepsilon\) as the independent variable I don’t need to solve Kepler’s equation, and I think that introducing the true anomaly into the problem only complicates calculations more. One thing is clear: it seems that it’s not easy to solve this without doing some numerical approximation in one way or another, so I would be gladly surprised if someone can show us another approach.

By the way, reviewing my solution I’ve found a silly mistake. When scanning the NEOCC database for objects that spend approximately 50% of their time inside 1.3 au and have an aphelion as large as possible, I limited the aphelion search to those between 1.5 and 1.6 au, while actually the maximum aphelion can go up to 1.603 au, as I showed in the first part of the riddle.

In this respect, I really like NEOCC’s simple and tidy solution for the second part of the riddle. But they don’t tell us how to compute the percentage of time below 1.3 au using the values in the database! Well, this is simple. If we denote by \(C\) the quotient between the aphelion and 1.3au, we have\[C = \frac{1+\varepsilon}{1 – \varepsilon \cos E},\]where \(E\) denotes the eccentric anomaly at the moment when the 1.3au radius is crossed. Since the eccentricity \(\varepsilon\) is also in the database, we can get \(E\) from this equation and then find the corresponding mean anomaly as\[M = E – \varepsilon \sin E.\]Finally, \(M/\pi\) gives us the fraction of time that the object spends inside the 1.3au radius.

]]>In the two figures below you can see the BER curve I got and the BER curve in my older post. The high SNR behaviour is nearly the same, but for low SNR the BER curve I got is much worse. The curve only starts “behaving well” at around 11dB Eb/N0, where the former curve started behaving well at maybe 7dB Eb/N0.

The reason for the change in regimes between a flat portion of the curve where the BER is 0.5, a transition region, and a portion where the demodulator “behaves well” is the clock recovery loop. When the SNR is below a certain threshold, the clock recovery loop doesn’t lock, so we are essentially decoding random garbage. In the transition region, the clock recovery loop barely locks, so it incurs a significant performance penalty. When the SNR is high enough, the clock recovery loop locks just fine and it doesn’t disturb the BER.

The threshold at which the clock recovery loop can lock is directly related to the clock bandwidth. A larger bandwidth will make locking faster, but will also bring the locking threshold up. The current default clock bandwidth in gr-satellites is 0.06. Repeating the simulation with a clock bandwidth of 0.02 I get the figure below, which is pretty close to the old figure.

Now, choosing the clock recovery bandwidth is a tradeoff between fast lock (which is needed to get packets with short preambles or unstable clocks) and low SNR performance. I have tried to optimize the default parameters for gr-satellites with many real world recordings, but in particular cases other parameter choices will work better.

I also find that the change in locking threshold from 7dB to 11dB Eb/N0 is perhaps not so significant as it might seem. Below 11dB Eb/N0 the BER is still pretty high even if the clock recovery has locked, so unless there is a good FEC cleaning all these bit errors, it is impossible to decode. Thus, we don’t really care whether the clock recovery can lock or not. In fact, many FSK Amateur satellites are still using AX.25 unfortunately. Since AX.25 has no FEC, anything below 13dB Eb/N0 or so is a no-go for these satellites.

]]>As I already suspected in my other post, the 1kbaud beacon data is differentially encoded. The data is structured in frames of 61440 bits, which matches the detail of 61K bits that Scott found in some technical report (this one was about EMC testing, so the 61K bit frame size was only mentioned in passing).

The first 100 bits of each frame are a synchronization pattern which is composed by 18 repetitions of the pattern `11110`

followed by two repetitions of the pattern `00001`

, as shown here.

The figure below shows the correlation of the stream of (differentially decoded) bits, where we can see that the correlation pattern appears every 61440 bits.

The frames are too long to plot completely in a raster plot, so the figure below shows the first 1024 bits of each frame (as a row). The remaining part of the frame looks just like the right hand side of this plot.

Now, we see three distinct regions in the plot above. The first is the synchronization marker. The second region, between bits 100 and 600, corresponds to 100 bits sent at 200baud, so each group of 5 bits has the same value. Besides this, the data in this region looks random. The third region starts at bit 600 and continues until the end of the frame. This region also looks random and is sent at 1kbaud.

This is all I’ve been able to find so far. Since the data looks pretty much random, it might be that it is encrypted (which wouldn’t be at all surprising in a military communications satellite), or at least scrambled.

It is quite interesting the fact that 100 bits at the beginning of the frame are sent at 200baud, most likely to increase reliability. What could this data be? It certainly has to be something important. It might be that failure to receive this part renders the rest of the very long frame useless. If cryptography is used, it might be an IV, but I’m just speculating wildly here.

I’ve updated the Jupyter notebook with this plots. The data is also in the same repository, so maybe someone wants to have a go at this and see what they can find.

]]>What makes finding the convolutional encoder specifications quite easy is that the encoder is systematic, which means that its input is a subset of the output. As we saw yesterday, the input bits, which we will call \(x_n\) are sent as the even bits of the output, while the odd bits of the output, which we will call \(y_n\), are computed in terms of \(x_n\) by the encoder.

The general formula for such a convolutional encoder is simple:\[y_n = \sum_{j = 0}^{k-1} a_j x_{n-j},\]where all the arithmetic is done in \(GF(2)\). When applying this to a real world system, the data starts at some point, say at \(n = 0\), and we put formally \(x_n = 0\) for \(n < 0\) so that the formula above makes sense. In the usual implementation of the convolutional encoder by using a shift register, this corresponds to starting with the register filled with zeros.

In the formula above, it is usually assumed that \(a_{k-1} \neq 0\), so that the number \(k\), which is called the constraint length, is uniquely defined. Assume for a moment that we now the number \(k\), or at least have an upper bound \(l\) for it. Then we can try to collect \(l\) row vectors formed by \(l\) consecutive input bits \(v_j = (x_{n_j}, x_{n_j+1}, …, x_{n_j+l-1})\), \(j = 1,\ldots,l\), in such a way that \(\{v_j\}\) are linearly independent. In the unlikely case that this is not possible, then the conclusion is that the input has not “enough variety” to determine the convolutional code uniquely.

If we write the matrix \(A\) which has the vectors \(v_j\) as rows, put \(\alpha = (a_{k-1},\ldots,a_0)^T\) and \(\beta = (y_{n_1+l-1}, y_{n_2+l-1}, \ldots, y_{n_l+l-1})^T\), then \(A\alpha = \beta\). Since the matrix \(A\) is invertible and \(\beta\) is known, we can solve this linear equation to find \(\alpha\), which gives us the convolutional code specification.

What about finding an upper bound \(l\) for the constraint length? Well, first of all maybe this isn’t even necessary. Constraint lengths are usually not very large (the CCSDS convolutional code has a constraint length of 7), so we can proceed by guessing a large enough value for \(l\), say \(l = 20\). This is inefficient, but it will always yield a candidate solution. The candidate solution needs to be checked against the full set of input data \(x_n\) and output data \(y_n\). If it works, then we’ve found the convolutional code. If it doesn’t work, then the constraint length is actually greater than \(l\), so we can use a larger guess for \(l\) and try again.

Alternatively, we can use the following approach. We consider a large integer \(M\) (that should be guaranteed to be larger than the constraint length), an index \(t\), and the vectors \(\gamma = (y_t, y_{t+1}, \ldots, y_{t+M})^T\), and \(w_j = (x_{t – j}, x_{t-j+1},\ldots, x_{t-j+M})^T\), for \(j = 0, 1, \ldots\). Note that \(\gamma\) is a linear combination of \(w_0, \ldots, w_{k-1}\). So to find \(k\) we start only with \(w_0\) and keep adding vectors \(w_1, w_2, \ldots\) as necessary until we obtain a set of vectors such that \(\gamma\) is a linear combination of them. Note that this condition corresponds to whether an overdetermined linear system has a solution, so it can be checked by the usual methods of Gaussian elimination or computing determinants.

In the case of the DSCS-III beacon, finding the constraint length is more simple, since the data has lots of zeros. In this case, we can look at one point in the input where a one appears followed by many zeros (looking at the figures in the last post, the bit at position 80 is a good choice), and then look at which position in the output after this point a one appears for the last time (which happens at position 89). By doing so, we find the length of the shift register, which is precisely the constraint length.

So for DSCS-III the constraint length is 10. It is easy to choose a linearly independent set of 10 vectors formed by 10 consecutive input bits (choosing them about the point where ones start to appear in the input is enough). By doing so, we can solve the linear system introduced above and find that the convolutional code is\[y_n = x_n + x_{n-1} + x_{n-2}+ x_{n-5} + x_{n-6} + x_{n-9}.\]I have checked that this is correct by going over the full dataset. Since all the frames start by and end with a bunch of zeros, we need not care about (and cannot distinguish) whether this code is applied in a streaming fashion, or separately per data frame. I have updated the Jupyter notebook to include the relevant calculations.

The fact that the convolutional code was systematic has made our life much easier. Usually, convolutional codes are not systematic, so we don’t have direct access to the input of the encoder. Still, similar linear algebra techniques can be used to reverse-engineer the code. For more details see this post by Gonzalo Carracedo EA1IYR about some work regarding reverse-engineering of convolutional codes that he originally presented in STARcon 2019.

]]>Scott’s tweets included a very impressive and interesting find: a Master’s thesis about a DSCS-III beacon decoder made by James Coppola in 1992. The thesis contains a wealth of information about the beacon, as well as the complete C source code for the decoder.

Scott has also been kind enough to share with me some recordings that he made of the beacon, so in this post I’ll be looking at these and how they relate to the information in the thesis.

The modulation on its own is not very interesting. As you can see in Scott’s tweet below, the beacon has a central narrowband carrier and two other narrowband carriers spaced some 75kHz away from the centre frequency. The central carrier is an 800baud BPSK signal, while the carriers on the sides are 1kbaud BPSK signals. The thesis speaks only about the 800baud signal.

The 800baud signal is demodulated with the GNU Radio flowgraph `dscs_demod_800baud.grc`

, which is just a simple BPSK demodulator that outputs soft bits. The soft bits are then examined in a Jupyter notebook.

The thesis explains that the 800 baud beacon is organized in 8-bit frames. Each of the bits in the frame can be thought of as a TDMA channel, as shown in the table below.

The first channel contains a constant stream of zeros, so it can be used for frame synchronization and to resolve the BPSK phase ambiguity. Channels 1-5 contain PN sequences of 25, 27, 29, 31 and 32 bits. Since these lengths are pairwise coprime, the combination of the five sequences takes 19418400 frames to repeat (which corresponds to 53.94 days). Channel 6 contains the inverse of channel 4 (I’m not so sure about the reason behind this design decision).

Now, the thesis says that channel 7 contains the XOR of the telemetry and channel 2. This is stated both in the table shown above and in the main text. However, I think this is an error (or at least USA-167 does it differently), since in the recordings that Scott has sent me channel 7 is actually the XOR of the telemetry and channel 3.

The figure below shows the soft symbols obtained from the demodulator. We see that the SNR is good enough to guarantee that there are no bit errors.

I have grouped the symbols in frames of 8 symbols and performed the average of each of the bits in the frame (i.e., of each channel) to check which one is bit 0. The figure below shows the averages, where I have already aligned the stream so that frame bit 0 is constantly -1, as indicated by the table above.

It is interesting to compute the autocorrelation of channels 1 through 5 to check that they contain a repeating sequence of the length indicated in the table above.

I have also checked that channel 6 contains the inverse of channel 4.

Regarding the telemetry data, the thesis says that a telemetry frame has 100 bits. These 100 bits are convolutionally encoded with r=1/2 to produce 200 bits that go in channel 7, so a telemetry frame takes 2 seconds to transmit. As I said, the information in the thesis that the telemetry stream in channel 7 is XORed with channel 2 is incorrect. If we take the XOR of channels 2 and 7 and compute the autocorrelation, we only obtain weak peaks. However, if we take the XOR of channels 3 and 7 instead, then we get strong autocorrelation peaks at lags which are integer multiples of 200 bits.

The description in the thesis about synchronization to the 200 bit data frame is done is shown below.

I find this paragraph a bit ambiguous to understand. We can see that the 25 and 32 bit sequences have a single run of 5 ones each (and no longer runs of ones). These runs of 5 ones are transmitted simultaneously every 800 frames, owing to the coprime periods of the sequences. So we learn that whenever the runs of 5 ones in both sequences coincide, there is the start of a data frame. A data frame is only 200 frames long, so there are also data frames starts elsewhere. This is what is meant by “The message synchronization pulse is present every eigth repeat of the 11111 state of the 25-bit sequence”.

Now, it is not completely clear to me from this paragraph whether the first bit of the data frame is transmitted in the next frame after the coincident runs of 5 ones, or in the last frame of the run, or something else. It turns out that the second option is the correct one.

The figure below shows the data frames already synchronized, with one 200 bit frame per row.

The r=1/2 convolutional code is something rather peculiar. In contrast to the usual convolutional codes, it is systematic. This is said in the thesis only in passing as:

It should be noted that the GPM is the only demodulator/decoder which takes advantage of the convolutional encoder by using it in conjunction with a Viterbi decoder to improve the bit-error-rate performance. The other two receivers just ignore the extra bit.

This is very convenient, since it means that we can throw away the extra bit added by the encoder and obtain the 100 bit data frame, just as the “two other decoders” do. It turns out that the even bits are the data bits, and the odd bits are the FEC bits added by the convolutional encoder. To see this, we split the 200 bit frames into the two possible 100 bit frames, getting the figures below.

In the even bits we see that most of the data is static (it is the same in all the frames) but there is also something that looks pretty much like a binary counter. In contrast, the behaviour of the odd bits is more complicate and one can even imagine how the effect of the binary counter propagates through the shift register of the convolutional encoder.

A closer inspection at the data for the binary counter reveals that it is not a single counter, but several cascaded counters (which reset at values which are not powers of two). Indeed, the table below shows that the first 48 bits of the telemetry frames are essentially a huge clock. The way the fields are written suggests that BCD is used, and this is indeed the case. It is interesting that the clock value has 5us resolution but it is only sampled every 2 seconds.

Relating this table to the figure above that shows the data bits, we see that the bits that move as a binary counter correspond to the seconds and minutes. The hours and days do not change, since the recording is not that long, and the milliseconds and microseconds do not change either, since the clock is measured exactly every two seconds.

I have checked that by using the offsets given in this table to read the minutes and seconds I get a counter that increments in steps of two (by the way the least significant bit of the seconds is always set, so the clock counts odd seconds). This validates that I have aligned to the start of the frames correctly.

Pages 9 through 12 in the thesis contain tables describing the remaining bits in the telemetry, but I haven’t explored their values.

Regarding the 1kbaud subcarriers, I have only taken a quick look at the autocorrelation, which doesn’t reveal any obvious patterns. There is a strong negative autocorrelation at a lag of one symbol. This suggests that perhaps the stream is differentially coded and the data stream is unbalanced (there are more ones than zeros). Scott shows that there is an audible event every minute approximately, so at least it should be possible to see what causes this event.

]]>The QO-100 WB transponder allows much wider signals that can be used to achieve a ranging resolution of one metre or less. This weekend I have been doing my first experiments about ranging through the QO-100 WB transponder.

Since this post is rather long, I will start with a brief summary. I have designed a waveform that is based on the Galileo E1C Open Service pilot signal but with a chip rate four times slower with the goal of using it to perform two-way ranging of Es’hail 2 by transmitting this waveform through one of the 1Mbaud DVB-S2 channels in the WB transponder.

Synchronization of the RX and TX streams of the LimeSDR mini that I’m using for this experiment has been somewhat tricky, but seems more or less solved now.

I have made Python code from the scratch to acquire and track this ranging signal, and to compute round trip time measurements. Over the air tests by doing transmissions of a few minutes through the transponder show that the system is working well and produces a ranging result that matches the GEO location of Es’hail 2. Ignoring systematic errors, the precision of this ranging method is on the order of one meter or better.

The material for this post can be found in this repository.

I have decided to use a ranging waveform that is based on the Galileo E1C Open Service pilot signal. The reasons for this design choice are the following. First, it is something I know very well from my dayjob; second, with this I want to try to get more Amateur radio operators interested into GNSS in general; third, maybe some software tools intended for Galileo can be repurposed to use them with this waveform; and finally, the spectrum of this signal has two sidelobes, so this prevents annoying other operators that may mistake the ranging signal for a DVB-S2 signal (which represents almost all the traffic through the WB transponder) and fruitlessly try to decode it as such.

The design constraint is that the waveform must fit into a 1Mbaud DVB-S2 channel. There are two reasons for this. First, while a wider signal might be used over the WB transponder for short experiments (there are approximately 7.5MHz of the transponder which are not occupied by the DVB-S2 beacon), such an atypical use would need to be fairly limited in time, and coordinated with the rest of the transponder users. By designing a waveform that acts as a 1Mbaud DVB-S2 signal from a spectrum management point of view, it is easier to share the transponder with all the other users, by following the usual operating practices. Second, the Beaglebone black ARM computer I have in my QO-100 groundstation is limited to around 2Msps full-duplex with the LimeSDR mini. If I want to use a wider signal, I would have to upgrade the hardware.

The Galileo E1C signal is a BOC signal with a chip rate of 1.023Mchip/s (for those that know the details, the E1C signal is actually a CBOC signal, but here I’ll pretend it’s just BOC). BOC is a terminology used in GNSS to denote a BPSK signal in which the chips are Manchester encoded. This produces two sidelobes in the spectrum. As the ranging performance of a waveform is determined by its RMS bandwidth (the standard deviation of its power spectral density), the sidelobes of a BOC signal serve to send energy away from the centre of the modulation, increasing the RMS bandwidth and so the ranging performance. For this reason, BOC signals are used in many modern GNSS waveforms, such as the Galileo E1 signals, the GPS L1C signal, the Beidou B1C signal, and the QZSS L1C signal.

The chip rate of 1.023Mchip/s is too high to fit into a 1Mbaud DVB-S2 channel. In fact, the bandwidth of the main lobes of the BOC modulation is 4.092MHz. However, we can divide the chip rate by four and use a chip rate of 255.75kHz, so that the bandwidth of the main lobes becomes 1.023MHz.

GNSS signals often use unfiltered pulse shapes, in order to improve the ranging performance. Therefore, their total bandwidth is very large, since they have many sidelobes. To obtain a ranging waveform for the QO-100 WB transponder, we need to filter out the sidelobes. In the transponder bandplan, 1Mbaud channels have allocated 1.5MHz of spectrum, in order to leave some guard band for adjacent DVB-S2 signals with an excess bandwidth of 0.35, which have a bandwidth of 1.35MHz. For the design of the ranging waveform, we take this bandwidth as a guide and filter the waveform to a bandwidth of 1.35MHz.

The E1C signal uses a PRN of 4092 chips and a secondary code of 25 symbols. This means that each of the repetitions of the PRN is multiplied by one of the symbols of the secondary code, so the whole chip sequence repeats after 102300 chips. The PRNs are memory codes, meaning that they are not generated algorithmically. They have been found by a search process that minimizes autocorrelation sidelobes and cross-correlation against other PRNs in the set.

The secondary code of the E1C signal repeats every 100ms. Since we are using one fourth of the chip rate, our ranging waveform repeats every 400ms. Since round trip time to GEO is on the order of 260ms, our waveform can perform unambiguous round trip time measurements.

For using a single ranging waveform, an LFSR would be ideal as the PRN, since it has minimal autocorrelation sidelobes. However, I think it is interesting to allow for the possibility of having several simultaneous ranging signals transmitted by different stations, so I have decided to use one of the PRNs for Galileo E1C.

Since the ranging waveform uses different a carrier frequencies and chip rate in comparison to the real Galileo E1C signal, interference to Galileo equipment is very unlikely. But even so, I have decided to use PRN 42, which is not to be used by any of the real Galileo satellites. The Galileo signals have a set of PRNs numbered 1 through 50, but only PRNs 1 through 36 are to be used in the real satellites.

To sum up, the ranging waveform is exactly the same as the Galileo E1C signal for PRN 42, but with a chip rate of 255.75kHz, so everything runs 4 times slower, and filtered to a bandwidth of 1.35MHz. In the Signal generation Jupyter notebook I have included all the details about the generation of the signal. A sample rate of 1.5Msps is used throughout this post. The figure below shows the spectrum of the filtered signal. Note that the filter lets in part of the secondary lobes.

Below we can see the correlation of the filtered waveform against an unfiltered replica, compared to the autocorrelation of the unfiltered waveform. We see that the difference is small, so not much performance is lost by filtering the waveform.

A very important thing when using an SDR transceiver for ranging is to be able to synchronize the RX and TX sample streams. Since ultimately we are measuring the delay between the transmitted and received waveforms, we need to know the delay between the sample streams, if any. Most SDRs use the same sample clock for RX and TX, so when the RX and TX are already started, their relative delay will be constant (unless we lose samples).

My idea was to first set the RX channel to the same frequency as the TX channel, in order to receive the transmit waveform through leakage. This can be used to determine the offset between the RX and TX sample streams (which should be an integer number of samples). After recording the TX waveform for a few seconds, we would switch the RX frequency to tune it to the transponder downlink frequency (or rather its IF after downconversion by the LNB) without stopping the sample stream. Then the round trip time through the transponder could be measured. However, this didn’t work as expected. It turns out that changing the RX frequency (for example by calling `LMS_SetLOFrequency()`

) interrupts the sample stream in an unpredictable way.

Rather than trying harder to make this approach work, for instance by finding a lower level way to change the RX frequency, I decided to try to obtain a repeatable RX-TX synchronization behaviour by using hardware timestamps, inspired by the `dualRXTX.cpp`

LimeSuite example. It turns out that every time that you get a buffer of RX samples using `LMS_RecvStream()`

, you can get the hardware timestamp for the first sample in this buffer in an `lms_stream_meta_t`

structure. This hardware timestamp is just a sample counter which is shared between RX and TX. Then, when submitting a buffer of TX samples using `LMS_SendStream()`

, we can use an `lms_stream_meta_t`

to specify the hardware timestamp at which the first sample of this buffer should be transmitted.

So we do the following with this hardware timestamping functionality. We get our first buffer of RX samples and take note of the hardware timestamp. Then we increment this timestamp in `1024*128`

samples and send the first TX buffer marked to be transmitted at this future timestamp. The delay of of `1024*128`

samples is so that the call to `LMS_SendStream()`

can get through completely before the required time for start of TX arrives. Instead of starting the transmission by the first sample of the ranging waveform, we skip the first `1024*128`

samples. Therefore, the effect is as if the transmission of the ranging waveform had started at the same time that the first RX sample was taken. After sending this timestamped TX buffer, we do not use hardware timestamping anymore and proceed normally, getting samples from the RX buffer and sending samples into the TX buffer in order to prevent overruns or underruns.

There is another quirk regarding this synchronization scheme. For some unknown reason, shortly after starting the RX stream, a few RX packets are dropped, as reported by the `droppedPackets`

field in the `lms_stream_status_t`

. This causes a few thousands of samples to be missing from the RX stream, which offsets the TX and RX samples. What I do to get around this problem is to drop to the floor the first 48300 RX samples, so that the `droppedPackets`

happen as part of these ignored samples. After this, I start recording RX samples and bootstrap the RX-TX synchronization as described above.

Even after all this, the RX-TX synchronization is not perfect. By tuning the RX channel to the TX frequency and receiving the TX ranging waveform through leakage, I have determined that the RX and TX streams are still offset by 93 samples. Fortunately, the effect is repeatable almost always (on rare occasions I find an offset of 92 or 94 samples instead). I correct for this offset later by dropping the first 93 RX samples.

The software I use to drive the LimeSDR mini can be found in `limesdr_ranging.c`

. This is still in a rather crude state, but it works. It is based on my `limesdr_linrad.c`

. It reads the TX waveform from a file and keeps looping it, and streams RX samples through the network by using the Linrad protocol. The frames are received and recorded in another PC by using socat as

socat UDP4-RECV:50100,reuseaddr - | pv > /tmp/ranging.int16

Linrad is used to monitor the received signal.

After the downlink signal has been recorded, the ranging waveform is acquired and tracked. I have made my own implementation of these algorithms, but most likely existing tools for Galileo can be used by lying to them and saying that the sample rate of the recorded signal is 6Msps rather than 1.5Msps (to compensate for the fact that the ranging signal is four times slower than Galileo E1C). For instance, the great GNSS-DSP-tools Python scripts by Peter Monta can be used.

The acquisition is rather easy, since the signal is much stronger than the typical GNSS signal. Therefore, an FFT-based correlation with a single PRN repetition (16ms coherent integration time) is more than enough to have good acquisition SNR. Moreover, the range of Dopplers that needs to be swept is small (see this post, for instance, for the study of Es’hail 2 Doppler).

The acquisition produces frequency and delay estimates that are used to start the tracking. Tracking is done in the same way as for GNSS signals. We start by performing tracking for the first 100 symbols using a Costas PLL, in order to find the correct secondary code phase. Then the tracking is restarted from the beginning of the recording and we use a pure (non-Costas) PLL and secondary code wipeoff. A coherent integration period of 16ms is used for both the PLL and the DLL, and the DLL uses a coherent discriminator with an E-L spacing of 0.1 chips. I haven’t adjusted the loops yet. I have simply eyeballed some coefficients by trial and error, without taking care for critical damping, etc.

The code for acquisition and tracking can be seen in the`tracking.py`

Python file.

After doing several loopback tests receiving my own signal, I passed to over the air testing through the QO-100 WB transponder. The first over the air test was done at around 20:00 UTC on 2020-06-13. The WebSDR screen during the test can be seen below.

A transmit frequency of 2403.75MHz was used, thus occupying the first 1Mbaud DVB-S2 channel. Care was taken not to interfere with other stations by selecting a channel that was free at that moment. The transmission lasted only a few minutes, which is more than enough to measure round trip time and observe its drift with time.

For the test I used my QO-100 groundstation, which currently has a 24dBi linearly polarized grid parabola as transmit antenna, a 100W PA (though I wasn’t driving the PA to saturation and don’t know exactly how much power I was using) and a 1.2m offset dish as receive antenna.

Unfortunately, for this test I still hadn’t detected the problem of sample stream interruption when the RX frequency was changed, so the round trip time I computed was all wrong. Therefore, I made additional over the air tests around noon on 2020-06-14.

To identify my transmissions, as mandated by the Amateur radio regulations I’m using a very crude but perhaps effective method. I’m keying on and off the transmission of the ranging waveform by using my callsign in Morse code. I do this when I’m not recording the receive signal for ranging measurement, as for this I need a steady signal.

The figure below shows the spectrum of the downlink signal for a test done around 11:56 UTC on 2020-06-14. The (S+N)/N is approximately 2.5dB. The peak in the center is the TX DC spike (TX LO leakage plus ADC voltage offsets). For RX there is no DC spike, since I’m using the LMS7200M NCO to place the DC spike outside the passband.

The correlation IQs obtained by tracking the downlink signal with the Costas PLL can be seen below. The repeating secondary code is clearly visible. The keen reader might already get an estimate for the round trip time from this figure by observing in which phase the secondary code appears.

After the phase of the secondary code has been determined, tracking with a pure PLL and secondary code wipeoff is done. The IQs are shown below. The slight droop in the in-phase component is probably due to changes in the gain of my PA as it gets hot.

The measurements generated by the tracking are saved to an npz file and then analysed in this Jupyter notebook. The figure below shows the tracking frequency. It is not zero for a number of factors: Doppler (which only accounts for approximately 30Hz), transponder LO error (which in my studies of the NB transponder doesn’t seem that large), and offsets in my system such as fractional-N PLL errors, LMS7200M NCO quantization error, etc.

Next we plot the carrier phase, from which we’ve removed its linear trend in order to be able to see something. Since the transponder LO affects the downlink signal phase, using the phase observations is not very useful unless we had two stations transmitting simultaneously and we subtracted the observations corresponding to each of them.

Finally, we show the one-way range, which is defined as the round trip time divided by two and multiplied by the speed of light. We do not include the first measurements, as the round trip time starts off by its value coming from acquisition and then moves quickly several tens of metres to converge to the appropriate value.

Note the way the plot is done in Matplotlib with an offset shown in the corner upper left corner. The one-way range is actually around 38234322.0 metres. The units of the scale are metres, so the code tracking noise is on the order of one metre RMS without even having to tune the DLL loop carefully.

It is interesting that the one-way range is almost constant throughout the 3 minute recording. This is because the time of closest approach from Es’hail 2 to my station is around 12:00 UTC. When this recording was done, the Doppler was near zero and the range changed very slowly.

To check this effect, I made a second test roughly one hour later, at 12:48UTC. The correlations for the second test can be seen below. They show a similar behaviour to the first test.

Below we show the tracking frequency. It has decreased 27Hz in comparison to the first test. This can’t be caused by a change in Doppler, as it doesn’t change that much in one hour. I think that I may have used a different 1Mbaud DVB-S2 channel for this test, since the channel where I did the first test was now in use. If this is true, then this can explain the difference, since changing the TX and RX frequencies involves a new set of fractional-N PLL errors.

The phase observations don’t really show much, as it was the case during the first test.

The one-way range now shows an average value of 38234442.3 metres, which is 120.3 metres more than in the first test. Since there was a difference of approximately 52 minutes between the tests, this gives an average rate of 38.6mm/s. During this test we see a rate of change of one-way range of approximately 52.3mm/s. This matches the spacecraft exiting its point of closest approach to my station and starting to move away. Near the point of closest approach, the range evolution with respect to time is approximately a parabola.

In these ranging measurements there are the following systematic errors. The most important error comes from the fact that the round trip time measurement is ultimately determined by the sample rate of the SDR. My whole groundstation is locked to a 10MHz GPSDO, but even so the sample rate is not exactly 1.5MHz because of the fractional-N PLL errors involved in the synthesis of the sample clock: a 27MHz DF9NP PLL, and the LMS7002M. Fractional-N PLLs multiply their input frequency by a fraction that (usually) has a large numerator and denominator, and which is quite close to (but not exactly) the nominal multiplication ratio. For precise results, the actual PLL multiplication ratio needs to be determined. The errors can be on the order of ppm’s, which when related to the range measurement can give an error of a few hundreds of meters.

Next, there is the delay of the WB transponder. This is unknown. Measuring the transponder delay in orbit is not easy, since the orbit of the satellite and all other sources of error need to be computed to better precision than the transponder delay. In a similar manner, there are the delays associated with my groundstation hardware and the fact that my transmit and receive antennas are a few metres apart.

There are also atmospheric errors. The ionospheric delay plays a small role at the 2.4GHz uplink, with a contribution of 7cm per TECU, and an insignificant role at the 10.5GHz downlink, with a contribution of 3.6mm per TECU. The tropospheric delay is thus more important. Since the troposphere is non-dispersive, the effect doesn’t depend on the frequency, and usually amounts to a few meters.

While it is not really an error, but rather a particularity of this measurement, one must take into account the fact that light doesn’t travel in straight lines in ECEF coordinates, which are the natural coordinates for this GEO ranging measurement. Thus, the Earth rotation is usually introduced as a correction to the measurement. See this post for details.

Finally, there are relativistic errors, but I think they are very small and can be ignored.

]]>The modified GMAT version and accompanying GMAT scripts for this project can be found in the gmat-dslwp Github repository. This post is an account of the work I’ve made.

The script `simulate-vlbi-june-2019.script`

is used to showcase all the new functionality. It can be used to simulate the VLBI observations that were done during the first week of June 2019 and then run orbit determination with the simulated observations. The script includes the groundstations of PI9CAM (the Dwingeloo radiotelescope in the Netherlads), Shahe (in Bejing, China), Harbin (China) and Wakayama University (Japan).

The simulation runs between 2019-06-03 and 2019-06-08, which is when the observations were made. It computes the delta-ranges and delta-range rates for all the possible baselines whenever DSLWP-B is in common view of the groundstations. I have made a Jupyter notebook to plot the results of these simulations. The plots can be seen in the figures below.

An error model with the typical noise we saw in the VLBI observations is used for simulation. This has a sigma of 1.2km for delta-range and 0.2m/s for delta-range rate.

For estimation we use AcceptFilter to select only the observations corresponding to the slots when the Amateur payload was active. These are a 2 hour slot on each observation day.

The figure below shows the results of running estimation with a batch filter using the simulated observations. The delta-range residuals of one of the baselines can be seen in the plot.

In my previous post I gave the precise definition for delta-range, taking appropriate care about the light-time corrections. The definition for delta-range rate is easier, as it is the incremental quotient of delta-range. If \(d(t)\) denotes the delta-range at observation time \(t\) (recall that the observation time is defined as the instant that the signal arrives to the reference station), and \(T\) denotes the “Doppler count interval”, then the delta-range rate at observation time \(t\) is\[\frac{d(t) – d(t-T)}{T}.\]

The derivative computation for the `DeltaRangeAdapter`

is based on the computation done for the `TDRSDopplerAdapter`

. Since a delta-range is the difference of two ranges, its derivative is just the difference of their derivatives. However, there is an important peculiarity regarding the state transformation matrices and how GMAT computes derivatives with respect to velocity.

Let’s say we have a range between a spacecraft and a groundstation and we want to compute the derivative with respect to the position of the spacecraft. Then this is done basically by projecting onto the line of sight the unit vector corresponding to the direction in which the derivative is taken. However, if we want to compute the derivative with respect to velocity, then it matters if the range observation timestamp is the transmission time by the spacecraft or the reception time by the groundstation.

If the timestamp is the transmission time, then the derivative is zero, as nothing changes about the range measurement with respect to the spacecraft velocity. If however the timestamp is the reception time, then the position of the spacecraft at transmission time depends on the spacecraft’s velocity, and so the derivative is not zero in general.

In a delta-range calculation the situation is a bit tricky. For the reference leg, we timestamp at reception time, so everything goes as expected. However, for the other leg the timestamp is taken at transmission time, and taken to coincide with the transmission time for the reference leg. If we don’t do anything special, the derivative with respect to velocity of the other leg will be computed incorrectly.

The trick I have done is to copy over the state transformation matrix from the reference leg into the other leg. This gives the correct calculation, because it includes the light-time of the reference leg, which is also what should be used when computing the derivative of the other leg with respect to velocity.

I have validated that with this trick the derivative calculation is correct by doing a detailed simulation as in my previous post, where I use debug macros to print out intermediate values. By changing slightly the state vector of the spacecraft (both for position and for velocity) I can check if the changes in the delta-range match what predicted by the derivatives.

Delta-range rate is computed following the approach of `TDRSDopplerAdapter`

. As shown in the mathematical definition, there are two “paths” subtracted: the end-path, which is timestamped at the observation time \(t\), and the start-path, which is time timestamped at \(t-T\). The way this is done so that derivatives are correctly computed in GMAT is to timestamp both paths at \(t\) and include the \(-T\) in the start-path as a receiver delay. This is done by using the method `SetCountInterval()`

to set the correct value for \(T\).

For the delta-range rate computation, `SetCountInterval()`

only needs to be called for the reference leg of the S-path, since the other leg has its timestamp already affected by the receiver delay applied to the reference leg.

Using debug simulations which print out many intermediate calculations I have checked that the calculations for delta-range rate and its derivatives are correct.

Even though this project is quite usable as it stands now, I have still some things to do. First, even though I announced in my previous post that I would have per baseline error models, this isn’t working yet. The problem is that groundstations don’t accept several error models of the same type assigned to them (and a groundstation would have an error model per baseline it participates in). This behaviour can be changed, but it is implemented in the StationPlugin, so I will have to go and build and modify that plugin as well.

Then, I marked myself a few TODOs and notes as I wrote the code. None of these are probably very important, since the code seems to be working anyway, but it would be good to review them.

Finally, the code should be tested with some of the real VLBI observations we have for DSLWP-B. This will involve reviewing the correlation and measurement calculation code to make sure it complies with the mathematical definitions of delta-range and delta-range rate given here and in the previous post. Perhaps something will need to be adapted, since I wasn’t as careful with the timing of events when I implemented the correlation code (in particular, regarding the lack of symmetry between the reference and other stations).

]]>