Analysis of DSLWP-B 2018-08-12 SSDV transmission

This notebook analyzes SSDV transmissions made by DSLWP-B from the Moon.


In [1]:
%matplotlib inline

import numpy as np
import scipy.signal
import matplotlib.pyplot as plt

We load a file containing the relevant GMSK transmission. The recording was done at the Dwingeloo radiotelescope and can be obtained here. Remember to edit the path below to point to the correct file.


In [2]:
x = np.fromfile('/home/daniel/Descargas/DSLWP-B_PI9CAM_2018-08-12T06_58_02_436.4MHz_40ksps_complex.raw', dtype='complex64')

The 250bps GMSK signal is converted down to baseband and lowpass filtered to 800Hz.


In [3]:
fs = 40e3
f = 1500
x = x * np.exp(-1j*2*np.pi*np.arange(x.size)*f/fs).astype('complex64')

h = scipy.signal.firwin(1000, 0.01).astype('float32')
x = scipy.signal.lfilter(h, 1, x).astype('complex64')

Perform arctangent FSK demodulation.


In [4]:
s = np.diff(np.angle(x).astype('float32'))

Correct for phase wrapping.


In [5]:
s[s > np.pi] -= 2*np.pi
s[s < -np.pi] += 2*np.pi

We extract the soft bits by guessing the correct clock phase and decimating. No pulse shaping matched filtering has been done, and tracking of the clock frequency doesn't seem necessary either. The separation between the bits 1 and 0 is good enough for demodulation without bit errors.

Note that we correct for the clock drift and for frequency offset and drift. This simple open loop clock recovery is enough to get good separation between the bits.


In [6]:
phase = 50
softbits = s[np.int32(np.arange(phase,s.size,160 - 0.0005))]
softbits = softbits + 1e-8 * np.arange(softbits.size) - 0.005 # correction for frequency offset and drift
plt.plot(softbits,'.')
plt.axhline(y = 0, color='green')
plt.ylim([-0.03,0.03]);


Soft bits are now converted to hard bits.


In [7]:
bits = (softbits > 0)*1

We construct the CCSDS ASM used to mark the beginning of each Turbo coded word. The ASM is precoded as indicated by the CCSDS standard. See this post for more information.


In [8]:
asm = np.unpackbits(np.array([0x03,0x47,0x76,0xC7,0x27,0x28,0x95,0xB0], dtype='uint8'))
asm_diff = asm[:-1] ^ asm[1:]
asm_diff[1::2] ^= 1

We correlated the received hard bits against the precoded ASM. The length of the ASM is 63 bits, so a correlation of 63 indicates that the ASM is found without bit errors. We see that the ASM is found 4 times without any bit errors. Each of these occurences of the ASM marks the start of a Turbo codeword containing a single SSDV frame.


In [9]:
asm_corr = scipy.signal.correlate(2*bits-1, 2*asm_diff.astype('float')-1)
plt.plot(asm_corr)


Out[9]:
[<matplotlib.lines.Line2D at 0x7f5002345f98>]

Note that all the ASMs have correlated without any bit errors.


In [10]:
asm_corr[asm_corr > 40]


Out[10]:
array([ 63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,
        63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.,  63.])

We have found a total of 130 ASMs. Of these, 117 mark SSDV packets, 5 are telemetry packets transmitted before the SSDV transmission, and the remaining 8 are telemetry packets interleaved with the SSDV transmission.


In [11]:
asm_corr[asm_corr > 40].size


Out[11]:
130

We now look at the distances between the ASMs. DSLWP-B uses Turbo codewords of 3576 symbols. Since the ASM is 64 bits long and Turbo codewords are transmitted back-to-back, without any gap between them, we expect a distance of 3640 bits.

Note that before we have stated that the ASM is 63 bits long. This is because the GMSK precoder is differential, so the first bit of the ASM is not defined, as it depends on the preceding data. Thus, we only use the 63 bits of the ASM that are fixed when doing the correlation.

Below we show the distances between the ASMs.


In [12]:
np.diff(np.where(asm_corr > 40)[0])


Out[12]:
array([30353, 31808, 29833, 13827, 49426,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,  3640,
        3640,  3640,  3640])

We see that there are no problems with Turbo words being cut short.