Lab: Simulating and Sounding a Multi-Path Channel
Table of Contents
Visualizing the 3GPP TDL channel model..............................................................................................................1
Simulating the Multi-Path Channel.......................................................................................................................... 3
Creating the TX Channel Sounding Waveform.......................................................................................................6
Measuring the RX Channel Frequency Response..................................................................................................8
Compute the Time-Domain Channel Impulse Response........................................................................................9
Running the Channel Sounder over the SDR .....................................................................................................10
Create a Continuous Monitor.................................................................................................................................12
Further Enhancements...........................................................................................................................................14
Multi-path channel models are widely-used in the simulating wireless systems. In this lab, we will build a simple
simulator for a static multi-path channel. We will also show how to build a channel sounder, which is a device
that measures the channel response between a transmitter (TX) and receiver (RX). Channel sounders are key
for researchers studying wireless channel propagation. They are also useful in bringing up any SDR platform to
ensure the wideband response through the TX and RX chains is as expected. Indeed, after ensuring you can
receive a tone correctly, use a channel sounder to make sure the device works and correctly characterize the
TX and RX filters.
In going through this lab, you will learn to:
• Represent multi-path channels and download the 3GPP TDL multi-path model
• Simulate multi-path propagation with a fractional delay
• Measure the channel frequency response via frequency-domain correlation
• Compute the time-domain channel impulse response via an IFFT
• Align the channel impulse response via peak detection
• Build a continuous monitor for the channel
Files: You will need:
• chanSounder.mlx: This file, the main file for the lab
• SISOChan.m: Class file for the main simulator. You will use this in subsequent labs once done
• estChanResp.m: Function for estimating the channel
• TxFilt.m: Use the function that you created in the previous lab
Submissions: Run the lab with Pluto devices with either one or two hosts. Fill in all sections labeled TODO .
Print and submit the PDF. Do not submit the source code.
Visualizing the 3GPP TDL channel model
3GPP is an organization that designs common standards for wireless technologies such as 5G. Among many
other documents, 3GPP publishes channel models that can be used by designer for evaluating their products.
In this lab, we will use a simple tap delay line (TDL) model from 3GPP TR 38.900. In this model, the channel is
described as a number of "taps" or physical paths,
hchan(t) = \sum_k g(k) \delta(t-tau(k)),
2
where g(k) and tau(k) are the gain and delay of path k. MATLAB's excellent 5G Toolbox has a function to
retrieve these standard channels. The following code uses this function. We have set the delay spread to a
typical value for an outdoor suburban setting. The outputs are:
• dlyPath: A vector of delays of each path. Each value is in seconds.
• gainPath: A vector of relative gains of each path. Each value is in dB relative to the strongest path.
This channel would be considered an example of rich scattering, since it corresponds to a case of many paths,
posisbly from reflections, diffractions, and other paths. We will discuss channel modeling more in the wireless
class. For now, we will just use this channel.
tdl = nrTDLChannel('DelayProfile', 'TDL-A', 'DelaySpread', 1e-7);
chaninfo = info(tdl);
gainPath = chaninfo.AveragePathGains';
dlyPath = chaninfo.PathDelays';
To visualize the channel, plot gainPath vs. the delay.
• Use a stem pllot
• Set the BaseValue to -40 so that x-axis is well below the maximum path
• Put the delay in nanoseconds.
% Convert delay to nanoseconds for the stem plot.
dlyns = dlyPath * 1e9;
% Plot using stem
stem(dlyns, gainPath, 'filled');
xlabel('Delay (ns)');
ylabel('Gain (dB)');
title('3GPP TDL channel model');
grid on;
3
To simulate wireless multi-path channels, we need to implement a fractional delay. Suppose that, in
continuous-time, yc(t) = xc(t-tau) for some delay tau, and continuous-time signals xc(t) and yc(t),
Let x(n) = xc(nT) and y(n)=yc(nT) be discrete-time sampled versions of the signals. If the signals are
band-limited, we can write the discrete-time signals as:
y(n) = h(n)*x(n)
where h(n) is a filter that depends on the delay tau. As discussed in class, when tau=kT, so the delay is an
integer number of samples, we have y(n)=x(n-k) or equivalently, the filter h(n) = delta(n-k). When tau
is not an integer number of samples, it is called a fractional delay. MATLAB has excellent tools to implement
fractional delays. Complete and run the following code, which shift a ramp signal by a fractional delay. The
class dsp.VariableFractionalDelay is a bit of an over-kill for this task, but is simple to use. Under the
hood, the class creates a fractional FIR filter and then uses it to predict the delay signal.
% Create a fractional delay object fracDly =
dsp.VariableFractionalDelay(...
'InterpolationMethod', 'Farrow','FilterLength',8,...
'FarrowSmallDelayAction','Use off-centered kernel',...
'MaximumDelay', 1024);
% Create a ramp signal
nt = 100; x = (0:nt-1)'/nt;
Simulating the Multi-Path Channel
4
% Delay the signal by a fractional amount
dlySamp = 10.7;
y = fracDly(x, dlySamp);
% TODO: Plot x and y
plot(x, '-o', 'DisplayName', 'Original');
hold on;
plot(y, '-x', 'DisplayName', 'Delayed');
legend;
xlabel('Sample Number'); ylabel('Amplitude');
The class SISOChan uses dsp.VariableFractionalDelay class to implement the fractional delay along each
path. The constructor of the class creates the dsp.VariableFractionalDelay class objects. Complete the
code in SISOChan.stepImpl() method that:
• Computes the delays of each path in samples
• Computes the complex gain of each path
• Delay the input signal by the path delays using the fractional delay class
• Multiplies the delayed signals by the path gains and add them
% TODO: Complete the code in SISOChan.stepImpl() method
title('Fractional Delay of Ramp Signal');
5
To validate the channel model, we will send an impulse through the channel. Complete the following code which
creates an impulse signal, sends the impulse through the channel, and plots the output. You should see that
the observed impulse response mostly overlaps with the true impulse response. The difference arises from the
fact that we are sampling the paths at fractional delays.
% Create the channel object
fsamp = 30.72e6*2;
%chan = SISOChan(dlyPath == dlyPath, fsamp==fsamp, gainPath==gainPath);
% Create the channel object
fsamp = 30.72e6*2; %chan = SISOChan('dlyPath', dlyPath, 'fsamp', fsamp,
'gainPath', gainPath);
% Create a vector of times at the sample
rate. tmin = -0.5e-6; tmax = 2e-6;
t = (tmin:1/fsamp:tmax)';
nt = length(t);
% Create a signal ximp of length nt representing an impulse at zero.
ximp = zeros(nt, 1);
ximp(t == 0) = 1; % Set the value to 1 at the sample where time is zero
% Get the impulse response by sending ximp through the channel
%yimp = chan.stepImpl(ximp);
% Compute the energy in each output sample
%ypow = pow2db(max(abs(yimp).^2, 1e-8));
% Plot ypow vs. time using the stem plot
figure;
%stem(t*1e9, ypow); % Multiplying time by 1e9 to convert from seconds to nanoseconds
xlabel('Time (ns)');
ylabel('Energy (dB)');
title('Channel Impulse Response');
grid on;
6
Creating the TX Channel Sounding Waveform
The above code shows that, in principle, you could measure the channel by sending an impulse and looking
at the response. However, an impulse is typically not practical since it requires the transmitter to send a lot of
energy in a short period. Instead, most systems use a continuous wide band transmission. In this lab, we will
illustrate a very simple but effecitve frequency-domain channel sounding methd.
In frequency-domain channel sounding, we create the waveform in frequency domain. First, we create a vector
xfd of nfft QPSK symbols. Then, we take the IFFT to create the time-domain waveform.
% Parameters nfft
= 1024; fsamp =
30.72e6*2;
% Set the random number seed generator so that the TX and RX use the same
% random seed
rng(1, "twister");
% Flags
usePreRecorded = true; % Set to true to use pre-recorded data. Set to false to generate new
da saveData = true; % Set to true to save the generated data. Set to false to not save.
if usePreRecorded
try
load txData;
7
catch
warning('Could not find txData. Generating new data.');
usePreRecorded = false; % If the data cannot be loaded, generate new data. end
end
if ~usePreRecorded
% Generate a vector of nfft random QPSK symbols. xfd =
(randi(4, nfft, 1) - 2.5) + 1i * (randi(4, nfft, 1) - 2.5);
% Take the IFFT to obtain time-domain representation
x = ifft(xfd);
% Save frequency-domain TX data if the saveData flag is true
if saveData
save txData xfd;
end
end
% TODO: Take IFFT
x = ifft(xfd);
We next create a TX filter object that we built in the previous lab. Since we are not over-sampling (ovRatio =
1), we are just using the filter to scale the samples to the range of [-1,1].
% Create the TX filter object
backoffLev = 12; txFilt = TxFilt(vratio==1, rateIn==fsamp, backoffLev == backoffLev,
backoffAutoScale == true);
Not enough input arguments.
Error in vratio (line 12)
[s,c] = ellipj(u,mp);
% Filter the signal
x = txFilt(x);
In the real system, the TX will continuously transmit the signal x. For the simulation, we will create a signal
xrep with that is repeated nrep times. The resulting signal should be nrep*nfft x 1. You can use the
repmat function.
nrep = 4;
% TODO: Repeat the signal x, nrep times
% xrep
% Repeat the signal x, nrep times
xrep = repmat(x, [nrep, 1]);
Finally, we run the repeated signal through the channel. We will also add some noise 30 dB below the received
signal. We will discuss how to add noise later. For now, you can just run the provided code.
8
% Create the channel object
fsamp = 30.72e6*2;
chan = SISOChan(dlyPath == dlyPath, fsamp==fsamp, gainPath==gainPath);
% TODO: Run the input xrep through the channel
% r0 = ... r0
= chan(xrep);
% Add noise to r0
signalPower = mean(abs(r0).^2);
noisePower = signalPower / (10^(30/10));
noise = sqrt(noisePower/2) * (randn(size(r0)) + 1i*randn(size(r0)));
r0 = r0 + noise;
Measuring the RX Channel Frequency Response
Since the signal x is being repeatedly transmitted, the channel will act as a circular convolution. Therefore, the
channel frequency response can be estimated via:
hfd(k) = rfd(k) / xfd(k)
where rfd = fft(r). Complete the first two sections in the function estChanResp to estimate the frequency
response. You can ignore the other parts and outputs for now. After completing this section, run the following
code.
% Extract one FFT period of the data
r1 = r(nfft+1:2*nfft);
% TODO: Complete the TODO sections in estChanResp up to hfd = ...
% Then run:
% hfd = estChanResp(r1,xfd);
hfd = rfd ./ xfd;
The vector hfd from the previous part provides the sampled channel frequency response. Plot the power gain
in dB vs. frequency. The frequency should be in MHz and can be computed from the sample rate, fsamp. Use
the command fftshift to place the DC frequency in the center.
You should see the following:
• The channel has a lot of variation across frequency. This is due to the multi-path with high delay spread.
We will discuss in the wireless class.
• The channel starts to roll off after about |f| > 20 MHz. This is not from the physical channel, but the
filtering in the channel fractional delay.
• There is an arbitrary scaling we have scaled the signals in the TX.
9
% TODO: Compute the channel power gain and shift it using fftshift
and % plot against the frequency in MHz. Label your aees.
% hfddB = ...
% fMHz = ...
% plot(...)
% Estimate the channel frequency response
hfddB = 20 * log10(abs(hfd));
fMHz = fsamp * (-nfft/2:nfft/2-1) / nfft / 1e6;
plot(fMHz, fftshift(hfddB));
xlabel('Frequency (MHz)');
ylabel('Gain (dB)');
title('Estimated Channel Frequency Response');
grid on;
Compute the Time-Domain Channel Impulse Response
We can compute the channel impulse response by simply taking the IFFT of the channel frequency response. This
IFFT is performed in the function estChanResp. Note that we also shift the maximum peak to a fixed position and
normalize the channel response to the average energy. This last step is useful since the signals have an arbitrary
scaling at this point. In the wireless class, we will discuss how to measure the SNR.
Complete the TODO section of that code and run the following command.
% TODO: Complete the TODO sections in estChanResp up to h = ...
% Then run:
% [hfd, h] = estChanResp(r,xfd);
% TODO: Compute the estimated impulse responses
% hpow = pow2db(...);
% TODO: Plot hpow vs. time in
ns % tns = ...
% plot(tns, ...); hpow
= pow2db(abs(h).^2);
tns = (0:length(h)-1) / fsamp * 1e9; % convert to ns
plot(tns, hpow);
xlabel('Time (ns)');
ylabel('Gain (dB)');
title('Estimated Channel Impulse Response');
grid on;
The channel can have an arbitrary delay and gain relative to the true impulse response. Complete the code below
that scales and shifts the true and measures impulse response. You will see in this rich multipath environment the
channel esimator gets the rough locations of paths, but there is a lot of noise.
10
% Shift and scale the true path gains relative to the strongest path
[Pm, im] = max(gainPath);
relDlyTrue = dlyPath - dlyPath(im); % shift in time relative to strongest path
relDlyTrue = relDlyTrue*1e9; % convert to ns
gainTrue = gainPath - Pm; % shift in gain relative to strongest path
% TODO: Shift and scale the values in hpow and tns
% [Pm,im] =
max(hpow); %
relDlyMeas = ...
% relGainMeas = ...
% TODO: Plot the relative true and measured gains.
% Try to adjust the plot to get a good visualization.
Running the Channel Sounder over the SDR
We will now run the channel sounder over a real channel with the SDRs!
As before, set the mode to the desired configuration (loopback, two hosts with single host, or two hosts with two
hosts).
% TODO: Set parameters
% Set to true for loopback, else set to false
loopback = false;
% Select to run TX and RX
runTx = true;
runRx = true;
We a;sp set the parameters
• saveData: Indicates if the data is to be saved
• usePreRecorded: Indicates if the data is to use the pre-save data. This way, you will not need to run the
device
% Parameters saveData
= true; usePreRecorded
= false;
% Sample rate
fsamp = 30.72e6*2;
Plug one or two Plutos into the USB ports of your computer. If it is correctly working, in each device the Ready
light should be solid on and the LED1 light should be blinking. Next, run the following command that will detect
the devices and create the TX and RX ob
% clear previous instances
11
...
Transmit the data xrep repreatedly through the Pluto device.
if runTx && ~usePreRecorded
% TODO: Use the tx.release and tx.transmitRelease commands to
% continuously send xrep
end
% If not running the RX, stop the live script now
if ~runRx
return;
end
On the RX Pluto device, receive the data
nbits = 12; % number of ADC
bits fullScale = 2^(nbits-1); % full scale
if ~usePreRecorded
% TODO: Capture data for nfft samples
% r = rx.capture(...)
% TODO: Scale to floating
point % r = ...
% Save the data
if saveData
save rxDataSing r;
end
else
% Load pre-recorded
data load rxDataSing;
end
Estimate and plot the channel impulse response. You should a strong peak, but you will likely see no other
peaks since this system does not have sufficient bandwidth to resolve the multi-path in indoors (assuming you
are running the experiment indoors).
clear tx rx
% add path to the common directory where the function is found
addpath('..\..\common');
% Run the creation function. Skip this if we are using pre-recorded
% samples
if ~usePreRecorded
[tx, rx] = plutoCreateTxRx(createTx == runTx, createRx == runRx, loopback ==
loopback,
nsampsFrameRx == nfft, nsampsFrameTx == nfft, sampleRate == fsamp);
end
12
% Estimate the channel
[hfd,h] = estChanResp(r,xfd,'normToMean', true);
% Compute the estimated impulse responses
% hpow = pow2db(...);
hpow = pow2db(abs(h).^2);
% TODO: Plot hpow vs. time in
ns % tns = ...
% plot(tns, ...);
Create a Continuous Monitor
We will now continuously monitor and plot the channel impulse response and SNR.
if runTx && ~usePreRecorded
% TODO: Use the tx.release and tx.transmitRelease commands to
% continuously send xclip
end
% Number of captures
ncaptures = 100;
% Load pre-recorded data if required
if usePreRecorded load
rxDatMult; ncaptures =
size(rdat,2);
else
rdat = zeros(nfft, ncaptures);
end
if usePreRecorded
r = rdat(:,1);
else % TODO: Capture
initial data
% r = ...
end
% TODO: Get the channel response
% [hfd,h] = estChanResp(...);
% Re-compute the impulse response and impulse power
[hfd, h] = estChanResp(r, xfd, 'normToMean', true);
13
hpow1 = pow2db(abs(h(1:nplot)).^2);
% TODO: Get the initial impulse response as before.
%
% tus1 = relative delays in micro-seconds for the first nplot samples
% hpow1 = vector of impulse reponse
nplot = 256;
% Create the initial plot
clf;
p = plot(tus1,hpow1);
xlim([min(tus1),max(tus1)]);
ylim([-20, 40]);
xlabel('Time [us]');
ylabel('Path gain [dB]');
grid on;
% Set pointers to Y data for both plots
p.YDataSource = 'hpow1';
snr =
zeros(ncaptures,1); for
t = 1:ncaptures if
~usePreRecorded
% TODO: Capture data
% r = rx.capture(...);
% Save data if required
if saveData
rdat(:,t) = r;
end
else
% Load pre-recorded
r = rdat(:,t);
end
% TODO: Re-compute the impulse response
% [~,h] = estChanResp(...);
% TODO: Re-compute hpow1 = impulse response on the first nplot samples.
% hpow1 = ...
% Re-compute the impulse response
[~, h] = estChanResp(r, xfd, 'normToMean', true);
% Re-compute hpow1 = impulse response on the first nplot samples
hpow1 = pow2db(abs(h(1:nplot)).^2);
14
% Redraw plot
refreshdata(p);
drawnow
15
end
% Save data
if saveData
save rxDatMult rdat;
end
Further Enhancements
There are several ways we can improve the channel sounder. Feel free to take this code and modify it if you
are interested.
• We can integrate over multiple RX frames to average out the noise.
• Also, if we have multiple RX frames, we can estimate the carrier frequency offset (CFO). Estimating and
correcting the CFO is essential to integrate over multiple frames.
• Multiple frames will also allow us to measure fading, which is the variation of the channel over time. This
process is discussed in the wireless communications class
• We can use a wider band sounder (i.e., higher sample rate) to resolve closer multipath component
• A very similar method can be used for RADAR