├── 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 | image 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 | image 22 | 23 | **The power spectrum density of the OFDM data symbols** 24 | 25 | image 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 | image 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 | image 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 | image 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 | image 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 | image 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 | --------------------------------------------------------------------------------