[2842] | 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 2 | % wl_example_siso_ofdm_txrx.m |
---|
[4808] | 3 | % A detailed write-up of this example is available on the wiki: |
---|
| 4 | % http://warpproject.org/trac/wiki/WARPLab/Examples/OFDM |
---|
[4838] | 5 | % |
---|
| 6 | % Copyright (c) 2015 Mango Communications - All Rights Reserved |
---|
| 7 | % Distributed under the WARP License (http://warpproject.org/license) |
---|
[2842] | 8 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
[2834] | 9 | clear |
---|
[2848] | 10 | |
---|
[4337] | 11 | % Params: |
---|
| 12 | USE_WARPLAB_TXRX = 1; % Enable WARPLab-in-the-loop (otherwise sim-only) |
---|
[4782] | 13 | WRITE_PNG_FILES = 0; % Enable writing plots to PNG |
---|
| 14 | CHANNEL = 11; % Channel to tune Tx and Rx radios |
---|
[2863] | 15 | |
---|
[4337] | 16 | % Waveform params |
---|
[4839] | 17 | N_OFDM_SYMS = 500; % Number of OFDM symbols |
---|
| 18 | MOD_ORDER = 16; % Modulation order (2/4/16/64 = BSPK/QPSK/16-QAM/64-QAM) |
---|
| 19 | TX_SCALE = 1.0; % Scale for Tx waveform ([0:1]) |
---|
[2863] | 20 | |
---|
[4337] | 21 | % OFDM params |
---|
| 22 | SC_IND_PILOTS = [8 22 44 58]; % Pilot subcarrier indices |
---|
| 23 | SC_IND_DATA = [2:7 9:21 23:27 39:43 45:57 59:64]; % Data subcarrier indices |
---|
| 24 | N_SC = 64; % Number of subcarriers |
---|
| 25 | CP_LEN = 16; % Cyclic prefix length |
---|
| 26 | N_DATA_SYMS = N_OFDM_SYMS * length(SC_IND_DATA); % Number of data symbols (one per data-bearing subcarrier per OFDM symbol) |
---|
[4839] | 27 | INTERP_RATE = 2; % Interpolation rate (must be 2) |
---|
[2863] | 28 | |
---|
[4337] | 29 | % Rx processing params |
---|
[4782] | 30 | FFT_OFFSET = 4; % Number of CP samples to use in FFT (on average) |
---|
| 31 | LTS_CORR_THRESH = 0.8; % Normalized threshold for LTS correlation |
---|
| 32 | DO_APPLY_CFO_CORRECTION = 1; % Enable CFO estimation/correction |
---|
| 33 | DO_APPLY_PHASE_ERR_CORRECTION = 1; % Enable Residual CFO estimation/correction |
---|
| 34 | DO_APPLY_SFO_CORRECTION = 1; % Enable SFO estimation/correction |
---|
[2834] | 35 | |
---|
[4782] | 36 | DECIMATE_RATE = INTERP_RATE; |
---|
| 37 | |
---|
[4337] | 38 | % WARPLab experiment params |
---|
| 39 | USE_AGC = true; % Use the AGC if running on WARP hardware |
---|
[4839] | 40 | MAX_TX_LEN = 2^20; % Maximum number of samples to use for this experiment |
---|
| 41 | TRIGGER_OFFSET_TOL_NS = 3000; % Trigger time offset toleration between Tx and Rx that can be accomodated |
---|
[4337] | 42 | |
---|
[5621] | 43 | |
---|
| 44 | |
---|
[4838] | 45 | if(USE_WARPLAB_TXRX) |
---|
| 46 | |
---|
[2846] | 47 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 48 | % Set up the WARPLab experiment |
---|
| 49 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 50 | |
---|
| 51 | NUMNODES = 2; |
---|
| 52 | |
---|
[4337] | 53 | % Create a vector of node objects |
---|
[4985] | 54 | nodes = wl_initNodes(NUMNODES); |
---|
| 55 | node_tx = nodes(1); |
---|
| 56 | node_rx = nodes(2); |
---|
[2846] | 57 | |
---|
[4337] | 58 | % Create a UDP broadcast trigger and tell each node to be ready for it |
---|
[2846] | 59 | eth_trig = wl_trigger_eth_udp_broadcast; |
---|
[4337] | 60 | wl_triggerManagerCmd(nodes, 'add_ethernet_trigger', [eth_trig]); |
---|
[4838] | 61 | |
---|
[4385] | 62 | % Read Trigger IDs into workspace |
---|
[4689] | 63 | trig_in_ids = wl_getTriggerInputIDs(nodes(1)); |
---|
| 64 | trig_out_ids = wl_getTriggerOutputIDs(nodes(1)); |
---|
[2846] | 65 | |
---|
[4385] | 66 | % For both nodes, we will allow Ethernet to trigger the buffer baseband and the AGC |
---|
[4689] | 67 | wl_triggerManagerCmd(nodes, 'output_config_input_selection', [trig_out_ids.BASEBAND, trig_out_ids.AGC], [trig_in_ids.ETH_A]); |
---|
[4385] | 68 | |
---|
[4838] | 69 | % Set the trigger output delays. |
---|
[4689] | 70 | nodes.wl_triggerManagerCmd('output_config_delay', [trig_out_ids.BASEBAND], 0); |
---|
[4839] | 71 | nodes.wl_triggerManagerCmd('output_config_delay', [trig_out_ids.AGC], TRIGGER_OFFSET_TOL_NS); |
---|
[4385] | 72 | |
---|
[4985] | 73 | % Get IDs for the interfaces on the boards. |
---|
| 74 | ifc_ids_TX = wl_getInterfaceIDs(node_tx); |
---|
| 75 | ifc_ids_RX = wl_getInterfaceIDs(node_rx); |
---|
[2846] | 76 | |
---|
[4985] | 77 | % Set up the TX / RX nodes and RF interfaces |
---|
| 78 | TX_RF = ifc_ids_TX.RF_A; |
---|
| 79 | TX_RF_VEC = ifc_ids_TX.RF_A; |
---|
| 80 | TX_RF_ALL = ifc_ids_TX.RF_ALL; |
---|
| 81 | |
---|
| 82 | RX_RF = ifc_ids_RX.RF_A; |
---|
| 83 | RX_RF_VEC = ifc_ids_RX.RF_A; |
---|
| 84 | RX_RF_ALL = ifc_ids_RX.RF_ALL; |
---|
| 85 | |
---|
[4337] | 86 | % Set up the interface for the experiment |
---|
[4985] | 87 | wl_interfaceCmd(node_tx, TX_RF_ALL, 'channel', 2.4, CHANNEL); |
---|
| 88 | wl_interfaceCmd(node_rx, RX_RF_ALL, 'channel', 2.4, CHANNEL); |
---|
[2846] | 89 | |
---|
[4985] | 90 | wl_interfaceCmd(node_tx, TX_RF_ALL, 'tx_gains', 3, 30); |
---|
| 91 | |
---|
[2846] | 92 | if(USE_AGC) |
---|
[4985] | 93 | wl_interfaceCmd(node_rx, RX_RF_ALL, 'rx_gain_mode', 'automatic'); |
---|
[4385] | 94 | wl_basebandCmd(nodes, 'agc_target', -13); |
---|
[2846] | 95 | else |
---|
[4985] | 96 | wl_interfaceCmd(node_rx, RX_RF_ALL, 'rx_gain_mode', 'manual'); |
---|
[4337] | 97 | RxGainRF = 2; % Rx RF Gain in [1:3] |
---|
| 98 | RxGainBB = 12; % Rx Baseband Gain in [0:31] |
---|
[4985] | 99 | wl_interfaceCmd(node_rx, RX_RF_ALL, 'rx_gains', RxGainRF, RxGainBB); |
---|
[2846] | 100 | end |
---|
| 101 | |
---|
[4337] | 102 | % Get parameters from the node |
---|
[4838] | 103 | SAMP_FREQ = wl_basebandCmd(nodes(1), 'tx_buff_clk_freq'); |
---|
[4485] | 104 | Ts = 1/SAMP_FREQ; |
---|
[4838] | 105 | |
---|
[4339] | 106 | % We will read the transmitter's maximum I/Q buffer length |
---|
| 107 | % and assign that value to a temporary variable. |
---|
| 108 | % |
---|
[4985] | 109 | % NOTE: We assume that the buffers sizes are the same for all interfaces |
---|
[4339] | 110 | |
---|
[4985] | 111 | maximum_buffer_len = min(MAX_TX_LEN, wl_basebandCmd(node_tx, TX_RF_VEC, 'tx_buff_max_num_samples')); |
---|
[2869] | 112 | example_mode_string = 'hw'; |
---|
[4838] | 113 | else |
---|
[4337] | 114 | % Use sane defaults for hardware-dependent params in sim-only version |
---|
[4777] | 115 | maximum_buffer_len = min(MAX_TX_LEN, 2^20); |
---|
[4337] | 116 | SAMP_FREQ = 40e6; |
---|
[2869] | 117 | example_mode_string = 'sim'; |
---|
[2846] | 118 | end |
---|
| 119 | |
---|
[4337] | 120 | %% Define a half-band 2x interpolation filter response |
---|
[2834] | 121 | interp_filt2 = zeros(1,43); |
---|
| 122 | interp_filt2([1 3 5 7 9 11 13 15 17 19 21]) = [12 -32 72 -140 252 -422 682 -1086 1778 -3284 10364]; |
---|
| 123 | interp_filt2([23 25 27 29 31 33 35 37 39 41 43]) = interp_filt2(fliplr([1 3 5 7 9 11 13 15 17 19 21])); |
---|
| 124 | interp_filt2(22) = 16384; |
---|
| 125 | interp_filt2 = interp_filt2./max(abs(interp_filt2)); |
---|
| 126 | |
---|
[4337] | 127 | % Define the preamble |
---|
[4808] | 128 | % Note: The STS symbols in the preamble meet the requirements needed by the |
---|
| 129 | % AGC core at the receiver. Details on the operation of the AGC are |
---|
| 130 | % available on the wiki: http://warpproject.org/trac/wiki/WARPLab/AGC |
---|
[2834] | 131 | sts_f = zeros(1,64); |
---|
| 132 | sts_f(1:27) = [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]; |
---|
| 133 | sts_f(39:64) = [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]; |
---|
| 134 | sts_t = ifft(sqrt(13/6).*sts_f, 64); |
---|
| 135 | sts_t = sts_t(1:16); |
---|
| 136 | |
---|
[4337] | 137 | % LTS for CFO and channel estimation |
---|
[2834] | 138 | lts_f = [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 0 0 0 0 0 0 0 0 0 0 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]; |
---|
| 139 | lts_t = ifft(lts_f, 64); |
---|
| 140 | |
---|
[4337] | 141 | % Use 30 copies of the 16-sample STS for extra AGC settling margin |
---|
[2842] | 142 | preamble = [repmat(sts_t, 1, 30) lts_t(33:64) lts_t lts_t]; |
---|
[2834] | 143 | |
---|
[4838] | 144 | % Sanity check variables that affect the number of Tx samples |
---|
[4839] | 145 | num_samps_needed = ceil((TRIGGER_OFFSET_TOL_NS*1e-9) / (1/SAMP_FREQ)) + ... |
---|
| 146 | INTERP_RATE*((N_OFDM_SYMS * (N_SC + CP_LEN)) + length(preamble) + ceil(length(interp_filt2)/2)); |
---|
| 147 | |
---|
| 148 | |
---|
| 149 | if(num_samps_needed > maximum_buffer_len) |
---|
[2842] | 150 | fprintf('Too many OFDM symbols for TX_NUM_SAMPS!\n'); |
---|
[4839] | 151 | fprintf('Raise MAX_TX_LEN to %d, or \n', num_samps_needed); |
---|
| 152 | fprintf('Reduce N_OFDM_SYMS to %d\n', floor(((maximum_buffer_len - ceil((TRIGGER_OFFSET_TOL_NS*1e-9) / (1/SAMP_FREQ)))/INTERP_RATE - (length(preamble) + ceil(length(interp_filt2)/2)))/(N_SC + CP_LEN))); |
---|
[2842] | 153 | return; |
---|
| 154 | end |
---|
| 155 | |
---|
[4838] | 156 | %% Generate a payload of random integers |
---|
[2849] | 157 | tx_data = randi(MOD_ORDER, 1, N_DATA_SYMS) - 1; |
---|
[2834] | 158 | |
---|
[4838] | 159 | % Functions for data -> complex symbol mapping (like qammod, avoids comm toolbox requirement) |
---|
| 160 | % These anonymous functions implement the modulation mapping from IEEE 802.11-2012 Section 18.3.5.8 |
---|
[4337] | 161 | modvec_bpsk = (1/sqrt(2)) .* [-1 1]; |
---|
[4777] | 162 | modvec_16qam = (1/sqrt(10)) .* [-3 -1 +3 +1]; |
---|
| 163 | modvec_64qam = (1/sqrt(43)) .* [-7 -5 -1 -3 +7 +5 +1 +3]; |
---|
[2834] | 164 | |
---|
[4337] | 165 | mod_fcn_bpsk = @(x) complex(modvec_bpsk(1+x),0); |
---|
| 166 | mod_fcn_qpsk = @(x) complex(modvec_bpsk(1+bitshift(x, -1)), modvec_bpsk(1+mod(x, 2))); |
---|
[2834] | 167 | mod_fcn_16qam = @(x) complex(modvec_16qam(1+bitshift(x, -2)), modvec_16qam(1+mod(x,4))); |
---|
[4777] | 168 | mod_fcn_64qam = @(x) complex(modvec_64qam(1+bitshift(x, -3)), modvec_64qam(1+mod(x,8))); |
---|
[2834] | 169 | |
---|
[4337] | 170 | % Map the data values on to complex symbols |
---|
[2834] | 171 | switch MOD_ORDER |
---|
[4337] | 172 | case 2 % BPSK |
---|
[2834] | 173 | tx_syms = arrayfun(mod_fcn_bpsk, tx_data); |
---|
[4337] | 174 | case 4 % QPSK |
---|
[2834] | 175 | tx_syms = arrayfun(mod_fcn_qpsk, tx_data); |
---|
[4337] | 176 | case 16 % 16-QAM |
---|
[4838] | 177 | tx_syms = arrayfun(mod_fcn_16qam, tx_data); |
---|
[4777] | 178 | case 64 % 64-QAM |
---|
[4838] | 179 | tx_syms = arrayfun(mod_fcn_64qam, tx_data); |
---|
[2834] | 180 | otherwise |
---|
[4838] | 181 | fprintf('Invalid MOD_ORDER (%d)! Must be in [2, 4, 16, 64]\n', MOD_ORDER); |
---|
[2834] | 182 | return; |
---|
| 183 | end |
---|
| 184 | |
---|
[4337] | 185 | % Reshape the symbol vector to a matrix with one column per OFDM symbol |
---|
[2834] | 186 | tx_syms_mat = reshape(tx_syms, length(SC_IND_DATA), N_OFDM_SYMS); |
---|
| 187 | |
---|
[4838] | 188 | % Define the pilot tone values as BPSK symbols |
---|
[4782] | 189 | pilots = [1 1 -1 1].'; |
---|
[2834] | 190 | |
---|
[4337] | 191 | % Repeat the pilots across all OFDM symbols |
---|
[2834] | 192 | pilots_mat = repmat(pilots, 1, N_OFDM_SYMS); |
---|
| 193 | |
---|
[2849] | 194 | %% IFFT |
---|
| 195 | |
---|
[4337] | 196 | % Construct the IFFT input matrix |
---|
[2834] | 197 | ifft_in_mat = zeros(N_SC, N_OFDM_SYMS); |
---|
| 198 | |
---|
[4337] | 199 | % Insert the data and pilot values; other subcarriers will remain at 0 |
---|
| 200 | ifft_in_mat(SC_IND_DATA, :) = tx_syms_mat; |
---|
[2834] | 201 | ifft_in_mat(SC_IND_PILOTS, :) = pilots_mat; |
---|
| 202 | |
---|
[2849] | 203 | %Perform the IFFT |
---|
[2834] | 204 | tx_payload_mat = ifft(ifft_in_mat, N_SC, 1); |
---|
| 205 | |
---|
[4337] | 206 | % Insert the cyclic prefix |
---|
[2834] | 207 | if(CP_LEN > 0) |
---|
| 208 | tx_cp = tx_payload_mat((end-CP_LEN+1 : end), :); |
---|
| 209 | tx_payload_mat = [tx_cp; tx_payload_mat]; |
---|
| 210 | end |
---|
| 211 | |
---|
[4337] | 212 | % Reshape to a vector |
---|
[2834] | 213 | tx_payload_vec = reshape(tx_payload_mat, 1, numel(tx_payload_mat)); |
---|
| 214 | |
---|
[4337] | 215 | % Construct the full time-domain OFDM waveform |
---|
[2834] | 216 | tx_vec = [preamble tx_payload_vec]; |
---|
| 217 | |
---|
[4839] | 218 | % Pad with zeros for transmission to deal with delay through the |
---|
| 219 | % interpolation filter |
---|
| 220 | tx_vec_padded = [tx_vec, zeros(1, ceil(length(interp_filt2)/2))]; |
---|
[2834] | 221 | |
---|
| 222 | %% Interpolate |
---|
[4839] | 223 | % Zero pad then filter (same as interp or upfirdn without signal processing toolbox) |
---|
| 224 | if( INTERP_RATE ~= 2) |
---|
| 225 | fprintf('Error: INTERP_RATE must equal 2\n'); |
---|
| 226 | return; |
---|
[2834] | 227 | end |
---|
| 228 | |
---|
[4839] | 229 | tx_vec_2x = zeros(1, 2*numel(tx_vec_padded)); |
---|
| 230 | tx_vec_2x(1:2:end) = tx_vec_padded; |
---|
| 231 | tx_vec_air = filter(interp_filt2, 1, tx_vec_2x); |
---|
| 232 | |
---|
| 233 | |
---|
[4838] | 234 | % Scale the Tx vector to +/- 1 |
---|
[2834] | 235 | tx_vec_air = TX_SCALE .* tx_vec_air ./ max(abs(tx_vec_air)); |
---|
| 236 | |
---|
[4777] | 237 | TX_NUM_SAMPS = length(tx_vec_air); |
---|
| 238 | |
---|
| 239 | if(USE_WARPLAB_TXRX) |
---|
| 240 | wl_basebandCmd(nodes, 'tx_delay', 0); |
---|
[4839] | 241 | wl_basebandCmd(nodes, 'tx_length', TX_NUM_SAMPS); % Number of samples to send |
---|
| 242 | wl_basebandCmd(nodes, 'rx_length', TX_NUM_SAMPS + ceil((TRIGGER_OFFSET_TOL_NS*1e-9) / (1/SAMP_FREQ))); % Number of samples to receive |
---|
[4777] | 243 | end |
---|
| 244 | |
---|
| 245 | |
---|
[2834] | 246 | %% WARPLab Tx/Rx |
---|
[4838] | 247 | if(USE_WARPLAB_TXRX) |
---|
[4337] | 248 | % Write the Tx waveform to the Tx node |
---|
[4985] | 249 | wl_basebandCmd(node_tx, TX_RF_VEC, 'write_IQ', tx_vec_air(:)); |
---|
[2849] | 250 | |
---|
[4337] | 251 | % Enable the Tx and Rx radios |
---|
[4985] | 252 | wl_interfaceCmd(node_tx, TX_RF, 'tx_en'); |
---|
| 253 | wl_interfaceCmd(node_rx, RX_RF, 'rx_en'); |
---|
[2842] | 254 | |
---|
[4337] | 255 | % Enable the Tx and Rx buffers |
---|
[4985] | 256 | wl_basebandCmd(node_tx, TX_RF, 'tx_buff_en'); |
---|
| 257 | wl_basebandCmd(node_rx, RX_RF, 'rx_buff_en'); |
---|
[2842] | 258 | |
---|
[4337] | 259 | % Trigger the Tx/Rx cycle at both nodes |
---|
[2842] | 260 | eth_trig.send(); |
---|
| 261 | |
---|
[4337] | 262 | % Retrieve the received waveform from the Rx node |
---|
[4985] | 263 | rx_vec_air = wl_basebandCmd(node_rx, RX_RF_VEC, 'read_IQ', 0, TX_NUM_SAMPS + (ceil((TRIGGER_OFFSET_TOL_NS*1e-9) / (1/SAMP_FREQ)))); |
---|
[4838] | 264 | |
---|
[2842] | 265 | rx_vec_air = rx_vec_air(:).'; |
---|
[4838] | 266 | |
---|
[4337] | 267 | % Disable the Tx/Rx radios and buffers |
---|
[4985] | 268 | wl_basebandCmd(node_tx, TX_RF_ALL, 'tx_rx_buff_dis'); |
---|
| 269 | wl_basebandCmd(node_rx, RX_RF_ALL, 'tx_rx_buff_dis'); |
---|
| 270 | |
---|
| 271 | wl_interfaceCmd(node_tx, TX_RF_ALL, 'tx_rx_dis'); |
---|
| 272 | wl_interfaceCmd(node_rx, RX_RF_ALL, 'tx_rx_dis'); |
---|
[2834] | 273 | else |
---|
[4337] | 274 | % Sim-only mode: Apply wireless degradations here for sim (noise, fading, etc) |
---|
[2834] | 275 | |
---|
[4337] | 276 | % Perfect (ie. Rx=Tx): |
---|
| 277 | % rx_vec_air = tx_vec_air; |
---|
[2834] | 278 | |
---|
[4337] | 279 | % AWGN: |
---|
[4839] | 280 | rx_vec_air = [tx_vec_air, zeros(1,ceil((TRIGGER_OFFSET_TOL_NS*1e-9) / (1/SAMP_FREQ)))]; |
---|
[4808] | 281 | rx_vec_air = rx_vec_air + 0*complex(randn(1,length(rx_vec_air)), randn(1,length(rx_vec_air))); |
---|
[4838] | 282 | |
---|
[4337] | 283 | % CFO: |
---|
| 284 | % rx_vec_air = tx_vec_air .* exp(-1i*2*pi*1e-4*[0:length(tx_vec_air)-1]); |
---|
[2834] | 285 | end |
---|
| 286 | |
---|
| 287 | %% Decimate |
---|
[4839] | 288 | if( INTERP_RATE ~= 2) |
---|
| 289 | fprintf('Error: INTERP_RATE must equal 2\n'); |
---|
| 290 | return; |
---|
[2834] | 291 | end |
---|
| 292 | |
---|
[4839] | 293 | raw_rx_dec = filter(interp_filt2, 1, rx_vec_air); |
---|
| 294 | raw_rx_dec = raw_rx_dec(1:2:end); |
---|
| 295 | |
---|
| 296 | |
---|
[2834] | 297 | %% Correlate for LTS |
---|
[2849] | 298 | |
---|
[4838] | 299 | % Complex cross correlation of Rx waveform with time-domain LTS |
---|
[2842] | 300 | lts_corr = abs(conv(conj(fliplr(lts_t)), sign(raw_rx_dec))); |
---|
[2849] | 301 | |
---|
[4838] | 302 | % Skip early and late samples - avoids occasional false positives from pre-AGC samples |
---|
[2834] | 303 | lts_corr = lts_corr(32:end-32); |
---|
[2849] | 304 | |
---|
[4337] | 305 | % Find all correlation peaks |
---|
[4808] | 306 | lts_peaks = find(lts_corr(1:800) > LTS_CORR_THRESH*max(lts_corr)); |
---|
[2834] | 307 | |
---|
[4337] | 308 | % Select best candidate correlation peak as LTS-payload boundary |
---|
[2842] | 309 | [LTS1, LTS2] = meshgrid(lts_peaks,lts_peaks); |
---|
| 310 | [lts_second_peak_index,y] = find(LTS2-LTS1 == length(lts_t)); |
---|
| 311 | |
---|
[4337] | 312 | % Stop if no valid correlation peak was found |
---|
[2842] | 313 | if(isempty(lts_second_peak_index)) |
---|
[2834] | 314 | fprintf('No LTS Correlation Peaks Found!\n'); |
---|
| 315 | return; |
---|
| 316 | end |
---|
| 317 | |
---|
[4337] | 318 | % Set the sample indices of the payload symbols and preamble |
---|
[4838] | 319 | % The "+32" corresponds to the 32-sample cyclic prefix on the preamble LTS |
---|
| 320 | % The "-160" corresponds to the length of the preamble LTS (2.5 copies of 64-sample LTS) |
---|
| 321 | payload_ind = lts_peaks(max(lts_second_peak_index)) + 32; |
---|
[2834] | 322 | lts_ind = payload_ind-160; |
---|
| 323 | |
---|
| 324 | if(DO_APPLY_CFO_CORRECTION) |
---|
| 325 | %Extract LTS (not yet CFO corrected) |
---|
| 326 | rx_lts = raw_rx_dec(lts_ind : lts_ind+159); |
---|
| 327 | rx_lts1 = rx_lts(-64+-FFT_OFFSET + [97:160]); |
---|
| 328 | rx_lts2 = rx_lts(-FFT_OFFSET + [97:160]); |
---|
| 329 | |
---|
| 330 | %Calculate coarse CFO est |
---|
[4782] | 331 | rx_cfo_est_lts = mean(unwrap(angle(rx_lts2 .* conj(rx_lts1)))); |
---|
[2834] | 332 | rx_cfo_est_lts = rx_cfo_est_lts/(2*pi*64); |
---|
| 333 | else |
---|
[2844] | 334 | rx_cfo_est_lts = 0; |
---|
[2834] | 335 | end |
---|
| 336 | |
---|
[4337] | 337 | % Apply CFO correction to raw Rx waveform |
---|
[4782] | 338 | rx_cfo_corr_t = exp(-1i*2*pi*rx_cfo_est_lts*[0:length(raw_rx_dec)-1]); |
---|
[2844] | 339 | rx_dec_cfo_corr = raw_rx_dec .* rx_cfo_corr_t; |
---|
| 340 | |
---|
[4337] | 341 | % Re-extract LTS for channel estimate |
---|
[2834] | 342 | rx_lts = rx_dec_cfo_corr(lts_ind : lts_ind+159); |
---|
| 343 | rx_lts1 = rx_lts(-64+-FFT_OFFSET + [97:160]); |
---|
| 344 | rx_lts2 = rx_lts(-FFT_OFFSET + [97:160]); |
---|
| 345 | |
---|
| 346 | rx_lts1_f = fft(rx_lts1); |
---|
| 347 | rx_lts2_f = fft(rx_lts2); |
---|
| 348 | |
---|
[4838] | 349 | % Calculate channel estimate from average of 2 training symbols |
---|
[2834] | 350 | rx_H_est = lts_f .* (rx_lts1_f + rx_lts2_f)/2; |
---|
| 351 | |
---|
[4337] | 352 | %% Rx payload processing |
---|
[2849] | 353 | |
---|
[4337] | 354 | % Extract the payload samples (integral number of OFDM symbols following preamble) |
---|
[2834] | 355 | payload_vec = rx_dec_cfo_corr(payload_ind : payload_ind+N_OFDM_SYMS*(N_SC+CP_LEN)-1); |
---|
[2849] | 356 | payload_mat = reshape(payload_vec, (N_SC+CP_LEN), N_OFDM_SYMS); |
---|
[2834] | 357 | |
---|
[4337] | 358 | % Remove the cyclic prefix, keeping FFT_OFFSET samples of CP (on average) |
---|
[2834] | 359 | payload_mat_noCP = payload_mat(CP_LEN-FFT_OFFSET+[1:N_SC], :); |
---|
[2849] | 360 | |
---|
[4337] | 361 | % Take the FFT |
---|
[2834] | 362 | syms_f_mat = fft(payload_mat_noCP, N_SC, 1); |
---|
[2849] | 363 | |
---|
[4838] | 364 | % Equalize (zero-forcing, just divide by complex chan estimates) |
---|
[2834] | 365 | syms_eq_mat = syms_f_mat ./ repmat(rx_H_est.', 1, N_OFDM_SYMS); |
---|
| 366 | |
---|
[4757] | 367 | if DO_APPLY_SFO_CORRECTION |
---|
[4782] | 368 | % SFO manifests as a frequency-dependent phase whose slope increases |
---|
| 369 | % over time as the Tx and Rx sample streams drift apart from one |
---|
| 370 | % another. To correct for this effect, we calculate this phase slope at |
---|
| 371 | % each OFDM symbol using the pilot tones and use this slope to |
---|
| 372 | % interpolate a phase correction for each data-bearing subcarrier. |
---|
[4838] | 373 | |
---|
| 374 | % Extract the pilot tones and "equalize" them by their nominal Tx values |
---|
[4757] | 375 | pilots_f_mat = syms_eq_mat(SC_IND_PILOTS, :); |
---|
| 376 | pilots_f_mat_comp = pilots_f_mat.*pilots_mat; |
---|
[4838] | 377 | |
---|
| 378 | % Calculate the phases of every Rx pilot tone |
---|
| 379 | pilot_phases = unwrap(angle(fftshift(pilots_f_mat_comp,1)), [], 1); |
---|
| 380 | |
---|
| 381 | % Calculate slope of pilot tone phases vs frequency in each OFDM symbol |
---|
[4839] | 382 | pilot_spacing_mat = repmat(mod(diff(fftshift(SC_IND_PILOTS)),64).', 1, N_OFDM_SYMS); |
---|
[4838] | 383 | pilot_slope_mat = mean(diff(pilot_phases) ./ pilot_spacing_mat); |
---|
| 384 | |
---|
| 385 | % Calculate the SFO correction phases for each OFDM symbol |
---|
| 386 | pilot_phase_sfo_corr = fftshift((-32:31).' * pilot_slope_mat, 1); |
---|
[4757] | 387 | pilot_phase_corr = exp(-1i*(pilot_phase_sfo_corr)); |
---|
| 388 | |
---|
| 389 | % Apply the pilot phase correction per symbol |
---|
[4838] | 390 | syms_eq_mat = syms_eq_mat .* pilot_phase_corr; |
---|
[4777] | 391 | else |
---|
[4838] | 392 | % Define an empty SFO correction matrix (used by plotting code below) |
---|
[4777] | 393 | pilot_phase_sfo_corr = zeros(N_SC, N_OFDM_SYMS); |
---|
[4757] | 394 | end |
---|
| 395 | |
---|
| 396 | |
---|
[4782] | 397 | if DO_APPLY_PHASE_ERR_CORRECTION |
---|
| 398 | % Extract the pilots and calculate per-symbol phase error |
---|
| 399 | pilots_f_mat = syms_eq_mat(SC_IND_PILOTS, :); |
---|
| 400 | pilots_f_mat_comp = pilots_f_mat.*pilots_mat; |
---|
| 401 | pilot_phase_err = angle(mean(pilots_f_mat_comp)); |
---|
| 402 | else |
---|
[4838] | 403 | % Define an empty phase correction vector (used by plotting code below) |
---|
| 404 | pilot_phase_err = zeros(1, N_OFDM_SYMS); |
---|
[4782] | 405 | end |
---|
| 406 | pilot_phase_err_corr = repmat(pilot_phase_err, N_SC, 1); |
---|
| 407 | pilot_phase_corr = exp(-1i*(pilot_phase_err_corr)); |
---|
[2834] | 408 | |
---|
[4337] | 409 | % Apply the pilot phase correction per symbol |
---|
[2834] | 410 | syms_eq_pc_mat = syms_eq_mat .* pilot_phase_corr; |
---|
| 411 | payload_syms_mat = syms_eq_pc_mat(SC_IND_DATA, :); |
---|
| 412 | |
---|
[4337] | 413 | %% Demodulate |
---|
[2844] | 414 | rx_syms = reshape(payload_syms_mat, 1, N_DATA_SYMS); |
---|
| 415 | |
---|
| 416 | demod_fcn_bpsk = @(x) double(real(x)>0); |
---|
| 417 | demod_fcn_qpsk = @(x) double(2*(real(x)>0) + 1*(imag(x)>0)); |
---|
[2848] | 418 | demod_fcn_16qam = @(x) (8*(real(x)>0)) + (4*(abs(real(x))<0.6325)) + (2*(imag(x)>0)) + (1*(abs(imag(x))<0.6325)); |
---|
[4777] | 419 | demod_fcn_64qam = @(x) (32*(real(x)>0)) + (16*(abs(real(x))<0.6172)) + (8*((abs(real(x))<(0.9258))&&((abs(real(x))>(0.3086))))) + (4*(imag(x)>0)) + (2*(abs(imag(x))<0.6172)) + (1*((abs(imag(x))<(0.9258))&&((abs(imag(x))>(0.3086))))); |
---|
[2844] | 420 | |
---|
| 421 | switch(MOD_ORDER) |
---|
[4337] | 422 | case 2 % BPSK |
---|
[2844] | 423 | rx_data = arrayfun(demod_fcn_bpsk, rx_syms); |
---|
[4337] | 424 | case 4 % QPSK |
---|
[2844] | 425 | rx_data = arrayfun(demod_fcn_qpsk, rx_syms); |
---|
[4337] | 426 | case 16 % 16-QAM |
---|
[4838] | 427 | rx_data = arrayfun(demod_fcn_16qam, rx_syms); |
---|
[4777] | 428 | case 64 % 64-QAM |
---|
[4838] | 429 | rx_data = arrayfun(demod_fcn_64qam, rx_syms); |
---|
[2844] | 430 | end |
---|
| 431 | |
---|
| 432 | %% Plot Results |
---|
[2848] | 433 | cf = 0; |
---|
[4337] | 434 | |
---|
| 435 | % Tx signal |
---|
[2848] | 436 | cf = cf + 1; |
---|
| 437 | figure(cf); clf; |
---|
[2834] | 438 | |
---|
[2848] | 439 | subplot(2,1,1); |
---|
| 440 | plot(real(tx_vec_air), 'b'); |
---|
| 441 | axis([0 length(tx_vec_air) -TX_SCALE TX_SCALE]) |
---|
| 442 | grid on; |
---|
| 443 | title('Tx Waveform (I)'); |
---|
[2834] | 444 | |
---|
[2848] | 445 | subplot(2,1,2); |
---|
| 446 | plot(imag(tx_vec_air), 'r'); |
---|
| 447 | axis([0 length(tx_vec_air) -TX_SCALE TX_SCALE]) |
---|
| 448 | grid on; |
---|
| 449 | title('Tx Waveform (Q)'); |
---|
[2834] | 450 | |
---|
[2866] | 451 | if(WRITE_PNG_FILES) |
---|
[4337] | 452 | print(gcf,sprintf('wl_ofdm_plots_%s_txIQ', example_mode_string), '-dpng', '-r96', '-painters') |
---|
[2866] | 453 | end |
---|
| 454 | |
---|
[4337] | 455 | % Rx signal |
---|
[2848] | 456 | cf = cf + 1; |
---|
| 457 | figure(cf); clf; |
---|
| 458 | subplot(2,1,1); |
---|
| 459 | plot(real(rx_vec_air), 'b'); |
---|
| 460 | axis([0 length(rx_vec_air) -TX_SCALE TX_SCALE]) |
---|
| 461 | grid on; |
---|
| 462 | title('Rx Waveform (I)'); |
---|
[2834] | 463 | |
---|
[2848] | 464 | subplot(2,1,2); |
---|
| 465 | plot(imag(rx_vec_air), 'r'); |
---|
| 466 | axis([0 length(rx_vec_air) -TX_SCALE TX_SCALE]) |
---|
| 467 | grid on; |
---|
| 468 | title('Rx Waveform (Q)'); |
---|
[2834] | 469 | |
---|
[2866] | 470 | if(WRITE_PNG_FILES) |
---|
[4337] | 471 | print(gcf,sprintf('wl_ofdm_plots_%s_rxIQ', example_mode_string), '-dpng', '-r96', '-painters') |
---|
[2866] | 472 | end |
---|
| 473 | |
---|
[4337] | 474 | % Rx LTS correlation |
---|
[2848] | 475 | cf = cf + 1; |
---|
| 476 | figure(cf); clf; |
---|
[4777] | 477 | lts_to_plot = lts_corr; |
---|
[2848] | 478 | plot(lts_to_plot, '.-b', 'LineWidth', 1); |
---|
| 479 | hold on; |
---|
| 480 | grid on; |
---|
| 481 | line([1 length(lts_to_plot)], LTS_CORR_THRESH*max(lts_to_plot)*[1 1], 'LineStyle', '--', 'Color', 'r', 'LineWidth', 2); |
---|
| 482 | title('LTS Correlation and Threshold') |
---|
| 483 | xlabel('Sample Index') |
---|
[4777] | 484 | myAxis = axis(); |
---|
| 485 | axis([1, 1000, myAxis(3), myAxis(4)]) |
---|
[2834] | 486 | |
---|
[2866] | 487 | if(WRITE_PNG_FILES) |
---|
[4337] | 488 | print(gcf,sprintf('wl_ofdm_plots_%s_ltsCorr', example_mode_string), '-dpng', '-r96', '-painters') |
---|
[2866] | 489 | end |
---|
| 490 | |
---|
[4337] | 491 | % Channel Estimates |
---|
[2848] | 492 | cf = cf + 1; |
---|
[2834] | 493 | |
---|
[2848] | 494 | rx_H_est_plot = repmat(complex(NaN,NaN),1,length(rx_H_est)); |
---|
| 495 | rx_H_est_plot(SC_IND_DATA) = rx_H_est(SC_IND_DATA); |
---|
| 496 | rx_H_est_plot(SC_IND_PILOTS) = rx_H_est(SC_IND_PILOTS); |
---|
[2834] | 497 | |
---|
[2848] | 498 | x = (20/N_SC) * (-(N_SC/2):(N_SC/2 - 1)); |
---|
[2842] | 499 | |
---|
[2848] | 500 | figure(cf); clf; |
---|
| 501 | subplot(2,1,1); |
---|
| 502 | stairs(x - (20/(2*N_SC)), fftshift(real(rx_H_est_plot)), 'b', 'LineWidth', 2); |
---|
| 503 | hold on |
---|
| 504 | stairs(x - (20/(2*N_SC)), fftshift(imag(rx_H_est_plot)), 'r', 'LineWidth', 2); |
---|
| 505 | hold off |
---|
| 506 | axis([min(x) max(x) -1.1*max(abs(rx_H_est_plot)) 1.1*max(abs(rx_H_est_plot))]) |
---|
| 507 | grid on; |
---|
| 508 | title('Channel Estimates (I and Q)') |
---|
| 509 | |
---|
| 510 | subplot(2,1,2); |
---|
| 511 | bh = bar(x, fftshift(abs(rx_H_est_plot)),1,'LineWidth', 1); |
---|
| 512 | shading flat |
---|
| 513 | set(bh,'FaceColor',[0 0 1]) |
---|
| 514 | axis([min(x) max(x) 0 1.1*max(abs(rx_H_est_plot))]) |
---|
| 515 | grid on; |
---|
| 516 | title('Channel Estimates (Magnitude)') |
---|
| 517 | xlabel('Baseband Frequency (MHz)') |
---|
| 518 | |
---|
[2866] | 519 | if(WRITE_PNG_FILES) |
---|
[4337] | 520 | print(gcf,sprintf('wl_ofdm_plots_%s_chanEst', example_mode_string), '-dpng', '-r96', '-painters') |
---|
[2866] | 521 | end |
---|
| 522 | |
---|
[4757] | 523 | %% Pilot phase error estimate |
---|
[2848] | 524 | cf = cf + 1; |
---|
| 525 | figure(cf); clf; |
---|
[4757] | 526 | subplot(2,1,1) |
---|
[2848] | 527 | plot(pilot_phase_err, 'b', 'LineWidth', 2); |
---|
| 528 | title('Phase Error Estimates') |
---|
| 529 | xlabel('OFDM Symbol Index') |
---|
[4757] | 530 | ylabel('Radians') |
---|
[2848] | 531 | axis([1 N_OFDM_SYMS -3.2 3.2]) |
---|
| 532 | grid on |
---|
| 533 | |
---|
[4777] | 534 | h = colorbar; |
---|
| 535 | set(h,'Visible','off'); |
---|
| 536 | |
---|
[4757] | 537 | subplot(2,1,2) |
---|
| 538 | imagesc(1:N_OFDM_SYMS, (SC_IND_DATA - N_SC/2), fftshift(pilot_phase_sfo_corr,1)) |
---|
| 539 | xlabel('OFDM Symbol Index') |
---|
| 540 | ylabel('Subcarrier Index') |
---|
| 541 | title('Phase Correction for SFO') |
---|
| 542 | colorbar |
---|
| 543 | myAxis = caxis(); |
---|
[4777] | 544 | if(myAxis(2)-myAxis(1) < (pi)) |
---|
[4838] | 545 | caxis([-pi/2 pi/2]) |
---|
[4757] | 546 | end |
---|
| 547 | |
---|
| 548 | |
---|
[2866] | 549 | if(WRITE_PNG_FILES) |
---|
[4337] | 550 | print(gcf,sprintf('wl_ofdm_plots_%s_phaseError', example_mode_string), '-dpng', '-r96', '-painters') |
---|
[2866] | 551 | end |
---|
| 552 | |
---|
[4757] | 553 | %% Symbol constellation |
---|
[2848] | 554 | cf = cf + 1; |
---|
| 555 | figure(cf); clf; |
---|
| 556 | |
---|
[4777] | 557 | plot(payload_syms_mat(:),'ro','MarkerSize',1); |
---|
[2848] | 558 | axis square; axis(1.5*[-1 1 -1 1]); |
---|
| 559 | grid on; |
---|
| 560 | hold on; |
---|
| 561 | |
---|
| 562 | plot(tx_syms_mat(:),'bo'); |
---|
| 563 | title('Tx and Rx Constellations') |
---|
[4808] | 564 | legend('Rx','Tx','Location','EastOutside'); |
---|
[2866] | 565 | |
---|
| 566 | if(WRITE_PNG_FILES) |
---|
[4337] | 567 | print(gcf,sprintf('wl_ofdm_plots_%s_constellations', example_mode_string), '-dpng', '-r96', '-painters') |
---|
[4757] | 568 | end |
---|
| 569 | |
---|
| 570 | |
---|
| 571 | % EVM & SNR |
---|
| 572 | cf = cf + 1; |
---|
| 573 | figure(cf); clf; |
---|
| 574 | |
---|
| 575 | evm_mat = abs(payload_syms_mat - tx_syms_mat).^2; |
---|
| 576 | aevms = mean(evm_mat(:)); |
---|
| 577 | snr = 10*log10(1./aevms); |
---|
| 578 | |
---|
| 579 | subplot(2,1,1) |
---|
| 580 | plot(100*evm_mat(:),'o','MarkerSize',1) |
---|
| 581 | axis tight |
---|
| 582 | hold on |
---|
| 583 | plot([1 length(evm_mat(:))], 100*[aevms, aevms],'r','LineWidth',4) |
---|
| 584 | myAxis = axis; |
---|
| 585 | h = text(round(.05*length(evm_mat(:))), 100*aevms+ .1*(myAxis(4)-myAxis(3)), sprintf('Effective SNR: %.1f dB', snr)); |
---|
| 586 | set(h,'Color',[1 0 0]) |
---|
| 587 | set(h,'FontWeight','bold') |
---|
| 588 | set(h,'FontSize',10) |
---|
| 589 | set(h,'EdgeColor',[1 0 0]) |
---|
| 590 | set(h,'BackgroundColor',[1 1 1]) |
---|
| 591 | hold off |
---|
| 592 | xlabel('Data Symbol Index') |
---|
| 593 | ylabel('EVM (%)'); |
---|
[4808] | 594 | legend('Per-Symbol EVM','Average EVM','Location','NorthWest'); |
---|
[4757] | 595 | title('EVM vs. Data Symbol Index') |
---|
| 596 | grid on |
---|
| 597 | |
---|
| 598 | subplot(2,1,2) |
---|
| 599 | imagesc(1:N_OFDM_SYMS, (SC_IND_DATA - N_SC/2), 100*fftshift(evm_mat,1)) |
---|
| 600 | |
---|
| 601 | grid on |
---|
| 602 | xlabel('OFDM Symbol Index') |
---|
| 603 | ylabel('Subcarrier Index') |
---|
| 604 | title('EVM vs. (Subcarrier & OFDM Symbol)') |
---|
| 605 | h = colorbar; |
---|
| 606 | set(get(h,'title'),'string','EVM (%)'); |
---|
| 607 | myAxis = caxis(); |
---|
[4777] | 608 | if (myAxis(2)-myAxis(1)) < 5 |
---|
| 609 | caxis([myAxis(1), myAxis(1)+5]) |
---|
[4757] | 610 | end |
---|
| 611 | |
---|
| 612 | if(WRITE_PNG_FILES) |
---|
| 613 | print(gcf,sprintf('wl_ofdm_plots_%s_evm', example_mode_string), '-dpng', '-r96', '-painters') |
---|
| 614 | end |
---|
| 615 | |
---|
[4782] | 616 | %% Calculate Rx stats |
---|
[4757] | 617 | |
---|
[4782] | 618 | sym_errs = sum(tx_data ~= rx_data); |
---|
| 619 | bit_errs = length(find(dec2bin(bitxor(tx_data, rx_data),8) == '1')); |
---|
| 620 | rx_evm = sqrt(sum((real(rx_syms) - real(tx_syms)).^2 + (imag(rx_syms) - imag(tx_syms)).^2)/(length(SC_IND_DATA) * N_OFDM_SYMS)); |
---|
[4757] | 621 | |
---|
[4782] | 622 | fprintf('\nResults:\n'); |
---|
| 623 | fprintf('Num Bytes: %d\n', N_DATA_SYMS * log2(MOD_ORDER) / 8); |
---|
| 624 | fprintf('Sym Errors: %d (of %d total symbols)\n', sym_errs, N_DATA_SYMS); |
---|
| 625 | fprintf('Bit Errors: %d (of %d total bits)\n', bit_errs, N_DATA_SYMS * log2(MOD_ORDER)); |
---|
| 626 | |
---|
| 627 | cfo_est_lts = rx_cfo_est_lts*(SAMP_FREQ/INTERP_RATE); |
---|
| 628 | cfo_est_phaseErr = mean(diff(unwrap(pilot_phase_err)))/(4e-6*2*pi); |
---|
| 629 | cfo_total_ppm = ((cfo_est_lts + cfo_est_phaseErr) / ((2.412+(.005*(CHANNEL-1)))*1e9)) * 1e6; |
---|
| 630 | |
---|
| 631 | fprintf('CFO Est: %3.2f kHz (%3.2f ppm)\n', (cfo_est_lts + cfo_est_phaseErr)*1e-3, cfo_total_ppm); |
---|
| 632 | fprintf(' LTS CFO Est: %3.2f kHz\n', cfo_est_lts*1e-3); |
---|
| 633 | fprintf(' Phase Error Residual CFO Est: %3.2f kHz\n', cfo_est_phaseErr*1e-3); |
---|
| 634 | |
---|
| 635 | if DO_APPLY_SFO_CORRECTION |
---|
[4839] | 636 | drift_sec = pilot_slope_mat / (2*pi*312500); |
---|
[4782] | 637 | sfo_est_ppm = 1e6*mean((diff(drift_sec) / 4e-6)); |
---|
| 638 | sfo_est = sfo_est_ppm*20; |
---|
| 639 | fprintf('SFO Est: %3.2f Hz (%3.2f ppm)\n', sfo_est, sfo_est_ppm); |
---|
[4838] | 640 | |
---|
[4782] | 641 | end |
---|
| 642 | |
---|
| 643 | |
---|
| 644 | |
---|