I have wanted to implement a simple APT decoder for the NOAA weather satellites in GNU Radio for quite some time now, in particular since the USRP equipped with a TVRX or WBX daughterboard and a GNU Radio FM receiver can be an extremely good receiver for this purpose. Today I have spent some time looking at the details of decoding APT and actually ended up with a working prototype implemented using the GNU Radio Companion.
The APT format is very simple and straightforward. The pixel intensity from the instruments AM modulates a 2.4 kHz tone, the sub-carrier, which is then used to FM modulate the 137 MHz carrier. Therefore, the receiver consists of an FM receiver followed by an amplitude detector that translates the amplitude of the 2.4 kHz audio tone to pixel intensity on an 8 bit gray scale. The details including line sync and framing are available in the NOAA KLM user’s guide section 4.2.
A simple way to measure the amplitude of the 2.4 kHz subcarrier was presented by Neoklis 5B4AZ in Wxapt: If we convert the audio to 9600 kHz, the 2.4 kHz subcarrier will have 4 samples during each period. Therefore, two consecutive samples will have a phase difference of 90° between them. The instantaneous amplitude of the 2.4 kHz subcarrier will then be given by:
A = sqrt(s12 + s22)
where s1 and s2 are two consecutive samples. If it’s not obvious, try to draw a sine wave and do the math using any two samples separated by 90°. You’ll see that the above formula gives the correct amplitude regardless of where you pick the samples.
Below you can find an implementation of this algorithm in GNU Radio Companion. The band pass filter is necessary to reduce the noise in the audio, which can be quite significant in particular for audio with higher rate, like 44.1 or 48 kHz. since there is no square root in GNU Radio Companion I pack S1 and S2 into a complex sample and calculate the magnitude. Finally, the output stream is re-sampled to 4160 samples per second which is the symbol rate of the APT data. The data is converted to unsigned char corresponding to the 8 bit resolution of APT.
To convert the output of the decoder to an image we can use ImageMagick:
convert -size 2080x1500 -depth 8 gray:input.dat output.png
The resulting image is shown below. The black bar at the bottom is because there wasn’t enough data for 1500 lines.
As you can see a practical decoder will need some more tuning to produce a perfect image. The dynamic range looks quite good though, as you can see from the telemetry bars. The same data decoded and contrast enhanced using wxtoimg on Linux is available here.
The curvature in the image is timing skew caused by Doppler shift. The simple decoder runs at a fixed clock; however, the clock in the signal runs too fast while the satellite is approaching, and too slow while the satellite is receding. Too fast TX clock will lead to lost pixels in the received image (lines are shorter), while the too slow clock will cause too many pixels (lines are longer).