[2046] | 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
[2047] | 2 | % wl_example_siso_txrx_nodeSync.m |
---|
[2046] | 3 | % |
---|
[2047] | 4 | % This example illustrates how to synchronize multiple WARP v3 nodes |
---|
| 5 | % to eliminate all frequency and timing offsets. |
---|
[2046] | 6 | % |
---|
[4689] | 7 | % Requirements: |
---|
| 8 | % - 2 WARP nodes (same hardware generation); 1 RF interface each |
---|
| 9 | % - Ether: |
---|
| 10 | % - 2 CM-MMCX modules; MMCX coax cable assemblies to connect the CM-MMCX I/O |
---|
| 11 | % and a 2-pin twisted pair cable assembly to route the inter-node trigger |
---|
| 12 | % - 2 CM-PLL modules; CM-PLL connector |
---|
| 13 | % (see: http://warpproject.org/trac/wiki/HardwareUsersGuides/CM-PLL/Connectors#Cables ) |
---|
| 14 | % - WARPLab 7.6.0 and higher |
---|
[2050] | 15 | % |
---|
[4689] | 16 | % More details on using this example are available on the WARP site: |
---|
| 17 | % http://warpproject.org/w/WARPLab/Examples |
---|
| 18 | % |
---|
[2046] | 19 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
[2050] | 20 | |
---|
[2046] | 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
[4689] | 22 | % Top Level Control Variables |
---|
[2046] | 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 24 | |
---|
[4689] | 25 | % External trigger mode requires a connection from the trigger output EXT_OUT_P0 on node 0 |
---|
| 26 | % to EXT_IN_P3 on node 1 (see http://warpproject.org/w/WARPLab/Examples for details) |
---|
[4485] | 27 | % |
---|
[4689] | 28 | USE_EXTERNAL_TRIGGER = true; |
---|
[2046] | 29 | |
---|
[4689] | 30 | % To maintain constant phase offsets among nodes sharing an RF reference clock, bypass |
---|
| 31 | % wl_initNodes() which executes a reset of the MAX2829 transceivers that forces a re-tune |
---|
| 32 | % of the PLL that changes the inter-node phases. |
---|
| 33 | % |
---|
| 34 | % NOTE: This has to be false the first time this script is run otherwise, the script will |
---|
| 35 | % not have the "nodes" variable populated. |
---|
| 36 | % |
---|
[4819] | 37 | BYPASS_INIT_NODES = false; |
---|
[4485] | 38 | |
---|
| 39 | % RX variables |
---|
[4373] | 40 | USE_AGC = true; |
---|
[4485] | 41 | ManualRxGainRF = 1; % Rx RF Gain in [1:3] (ignored if USE_AGC is true) |
---|
| 42 | ManualRxGainBB = 15; % Rx Baseband Gain in [0:31] (ignored if USE_AGC is true) |
---|
[4373] | 43 | |
---|
[4689] | 44 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 45 | % Set up the WARPLab experiment |
---|
| 46 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 47 | |
---|
[4373] | 48 | % Create a vector of node objects |
---|
[4689] | 49 | if ( ~BYPASS_INIT_NODES ) |
---|
[2046] | 50 | |
---|
[4689] | 51 | nodes = wl_initNodes(2); |
---|
| 52 | |
---|
| 53 | else |
---|
| 54 | % This example assumes that the node is in the state from which it exits initNodes. |
---|
| 55 | % If the example does not run initNodes to keep the phase offsets constant, then we need |
---|
| 56 | % to issue a couple of commands to put the node in a known state. |
---|
| 57 | % |
---|
| 58 | |
---|
| 59 | % Set the transmit delay to zero |
---|
| 60 | wl_basebandCmd(nodes, 'tx_delay', 0); |
---|
| 61 | |
---|
| 62 | % Disable the buffers and RF interfaces for TX / RX |
---|
| 63 | wl_basebandCmd(nodes, ifc_ids.RF_ALL, 'tx_rx_buff_dis'); |
---|
| 64 | wl_interfaceCmd(nodes, ifc_ids.RF_ALL, 'tx_rx_dis'); |
---|
| 65 | end |
---|
| 66 | |
---|
| 67 | |
---|
[4485] | 68 | % Assign roles to the nodes (ie transmitter / receiver) |
---|
[4336] | 69 | node_tx = nodes(1); |
---|
| 70 | node_rx = nodes(2); |
---|
| 71 | |
---|
[4689] | 72 | |
---|
| 73 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 74 | % Set up Trigger Manager |
---|
| 75 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 76 | |
---|
[4373] | 77 | % Create a UDP broadcast trigger and primary node to be ready for it |
---|
[2046] | 78 | eth_trig = wl_trigger_eth_udp_broadcast; |
---|
[4478] | 79 | nodes.wl_triggerManagerCmd('add_ethernet_trigger', [eth_trig]); |
---|
[2046] | 80 | |
---|
[4373] | 81 | % Read Trigger IDs into workspace |
---|
[4689] | 82 | trig_in_ids = wl_getTriggerInputIDs(node_tx); |
---|
| 83 | trig_out_ids = wl_getTriggerOutputIDs(node_tx); |
---|
[2046] | 84 | |
---|
[4689] | 85 | % For the transmit node, we will allow Ethernet to trigger the buffer baseband, the AGC, and debug0 |
---|
[4373] | 86 | % (which is mapped to pin 8 on the debug header) |
---|
[4689] | 87 | node_tx.wl_triggerManagerCmd('output_config_input_selection', [trig_out_ids.BASEBAND, trig_out_ids.EXT_OUT_P0], [trig_in_ids.ETH_A]); |
---|
[2046] | 88 | |
---|
| 89 | if(USE_EXTERNAL_TRIGGER) |
---|
[4373] | 90 | % For the receive node, we will allow debug3 (mapped to pin 15 on the |
---|
| 91 | % debug header) to trigger the buffer baseband, and the AGC |
---|
[4702] | 92 | % Note that the below line selects both P0 and P3. This will allow the |
---|
| 93 | % script to work with either the CM-PLL (where output P0 directly |
---|
| 94 | % connects to input P0) or the CM-MMCX (where output P0 is usually |
---|
| 95 | % connected to input P3 since both neighbor ground pins). |
---|
| 96 | node_rx.wl_triggerManagerCmd('output_config_input_selection', [trig_out_ids.BASEBAND, trig_out_ids.AGC], [trig_in_ids.EXT_IN_P0, trig_in_ids.EXT_IN_P3]); |
---|
[2046] | 97 | else |
---|
[4689] | 98 | node_rx.wl_triggerManagerCmd('output_config_input_selection', [trig_out_ids.BASEBAND, trig_out_ids.AGC], [trig_in_ids.ETH_A]); |
---|
[2046] | 99 | end |
---|
[2047] | 100 | |
---|
[4373] | 101 | % For the receive node, we enable the debounce circuity on the debug 3 input |
---|
| 102 | % to deal with the fact that the signal may be noisy. |
---|
[4984] | 103 | node_rx.wl_triggerManagerCmd('input_config_debounce_mode', [trig_in_ids.EXT_IN_P0, trig_in_ids.EXT_IN_P3], true); |
---|
[2046] | 104 | |
---|
[4373] | 105 | % Since the debounce circuitry is enabled, there will be a delay at the |
---|
| 106 | % receiver node for its input trigger. To better align the transmitter and |
---|
| 107 | % receiver, we can artifically delay the transmitters trigger outputs that |
---|
| 108 | % drive the buffer baseband and the AGC. |
---|
[4702] | 109 | node_tx.wl_triggerManagerCmd('output_config_delay', [trig_out_ids.EXT_OUT_P0], 0); |
---|
| 110 | node_tx.wl_triggerManagerCmd('output_config_delay', [trig_out_ids.BASEBAND], 62.5); % 62.5ns delay |
---|
[4689] | 111 | node_rx.wl_triggerManagerCmd('output_config_delay', [trig_out_ids.AGC], 3000); % 3000ns delay |
---|
[2046] | 112 | |
---|
| 113 | |
---|
[4689] | 114 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 115 | % Set up the Interface parameters |
---|
| 116 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
[4336] | 117 | |
---|
[4689] | 118 | % Get IDs for the interfaces on the boards. |
---|
| 119 | % |
---|
| 120 | % NOTE: This example assumes each board has the same interface capabilities (ie 2 RF |
---|
| 121 | % interfaces; RFA and RFB). Therefore, we only need to get the IDs from one of the boards. |
---|
| 122 | % |
---|
| 123 | ifc_ids = wl_getInterfaceIDs(node_tx); |
---|
[2046] | 124 | |
---|
[4689] | 125 | % Set the Transmit and Receive interfaces |
---|
| 126 | % Transmit from RFA of one node to RFA of the other node |
---|
| 127 | % |
---|
| 128 | % NOTE: Variables are used to make it easier to change interfaces. |
---|
| 129 | % |
---|
| 130 | RF_TX = ifc_ids.RF_A; % Transmit RF interface |
---|
| 131 | RF_RX = ifc_ids.RF_A; % Receive RF interface |
---|
| 132 | |
---|
| 133 | RF_RX_VEC = ifc_ids.RF_A; % Vector version of transmit RF interface |
---|
| 134 | RF_TX_VEC = ifc_ids.RF_A; % Vector version of receive RF interface |
---|
| 135 | |
---|
| 136 | % Set the RF center frequency on all interfaces |
---|
| 137 | % - Frequency Band : Must be 2.4 or 5, to select 2.4GHz or 5GHz channels |
---|
| 138 | % - Channel : Must be an integer in [1,11] for BAND = 2.4; [1,23] for BAND = 5 |
---|
| 139 | % |
---|
| 140 | wl_interfaceCmd(nodes, ifc_ids.RF_ALL, 'channel', 2.4, 11); |
---|
| 141 | |
---|
| 142 | % Set the RX gains on all interfaces or use AGC |
---|
| 143 | % - Rx RF Gain : Must be an integer in [1:3] |
---|
| 144 | % - Rx Baseband Gain: Must be an integer in [0:31] |
---|
| 145 | % |
---|
| 146 | % NOTE: The gains may need to be modified depending on your experimental setup |
---|
| 147 | % |
---|
[2046] | 148 | if(USE_AGC) |
---|
[4689] | 149 | wl_interfaceCmd(nodes, ifc_ids.RF_ALL, 'rx_gain_mode', 'automatic'); |
---|
[4373] | 150 | wl_basebandCmd(nodes, 'agc_target', -10); |
---|
[2046] | 151 | else |
---|
[4689] | 152 | wl_interfaceCmd(nodes, ifc_ids.RF_ALL, 'rx_gain_mode', 'manual'); |
---|
| 153 | wl_interfaceCmd(nodes, ifc_ids.RF_ALL, 'rx_gains', ManualRxGainRF, ManualRxGainBB); |
---|
[2046] | 154 | end |
---|
| 155 | |
---|
[4689] | 156 | % Set the TX gains on all interfaces |
---|
| 157 | % - Tx Baseband Gain: Must be an integer in [0:3] for approx [-5, -3, -1.5, 0]dB baseband gain |
---|
| 158 | % - Tx RF Gain : Must be an integer in [0:63] for approx [0:31]dB RF gain |
---|
| 159 | % |
---|
| 160 | % NOTE: The gains may need to be modified depending on your experimental setup |
---|
| 161 | % |
---|
| 162 | wl_interfaceCmd(nodes, ifc_ids.RF_ALL, 'tx_gains', 3, 30); |
---|
| 163 | |
---|
| 164 | |
---|
| 165 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 166 | % Set up the Baseband parameters |
---|
| 167 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 168 | |
---|
| 169 | % Get the sample frequency from the board |
---|
| 170 | ts = 1 / (wl_basebandCmd(nodes(1), 'tx_buff_clk_freq')); |
---|
| 171 | |
---|
| 172 | % Read the maximum I/Q buffer length. |
---|
| 173 | % |
---|
| 174 | % NOTE: This example assumes that each board has the same baseband capabilities (ie both nodes are |
---|
| 175 | % the same WARP hardware version, for example WARP v3). This example also assumes that each RF |
---|
| 176 | % interface has the same baseband capabilities (ie the max number of TX samples is the same as the |
---|
| 177 | % max number of RF samples). Therefore, we only need to read the max I/Q buffer length of node_tx RFA. |
---|
| 178 | % |
---|
[4336] | 179 | maximum_buffer_len = wl_basebandCmd(node_tx, RF_TX, 'tx_buff_max_num_samples'); |
---|
| 180 | |
---|
[4689] | 181 | % Set the transmission / receptions lengths (in samples) |
---|
| 182 | % See WARPLab user guide for maximum length supported by WARP hardware |
---|
| 183 | % versions and different WARPLab versions. |
---|
| 184 | % |
---|
| 185 | tx_length = 2^15; |
---|
| 186 | rx_length = tx_length; |
---|
[4336] | 187 | |
---|
[4689] | 188 | % Check the transmission length |
---|
| 189 | if (tx_length > maximum_buffer_len) |
---|
| 190 | error('Node supports max transmission length of %d samples. Requested %d samples.', maximum_buffer_len, tx_length); |
---|
| 191 | end |
---|
[2046] | 192 | |
---|
[4689] | 193 | % Set the length for the transmit and receive buffers based on the transmission length |
---|
| 194 | wl_basebandCmd(nodes, 'tx_length', tx_length); |
---|
| 195 | wl_basebandCmd(nodes, 'rx_length', rx_length); |
---|
[4373] | 196 | |
---|
[4689] | 197 | |
---|
[2046] | 198 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 199 | % Signal processing to generate transmit signal |
---|
| 200 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 201 | |
---|
[4689] | 202 | % First generate the preamble for AGC. |
---|
| 203 | % NOTE: The preamble corresponds to the short symbols from the 802.11a PHY standard |
---|
| 204 | % |
---|
[2046] | 205 | shortSymbol_freq = [0 0 0 0 0 0 0 0 1+i 0 0 0 -1+i 0 0 0 -1-i 0 0 0 1-i 0 0 0 -1-i 0 0 0 1-i 0 0 0 0 0 0 0 1-i 0 0 0 -1-i 0 0 0 1-i 0 0 0 -1-i 0 0 0 -1+i 0 0 0 1+i 0 0 0 0 0 0 0].'; |
---|
| 206 | shortSymbol_freq = [zeros(32,1);shortSymbol_freq;zeros(32,1)]; |
---|
| 207 | shortSymbol_time = ifft(fftshift(shortSymbol_freq)); |
---|
| 208 | shortSymbol_time = (shortSymbol_time(1:32).')./max(abs(shortSymbol_time)); |
---|
[4689] | 209 | shortsyms_rep = repmat(shortSymbol_time,1,30); |
---|
| 210 | preamble = shortsyms_rep; |
---|
| 211 | preamble = preamble(:); |
---|
[2046] | 212 | |
---|
| 213 | |
---|
[4689] | 214 | t = [0:ts:((tx_length - length(preamble) - 1))*ts].'; % Create time vector(Sample Frequency is ts (Hz)) |
---|
[2046] | 215 | |
---|
[4689] | 216 | sinusoid = 0.6 * exp(j*2*pi * 2e6 * t); % Create 2 MHz sinusoid |
---|
[2046] | 217 | |
---|
[4689] | 218 | tx_data = [preamble; sinusoid]; |
---|
[2046] | 219 | |
---|
[4373] | 220 | |
---|
[2046] | 221 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 222 | % Transmit and receive signal using WARPLab |
---|
| 223 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 224 | |
---|
[4485] | 225 | % Transmit IQ data to the TX node |
---|
[4689] | 226 | wl_basebandCmd(node_tx, RF_TX_VEC, 'write_IQ', tx_data(:)); |
---|
[4485] | 227 | |
---|
| 228 | % Enabled the RF interfaces for TX / RX |
---|
[4373] | 229 | wl_interfaceCmd(node_tx, RF_TX, 'tx_en'); |
---|
| 230 | wl_interfaceCmd(node_rx, RF_RX, 'rx_en'); |
---|
[2046] | 231 | |
---|
[4485] | 232 | % Enable the buffers for TX / RX |
---|
| 233 | wl_basebandCmd(node_tx, RF_TX, 'tx_buff_en'); |
---|
| 234 | wl_basebandCmd(node_rx, RF_RX, 'rx_buff_en'); |
---|
[2046] | 235 | |
---|
[4485] | 236 | % Send the Ethernet trigger to start the TX |
---|
[2047] | 237 | eth_trig.send(); |
---|
[4336] | 238 | |
---|
[4485] | 239 | % Read the IQ data from the RX node |
---|
[4689] | 240 | rx_IQ = wl_basebandCmd(node_rx, RF_RX_VEC, 'read_IQ', 0, rx_length); |
---|
[4485] | 241 | |
---|
| 242 | % Disable the buffers and RF interfaces for TX / RX |
---|
[4689] | 243 | wl_basebandCmd(nodes, ifc_ids.RF_ALL, 'tx_rx_buff_dis'); |
---|
| 244 | wl_interfaceCmd(nodes, ifc_ids.RF_ALL, 'tx_rx_dis'); |
---|
[2046] | 245 | |
---|
| 246 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
| 247 | % Visualize results |
---|
| 248 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
---|
[4689] | 249 | tVec = [0:ts:((tx_length -1 )*ts)]*1e6; |
---|
[2046] | 250 | |
---|
| 251 | sampStart = 5000; |
---|
[4689] | 252 | sampEnd = tx_length; |
---|
[2047] | 253 | htxt = []; |
---|
[2046] | 254 | |
---|
| 255 | figure(1);clf; |
---|
| 256 | subplot(3,1,1) |
---|
[4689] | 257 | plot(tVec(sampStart:sampEnd),real(tx_data(sampStart:sampEnd)),'b') |
---|
[2047] | 258 | htxt(end+1) = ylabel('Amplitude'); |
---|
| 259 | htxt(end+1) = title('Transmitted I Waveform'); |
---|
| 260 | axis([tVec(sampStart) tVec(sampEnd) -1 1]); |
---|
| 261 | grid on; |
---|
[2046] | 262 | |
---|
| 263 | subplot(3,1,2) |
---|
[2047] | 264 | plot(tVec(sampStart:sampEnd),real(rx_IQ(sampStart:sampEnd,:)), 'r') |
---|
| 265 | htxt(end+1) = ylabel('Amplitude'); |
---|
| 266 | htxt(end+1) = title('Received I Waveform'); |
---|
| 267 | axis([tVec(sampStart) tVec(sampEnd) -1 1]); |
---|
| 268 | grid on; |
---|
[2046] | 269 | |
---|
| 270 | subplot(3,1,3) |
---|
[4689] | 271 | phase_diff = unwrap(angle(rx_IQ(sampStart:sampEnd))) - unwrap(angle(tx_data(sampStart:sampEnd))); |
---|
[2047] | 272 | plot(tVec(sampStart:sampEnd), phase_diff) |
---|
[4340] | 273 | axis tight |
---|
[4343] | 274 | myAxis = axis; |
---|
| 275 | |
---|
| 276 | if(myAxis(4)-myAxis(3) < 2*pi) |
---|
| 277 | %Zoom out to at least a 2*pi range of angles |
---|
| 278 | axis([myAxis(1), myAxis(2), mean(myAxis(3:4))-pi, mean(myAxis(3:4))+pi]); |
---|
| 279 | |
---|
| 280 | end |
---|
| 281 | |
---|
[2047] | 282 | grid on; |
---|
[4336] | 283 | htxt(end+1) = title('Tx-Rx Phase Offset'); |
---|
[2047] | 284 | htxt(end+1) = ylabel('Phase Difference (radians)'); |
---|
| 285 | htxt(end+1) = xlabel('Time (us)'); |
---|
[2046] | 286 | |
---|
[2047] | 287 | set(htxt, 'FontWeight', 'bold'); |
---|