Decoding the Roman Space Telescope

The Nancy Grace Roman Space Telescope is a NASA space telescope that carries a wide field instrument with a 300.8 megapixel visible and near-infrared sensor that provides a sensitivity comparable to Hubble with a field of view 100 times larger, and a coronagraph instrument with a camera and spectrometer used to detect and study exoplanets. It will be stationed at the Sun-Earth L2 Lagrange point. The spacecraft was launched on 2026-08-30 on a Falcon Heavy from Kennedy Space Center.

The spacecraft uses S-band for TT&C and Ka-band to transmit science data. I made an observation of the S-band downlink with two antennas from the Allen Telescope Array 18 hours after launch. In this post I comment on the observation and decode the telemetry. Detailed analysis of the telemetry is left for a future post.

As usual, I have published the IQ data for this observation in Zenodo, in the dataset Recording of Roman Space Telescope S-band telemetry with the Allen Telescope Array shortly after launch.

Observation set up

I planned the observation for 2026-08-31, between 05:00 and 09:30 UTC, which is the first time that the spacecraft would be visible from the ATA and above the 16 degree elevation mask of the antennas. During this observation the spacecraft was at a distance of roughly 200000 km. The plot below shows the azimuth and elevation used for antenna tracking, and the distance to the object.

Initially I had planned to use antenna 1a, which is one of the old feed antennas, and it is not part of the array used by the main science backend. This antenna is normally used for education and engineering observations. However, the antenna was out of order, so I used antennas 1d and 3c instead with the two USRPs of the GNU Radio backend. These antennas are normally used by the science backend, but no science observations were being conducted at the time.

I knew that S-band was used for the TT&C downlink, but I didn’t know the exact frequency. I tried to find it in the ITU Space Explorer, but I couldn’t find it. In hindsight, the spacecraft appears there and in other registries as WFIRST, which is the original name that the telescope had during development (Wide Field Infrared Survey Telescope). Peter Gülzow, from AMSAT-DL, used the Bochum 20 metre antenna to observe the spacecraft some time before my observation and shared some spectrum plots on Twitter. This was helpful because I could see that the downlink frequency was close to 2258 MHz.

After confirming that I was seeing the right signal and checking the signal bandwidth, I chose a sample rate of 10.24 Msps with a centre frequency of 2258 MHz and started recording IQ data for the rest of the observation. I only had to do a short stop at 08:50 UTC to change the output to a different disk, as the main disk I was using filled up.

Waterfall analysis

The first step in the recording analysis is to compute and plot waterfall data. I have used \(2^{16}\)-point FFTs and integrated 1562 FFTs together, giving a resolution of 156.25 Hz times 10 seconds. The following figures show the waterfall and average spectrum for Stokes I (total power) of antenna 1d. We can see the residual carrier close to 2258 MHz and narrow telemetry sidebands close to 2260 and 2256 MHz, as well as other weaker signals. There are some other signals occasionally appearing in the waterfall. These correspond to other satellites that happened to cross the antenna beam or enter through a sidelobe. For some time before 07:00 UTC the spacecraft stops transmitting.

The corresponding plots for antenna 3c are shown next. There is noticeable interference in the upper half of the spectrum. I don’t know what caused this. Because the single-antenna SNR is good enough and because antenna 1d has better SNR, I will ignore antenna 3c in what follows.

The next plots show a zoom to the frequency range occupied by the residual carrier. We can see that there is a telecommand loopback with a subcarrier of 16 kHz and an idle sequence corresponding to 2000 baud (which are very common choices). Interestingly, we can see that after the spacecraft’s signal comes back at 07:00 UTC, the telecommand deviation is higher, and then it is set back to the usual level.

The next figures show the upper telemetry sideband. As we will see below, the telemetry subcarrier is 1.7 MHz and the symbol rate is 19.5 kbaud.

The next figures show the upper sequential ranging tones. These were difficult to see in the full waterfall, but they can be seen more easily in AMSAT-DL’s tweets. The ranging clock frequency is about 507.645 kHz. In principle, Roman’s carrier frequency of about 2258 MHz is pretty close to a standard S-band downlink frequency channel obtained by defining channel 14 as 2295 MHz and the remaining channels using 10/27 MHz channel spacing (see this reference). This is normally done only for the deep space band, which is 2290-2300 MHz, but if we extend this assignment, channel -86 would be 2257.962963 MHz. Roman’s carrier, including Doppler (which would be negative), is about 2258.0765 MHz. This is close to this hypothetical channel -86, but not quite exact. According to the definitions in the DSN sequential ranging documentation, a downlink frequency of 2258.0765 MHz would correspond to an uplink of 2079.31211 MHz and get a ranging clock of 507.645 kHz (the uplink frequency divided by \(2^{12}\)).

In the spectrum above we can see the ranging clock \(f_{RC}\) in the centre. The next ranging tone, \(f_{RC}/2\), is already chopped, so it produces tones at \(f_{RC}/2\) and \(3f_{RC}/2\) (although the higher tone is visibly affected by the transponder’s filter). I will leave the detailed analysis of the sequential ranging for another post. For now I will just say that the ranging sequence begins with a 7-second \(f_{RC}\), then components \(f_{RC}/2\) to \(f_{RC}/2048\) are 4 seconds long and chopped on \(f_{RC}\), and finally there is \(f_{RC}/4096\), which is 5 seconds long and also chopped on \(f_{RC}\). The 56-second ranging sequence then restarts without any gap.

Something else that we can notice in this waterfall is that at 07:00 UTC the ranging tone power is weaker. This coincides with the interval when telecommand power is stronger. It makes sense that for some time the uplink power sharing diverted more power towards the telecommand signal, causing a weaker ranging signal in the downlink.

It is curious that the telemetry subcarrier frequency is higher than the ranging clock. This places the ranging tones between the telemetry sidebands and the residual carrier. I’m more used to the opposite situation, where the ranging clock is higher than the telemetry subcarrier, and thus the ranging tones are in the outermost part of the spectrum. There are probably good reasons behind this choice. First, in near-Earth missions (remember that near Earth is anything up to 2 million km, so the Lagrange points count as near-Earth) a 500 kHz ranging clock is common, while in deep space it is more common to use 1 MHz. Second, this telemetry downlink needs to support much higher symbol rates. For instance, AMSAT-DL has seen a higher symbol rate mode where the symbol rate is around 700 ksyms. That really requires putting the telemetry sidebands outside of the 500 kHz ranging clock, because otherwise they wouldn’t fit. AMSAT-DL has also spotted a suppressed-carrier mode at around 1.5 Msyms, but sequential ranging does not seem to be used with that mode.

Channelization

Four hours of IQ data at 10.24 Msps takes up a lot of space, and for telemetry decoding we only need the residual carrier and the two telemetry sidebands, which only occupy a small part of the spectrum. Therefore, I have channelized each of these to 256 ksps in order to save space when sharing this recording in Zenodo. Since the X polarization from antenna 1d has sufficient SNR, I have only processed this polarization. I could have used a smaller sample rate for the residual carrier, but it is simpler to have the same sample rate in the three channels.

It is convenient to do some math to check that we can process the three channels coherently and obtain the same performance as with the original IQ data. Let us consider a signal model where the transmitted signal is\[x(t) = A(t) + iB(t)\cos(2\pi f_{sc}t + \varphi_{sc}(t)).\]This corresponds to the complex baseband representation of the linearization of phase modulation with residual carrier using a subcarrier of frequency \(f_{sc}\). Because it doesn’t matter to our approach, I have allowed for the possibility that the residual carrier amplitude \(A(t)\) and the subcarrier amplitude \(B(t)\) are time-varying. We also allow the phase of the subcarrier \(\varphi_{sc}(t)\) to be time-varying, which is important because this is how telemetry is modulated using BPSK.

The received signal is affected by carrier frequency offset and carrier phase offset, so ignoring noise we see \[y(t) = e^{i\varphi(t)}x(t).\]Now we take estimates \(\widehat{f}_d\) and \(\widehat{f}_{sc}\) for the residual Doppler and for the subcarrier frequency and channelize at frequencies \(\widehat{f}_d\), \(\widehat{f}_d + \widehat{f}_{sc}\) and \(\widehat{f}_d-\widehat{f}_{sc}\), obtaining after frequency shifting and lowpass filtering\[\begin{split}y_0(t) &= A(t)\exp(-2\pi i \widehat{f}_d t + i \varphi(t)),\\y_+(t) &= \frac{i}{2}B(t)\exp(2 \pi i (f_{sc}-\widehat{f}_{sc}-\widehat{f}_d)t + i(\varphi(t) + \varphi_{sc}(t))),\\ y_-(t) &= \frac{i}{2}B(t)\exp(2 \pi i (-f_{sc}+\widehat{f}_{sc}-\widehat{f}_d)t + i(\varphi(t)-\varphi_{sc}(t))).\end{split}.\]

In the receiver we use a PLL to track the phase of \(y_0(t)\), producing \(\widehat{\varphi}(t)\), which is an estimate of \(-2\pi \widehat{f}_d t + \varphi(t)\). We denote \(\varepsilon(t) = -2\pi \widehat{f}_d t + \varphi(t)-\widehat{\varphi}(t)\) and assume that it is close to zero. We rotate the sidebands \(y_+(t)\) and \(y_-(t)\) with the PLL phase, obtaining\[\begin{split}z_+(t) &= e^{-i\widehat{\varphi}(t)}y_+(t) = \frac{i}{2}B(t)\exp(2 \pi i (f_{sc}-\widehat{f}_{sc})t + i(\varphi_{sc}(t)+i\varepsilon(t))),\\z_-(t) &= e^{-i\widehat{\varphi}(t)}y_-(t) = \frac{i}{2}B(t)\exp(2 \pi i (-f_{sc}+\widehat{f}_{sc})t + i(-\varphi_{sc}(t)+i\varepsilon(t))).\end{split}\]

Now, if we combine the two sidebands as \[w(t) = z_+(t) – \overline{z_-(t)},\]we get\[w(t) = i\cos(\varepsilon(t))B(t)\exp(i\varphi_{sc}(t))\exp(2 \pi i (f_{sc}-\widehat{f}_{sc})t).\]Since \(\varepsilon(t)\) is close to zero, the cosine term is close to one. It represents the amplitude degradation caused by residual carrier tracking errors. Therefore, essentially we get the complex baseband modulation for the subcarrier, \(B(t)\exp(i\varphi_{sc}(t))\), affected by a residual subcarrier error, which in the case of PCM/PSK/PM we can track with a Costas loop as we would do when processing the original IQ data. Also note that \(w(t)\) has correctly combined both sidebands coherently and increased the SNR by 3 dB. The amplitude compared to \(z_+(t)\) and \(z_-(t)\) has grown by a factor of two (if \(\varepsilon(t) = 0\)), while the amplitude of the noise would grow by a factor of \(\sqrt{2}\) because the noise in \(z_+(t)\) and \(z_-(t)\) is uncorrelated.

When doing this channelization, it is crucial that the LO frequencies used for the channels, \(\alpha = \widehat{f}_d\), \(\beta = \widehat{f}_d + \widehat{f}_{sc}\) and \(\gamma = \widehat{f}_d-\widehat{f}_{sc}\) have this form and satisfy the relation \(-2 \alpha + \beta + \gamma = 0\) exactly. If floating point approximations are used for each LO frequency separately, it is likely that this relation will not be satisfied exactly. That is fatal, because it causes the phase relation of \(z_+(t)\) and \(z_-(t)\) to change over time, so the combination done in \(w(t)\) will be constructive some times and destructive other times. To achieve this relation exactly, the easiest method is first to shift the signal down by \(\widehat{f}_d\), and then to generate a common LO for the two sidebands at frequency \(\widehat{f}_{sc}\), and use the complex conjugate product for the upper sideband and the complex product for the lower sideband.

I have used the following GNU Radio flowgraph for channelization. It implements the shifting by \(\widehat{f}_d\) with a Rotator block, so we don’t have good control over which exact \(\widehat{f}_d\) this achieves (but we don’t care). Instead of using a Signal Source to generate the LO at \(\widehat{f}_{sc}\), a vector source of \(N\) samples, where \(N\) is chosen as\[N = \operatorname{lcm}(\widehat{f}_{sc}, f_s) / \gcd(\widehat{f}_{sc}, f_s),\]where \(f_s\) is the sample rate, allows us to generate the desired \(\widehat{f}_{sc}\) exactly despite floating point rounding errors. This is not required, as proven by the reasoning above (the exact choice of \(\widehat{f}_{sc}\) does not matter as long as it is close to the true \(f_{sc}\)), but it is nice to have (for instance it allows us to measure \(f_{sc}\) exactly in the channelized data if we want to).

GNU Radio channelizer for residual carrier and telemetry sidebands

Telemetry decoding

After some quick analysis with GNU Radio I have determined that the telemetry is PCM/PSK/PM with a 1.7 MHz subcarrier and 19.5 kbaud. The coding is CCSDS concatenated coding with 8 full Reed-Solomon codewords from the (255, 223) code, so the information frame size is 1784 bytes. Frames are CCSDS AOS frames and there is no frame error control field (CRC), as the error detection capabilities of Reed-Solomon are usually sufficient.

The following figure shows the GNU Radio flowgraph that I have made to decode the telemetry. It processes the three channelized sub-bands (residual carrier and each of the telemetry sidebands) as I have explained in the previous section. The rest of the flowgraph is a straightforward decoder for CCSDS concatenated coding.

GNU Radio decoder flowgraph

Telemetry analysis

So far I only have done an initial analysis of the telemetry. A more detailed analysis will be done in a future post. The telemetry frames are CCSDS AOS frames. The spacecraft ID is 0x27, which is assigned in the SANA registry to WFIRST, which is the former name of the telescope. Only virtual channels 0 and 63 (the only idle data channel) are in use.

Virtual channel 0 uses M_PDU to carry CCSDS Space Packets. In addition, the last 4 bytes of the frame are an Operational Control Field that contains a CLCW (communications link control word). Virtual channel 63 contains an M_PDU header with the first header pointer set to 2046, which indicates that there is only idle data according to the CCSDS specification. The packet data zone is filled with an 8-bit counter, and there is no Operational Control Field.

There is something interesting about the virtual channel frame counts. In virtual channel 63 the count resets to zero when the spacecraft transmitter is turned off some time before 07:00 UTC. However, in virtual channel 0 the count keeps incrementing without a reset. I don’t know the TT&C radio’s architecture, but it probably makes sense that an idle packet generator would be reset but the source that is generating telemetry data is not reset.

I have used the virtual channel frame counts of both channels to detect frame loss and piece together a master channel frame count that begins counting at zero at the first frame. The following figure shows the frame loss over time. Occasionally we lose individual frames. The transmitter gap before 07:00 UTC is shown as ~70 lost frames, which probably correspond to the time that the decoder takes to lock to the new signal (there is a carrier sweep when the transmitter starts again). The gap where the transmitter is off is much longer than 70 frames. This means that the spacecraft was not generating any telemetry frames during this gap, rather than generating frames that were not sent out because the transmitter was off. We also see a few lost frames in the switch between the two recordings.

The following plot shows the usage ratio of each of the two virtual channels. We can see that the downlink is used at 50% capacity.

The CLCW in the virtual channel 0 frames contains static values in all the fields except the report value. Interestingly the no RF available and no bit lock flags are set to true. This is unexpected, because we can see the loopback of a valid telecommand uplink that is sending an idle sequence. The values of the remaining fields are what I would expect for a COP-1 link in a nominal state.

The report value field increases twice throughout the recording, acknowledging that a sequence-controlled telecommand has been received.

I have used the timestamps of the Space Packets carried in the AOS frames where these jumps happen in order to find the corresponding telecommands (the Space Packet timestamps are explained below). These timestamps and the corresponding offset in seconds since the beginning of the recording are the following:

2026-08-31T07:02:39.496320768 5376.496320768
2026-08-31T07:20:15.920888064 6432.920888064

These telecommand packets can indeed be seen in the telecommand loopback in Inspectrum, close to the times indicated above. The loopback is weak, especially for the second packet, so the main sign of these packets is that the spectral lines caused by the idle sequence get interrupted during the packet.

Waterfall of first telecommand packet
Waterfall of second telecommand packet

Virtual channel 0 carries CCSDS Space Packets. All these packets have a secondary header. The header is 8 bytes long and contains a big-endian integer that counts the number of \(2^{-32}\) second units since the CCSDS epoch, which is 1958-01-01T00:00:00. There are many different APIDs in use, and each APID carries packets of the same fixed size. The idle APID is not used.

The following figure shows which APIDs are used over time. Each dot corresponds to one packet. We can see that APID 8 is only used sporadically, presumably linked to some events happening, while the other APIDs are periodic with different periods. APID 58 has the longest period.

The behaviour of the packet sequence count field is interesting. In most APIDs it jumps by a power of 2 such as 16, 32, 64, 128 or 256. In a few APIDs it jumps by other numbers, such as 60 or 500. Additionally, there is a big jump in the sequence count when the transmitter stops before 07:00 UTC. The following plot shows the packet sequence count for each of the APIDs. We see that there are three distinct slopes.

The interpretation of this is that Space Packets for each APID are generated at a given fixed rate, even when the transmitter is off. They are then downsampled (usually with a power-of-two ratio) to the desired rate when including them in the telemetry. We can compute the natural rate of production of Space Packets for each APID as the increment of the sequence count divided by the timestamp increment. This gives the number of packets per second that the APID generates. Except for APID 8, which is not periodic, the packets per second can be 1, 2 or 4. This is why we see three different slopes in the plot above.

I have made raster plots for the packet payloads of each of the APIDs. Here is the raster for APID 8. It clearly contains ASCII strings.

Indeed, these are the strings:

RTS Number 404 Started
Successfully loaded 'TO.FilterTable' from '/boot/TO_LGA_8K.tbl'
Loaded New Filter Table: /boot/TO_LGA_8K.tbl
Set Tlm Rate Limit command: 8504
Set VC0 Real-time Rate Limit command: 8504
Processing downlink configuration for S-Comm 0
COMM 0 COMMAND VALID: 19500 sps, PR=1, RS=1, Cv=1, NRZM=0
Uplink SCOMMA-XPNDRB is selected as the Active Uplink
RTS 404 Execution Completed
SComm A Transponder B receiver has carrier and bit sync lock
No-op command. Version 4.9.1.0
SComm A Transponder B receiver has carrier and bit sync lock
No-op command. Version 4.9.1.0

We see that APID 8 contains log messages that are reported when some events happen. Presumably all these are related to the communications subsystem. We can see for instance that the telemetry downlink configuration is logged as 19500 symbols per second, Reed-Solomon enabled (RS=1), convolutional enabled (Cv=1) and NRZ-M disabled (I don’t know what PR=1 stands for).

There are other APIDs that contain ASCII strings, such as APID 4, which contains the strings DS.FILTER_TBL, TO.FilterTable, /boot/DS_LAUNCH.tbl, and /ram/cf.config_table_262420343, and APIDs 19 and 22, which contain the strings /boot/TO_LGA_8K.tbl and /boot/DS_LAUNCH.tbl respectively.

There are multiple APIDs that seemingly contain a bunch of IEEE floating point numbers. For instance APID 94, shown here. I’ll look at these in detail in the future.

The Jupyter notebook contains raster map plots of all the APIDs.

Code and data

The Jupyter notebooks used for ephemeris calculations and waterfall plotting, as well as the GNU Radio flowgraphs used for waterfall computation and channelization, are in this repository. The GNU Radio telemetry decoder, the Jupyter notebook used for telemetry analysis and the binary files containing the decoded AOS frames are in this repository. The channelized IQ recordings of the telemetry signal are in Zenodo and linked at the beginning of the post.

One comment

  1. While Roman is technically near-earth (<2 million km) so it sits in that allocation, telecom ops are being done with DSN, so using all the usual deep space DSN ratios, processes, and so forth makes a lot of sense. So the ranging tone would be divided down from the carrier, the turn around ratios would be the standard, channelization would follow the same pattern (presumably with a negative integer channel number, since the near earth allocation is lower than the deep space allocation in S-band) (810-005 module 201 is the relevant one)

Leave a comment

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

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