Due: Friday, June 12, 2026 at 11:59 PM via Gradescope

Introduction

In Lab 5, we explored the challenges of single-carrier communication in multi-path environments. In Lab 6, we pivot to orthogonal frequency-division multiplexing (OFDM). Instead of sending one high-speed stream of symbols, we will divide our data across several narrowband subcarriers. This allows us to handle multi-path fading through simple single-tap frequency-domain equalization.

For this lab, you must use SDR ID 9 or SDR ID 10, as these specific devices are configured in a multi-tap loopback manner to emulate a multipath frequency-selective channel, which is exactly where OFDM shines.

Use the following parameters.

  • A sampling rate of 30e6 samples per second.
  • A transmit gain of -10 dB.
  • A receive gain of 60 dB.
  • An RF bandwidth of 30e6.
  • An RX buffer size of 100e3.

Part I: Create an OFDM Symbol

With OFDM, we build our symbols in the frequency domain before converting them to a time domain signal via an IDFT.

Create an OFDM symbol consisting of 1024 subcarriers, populated with data, pilots, and nulls. There should be one null on the lowest subcarrier, one null on the highest subcarrier, and one null on the DC subcarrier. On the remaining subcarriers, every fourth subcarrier should be reserved for a pilot. All other subcarriers should be populated with data. Data symbols and pilot symbols can both be drawn from M-QAM, with M=4.

To help in this process, create a function get_subcarrier_indices() that looks something like:

[idx_subs_data,idx_subs_pilots,idx_subs_nulls] = get_subcarrier_indices(
    num_subcarriers, 
    pilot_spacing, 
    num_nulls_dc=num_nulls_dc, 
    num_nulls_lower=num_nulls_lower, 
    num_nulls_upper=num_nulls_upper)

where

num_subcarriers = 2**10
num_nulls_upper = 1
num_nulls_lower = 1
num_nulls_dc = 1
pilot_spacing = 4

Then, perform an IDFT to convert the frequency domain symbols to an OFDM signal; in this process, you may find np.fft.ifftshift() particularly useful. Prepend a cyclic prefix (CP) by copying the last L samples of your OFDM signal to its front; let L be one fourth of the number of subcarriers. This comprises an OFDM symbol.

Calculate the peak-to-average power ratio (PAPR) of your OFDM symbol (in dB) and report it in your lab report.

Part II: Transmit and Receive a Single OFDM Symbol

Transmit a single, continuous OFDM symbol using the cyclic=True buffer on the Pluto. Use sdr.rx() to receive from the Pluto, and then extract one copy of this received OFDM symbol from the RX buffer via a simple cross-correlation between the transmitted OFDM symbol and the received samples.

Toss out the cyclic prefix portion of your OFDM symbol. Then, use only the pilots to estimate the channel gain on each of the pilot subcarriers. Then, interpolate between pilots to get the full channel frequency response at all the subcarriers. Plot the interpolated channel response across subcarriers, with markers to indicate the pilot estimates.

Use this interpolated channel response to equalize your data symbols. Then, plot the equalized symbols on the complex plane, with your transmitted constellation overlaid. Include in the title of your plot the resulting symbol error rate (SER).

Part III: SER Curves

Now, produce an SER curve by varying the transmit gain from -40 dB to -10 dB in steps of 5 dB. For each transmit gain setting, run at least 20 trials and average the resulting SER. Create a single plot that shows SER vs. transmit gain for both QPSK (M=4) and 16-QAM (M=16).