root / host / utils / usrp_gen_db_cal_table.cpp @ 3e4f6418
History | View | Annotate | Download (15 KB)
| 1 | bb2259e0 | Josh Blum | //
|
|---|---|---|---|
| 2 | // Copyright 2010 Ettus Research LLC
|
||
| 3 | //
|
||
| 4 | // This program is free software: you can redistribute it and/or modify
|
||
| 5 | // it under the terms of the GNU General Public License as published by
|
||
| 6 | // the Free Software Foundation, either version 3 of the License, or
|
||
| 7 | // (at your option) any later version.
|
||
| 8 | //
|
||
| 9 | // This program is distributed in the hope that it will be useful,
|
||
| 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
| 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
| 12 | // GNU General Public License for more details.
|
||
| 13 | //
|
||
| 14 | // You should have received a copy of the GNU General Public License
|
||
| 15 | // along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
| 16 | //
|
||
| 17 | |||
| 18 | #include <uhd/utils/thread_priority.hpp> |
||
| 19 | #include <uhd/utils/safe_main.hpp> |
||
| 20 | 84594320 | Josh Blum | #include <uhd/utils/paths.hpp> |
| 21 | 3e4f6418 | Josh Blum | #include <uhd/utils/algorithm.hpp> |
| 22 | 84594320 | Josh Blum | #include <uhd/property_tree.hpp> |
| 23 | bb2259e0 | Josh Blum | #include <uhd/usrp/multi_usrp.hpp> |
| 24 | 84594320 | Josh Blum | #include <uhd/usrp/dboard_eeprom.hpp> |
| 25 | bb2259e0 | Josh Blum | #include <boost/program_options.hpp> |
| 26 | #include <boost/format.hpp> |
||
| 27 | #include <boost/thread/thread.hpp> |
||
| 28 | 84594320 | Josh Blum | #include <boost/filesystem.hpp> |
| 29 | bb2259e0 | Josh Blum | #include <boost/math/special_functions/round.hpp> |
| 30 | #include <iostream> |
||
| 31 | #include <fstream> |
||
| 32 | #include <complex> |
||
| 33 | #include <cmath> |
||
| 34 | 84594320 | Josh Blum | #include <ctime> |
| 35 | bb2259e0 | Josh Blum | |
| 36 | namespace po = boost::program_options;
|
||
| 37 | 84594320 | Josh Blum | namespace fs = boost::filesystem;
|
| 38 | bb2259e0 | Josh Blum | |
| 39 | /***********************************************************************
|
||
| 40 | * Constants
|
||
| 41 | **********************************************************************/
|
||
| 42 | static const double tau = 6.28318531; |
||
| 43 | static const double alpha = 0.0001; //very tight iir filter |
||
| 44 | static const size_t wave_table_len = 8192; |
||
| 45 | 3e4f6418 | Josh Blum | static const size_t num_search_steps = 5; |
| 46 | static const size_t num_search_iters = 7; |
||
| 47 | bb2259e0 | Josh Blum | |
| 48 | /***********************************************************************
|
||
| 49 | * Sinusoid wave table
|
||
| 50 | **********************************************************************/
|
||
| 51 | static std::vector<std::complex<float> > gen_table(void){ |
||
| 52 | std::vector<std::complex<float> > wave_table(wave_table_len);
|
||
| 53 | for (size_t i = 0; i < wave_table_len; i++){ |
||
| 54 | 3e4f6418 | Josh Blum | wave_table[i] = std::polar<float>(1.0, (tau*i)/wave_table_len); |
| 55 | bb2259e0 | Josh Blum | } |
| 56 | return wave_table;
|
||
| 57 | } |
||
| 58 | |||
| 59 | 3e4f6418 | Josh Blum | static std::complex<float> wave_table_lookup(const size_t index){ |
| 60 | bb2259e0 | Josh Blum | static const std::vector<std::complex<float> > wave_table = gen_table(); |
| 61 | 3e4f6418 | Josh Blum | return wave_table[index % wave_table_len];
|
| 62 | bb2259e0 | Josh Blum | } |
| 63 | |||
| 64 | /***********************************************************************
|
||
| 65 | * Compute power of a tone
|
||
| 66 | **********************************************************************/
|
||
| 67 | static double compute_tone_dbrms( |
||
| 68 | const std::vector<std::complex<float> > &samples, |
||
| 69 | const double freq //freq is fractional |
||
| 70 | ){
|
||
| 71 | //shift the samples so the tone at freq is down at DC
|
||
| 72 | std::vector<std::complex<double> > shifted(samples.size());
|
||
| 73 | for (size_t i = 0; i < shifted.size(); i++){ |
||
| 74 | 3e4f6418 | Josh Blum | shifted[i] = std::complex<double>(samples[i]) * std::polar<double>(1.0, -freq*tau*i); |
| 75 | bb2259e0 | Josh Blum | } |
| 76 | |||
| 77 | //filter the samples with a narrow low pass
|
||
| 78 | std::complex<double> iir_output = 0, iir_last = 0; |
||
| 79 | double output = 0; |
||
| 80 | for (size_t i = 0; i < shifted.size(); i++){ |
||
| 81 | iir_output = alpha * shifted[i] + (1-alpha)*iir_last;
|
||
| 82 | iir_last = iir_output; |
||
| 83 | output += std::abs(iir_output); |
||
| 84 | } |
||
| 85 | |||
| 86 | return 20*std::log10(output/shifted.size()); |
||
| 87 | } |
||
| 88 | |||
| 89 | /***********************************************************************
|
||
| 90 | * Transmit thread
|
||
| 91 | **********************************************************************/
|
||
| 92 | 3cb60c97 | Josh Blum | static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_freq, const double tx_wave_ampl){ |
| 93 | bb2259e0 | Josh Blum | uhd::set_thread_priority_safe(); |
| 94 | |||
| 95 | //create a transmit streamer
|
||
| 96 | uhd::stream_args_t stream_args("fc32"); //complex floats |
||
| 97 | uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); |
||
| 98 | |||
| 99 | //setup variables and allocate buffer
|
||
| 100 | uhd::tx_metadata_t md; |
||
| 101 | md.has_time_spec = false;
|
||
| 102 | std::vector<std::complex<float> > buff(tx_stream->get_max_num_samps()*10); |
||
| 103 | |||
| 104 | //values for the wave table lookup
|
||
| 105 | size_t index = 0;
|
||
| 106 | const double tx_rate = usrp->get_tx_rate(); |
||
| 107 | const size_t step = boost::math::iround(wave_table_len * tx_wave_freq/tx_rate);
|
||
| 108 | |||
| 109 | //fill buff and send until interrupted
|
||
| 110 | while (not boost::this_thread::interruption_requested()){ |
||
| 111 | for (size_t i = 0; i < buff.size(); i++){ |
||
| 112 | 3e4f6418 | Josh Blum | buff[i] = float(tx_wave_ampl) * wave_table_lookup(index += step);
|
| 113 | bb2259e0 | Josh Blum | } |
| 114 | tx_stream->send(&buff.front(), buff.size(), md); |
||
| 115 | } |
||
| 116 | |||
| 117 | //send a mini EOB packet
|
||
| 118 | md.end_of_burst = true;
|
||
| 119 | tx_stream->send("", 0, md); |
||
| 120 | } |
||
| 121 | |||
| 122 | /***********************************************************************
|
||
| 123 | 3cb60c97 | Josh Blum | * Tune RX and TX routine
|
| 124 | **********************************************************************/
|
||
| 125 | 3e4f6418 | Josh Blum | static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double tx_lo_freq, const double rx_offset){ |
| 126 | 3cb60c97 | Josh Blum | //tune the transmitter with no cordic
|
| 127 | uhd::tune_request_t tx_tune_req(tx_lo_freq); |
||
| 128 | tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; |
||
| 129 | tx_tune_req.dsp_freq = 0;
|
||
| 130 | usrp->set_tx_freq(tx_tune_req); |
||
| 131 | |||
| 132 | //tune the receiver
|
||
| 133 | usrp->set_rx_freq(usrp->get_tx_freq() - rx_offset); |
||
| 134 | |||
| 135 | //wait for the LOs to become locked
|
||
| 136 | boost::system_time start = boost::get_system_time(); |
||
| 137 | while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ |
||
| 138 | if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ |
||
| 139 | throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); |
||
| 140 | } |
||
| 141 | } |
||
| 142 | 3e4f6418 | Josh Blum | |
| 143 | return usrp->get_tx_freq();
|
||
| 144 | 3cb60c97 | Josh Blum | } |
| 145 | |||
| 146 | /***********************************************************************
|
||
| 147 | * Data capture routine
|
||
| 148 | **********************************************************************/
|
||
| 149 | static void capture_samples(uhd::usrp::multi_usrp::sptr usrp, uhd::rx_streamer::sptr rx_stream, std::vector<std::complex<float> > &buff){ |
||
| 150 | uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); |
||
| 151 | stream_cmd.num_samps = buff.size(); |
||
| 152 | stream_cmd.stream_now = true;
|
||
| 153 | usrp->issue_stream_cmd(stream_cmd); |
||
| 154 | uhd::rx_metadata_t md; |
||
| 155 | const size_t num_rx_samps = rx_stream->recv(&buff.front(), buff.size(), md);
|
||
| 156 | |||
| 157 | //validate the received data
|
||
| 158 | if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE){
|
||
| 159 | throw std::runtime_error(str(boost::format(
|
||
| 160 | "Unexpected error code 0x%x"
|
||
| 161 | ) % md.error_code)); |
||
| 162 | } |
||
| 163 | if (num_rx_samps != buff.size()){
|
||
| 164 | throw std::runtime_error("did not get all the samples requested"); |
||
| 165 | } |
||
| 166 | } |
||
| 167 | |||
| 168 | 84594320 | Josh Blum | /***********************************************************************
|
| 169 | * Store data to file
|
||
| 170 | **********************************************************************/
|
||
| 171 | struct result_t{double freq, real_corr, imag_corr, sup;}; |
||
| 172 | static void store_results(uhd::usrp::multi_usrp::sptr usrp, const std::vector<result_t> &results){ |
||
| 173 | //extract eeprom serial
|
||
| 174 | uhd::property_tree::sptr tree = usrp->get_device()->get_tree(); |
||
| 175 | const uhd::fs_path db_path = "/mboards/0/dboards/A/tx_eeprom"; |
||
| 176 | const uhd::usrp::dboard_eeprom_t db_eeprom = tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get();
|
||
| 177 | if (db_eeprom.serial.empty()) throw std::runtime_error("TX dboard has empty serial!"); |
||
| 178 | |||
| 179 | //make the calibration file path
|
||
| 180 | fs::path cal_data_path = fs::path(uhd::get_app_path()) / ".uhd";
|
||
| 181 | fs::create_directory(cal_data_path); |
||
| 182 | cal_data_path = cal_data_path / "cal";
|
||
| 183 | fs::create_directory(cal_data_path); |
||
| 184 | cal_data_path = cal_data_path / ("tx_fe_cal_v0.1_" + db_eeprom.serial + ".csv"); |
||
| 185 | 3e4f6418 | Josh Blum | if (fs::exists(cal_data_path)){
|
| 186 | fs::rename(cal_data_path, cal_data_path.string() + str(boost::format(".%d") % time(NULL))); |
||
| 187 | } |
||
| 188 | 84594320 | Josh Blum | |
| 189 | //fill the calibration file
|
||
| 190 | std::ofstream cal_data(cal_data_path.string().c_str());
|
||
| 191 | cal_data << boost::format("name, TX Frontend Calibration\n");
|
||
| 192 | cal_data << boost::format("serial, %s\n") % db_eeprom.serial;
|
||
| 193 | cal_data << boost::format("timestamp, %d\n") % time(NULL); |
||
| 194 | cal_data << boost::format("version, 0, 1\n");
|
||
| 195 | cal_data << boost::format("DATA STARTS HERE\n");
|
||
| 196 | cal_data << "tx_lo_frequency, tx_iq_correction_real, tx_iq_correction_imag, measured_suppression\n";
|
||
| 197 | |||
| 198 | for (size_t i = 0; i < results.size(); i++){ |
||
| 199 | cal_data |
||
| 200 | << results[i].freq << ", "
|
||
| 201 | << results[i].real_corr << ", "
|
||
| 202 | << results[i].imag_corr << ", "
|
||
| 203 | << results[i].sup << "\n"
|
||
| 204 | ; |
||
| 205 | } |
||
| 206 | |||
| 207 | std::cout << "wrote cal data to " << cal_data_path << std::endl;
|
||
| 208 | } |
||
| 209 | 3cb60c97 | Josh Blum | |
| 210 | /***********************************************************************
|
||
| 211 | bb2259e0 | Josh Blum | * Main
|
| 212 | **********************************************************************/
|
||
| 213 | int UHD_SAFE_MAIN(int argc, char *argv[]){ |
||
| 214 | std::string args;
|
||
| 215 | 3e4f6418 | Josh Blum | double rate, tx_wave_freq, tx_wave_ampl, rx_offset, freq_step, tx_gain, rx_gain;
|
| 216 | 3cb60c97 | Josh Blum | size_t nsamps; |
| 217 | bb2259e0 | Josh Blum | |
| 218 | po::options_description desc("Allowed options");
|
||
| 219 | desc.add_options() |
||
| 220 | ("help", "help message") |
||
| 221 | 3e4f6418 | Josh Blum | ("verbose", "enable some verbose") |
| 222 | bb2259e0 | Josh Blum | ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") |
| 223 | ("rate", po::value<double>(&rate)->default_value(12.5e6), "RX and TX sample rate in Hz") |
||
| 224 | 3cb60c97 | Josh Blum | ("tx_wave_freq", po::value<double>(&tx_wave_freq)->default_value(507.123e3), "Transmit wave frequency in Hz") |
| 225 | ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") |
||
| 226 | ("rx_offset", po::value<double>(&rx_offset)->default_value(.9344e6), "RX LO offset from the TX LO in Hz") |
||
| 227 | 3e4f6418 | Josh Blum | ("tx_gain", po::value<double>(&tx_gain)->default_value(0), "TX gain in dB") |
| 228 | ("rx_gain", po::value<double>(&rx_gain)->default_value(0), "RX gain in dB") |
||
| 229 | ("freq_step", po::value<double>(&freq_step)->default_value(10e6), "Step size for LO sweep in Hz") |
||
| 230 | 3cb60c97 | Josh Blum | ("nsamps", po::value<size_t>(&nsamps)->default_value(10000), "Samples per data capture") |
| 231 | bb2259e0 | Josh Blum | ; |
| 232 | |||
| 233 | po::variables_map vm; |
||
| 234 | po::store(po::parse_command_line(argc, argv, desc), vm); |
||
| 235 | po::notify(vm); |
||
| 236 | |||
| 237 | //print the help message
|
||
| 238 | if (vm.count("help")){ |
||
| 239 | std::cout << boost::format("USRP Generate Daughterboard Calibration Table %s") % desc << std::endl;
|
||
| 240 | std::cout << |
||
| 241 | "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n"
|
||
| 242 | << std::endl; |
||
| 243 | return ~0; |
||
| 244 | } |
||
| 245 | |||
| 246 | //create a usrp device
|
||
| 247 | std::cout << std::endl; |
||
| 248 | std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl;
|
||
| 249 | uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); |
||
| 250 | |||
| 251 | 3e4f6418 | Josh Blum | //set the antennas to cal
|
| 252 | if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ |
||
| 253 | throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); |
||
| 254 | } |
||
| 255 | usrp->set_rx_antenna("CAL");
|
||
| 256 | usrp->set_tx_antenna("CAL");
|
||
| 257 | |||
| 258 | bb2259e0 | Josh Blum | //set the sample rates
|
| 259 | usrp->set_rx_rate(rate); |
||
| 260 | usrp->set_tx_rate(rate); |
||
| 261 | |||
| 262 | 3e4f6418 | Josh Blum | //set midrange rx gain, default 0 tx gain
|
| 263 | usrp->set_tx_gain(tx_gain); |
||
| 264 | usrp->set_rx_gain(rx_gain); |
||
| 265 | bb2259e0 | Josh Blum | |
| 266 | //create a receive streamer
|
||
| 267 | uhd::stream_args_t stream_args("fc32"); //complex floats |
||
| 268 | uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); |
||
| 269 | |||
| 270 | //create a transmitter thread
|
||
| 271 | boost::thread_group threads; |
||
| 272 | 3cb60c97 | Josh Blum | threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_freq, tx_wave_ampl)); |
| 273 | bb2259e0 | Josh Blum | |
| 274 | 3cb60c97 | Josh Blum | //re-usable buffer for samples
|
| 275 | std::vector<std::complex<float> > buff(nsamps);
|
||
| 276 | |||
| 277 | 3e4f6418 | Josh Blum | //store the results here
|
| 278 | 3cb60c97 | Josh Blum | std::vector<result_t> results; |
| 279 | |||
| 280 | 3e4f6418 | Josh Blum | const uhd::meta_range_t freq_range = usrp->get_tx_freq_range();
|
| 281 | for (double tx_lo_i = freq_range.start()+50e6; tx_lo_i < freq_range.stop()-50e6; tx_lo_i += freq_step){ |
||
| 282 | const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset); |
||
| 283 | 3cb60c97 | Josh Blum | |
| 284 | 3e4f6418 | Josh Blum | //bounds and results from searching
|
| 285 | 3cb60c97 | Josh Blum | std::complex<double> best_correction;
|
| 286 | 3e4f6418 | Josh Blum | double phase_corr_start = -.3, phase_corr_stop = .3, phase_corr_step; |
| 287 | double ampl_corr_start = -.3, ampl_corr_stop = .3, ampl_corr_step; |
||
| 288 | double best_suppression = 0, best_phase_corr = 0, best_ampl_corr = 0; |
||
| 289 | 3cb60c97 | Josh Blum | |
| 290 | 3e4f6418 | Josh Blum | for (size_t i = 0; i < num_search_iters; i++){ |
| 291 | 3cb60c97 | Josh Blum | |
| 292 | 3e4f6418 | Josh Blum | phase_corr_step = (phase_corr_stop - phase_corr_start)/(num_search_steps-1);
|
| 293 | ampl_corr_step = (ampl_corr_stop - ampl_corr_start)/(num_search_steps-1);
|
||
| 294 | 3cb60c97 | Josh Blum | |
| 295 | 3e4f6418 | Josh Blum | for (double phase_corr = phase_corr_start; phase_corr <= phase_corr_stop + phase_corr_step/2; phase_corr += phase_corr_step){ |
| 296 | for (double ampl_corr = ampl_corr_start; ampl_corr <= ampl_corr_stop + ampl_corr_step/2; ampl_corr += ampl_corr_step){ |
||
| 297 | 3cb60c97 | Josh Blum | |
| 298 | const std::complex<double> correction = std::polar(ampl_corr+1, phase_corr*tau); |
||
| 299 | usrp->set_tx_iq_balance(correction); |
||
| 300 | |||
| 301 | //receive some samples
|
||
| 302 | capture_samples(usrp, rx_stream, buff); |
||
| 303 | |||
| 304 | const double actual_rx_rate = usrp->get_rx_rate(); |
||
| 305 | const double actual_tx_freq = usrp->get_tx_freq(); |
||
| 306 | const double actual_rx_freq = usrp->get_rx_freq(); |
||
| 307 | const double bb_tone_freq = actual_tx_freq + tx_wave_freq - actual_rx_freq; |
||
| 308 | const double bb_imag_freq = actual_tx_freq - tx_wave_freq - actual_rx_freq; |
||
| 309 | |||
| 310 | const double tone_dbrms = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate); |
||
| 311 | const double imag_dbrms = compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); |
||
| 312 | 84594320 | Josh Blum | const double suppression = tone_dbrms - imag_dbrms; |
| 313 | 3cb60c97 | Josh Blum | |
| 314 | //std::cout << "bb_tone_freq " << bb_tone_freq << std::endl;
|
||
| 315 | //std::cout << "bb_imag_freq " << bb_imag_freq << std::endl;
|
||
| 316 | //std::cout << "tone_dbrms " << tone_dbrms << std::endl;
|
||
| 317 | //std::cout << "imag_dbrms " << imag_dbrms << std::endl;
|
||
| 318 | 84594320 | Josh Blum | //std::cout << "suppression " << (tone_dbrms - imag_dbrms) << std::endl;
|
| 319 | 3cb60c97 | Josh Blum | |
| 320 | 84594320 | Josh Blum | if (suppression > best_suppression){
|
| 321 | 3cb60c97 | Josh Blum | best_correction = correction; |
| 322 | 84594320 | Josh Blum | best_suppression = suppression; |
| 323 | 3cb60c97 | Josh Blum | best_phase_corr = phase_corr; |
| 324 | best_ampl_corr = ampl_corr; |
||
| 325 | } |
||
| 326 | |||
| 327 | }} |
||
| 328 | |||
| 329 | //std::cout << "best_phase_corr " << best_phase_corr << std::endl;
|
||
| 330 | //std::cout << "best_ampl_corr " << best_ampl_corr << std::endl;
|
||
| 331 | 84594320 | Josh Blum | //std::cout << "best_suppression " << best_suppression << std::endl;
|
| 332 | 3cb60c97 | Josh Blum | |
| 333 | phase_corr_start = best_phase_corr - phase_corr_step; |
||
| 334 | phase_corr_stop = best_phase_corr + phase_corr_step; |
||
| 335 | ampl_corr_start = best_ampl_corr - ampl_corr_step; |
||
| 336 | ampl_corr_stop = best_ampl_corr + ampl_corr_step; |
||
| 337 | } |
||
| 338 | |||
| 339 | 84594320 | Josh Blum | if (best_suppression > 30){ //most likely valid, keep result |
| 340 | 3cb60c97 | Josh Blum | result_t result; |
| 341 | result.freq = tx_lo; |
||
| 342 | 84594320 | Josh Blum | result.real_corr = best_correction.real(); |
| 343 | result.imag_corr = best_correction.imag(); |
||
| 344 | result.sup = best_suppression; |
||
| 345 | 3cb60c97 | Josh Blum | results.push_back(result); |
| 346 | } |
||
| 347 | 3e4f6418 | Josh Blum | if (vm.count("verbose")){ |
| 348 | std::cout << boost::format("%f MHz: best suppression %fdB") % (tx_lo/1e6) % best_suppression << std::endl; |
||
| 349 | } |
||
| 350 | else std::cout << "." << std::flush; |
||
| 351 | bb2259e0 | Josh Blum | |
| 352 | } |
||
| 353 | 3cb60c97 | Josh Blum | std::cout << std::endl; |
| 354 | bb2259e0 | Josh Blum | |
| 355 | //stop the transmitter
|
||
| 356 | threads.interrupt_all(); |
||
| 357 | threads.join_all(); |
||
| 358 | |||
| 359 | 84594320 | Josh Blum | store_results(usrp, results); |
| 360 | bb2259e0 | Josh Blum | |
| 361 | return 0; |
||
| 362 | } |