Last week you used an integrated program, gqrx or SDR#, to control your SDR and listen to commercial FM, along with a range of other frequencies.
This week we'll get a little closer to the hardware, and learn how to control the SDR's more directly. We'll use MATLAB's support for the rtl-sdr to read data directly, so that we can examine and decode it. We'll capture AM signals in the airband.
This week we'll look at other ways to control your SDR to capture AM airband data with MATLAB and process it. You will capture a signal, display it's spectrum as an image, decode the signal, plot it, and play back the audio.
PLease install Matlab 2025. If you have earlier versions, please upgrade. Matlab 2025 is needed for the support of the RTL-SDR .com V4 dongle. Make sure you have the following toolboxes installed:
Signal Processing Toolbox
Communication Toolbox
DSP systems Toolbox
Instrumentation Control Toolbox
Optimization Toolbox
Statistics and Machine Learning Toolbox
If you already have Matlab 2025, you can add the toolboxes within MATLAB by clicking on the “Add-ons” button. This brings you to a pulldown menu to manage and explore add-ons.
Choose again Add-Ons Menu, click the “Get Hardware Package Support” entry. This brings up a page for all of the different types of hardware MATLAB knows how to work with, including several sdr's, including the rtl-sdr that we are using. Scrolling doen the pgae, you should see somethign like this:
Click on the rtl-sdr entry. This brings up the following page:
Click the install button, to install the package. If you already have it installed, it may say “Manage” because . Follow the instructions on that page, including plugging your sdr in, and running the initialization process from the pop up window. At this point you should be all set. If you are a windows user, you already have the driver installed, so you can skip that step.
You can check to see if your setup is working by first plugging the rtl-sdr in, and then running
>> sdrinfo
ans =
RadioName: 'Generic RTL2832U OEM'
RadioAddress: '0'
RadioIsOpen: 0
TunerName: 'UNKNOWN'
Manufacturer: 'RTLSDRBlog'
Product: 'Blog V4'
GainValues: [29×1 double]
RTLCrystalFrequency: 28800000
TunerCrystalFrequency: 16000000
SamplingMode: 'Quadrature'
OffsetTuning: 'Disabled'
MATLAB has a number of different ways to interact with the rtl-sdr. For now we are just going to use it to collect a frame of several seconds of RF data, which we can then process using MATLAB's signal processing tools, and then play back the result.
function y = Read_RTLSDR(cf, sf, fs, nf ,g)
%
% Utility to read data from an rtl-sdr
%
% y = Read_RTLSDR(cf, sf, fs, nf, g)
%
% Inputs:
% cf - center frequeny in Hz (88.5e6 for KQED)
% sf - sampling frequecy in Hz, typically 2.048e6
% fs - frame size, typically 1e5
% nf - number of frames
% g - reciever gain in dB, typically 10 to 30
%
% Returns:
% y - complex I and Q samples of the RF waveform
%
% The total acquisition time is (fs*nf)/sf seconds
%
% Choose the gain so that the amplitude is less than 128, where
% the rtl-sdr clips, but large enough you can't see the quantization
%
% set up the rtl-sdr
hSDR = comm.SDRRTLReceiver('0', ...
'CenterFrequency', cf, ...
'SampleRate', sf, ...
'SamplesPerFrame', fs, ...
'EnableTunerAGC',false);
% we can't do this in the call above for some reason!
hSDR.TunerGain = g;
% collect the data
buf = [];
for counter = 1:nf
buf = [buf; step(hSDR)];
end
% free up the device
release(hSDR);
% convert the data into doubles for MATLAB
y = double(buf);
end
The rtl-sdr device is freed after it is used, so that you can run gqrx. However, you need to quit these before you run the m-file again, or MATLAB hangs, since it can't get access to the device.
Let's collect data around the FM band, where we know what spectrum to expect
capture 1 second of data with using: Read_RTLSDR(94.1,2.4e6,1e5,24,25); The center frequency is 94.1Mhz, sampling rate 2.4MS/s, each buffer from the sdr is 10000 samples, we collect 24 buffers, and set the SDR gain to 25db. 48*10000/2.4e6 = 1 seconds!
>> fm941 = Read_RTLSDR(94.1e6,2.4e6,1e5,24,25);
Now, compute the spectrum using the FFT. We will use fftshift to center the spectrum.
>> FM941 = abs(fftshift(fft(fm941)));
Let's display the signal. The frequency range is between 94.1-1.2 and 94.1+1.2 MHz and will have 2.4e6 samples.
>> f = [-1.2e6:1.2e6-1]/1e6+94.1;
>> figure, semilogy(f,FM941);
>> xlabel('Mhz')
The spectrum should look like this:
The spectrum will look very noisy, and will have high resolution. The resolution is inversly proportional to the window size. So for 1 second, the resolution will be 1 Hz, which is too much! To obtain better signal-to-noise ratio in the spectrum, we will use the matlab function pwelch.
This function breaks the signal to (potentially overlapping) windows, computes their power spectrum and then averages the result. The spectral resolution will be determined by the size of the window and the signal to noise ratio will improve with more averaging. The default windowing function used in the function is a rect window. But it is possible to use other windows, like hamming, which will result in less ringing in the spectrum.
Use a window of 2400, which is 1ms window and a spectral resolution of 1KHz. Make sure to add: "centered" to indicate that the spectrum is double sided, and centered at zero. The spectrum will be much clearer!
>> [FM941,f] = pwelch(fm941,2400,[],[],2.4,"centered");
>> figure, semilogy(f+94.1,FM941);
>> xlabel('Mhz')
The spectrum should look like this:
It is also useful to plot something like the waterfall plot that you see in gqrx. This is called a spectrogram. Similar to the welch method, we compute the spectrum of blocks of the signal. But instead of averaging them, we display it as an image of how the spectrum changes over time. While Matlab has several implementations of spectrograms, we will use this basic spectrogram program that is provided here
It is msg.m for “my spectrogram”.
>> help msg
msg(x,n0,nf,nt,dbf, sf, fs)
Computes and displays a spectrogram
x -- input signal
n0 -- first sample
nf -- block size for transform
nt -- number of time steps (blocks)
dbf -- dynamic range in dB (default 40)
sf -- sampling frequency (for time/frequency axis labels)
fs -- center frequency (for freq. axis labels)
This extracts a segment of x starting at n0, of length nf*nt
The image plot is in dB, and autoscaled. This can look very noisy
if there aren't any interesting signals present.
Compute and display the spectrogram of the fm941 signal you collected:
>> msg(fm941,1,2400,2.4e6/2400,50, 2.4e6,94.1e6);
The spectrogram should look like this:
This takes an input signal starting at sample n0, and computes the spectrum of nt segments of the signal, each of length nf. The result is displayed as an image, with time going horizontally, and frequency vertically. The center frequency is in the middle of the plot. To look at the spectrum for the entire 1 s signal
The main part of the lab is to capture and decode AM signals in the air band, with is right above the commercial FM band we were decoding last week. There is a band from 108-118 MHz that mostly has navigation beacons that identify themselves by Morse code. Then from 118-137 MHz there are several bands used for communication between aircraft and the ground. Communications in these bands uses AM modulation. This is because when two users try to talk on the same channel, you hear both of them with AM. With FM, you hear only the stronger of the two, or something completely intelligible if both are the same strength. With air traffic control, it is important to hear everyone that is out there!
I had lots of luck listening to Remote Comm Air Ground (RCAG) frmo Mt. Tam from Cory. In particular 353.500 and 323.00
We will also hear traffic from San Francisco (SFO), Hayward (HWD), as well as NORCAL Approach, which coordinates the airspace. The frequencies for SFO are
and Hayward are
and NORCAL Approach are
Choose a frequency where you might expect to get a signal. The ATIS frequencies would be agood initial candidates, because these continuously transmit information about the airport, and how to contact them. But I was not able to capture them from my office. The other frequencies, such at the air traffic control frequencies, are more interesting, but are not always in use. Often a transmission lasts just a few seconds, and can be hard to capture.
One busy channel that has a strong signal around near Berkeley is 353.5 MHz, from Mt. Tam. Frequencies in this range are a good match for your SDR antenna. At 353.5 MHz a quarter wave antenna is 24 cm, or about 9.5 inches. Set each antenna to that length and angle at about 120 degree from each other.
Use gqrx or to see if you can find any activity. Here is an example
You see a couple of frequencies in use. The one that is on continuously is an ATIS signal. The others are planes and towers talking to each other. You can adjust the LNA gain if necessary.
Once you know there is a signal out there, you can start capturing data. Close gqrx or SDR# to free up the SDR, and then capture 8 seconds of data with, for example: Read_RTLSDR(310.8e6,2.4e6,1e5,192,25); The center frequency is 310.8Mhz, sampling rate 2.4MS/s, each buffer from the sdr is 10000 samples, we collect 192 buffers, and set the SDR gain to 25db. 192*10000/2.4e6 = 8 seconds!
>> ab310 = Read_RTLSDR(310.8e6,2.4e6,1e5,192,25);
where I have chosen 310.8 MHz, which is Norcal Approach/Departure South. We are setting the frequency exactly to the channel we are interested in to make the processing easier. This is generally a bad idea, as we will see shortly. The data file is available at
You can use this file if you are having trouble finding signals to capture. Another signal you can usually see is the Oakland ATIS, as it transmits all of the time, but is more challenging to capture.
The first thing to note is that just 8 seconds of RF is a lot of data! It is hard to tell if we have anything, or how to extract it. What we will do is to compute the spectrogram.
>> msg(ab310,1,2048,8192,60,2.4e6,310.8e6);
Unless you have a very big display, you'll get an error message that the image doesn't fit, and was scaled down. The result looks like this
This is 8 seconds of data. The horizontal axis is time, 0 to 8 seconds, and the vertical axis is frequency, -1.2 MHz to 1.2 MHz. The signal we want is right in the middle at 0 MHz. The signal starts at about 2 seconds, and continues to about 7 seconds. Depending on your luck, you may see several signals at other different frequencies.
For now we'll just focus on the signal at 0 MHz. The audio signal has a bandwidth of about +/- 5 kHz, so we need to reduce the sampling rate from 2.4 MHz to 10 kHz. This is called decimation, in this case by a ratio of 2.4e6/10e3 = 240. A lowpass filter first limits the bandwidth to +/- 5 kHz, and then takes every 240th sample.
>> ab310d = decimate(ab310,240);
If we plot the magnitude of the decimated signal with
>> t = [1:length(ab310d)]/(2.4e6/240);
>> plot(t,abs(ab310d));
>> xlabel('Time, seconds')
>> ylabel('Magnitude')
we see
This is a classic AM signal. There is an offset of about 1 unit from the DC feedthrough because we are receiving right at zero frequency. We'll look at fixing this below. Then the AM signal starts at about 1.5 seconds. What you see is the AM offset that biases the audio waveform to make it always positive. On top of that you see the actual audio signal.
Let's plot the spectrogram of the decimated signal! Remember, the signal is decimated, so the sampling rate is 10KHz. Choose a window size of 200. This will give you a spectral resolution of 10000/200 = 50Hz.
>> msg(ab310d,1,200,400,50, 1e5,94.1e6);
You should be able to see the zero frequency DC feedthrough. You should also see the strong DC signal of the AM modulation, and the symmetric spectrum of the speech signal!
You can play the signal with
>> soundac(abs(ab310d), 10e3)
Soundac scales the waveform to be less than one, which is what sound() requires to avoid clipping, and 10e3 is the sampling rate after decimation. We haven't suppressed the AM offset, because your audio system can't reproduce it anyway.
In general you don't want to acquire a signal exactly on frequency. Receivers always have a noise spike there. Acquire another data set where the receive frequency is offset from the signal you are interested in. For the example above, set the receive frequency to 310.7e6, so that you are 100 kHz below the signal you are capturing. A sample data set is here:
Another data set is here:
This is acquired at 135 MHz, just 275 kHz below the Palo Alto ATIS frequency, which is in the data set. It also has a signal from an aircraft that you can find and decode. You can use this data set if you'd like, or the one above, or one you capture yourself.
Since we are not directly on the signal's frequency we need to shift the frequency before we decimate. If d is our raw data vector and fs is the sampling frequency, we can do this with
>> t = [1:length(d)]'/fs;
>> dm = d.*exp(-i*2*pi*f_offset*t);
where f_offset is the frequency you want to shift to. The signal dm should have our signal at the center of the spectrogram.
For your report
What happens if we don't shift the frequency and just decimate, as we did above. Plot the signal you get.
See if you can center the signal you are interested in, and show the spectrogram in your report.
Plot the magnitude of the decimated signal, and include that in your report. Is the DC offset gone?
Play back the audio, and describe what you hear.
What happens if you decimate by 120, instead of 240? When you play the signal the sampling frequency is now 20 kHz. Describe the difference in the audio quality.
For all of these use screen captures in your report. There is a lot of data, and pdf's can get huge.
Based on Lab developed by John Pauly