ADS-B Decoding in MATLAB
Ham radio seems to go hand-in-hand with the current quarantine. Over the past week I’ve been studying some online stanford labs to do more digital signal processing. The final project for this class involves decoding ADS-B signals, which are transmitted out in the open by any aircraft. MATLAB already has an example for doing this, but I wanted to learn a little more.
This lab, in abstract, dealt with signal discrimination and logic. It involved finding a signal, then decoding it into ones and zeros.
1. The Data
The Stanford lab gives a 10-second IQ data file on a single frequency, 1090 MHz. There is no frequency domain, only time domain. You can do this too using lab 2 ‘s steps. I used the file “adsb_3.2_3.dat.” Below is a snapshot of what the signals look like from 0-2 seconds:
Understanding Sample Rates / Bits
The biggest hurdle to this project was understanding the incoming data stream. The data from the rtl-sdr is taken in at 3.2 Megahertz, or 3.2e6 samples/second. In the lab, the data is resampled to 4 MHz. That way the signal rate is 2 times what we need to decode it. There really isn’t much to “desampling” the waveform; I only took the odd-numbered bits and got a signal. However, sampling immediately at 2MHz results in hard-to-decode signals.
The ADS-B Packet
The Packet first starts with a preamble 8 microseconds (32 samples at 4MHz) long. The preamble has 4 sharp spikes as shown below. The packets can be 56 or 112 microseconds long following the preamble, however I only looked for packets 112 us long. Here is an example packet from the dataset, note the clear peaks and troughs of the signal.
These peaks and troughs vary for each signal in the sample; the amplitude could peak at 8 for one signal, or 20 for another. You will threshold the signal to discern it above a noise floor. In matlab it is super simple:
x = x > 10 %Keep only signals above 10
Here’s a breakdown of the packet. I am concerned with the ICAO, but the lab I used is concerned with the DATA section, led by a Type Code packet.
2. The Code
I uploaded everything I’ve played with on github here. I’ll go step-by-step with my methodology for how I decoded the signals.
A. Process the Data
Data is processed straightforward with the lab’s guidance:
da = abs(loadFile('adsb_3.2M_3.dat')); d = resample(da,5,4);
B. Filter the Data
From here, I used MATLAB’s conv() function to make a matched filter between the data and my preamble.
PreambleVector = [1 1 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0]; w = conv(d, PreambleAdj); for n = 1:length(w) if w(n) > 200 %if the current bit is ~definitely~ a preamble
A matched filter takes both a sample signal and a message signal (in this case it is the PreambleVector backwards). It returns the area overlap between the two signals.
So here is the signal d, with the output of the filter, w. W’s peaks occur where there is a large amount of overlap between the preamble and the signal.
C. Sample the data
Pretty straightforward, now after I know where my packet is, I downsample it and convert it to logic. First I take a small chunk of data, starting where my filter returns a peak, and sample every other bit. Remember 4MHz gives 4x the needed length:
RawDataPacket = d(n:end); %get a chunk of the data RawDataPacket = RawDataPacket > 20; %Threshold DataPacket = RawDataPacket((find(RawDataPacket, 1)+8*4):(find(RawDataPacket,1)+120*4)); %Packet that is 4x the needed length DataPacket = DataPacket(1:2:end); %take every other bit
From here I run it through a logic table. The logic is very straightforward:
So it can be rewritten as
DataPacketAdj = DataPacket(1:2:end);
Now I have a 112-bit long vector with my binary values, ready for decoding.
D. Decode the Data
This is very easy: let someone else do it. I ran it through MATLAB’s binaryVectorToHex function (my DataPacketAdj is a column vector).
NewID = binaryVectorToHex(DataPacketAdj(9:32).'); if NewID ~= [0 0 0 0 0 0] %ignore empty values ICAO = [ICAO ; NewID]; %save ICAO addresses into an array end
So overall, my code generates usable packets as shown below. It takes a chunk, downsamples it, then converts it to logic.
In the end, I get quite a few ICAO addresses. I auto-stored all of my results in a .csv file, though most of them are junk results. The script takes quite a while to compile but does give usable data. Here’s one I found:
from AirNav RadarBox
In the future, I would like to look at other ways to filter for my preambles. The conv() function works but other methods could give better results. Also, I can take this algorithm and apply it to other single-frequency data channels, such as APRS (which is actually Lab 7 in the Stanford class).
I could also apply this to a range of frequencies, by taking snapshots at a certain time. However, a problem I am struggling with is procedurally decoding different types of modulation: if I am in the 20m ham band, I can encounter all sorts of different signals.
I would also like to see what ADS-B looks like currently given the coronavirus situation. I may get many fewer flights than usual.