First SSDV transmission from DSLWP-B

As some of you may know, DSLWP-B, the Chinese lunar-orbiting Amateur satellite carries a camera which is able to take pictures of the Moon and stars. The pictures can be downlinked through the 70cm 250bps GMSK telemetry channel using the SSDV protocol. Since an r=1/2 turbo code is used, this gives a net rate of 125bps, without taking into account overhead due to headers. Thus, even small 640×480 images can take many minutes to transfer, but that is the price one must pay for sending pictures over a distance of 400000km.

On Saturday August 3, at 01:27 UTC, the first SSDV downlink in the history of DSLWP-B was attempted. According to Wei Mingchuan BG2BHC, the groundstation at Harbin managed to command the picture download at 436.400MHz a few minutes before the GMSK transmitter went off at 01:30 UTC. A few SSDV frames were received by the PI9CAM radiotelescope at Dwingeloo.

The partial image that was received was quickly shared on Twitter and on the DSLWP-B camera webpage. The PI9CAM team has now published the IQ recording of this event in their recording repository. Here I analyze that recording and perform my own decoding of the image.

The camera in DLSPW-B, informally known as Inory Eye, was designed and built by students in the Amateur Radio club of the Harbin Institute of Technology, China. It is worth to have a look at Sora’s Twitter account for some posts regarding the development, such as those highlighted below.

Here we see the really small camera lenses. The small black box under the lenses is where the camera controller PCB is mounted.

The next image shows the camera controller PCB, which uses an STM32F ARM microcontroller.

The image below shows the camera integrated in the front of the DSLWP Amateur radio unit, which includes the SDR radio.

Note that DSLWP-B also has a “professional” camera built in Saudi Arabia. That camera got really good pictures of the Moon and Earth a few months ago. These were beamed back to Earth using the commercial X-band transmitter on DSLWP-B.

The Dwingeloo recording that includes the SSDV transmission is DSLWP-B_PI9CAM_2018-08-03T23:11:10_436.4MHz_40ksps_complex.raw. The SSDV transmission is at the end of the recording. The command below can be used to extract the interesting part of the recording.

tail -c +2600000001 DSLWP-B_PI9CAM_2018-08-03T23_11_10_436.4MHz_40ksps_complex.raw | head -c 80000000 > /tmp/dslwp_photo.c64

I have used the script waterfall_dslwp.py to generate the waterfall below using the chunk extracted from the recording.

DSLWP-B telemetry packet (left) and several SSDV packets (right)

The resolution of this waterfall is 0.4s/pixel or 4.88Hz/pixel, which makes a total of 244s x 2000Hz. The centre frequency is 2400Hz. The short packet on the left is a regular telemetry packet. The longer transmission on the right contains a total of four SSDV frames.

The SSDV frames can be extracted from the recording by using the GNU Radio companion flowgraph ssdv_replay.grc. This flowgraph plays back the recording and saves the SSDV frames to the file /tmp/dslwp_ssdv.bin.

The results of running this flowgraph are shown in this gist. SSDV frames are shown as a hex dump. There are a total of four SSDV frames decoded. However, if you look closely, you should be able to see some bit errors affecting the second and fourth frames (note that some fields of their corresponding TM Frame Header are corrupted). I don’t know why these frames haven’t decoded correctly, since the SNR of Dwingeloo’s recording is excellent and the decoder seems to be working well. Probably it is worth to look at this more in detail.

A total of 872 bytes worth of SSDV data were transmited. One of the SSDV packets (the third one) is shown below.

26 00 02 28 1e 0a 02 00 56 56 3f f1 4e 9c 51 c5 
4d 80 4a 5f c2 80 12 8a 04 1e f4 50 30 eb ce 28 
a0 41 45 00 1e f4 50 30 e3 34 50 20 a2 80 0a 38 
a0 61 45 00 14 50 21 29 68 18 66 8a 04 1d a8 ed 
40 05 6a e8 de 20 d4 74 1b 83 3e 9d 70 f0 39 52 
a4 a9 ea 28 2a 2e c5 1b ab a9 ae ee 1e 79 9c bc 
8e 77 33 13 d4 d4 14 5c 1b be a2 51 41 21 9a 5a 
06 14 94 08 28 a0 02 8a 00 5a 4c d0 01 45 00 1d 
05 14 00 51 40 05 1d 45 00 19 c5 1f 5a 00 28 a0 
03 b5 14 00 52 50 02 d1 40 05 1d f3 40 07 19 e2 
ae 69 78 fe d6 b1 ff af 88 ff f4 21 40 10 67 9f 
6a 4c 0f 53 40 ec 26 39 a0 0e 68 10 51 c5 00 14 
1e b4 00 94 7d 28 63 12 9d c7 7a 04 25 18 e3 9e 
68 01 29 7b 50 01 44 f6 16 2d 

DSLWP-B uses a non-standard format for the SSDV frames in order to save some precious bandwidth. It dispenses with some useless headers. The packet format of standard SSDV frames can be seen in this table. DSLWP-B uses the normal mode, so there are 205 bytes of payload data per frame. However, the sync byte, packet type and callsign headers are not sent. The FEC symbols are not sent either. This leaves us 205 bytes of payload, plus 9 bytes of headers, plus 4 bytes of CRC for a total of 218 bytes.

In the sample frame shown above we can see that the image ID is 0x26, that this is packet number 2, and that the width and height are 40 and 30 MCU blocks respectively (so 640×480 pixels).

I haven’t been able to process the CRC32 field correctly yet. Probably it includes some of the fields that are not transmitted. In particular, the callsign, which I do not know. I have asked the details to Wei Mingchuan BG2BHC.

To decode the custom SSDV format used by DSLWP-B, I have adapted Philip Heron’s SSDV decoder. The resulting decoder can be obtained from my SSDV fork. The -D command line argument can be used to set the decoder into DSLWP format mode. Encoding using the DSLWP format is also supported.

The SSDV data obtained in GNU Radio can be decoded into a JPEG file as shown below. The decoder throws the second packet because some of its headers are corrupted and have incorrect values. However, the fourth packet is accepted, since the CRC check is bypassed.

$ ssdv -d -D -v dslwp_ssdv.bin dslwp_ssdv.jpg
CRC32 incorrect, but processing packet anyway
Decoded image packet. Callsign: DSLWP, Image ID: 38, Resolution: 640x480, Packet ID: 0 (0 errors corrected)
>> Type: 1, Quality: 5, EOI: 0, MCU Mode: 2, MCU Offset: 0, MCU ID: 0/2400
Callsign: DSLWP
Image ID: 26
Resolution: 640x480
MCU blocks: 2400
Sampling factor: 2x1
Quality level: 5
CRC32 incorrect, but processing packet anyway
CRC32 incorrect, but processing packet anyway
Decoded image packet. Callsign: DSLWP, Image ID: 38, Resolution: 640x480, Packet ID: 2 (0 errors corrected)
>> Type: 1, Quality: 5, EOI: 0, MCU Mode: 2, MCU Offset: 2, MCU ID: 86/2400
Gap detected between packets 0 and 2
CRC32 incorrect, but processing packet anyway
Decoded image packet. Callsign: DSLWP, Image ID: 38, Resolution: 640x480, Packet ID: 3 (0 errors corrected)
>> Type: 1, Quality: 5, EOI: 0, MCU Mode: 2, MCU Offset: 4, MCU ID: 129/2400
Read 3 packets

The fact that, out of the four SSDV frames only two were correct coincides with the report by Wei about two frames received by Dwingeloo. However, I don’t know what was the problem with the other two frames.

The decoded JPEG image can be seen below. Note that it coincides with the first image shown in the DSLWP-B camera webpage. The planet Mars can be seen in the top of the image, in the only part that was transmitted. The same image (or a very similar one) was then transmitted correctly in a later try and it has been shared on Twitter.

First SSDV image transmitted by DSLWP-B

In this post I have shown how to decode the SSDV images transmitted by DSLWP-B on your own. Since they are transmitted using the regular 250bps GMSK channel, any station capable of receiving the GMSK telemetry should also be able of receving SSDV images. Note that some people such as Mike DK3WN and Bob N6RFM have been able to receive the GMSK telemetry correctly with as little as a single yagi with 20 elements. If you attempt your own SSDV decoding, remember to upload your received data to the telemetry server also, so that collective decoding can be done in case there are missing chunks and so that the image appears in the listing.

Update 2018-08-10: Shortly after publishing this post, Wei has told me that the callsign which is used (but not transmitted) to generate SSDV frames onboard DSLWP-B is SORA (in honour of the developer of the Inory Eye camera). Thus, to calculate the CRC we need to prepend 66 00 0e 72 40 to the frame, to account for the missing packet type field (0x66) and callsign (encoded in base 4). Note that the 0x55 sync byte is never included in the CRC calculation.

Using the properties of the CRC, this can be done implicitly. Indeed, instead of using 0xFFFFFFFF as the initial register value for the CRC32 calculation, we can use 0x4EE4FDE1, which is the register contents just after the initial 66 00 0e 72 40 would have been processed. Thus, using this initial value, the CRC calculation proceeds just as if the data would have started by 66 00 0e 72 40.

With this in mind, I have now implemented the CRC calculation for the DSLWP mode in my SSDV decoder fork, both for encoding and decoding.

The decoder now drops the two invalid packets, as one can see below, and produces a JPEG image without any corrupted blocks.

$ ssdv -d -D -v /tmp/dslwp_ssdv.bin /tmp/dslwp_ssdv.jpg 
Decoded image packet. Callsign: DSLWP, Image ID: 38, Resolution: 640x480, Packet ID: 0 (0 errors corrected)
>> Type: 1, Quality: 5, EOI: 0, MCU Mode: 2, MCU Offset: 0, MCU ID: 0/2400
Callsign: DSLWP
Image ID: 26
Resolution: 640x480
MCU blocks: 2400
Sampling factor: 2x1
Quality level: 5
Decoded image packet. Callsign: DSLWP, Image ID: 38, Resolution: 640x480, Packet ID: 2 (0 errors corrected)
>> Type: 1, Quality: 5, EOI: 0, MCU Mode: 2, MCU Offset: 2, MCU ID: 86/2400
Gap detected between packets 0 and 2
Read 2 packets
First SSDV image transmitted by DSLWP-B (discarding corrupted frames)

5 comments

  1. Thanks for the CRC update – so neat to see it decode cleanly this time! Both the original & the update worked perfectly on my Ubuntu 16.04 setup, but it’s so nice to have all the information from the team to see it run with no errors.

Leave a Reply to Scott - K4KDR Cancel reply

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.