├── BPSK_modulate.m
├── Correlation_Energy_Results.png
├── Cross_correlation_results.png
├── Magnitude_Samples_SFT.png
├── OFDM_modulate.m
├── Power_Spectrum_Density.png
├── README.md
├── STF_Magnitudes_PostDistortion.png
├── addLTF.m
├── addSTF.m
├── main.m
├── plotUtility.m
└── printBitsAsString.m
/BPSK_modulate.m:
--------------------------------------------------------------------------------
1 | function OFDM_symbols = BPSK_modulate(bits, data_positions, pilot_positions)
2 | % Perform BPSK modulation
3 | bpsk_symbols = pskmod(bits,2);
4 | bpsk_symbols = reshape(bpsk_symbols,[],48);
5 |
6 | % Allocate and place symbols into OFDM frame
7 | numRows = size(bpsk_symbols, 1);
8 | OFDM_symbols = complex(zeros(numRows,64));
9 | OFDM_symbols(:,data_positions) = bpsk_symbols;
10 | % Assume all of the 4 pilots are fixed to 1 + 0 * j
11 | OFDM_symbols(:,pilot_positions) = 1;
12 | end
--------------------------------------------------------------------------------
/Correlation_Energy_Results.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GAOChengzhan/OFDM_Communication_System/f87b447f352e988f3dd57647392812309abeaef8/Correlation_Energy_Results.png
--------------------------------------------------------------------------------
/Cross_correlation_results.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GAOChengzhan/OFDM_Communication_System/f87b447f352e988f3dd57647392812309abeaef8/Cross_correlation_results.png
--------------------------------------------------------------------------------
/Magnitude_Samples_SFT.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GAOChengzhan/OFDM_Communication_System/f87b447f352e988f3dd57647392812309abeaef8/Magnitude_Samples_SFT.png
--------------------------------------------------------------------------------
/OFDM_modulate.m:
--------------------------------------------------------------------------------
1 | function output_symbols = OFDM_modulate(input_symbols)
2 | N = 64;
3 | prefixLength = 16;
4 |
5 | % Transform symbols from frequency to time domain
6 | time_samples = ifft(input_symbols, N, 2);
7 |
8 | % Add cyclic prefix for OFDM
9 | cyclic_prefix = time_samples(:, end-prefixLength+1:end);
10 | withPrefix = [cyclic_prefix, time_samples];
11 |
12 | % Flatten the output for transmission
13 | output_symbols = withPrefix(:).';
14 | end
--------------------------------------------------------------------------------
/Power_Spectrum_Density.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GAOChengzhan/OFDM_Communication_System/f87b447f352e988f3dd57647392812309abeaef8/Power_Spectrum_Density.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # OFDM_Communication_System
2 |
3 | OFDM is a mainstream PHY-layer technology widely adopted by modern wireless networks (e.g., WiFi and LTE). In this repo, an OFDM communication system in Matlab is implemented. The system represents a simplified version of the WiFi (802.11) PHY layer, which involves packet construction at the transmitter side, and packet
4 | detection, synchronization and decoding at the receiver side.
5 |
6 | ## (1) Packet construction and OFDM modulation.
7 | #### Step (a) BPSK modulation:
8 | Create a packet represented by a vector of random bits {0,1,1,0,1…}. The vector contains 4800 bits. Convert the digital bits into BPSK symbols (1+0*j or -1+0*j), and then group the BPSK symbols into 802.11 OFDM symbols.
9 |
10 |
11 |
12 |
13 | #### Step (b) OFDM modulation:
14 | Modulate each OFDM symbol using 64-point IFFT, and add a 16-sample cyclic prefix to it.
15 | Ref: IEEE 802.11-2007, Section 17.3.5.9.
16 | #### Step (c) Add STF and LTF preambles to each data packet
17 | Follow Sec. 17.3.3 of IEEE 802.11-2007.
18 |
19 | **The magnitude of samples in the resulting STF**
20 |
21 |
22 |
23 | **The power spectrum density of the OFDM data symbols**
24 |
25 |
26 |
27 | ## (2) Packet transmission and channel distortion
28 | After the above steps, the packet is modulated into a sequence of samples (complex numbers). A number of (e.g., 100) zero samples is added before the packet, to represent the idle period before the actual transmission happens. Suppose the packet is sent through a simplified wireless channel, with the following distortion effects:
29 |
30 | (i) Magnitude distortion: the channel attenuates the magnitude of each sample to 10^-5 of the original
31 |
32 | (ii) The channel shifts the phase of each sample by -3pi/4, i.e., multiplying it by exp(-j*3pi/4)
33 |
34 | (iii) The imperfect radio hardware causes a frequency offset between the transmitter and receiver. This offset causes the receiver's phase to drift from the transmitter's phase. Suppose the phase drift is exp(-j * 2 * pi * 0.00017) per sample, i.e., for the kth sample, the phase drift is exp(-j * 2 * pi * 0.00017 * k)
35 |
36 | (iv) Channel noise. For each sample, a Gaussian random number (mean 0 and variance 10^-14) is added to represent channel noise.
37 |
38 | **The magnitude of samples in the packet’s STF, after the channel distortion effects:**
39 |
40 |
41 |
42 | ## (3) Packet detection
43 | The channel-distorted samples are what the receiver actually receives. But the receiver actually needs the packets and the bits therein. So how does a receiver know a packet arrives? The answer is using a self-correlation algorithm to detect the presence of the STF, thus identifying the arrival of a packet. So, implement the self-correlation-based algorithm to detect packets.
44 |
45 | **The self-correlation results as a function of sample index**
46 |
47 |
48 |
49 |
50 | The indexes of the samples where packets are detected range from 101 to 260.
51 |
52 | ## (4) Packet synchronization
53 | Packet detection does not tell us the exact starting time of a packet. But since the STF sequence is known to the receiver, a cross-correlation algorithm can be used to single out the first sample of the STF, thus achieving synchronization between receiver and transmitter. So, the cross-correlation algorithm is implemented for synchronization, again using the channel-distorted sequence of samples as input.
54 |
55 | **The cross-correlation results, as a function of sample index**
56 |
57 |
58 |
59 | Indices of samples corresponding to the STF starting time, for each packet: 101 117 133 149 165 181 197 213 229 245.
60 |
61 | ## (5) Channel estimation and packet decoding
62 | Ref: Chapter 6 of the MS thesis “SOFTWARE DEFINED RADIO (SDR) BASED IMPLEMENTATION OF IEEE 802.11 WLAN BASEBAND PROTOCOLS”
63 |
64 | Step (a)
65 | After synchronization, the receiver knows the exact starting time of the LTF as well. Leverage the LTF to estimate the frequency offset between the transmitter and receiver.
66 |
67 | **The frequency offset between the transmitter and receiver: 0.000170**
68 |
69 | Step (b)
70 | The receiver knows the sequence of samples in LTF, so it can estimate the channel magnitude/phase distortion to each sample (corresponding to each subcarrier in an OFDM
71 | symbol).
72 |
73 | **The channel distortion to each subcarrier:**
74 |
75 |
76 |
77 |
78 | Step (c) Decode the digital information in each OFDM data symbol.
79 |
80 | First, as the receiver knows the starting time of each OFDM data symbol, it can run FFT over each OFDM symbol and convert it into a number of (64) subcarriers.
81 |
82 | Then, it can recover the original BPSK symbols by reverting the channel distortion (We assume the channel is stable over an entire packet. So the OFDM symbols suffer from the same channel distortion as the LTF).
83 |
84 | Finally, it can demap the BPSK symbols to {0,1} information bits and convert the bits into characters. Now the receiver fully recovers the sequence of bits sent by the transmitter!
85 |
86 |
87 |
--------------------------------------------------------------------------------
/STF_Magnitudes_PostDistortion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GAOChengzhan/OFDM_Communication_System/f87b447f352e988f3dd57647392812309abeaef8/STF_Magnitudes_PostDistortion.png
--------------------------------------------------------------------------------
/addLTF.m:
--------------------------------------------------------------------------------
1 | function [output_symbols,ltf_freq] = addLTF(input_symbols)
2 | % Generate LTF symbols
3 | ltf_freq = genLTFFrequencySymbols();
4 | ltf_time = genLTFTimeSymbols(ltf_freq);
5 |
6 | % Append to the input symbols
7 | output_symbols = [ltf_time, input_symbols];
8 | end
9 |
10 | function ltf_freq = genLTFFrequencySymbols()
11 | ltf_freq = complex(zeros(1, 64));
12 | ltf_freq(end-25:end) = [1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1];
13 | ltf_freq(1:27) = [0, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1];
14 | end
15 |
16 | function ltf_time = genLTFTimeSymbols(ltf_freq)
17 | ltf_single = ifft(ltf_freq);
18 | ltf_time = [ltf_single(end-31:end), ltf_single, ltf_single];
19 | end
20 |
--------------------------------------------------------------------------------
/addSTF.m:
--------------------------------------------------------------------------------
1 | function [output_symbols,stf_single] = addSTF(input_symbols)
2 | % Generate STF symbols
3 | stf_freq = genSTFFrequencySymbols();
4 | [stf_time,stf_single] = genSTFTimeSymbols(stf_freq);
5 |
6 | % Visualize
7 | plotUtility('single', linspace(1,64, 64), ...
8 | abs(fftshift(fft(input_symbols,64))).^2, ...
9 | [], 'Power Spectrum Density of the OFDM Data Symbols', ...
10 | 'Indices', 'Power Spectrum Density', '', '', ...
11 | 'Power_Spectrum_Density.png');
12 | plotUtility('single',linspace(1,size(stf_time,2),size(stf_time,2)), ...
13 | abs(stf_time), [], 'Magnitude of the Samples in the STF', ...
14 | 'Sample Indices', 'Magnitude','', '','Magnitude_Samples_SFT.png');
15 |
16 | % Append to the input symbols
17 | output_symbols = [stf_time, input_symbols];
18 | end
19 |
20 | function stf_freq = genSTFFrequencySymbols()
21 | stf_freq = complex(zeros(1, 64));
22 | stf_freq(end-25:end) = sqrt(13/6) * [0, 0, 1+1i, 0, 0, 0, -1-1i, 0, 0, 0, 1+1i, 0, 0, 0, -1-1i, 0, 0, 0, -1-1i, 0, 0, 0, 1+1i, 0, 0, 0];
23 | stf_freq(1:27) = sqrt(13/6) * [0, 0, 0, 0, -1-1i, 0, 0, 0, -1-1i, 0, 0, 0, 1+1i, 0, 0, 0, 1+1i, 0, 0, 0, 1+1i, 0, 0, 0, 1+1i, 0, 0];
24 | end
25 |
26 | function [stf_time, stf_single] = genSTFTimeSymbols(stf_freq)
27 | stf_single = ifft(stf_freq);
28 | stf_single = stf_single(1:16);
29 | stf_time = repmat(stf_single, [1, 10]);
30 | end
--------------------------------------------------------------------------------
/main.m:
--------------------------------------------------------------------------------
1 | %%% Preparation
2 | clear;
3 | close all;
4 | % Subcarrier allocation
5 | data_positions = [2:7, 9:21, 23:27, 39:43, 45:57, 59:64];
6 | pilot_positions = [8, 22, 44, 58];
7 |
8 | %%% ========== =========================================== ========== %%%
9 | %%% ========== (1) Packet Construction and OFDM Modulation ========== %%%
10 | %%% ========== =========================================== ========== %%%
11 |
12 | %%% Step (a) BPSK modulation:
13 | % Random packet of bits
14 | PACKET_LENGTH = 4800;
15 | bits = double(randi([0 1], PACKET_LENGTH, 1));
16 | fprintf("Transmitted Strings:\n");
17 | printBitsAsString(bits);
18 | % BPSK modulation: Convert 0 -> 1 and 1 -> -1
19 | OFDM_symbols = BPSK_modulate(bits, data_positions, pilot_positions);
20 | freq_domain_symbols = OFDM_symbols;
21 | %%% Step (b) OFDM Modulation:
22 | OFDM_symbols = OFDM_modulate(OFDM_symbols);
23 |
24 | %%% Step (c) Add STF and LTF preambles to each data packet
25 | %%% Step (d) Plot the magnitude of samples in the resulting STF.
26 | [OFDM_symbols,ltf_freq] = addLTF(OFDM_symbols);
27 | [packet_data,stf_single] = addSTF(OFDM_symbols);
28 |
29 | %%% ======== ============================================== ========== %%%
30 | %%% ======== (2) Packet transmission and channel distortion ========== %%%
31 | %%% ======== ============================================== ========== %%%
32 |
33 | %%% Prepend (e.g., 100) zero samples before the transmitted packet
34 | ZERO_PADDING = 100;
35 | packet_data = [zeros(1, ZERO_PADDING), packet_data];
36 |
37 | %%% (i) Attenuate the signal magnitude
38 | ATTENUATION_FACTOR = 10^(-5);
39 | packet_data = packet_data * ATTENUATION_FACTOR;
40 |
41 | %%% (ii) Apply a global phase shift due to the channel
42 | PHASE_SHIFT = -3 * pi / 4;
43 | packet_data = packet_data * exp(1i * PHASE_SHIFT);
44 |
45 | %%% (iii) Compensate for the frequency offset due to radio hardware
46 | % imperfections
47 | FREQ_OFFSET = 0.00017; % Frequency offset value
48 | TIME_VECTOR = linspace(1, length(packet_data), length(packet_data));
49 | packet_data = packet_data .* exp(-2i * pi * FREQ_OFFSET * TIME_VECTOR);
50 |
51 | %%% (iv) Introduce Gaussian noise to simulate a noisy channel
52 | NOISE_STDDEV = 10^(-14);
53 | channel_noise = normrnd(0, NOISE_STDDEV, size(packet_data));
54 | packet_data = packet_data + channel_noise;
55 |
56 | %%% Visualize the magnitude of the STF after channel distortion
57 | STF_SAMPLES = 160;
58 | plotUtility('single', TIME_VECTOR(1:STF_SAMPLES), ...
59 | abs(packet_data(:, (ZERO_PADDING + 1):(ZERO_PADDING + STF_SAMPLES))), ...
60 | [], 'Post-Distortion Sample Magnitudes of the STF', 'Sample Indices', ...
61 | 'Magnitude', '', '', 'STF_Magnitudes_PostDistortion.png');
62 |
63 |
64 | %%% ====================== ==================== ====================== %%%
65 | %%% ====================== (3) Packet detection ====================== %%%
66 | %%% ====================== ==================== ====================== %%%
67 |
68 | %%% Number of samples in the repeated STF section
69 | STF_REPEAT_LENGTH = 16;
70 |
71 | %%% Compute the sliding self-correlation and energy
72 | len = length(packet_data) - 2 * STF_REPEAT_LENGTH + 1;
73 | correlation_results = zeros(1, len);
74 | energy_results = zeros(1, len);
75 |
76 | for idx = 1:len
77 | % Segment samples corresponding to repeated STF sections
78 | stf_1 = packet_data(idx : idx + STF_REPEAT_LENGTH - 1);
79 | stf_2 = packet_data(idx + STF_REPEAT_LENGTH : ...
80 | idx + 2 * STF_REPEAT_LENGTH - 1);
81 |
82 | % Self-correlation
83 | correlation_results(idx) = abs(dot(stf_1, stf_2));
84 |
85 | % Energy computation
86 | energy_results(idx) = dot(stf_1, stf_1);
87 | end
88 |
89 | %%% Identify the start of the STF by thresholding the self-correlation
90 | % results
91 | THRESHOLD = 0.9999;
92 | potential_stf_starts = find(correlation_results > ...
93 | (THRESHOLD * abs(energy_results)));
94 |
95 | %%% Visualization
96 | plotUtility('dual', 1:len, correlation_results, energy_results, ...
97 | 'Self-Correlation and Energy of STF Segments', 'Indices', ...
98 | 'Amplitude', 'Self-Correlation', 'Energy', ...
99 | 'Correlation_Energy_Results.png');
100 |
101 | % Display the potential STF start indices
102 | fprintf("Indices of samples where packets are detected:\n");
103 | % Adjust for the sliding window offset
104 | disp(potential_stf_starts +2*STF_REPEAT_LENGTH-1);
105 |
106 |
107 | %%% =================== ========================== =================== %%%
108 | %%% =================== (4) Packet synchronization =================== %%%
109 | %%% =================== ========================== =================== %%%
110 | % Calculate the cross-correlation between the data and STF preamble
111 | cross_corr_results = xcorr(packet_data, stf_single);
112 | cross_corr_results = cross_corr_results(length(packet_data) - ...
113 | length(stf_single) + 16:end);
114 |
115 | %disp(size(cross_correlation))
116 | len_cc = length(cross_corr_results);
117 |
118 | % plot cross correlation
119 | plotUtility('single', 1:len_cc, abs(cross_corr_results), [], ...
120 | 'Cross-correlation for Synchronization', 'Indices', 'Amplitude', ...
121 | '', '', 'Cross_Correlation_Results.png');
122 |
123 | % Detecting the start of STF
124 | stf_detection_threshold = 0.9 * max(abs(cross_corr_results));
125 | detected_stf_starts = find(abs(cross_corr_results) > ...
126 | stf_detection_threshold);
127 | fprintf("Indices of samples corresponding to the STF starting time:\n");
128 | disp(detected_stf_starts);
129 |
130 | %%% =========== ========================================== =========== %%%
131 | %%% =========== (5) Channel estimation and packet decoding =========== %%%
132 | %%% =========== ========================================== =========== %%%
133 |
134 | %%% Step (a) leverage the LTF to estimate the frequency offset
135 | % exact starting time of the LTF
136 | ltf_initial_idx = detected_stf_starts(end) + STF_REPEAT_LENGTH;
137 | ltf_segment_1 = packet_data(ltf_initial_idx : ltf_initial_idx + 63);
138 | ltf_segment_2 = packet_data(ltf_initial_idx + 64 : ltf_initial_idx + 127);
139 |
140 | estimated_freq_offset = sum(imag(rdivide(ltf_segment_1, ...
141 | ltf_segment_2))) / (2*pi*64*64);
142 |
143 | fprintf("The frequency offset between the transmitter and receiver:" + ...
144 | " %f\n", estimated_freq_offset);
145 | packet_data = packet_data .* exp(2i * pi * estimated_freq_offset * ...
146 | (1:length(packet_data)));
147 |
148 | %%% Step (b) estimate the channel magnitude/phase distortion to each sample
149 | ltf_segment_1 = packet_data(ltf_initial_idx + 32 : ltf_initial_idx + 95);
150 | ltf_segment_2 = packet_data(ltf_initial_idx + 96 : ltf_initial_idx + 159);
151 |
152 | ltf_fft_1 = fft(ltf_segment_1);
153 | ltf_fft_2 = fft(ltf_segment_2);
154 | estimated_channel_distortion = times((ltf_fft_1 + ...
155 | ltf_fft_2) / 2, ltf_freq);
156 | fprintf("The channel distortion to each subcarrier \n");
157 | disp(estimated_channel_distortion);
158 |
159 | %%% Step (c) decode the digital information in each OFDM data symbol
160 | % (i) run FFT over each OFDM symbol and convert it into a number of
161 | % (64) subcarriers
162 | packet_data = packet_data(ltf_initial_idx + 160:end);
163 | prefixed_data = reshape(packet_data, [], 80);
164 | sample_data = prefixed_data(:, 17:end);
165 | ofdm_transformed = fft(sample_data, 64, 2);
166 |
167 | % (ii) recover the original BPSK symbols by reverting the channel
168 | % distortion
169 | distortion_zero_indices = find(estimated_channel_distortion == 0);
170 | distortion_nonzero_indices = find(estimated_channel_distortion ~= 0);
171 | ofdm_transformed(:, distortion_nonzero_indices) = ofdm_transformed(:, ...
172 | distortion_nonzero_indices) ./ repmat(estimated_channel_distortion ...
173 | (distortion_nonzero_indices), [size(ofdm_transformed, 1), 1]);
174 | ofdm_transformed(:, distortion_zero_indices) = 0;
175 |
176 | % (iii) demap the BPSK symbols to {0,1} information bits and convert the
177 | % bits into characters.
178 | bpsk_demap_data = reshape(ofdm_transformed(:, data_positions), [], 1);
179 | decoded_bits = pskdemod(bpsk_demap_data, 2);
180 |
181 | fprintf("Transmitted Strings:\n");
182 | printBitsAsString(decoded_bits);
183 |
184 |
185 |
--------------------------------------------------------------------------------
/plotUtility.m:
--------------------------------------------------------------------------------
1 | function plotUtility(type, xdata, ydata1, ydata2, titleStr, xLabelStr, yLabelStr, legendStr1, legendStr2, filename)
2 | % plotUtility - A function to standardize the plotting process.
3 | figure;
4 |
5 | if strcmp(type, 'single')
6 | plot(xdata, ydata1,'Color', '#0072BD');
7 | elseif strcmp(type, 'dual')
8 | plot(xdata, ydata1, 'Color', '#0072BD', 'LineStyle', '-');
9 | hold on;
10 | plot(xdata, ydata2, 'Color', '#EDB120', 'LineStyle', '-');
11 | hold off;
12 | legend(legendStr1, legendStr2);
13 | end
14 |
15 | title(titleStr);
16 | xlabel(xLabelStr);
17 | ylabel(yLabelStr);
18 | grid on;
19 | saveas(gcf, filename);
20 | end
--------------------------------------------------------------------------------
/printBitsAsString.m:
--------------------------------------------------------------------------------
1 | function printBitsAsString(bits)
2 | % Convert bits to string
3 | str = char(bin2dec(num2str(reshape(bits,[],8)))).';
4 |
5 | % Print the string in chunks of 80 characters
6 | chunkSize = 80;
7 | numChunks = ceil(length(str) / chunkSize);
8 | for i = 1:numChunks
9 | startIdx = (i - 1) * chunkSize + 1;
10 | endIdx = min(i * chunkSize, length(str));
11 | fprintf('%s\n', str(startIdx:endIdx));
12 | end
13 | end
14 |
--------------------------------------------------------------------------------