Contenu principal

OFDM Receiver Using Software-Defined Radio

R2026b

This example shows how to design an orthogonal frequency division multiplexing (OFDM) receiver for a single-input single-output (SISO) channel using a software-defined radio (SDR). The OFDM receiver captures and demodulates the OFDM signal that the OFDM Transmitter Using Software-Defined Radio example sends. The OFDM receiver design includes sample buffering for timing adjustment, filtering, carrier frequency adjustment, and OFDM demodulation.

Required Hardware and Software

To run this example, you need one of these SDRs and the corresponding software support package.

The example requires two MATLAB™ sessions, one for the transmitter and one for the receiver. You run the OFDM Transmitter Using Software-Defined Radio example in one MATLAB session to transmit the OFDM signal.

Choose the OFDM Frame Parameters

Choose the OFDM parameters according to your baseband sample rate by selecting the appropriate OFDM Waveform index from the table

OFDM Waveform Index

1

2

Baseband sample rate

3.84 Msps

7.68 Msps

FFT Length

128

256

Cyclic Prefix Length

32

64

Number of Subcarriers

90

180

Subcarrier Spacing

30 KHz

30 KHz

Pilot Subcarrier Spacing

9

20

Channel Bandwidth

3 MHz

6 MHz

Note that the baseband sample rate is derived as BasebandSampleRate=FFTLength×SubcarrierSpacing.

After selecting the OFDM parameters, you must set the data parameters such as modulation order, code rate, number of symbols per frame, and the number of frames per transmission.

You can enable or disable the visualization scopes. However, for long simulations, it is recommended to disable the scope. To control the display of the diagnostic output text, enable or disable the verbosity as needed. To view the decoded data in each frame, enable the print data flag. To view the error vector magnitude (EVM) and peak-to-average power ratio (PAPR) for each frame, enable the corresponding flags.

% Choose the OFDM waveform parameters according to your baseband sample
% rate and provide the corresponding OFDMWaveformIndex from the table above
OFDMWaveformIndex     = 1;

% Data Parameters
dataParams.modOrder       = 4;   % Data modulation order
dataParams.coderate       = "1/2";   % Code rate
dataParams.numSymPerFrame = 30;   % Number of data symbols per frame
dataParams.numFrames      = 45;   % Number of frames to transmit

% Output Parameters
dataParams.enableScopes   = true;                    % Switch to enable or disable the visibility of scopes
dataParams.verbosity      = false;                    % Control to print the output diagnostics at each level of receiver processing
dataParams.printData      = false;                    % Control to print the output decoded data
dataParams.enableEVMCalculation     = false;          % Switch to enable or disable EVM calculation
dataParams.enablePAPRCalculation    = false;          % Switch to enable or disable PAPR calculation

Initialize Receiver Parameters

Use the helperOFDMSetParamsSDR function to initialize the OFDM transmit parameters and the common transmitter/receiver settings needed for the simulation. Then call helperGetRadioRxObj to initialize the parameters required for the receiver System object™ radio.

Set the radio address, center frequency, gain, and the sample rate at which the radio captures data.

By default, the example uses burst mode for reception using USRP or ADALM‑PLUTO radios, since streaming mode processing can cause receive overflows at higher data rates.

If the receiving radio device is a USRP radio, you can enable reception in streaming mode by setting enableRxStreamingInBackground to true. USRP radios support background processing. When enableRxStreamingInBackground is set to true, radio reception runs in the background while received data processing executes in the foreground. This separation reduces the processing load on a single execution thread and can help minimize receive overflows in streaming mode and also achieve higher sample rates.

radioParams.RadioDevice                   = "B210";   % Choose radio device for reception
radioParams.RadioAddress                  = '30F597A';   % update radio address
radioParams.CenterFrequency               = 3e9;   % Center Frequency
radioParams.Gain                          = 60;   % Set radio gain
radioParams.EnableRxStreamingInBackground = false;                    % Enable the flag to receive data in background in streaming mode

If you use an X3xx radio with a TwinRX daughterboard, set the isTwinRxDaughterBoard option to true inside helperGetRadioRxObj to ensure proper channel mapping and synchronization.

Note that the radio receives the OFDM waveform at a higher sample rate and resamples it to baseband sample rate using the resampling factors provided by helperGetRadioRxObj.

Finally, initialize the system parameters and receiver objects.

[sysParams,txParam,transportBlk] = helperOFDMSetParamsSDR(OFDMWaveformIndex,dataParams);
[radio, spectrumAnalyze, constDiag, resampleNum, resampleDen] = helperGetRadioRxObj(radioParams,sysParams);

Execute Receiver Loop

Synchronization

The OFDM receiver checks the sample buffer for the synchronization symbol to find the starting point of the data frames within the received OFDM signal. The receiver correlated the received OFDM signal with the known synchronization symbol. Once the OFDM receiver detects a high correlation peak, it identifies the position where it finds the synchronization symbol as the start of the frame.

Frequency Offset Estimation and Correction

The OFDM receiver estimates and corrects the frequency and timing offset introduced to the transmitted OFDM signal due to channel impairments. The OFDM receiver checks the receiver buffer for the required number of frames and performs automatic frequency correction over each symbol. The receiver averages the frequency correction across the subcarriers and then across every six symbols, considering a group of six symbols as a slot. The receiver considers the overall average value of these corrections as the frequency offset and compensates this frequency offset across the entire frame.

The receiver is considered camped after achieving successful synchronization and channel impairment correction.

A flowchart that shows how an OFDM receiver using SDR synchronizes symbols and calculates carrier frequency offset. The process starts with the SDR device receiving a waveform, rxWaveform. The device begins the sync symbol search and reads in a frame of samples. It then searches for the sync symbol position within the sample buffer. If it detects the sync symbol, it adjusts the sample buffer output pointer to the start of the frame. If it does not detect the sync symbol, it checks if at least 144 symbols have been processed. If yes, it calculates the carrier frequency offset; if no, it reads in another frame of samples. If the receiver is camped, the process ends; otherwise, it loops back to begin the sync symbol search again.

Receiver Processing

This process is the reverse of the process that happens at the transmitter.

Channel Estimation and Equalization

Initially, the OFDM receiver performs channel estimation on the OFDM demodulated reference symbols. To remove the effects of time-varying fading, the receiver selects two reference symbols from adjacent frames to estimate the channel at two different points in time. The receiver then linearly interpolates the channel estimates between the two reference symbols to get the channel estimates for the header and data symbols. The ofdmEqualize function then equalizes the reference symbols and data symbols using the channel estimates.

Header Decoding

The receiver extracts and decodes the header symbols to get the data symbol parameters such as FFT length, subcarrier modulation scheme, and code rate. The receiver uses these parameters to demodulate and decode the data symbols.

Data Decoding

The common phase error (CPE) affects all subcarriers equally and the OFDM receiver uses the pilot symbols within the data symbols to estimate CPE. The helperOFDMRx function corrects the phase errors in the data symbols and the qamdemod function soft decodes the data subcarriers into log likelihood ratios (LLRs).

The receiver then deinterleaves the demodulated bitstream and the vitdec function performs maximum likelihood decoding using the Viterbi algorithm. The descrambler descrambles the decoded bits, and the crcDetect computes the cyclic redundancy check (CRC) and compares it with the appended CRC.

A block diagram of an OFDM receiver processing, which shows the process of header and user data recovery. The Header Data Recovery section includes blocks for OFDM Demodulation, Equalization, BPSK Soft Demodulation, Deinterleaving, Viterbi Decoding, and CRC Detection. The User Data Recovery section includes blocks for OFDM Demodulation, Equalization, Channel Estimation and Interpolation, Carrier Phase Estimation and Correction, QAM Soft Demodulation, Deinterleaving, Viterbi Decoding, Descrambler, and CRC Detection. There are also additional blocks for Rx Filtering, Sample Buffering, and Frequency Offset Estimation and Correction.

% Clear all the function data as they contain some persistent variables
clear helperOFDMRx helperOFDMRxFrontEnd helperOFDMRxSearch helperOFDMFrequencyOffset;
close all;
errorRate = comm.ErrorRate();
toverflow = 0; % Receiver overflow count
rxObj = helperOFDMRxInit(sysParams);
BER = zeros(1,dataParams.numFrames);

% Perform radio reception in the background and store data in a dataQueue
% continuously. Retrieve the data frame-by-frame from the queue to base
% workspace sequentially as after each reception
if radioParams.EnableRxStreamingInBackground && ~strcmpi(radioParams.RadioDevice,'PLUTO')
    dataQueue = parallel.pool.PollableDataQueue;
    pool = backgroundPool;
    future = parfeval(pool, @receiveData, 0, radio, dataQueue, dataParams.numFrames);
end

for frameNum = 1:dataParams.numFrames
    sysParams.frameNum = frameNum;
    if radioParams.EnableRxStreamingInBackground && ~strcmpi(radioParams.RadioDevice,'PLUTO')
        if frameNum == 1
            [data, ok] = poll(dataQueue, 30);
        else
            [data, ok] = poll(dataQueue, 3);
        end
        if ~ok
            if ~isempty(future.Error)
                error('Background receiver failed: %s', future.Error.message);
            else
                warning('Timeout waiting for chunk %d of sweep %d', chunkIdx, sweepIdx);
                continue;
            end
        end
        rxWaveform = data.rxWaveform;
        overflow = data.overflow;
    else
        % Receive data in burst mode otherwise
        [rxWaveform, ~, overflow] = radio();
    end

    % Resample the waveform to baseband sample rate
    rxWaveform = resample(rxWaveform,resampleNum,resampleDen);

    toverflow = toverflow + overflow;

    % Run the receiver processing only when there is no overflow
    if ~overflow

        rxIn = helperOFDMRxFrontEnd(rxWaveform,sysParams,rxObj);

        % Run the receiver processing
        [rxDataBits,isConnected,toff,rxDiagnostics] = helperOFDMRx(rxIn,sysParams,rxObj);

        sysParams.timingAdvance = toff;
        if toff<0
            sysParams.timingAdvance = sysParams.txWaveformSize;
        end

        % Collect bit and frame error statistics
        if isConnected
            % Continuously update the bit error rate using the |comm.ErrorRate|
            % System object
            berVals = errorRate(...
                transportBlk((1:sysParams.trBlkSize)).', ...
                rxDataBits);
            BER(frameNum) = berVals(1);
            if dataParams.printData
                % As each character in the data is encoded by 7 bits, decode the received data up to last multiples of 7
                numBitsToDecode = length(rxDataBits) - mod(length(rxDataBits),7);
                recData = char(bit2int(reshape(rxDataBits(1:numBitsToDecode),7,[]),7));
                fprintf('Received data in frame %d: %s',frameNum,recData);
            end
        end

        if isConnected && dataParams.enableScopes
            constDiag(complex(rxDiagnostics.rxConstellationHeader(:)), ...
                complex(rxDiagnostics.rxConstellationData(:)));
        end

        if isConnected && dataParams.enableEVMCalculation
            evm = helperCalculateEVM(complex(rxDiagnostics.rxConstellationData(:)),dataParams.modOrder);
            fprintf('RMS Error Vector Magitude(EVM) of received OFDM symbols is %.2f',evm);
        end

        if isConnected && dataParams.enablePAPRCalculation
            pm = powermeter(Measurement="Peak-to-average power ratio");
            pm.WindowLength = length(rxDiagnostics.rxConstellationData(:));
            pm.OverlapLength = 0;
            papr = pm(complex(rxDiagnostics.rxConstellationData(:)));
            fprintf('PAPR of received OFDM symbols is %.2f dB',papr);
            release(pm);
        end

        if dataParams.enableScopes
            spectrumAnalyze(rxWaveform);
        end
    else
        % Clear all the data buffers, reset the timing advance value and perform re-synchronization if
        % there is an overflow
        clear helperOFDMRx helperOFDMRxFrontEnd helperOFDMRxSearch;
        disp("Overflow at frame: "+frameNum);
        sysParams.timingAdvance = sysParams.txWaveformSize;
    end
end
Sync symbol found.
Estimating carrier frequency offset ........
Receiver synchronization complete.
......................................

% Display the mean BER value across all frames
fprintf('Simulation complete!\nAverage BER = %d',mean(BER))
Simulation complete!
Average BER = 0
release(radio);

Local Functions

When the receiver is a USRP device, data reception can run in the background while the receiver operates in streaming mode. The receiveData function executes asynchronously in a background thread to continuously fetch samples from the radio. This function places the received data into a data queue.

In the foreground, you can retrieve the data from the data queue sequentially by calling the storeData function

% Run the radio reception in background and send the received data frames
% into a queue
function receiveData(radio,queue,numFrames)
    for i = 1:numFrames
        [data,~,ov] = radio();
        rxData.rxWaveform = data;
        rxData.overflow = ov;
        rxData.frameNum = i;
        send(queue,rxData);
    end
end

Troubleshooting

No data in any frame

Problem

  • The receiver continuously receives no data in any frame.

Possible causes

  • Transmitter is not running

  • Too high or too low transmitter and receiver gains

  • Too many under-runs at the transmitter

Possible solutions

  • Ensure that the transmitter is running

  • Adjust the transmitter and receiver gains

  • Adjust the frame length to make sure there are very few under-runs at the transmitter.

Header CRC fails

Problem

  • Even when synchronization and receiver camping are successful, the header CRC check fails.

Possible causes

  • Insufficient compensation of clock frequency offset.

Possible solutions

  • The frequency compensation algorithm measures and corrects the frequency and phase offset for each frame, and it can estimate the correct offset up to half of the subcarrier spacing. You can calibrate the frequency offset to resolve this error.

For USRP radios, you can determine the frequency offset by sending a tone at a known frequency from the transmitter and measure the offset between the transmitted and received frequency. Apply the measured offset to the center frequency of comm.SDRuReceiver receiver System object.

For ADALM-Pluto radios, run the Frequency Offset Calibration with ADALM-PLUTO Radio in Simulink example to get the frequency offset. Apply the measured offset to the center frequency of the comm.SDRRxPluto receiver System object.

Data decoding fails

Problem

  • The data decoding fails even when the header CRC decoding is successful.

Possible causes

  • Data overflow at the receiver

  • Inaccurate channel estimate due to exceeding frame length.

Possible solutions

  • Adjust the frame size

  • Change the number of symbols per frame to minimize the frame length.

Further Exploration

You can use an external clock source or GPSDO with the radios to try smaller subcarrier spacing of 15 KHz with smaller bandwidths such as 1.4 MHz (LTE bandwidths) or higher FFTLengths such as 512.

This example uses burst mode in the radios to receive the data, with the burst size set to the number of frames. You can further explore receiving the data using the radios in non-burst mode.

See Also

Topics