Chapter: 7 (Week 8) Duration: 3.5 hr Substrate: ANTSDR E200 (physical); virtual path: lab7-2msps.iq + lab7-8msps.iq + lab7-20msps.iq Points: 10
Overview
Capture the same 433.92 MHz OOK signal at three different sample rates (2/8/20 MSPS) and analyse the spectral and temporal differences. Observe: spectral resolution, noise floor, dynamic range, and neighbouring-signal visibility. Connect observations to the Wyglinski IQ-sampling theory from Week 8.
Part 1: Capture Setup (45 min)
Physical path (ANTSDR E200)
Configure three sequential captures of the same ISM-band signal source. Use a known signal source: a 433.92 MHz OOK temperature sensor (or the instructor-provided lab signal source at 433.92 MHz).
GNU Radio capture flowgraph:
[PlutoSDR Source]
URI: ip:192.168.1.10
Center Freq: 433.92e6
Sample Rate: (varies: 2e6 / 8e6 / 20e6)
RF Bandwidth: (varies: same as sample rate)
Gain: Manual, 40 dB
|
[File Sink: lab7-Nmsps.iq]
|
[QT GUI Frequency Sink]
Run each for 15 seconds. You will have three files: lab7-2msps.iq, lab7-8msps.iq, lab7-20msps.iq.
Virtual path
Use the provided three IQ archive files. They were captured from a 433.92 MHz OOK temperature sensor at the three sample rates.
Part 2: Spectrum Comparison (60 min)
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import welch
def spectrum_analysis(filename, fs, label, colour):
samples = np.fromfile(filename, dtype=np.complex64)
# Use Welch's method for a smoother power spectrum estimate
N_seg = min(len(samples), 8192)
freqs, psd = welch(samples, fs=fs, nperseg=N_seg, return_onesided=False)
freqs = np.fft.fftshift(freqs)
psd_db = 10 * np.log10(np.fft.fftshift(psd) + 1e-15)
plt.plot(freqs / 1e3, psd_db, label=label, color=colour, alpha=0.8, linewidth=0.8)
plt.figure(figsize=(14, 7))
spectrum_analysis("lab7-2msps.iq", 2e6, "2 MSPS", 'blue')
spectrum_analysis("lab7-8msps.iq", 8e6, "8 MSPS", 'orange')
spectrum_analysis("lab7-20msps.iq", 20e6, "20 MSPS", 'green')
plt.xlabel("Frequency offset from centre (kHz)")
plt.ylabel("Power spectral density (dB/Hz)")
plt.title("433.92 MHz OOK sensor at three sample rates")
plt.legend(); plt.grid(True, alpha=0.4)
plt.xlim(-10000, 10000) # ±10 MHz view
plt.tight_layout(); plt.show()
From the plot, answer:
- What is the noise floor (dBm/Hz) for each sample rate? (Look at the flat portions of the PSD away from the signal)
- The noise floor shifts as sample rate increases. By approximately how many dB between 2 MSPS and 20 MSPS? What is the theoretical prediction (thermal noise power = kTB; doubling bandwidth = +3 dB noise)?
- At 20 MSPS, are there any signals visible in the spectrum besides the target 433.92 MHz source? Identify and describe them (hint: adjacent ISM devices, harmonics, or interference).
- At 2 MSPS, how wide is the target OOK signal's occupied bandwidth (the −3 dB and −20 dB bandwidth)?
Part 3: Time-Domain Analysis (45 min)
Look at the OOK pulse structure in the time domain.
import numpy as np
import matplotlib.pyplot as plt
def time_analysis(filename, fs, label):
samples = np.fromfile(filename, dtype=np.complex64)
envelope = np.abs(samples[:50000])
t = np.arange(len(envelope)) / fs * 1e3 # time in ms
plt.figure(figsize=(14, 3))
plt.plot(t, envelope, linewidth=0.4)
plt.xlabel("Time (ms)"); plt.ylabel("Amplitude")
plt.title(f"OOK envelope: {label} (first 50,000 samples)")
plt.grid(True, alpha=0.3); plt.tight_layout(); plt.show()
time_analysis("lab7-2msps.iq", 2e6, "2 MSPS")
time_analysis("lab7-8msps.iq", 8e6, "8 MSPS")
time_analysis("lab7-20msps.iq", 20e6, "20 MSPS")
From the three plots:
- What is the approximate symbol period (in milliseconds) of the OOK signal?
- At which sample rate is it easiest to see individual bit transitions? Why? (More samples per symbol = more time-domain resolution)
- At 20 MSPS, how many samples represent one symbol period? (samples_per_symbol = fs × T_symbol)
Part 4: Dynamic Range Measurement (30 min)
import numpy as np
def measure_dynamic_range(filename, fs):
samples = np.fromfile(filename, dtype=np.complex64)
envelope = np.abs(samples)
# Signal peak (dBFS)
peak = 20 * np.log10(envelope.max())
# Noise floor estimate: median of the lowest 10% of samples (silence periods)
sorted_env = np.sort(envelope)
noise_floor_lin = np.median(sorted_env[:len(sorted_env)//10])
noise_floor_db = 20 * np.log10(noise_floor_lin + 1e-9)
dynamic_range = peak - noise_floor_db
print(f" Peak: {peak:.1f} dBFS Noise floor: {noise_floor_db:.1f} dBFS DR: {dynamic_range:.1f} dB")
print("2 MSPS:"); measure_dynamic_range("lab7-2msps.iq", 2e6)
print("8 MSPS:"); measure_dynamic_range("lab7-8msps.iq", 8e6)
print("20 MSPS:"); measure_dynamic_range("lab7-20msps.iq", 20e6)
Theoretical prediction check: The noise floor should rise by 10×log10(fs_new/fs_old) dB when sample rate increases (wider bandwidth admits more noise power). Does the measured shift match the theory?
Part 5: Sample-Rate Choice Analysis (30 min)
Write a 1-paragraph analysis: for capturing and demodulating this specific OOK ISM protocol, which sample rate would you choose in a real deployment and why? Your answer must address:
- Symbol resolution (samples per symbol at each rate)
- Noise floor (wider bandwidth = more noise)
- Spectrum visibility (ability to see adjacent channels)
- Storage cost (file size per unit time at each rate)
- Aliasing risk (are any nearby signals aliased in at any rate?)
Deliverables
- Spectrum comparison plot (all three sample rates on one figure)
- Three time-domain envelope plots (one per sample rate)
- Dynamic-range measurements (Python output, Part 4)
- Written answers to all analysis questions (Parts 2, 3, 4)
- Sample-rate choice analysis paragraph (Part 5)
Grading (10 points)
| Item | Points |
|---|---|
| Spectrum comparison plot (correct x-axis, all three rates visible) | 2 |
| Time-domain plots with correct sample-rate labeling | 2 |
| Dynamic range measurements + theoretical comparison | 2.5 |
| Analysis question answers (Parts 2-4) | 2 |
| Sample-rate choice analysis paragraph | 1.5 |