Prerequisites: Week 8 lecture; RTL-SDR v4 installed (rtl_test passes); GNU Radio + Python NumPy/SciPy/Matplotlib Duration: ~90 min Points: 100
Authorization
- Lab Authorization Form signed
- Receive only; no transmission in this lab
- Survey frequencies: 300-450 MHz sub-GHz ISM band only
- No capture, decode, or replay of communications not expressly authorized
Objective
Use the RTL-SDR to perform a sub-GHz spectrum survey, identify at least two signal types from their modulation characteristics, record a 30-second IQ capture, and perform basic signal analysis in Python: FFT spectrum plot, amplitude envelope, and IQ constellation.
Part A — Sub-GHz Survey with rtl_power (20 min)
Run a wide-band power sweep across the sub-GHz ISM range:
rtl_power -f 300000000:450000000:25000 -g 40 -1 survey_300_450.csv
Then visualize:
# survey_plot.py
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
df = pd.read_csv('survey_300_450.csv', header=None)
freqs = []
powers = []
for _, row in df.iterrows():
start = float(row[2])
step = float(row[4])
vals = [float(x) for x in row[6:] if str(x).strip()]
freqs.extend([start + i*step for i in range(len(vals))])
powers.extend(vals)
freq_mhz = [f/1e6 for f in freqs]
plt.figure(figsize=(16,5))
plt.plot(freq_mhz, powers, linewidth=0.4, color='steelblue')
plt.xlabel('Frequency (MHz)')
plt.ylabel('Power (dB)')
plt.title('Sub-GHz Survey 300-450 MHz')
plt.grid(True, alpha=0.3)
plt.axvline(315, color='r', linestyle='--', alpha=0.5, label='315 MHz (US remotes)')
plt.axvline(433.92, color='g', linestyle='--', alpha=0.5, label='433.92 MHz (EU sensors)')
plt.legend()
plt.tight_layout()
plt.savefig('survey_300_450.png', dpi=150)
print("Saved survey_300_450.png")
Annotate the plot (in Python or in an image editor) marking any peaks that appear significantly above the noise floor.
Deliverable A
survey_300_450.png (annotated) + a table listing every peak you identified above the noise floor (frequency in MHz, approximate peak power in dB, likely source if identifiable).
Part B — Targeted IQ Capture (20 min)
Pick the strongest peak you observed in Part A (or use 433.92 MHz if you see activity there; use 315 MHz if you are in the US with sub-GHz remote activity nearby).
Capture 30 seconds of IQ data at that frequency:
# Using rtl_sdr (raw IQ output)
rtl_sdr -f 433920000 -s 250000 -g 40 -n $((250000 * 30)) target_433.iq
Or use GQRX: Device → Record → I/Q recording to .iq file.
The resulting file contains 32-bit float pairs (I, Q) interleaved. File size should be approximately:
- 250 kSps × 30 s × 8 bytes/sample (2× float32) = 60 MB
Confirm:
ls -lh target_433.iq # should be ~60 MB
Virtual Path: The course portal provides target_433_virtual.iq -- a pre-captured 30-second recording from a 433 MHz environment. Download and use it in place of the live capture.
Deliverable B
Confirm file size + SHA256 checksum of your capture (or the virtual path file):
sha256sum target_433.iq
Part C — FFT Spectrum Analysis in Python (20 min)
# fft_analysis.py
import numpy as np
import matplotlib.pyplot as plt
sample_rate = 250000 # samples per second
center_freq = 433.92e6 # Hz
# Load IQ data
samples = np.fromfile('target_433.iq', dtype=np.complex64)
print(f"Loaded {len(samples):,} samples ({len(samples)/sample_rate:.1f} seconds)")
# FFT of first 8192 samples (one window)
fft_size = 8192
window = samples[:fft_size] * np.blackman(fft_size)
spectrum = np.fft.fftshift(np.fft.fft(window, fft_size))
freqs = np.fft.fftshift(np.fft.fftfreq(fft_size, d=1/sample_rate)) + center_freq
power_db = 20 * np.log10(np.abs(spectrum) + 1e-10)
plt.figure(figsize=(12,4))
plt.plot(freqs/1e6, power_db, linewidth=0.5)
plt.xlabel('Frequency (MHz)')
plt.ylabel('Power (dBFS)')
plt.title(f'FFT Spectrum @ {center_freq/1e6} MHz (8192-pt Blackman window)')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('fft_spectrum.png', dpi=150)
Answer:
- Where is the signal peak relative to the center frequency?
- What is the approximate signal bandwidth (measure the -10 dB bandwidth)?
Deliverable C
fft_spectrum.png + answers to the two questions.
Part D — Amplitude Envelope and Modulation Identification (15 min)
# amplitude_analysis.py
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import butter, filtfilt
sample_rate = 250000
samples = np.fromfile('target_433.iq', dtype=np.complex64)
# Take a 1-second slice where the signal is active
# Adjust start_sample to find a burst; use GQRX/Inspectrum to identify the approximate time
start_sample = 50000 # adjust based on your capture
slice_samples = samples[start_sample:start_sample + sample_rate]
# Amplitude envelope
amplitude = np.abs(slice_samples)
# Smooth with LPF
b, a = butter(4, 5000/(sample_rate/2), btype='low')
smoothed = filtfilt(b, a, amplitude)
# IQ constellation
plt.figure(figsize=(14,4))
plt.subplot(1,3,1)
plt.plot(amplitude[:10000])
plt.title('Amplitude Envelope (raw)')
plt.xlabel('Sample')
plt.subplot(1,3,2)
plt.plot(smoothed[:10000])
plt.title('Amplitude Envelope (LPF)')
plt.xlabel('Sample')
plt.subplot(1,3,3)
plt.scatter(slice_samples.real[:5000], slice_samples.imag[:5000],
alpha=0.1, s=1, c='steelblue')
plt.scatter([0], [0], c='red', s=20, zorder=5)
plt.xlabel('I')
plt.ylabel('Q')
plt.title('IQ Constellation')
plt.axis('equal')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('amplitude_iq.png', dpi=150)
From the plots, identify the modulation type:
- ASK/OOK: amplitude clearly switches between on/off (two amplitude levels); IQ constellation shows two clusters (one near origin, one offset)
- FSK: amplitude is relatively constant; two frequency blobs visible in spectrum; IQ constellation is a ring (constant amplitude, varying phase)
- BPSK: amplitude constant; two phase clusters at 0° and 180° in IQ constellation
Deliverable D
amplitude_iq.png + 1 paragraph: what modulation type is this signal? What evidence from the plots supports your conclusion?
Write-up Questions
- Your RTL-SDR has a maximum sample rate of 2.4 Msps. The 802.11n channel bandwidth at 2.4 GHz is 20 MHz. Can you capture a full 802.11n channel with this SDR? What sample rate would you need, and what SDR hardware supports it?
- The Blackman window was applied before the FFT in Part C. What would the spectrum look like without any window? Why is windowing used?
- You identified an ASK signal on 433 MHz. The signal's bit rate appears to be approximately 2400 bits/sec from the amplitude envelope transitions. What is the minimum channel bandwidth required for this signal (using Nyquist's minimum bandwidth theorem: BW = bit_rate / 2)?
- Inspectrum and GQRX both show the same IQ capture as a time-frequency waterfall. What information does the waterfall show that a single FFT frame does not?
Submission
Zip into lab8_YOURNAME.zip:
survey_300_450.png(annotated)deliverable_A_table.mddeliverable_B_checksum.txtfft_spectrum.pngdeliverable_C_answers.mdamplitude_iq.pngdeliverable_D.mdsurvey_plot.py,fft_analysis.py,amplitude_analysis.pywriteup.md