├── README.md ├── bladeRF_MIMO_Simulink.m ├── bladeRF_MIMO_XCVR.m └── bladeRF_MIMO.m /README.md: -------------------------------------------------------------------------------- 1 | # BladeRF_MIMO 2 | 3 | MATLAB and Simulink Interface for the BladeRF MIMO layout. These scripts are a modifyied version of the 4 | [original scripts](https://github.com/Nuand/bladeRF/tree/master/host/libraries/libbladeRF_bindings/matlab) 5 | provided by Nuand. The modifications were done as described in the [libbladeRF API documentation](http://www.nuand.com/libbladeRF-doc/v2.2.1/). 6 | 7 | * Created as part of research on 8 | 9 | ```User Equipment Tracking Beamforming Using a MIMO Software-Defined Radio``` 10 | 11 | 12 | ## You are here 13 | 14 | * https://github.com/JoseAmador95/BladeRF_MIMO 15 | 16 | 17 | ### Return to UoS Beamforming By JoseAmador95 18 | 19 | * https://github.com/JoseAmador95/UoS_Beamforming 20 | 21 | #### 22 | 23 | * Model setup script 24 | * Simulations 25 | * Modulatior and Receiver simulation test model 26 | * TX Beamformer simulation test model 27 | * RX Beamformer simulation test model 28 | * Theoretical Radiation pattern generator 29 | * User Equipment tracking simulation 30 | * Real Implementation 31 | * [MIMO Interface for the BladeRF](https://github.com/JoseAmador95/BladeRF_MIMO) <---------- You are Here 32 | * MIMO SDR layout test model 33 | * User Equipment tracking model 34 | * CAD 35 | * Fusion 360 36 | * STEP 37 | * [Report](https://github.com/JoseAmador95/UoS_Beamforming/blob/master/Report.pdf) <----- Great Reference 38 | 39 | ## Requirements 40 | The MIMO objects require an older version of the BladeRF release. The recommended version is [2019.07](https://github.com/Nuand/bladeRF/releases/tag/2019.07). Particularly, 2021.02 is incompatible. Please let me know if a workaround is found for the newer version. 41 | 42 | ## How to Use 43 | These scripts require the official tools from [Nuand](https://www.nuand.com/support/); include the root directory into 44 | MATLAB's path. To use within Simulink, copy the scripts in this repo into the working directory and introduce a _MATLAB System_ 45 | block (From Simulink/User-Defined Functions) into the Simulink model and select _bladeRF_MIMO_Simulink.m_. Make sure to test the 46 | BladeRF using Nuand's BladeRF CLI and to close all of its instances before running the Simulink model. 47 | 48 | ## How was this done 49 | The [original scripts](https://github.com/Nuand/bladeRF/tree/master/host/libraries/libbladeRF_bindings/matlab) allow to transmit 50 | and receive signals from a single channel of the BladeRF; leaving the other channel unused. The scripts in this repo enables every 51 | channel in the SDR, executes the synchronous TX/RX functions considering the 2x2 layout and interleaves/de-interleaves the samples 52 | according to the [streaming format](http://www.nuand.com/libbladeRF-doc/v2.2.1/group___s_t_r_e_a_m_i_n_g___f_o_r_m_a_t.html#ga4c61587834fd4de51a8e2d34e14a73b2). 53 | 54 | ## Known Issues 55 | Altough these scripts do transmit and receive samples as expected, some stability issues exist when opening and closing the device. 56 | Some of these issues are not causes by the modifications to support the MIMO layout, as they are also present in Nuand's scripts. 57 | 58 | These issues cause the SDR to become unreachable for any application until the PC is rebooted or the user logs-in again (Windows). 59 | 60 | * Stoping the Simulink model execution may cause an issue, but not every time. 61 | * Modifying the gain of either the TX and RX front-ends at runtime causes an issue every time. 62 | 63 | 64 | -------------------------------------------------------------------------------- /bladeRF_MIMO_Simulink.m: -------------------------------------------------------------------------------- 1 | % This class implements a bladeRF MATLAB System Simulink block. 2 | % 3 | % The bladeRF_Simulink block interfaces with a single bladeRF device and is 4 | % capable of utilizing both the transmit and receive paths on the device. The 5 | % block properties may be used to enable and use RX, TX, or both. 6 | % 7 | % To use this block, place a "MATLAB System" block in your Simulink Model and 8 | % specify "bladeRF_Simulink" for the system object name. 9 | % 10 | % Next, configure the device by double clicking on the block. Here, a few 11 | % groups of settings are presented: 12 | % * Device Device selection and device-wide settings 13 | % * RX Configuration RX-specific settings 14 | % * TX Configuration TX-specific settings 15 | % * Miscellaneous Other library-specific options 16 | % 17 | % Currently only "Interpreted Execution" is supported. Be sure to select 18 | % this in the first tab. 19 | % 20 | % In the "Device" tab, the "Device Specification String" allows one to specify 21 | % which device to use if multiple bladeRFs are connected. For example, to use 22 | % a device with a serial number starting with a3f..., a valid device 23 | % specification string would be: 24 | % 25 | % "*:serial=a3f" 26 | % 27 | % Alternatively, one can specify the "Nth" device if the block<->device 28 | % assignments do not matter. For two devices, one could use: 29 | % "*:instance=0" and "*:instance=1" 30 | % 31 | % If left blank, this string will select the first available device. 32 | % 33 | % To enable the receive output port, check the "Enable Receiver" checkbox in 34 | % the "RX Configuration" tab. Similarly to enable the transmit input port, 35 | % check the "Enable Transmitter" checkbox in the "TX Configuration" tab. 36 | % 37 | % See also: bladeRF 38 | 39 | % Copyright (c) 2015 Nuand LLC 40 | % 41 | % Permission is hereby granted, free of charge, to any person obtaining a copy 42 | % of this software and associated documentation files (the "Software"), to deal 43 | % in the Software without restriction, including without limitation the rights 44 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 45 | % copies of the Software, and to permit persons to whom the Software is 46 | % furnished to do so, subject to the following conditions: 47 | % 48 | % The above copyright notice and this permission notice shall be included in 49 | % all copies or substantial portions of the Software. 50 | % 51 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 52 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 53 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 54 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 55 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 56 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 57 | % THE SOFTWARE. 58 | % 59 | 60 | classdef bladeRF_MIMO_Simulink < matlab.System & ... 61 | matlab.system.mixin.Propagates & ... 62 | matlab.system.mixin.CustomIcon 63 | %% Properties 64 | properties 65 | verbosity = 'Info' % libbladeRF verbosity 66 | 67 | rx_frequency = 890e6; % Frequency (Hz) 68 | rx_gain = [60 60]; % RX Gain [-10, 90] 69 | rx_agc = 'SLOW'; % AGC mode 70 | 71 | tx_frequency = 890e6; % Frequency (Hz) 72 | tx_gain = [60 60]; % TX Gain [-10, 60] 73 | end 74 | 75 | properties(Nontunable) 76 | device_string = ''; % Device specification string 77 | loopback_mode = 'None' % Active loopback mode 78 | 79 | rx_bandwidth = '1.5'; % LPF Bandwidth (MHz) 80 | rx_samplerate = 1e6; % Sample rate 81 | rx_num_buffers = 64; % Number of stream buffers to use 82 | rx_num_transfers = 16; % Number of USB transfers to use 83 | rx_buf_size = 1024*10; % Size of each stream buffer, in samples (must be multiple of 1024) 84 | rx_step_size = 10000; % Number of samples to RX during each simulation step 85 | rx_timeout_ms = 5000; % Stream timeout (ms) 86 | 87 | tx_bandwidth = '1.5'; % LPF Bandwidth (MHz) 88 | tx_samplerate = 1e6; % Sample rate 89 | tx_num_buffers = 64; % Number of stream buffers to use 90 | tx_num_transfers = 16; % Number of USB transfers to use 91 | tx_buf_size = 16384; % Size of each stream buffer, in samples (must be multiple of 1024) 92 | tx_step_size = 1024*10; % Number of samples to TX during each simulation step 93 | tx_timeout_ms = 5000; % Stream timeout (ms) 94 | end 95 | 96 | properties(Logical, Nontunable) 97 | enable_rx = true; % Enable Receiver 98 | enable_rx_biastee = false; % Enable RX Biastee 99 | enable_overrun = false; % Enable Overrun output 100 | enable_tx = true; % Enable Transmitter 101 | enable_tx_biastee = false; % Enable TX Biastee 102 | enable_underrun = false; % Enable Underrun output (for future use) 103 | xb200 = false % Enable use of XB-200 (must be attached) 104 | end 105 | 106 | properties(Hidden, Transient) 107 | rx_bandwidthSet = matlab.system.StringSet({ ... 108 | '1.5', '1.75', '2.5', '2.75', ... 109 | '3', '3.84', '5', '5.5', ... 110 | '6', '7', '8.75', '10', ... 111 | '12', '14', '20', '28', ... 112 | '30', '32', '34', '36', ... 113 | '38', '40', '42', '44', ... 114 | '46', '48', '50', '52', ... 115 | '54', '56', '58', '60', ... 116 | '62', '64', '64.11' ... 117 | }); 118 | 119 | tx_bandwidthSet = matlab.system.StringSet({ ... 120 | '1.5', '1.75', '2.5', '2.75', ... 121 | '3', '3.84', '5', '5.5', ... 122 | '6', '7', '8.75', '10', ... 123 | '12', '14', '20', '28', ... 124 | '30', '32', '34', '36', ... 125 | '38', '40', '42', '44', ... 126 | '46', '48', '50', '52', ... 127 | '54', '56', '58', '60', ... 128 | '62', '64', '64.11' ... 129 | }); 130 | 131 | rx_agcSet = matlab.system.StringSet({ 132 | 'AUTO', 'MANUAL', ... 133 | 'SLOW', 'FAST', 'HYBRID' ... 134 | }); 135 | 136 | loopback_modeSet = matlab.system.StringSet({ 137 | 'None', ... 138 | 'BB_TXLPF_RXVGA2', 'BB_TXVGA1_RXVGA2', 'BB_TXLPF_RXLPF', ... 139 | 'BB_TXVGA1_RXLPF', 'RF_LNA1', 'RF_LNA2', 'RF_LNA3', ... 140 | 'Firmware' 141 | }); 142 | 143 | verbositySet = matlab.system.StringSet({ 144 | 'Verbose', 'Debug', 'Info', 'Warning', 'Critical', 'Silent' ... 145 | }); 146 | end 147 | 148 | properties (Access = private) 149 | device = [] 150 | 151 | % Cache previously set tunable values to avoid querying the device 152 | % for all properties when only one changes. 153 | curr_rx_frequency 154 | curr_rx_lna 155 | curr_rx_vga1 156 | curr_rx_vga2 157 | curr_rx_gain 158 | curr_rx_agc 159 | curr_rx_biastee 160 | curr_tx_frequency 161 | curr_tx_vga1 162 | curr_tx_vga2 163 | curr_tx_gain 164 | curr_tx_biastee 165 | 166 | end 167 | 168 | %% Static Methods 169 | methods (Static, Access = protected) 170 | function groups = getPropertyGroupsImpl 171 | device_section_group = matlab.system.display.SectionGroup(... 172 | 'Title', 'Device', ... 173 | 'PropertyList', {'device_string', 'loopback_mode', 'xb200' } ... 174 | ); 175 | 176 | rx_gain_section = matlab.system.display.Section(... 177 | 'Title', 'Gain', ... 178 | 'PropertyList', { 'rx_gain', 'enable_rx_biastee', 'rx_agc' } ... 179 | ); 180 | 181 | rx_stream_section = matlab.system.display.Section(... 182 | 'Title', 'Stream', ... 183 | 'PropertyList', {'rx_num_buffers', 'rx_num_transfers', 'rx_buf_size', 'rx_timeout_ms', 'rx_step_size', } ... 184 | ); 185 | 186 | rx_section_group = matlab.system.display.SectionGroup(... 187 | 'Title', 'RX Configuration', ... 188 | 'PropertyList', { 'enable_rx', 'enable_overrun', 'rx_frequency', 'rx_samplerate', 'rx_bandwidth' }, ... 189 | 'Sections', [ rx_gain_section, rx_stream_section ] ... 190 | ); 191 | 192 | tx_gain_section = matlab.system.display.Section(... 193 | 'Title', 'Gain', ... 194 | 'PropertyList', { 'tx_gain', 'enable_tx_biastee' } ... 195 | ); 196 | 197 | tx_stream_section = matlab.system.display.Section(... 198 | 'Title', 'Stream', ... 199 | 'PropertyList', {'tx_num_buffers', 'tx_num_transfers', 'tx_buf_size', 'tx_timeout_ms', 'tx_step_size', } ... 200 | ); 201 | 202 | tx_section_group = matlab.system.display.SectionGroup(... 203 | 'Title', 'TX Configuration', ... 204 | 'PropertyList', { 'enable_tx', 'enable_underrun', 'tx_frequency', 'tx_samplerate', 'tx_bandwidth' }, ... 205 | 'Sections', [ tx_gain_section, tx_stream_section ] ... 206 | ); 207 | 208 | misc_section_group = matlab.system.display.SectionGroup(... 209 | 'Title', 'Miscellaneous', ... 210 | 'PropertyList', {'verbosity'} ... 211 | ); 212 | 213 | groups = [ device_section_group, rx_section_group, tx_section_group, misc_section_group ]; 214 | end 215 | 216 | function header = getHeaderImpl 217 | text = 'This block provides access to a Nuand bladeRF device via libbladeRF MATLAB bindings.'; 218 | header = matlab.system.display.Header('bladeRF_Simulink', ... 219 | 'Title', 'bladeRF', 'Text', text ... 220 | ); 221 | end 222 | end 223 | 224 | methods (Access = protected) 225 | %% Output setup 226 | function count = getNumOutputsImpl(obj) 227 | if obj.enable_rx == true 228 | if obj.enable_overrun == true 229 | count = 2; 230 | else 231 | count = 1; 232 | end 233 | else 234 | count = 0; 235 | end 236 | 237 | if obj.enable_tx == true && obj.enable_underrun == true 238 | count = count + 1; 239 | end 240 | end 241 | 242 | function varargout = getOutputNamesImpl(obj) 243 | if obj.enable_rx == true 244 | varargout{1} = 'RX Samples'; 245 | n = 2; 246 | 247 | if obj.enable_overrun == true 248 | varargout{n} = 'RX Overrun'; 249 | n = n + 1; 250 | end 251 | else 252 | n = 1; 253 | end 254 | 255 | if obj.enable_tx == true && obj.enable_underrun == true 256 | varargout{n} = 'TX Underrun'; 257 | end 258 | end 259 | 260 | function varargout = getOutputDataTypeImpl(obj) 261 | if obj.enable_rx == true 262 | varargout{1} = 'double'; % RX Samples 263 | n = 2; 264 | 265 | if obj.enable_overrun == true 266 | varargout{n} = 'logical'; % RX Overrun 267 | n = n + 1; 268 | end 269 | else 270 | n = 1; 271 | end 272 | 273 | if obj.enable_tx == true && obj.enable_underrun == true 274 | varargout{n} = 'logical'; % TX Underrun 275 | end 276 | end 277 | 278 | function varargout = getOutputSizeImpl(obj) 279 | if obj.enable_rx == true 280 | varargout{1} = [obj.rx_step_size 2]; % RX Samples 281 | n = 2; 282 | 283 | if obj.enable_overrun == true 284 | varargout{n} = [1 1]; % RX Overrun 285 | n = n + 1; 286 | end 287 | else 288 | n = 1; 289 | end 290 | 291 | if obj.enable_tx == true && obj.enable_underrun == true 292 | varargout{n} = [1 1]; % TX Underrun 293 | end 294 | end 295 | 296 | function varargout = isOutputComplexImpl(obj) 297 | if obj.enable_rx == true 298 | varargout{1} = true; % RX Samples 299 | n = 2; 300 | else 301 | n = 1; 302 | end 303 | 304 | if obj.enable_overrun == true 305 | varargout{n} = false; % RX Overrun 306 | n = n + 1; 307 | end 308 | 309 | if obj.enable_tx == true && obj.enable_underrun == true 310 | varargout{n} = false; % TX Underrun 311 | end 312 | end 313 | 314 | function varargout = isOutputFixedSizeImpl(obj) 315 | if obj.enable_rx == true 316 | varargout{1} = true; % RX Samples 317 | varargout{2} = true; % RX Overrun 318 | n = 3; 319 | else 320 | n = 1; 321 | end 322 | 323 | if obj.enable_overrun == true 324 | end 325 | 326 | if obj.enable_tx == true && obj.enable_underrun == true 327 | varargout{n} = true; % TX Underrun 328 | end 329 | end 330 | 331 | %% Input setup 332 | function count = getNumInputsImpl(obj) 333 | if obj.enable_tx == true 334 | count = 1; 335 | else 336 | count = 0; 337 | end 338 | end 339 | 340 | function varargout = getInputNamesImpl(obj) 341 | if obj.enable_tx == true 342 | varargout{1} = 'TX Samples'; 343 | else 344 | varargout = {}; 345 | end 346 | end 347 | 348 | %% Property and Execution Handlers 349 | function icon = getIconImpl(~) 350 | icon = sprintf('Nuand\nbladeRF 2.0\nMIMO'); 351 | end 352 | 353 | function setupImpl(obj) 354 | %% Library setup 355 | bladeRF_MIMO.log_level(obj.verbosity); 356 | 357 | %% Device setup 358 | if obj.xb200 == true 359 | xb = 'XB200'; 360 | else 361 | xb = []; 362 | end 363 | 364 | obj.device = bladeRF_MIMO(obj.device_string, [], xb); 365 | obj.device.loopback = obj.loopback_mode; 366 | 367 | %% RX Setup 368 | obj.device.rx.config.num_buffers = obj.rx_num_buffers; 369 | obj.device.rx.config.buffer_size = obj.rx_buf_size; 370 | obj.device.rx.config.num_transfers = obj.rx_num_transfers; 371 | obj.device.rx.config.timeout_ms = obj.rx_timeout_ms; 372 | 373 | obj.device.rx.frequency = obj.rx_frequency; 374 | %obj.rx_frequency = obj.device.rx.frequency; 375 | obj.curr_rx_frequency = obj.device.rx.frequency; 376 | 377 | obj.device.rx.samplerate = obj.rx_samplerate; 378 | obj.device.rx.bandwidth = str2double(obj.rx_bandwidth) * 1e6; 379 | 380 | obj.device.rx.biastee = obj.enable_rx_biastee; 381 | obj.curr_rx_biastee = obj.device.rx.biastee; 382 | 383 | obj.device.rx.agc = obj.rx_agc; 384 | %obj.rx_agc = obj.device.rx.agc; 385 | obj.curr_rx_agc = obj.device.rx.agc; 386 | 387 | if strcmpi(obj.curr_rx_agc, 'manual') 388 | obj.device.rx.gain = obj.rx_gain; 389 | %obj.rx_gain = obj.device.rx.gain; 390 | obj.curr_rx_gain = obj.device.rx.gain; 391 | else 392 | warning([ 'Cannot set RX gain because AGC is set to ' (obj.curr_rx_agc) ' mode']) 393 | end 394 | 395 | %% TX Setup 396 | obj.device.tx.config.num_buffers = obj.tx_num_buffers; 397 | obj.device.tx.config.buffer_size = obj.tx_buf_size; 398 | obj.device.tx.config.num_transfers = obj.tx_num_transfers; 399 | obj.device.tx.config.timeout_ms = obj.tx_timeout_ms; 400 | 401 | obj.device.tx.samplerate = obj.tx_samplerate; 402 | obj.device.tx.bandwidth = str2double(obj.tx_bandwidth) * 1e6; 403 | 404 | obj.device.tx.frequency = obj.tx_frequency; 405 | %obj.tx_frequency = obj.device.tx.frequency; 406 | obj.curr_tx_frequency = obj.device.tx.frequency; 407 | 408 | obj.device.tx.biastee = obj.enable_tx_biastee; 409 | obj.curr_tx_biastee = obj.device.tx.biastee; 410 | 411 | obj.device.tx.gain = obj.tx_gain; 412 | %obj.tx_gain = obj.device.tx.gain; 413 | obj.curr_tx_gain = obj.device.tx.gain; 414 | 415 | end 416 | 417 | function releaseImpl(obj) 418 | delete(obj.device); 419 | end 420 | 421 | function resetImpl(obj) 422 | obj.device.rx.stop(); 423 | obj.device.tx.stop(); 424 | end 425 | 426 | % Perform a read of received samples and an 'overrun' array that denotes whether 427 | % the associated samples is invalid due to a detected overrun. 428 | function varargout = stepImpl(obj, varargin) 429 | varargout = {}; 430 | 431 | if obj.enable_rx == true 432 | if obj.device.rx.running == false 433 | obj.device.rx.start(); 434 | end 435 | 436 | [rx_samples, ~, ~, rx_overrun] = obj.device.receive(obj.rx_step_size, obj.rx_timeout_ms); 437 | 438 | varargout{1} = rx_samples; 439 | varargout{2} = rx_overrun; 440 | out_idx = 3; 441 | else 442 | out_idx = 1; 443 | end 444 | 445 | if obj.enable_tx == true 446 | if obj.device.tx.running == false 447 | obj.device.tx.start(); 448 | end 449 | 450 | obj.device.transmit(varargin{1}); 451 | 452 | % Detecting TX Underrun is not yet supported by libbladeRF. 453 | % This is for future use. 454 | varargout{out_idx} = false; 455 | else 456 | if obj.device.tx.running 457 | obj.device.tx.stop(); 458 | end 459 | end 460 | end 461 | 462 | function processTunedPropertiesImpl(obj) 463 | 464 | %% RX Properties 465 | if isChangedProperty(obj, 'rx_frequency') && obj.rx_frequency ~= obj.curr_rx_frequency 466 | obj.device.rx.frequency = obj.rx_frequency; 467 | %obj.rx_frequency = obj.device.rx.frequency; 468 | 469 | obj.curr_rx_frequency = obj.device.rx.frequency; 470 | %disp('Updated RX frequency'); 471 | end 472 | 473 | if isChangedProperty(obj, 'rx_gain') && obj.rx_gain ~= obj.curr_rx_gain 474 | obj.device.rx.gain = obj.rx_gain; 475 | %obj.rx_gain = obj.device.rx.gain; 476 | 477 | obj.curr_rx_gain = obj.device.rx.gain; 478 | %disp('Updated RX gain'); 479 | end 480 | 481 | if isChangedProperty(obj, 'rx_agc') && obj.rx_agc ~= obj.curr_rx_agc 482 | obj.device.rx.agc = obj.rx_agc; 483 | %obj.rx_agc = obj.device.rx.agc; 484 | 485 | obj.curr_rx_agc = obj.device.rx.agc; 486 | %disp('Updated RX AGC gain'); 487 | end 488 | 489 | %% TX Properties 490 | if isChangedProperty(obj, 'tx_frequency') && obj.tx_frequency ~= obj.curr_tx_frequency 491 | obj.device.tx.frequency = obj.tx_frequency; 492 | obj.curr_tx_frequency = obj.device.rx.frequency; 493 | %disp('Updated TX frequency'); 494 | end 495 | 496 | if isChangedProperty(obj, 'tx_gain') && obj.tx_gain ~= obj.curr_tx_gain 497 | obj.device.tx.gain = obj.tx_gain; 498 | obj.curr_tx_gain = obj.device.tx.gain; 499 | %disp('Updated TX gain'); 500 | end 501 | end 502 | 503 | function validatePropertiesImpl(obj) 504 | if obj.enable_rx == false && obj.enable_tx == false 505 | warning('Neither bladeRF RX or TX is enabled. One or both should be enabled.'); 506 | end 507 | 508 | if isempty(obj.device) 509 | tx_min_freq = 0; 510 | tx_max_freq = 0; 511 | tx_min_sampling = 0; 512 | tx_max_sampling = 0; 513 | 514 | rx_min_freq = 0; 515 | rx_max_freq = 0; 516 | rx_min_sampling = 0; 517 | rx_max_sampling = 0; 518 | return 519 | else 520 | tx_min_freq = obj.device.tx.min_frequency; 521 | tx_max_freq = obj.device.tx.max_frequency; 522 | tx_min_sampling = obj.device.tx.min_sampling; 523 | tx_max_sampling = obj.device.tx.max_sampling; 524 | 525 | rx_min_freq = obj.device.rx.min_frequency; 526 | rx_max_freq = obj.device.rx.max_frequency; 527 | rx_min_sampling = obj.device.rx.min_sampling; 528 | rx_max_sampling = obj.device.rx.max_sampling; 529 | end 530 | 531 | %% Validate RX properties 532 | if obj.rx_num_buffers < 1 533 | error('rx_num_buffers must be > 0.'); 534 | end 535 | 536 | if obj.rx_num_transfers >= obj.rx_num_buffers 537 | error('rx_num_transfers must be < rx_num_buffers.'); 538 | end 539 | 540 | if obj.rx_buf_size < 1024 || mod(obj.rx_buf_size, 1024) ~= 0 541 | error('rx_buf_size must be a multiple of 1024.'); 542 | end 543 | 544 | if obj.rx_timeout_ms < 0 545 | error('rx_timeout_ms must be >= 0.'); 546 | end 547 | 548 | if obj.rx_step_size <= 0 549 | error('rx_step_size must be > 0.'); 550 | end 551 | 552 | if obj.rx_samplerate < rx_min_sampling 553 | error(['rx_samplerate must be >= ' (num2str(rx_min_sampling)/1e6) ' MHz.']); 554 | elseif obj.rx_samplerate > 40e6 555 | error(['rx_samplerate must be <= ' (num2str(rx_max_sampling)/1e6) ' MHz.']); 556 | end 557 | 558 | if obj.rx_frequency < rx_min_freq 559 | error(['rx_frequency must be >= ' (num2str(rx_min_freq)/1e6) ' MHz.']); 560 | elseif obj.rx_frequency > rx_max_freq 561 | error(['rx_frequency must be <= ' (num2str(rx_max_freq)/1e6) ' MHz.']); 562 | end 563 | 564 | if length(obj.rx_gain) ~= 2 565 | error ('Gain must be a 2-element array.') 566 | elseif max(obj.rx_gain) > 90 || min(obj.rx_gain) < -10 567 | error('Invalid Gain value. Must be between -10 and 90.') 568 | end 569 | 570 | 571 | %% Validate TX Properties 572 | if obj.tx_num_buffers < 1 573 | error('tx_num_buffers must be > 0.'); 574 | end 575 | 576 | if obj.tx_num_transfers >= obj.tx_num_buffers 577 | error('tx_num_transfers must be < tx_num_transfers'); 578 | end 579 | 580 | if obj.tx_buf_size < 1024 || mod(obj.tx_buf_size, 1024) ~= 0 581 | error('tx_buf_size must be a multiple of 1024.'); 582 | end 583 | 584 | if obj.tx_timeout_ms < 0 585 | error('tx_timeout_ms must be >= 0.'); 586 | end 587 | 588 | if obj.tx_step_size <= 0 589 | error('tx_step_size must be > 0.'); 590 | end 591 | 592 | if obj.tx_samplerate < 160.0e3 593 | error('tx_samplerate must be >= 160 kHz.'); 594 | elseif obj.tx_samplerate > 40e6 595 | error('tx_samplerate must be <= 40 MHz.') 596 | end 597 | 598 | if obj.tx_frequency < tx_min_freq 599 | error(['tx_frequency must be >= ' num2str(tx_min_freq) '.']); 600 | elseif obj.tx_frequency > 3.8e9 601 | error('tx_frequency must be <= 3.8 GHz.'); 602 | end 603 | 604 | if length(obj.tx_gain) ~= 2 605 | error ('Gain must be a 2-element array.') 606 | elseif max(obj.tx_gain) > 60 || min(obj.rx_gain) < -10 607 | error('Invalid Gain value. Must be between -10 and 60.') 608 | end 609 | end 610 | end 611 | end 612 | -------------------------------------------------------------------------------- /bladeRF_MIMO_XCVR.m: -------------------------------------------------------------------------------- 1 | % bladeRF RX/TX control and configuration. 2 | % 3 | % This is a submodule of the bladeRF object. It is not intended to be 4 | % accessed directly, but through the top-level bladeRF object. 5 | % 6 | 7 | % 8 | % Copyright (c) 2015 Nuand LLC 9 | % 10 | % Permission is hereby granted, free of charge, to any person obtaining a copy 11 | % of this software and associated documentation files (the "Software"), to deal 12 | % in the Software without restriction, including without limitation the rights 13 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | % copies of the Software, and to permit persons to whom the Software is 15 | % furnished to do so, subject to the following conditions: 16 | % 17 | % The above copyright notice and this permission notice shall be included in 18 | % all copies or substantial portions of the Software. 19 | % 20 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | % THE SOFTWARE. 27 | % 28 | 29 | %% Control and configuration of transceiver properties 30 | classdef bladeRF_MIMO_XCVR < handle 31 | 32 | properties 33 | config % Stream configuration. See bladeRF_StreamConfig. 34 | corrections % IQ corrections. See bladeRF_IQCorr. 35 | end 36 | 37 | properties(Dependent = true) 38 | agc % Enable automatic gain control ['AUTO', 'MANUAL', 'FAST', 'SLOW'] 39 | gain % Universal gain setting. This value is standardized to output 0dBm when set to 60. 40 | biastee % Control biastee 41 | samplerate % Samplerate. Must be within 160 kHz and 40 MHz. A 2-3 MHz minimum is suggested unless external filters are being used. 42 | frequency % Frequency. Must be within [237.5 MHz, 3.8 GHz] when no XB-200 is attached, or [0 GHz, 3.8 GHz] when an XB-200 is attached. 43 | bandwidth % LPF bandwidth seting. This is rounded to the nearest of the available discrete settings. It is recommended to set this and read back the actual value. 44 | vga1 % VGA1 gain. RX range: [5, 30], TX Range: [-35, -4] 45 | vga2 % VGA2 gain. RX range: [0, 30], TX range: [0, 25] 46 | lna % RX LNA gain. Values: { 'BYPASS', 'MID', 'MAX' } 47 | xb200_filter % XB200 Filter selection. Only valid when an XB200 is attached. Options are: '50M', '144M', '222M', 'AUTO_1DB', 'AUTO_3DB', 'CUSTOM' 48 | mux % FPGA sample FIFO mux mode. Only valid for RX, with options 'BASEBAND_LMS', '12BIT_COUNTER', '32BIT_COUNTER', 'DIGITAL_LOOPBACK' 49 | channel % Channel number 50 | end 51 | 52 | properties(SetAccess = immutable, Hidden=true) 53 | bladerf % Associated bladeRF device handle 54 | module % Module specifier (as a libbladeRF enum) 55 | direction % Module direction: { 'RX', 'TX' } 56 | min_frequency % Lower frequency tuning limit 57 | max_frequency % Higher frequency tuning limit 58 | min_sampling % Lower sampling rate tuning limit 59 | max_sampling % Higher sampling rate tuning limit 60 | xb200_attached; % Modify behavior due to XB200 being attached 61 | end 62 | 63 | properties(SetAccess = private, Hidden=true) 64 | current_channel % Current active channel 65 | end 66 | 67 | properties(SetAccess = private) 68 | running % Denotes whether or not the module is enabled to stream samples. 69 | timestamp % Provides a coarse readback of the timestamp counter. 70 | end 71 | 72 | properties(Access={?bladeRF_MIMO}) 73 | sob = true % TX Start of Burst (Applicable to TX only.) 74 | eob = false % TX end-of-burst flag (Applicable to TX only.) 75 | end 76 | 77 | methods 78 | %% Property handling 79 | 80 | % Samplerate 81 | function set.samplerate(obj, val) 82 | % Create the holding structures 83 | rate = libstruct('bladerf_rational_rate'); 84 | actual = libstruct('bladerf_rational_rate'); 85 | 86 | % Requested rate 87 | rate.integer = floor(val); 88 | [rate.num, rate.den] = rat(mod(val,1)); 89 | 90 | % Set the samplerate 91 | for i = 1:2 92 | [status, ~, ~, actual] = calllib('libbladeRF', 'bladerf_set_rational_sample_rate', obj.bladerf.device, obj.module{i}, rate, rate); 93 | bladeRF.check_status('bladerf_set_rational_sample_rate', status); 94 | 95 | fprintf('Set %s%d samplerate. Requested: %d + %d/%d, Actual: %d + %d/%d\n', ... 96 | obj.direction, ... 97 | i, ... 98 | rate.integer, rate.num, rate.den, ... 99 | actual.integer, actual.num, actual.den); 100 | end 101 | end 102 | 103 | function samplerate_val = get.samplerate(obj) 104 | rate = libstruct('bladerf_rational_rate'); 105 | rate.integer = 0; 106 | rate.num = 0; 107 | rate.den = 1; 108 | 109 | % Get the sample rate from the hardware 110 | [status, ~, rate] = calllib('libbladeRF', 'bladerf_get_rational_sample_rate', obj.bladerf.device, obj.module{1}, rate); 111 | bladeRF.check_status('bladerf_get_rational_sample_rate', status); 112 | 113 | %fprintf('Read %s samplerate: %d + %d/%d\n', ... 114 | % obj.direction, rate.integer, rate.num, rate.den); 115 | 116 | samplerate_val = rate.integer + rate.num / rate.den; 117 | end 118 | 119 | % Frequency 120 | function set.frequency(obj, val) 121 | [status, ~] = calllib('libbladeRF', 'bladerf_set_frequency', obj.bladerf.device, obj.module{1}, val); 122 | bladeRF.check_status('bladerf_set_frequency', status); 123 | actual = obj.frequency; 124 | fprintf('Set %s frequency. Requested: %d, Actual: %d\n', ... 125 | obj.direction, val, actual); 126 | end 127 | 128 | function freq_val = get.frequency(obj) 129 | freq_val = uint32(0); 130 | [status, ~, freq_val] = calllib('libbladeRF', 'bladerf_get_frequency', obj.bladerf.device, obj.module{1}, freq_val); 131 | bladeRF.check_status('bladerf_get_frequency', status); 132 | 133 | %fprintf('Read %s frequency: %f\n', obj.direction, freq_val); 134 | end 135 | 136 | % Configures the LPF bandwidth on the associated module 137 | function set.bandwidth(obj, val) 138 | for i = 1:2 139 | actual = uint32(0); 140 | [status, ~, actual] = calllib('libbladeRF', 'bladerf_set_bandwidth', obj.bladerf.device, obj.module{i}, val, actual); 141 | bladeRF.check_status('bladerf_set_bandwidth', status); 142 | 143 | fprintf('Set %s%d bandwidth. Requested: %f, Actual: %f\n', ... 144 | obj.direction, i, val, actual) 145 | end 146 | end 147 | 148 | % Reads the LPF bandwidth configuration on the associated module 149 | function bw_val = get.bandwidth(obj) 150 | bw_val = uint32(0); 151 | [status, ~, bw_val] = calllib('libbladeRF', 'bladerf_get_bandwidth', obj.bladerf.device, obj.module{1}, bw_val); 152 | bladeRF.check_status('bladerf_get_bandwidth', status); 153 | 154 | %fprintf('Read %s bandwidth: %f\n', obj.direction, bw_val); 155 | end 156 | 157 | % Configures the automatic gain control setting 158 | function set.agc(obj, val) 159 | if strcmpi(obj.direction,'RX') == true 160 | ch = 'BLADERF_CHANNEL_RX1'; 161 | else 162 | ch = 'BLADERF_CHANNEL_TX1'; 163 | end 164 | ch = obj.channel; 165 | 166 | switch lower(val) 167 | case 'auto' 168 | agc_val = 'BLADERF_GAIN_DEFAULT'; 169 | case 'manual' 170 | agc_val = 'BLADERF_GAIN_MGC'; 171 | case 'fast' 172 | agc_val = 'BLADERF_GAIN_FASTATTACK_AGC'; 173 | case 'enable' 174 | agc_val = 'BLADERF_GAIN_SLOWATTACK_AGC'; 175 | case 'slow' 176 | agc_val = 'BLADERF_GAIN_SLOWATTACK_AGC'; 177 | case 'hybrid' 178 | agc_val = 'BLADERF_GAIN_HYBRID_AGC'; 179 | otherwise 180 | error(strcat('Invalid AGC setting: ', val)); 181 | end 182 | for i = 1:2 183 | [status, ~] = calllib('libbladeRF', 'bladerf_set_gain_mode', obj.bladerf.device, ch{i}, agc_val); 184 | if status == -8 185 | if obj.bladerf.info.gen == 1 186 | disp('Cannot enable AGC. AGC DC LUT file is missing, run `cal table agc rx'' in bladeRF-cli.') 187 | end 188 | else 189 | bladeRF.check_status('bladerf_set_gain_mode', status); 190 | end 191 | end 192 | fprintf('Set AGC. Requested: %s, Actual: %s\n', ... 193 | val, obj.agc) 194 | end 195 | 196 | % Reads the current automatic gain control setting 197 | function val = get.agc(obj) 198 | val = int32(0); 199 | if strcmpi(obj.direction,'RX') == true 200 | ch = 'BLADERF_CHANNEL_RX1'; 201 | else 202 | ch = 'BLADERF_CHANNEL_TX1'; 203 | end 204 | ch = obj.channel; 205 | 206 | tmp = int32(0); 207 | [status, ~, mode] = calllib('libbladeRF', 'bladerf_get_gain_mode', obj.bladerf.device, ch{1}, tmp); 208 | bladeRF.check_status('bladerf_get_gain_mode', status); 209 | 210 | switch mode 211 | case 'BLADERF_GAIN_DEFAULT' 212 | val = 'auto'; 213 | case 'BLADERF_GAIN_MGC' 214 | val = 'manual'; 215 | case 'BLADERF_GAIN_FASTATTACK_AGC' 216 | val = 'fast'; 217 | case 'BLADERF_GAIN_SLOWATTACK_AGC' 218 | val = 'slow'; 219 | case 'BLADERF_GAIN_HYBRID_AGC' 220 | val = 'hybrid'; 221 | otherwise 222 | error(strcat('Invalid AGC setting: ', val)); 223 | end 224 | %fprintf('Read %s gain: %d\n', obj.direction, val); 225 | end 226 | 227 | % Configures active channel setting 228 | function set.channel(obj, val) 229 | if (strcmpi(val,'RX') == true || strcmpi(val, 'RX1') || strcmp(val, 'BLADERF_CHANNEL_RX1')) 230 | channel = {'BLADERF_CHANNEL_RX1', 'BLADERF_CHANNEL_RX2'}; 231 | % elseif (strcmpi(val, 'RX2') || strcmp(val, 'BLADERF_CHANNEL_RX2')) 232 | % channel = 'BLADERF_CHANNEL_RX2'; 233 | elseif (strcmpi(val,'TX') == true || strcmpi(val, 'TX1') || strcmp(val, 'BLADERF_CHANNEL_TX1')) 234 | channel = {'BLADERF_CHANNEL_TX1', 'BLADERF_CHANNEL_TX2'}; 235 | % elseif (strcmpi(val, 'TX2') || strcmp(val, 'BLADERF_CHANNEL_TX2')) 236 | % channel = 'BLADERF_CHANNEL_TX2'; 237 | end 238 | 239 | obj.current_channel = channel; 240 | 241 | % if obj.running 242 | % [status, ~] = calllib('libbladeRF', 'bladerf_enable_module', ... 243 | % obj.bladerf.device, ... 244 | % obj.current_channel, ... 245 | % false); 246 | % 247 | % bladeRF.check_status('bladerf_enable_module', status); 248 | % 249 | % 250 | % [status, ~] = calllib('libbladeRF', 'bladerf_enable_module', ... 251 | % obj.bladerf.device, ... 252 | % channel, ... 253 | % true); 254 | % 255 | % bladeRF.check_status('bladerf_enable_module', status); 256 | % end 257 | 258 | end 259 | 260 | % Reads the current active channel setting 261 | function val = get.channel(obj) 262 | val = obj.current_channel; 263 | end 264 | 265 | % Configures the universal gain 266 | function set.gain(obj, val) 267 | % TODO: Check Gain assigment. Only the first value is being 268 | % writen 269 | assert(length(val) == 2, "Input must be a 2 element array. i.e. [60, 40]") 270 | 271 | if strcmpi(obj.direction,'RX') == true && strcmpi(obj.agc,'manual') == 0 272 | warning(['Cannot set ' obj.direction ' gain when AGC is in ' obj.agc ' mode']) 273 | end 274 | 275 | % if strcmpi(obj.direction,'RX') == true 276 | % ch = 'BLADERF_CHANNEL_RX1'; 277 | % else 278 | % ch = 'BLADERF_CHANNEL_TX1'; 279 | % end 280 | for i = 1:2 281 | [status, ~] = calllib('libbladeRF', 'bladerf_set_gain', obj.bladerf.device, obj.channel{i}, val(i)); 282 | bladeRF.check_status('bladerf_set_gain', status); 283 | end 284 | 285 | fprintf('Set %s%d bandwidth. Requested: [%d, %d], Actual: [%d, %d]\n', ... 286 | obj.direction, i, val, obj.gain) 287 | end 288 | 289 | % Reads the current universal gain configuration 290 | function vals = get.gain(obj) 291 | val = int32(0); 292 | if strcmpi(obj.direction,'RX') == true 293 | ch = 'BLADERF_CHANNEL_RX1'; 294 | else 295 | ch = 'BLADERF_CHANNEL_TX1'; 296 | end 297 | ch = obj.channel; 298 | 299 | tmp = int32(0); 300 | vals = zeros(1,2); 301 | for i = 1:2 302 | [status, ~, val] = calllib('libbladeRF', 'bladerf_get_gain', obj.bladerf.device, ch{i}, tmp); 303 | bladeRF.check_status('bladerf_get_gain', status); 304 | vals(i) = val; 305 | end 306 | %fprintf('Read %s gain: %d\n', obj.direction, val); 307 | end 308 | 309 | % Configures the gain of VGA1 310 | function set.vga1(obj, val) 311 | if strcmpi(obj.direction,'RX') == true 312 | [status, ~] = calllib('libbladeRF', 'bladerf_set_rxvga1', obj.bladerf.device, val); 313 | else 314 | [status, ~] = calllib('libbladeRF', 'bladerf_set_txvga1', obj.bladerf.device, val); 315 | end 316 | 317 | bladeRF.check_status('bladerf_set_vga1', status); 318 | 319 | %fprintf('Set %s VGA1: %d\n', obj.direction, val); 320 | end 321 | 322 | % Reads the current VGA1 gain configuration 323 | function val = get.vga1(obj) 324 | if obj.bladerf.info.gen ~= 1 325 | val = 0; 326 | return 327 | end 328 | 329 | val = int32(0); 330 | if strcmpi(obj.direction,'RX') == true 331 | [status, ~, val] = calllib('libbladeRF', 'bladerf_get_rxvga1', obj.bladerf.device, val); 332 | else 333 | [status, ~, val] = calllib('libbladeRF', 'bladerf_get_txvga1', obj.bladerf.device, val); 334 | end 335 | 336 | bladeRF.check_status('bladerf_get_vga1', status); 337 | 338 | %fprintf('Read %s VGA1: %d\n', obj.direction, val); 339 | end 340 | 341 | % Configures the gain of VGA2 342 | function set.vga2(obj, val) 343 | if strcmpi(obj.direction,'RX') == true 344 | [status, ~] = calllib('libbladeRF', 'bladerf_set_rxvga2', obj.bladerf.device, val); 345 | else 346 | [status, ~] = calllib('libbladeRF', 'bladerf_set_txvga2', obj.bladerf.device, val); 347 | end 348 | 349 | bladeRF.check_status('bladerf_set_vga2', status); 350 | 351 | %fprintf('Set %s VGA2: %d\n', obj.direction, obj.vga2); 352 | end 353 | 354 | % Reads the current VGA2 configuration 355 | function val = get.vga2(obj) 356 | if obj.bladerf.info.gen ~= 1 357 | val = 0; 358 | return 359 | end 360 | 361 | val = int32(0); 362 | if strcmpi(obj.direction,'RX') == true 363 | [status, ~, val] = calllib('libbladeRF', 'bladerf_get_rxvga2', obj.bladerf.device, val); 364 | else 365 | [status, ~, val] = calllib('libbladeRF', 'bladerf_get_txvga2', obj.bladerf.device, val); 366 | end 367 | 368 | bladeRF.check_status('bladerf_get_vga2', status); 369 | 370 | %fprintf('Read %s VGA2: %d\n', obj.direction, val); 371 | end 372 | 373 | % Configure the RX LNA gain 374 | function set.lna(obj, val) 375 | if strcmpi(obj.direction,'TX') == true 376 | error('LNA gain is not applicable to the TX path'); 377 | end 378 | 379 | valid_value = true; 380 | 381 | if isnumeric(val) 382 | switch val 383 | case 0 384 | lna_val = 'BLADERF_LNA_GAIN_BYPASS'; 385 | 386 | case 3 387 | lna_val = 'BLADERF_LNA_GAIN_MID'; 388 | 389 | case 6 390 | lna_val = 'BLADERF_LNA_GAIN_MAX'; 391 | 392 | otherwise 393 | valid_value = false; 394 | end 395 | else 396 | if strcmpi(val,'bypass') == true 397 | lna_val = 'BLADERF_LNA_GAIN_BYPASS'; 398 | elseif strcmpi(val, 'mid') == true 399 | lna_val = 'BLADERF_LNA_GAIN_MID'; 400 | elseif strcmpi(val, 'max') == true 401 | lna_val = 'BLADERF_LNA_GAIN_MAX'; 402 | else 403 | valid_value = false; 404 | end 405 | end 406 | 407 | if valid_value ~= true 408 | error('Valid LNA values are [''BYPASS'', ''MID'', ''MAX''] or [0, 3, 6], respectively.'); 409 | else 410 | [status, ~] = calllib('libbladeRF', 'bladerf_set_lna_gain', obj.bladerf.device, lna_val); 411 | bladeRF.check_status('bladerf_set_lna_gain', status); 412 | 413 | %fprintf('Set RX LNA gain to: %s\n', lna_val); 414 | end 415 | end 416 | 417 | % Read current RX LNA gain setting 418 | function val = get.lna(obj) 419 | if obj.bladerf.info.gen ~= 1 420 | val = 0; 421 | return 422 | end 423 | if strcmpi(obj.direction,'TX') == true 424 | error('LNA gain is not applicable to the TX path'); 425 | end 426 | 427 | val = 0; 428 | [status, ~, lna] = calllib('libbladeRF', 'bladerf_get_lna_gain', obj.bladerf.device, val); 429 | bladeRF.check_status('bladerf_get_lna_gain', status); 430 | 431 | if strcmpi(lna, 'BLADERF_LNA_GAIN_BYPASS') == true 432 | val = 'BYPASS'; 433 | elseif strcmpi(lna, 'BLADERF_LNA_GAIN_MID') == true 434 | val = 'MID'; 435 | elseif strcmpi(lna, 'BLADERF_LNA_GAIN_MAX') == true 436 | val = 'MAX'; 437 | else 438 | val = 'UNKNOWN'; 439 | end 440 | 441 | %fprintf('Got RX LNA gain: %s\n', val); 442 | end 443 | 444 | % Configures the bias tee 445 | function set.biastee(obj, val) 446 | if strcmpi(obj.direction,'RX') == true 447 | ch = 'BLADERF_CHANNEL_RX1'; 448 | else 449 | ch = 'BLADERF_CHANNEL_TX1'; 450 | end 451 | 452 | [status, ~] = calllib('libbladeRF', 'bladerf_set_bias_tee', obj.bladerf.device, ch, val); 453 | 454 | %fprintf('Set %s biastee: %d\n', obj.direction, obj.vga2); 455 | end 456 | 457 | % Reads the current bias tee configuration 458 | function val = get.biastee(obj) 459 | if strcmpi(obj.direction,'RX') == true 460 | ch = 'BLADERF_CHANNEL_RX1'; 461 | else 462 | ch = 'BLADERF_CHANNEL_TX1'; 463 | end 464 | tmp = int32(0); 465 | [status, ~, val] = calllib('libbladeRF', 'bladerf_get_bias_tee', obj.bladerf.device, ch, tmp); 466 | 467 | %fprintf('Get %s biastee: %d\n', obj.direction, val); 468 | end 469 | 470 | % Read the timestamp counter from the associated module 471 | function val = get.timestamp(obj) 472 | val = uint64(0); 473 | actualDirection = ''; 474 | if strcmpi(obj.current_channel{1}, 'BLADERF_CHANNEL_TX1') == true || strcmpi(obj.current_channel{2}, 'BLADERF_CHANNEL_TX2') == true 475 | actualDirection = 'BLADERF_TX'; 476 | else 477 | actualDirection = 'BLADERF_RX'; 478 | end 479 | [status, ~, val] = calllib('libbladeRF', 'bladerf_get_timestamp', obj.bladerf.device, actualDirection, val); 480 | %[status, ~, val] = calllib('libbladeRF', 'bladerf_get_timestamp', obj.bladerf.device, obj.module, val); 481 | bladeRF.check_status('bladerf_get_timestamp', status); 482 | end 483 | 484 | % Set the current XB200 filter 485 | function set.xb200_filter(obj, filter) 486 | if obj.xb200_attached == false 487 | error('Cannot set XB200 filter because the handle was not initialized for use with the XB200.'); 488 | end 489 | 490 | filter = upper(filter); 491 | switch filter 492 | case '50M' 493 | case '144M' 494 | case '222M' 495 | case 'AUTO_1DB' 496 | case 'AUTO_3DB' 497 | case 'CUSTOM' 498 | otherwise 499 | error(['Invalid XB200 filter: ' filter]); 500 | end 501 | 502 | filter_val = ['BLADERF_XB200_' filter ]; 503 | for i = 1:2 504 | status = calllib('libbladeRF', 'bladerf_xb200_set_filterbank', obj.bladerf.device, obj.module{i}, filter_val); 505 | bladeRF.check_status('bladerf_xb200_set_filterbank', status); 506 | end 507 | end 508 | 509 | % Get the current XB200 filter 510 | function filter_val = get.xb200_filter(obj) 511 | if obj.xb200_attached == false 512 | filter_val = 'N/A'; 513 | return; 514 | end 515 | 516 | filter_val = 0; 517 | [status, ~, filter_val] = calllib('libbladeRF', 'bladerf_xb200_get_filterbank', obj.bladerf.device, obj.module{1}, filter_val); 518 | bladeRF.check_status('bladerf_xb200_get_filterbank', status); 519 | 520 | filter_val = strrep(filter_val, 'BLADERF_XB200_', ''); 521 | end 522 | 523 | % Set the RX mux mode setting 524 | function set.mux(obj, mode) 525 | if strcmpi(obj.direction, 'TX') == true 526 | error('FPGA sample mux mode configuration is only applicable to the RX module.'); 527 | end 528 | 529 | mode = upper(mode); 530 | switch mode 531 | case { 'BASEBAND_LMS', '12BIT_COUNTER', '32BIT_COUNTER', 'DIGITAL_LOOPBACK' } 532 | mode = ['BLADERF_RX_MUX_' mode ]; 533 | otherwise 534 | error(['Invalid RX mux mode: ' mode]); 535 | end 536 | 537 | status = calllib('libbladeRF', 'bladerf_set_rx_mux', obj.bladerf.device, mode); 538 | bladeRF.check_status('bladerf_set_rx_mux', status); 539 | end 540 | 541 | % Get the current RX mux mode setting 542 | function mode = get.mux(obj) 543 | if strcmpi(obj.direction, 'TX') == true 544 | error('FPGA sample mux mode configuration is only applicable to the RX module.'); 545 | end 546 | 547 | mode = 'BLADERF_RX_MUX_INVALID'; 548 | 549 | [status, ~, mode] = calllib('libbladeRF', 'bladerf_get_rx_mux', obj.bladerf.device, mode); 550 | bladeRF.check_status('bladerf_get_rx_mux', status); 551 | 552 | mode = strrep(mode, 'BLADERF_RX_MUX_', ''); 553 | end 554 | 555 | % Constructor 556 | function obj = bladeRF_MIMO_XCVR(dev, dir, xb) 557 | if strcmpi(dir,'RX') == false && strcmpi(dir,'TX') == false 558 | error('Invalid direction specified'); 559 | end 560 | 561 | % Set the direction of the transceiver 562 | obj.direction = dir; 563 | obj.bladerf = dev; 564 | 565 | obj.channel = dir; 566 | obj.module = obj.current_channel; 567 | 568 | if strcmpi(xb, 'XB200') == true 569 | obj.min_frequency = 0; 570 | obj.xb200_attached = true; 571 | obj.xb200_filter = 'AUTO_3DB'; 572 | else 573 | obj.min_frequency = 237.5e6; 574 | obj.xb200_attached = false; 575 | end 576 | 577 | % Setup defaults 578 | fprintf('Initializing %s with default parameters\n', obj.direction) 579 | obj.config = bladeRF_StreamConfig; 580 | obj.samplerate = 1e6; 581 | obj.frequency = 890e6; 582 | obj.bandwidth = 1e6; 583 | 584 | freqrange = libstruct('bladerf_range'); 585 | 586 | status = calllib('libbladeRF', 'bladerf_get_frequency_range', obj.bladerf.device, obj.module{1}, freqrange); 587 | bladeRF.check_status('bladerf_get_frequency_range', status); 588 | obj.min_frequency = freqrange.min; 589 | obj.max_frequency = freqrange.max; 590 | 591 | samplerange = libstruct('bladerf_range'); 592 | 593 | status = calllib('libbladeRF', 'bladerf_get_sample_rate_range', obj.bladerf.device, obj.module{1}, samplerange); 594 | bladeRF.check_status('bladerf_get_frequency_range', status); 595 | obj.min_sampling = samplerange.min; 596 | obj.max_sampling = samplerange.max; 597 | 598 | if strcmpi(dir,'RX') == true 599 | for i = 1:2 600 | status = calllib('libbladeRF', 'bladerf_set_gain_mode', obj.bladerf.device, obj.module{i}, 'BLADERF_GAIN_DEFAULT'); 601 | if status == -8 602 | if obj.bladerf.info.gen == 1 603 | disp('Cannot enable AGC. AGC DC LUT file is missing, run `cal table agc rx'' in bladeRF-cli.') 604 | end 605 | else 606 | bladeRF.check_status('bladerf_set_gain_mode', status); 607 | end 608 | 609 | gainmode = int32(0); 610 | [status, ~, gainmode] = calllib('libbladeRF', 'bladerf_get_gain_mode', obj.bladerf.device, obj.module{i}, gainmode); 611 | end 612 | end 613 | 614 | 615 | if dev.info.gen == 1 616 | if strcmpi(dir, 'RX') == true 617 | obj.vga1 = 30; 618 | obj.vga2 = 0; 619 | obj.lna = 'MAX'; 620 | else 621 | obj.vga1 = -8; 622 | obj.vga2 = 16; 623 | end 624 | end 625 | 626 | obj.corrections = bladeRF_IQCorr(dev, obj.module{1}, 0, 0, 0, 0); 627 | obj.running = false; 628 | end 629 | 630 | 631 | function start(obj) 632 | % Apply stream configuration parameters and enable the module. 633 | % 634 | % bladeRF.rx.start() or bladeRF.tx.start(). 635 | % 636 | %fprintf('Starting %s stream.\n', obj.direction); 637 | 638 | obj.running = true; 639 | obj.config.lock(); 640 | 641 | % If we're starting up a TX module, reset our cached EOB/SOB 642 | % flags so that we can internally take care of these if the 643 | % user doesn't want to worry about them. 644 | if strcmpi(obj.direction,'TX') == true 645 | obj.sob = true; 646 | obj.eob = false; 647 | layout = 3; %'BLADERF_TX_X2'; 648 | else 649 | layout = 2; %'BLADERF_RX_X2'; 650 | end 651 | 652 | % Configure the sync config 653 | [status, ~] = calllib('libbladeRF', 'bladerf_sync_config', ... 654 | obj.bladerf.device, ... 655 | layout, ... %obj.module, ... 656 | 'BLADERF_FORMAT_SC16_Q11_META', ... 657 | obj.config.num_buffers, ... 658 | obj.config.buffer_size, ... 659 | obj.config.num_transfers, ... 660 | obj.config.timeout_ms); 661 | 662 | bladeRF.check_status('bladerf_sync_config', status); 663 | 664 | % Enable the module 665 | for i = 1:2 666 | [status, ~] = calllib('libbladeRF', 'bladerf_enable_module', ... 667 | obj.bladerf.device, ... 668 | obj.current_channel{i}, ... 669 | true); 670 | 671 | bladeRF.check_status('bladerf_enable_module', status); 672 | end 673 | end 674 | 675 | function stop(obj) 676 | % Stop streaming and disable the module 677 | % 678 | % bladeRF.rx.stop() or bladeRF.tx.stop(). 679 | % 680 | %fprintf('Stopping %s module.\n', obj.direction); 681 | 682 | % If the user is trying top stop "mid-burst", we'll want to 683 | % end the burst to ensure the TX DAC is reset to 0+0j 684 | if strcmpi(obj.direction,'TX') == true 685 | if obj.sob == false && obj.eob == false 686 | obj.eob = true; 687 | obj.bladerf.transmit([0 0], 0, obj.sob, obj.eob); 688 | 689 | % Ensure these zeros are transmitted by waiting for 690 | % any remaining data in buffers to flush 691 | max_buffered = obj.bladerf.tx.config.num_buffers * ... 692 | obj.bladerf.tx.config.buffer_size; 693 | 694 | target_time = obj.bladerf.tx.timestamp + ... 695 | max_buffered; 696 | 697 | while obj.bladerf.tx.timestamp <= target_time 698 | pause(1e-3); 699 | end 700 | end 701 | end 702 | 703 | % Disable the module 704 | for i = 1:2 705 | [status, ~] = calllib('libbladeRF', 'bladerf_enable_module', ... 706 | obj.bladerf.device, ... 707 | obj.current_channel{i}, ... 708 | false); 709 | 710 | bladeRF.check_status('bladerf_enable_module', status); 711 | end 712 | 713 | % Unlock the configuration for changing 714 | obj.config.unlock(); 715 | obj.running = false; 716 | end 717 | end 718 | end 719 | -------------------------------------------------------------------------------- /bladeRF_MIMO.m: -------------------------------------------------------------------------------- 1 | % bladeRF MATLAB interface 2 | % 3 | % This object is a MATLAB wrapper around libbladeRF. As such, much 4 | % of the documentation for the libbladeRF API is applicable. It may be 5 | % found here: 6 | % http://www.nuand.com/libbladeRF-doc 7 | % 8 | % The below series of steps illustrates how to perform a simple reception. 9 | % However, the process for configuring the device for transmission is 10 | % largely the same. Note the same device handle may be used to transmit and 11 | % receive. 12 | % 13 | % (1) Open a device handle: 14 | % 15 | % b = bladeRF('*:serial=43b'); % Open device via first 3 serial # digits 16 | % 17 | % (2) Setup device parameters. These may be changed while the device 18 | % is actively streaming. 19 | % 20 | % b.rx.frequency = 917.45e6; 21 | % b.rx.samplerate = 5e6; 22 | % b.rx.bandwidth = 2.5e6; 23 | % b.rx.lna = 'MAX'; 24 | % b.rx.vga1 = 30; 25 | % b.rx.vga2 = 5; 26 | % 27 | % (3) Setup stream parameters. These may NOT be changed while the device 28 | % is streaming. 29 | % 30 | % b.rx.config.num_buffers = 64; 31 | % b.rx.config.buffer_size = 16384; 32 | % b.rx.config.num_transfers = 16; 33 | % b.rx.timeout_ms = 5000; 34 | % 35 | % 36 | % (4) Start the module 37 | % 38 | % b.rx.start(); 39 | % 40 | % (5) Receive 0.250 seconds of samples 41 | % 42 | % samples = b.receive(0.250 * b.rx.samplerate); 43 | % 44 | % (6) Cleanup and shutdown by stopping the RX stream and having MATLAB 45 | % delete the handle object. 46 | % 47 | % b.rx.stop(); 48 | % clear b; 49 | % 50 | % 51 | % Below is a list of submodules within the bladeRF handle. See the help 52 | % text of each of these for the properties and methods exposed by modules. 53 | % 54 | % See also: bladeRF_MIMO_XCVR, bladeRF_VCTCXO, bladeRF_StreamConfig, bladeRF_IQCorr 55 | 56 | % Copyright (c) 2015 Nuand LLC 57 | % 58 | % Permission is hereby granted, free of charge, to any person obtaining a copy 59 | % of this software and associated documentation files (the "Software"), to deal 60 | % in the Software without restriction, including without limitation the rights 61 | % to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 62 | % copies of the Software, and to permit persons to whom the Software is 63 | % furnished to do so, subject to the following conditions: 64 | % 65 | % The above copyright notice and this permission notice shall be included in 66 | % all copies or substantial portions of the Software. 67 | % 68 | % THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 69 | % IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 70 | % FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 71 | % AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 72 | % LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 73 | % OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 74 | % THE SOFTWARE. 75 | % 76 | 77 | %% Top-level bladeRF object 78 | classdef bladeRF_MIMO < handle 79 | 80 | % Read-only handle properties 81 | properties(Access={?bladeRF_MIMO_XCVR, ?bladeRF_IQCorr, ?bladeRF_VCTCXO, ?bladeRF_Misc}) 82 | device % Device handle 83 | end 84 | 85 | properties 86 | rx % Receive chain 87 | tx % Transmit chain 88 | vctcxo % VCTCXO control 89 | misc % Misc 90 | end 91 | 92 | properties(Dependent) 93 | loopback % Loopback mode. Available options: 'NONE', 'FIRMWARE', 'BB_TXLPF_RXVGA2', 'BB_TXVGA1_RXVGA2', 'BB_TXLPF_RXLPF', 'BB_TXVGA1_RXLPF', 'RF_LNA1', 'RF_LNA2', 'RF_LNA3' 94 | end 95 | 96 | properties(SetAccess=immutable) 97 | info % Information about device properties and state 98 | versions % Device and library version information 99 | xb % Attached expansion board 100 | end 101 | 102 | properties(Access=private) 103 | initialized = false; 104 | end 105 | 106 | methods(Static, Hidden) 107 | % Test the libbladeRF status code and error out if it is != 0 108 | function check_status(fn, status) 109 | if status ~= 0 110 | err_num = num2str(status); 111 | err_str = calllib('libbladeRF', 'bladerf_strerror', status); 112 | str = [ 'libbladeRF error (' err_num ') in ' fn '(): ' err_str]; 113 | if status == -8 114 | warning(str); 115 | else 116 | error(str); 117 | end 118 | end 119 | end 120 | end 121 | 122 | methods(Static) 123 | 124 | function [val] = str2lna(str) 125 | % Convert RX LNA setting string to its associated numeric value. 126 | % 127 | % val = bladeRF.str2lna('MAX'); 128 | % 129 | if strcmpi(str, 'MAX') == 1 130 | val = 6; 131 | elseif strcmpi(str, 'MID') == 1 132 | val = 3; 133 | elseif strcmpi(str, 'BYPASS') == 1 134 | val = 0; 135 | else 136 | error('Invalid RX LNA string provided') 137 | end 138 | end 139 | 140 | function [major, minor, patch] = version() 141 | % Get the version of this MATLAB libbladeRF wrapper. 142 | % 143 | % [major, minor, patch] = bladeRF.version() 144 | % 145 | major = 0; 146 | minor = 1; 147 | patch = 2; 148 | end 149 | 150 | function [major, minor, patch, version_string] = library_version() 151 | % Get the libbladeRF version being used. 152 | % 153 | % [major, minor, patch, version_string] = bladeRF.library_version() 154 | % 155 | bladeRF_MIMO.load_library(); 156 | 157 | [~, ver_ptr] = bladeRF_MIMO.empty_version(); 158 | calllib('libbladeRF', 'bladerf_version', ver_ptr); 159 | major = ver_ptr.Value.major; 160 | minor = ver_ptr.Value.minor; 161 | patch = ver_ptr.Value.patch; 162 | version_string = ver_ptr.Value.describe; 163 | end 164 | 165 | function devs = devices 166 | % Probe the system for attached bladeRF devices. 167 | % 168 | % [device_list] = bladeRF.devices(); 169 | % 170 | bladeRF.load_library(); 171 | pdevlist = libpointer('bladerf_devinfoPtr'); 172 | [count, ~] = calllib('libbladeRF', 'bladerf_get_device_list', pdevlist); 173 | if count < 0 174 | error('bladeRF:devices', ['Error retrieving devices: ', calllib('libbladeRF', 'bladerf_strerror', count)]); 175 | end 176 | 177 | if count > 0 178 | devs = repmat(struct('backend', [], 'serial', [], 'usb_bus', [], 'usb_addr', [], 'instance', [], 'manufacturer', [], 'product', []), 1, count); 179 | for x = 0:(count-1) 180 | ptr = pdevlist+x; 181 | devs(x+1) = ptr.Value; 182 | devs(x+1).serial = char(devs(x+1).serial(1:end-1)); 183 | end 184 | else 185 | devs = []; 186 | end 187 | 188 | calllib('libbladeRF', 'bladerf_free_device_list', pdevlist); 189 | end 190 | 191 | function log_level(level) 192 | % Set libbladeRF's log level. 193 | % 194 | % bladeRF.log_level(level_string) 195 | % 196 | % Options for level_string are: 197 | % 'verbose' 198 | % 'debug' 199 | % 'info' 200 | % 'error' 201 | % 'warning' 202 | % 'critical' 203 | % 'silent' 204 | % 205 | level = lower(level); 206 | 207 | switch level 208 | case 'verbose' 209 | enum_val = 'BLADERF_LOG_LEVEL_VERBOSE'; 210 | case 'debug' 211 | enum_val = 'BLADERF_LOG_LEVEL_DEBUG'; 212 | case 'info' 213 | enum_val = 'BLADERF_LOG_LEVEL_INFO'; 214 | case 'warning' 215 | enum_val = 'BLADERF_LOG_LEVEL_WARNING'; 216 | case 'error' 217 | enum_val = 'BLADERF_LOG_LEVEL_ERROR'; 218 | case 'critical' 219 | enum_val = 'BLADERF_LOG_LEVEL_CRITICAL'; 220 | case 'silent' 221 | enum_val = 'BLADERF_LOG_LEVEL_SILENT'; 222 | otherwise 223 | error('Invalid log level'); 224 | end 225 | 226 | bladeRF_MIMO.load_library(); 227 | calllib('libbladeRF', 'bladerf_log_set_verbosity', enum_val); 228 | end 229 | 230 | function build_thunk 231 | % Build the MATLAB thunk library for use with the bladeRF MATLAB wrapper 232 | % 233 | % bladeRF.build_thunk(); 234 | % 235 | % This function is intended to provide developers with the means to 236 | % regenerate the libbladeRF_thunk_. files. 237 | % Users of pre-built binaries need not concern themselves with this 238 | % function. 239 | % 240 | % Use of this function requires that: 241 | % - The system contains a MATLAB-compatible compiler 242 | % - Any required libraries, DLLs, headers, etc. are in the search path or current working directory. 243 | % 244 | % For Windows users, the simplest approach is to copy the following 245 | % to this directory: 246 | % - libbladeRF.h 247 | % - bladeRF1.h 248 | % - bladeRF2.h 249 | % - bladeRF.dll 250 | % - libusb-1.0.dll and/or CyUSB.dll 251 | % - pthreadVC2.dll 252 | % 253 | % Linux users will need to copy libbladeRF.h to this directory 254 | % and add the following to it, after "#define BLADERF_H_" 255 | % 256 | % #define MATLAB_LINUX_THUNK_BUILD_ 257 | % 258 | if libisloaded('libbladeRF') == true 259 | unloadlibrary('libbladeRF'); 260 | end 261 | 262 | arch = computer('arch'); 263 | this_dir = pwd; 264 | proto_output = 'delete_this_file'; 265 | switch arch 266 | case 'win64' 267 | [notfound, warnings] = loadlibrary('bladeRF', 'libbladeRF.h', 'includepath', this_dir, 'addheader', 'stdbool.h', 'addheader', 'bladeRF1.h', 'addheader', 'bladeRF2.h', 'alias', 'libbladeRF', 'notempdir', 'mfilename', proto_output); 268 | case 'glnxa64' 269 | [notfound, warnings] = loadlibrary('libbladeRF', 'libbladeRF.h', 'includepath', this_dir, 'notempdir', 'mfilename', proto_output); 270 | case 'maci64' 271 | [notfound, warnings] = loadlibrary('libbladeRF.dylib', 'libbladeRF.h', 'includepath', this_dir, 'notempdir', 'mfilename', proto_output); 272 | otherwise 273 | error(strcat('Unsupported architecture: ', arch)) 274 | end 275 | 276 | % Remove the generated protofile that we don't want folks to 277 | % use. Our hand-crafted protofile contains workarounds for 278 | % loadlibrary() issues that the autogenerated file does not. 279 | delete 'delete_this_file.m' 280 | 281 | if isempty(notfound) == false 282 | fprintf('\nMissing functions:\n'); 283 | disp(notfound.'); 284 | error('Failed to find the above functions in libbladeRF.'); 285 | end 286 | 287 | if isempty(warnings) == false 288 | warning('Encountered the following warnings while loading libbladeRF:\n%s\n', warnings); 289 | end 290 | end 291 | end 292 | 293 | methods(Static, Access = private) 294 | function constructor_cleanup(obj) 295 | if obj.initialized == false 296 | calllib('libbladeRF', 'bladerf_close', obj.device); 297 | obj.device = []; 298 | end 299 | end 300 | 301 | function load_library 302 | if libisloaded('libbladeRF') == false 303 | arch = computer('arch'); 304 | switch arch 305 | case 'win64' 306 | [notfound, warnings] = loadlibrary('bladeRF', @libbladeRF_proto, 'alias', 'libbladeRF'); 307 | case 'glnxa64' 308 | [notfound, warnings] = loadlibrary('libbladeRF', @libbladeRF_proto); 309 | case 'maci64' 310 | [notfound, warnings] = loadlibrary('libbladeRF.dylib', @libbladeRF_proto); 311 | otherwise 312 | error(strcat('Unsupported architecture: ', arch)) 313 | end 314 | 315 | if isempty(notfound) == false 316 | error('Failed to find functions in libbladeRF.'); 317 | end 318 | 319 | if isempty(warnings) == false 320 | warning('Encountered the following warnings while loading libbladeRF:\n%s\n', warnings); 321 | end 322 | end 323 | end 324 | 325 | % Get an empty version structure initialized to known values 326 | function [ver, ver_ptr] = empty_version 327 | ver = libstruct('bladerf_version'); 328 | ver.major = 0; 329 | ver.minor = 0; 330 | ver.patch = 0; 331 | ver.describe = 'Unknown'; 332 | 333 | ver_ptr = libpointer('bladerf_version', ver); 334 | end 335 | end 336 | 337 | methods 338 | function obj = bladeRF_MIMO(devstring, fpga_bitstream, xb) 339 | % Create a device handle to the bladeRF specified by devstring. 340 | % 341 | % device = bladeRF(devstring) 342 | % 343 | % The syntax for devstring may be found in the libbladeRF 344 | % documentation for the bladerf_open() function. If devstring 345 | % is not provided or is empty, the first available device will be 346 | % used. 347 | % 348 | % If multiple devices are present, it is helpful to open then via 349 | % their serial numbers. This can be done by specifying at least 350 | % 3 characters from their serial number: 351 | % 352 | % device = bladeRF('*:serial=f39') 353 | % 354 | % Note that there can only be one active handle to a device at any 355 | % given time. The device will be closed when all references to the 356 | % device handle are cleared from the workspace. 357 | % 358 | % If FPGA autoloading [1] is not being used to have libbladeRF 359 | % automatically load FPGA images, an FPGA bitstream filename must 360 | % be provided to the constructor. For example, with a bladeRF x115: 361 | % 362 | % device = bladeRF(devstring, 'path/to/hostedx115.rbf'); 363 | % 364 | % [1] https://github.com/Nuand/bladeRF/wiki/FPGA-Autoloading#Host_softwarebased 365 | % 366 | % To enable an expansion board, the xb parameter should be 367 | % specified: 368 | % 369 | % device = bladeRF(devstring, [], 'xb200'); 370 | % 371 | % Valid xb arguments are: 'xb100', 'xb200' 372 | % 373 | bladeRF_MIMO.load_library(); 374 | 375 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 376 | % Open the device 377 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 378 | 379 | % Use wildcard empty string if no device string was provided 380 | if nargin < 1 381 | devstring = ''; 382 | end 383 | 384 | % Don't attempt to load an FPGA unless specified 385 | if nargin < 2 386 | fpga_bitstream = []; 387 | end 388 | 389 | % Don't attempt to attach an expansion board unless specified 390 | if nargin < 3 391 | xb = []; 392 | end 393 | 394 | dptr = libpointer('bladerfPtr'); 395 | status = calllib('libbladeRF', 'bladerf_open', dptr, devstring); 396 | 397 | % Check the return value 398 | bladeRF_MIMO.check_status('bladeRF_open', status); 399 | 400 | % Save off the device pointer 401 | obj.device = dptr; 402 | 403 | % Ensure we close the device handle if we error out anywhere 404 | % in this constructor. 405 | cleanupObj = onCleanup(@() bladeRF_MIMO.constructor_cleanup(obj)); 406 | 407 | if ~isempty(fpga_bitstream) 408 | status = calllib('libbladeRF', 'bladerf_load_fpga', ... 409 | obj.device, fpga_bitstream); 410 | 411 | bladeRF_MIMO.check_status('bladerf_load_fpga', status); 412 | end 413 | 414 | % Verify we have an FPGA loaded before continuing. 415 | status = calllib('libbladeRF', ... 416 | 'bladerf_is_fpga_configured', obj.device); 417 | 418 | if status < 0 419 | bladeRF_MIMO.check_status('bladerf_is_fpga_configured', status); 420 | elseif status == 0 421 | error(['No bladeRF FPGA bitstream is loaded. Place one' ... 422 | ' in an autoload location, or pass the filename' ... 423 | ' to the bladeRF constructor']); 424 | end 425 | 426 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 427 | % Populate version information 428 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 429 | [obj.versions.matlab.major, ... 430 | obj.versions.matlab.minor, ... 431 | obj.versions.matlab.patch] = bladeRF_MIMO.version(); 432 | 433 | [obj.versions.libbladeRF.major, ... 434 | obj.versions.libbladeRF.minor, ... 435 | obj.versions.libbladeRF.patch, ... 436 | obj.versions.libbladeRF.string] = bladeRF_MIMO.library_version(); 437 | 438 | % FX3 firmware version 439 | [~, ver_ptr] = bladeRF_MIMO.empty_version(); 440 | status = calllib('libbladeRF', 'bladerf_fw_version', dptr, ver_ptr); 441 | bladeRF_MIMO.check_status('bladerf_fw_version', status); 442 | obj.versions.firmware = ver_ptr.value; 443 | 444 | % FPGA version 445 | [~, ver_ptr] = bladeRF_MIMO.empty_version(); 446 | status = calllib('libbladeRF', 'bladerf_fpga_version', dptr, ver_ptr); 447 | bladeRF_MIMO.check_status('bladerf_fpga_version', status); 448 | obj.versions.fpga = ver_ptr.value; 449 | 450 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 451 | % Populate information 452 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 453 | 454 | % Serial number and backend interface 455 | [status, ~, devinfo] = calllib('libbladeRF', 'bladerf_get_devinfo', dptr, []); 456 | bladeRF_MIMO.check_status('bladerf_get_devinfo', status); 457 | 458 | obj.info.serial = char(devinfo.serial); 459 | 460 | switch devinfo.backend 461 | case 'BLADERF_BACKEND_LINUX' 462 | obj.info.backend = 'Linux kernel driver'; 463 | case 'BLADERF_BACKEND_LIBUSB' 464 | obj.info.backend = 'libusb'; 465 | case 'BLADERF_BACKEND_CYPRESS' 466 | obj.info.backend = 'Cypress CyAPI library & CyUSB driver'; 467 | otherwise 468 | obj.info.backend = devinfop.Value.backend; 469 | end 470 | 471 | % FPGA size 472 | fpga_size = 'BLADERF_FPGA_UNKNOWN'; 473 | [status, ~, fpga_size] = calllib('libbladeRF', 'bladerf_get_fpga_size', dptr, fpga_size); 474 | bladeRF_MIMO.check_status('bladerf_get_fpga_size', status); 475 | 476 | switch fpga_size 477 | case 'BLADERF_FPGA_40KLE' 478 | obj.info.fpga_size = '40 kLE'; 479 | obj.info.gen = 1; 480 | case 'BLADERF_FPGA_115KLE' 481 | obj.info.fpga_size = '115 kLE'; 482 | obj.info.gen = 1; 483 | case 'BLADERF_FPGA_A4' 484 | obj.info.fpga_size = 'A4 49 kLE'; 485 | obj.info.gen = 2; 486 | case 'BLADERF_FPGA_A9' 487 | obj.info.fpga_size = 'A9 301 kLE'; 488 | obj.info.gen = 2; 489 | end 490 | 491 | % USB Speed 492 | usb_speed = calllib('libbladeRF', 'bladerf_device_speed', dptr); 493 | switch usb_speed 494 | case 'BLADERF_DEVICE_SPEED_HIGH' 495 | obj.info.usb_speed = 'Hi-Speed (2.0)'; 496 | case 'BLADERF_DEVICE_SPEED_SUPER' 497 | obj.info.usb_speed = 'SuperSpeed (3.0)'; 498 | otherwise 499 | error(strcat('Unexpected device speed: ', usb_speed)) 500 | end 501 | 502 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 503 | % Expansion board 504 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 505 | if isempty(xb) == false 506 | switch upper(xb) 507 | case 'XB200' 508 | xb_val = 'BLADERF_XB_200'; 509 | case 'XB100' 510 | xb_val = 'BLADERF_XB_100'; 511 | otherwise 512 | error(['Invalid expansion board name: ' xb]); 513 | end 514 | 515 | status = calllib('libbladeRF', 'bladerf_expansion_attach', obj.device, xb_val); 516 | bladeRF_MIMO.check_status('bladerf_expansion_attach', status); 517 | 518 | obj.xb = upper(xb); 519 | else 520 | obj.xb = 'None'; 521 | end 522 | 523 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 524 | % Misc 525 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 526 | obj.misc = bladeRF_Misc(obj); 527 | 528 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 529 | % VCTCXO control 530 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 531 | obj.vctcxo = bladeRF_VCTCXO(obj); 532 | 533 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 534 | % Create transceiver chain 535 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 536 | 537 | obj.rx = bladeRF_MIMO_XCVR(obj, 'RX', xb); 538 | obj.tx = bladeRF_MIMO_XCVR(obj, 'TX', xb); 539 | 540 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 541 | % Default loopback mode 542 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 543 | obj.loopback = 'NONE'; 544 | 545 | obj.initialized = true; 546 | end 547 | 548 | 549 | function [samples, timestamp_out, actual_count, overrun] = receive(obj, num_samples, timeout_ms, timestamp_in) 550 | % RX samples immediately or at a future timestamp. 551 | % 552 | % [samples, timestamp_out, actual_count, overrun] = ... 553 | % bladeRF_MIMO.receive(num_samples, timeout_ms, timestamp_in) 554 | % 555 | % samples = bladeRF_MIMO.receive(4096) immediately receives 4096 samples. 556 | % 557 | % [samples, ~, ~, overrun] = bladeRF_MIMO.receive(1e6, 3000, 12345678) receives 558 | % 1 million samples at RX timestamp 12345678, with a 3 second timeout, 559 | % and fetches the overrun flag. 560 | % 561 | % Preconditions: 562 | % The bladeRF receiver has been previously configured via the 563 | % parameters in bladeRF_MIMO.rx.config (the defaults may suffice), 564 | % and bladeRF_MIMO.rx.start() has been called. 565 | % 566 | % Inputs: 567 | % num_samples Number of samples to receive 568 | % 569 | % timeout_ms Reception operation timeout, in ms. 0 implies no timeout. 570 | % Default = 2 * bladeRF_MIMO.rx.config.timeout_ms 571 | % 572 | % timestamp_in Timestamp to receive sample at. 0 implies "now." Default=0. 573 | % 574 | % Outputs: 575 | % 576 | % samples Received complex samples with real and imaginary component 577 | % amplitudes within [-1.0, 1.0]. These samples should be 578 | % contiguous if `overrun` is false. If `overrun` 579 | % is true, a discontinuity may have occurred and 580 | % only the first `actual_count` samples are 581 | % contiguous and valid. 582 | % 583 | % timestamp_out Timestamp of first sample in `samples`. 584 | % 585 | % actual_count Set to `num_samples` if no overrun occurred, 586 | % or the number of valid samples if an overrun 587 | % was detected. 588 | % 589 | % overrun Set to `true` if an overrun was detected 590 | % in this group of samples, and `false` otherwise. 591 | % 592 | % See also: bladeRF_MIMO_XCVR/start, bladeRF_StreamConfig 593 | 594 | if nargin < 3 595 | timeout_ms = 2 * obj.rx.config.timeout_ms; 596 | end 597 | 598 | if nargin < 4 599 | timestamp_in = 0; 600 | end 601 | 602 | s16 = int16(zeros(2 * 2 * num_samples, 1)); 603 | 604 | metad = libstruct('bladerf_metadata'); 605 | metad.actual_count = 0; 606 | metad.reserved = 0; 607 | metad.status = 0; 608 | 609 | if timestamp_in == 0 610 | % BLADERF_META_FLAG_RX_NOW 611 | metad.flags = bitshift(1,31); 612 | else 613 | metad.flags = 0; 614 | end 615 | 616 | metad.timestamp = timestamp_in; 617 | 618 | pmetad = libpointer('bladerf_metadata', metad); 619 | 620 | overrun = false; 621 | 622 | [status, ~, s16, ~] = calllib('libbladeRF', 'bladerf_sync_rx', ... 623 | obj.device, ... 624 | s16, ... 625 | 2 * num_samples, ... 626 | pmetad, ... 627 | timeout_ms); 628 | 629 | bladeRF_MIMO.check_status('bladerf_sync_rx', status); 630 | 631 | actual_count = pmetad.value.actual_count; 632 | if actual_count ~= 2 * num_samples 633 | overrun = true; 634 | end 635 | 636 | % Deinterleve and scale to [-1.0, 1.0). 637 | samples = [(double(s16(1:4:end)) + double(s16(2:4:end))*1j), ... 638 | (double(s16(3:4:end)) + double(s16(4:4:end))*1j)]; 639 | samples = samples ./ 2048.0; 640 | timestamp_out = metad.timestamp; 641 | end 642 | 643 | function transmit(obj, samples, timeout_ms, timestamp, sob, eob) 644 | % TX samples as part of a stream or as a burst immediately or at a specified timestamp. 645 | % 646 | % bladeRF_MIMO.transmit(samples, timeout_ms, timestamp, sob, eob) 647 | % 648 | % bladeRF_MIMO.transmit(samples) transmits samples as part of a larger 649 | % stream. Under the hood, this is implemented as a single 650 | % long-running burst. If successive calls are provided with a 651 | % timestamp, the stream will be "padded" with 0+0j up to that 652 | % timestamp. 653 | % 654 | % bladeRF_MIMO.transmit(samples, 3000, 0, true, true) immediately transmits 655 | % a single burst of samples with a 3s timeout. The use of the 656 | % sob and eob flags ensures the end of the burst will be 657 | % zero padded by libbladeRF in order to hold the TX DAC at 658 | % 0+0j after the burst completes. 659 | % 660 | % Preconditions: 661 | % The bladeRF transmitter has been previously configured via 662 | % parameters in bladeRF_MIMO.tx.config (the defaults may suffice), 663 | % and bladeRF_MIMO.tx.start() has been called. 664 | % 665 | % Inputs: 666 | % samples Complex samples to transmit. The amplitude of the real and 667 | % imaginary components are expected to be within [-1.0, 1.0]. 668 | % 669 | % timeout_ms Timeout for transmission function call. 0 implies no timeout. 670 | % Default = 2 * bladeRF_MIMO.tx.config.timeout_ms 671 | % 672 | % timestamp Timestamp counter value at which to transmit samples. 673 | % 0 implies "now." 674 | % 675 | % sob "Start of burst" flag. This informs libbladeRF to 676 | % consider all provided samples to be within a burst 677 | % until an eob flags is provided. This value should be 678 | % `true` or `false`. 679 | % 680 | % eob "End of burst" flag. This informs libbladeRF 681 | % that after the provided samples, the burst 682 | % should be ended. libbladeRF will zero-pad 683 | % the remainder of a buffer to ensure that 684 | % TX DAC is held at 0+0j after a burst. This value 685 | % should be `true` or `false` 686 | % 687 | % For more information about utilizing timestamps and bursts, see the 688 | % "TX with metadata" topic in the libbladeRF API documentation: 689 | % 690 | % http://www.nuand.com/libbladeRF-doc 691 | % 692 | % See also: bladeRF_MIMO_XCVR/start, bladeRF_StreamConfig 693 | % 694 | if nargin < 3 695 | timeout_ms = 2 * obj.tx.config.timeout_ms; 696 | end 697 | 698 | if nargin < 4 699 | timestamp = 0; 700 | end 701 | 702 | if nargin < 6 703 | % If the user has not specified SOB/EOB flags, we'll assume 704 | % they just want to stream samples and not worry about 705 | % these flags. We can support this by just starting a burst 706 | % that is as long as the transmission is active. 707 | sob = obj.tx.sob; 708 | eob = obj.tx.eob; 709 | 710 | % Ensure the next calls are "mid-burst" 711 | if obj.tx.sob == true 712 | obj.tx.sob = false; 713 | end 714 | else 715 | % The caller has specified the end of the burst. 716 | % Reset cached values to be ready to start a burst if 717 | % they call transmit() later with no flags. 718 | if eob == true 719 | obj.tx.sob = true; 720 | obj.tx.eob = false; 721 | end 722 | end 723 | 724 | assert(islogical(sob), 'Error: sob flag must be a `true` or `false` (logical type)'); 725 | assert(islogical(eob), 'Error: eob flag must be a `true` or `false` (logical type)'); 726 | assert(size(samples,2) == 2, 'Error: Sample matrix must have [N, 2] dimensions'); 727 | 728 | metad = libstruct('bladerf_metadata'); 729 | metad.actual_count = 0; 730 | metad.reserved = 0; 731 | metad.status = 0; 732 | metad.flag = 0; 733 | 734 | if timestamp == 0 735 | % Start the burst "Now" 736 | if sob == true 737 | % BLADERF_META_FLAG_TX_NOW 738 | metad.flags = bitor(metad.flags, bitshift(1, 2)); 739 | end 740 | else 741 | % If we're mid-burst, we need to use this flag to tell 742 | % libbladeRF that we want it to zero-pad up to the 743 | % specified timestamp (or at least until the end of the 744 | % current internal buffer). 745 | if sob == false 746 | % BLADERF_META_FLAG_TX_UPDATE_TIMESTAMP 747 | metad.flags = bitor(metad.flags, bitshift(1, 3)); 748 | end 749 | end 750 | 751 | metad.timestamp = timestamp; 752 | 753 | 754 | if sob == true 755 | % BLADERF_META_FLAG_TX_BURST_START 756 | metad.flags = bitor(metad.flags, bitshift(1, 0)); 757 | end 758 | 759 | if eob == true 760 | % BLADERF_META_FLAG_TX_BURST_END 761 | metad.flags = bitor(metad.flags, bitshift(1, 1)); 762 | end 763 | 764 | pmetad = libpointer('bladerf_metadata', metad); 765 | 766 | % Interleave and scale. We scale by 2047.0 rather than 2048.0 767 | % here because valid values are only [-2048, 2047]. However, 768 | % it's simpler to allow users to assume they can just input 769 | % samples within [-1.0, 1.0]. 770 | samples = samples .* 2047; 771 | 772 | s16 = zeros(2 * 2 * length(samples), 1, 'int16'); 773 | s16(1:4:end) = real(samples(:,1)); 774 | s16(2:4:end) = imag(samples(:,1)); 775 | s16(3:4:end) = real(samples(:,2)); 776 | s16(4:4:end) = imag(samples(:,2)); 777 | 778 | % s16(1:2:end/2) = real(samples(:,1)); 779 | % s16(2:2:end/2) = imag(samples(:,1)); 780 | % s16(end/2+1:2:end) = real(samples(:,2)); 781 | % s16(end/2+2:2:end) = imag(samples(:,2)); 782 | 783 | 784 | 785 | %fprintf('SOB=%d, EOB=%d, TS=0x%s, flags=0x%s\n', ... 786 | % sob, eob, dec2hex(metad.timestamp), dec2hex(metad.flags)); 787 | status = calllib('libbladeRF', 'bladerf_sync_tx', ... 788 | obj.device, ... 789 | s16, ... 790 | 2*length(samples), ... 791 | pmetad, ... 792 | timeout_ms * 1000); 793 | 794 | bladeRF_MIMO.check_status('bladerf_sync_tx', status); 795 | end 796 | 797 | function delete(obj) 798 | % Destructor. Stops all running stream and close the device handle. 799 | % 800 | % Do not call directly. Clear references to the handle from your 801 | % workspace and MATLAB will call this once the last reference is 802 | % cleared. 803 | 804 | %disp('Delete bladeRF called'); 805 | if isempty(obj.device) == false 806 | obj.rx.stop(); 807 | obj.tx.stop(); 808 | calllib('libbladeRF', 'bladerf_close', obj.device); 809 | end 810 | end 811 | 812 | function val = peek(obj, dev, addr) 813 | % Read from a device register. 814 | % 815 | % [value] = bladeRF_MIMO.peek(device, address) 816 | % 817 | % Device may be one of the following: 818 | % 'lms6002d', 'lms6', 'lms' - LMS6002D Transceiver registers 819 | % 'si5338', 'si' - Si5338 Clock Generator 820 | % 821 | switch dev 822 | case { 'lms', 'lms6', 'lms6002d' } 823 | x = uint8(0); 824 | [status, ~, x] = calllib('libbladeRF', 'bladerf_lms_read', obj.device, addr, x); 825 | bladeRF_MIMO.check_status('bladerf_lms_read', status); 826 | val = x; 827 | 828 | case { 'si', 'si5338' } 829 | x = uint8(0); 830 | [status, ~, x] = calllib('libbladeRF', 'bladerf_si5338_read', obj.device, addr, x); 831 | bladeRF_MIMO.check_status('bladerf_si5338_read', status); 832 | val = x; 833 | end 834 | end 835 | 836 | function poke(obj, dev, addr, val) 837 | % Write to a device register. 838 | % 839 | % bladeRF/poke(device, address, value) 840 | % 841 | % Device may be one of the following: 842 | % 'lms6002d', 'lms6', 'lms' - LMS6002D Transceiver registers 843 | % 'si5338', 'si' - Si5338 Clock Generator 844 | % 845 | switch dev 846 | case { 'lms', 'lms6', 'lms6002d' } 847 | status = calllib('libbladeRF', 'bladerf_lms_write', obj.device, addr, val); 848 | bladeRF_MIMO.check_status('bladerf_lms_write', status); 849 | 850 | case { 'si', 'si5338' } 851 | status = calllib('libbladeRF', 'bladerf_si5338_write', obj.device, addr, val); 852 | bladeRF_MIMO.check_status('bladerf_si5338_write', status); 853 | end 854 | end 855 | 856 | function set.loopback(obj, mode) 857 | switch upper(mode) 858 | case 'NONE' 859 | case 'FIRMWARE' 860 | case 'BB_TXLPF_RXVGA2' 861 | case 'BB_TXVGA1_RXVGA2' 862 | case 'BB_TXLPF_RXLPF' 863 | case 'BB_TXVGA1_RXLPF' 864 | case 'RF_LNA1' 865 | case 'RF_LNA2' 866 | case 'RF_LNA3' 867 | 868 | otherwise 869 | error(['Invalid loopback mode: ' mode]); 870 | end 871 | 872 | mode_val = [ 'BLADERF_LB_' mode ]; 873 | status = calllib('libbladeRF', 'bladerf_set_loopback', obj.device, mode_val); 874 | bladeRF_MIMO.check_status('bladerf_set_loopback', status); 875 | end 876 | 877 | function mode = get.loopback(obj) 878 | mode = 0; 879 | [status, ~, mode] = calllib('libbladeRF', 'bladerf_get_loopback', obj.device, mode); 880 | 881 | bladeRF_MIMO.check_status('bladerf_set_loopback', status); 882 | mode = strrep(mode, 'BLADERF_LB_', ''); 883 | end 884 | 885 | function calibrate(obj, module) 886 | % Perform the specified calibration. 887 | % 888 | % RX and TX should not be running when this is done. 889 | % 890 | % bladeRF/calibrate(module) 891 | % 892 | % Where `module` is one of: 893 | % - 'LPF_TUNING' - Calibrate the DC offset of the LPF Tuning module 894 | % - 'RX_LPF' - Calibrate the DC offset of the RX LPF 895 | % - 'TX_LPF' - Calibrate the DC offset of the TX LPF 896 | % - 'RXVGA2' - Calibrate the DC offset of RX VGA2 897 | % - 'ALL' - Perform all of the above 898 | % 899 | if obj.rx.running == true || obj.tx.running == true 900 | error('Calibration cannot be performed while the device is streaming.') 901 | end 902 | 903 | if nargin < 2 904 | module = 'ALL'; 905 | else 906 | module = strcat('BLADERF_DC_CAL_', upper(module)); 907 | end 908 | 909 | switch module 910 | case { 'BLADERF_DC_CAL_LPF_TUNING', ... 911 | 'BLADERF_DC_CAL_RX_LPF', ... 912 | 'BLADERF_DC_CAL_RXVGA2', ... 913 | 'BLADERF_DC_CAL_TX_LPF' } 914 | 915 | perform_calibration(obj, module); 916 | 917 | case { 'BLADERF_DC_CAL_ALL' } 918 | 919 | perform_calibration(obj, 'BLADERF_DC_CAL_LPF_TUNING'); 920 | perform_calibration(obj, 'BLADERF_DC_CAL_RX_LPF'); 921 | perform_calibration(obj, 'BLADERF_DC_CAL_RXVGA2'); 922 | perform_calibration(obj, 'BLADERF_DC_CAL_TX_LPF'); 923 | 924 | otherwise 925 | error(['Invalid module specified: ' module]) 926 | end 927 | 928 | end 929 | end 930 | 931 | methods(Access = private) 932 | 933 | function perform_calibration(obj, cal) 934 | if strcmpi(cal, 'BLADERF_DC_CAL_TX_LPF') == true 935 | samplerate_backup = obj.tx.samplerate; 936 | config_backup = obj.tx.config; 937 | loopback_backup = obj.loopback; 938 | 939 | obj.loopback = 'BB_TXVGA1_RXVGA2'; 940 | 941 | obj.tx.config.num_buffers = 16; 942 | obj.tx.config.num_transfers = 8; 943 | obj.tx.config.buffer_size = 8192; 944 | obj.tx.samplerate = 3e6; 945 | 946 | dummy_samples = zeros(obj.tx.config.buffer_size * obj.tx.config.num_buffers, 1); 947 | obj.tx.start(); 948 | obj.transmit(dummy_samples); 949 | obj.tx.stop(); 950 | 951 | obj.tx.config = config_backup; 952 | obj.tx.samplerate = samplerate_backup; 953 | 954 | obj.loopback = loopback_backup; 955 | end 956 | 957 | status = calllib('libbladeRF', 'bladerf_calibrate_dc', obj.device, cal); 958 | bladeRF_MIMO.check_status('bladerf_calibrate_dc', status); 959 | end 960 | end 961 | end 962 | --------------------------------------------------------------------------------