diff options
Diffstat (limited to 'host/utils')
-rw-r--r-- | host/utils/CMakeLists.txt | 88 | ||||
-rw-r--r-- | host/utils/fx2_init_eeprom.cpp | 93 | ||||
-rw-r--r-- | host/utils/uhd-usrp.rules | 19 | ||||
-rw-r--r-- | host/utils/uhd_cal_rx_iq_balance.cpp | 240 | ||||
-rw-r--r-- | host/utils/uhd_cal_tx_dc_offset.cpp | 238 | ||||
-rw-r--r-- | host/utils/uhd_cal_tx_iq_balance.cpp | 243 | ||||
-rw-r--r-- | host/utils/uhd_find_devices.cpp | 60 | ||||
-rw-r--r-- | host/utils/uhd_usrp_probe.cpp | 214 | ||||
-rwxr-xr-x | host/utils/usrp2_card_burner.py | 260 | ||||
-rwxr-xr-x | host/utils/usrp2_card_burner_gui.py | 176 | ||||
-rwxr-xr-x | host/utils/usrp2_recovery.py | 68 | ||||
-rw-r--r-- | host/utils/usrp_burn_db_eeprom.cpp | 100 | ||||
-rw-r--r-- | host/utils/usrp_burn_mb_eeprom.cpp | 78 | ||||
-rw-r--r-- | host/utils/usrp_cal_utils.hpp | 217 | ||||
-rwxr-xr-x | host/utils/usrp_n2xx_net_burner.py | 524 | ||||
-rwxr-xr-x | host/utils/usrp_n2xx_net_burner_gui.py | 233 |
16 files changed, 2851 insertions, 0 deletions
diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt new file mode 100644 index 000000000..0ecd6b4e7 --- /dev/null +++ b/host/utils/CMakeLists.txt @@ -0,0 +1,88 @@ +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# Utilities that get installed into the runtime path +######################################################################## +SET(util_runtime_sources + uhd_find_devices.cpp + uhd_usrp_probe.cpp + uhd_cal_rx_iq_balance.cpp + uhd_cal_tx_dc_offset.cpp + uhd_cal_tx_iq_balance.cpp +) + +#for each source: build an executable and install +FOREACH(util_source ${util_runtime_sources}) + GET_FILENAME_COMPONENT(util_name ${util_source} NAME_WE) + ADD_EXECUTABLE(${util_name} ${util_source}) + TARGET_LINK_LIBRARIES(${util_name} uhd) + INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${RUNTIME_DIR} COMPONENT utilities) +ENDFOREACH(util_source) + +######################################################################## +# Utilities that get installed into the share path +######################################################################## +SET(util_share_sources + usrp_burn_db_eeprom.cpp + usrp_burn_mb_eeprom.cpp +) + +IF(ENABLE_USB) + LIST(APPEND util_share_sources + fx2_init_eeprom.cpp + ) +ENDIF(ENABLE_USB) + +IF(LINUX AND ENABLE_USB) + INSTALL(FILES + uhd-usrp.rules + DESTINATION ${PKG_LIB_DIR}/utils + COMPONENT utilities + ) +ENDIF(LINUX AND ENABLE_USB) + +#for each source: build an executable and install +FOREACH(util_source ${util_share_sources}) + GET_FILENAME_COMPONENT(util_name ${util_source} NAME_WE) + ADD_EXECUTABLE(${util_name} ${util_source}) + TARGET_LINK_LIBRARIES(${util_name} uhd) + INSTALL(TARGETS ${util_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/utils COMPONENT utilities) +ENDFOREACH(util_source) + +IF(ENABLE_USRP2) + IF(WIN32 AND UHD_RELEASE_MODE) #include dd.exe + FILE(DOWNLOAD + "http://files.ettus.com/dd.exe" + ${CMAKE_CURRENT_BINARY_DIR}/dd.exe + ) + INSTALL(FILES + ${CMAKE_CURRENT_BINARY_DIR}/dd.exe + DESTINATION ${PKG_LIB_DIR}/utils + COMPONENT utilities + ) + ENDIF(WIN32 AND UHD_RELEASE_MODE) + INSTALL(PROGRAMS + usrp2_recovery.py + usrp2_card_burner.py + usrp2_card_burner_gui.py + usrp_n2xx_net_burner.py + usrp_n2xx_net_burner_gui.py + DESTINATION ${PKG_LIB_DIR}/utils + COMPONENT utilities + ) +ENDIF(ENABLE_USRP2) diff --git a/host/utils/fx2_init_eeprom.cpp b/host/utils/fx2_init_eeprom.cpp new file mode 100644 index 000000000..c210ae575 --- /dev/null +++ b/host/utils/fx2_init_eeprom.cpp @@ -0,0 +1,93 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <cstdlib> + +const std::string FX2_VENDOR_ID("0x04b4"); +const std::string FX2_PRODUCT_ID("0x8613"); + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string type; + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("image", po::value<std::string>(), "BIN image file") + ("vid", po::value<std::string>(), "VID of device to program") + ("pid", po::value<std::string>(), "PID of device to program") + ("type", po::value<std::string>(), "device type (usrp1 or b100)") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP EEPROM initialization %s") % desc << std::endl; + return ~0; + } + + //cant find a uninitialized usrp with this mystery module in the way... + if (std::system("/sbin/rmmod usbtest") != 0){ + std::cerr << "Did not rmmod usbtest, this may be ok..." << std::endl; + } + + //load the options into the address + uhd::device_addr_t device_addr; + device_addr["type"] = type; + if(vm.count("vid") or vm.count("pid") or vm.count("type")) { + if(not (vm.count("vid") and vm.count("pid") and vm.count("type"))) { + std::cerr << "ERROR: Must specify vid, pid, and type if specifying any of the three args" << std::endl; + } else { + device_addr["vid"] = vm["vid"].as<std::string>(); + device_addr["pid"] = vm["pid"].as<std::string>(); + device_addr["type"] = vm["type"].as<std::string>(); + } + } else { + device_addr["vid"] = FX2_VENDOR_ID; + device_addr["pid"] = FX2_PRODUCT_ID; + } + + //find and create a control transport to do the writing. + + uhd::device_addrs_t found_addrs = uhd::device::find(device_addr); + + if (found_addrs.size() == 0){ + std::cerr << "No USRP devices found" << std::endl; + return ~0; + } + + for (size_t i = 0; i < found_addrs.size(); i++){ + std::cout << "Writing EEPROM data..." << std::endl; + //uhd::device_addrs_t devs = uhd::device::find(found_addrs[i]); + uhd::device::sptr dev = uhd::device::make(found_addrs[i]); + uhd::property_tree::sptr tree = dev->get_tree(); + tree->access<std::string>("/mboards/0/load_eeprom").set(vm["image"].as<std::string>()); + } + + + std::cout << "Power-cycle the usrp for the changes to take effect." << std::endl; + return 0; +} diff --git a/host/utils/uhd-usrp.rules b/host/utils/uhd-usrp.rules new file mode 100644 index 000000000..56d9a8c43 --- /dev/null +++ b/host/utils/uhd-usrp.rules @@ -0,0 +1,19 @@ +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="fffe", ATTRS{idProduct}=="0002", MODE:="0666" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="2500", ATTRS{idProduct}=="0002", MODE:="0666" diff --git a/host/utils/uhd_cal_rx_iq_balance.cpp b/host/utils/uhd_cal_rx_iq_balance.cpp new file mode 100644 index 000000000..7b6f10f3b --- /dev/null +++ b/host/utils/uhd_cal_rx_iq_balance.cpp @@ -0,0 +1,240 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_cal_utils.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> +#include <cmath> +#include <ctime> + +namespace po = boost::program_options; + +/*********************************************************************** + * Transmit thread + **********************************************************************/ +static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_ampl){ + uhd::set_thread_priority_safe(); + + //create a transmit streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //setup variables and allocate buffer + uhd::tx_metadata_t md; + md.has_time_spec = false; + std::vector<samp_type> buff(tx_stream->get_max_num_samps()*10); + + //fill buff and send until interrupted + while (not boost::this_thread::interruption_requested()){ + for (size_t i = 0; i < buff.size(); i++){ + buff[i] = float(tx_wave_ampl); + } + tx_stream->send(&buff.front(), buff.size(), md); + } + + //send a mini EOB packet + md.end_of_burst = true; + tx_stream->send("", 0, md); +} + +/*********************************************************************** + * Tune RX and TX routine + **********************************************************************/ +static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double rx_lo_freq, const double tx_offset){ + //tune the receiver with no cordic + uhd::tune_request_t rx_tune_req(rx_lo_freq); + rx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + rx_tune_req.dsp_freq = 0; + usrp->set_rx_freq(rx_tune_req); + + //tune the transmitter with no cordic + uhd::tune_request_t tx_tune_req(usrp->get_rx_freq() - tx_offset); + tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + tx_tune_req.dsp_freq = 0; + usrp->set_tx_freq(tx_tune_req); + + //wait for the LOs to become locked + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + boost::system_time start = boost::get_system_time(); + while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ + if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ + throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); + } + } + + return usrp->get_rx_freq(); +} + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string args; + double tx_wave_ampl, tx_offset; + double freq_start, freq_stop, freq_step; + size_t nsamps; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("verbose", "enable some verbose") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") + ("tx_offset", po::value<double>(&tx_offset)->default_value(.9344e6), "TX LO offset from the RX LO in Hz") + ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") + ("freq_stop", po::value<double>(&freq_stop), "Frequency stop in Hz (do not specify for default)") + ("freq_step", po::value<double>(&freq_step)->default_value(default_freq_step), "Step size for LO sweep in Hz") + ("nsamps", po::value<size_t>(&nsamps)->default_value(default_num_samps), "Samples per data capture") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP Generate RX IQ Balance Calibration Table %s") % desc << std::endl; + std::cout << + "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" + << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //set the antennas to cal + if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ + throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); + } + usrp->set_rx_antenna("CAL"); + usrp->set_tx_antenna("CAL"); + + //set optimum defaults + set_optimum_defaults(usrp); + + //create a receive streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //create a transmitter thread + boost::thread_group threads; + threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_ampl)); + + //re-usable buffer for samples + std::vector<samp_type> buff; + + //store the results here + std::vector<result_t> results; + + if (not vm.count("freq_start")) freq_start = usrp->get_rx_freq_range().start() + 50e6; + if (not vm.count("freq_stop")) freq_stop = usrp->get_rx_freq_range().stop() - 50e6; + + for (double rx_lo_i = freq_start; rx_lo_i <= freq_stop; rx_lo_i += freq_step){ + const double rx_lo = tune_rx_and_tx(usrp, rx_lo_i, tx_offset); + + //frequency constants for this tune event + const double actual_rx_rate = usrp->get_rx_rate(); + const double actual_tx_freq = usrp->get_tx_freq(); + const double actual_rx_freq = usrp->get_rx_freq(); + const double bb_tone_freq = actual_tx_freq - actual_rx_freq; + const double bb_imag_freq = -bb_tone_freq; + + //capture initial uncorrected value + usrp->set_rx_iq_balance(0.0); + capture_samples(usrp, rx_stream, buff, nsamps); + const double initial_suppression = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate) - compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + + //bounds and results from searching + std::complex<double> best_correction; + double phase_corr_start = -.3, phase_corr_stop = .3, phase_corr_step; + double ampl_corr_start = -.3, ampl_corr_stop = .3, ampl_corr_step; + double best_suppression = 0, best_phase_corr = 0, best_ampl_corr = 0; + + for (size_t i = 0; i < num_search_iters; i++){ + + phase_corr_step = (phase_corr_stop - phase_corr_start)/(num_search_steps-1); + ampl_corr_step = (ampl_corr_stop - ampl_corr_start)/(num_search_steps-1); + + for (double phase_corr = phase_corr_start; phase_corr <= phase_corr_stop + phase_corr_step/2; phase_corr += phase_corr_step){ + for (double ampl_corr = ampl_corr_start; ampl_corr <= ampl_corr_stop + ampl_corr_step/2; ampl_corr += ampl_corr_step){ + + const std::complex<double> correction(ampl_corr, phase_corr); + usrp->set_rx_iq_balance(correction); + + //receive some samples + capture_samples(usrp, rx_stream, buff, nsamps); + + const double tone_dbrms = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate); + const double imag_dbrms = compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + const double suppression = tone_dbrms - imag_dbrms; + + if (suppression > best_suppression){ + best_correction = correction; + best_suppression = suppression; + best_phase_corr = phase_corr; + best_ampl_corr = ampl_corr; + } + + }} + + //std::cout << "best_phase_corr " << best_phase_corr << std::endl; + //std::cout << "best_ampl_corr " << best_ampl_corr << std::endl; + //std::cout << "best_suppression " << best_suppression << std::endl; + + phase_corr_start = best_phase_corr - phase_corr_step; + phase_corr_stop = best_phase_corr + phase_corr_step; + ampl_corr_start = best_ampl_corr - ampl_corr_step; + ampl_corr_stop = best_ampl_corr + ampl_corr_step; + } + + if (best_suppression > 30){ //most likely valid, keep result + result_t result; + result.freq = rx_lo; + result.real_corr = best_correction.real(); + result.imag_corr = best_correction.imag(); + result.best = best_suppression; + result.delta = best_suppression - initial_suppression; + results.push_back(result); + if (vm.count("verbose")){ + std::cout << boost::format("RX IQ: %f MHz: best suppression %f dB, corrected %f dB") % (rx_lo/1e6) % result.best % result.delta << std::endl; + } + else std::cout << "." << std::flush; + } + + } + std::cout << std::endl; + + //stop the transmitter + threads.interrupt_all(); + threads.join_all(); + + store_results(usrp, results, "RX", "rx", "iq"); + + return 0; +} diff --git a/host/utils/uhd_cal_tx_dc_offset.cpp b/host/utils/uhd_cal_tx_dc_offset.cpp new file mode 100644 index 000000000..1b2510ba4 --- /dev/null +++ b/host/utils/uhd_cal_tx_dc_offset.cpp @@ -0,0 +1,238 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_cal_utils.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> +#include <ctime> + +namespace po = boost::program_options; + +/*********************************************************************** + * Transmit thread + **********************************************************************/ +static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_freq, const double tx_wave_ampl){ + uhd::set_thread_priority_safe(); + + //create a transmit streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //setup variables and allocate buffer + uhd::tx_metadata_t md; + md.has_time_spec = false; + std::vector<samp_type> buff(tx_stream->get_max_num_samps()*10); + + //values for the wave table lookup + size_t index = 0; + const double tx_rate = usrp->get_tx_rate(); + const size_t step = boost::math::iround(wave_table_len * tx_wave_freq/tx_rate); + wave_table table(tx_wave_ampl); + + //fill buff and send until interrupted + while (not boost::this_thread::interruption_requested()){ + for (size_t i = 0; i < buff.size(); i++){ + buff[i] = table(index += step); + } + tx_stream->send(&buff.front(), buff.size(), md); + } + + //send a mini EOB packet + md.end_of_burst = true; + tx_stream->send("", 0, md); +} + +/*********************************************************************** + * Tune RX and TX routine + **********************************************************************/ +static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double tx_lo_freq, const double rx_offset){ + //tune the transmitter with no cordic + uhd::tune_request_t tx_tune_req(tx_lo_freq); + tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + tx_tune_req.dsp_freq = 0; + usrp->set_tx_freq(tx_tune_req); + + //tune the receiver + usrp->set_rx_freq(usrp->get_tx_freq() - rx_offset); + + //wait for the LOs to become locked + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + boost::system_time start = boost::get_system_time(); + while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ + if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ + throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); + } + } + + return usrp->get_tx_freq(); +} + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string args; + double tx_wave_freq, tx_wave_ampl, rx_offset; + double freq_start, freq_stop, freq_step; + size_t nsamps; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("verbose", "enable some verbose") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("tx_wave_freq", po::value<double>(&tx_wave_freq)->default_value(507.123e3), "Transmit wave frequency in Hz") + ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") + ("rx_offset", po::value<double>(&rx_offset)->default_value(.9344e6), "RX LO offset from the TX LO in Hz") + ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") + ("freq_stop", po::value<double>(&freq_stop), "Frequency stop in Hz (do not specify for default)") + ("freq_step", po::value<double>(&freq_step)->default_value(default_freq_step), "Step size for LO sweep in Hz") + ("nsamps", po::value<size_t>(&nsamps)->default_value(default_num_samps), "Samples per data capture") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP Generate TX DC Offset Calibration Table %s") % desc << std::endl; + std::cout << + "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" + << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //set the antennas to cal + if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ + throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); + } + usrp->set_rx_antenna("CAL"); + usrp->set_tx_antenna("CAL"); + + //set optimum defaults + set_optimum_defaults(usrp); + + //create a receive streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //create a transmitter thread + boost::thread_group threads; + threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_freq, tx_wave_ampl)); + + //re-usable buffer for samples + std::vector<samp_type> buff; + + //store the results here + std::vector<result_t> results; + + if (not vm.count("freq_start")) freq_start = usrp->get_tx_freq_range().start() + 50e6; + if (not vm.count("freq_stop")) freq_stop = usrp->get_tx_freq_range().stop() - 50e6; + + for (double tx_lo_i = freq_start; tx_lo_i <= freq_stop; tx_lo_i += freq_step){ + const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset); + + //frequency constants for this tune event + const double actual_rx_rate = usrp->get_rx_rate(); + const double actual_tx_freq = usrp->get_tx_freq(); + const double actual_rx_freq = usrp->get_rx_freq(); + const double bb_dc_freq = actual_tx_freq - actual_rx_freq; + + //capture initial uncorrected value + usrp->set_tx_dc_offset(std::complex<double>(0, 0)); + capture_samples(usrp, rx_stream, buff, nsamps); + const double initial_dc_dbrms = compute_tone_dbrms(buff, bb_dc_freq/actual_rx_rate); + + //bounds and results from searching + double dc_i_start = -.01, dc_i_stop = .01, dc_i_step; + double dc_q_start = -.01, dc_q_stop = .01, dc_q_step; + double lowest_offset = 0, best_dc_i = 0, best_dc_q = 0; + + for (size_t i = 0; i < num_search_iters; i++){ + + dc_i_step = (dc_i_stop - dc_i_start)/(num_search_steps-1); + dc_q_step = (dc_q_stop - dc_q_start)/(num_search_steps-1); + + for (double dc_i = dc_i_start; dc_i <= dc_i_stop + dc_i_step/2; dc_i += dc_i_step){ + for (double dc_q = dc_q_start; dc_q <= dc_q_stop + dc_q_step/2; dc_q += dc_q_step){ + + const std::complex<double> correction(dc_i, dc_q); + usrp->set_tx_dc_offset(correction); + + //receive some samples + capture_samples(usrp, rx_stream, buff, nsamps); + + const double dc_dbrms = compute_tone_dbrms(buff, bb_dc_freq/actual_rx_rate); + + if (dc_dbrms < lowest_offset){ + lowest_offset = dc_dbrms; + best_dc_i = dc_i; + best_dc_q = dc_q; + } + + }} + + //std::cout << "best_dc_i " << best_dc_i << std::endl; + //std::cout << "best_dc_q " << best_dc_q << std::endl; + //std::cout << "lowest_offset " << lowest_offset << std::endl; + + dc_i_start = best_dc_i - dc_i_step; + dc_i_stop = best_dc_i + dc_i_step; + dc_q_start = best_dc_q - dc_q_step; + dc_q_stop = best_dc_q + dc_q_step; + } + + if (lowest_offset < initial_dc_dbrms){ //most likely valid, keep result + result_t result; + result.freq = tx_lo; + result.real_corr = best_dc_i; + result.imag_corr = best_dc_q; + result.best = lowest_offset; + result.delta = initial_dc_dbrms - lowest_offset; + results.push_back(result); + if (vm.count("verbose")){ + std::cout << boost::format("TX DC: %f MHz: lowest offset %f dB, corrected %f dB") % (tx_lo/1e6) % result.best % result.delta << std::endl; + } + else std::cout << "." << std::flush; + } + + } + std::cout << std::endl; + + //stop the transmitter + threads.interrupt_all(); + threads.join_all(); + + store_results(usrp, results, "TX", "tx", "dc"); + + return 0; +} diff --git a/host/utils/uhd_cal_tx_iq_balance.cpp b/host/utils/uhd_cal_tx_iq_balance.cpp new file mode 100644 index 000000000..cff3d1646 --- /dev/null +++ b/host/utils/uhd_cal_tx_iq_balance.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp_cal_utils.hpp" +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/thread/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <iostream> +#include <complex> +#include <ctime> + +namespace po = boost::program_options; + +/*********************************************************************** + * Transmit thread + **********************************************************************/ +static void tx_thread(uhd::usrp::multi_usrp::sptr usrp, const double tx_wave_freq, const double tx_wave_ampl){ + uhd::set_thread_priority_safe(); + + //create a transmit streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::tx_streamer::sptr tx_stream = usrp->get_tx_stream(stream_args); + + //setup variables and allocate buffer + uhd::tx_metadata_t md; + md.has_time_spec = false; + std::vector<samp_type> buff(tx_stream->get_max_num_samps()*10); + + //values for the wave table lookup + size_t index = 0; + const double tx_rate = usrp->get_tx_rate(); + const size_t step = boost::math::iround(wave_table_len * tx_wave_freq/tx_rate); + wave_table table(tx_wave_ampl); + + //fill buff and send until interrupted + while (not boost::this_thread::interruption_requested()){ + for (size_t i = 0; i < buff.size(); i++){ + buff[i] = table(index += step); + } + tx_stream->send(&buff.front(), buff.size(), md); + } + + //send a mini EOB packet + md.end_of_burst = true; + tx_stream->send("", 0, md); +} + +/*********************************************************************** + * Tune RX and TX routine + **********************************************************************/ +static double tune_rx_and_tx(uhd::usrp::multi_usrp::sptr usrp, const double tx_lo_freq, const double rx_offset){ + //tune the transmitter with no cordic + uhd::tune_request_t tx_tune_req(tx_lo_freq); + tx_tune_req.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + tx_tune_req.dsp_freq = 0; + usrp->set_tx_freq(tx_tune_req); + + //tune the receiver + usrp->set_rx_freq(usrp->get_tx_freq() - rx_offset); + + //wait for the LOs to become locked + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + boost::system_time start = boost::get_system_time(); + while (not usrp->get_tx_sensor("lo_locked").to_bool() or not usrp->get_rx_sensor("lo_locked").to_bool()){ + if (boost::get_system_time() > start + boost::posix_time::milliseconds(100)){ + throw std::runtime_error("timed out waiting for TX and/or RX LO to lock"); + } + } + + return usrp->get_tx_freq(); +} + +/*********************************************************************** + * Main + **********************************************************************/ +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string args; + double tx_wave_freq, tx_wave_ampl, rx_offset; + double freq_start, freq_stop, freq_step; + size_t nsamps; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("verbose", "enable some verbose") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("tx_wave_freq", po::value<double>(&tx_wave_freq)->default_value(507.123e3), "Transmit wave frequency in Hz") + ("tx_wave_ampl", po::value<double>(&tx_wave_ampl)->default_value(0.7), "Transmit wave amplitude in counts") + ("rx_offset", po::value<double>(&rx_offset)->default_value(.9344e6), "RX LO offset from the TX LO in Hz") + ("freq_start", po::value<double>(&freq_start), "Frequency start in Hz (do not specify for default)") + ("freq_stop", po::value<double>(&freq_stop), "Frequency stop in Hz (do not specify for default)") + ("freq_step", po::value<double>(&freq_step)->default_value(default_freq_step), "Step size for LO sweep in Hz") + ("nsamps", po::value<size_t>(&nsamps)->default_value(default_num_samps), "Samples per data capture") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP Generate TX IQ Balance Calibration Table %s") % desc << std::endl; + std::cout << + "This application measures leakage between RX and TX on an XCVR daughterboard to self-calibrate.\n" + << std::endl; + return ~0; + } + + //create a usrp device + std::cout << std::endl; + std::cout << boost::format("Creating the usrp device with: %s...") % args << std::endl; + uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); + + //set the antennas to cal + if (not uhd::has(usrp->get_rx_antennas(), "CAL") or not uhd::has(usrp->get_tx_antennas(), "CAL")){ + throw std::runtime_error("This board does not have the CAL antenna option, cannot self-calibrate."); + } + usrp->set_rx_antenna("CAL"); + usrp->set_tx_antenna("CAL"); + + //set optimum defaults + set_optimum_defaults(usrp); + + //create a receive streamer + uhd::stream_args_t stream_args("fc32"); //complex floats + uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); + + //create a transmitter thread + boost::thread_group threads; + threads.create_thread(boost::bind(&tx_thread, usrp, tx_wave_freq, tx_wave_ampl)); + + //re-usable buffer for samples + std::vector<samp_type> buff; + + //store the results here + std::vector<result_t> results; + + if (not vm.count("freq_start")) freq_start = usrp->get_tx_freq_range().start() + 50e6; + if (not vm.count("freq_stop")) freq_stop = usrp->get_tx_freq_range().stop() - 50e6; + + for (double tx_lo_i = freq_start; tx_lo_i <= freq_stop; tx_lo_i += freq_step){ + const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset); + + //frequency constants for this tune event + const double actual_rx_rate = usrp->get_rx_rate(); + const double actual_tx_freq = usrp->get_tx_freq(); + const double actual_rx_freq = usrp->get_rx_freq(); + const double bb_tone_freq = actual_tx_freq + tx_wave_freq - actual_rx_freq; + const double bb_imag_freq = actual_tx_freq - tx_wave_freq - actual_rx_freq; + + //capture initial uncorrected value + usrp->set_tx_iq_balance(0.0); + capture_samples(usrp, rx_stream, buff, nsamps); + const double initial_suppression = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate) - compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + + //bounds and results from searching + std::complex<double> best_correction; + double phase_corr_start = -.3, phase_corr_stop = .3, phase_corr_step; + double ampl_corr_start = -.3, ampl_corr_stop = .3, ampl_corr_step; + double best_suppression = 0, best_phase_corr = 0, best_ampl_corr = 0; + + for (size_t i = 0; i < num_search_iters; i++){ + + phase_corr_step = (phase_corr_stop - phase_corr_start)/(num_search_steps-1); + ampl_corr_step = (ampl_corr_stop - ampl_corr_start)/(num_search_steps-1); + + for (double phase_corr = phase_corr_start; phase_corr <= phase_corr_stop + phase_corr_step/2; phase_corr += phase_corr_step){ + for (double ampl_corr = ampl_corr_start; ampl_corr <= ampl_corr_stop + ampl_corr_step/2; ampl_corr += ampl_corr_step){ + + const std::complex<double> correction(ampl_corr, phase_corr); + usrp->set_tx_iq_balance(correction); + + //receive some samples + capture_samples(usrp, rx_stream, buff, nsamps); + + const double tone_dbrms = compute_tone_dbrms(buff, bb_tone_freq/actual_rx_rate); + const double imag_dbrms = compute_tone_dbrms(buff, bb_imag_freq/actual_rx_rate); + const double suppression = tone_dbrms - imag_dbrms; + + if (suppression > best_suppression){ + best_correction = correction; + best_suppression = suppression; + best_phase_corr = phase_corr; + best_ampl_corr = ampl_corr; + } + + }} + + //std::cout << "best_phase_corr " << best_phase_corr << std::endl; + //std::cout << "best_ampl_corr " << best_ampl_corr << std::endl; + //std::cout << "best_suppression " << best_suppression << std::endl; + + phase_corr_start = best_phase_corr - phase_corr_step; + phase_corr_stop = best_phase_corr + phase_corr_step; + ampl_corr_start = best_ampl_corr - ampl_corr_step; + ampl_corr_stop = best_ampl_corr + ampl_corr_step; + } + + if (best_suppression > 30){ //most likely valid, keep result + result_t result; + result.freq = tx_lo; + result.real_corr = best_correction.real(); + result.imag_corr = best_correction.imag(); + result.best = best_suppression; + result.delta = best_suppression - initial_suppression; + results.push_back(result); + if (vm.count("verbose")){ + std::cout << boost::format("TX IQ: %f MHz: best suppression %f dB, corrected %f dB") % (tx_lo/1e6) % result.best % result.delta << std::endl; + } + else std::cout << "." << std::flush; + } + + } + std::cout << std::endl; + + //stop the transmitter + threads.interrupt_all(); + threads.join_all(); + + store_results(usrp, results, "TX", "tx", "iq"); + + return 0; +} diff --git a/host/utils/uhd_find_devices.cpp b/host/utils/uhd_find_devices.cpp new file mode 100644 index 000000000..b778eeb68 --- /dev/null +++ b/host/utils/uhd_find_devices.cpp @@ -0,0 +1,60 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>()->default_value(""), "device address args") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD Find Devices %s") % desc << std::endl; + return ~0; + } + + //discover the usrps and print the results + uhd::device_addrs_t device_addrs = uhd::device::find(vm["args"].as<std::string>()); + + if (device_addrs.size() == 0){ + std::cerr << "No UHD Devices Found" << std::endl; + return ~0; + } + + for (size_t i = 0; i < device_addrs.size(); i++){ + std::cout << "--------------------------------------------------" << std::endl; + std::cout << "-- UHD Device " << i << std::endl; + std::cout << "--------------------------------------------------" << std::endl; + std::cout << device_addrs[i].to_pp_string() << std::endl << std::endl; + //uhd::device::make(device_addrs[i]); //test make + } + + return 0; +} diff --git a/host/utils/uhd_usrp_probe.cpp b/host/utils/uhd_usrp_probe.cpp new file mode 100644 index 000000000..1bd49a5ff --- /dev/null +++ b/host/utils/uhd_usrp_probe.cpp @@ -0,0 +1,214 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/version.hpp> +#include <uhd/device.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/property_tree.hpp> +#include <boost/algorithm/string.hpp> //for split +#include <uhd/usrp/dboard_id.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <iostream> +#include <sstream> +#include <vector> + +namespace po = boost::program_options; +using namespace uhd; + +static std::string indent(size_t level){ + return (level)? (indent(level-1) + " ") : ""; +} + +static std::string make_border(const std::string &text){ + std::stringstream ss; + ss << boost::format(" _____________________________________________________") << std::endl; + ss << boost::format(" /") << std::endl; + std::vector<std::string> lines; boost::split(lines, text, boost::is_any_of("\n")); + while (lines.back().empty()) lines.pop_back(); //strip trailing newlines + if (lines.size()) lines[0] = " " + lines[0]; //indent the title line + BOOST_FOREACH(const std::string &line, lines){ + ss << boost::format("| %s") % line << std::endl; + } + //ss << boost::format(" \\_____________________________________________________") << std::endl; + return ss.str(); +} + +static std::string get_dsp_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("%s DSP: %s") % type % path.leaf() << std::endl; + //ss << std::endl; + meta_range_t freq_range = tree->access<meta_range_t>(path / "freq/range").get(); + ss << boost::format("Freq range: %.3f to %.3f Mhz") % (freq_range.start()/1e6) % (freq_range.stop()/1e6) << std::endl;; + return ss.str(); +} + +static std::string prop_names_to_pp_string(const std::vector<std::string> &prop_names){ + std::stringstream ss; size_t count = 0; + BOOST_FOREACH(const std::string &prop_name, prop_names){ + ss << ((count++)? ", " : "") << prop_name; + } + return ss.str(); +} + +static std::string get_subdev_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("%s Subdev: %s") % type % path.leaf() << std::endl; + //ss << std::endl; + + ss << boost::format("Name: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; + ss << boost::format("Antennas: %s") % prop_names_to_pp_string(tree->access<std::vector<std::string> >(path / "antenna/options").get()) << std::endl; + ss << boost::format("Sensors: %s") % prop_names_to_pp_string(tree->list(path / "sensors")) << std::endl; + + meta_range_t freq_range = tree->access<meta_range_t>(path / "freq/range").get(); + ss << boost::format("Freq range: %.3f to %.3f Mhz") % (freq_range.start()/1e6) % (freq_range.stop()/1e6) << std::endl; + + std::vector<std::string> gain_names = tree->list(path / "gains"); + if (gain_names.size() == 0) ss << "Gain Elements: None" << std::endl; + BOOST_FOREACH(const std::string &name, gain_names){ + meta_range_t gain_range = tree->access<meta_range_t>(path / "gains" / name / "range").get(); + ss << boost::format("Gain range %s: %.1f to %.1f step %.1f dB") % name % gain_range.start() % gain_range.stop() % gain_range.step() << std::endl; + } + + ss << boost::format("Connection Type: %s") % (tree->access<std::string>(path / "connection").get()) << std::endl; + ss << boost::format("Uses LO offset: %s") % ((tree->access<bool>(path / "use_lo_offset").get())? "Yes" : "No") << std::endl; + + return ss.str(); +} + +static std::string get_codec_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("%s Codec: %s") % type % path.leaf() << std::endl; + //ss << std::endl; + + ss << boost::format("Name: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; + std::vector<std::string> gain_names = tree->list(path / "gains"); + if (gain_names.size() == 0) ss << "Gain Elements: None" << std::endl; + BOOST_FOREACH(const std::string &name, gain_names){ + meta_range_t gain_range = tree->access<meta_range_t>(path / "gains" / name / "range").get(); + ss << boost::format("Gain range %s: %.1f to %.1f step %.1f dB") % name % gain_range.start() % gain_range.stop() % gain_range.step() << std::endl; + } + return ss.str(); +} + +static std::string get_dboard_pp_string(const std::string &type, property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("%s Dboard: %s") % type % path.leaf() << std::endl; + //ss << std::endl; + const std::string prefix = (type == "RX")? "rx" : "tx"; + usrp::dboard_eeprom_t db_eeprom = tree->access<usrp::dboard_eeprom_t>(path / (prefix + "_eeprom")).get(); + if (db_eeprom.id != usrp::dboard_id_t::none()) ss << boost::format("ID: %s") % db_eeprom.id.to_pp_string() << std::endl; + if (not db_eeprom.serial.empty()) ss << boost::format("Serial: %s") % db_eeprom.serial << std::endl; + if (type == "TX"){ + usrp::dboard_eeprom_t gdb_eeprom = tree->access<usrp::dboard_eeprom_t>(path / "gdb_eeprom").get(); + if (gdb_eeprom.id != usrp::dboard_id_t::none()) ss << boost::format("ID: %s") % gdb_eeprom.id.to_pp_string() << std::endl; + if (not gdb_eeprom.serial.empty()) ss << boost::format("Serial: %s") % gdb_eeprom.serial << std::endl; + } + BOOST_FOREACH(const std::string &name, tree->list(path / (prefix + "_frontends"))){ + ss << make_border(get_subdev_pp_string(type, tree, path / (prefix + "_frontends") / name)); + } + ss << make_border(get_codec_pp_string(type, tree, path.branch_path().branch_path() / (prefix + "_codecs") / path.leaf())); + return ss.str(); +} + +static std::string get_mboard_pp_string(property_tree::sptr tree, const fs_path &path){ + std::stringstream ss; + ss << boost::format("Mboard: %s") % (tree->access<std::string>(path / "name").get()) << std::endl; + //ss << std::endl; + usrp::mboard_eeprom_t mb_eeprom = tree->access<usrp::mboard_eeprom_t>(path / "eeprom").get(); + BOOST_FOREACH(const std::string &key, mb_eeprom.keys()){ + if (not mb_eeprom[key].empty()) ss << boost::format("%s: %s") % key % mb_eeprom[key] << std::endl; + } + ss << std::endl; + ss << "Time sources: " << prop_names_to_pp_string(tree->access<std::vector<std::string> >(path / "time_source" / "options").get()) << std::endl; + ss << "Clock sources: " << prop_names_to_pp_string(tree->access<std::vector<std::string> >(path / "clock_source" / "options").get()) << std::endl; + ss << "Sensors: " << prop_names_to_pp_string(tree->list(path / "sensors")) << std::endl; + BOOST_FOREACH(const std::string &name, tree->list(path / "rx_dsps")){ + ss << make_border(get_dsp_pp_string("RX", tree, path / "rx_dsps" / name)); + } + BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ + ss << make_border(get_dboard_pp_string("RX", tree, path / "dboards" / name)); + } + BOOST_FOREACH(const std::string &name, tree->list(path / "tx_dsps")){ + ss << make_border(get_dsp_pp_string("TX", tree, path / "tx_dsps" / name)); + } + BOOST_FOREACH(const std::string &name, tree->list(path / "dboards")){ + ss << make_border(get_dboard_pp_string("TX", tree, path / "dboards" / name)); + } + return ss.str(); +} + + +static std::string get_device_pp_string(property_tree::sptr tree){ + std::stringstream ss; + ss << boost::format("Device: %s") % (tree->access<std::string>("/name").get()) << std::endl; + //ss << std::endl; + BOOST_FOREACH(const std::string &name, tree->list("/mboards")){ + ss << make_border(get_mboard_pp_string(tree, "/mboards/" + name)); + } + return ss.str(); +} + +void print_tree(const uhd::fs_path &path, uhd::property_tree::sptr tree){ + std::cout << path << std::endl; + BOOST_FOREACH(const std::string &name, tree->list(path)){ + print_tree(path / name, tree); + } +} + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("version", "print the version string and exit") + ("args", po::value<std::string>()->default_value(""), "device address args") + ("tree", "specify to print a complete property tree") + ("string", po::value<std::string>(), "query a string value from the properties tree") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("UHD USRP Probe %s") % desc << std::endl; + return ~0; + } + + if (vm.count("version")){ + std::cout << uhd::get_version_string() << std::endl; + return 0; + } + + device::sptr dev = device::make(vm["args"].as<std::string>()); + property_tree::sptr tree = dev->get_tree(); + + if (vm.count("string")){ + std::cout << tree->access<std::string>(vm["string"].as<std::string>()).get() << std::endl; + return 0; + } + + if (vm.count("tree") != 0) print_tree("/", tree); + else std::cout << make_border(get_device_pp_string(tree)) << std::endl; + + return 0; +} diff --git a/host/utils/usrp2_card_burner.py b/host/utils/usrp2_card_burner.py new file mode 100755 index 000000000..8e4a4f224 --- /dev/null +++ b/host/utils/usrp2_card_burner.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import platform +import tempfile +import subprocess +try: + import urllib.request +except ImportError: + import urllib + urllib.request = urllib +import optparse +import math +import os +import re + +######################################################################## +# constants +######################################################################## +SECTOR_SIZE = 512 # bytes +MAX_FILE_SIZE = 1 * (2**20) # maximum number of bytes we'll burn to a slot + +FPGA_OFFSET = 0 # offset in flash to fpga image +FIRMWARE_OFFSET = 1 * (2**20) # offset in flash to firmware image + +MAX_SD_CARD_SIZE = 2048e6 # bytes (any bigger is sdhc) + +######################################################################## +# helper functions +######################################################################## +def command(*args): + p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + ret = p.wait() + verbose = p.stdout.read().decode('utf-8') + if ret != 0: raise Exception(verbose) + return verbose + +def get_dd_path(): + if platform.system() == 'Windows': + dd_path = os.path.join(os.path.dirname(__file__), 'dd.exe') + if os.path.exists(dd_path): return dd_path + dd_path = os.path.join(tempfile.gettempdir(), 'dd.exe') + if not os.path.exists(dd_path): + print('Downloading dd.exe to %s'%dd_path) + dd_bin = urllib.request.urlopen('http://files.ettus.com/dd.exe').read() + open(dd_path, 'wb').write(dd_bin) + return dd_path + return 'dd' + +def int_ceil_div(num, den): + return int(math.ceil(float(num)/float(den))) + +def get_tmp_file(): + tmp = tempfile.mkstemp() + os.close(tmp[0]) + return tmp[1] + +######################################################################## +# list possible devices +######################################################################## +def get_raw_device_hints(): + #################################################################### + # Platform Windows: parse the output of dd.exe --list + #################################################################### + if platform.system() == 'Windows': + def extract_info_value(info, key): + return info.split(key)[-1].split()[0] + def get_info_list(output): + in_info = False + for line in output.splitlines(): + if line.startswith('\\\\'): in_info = True; info = '' + elif in_info and not line.strip(): in_info = False; yield info + if in_info: info += '\n'+line.strip() + def is_info_valid(info): + try: + if 'link to' not in info: return False + #handles two spellings of remov(e)able: + if 'remov' not in info.lower(): return False + if 'size is' in info and int(extract_info_value(info, 'size is')) > MAX_SD_CARD_SIZE: return False + except: return False + return True + def extract_info_name(info): + for key in ('Mounted on', 'link to'): + if key in info: return extract_info_value(info, key) + return info.splitlines()[0].strip() + + return sorted(set(map(extract_info_name, list(filter(is_info_valid, get_info_list(command(get_dd_path(), '--list'))))))) + + #################################################################### + # Platform Linux: parse procfs /proc/partitions + #################################################################### + if platform.system() == 'Linux': + devs = list() + for line in command('cat', '/proc/partitions').splitlines(): + try: + major, minor, blocks, name = line.split() + if not name[-1].isdigit() and int(minor) == 0: continue + if int(blocks)*1024 > MAX_SD_CARD_SIZE: continue + except: continue + devs.append(os.path.join('/dev', name)) + + return sorted(set(devs)) + + #################################################################### + # Platform Mac OS X: parse diskutil list and info commands + #################################################################### + if platform.system() == 'Darwin': + devs = [d.split()[0] for d in [l for l in command('diskutil', 'list').splitlines() if l.startswith('/dev')]] + def output_to_info(output): + return dict([list(map(lambda x: x.strip(), pair.lower().split(':'))) for pair in [l for l in output.splitlines() if ':' in l]]) + def is_dev_valid(dev): + info = output_to_info(command('diskutil', 'info', dev)) + try: + if 'internal' in info and info['internal'] == 'yes': return False + if 'ejectable' in info and info['ejectable'] == 'no': return False + if 'total size' in info: + size_match = re.match('^.*\((\d+)\s*bytes\).*$', info['total size']) + if size_match and int(size_match.groups()[0]) > MAX_SD_CARD_SIZE: return False + except: return False + return True + + return sorted(set(filter(is_dev_valid, devs))) + + #################################################################### + # Platform Others: + #################################################################### + return () + +######################################################################## +# write and verify with dd +######################################################################## +def verify_image(image_file, device_file, offset): + #create a temporary file to store the readback image + tmp_file = get_tmp_file() + + #read the image data + img_data = open(image_file, 'rb').read() + count = int_ceil_div(len(img_data), SECTOR_SIZE) + + #execute a dd subprocess + verbose = command( + get_dd_path(), + "of=%s"%tmp_file, + "if=%s"%device_file, + "skip=%d"%(offset/SECTOR_SIZE), + "bs=%d"%SECTOR_SIZE, + "count=%d"%count, + ) + + #verfy the data + tmp_data = open(tmp_file, 'rb').read(len(img_data)) + if img_data != tmp_data: return 'Verification Failed:\n%s'%verbose + return 'Verification Passed:\n%s'%verbose + +def write_image(image_file, device_file, offset): + #create a temporary file to store the padded image + tmp_file = get_tmp_file() + + #write the padded image data + img_data = open(image_file, 'rb').read() + count = int_ceil_div(len(img_data), SECTOR_SIZE) + pad_len = SECTOR_SIZE*count - len(img_data) + padding = bytes(b'\x00')*pad_len #zero-padding + open(tmp_file, 'wb').write(img_data + padding) + + #execute a dd subprocess + verbose = command( + get_dd_path(), + "if=%s"%tmp_file, + "of=%s"%device_file, + "seek=%d"%(offset/SECTOR_SIZE), + "bs=%d"%SECTOR_SIZE, + "count=%d"%count, + ) + + try: #exec the sync command (only works on linux) + if platform.system() == 'Linux': command('sync') + except: pass + + return verbose + +def write_and_verify(image_file, device_file, offset): + if os.path.getsize(image_file) > MAX_FILE_SIZE: + raise Exception('Image file larger than %d bytes!'%MAX_FILE_SIZE) + return '%s\n%s'%( + write_image( + image_file=image_file, + device_file=device_file, + offset=offset, + ), verify_image( + image_file=image_file, + device_file=device_file, + offset=offset, + ), + ) + +def burn_sd_card(dev, fw, fpga): + verbose = '' + if fw: verbose += 'Burn firmware image:\n%s\n'%write_and_verify( + image_file=fw, device_file=dev, offset=FIRMWARE_OFFSET + ) + if fpga: verbose += 'Burn fpga image:\n%s\n'%write_and_verify( + image_file=fpga, device_file=dev, offset=FPGA_OFFSET + ) + return verbose + +######################################################################## +# command line options +######################################################################## +def get_options(): + parser = optparse.OptionParser() + parser.add_option("--dev", type="string", help="raw device path", default='') + parser.add_option("--fw", type="string", help="firmware image path (optional)", default='') + parser.add_option("--fpga", type="string", help="fpga image path (optional)", default='') + parser.add_option("--list", action="store_true", help="list possible raw devices", default=False) + parser.add_option("--force", action="store_true", help="override safety check", default=False) + (options, args) = parser.parse_args() + + return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = get_options() + device_hints = get_raw_device_hints() + show_listing = options.list + + if not show_listing and not options.force and options.dev and options.dev not in device_hints: + print('The device "%s" was not in the list of possible raw devices.'%options.dev) + print('The card burner application will now exit without burning your card.') + print('To override this safety check, specify the --force option.\n') + show_listing = True + + if show_listing: + print('Possible raw devices:') + print(' ' + '\n '.join(device_hints)) + exit() + + if not options.dev: raise Exception('no raw device path specified') + print(burn_sd_card(dev=options.dev, fw=options.fw, fpga=options.fpga)) diff --git a/host/utils/usrp2_card_burner_gui.py b/host/utils/usrp2_card_burner_gui.py new file mode 100755 index 000000000..2941629b9 --- /dev/null +++ b/host/utils/usrp2_card_burner_gui.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import usrp2_card_burner #import implementation +try: + import tkinter, tkinter.filedialog, tkinter.font, tkinter.messagebox +except ImportError: + import tkFileDialog, tkFont, tkMessageBox + import Tkinter as tkinter + tkinter.filedialog = tkFileDialog + tkinter.font = tkFont + tkinter.messagebox = tkMessageBox +import os + +class BinFileEntry(tkinter.Frame): + """ + Simple file entry widget for getting the file path of bin files. + Combines a label, entry, and button with file dialog callback. + """ + + def __init__(self, root, what, def_path=''): + self._what = what + tkinter.Frame.__init__(self, root) + tkinter.Label(self, text=what+":").pack(side=tkinter.LEFT) + self._entry = tkinter.Entry(self, width=50) + self._entry.insert(tkinter.END, def_path) + self._entry.pack(side=tkinter.LEFT) + tkinter.Button(self, text="...", command=self._button_cb).pack(side=tkinter.LEFT) + + def _button_cb(self): + filename = tkinter.filedialog.askopenfilename( + parent=self, + filetypes=[('bin files', '*.bin'), ('all files', '*.*')], + title="Select bin file for %s"%self._what, + initialdir=os.path.dirname(self.get_filename()), + ) + + # open file on your own + if filename: + self._entry.delete(0, tkinter.END) + self._entry.insert(0, filename) + + def get_filename(self): + return self._entry.get() + +class DeviceEntryWidget(tkinter.Frame): + """ + Simple entry widget for getting the raw device name. + Combines a label, entry, and helpful text box with hints. + """ + + def __init__(self, root, text=''): + tkinter.Frame.__init__(self, root) + + tkinter.Button(self, text="Rescan for Devices", command=self._reload_cb).pack() + + self._hints = tkinter.Listbox(self) + self._hints.bind("<<ListboxSelect>>", self._listbox_cb) + self._reload_cb() + self._hints.pack(expand=tkinter.YES, fill=tkinter.X) + + frame = tkinter.Frame(self) + frame.pack() + + tkinter.Label(frame, text="Raw Device:").pack(side=tkinter.LEFT) + self._entry = tkinter.Entry(frame, width=50) + self._entry.insert(tkinter.END, text) + self._entry.pack(side=tkinter.LEFT) + + def _reload_cb(self): + self._hints.delete(0, tkinter.END) + for hint in usrp2_card_burner.get_raw_device_hints(): + self._hints.insert(tkinter.END, hint) + + def _listbox_cb(self, event): + try: + sel = self._hints.get(self._hints.curselection()[0]) + self._entry.delete(0, tkinter.END) + self._entry.insert(0, sel) + except Exception as e: print(e) + + def get_devname(self): + return self._entry.get() + +class SectionLabel(tkinter.Label): + """ + Make a text label with bold font. + """ + + def __init__(self, root, text): + tkinter.Label.__init__(self, root, text=text) + + #set the font bold + f = tkinter.font.Font(font=self['font']) + f['weight'] = 'bold' + self['font'] = f.name + +class USRP2CardBurnerApp(tkinter.Frame): + """ + The top level gui application for the usrp2 sd card burner. + Creates entry widgets and button with callback to write images. + """ + + def __init__(self, root, dev, fw, fpga): + + tkinter.Frame.__init__(self, root) + + #pack the file entry widgets + SectionLabel(self, text="Select Images").pack(pady=5) + self._fw_img_entry = BinFileEntry(self, "Firmware Image", def_path=fw) + self._fw_img_entry.pack() + self._fpga_img_entry = BinFileEntry(self, "FPGA Image", def_path=fpga) + self._fpga_img_entry.pack() + + #pack the destination entry widget + SectionLabel(self, text="Select Device").pack(pady=5) + self._raw_dev_entry = DeviceEntryWidget(self, text=dev) + self._raw_dev_entry.pack() + + #the do it button + SectionLabel(self, text="").pack(pady=5) + tkinter.Label(self, text="Warning! This tool can overwrite your hard drive. Use with caution.").pack() + tkinter.Button(self, text="Burn SD Card", command=self._burn).pack() + + def _burn(self): + #grab strings from the gui + fw = self._fw_img_entry.get_filename() + fpga = self._fpga_img_entry.get_filename() + dev = self._raw_dev_entry.get_devname() + + #check input + if not dev: + tkinter.messagebox.showerror('Error:', 'No device specified!') + return + if not fw and not fpga: + tkinter.messagebox.showerror('Error:', 'No images specified!') + return + if fw and not os.path.exists(fw): + tkinter.messagebox.showerror('Error:', 'Firmware image not found!') + return + if fpga and not os.path.exists(fpga): + tkinter.messagebox.showerror('Error:', 'FPGA image not found!') + return + + #burn the sd card + try: + verbose = usrp2_card_burner.burn_sd_card(dev=dev, fw=fw, fpga=fpga) + tkinter.messagebox.showinfo('Verbose:', verbose) + except Exception as e: + tkinter.messagebox.showerror('Verbose:', 'Error: %s'%str(e)) + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = usrp2_card_burner.get_options() + root = tkinter.Tk() + root.title('USRP2 SD Card Burner') + USRP2CardBurnerApp(root, dev=options.dev, fw=options.fw, fpga=options.fpga).pack() + root.mainloop() + exit() diff --git a/host/utils/usrp2_recovery.py b/host/utils/usrp2_recovery.py new file mode 100755 index 000000000..c7578d3a0 --- /dev/null +++ b/host/utils/usrp2_recovery.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +""" +The usrp2 recovery app: + +When the usrp2 has an unknown or bad ip address in its eeprom, +it may not be possible to communicate with the usrp2 over ip/udp. + +This app will send a raw ethernet packet to bypass the ip layer. +The packet will contain a known ip address to burn into eeprom. +Because the recovery packet is sent with a broadcast mac address, +only one usrp2 should be present on the interface upon execution. + +This app requires super-user privileges and only works on linux. +""" + +import socket +import struct +import optparse + +BCAST_MAC_ADDR = 'ff:ff:ff:ff:ff:ff' +RECOVERY_ETHERTYPE = 0xbeee +IP_RECOVERY_CODE = 'addr' + +def mac_addr_repr_to_binary_string(mac_addr): + return ''.join([chr(int(x, 16)) for x in mac_addr.split(':')]) + +if __name__ == '__main__': + parser = optparse.OptionParser(usage='usage: %prog [options]\n'+__doc__) + parser.add_option('--ifc', type='string', help='ethernet interface name [default=%default]', default='eth0') + parser.add_option('--new-ip', type='string', help='ip address to set [default=%default]', default='192.168.10.2') + (options, args) = parser.parse_args() + + #create the raw socket + print("Opening raw socket on interface:", options.ifc) + soc = socket.socket(socket.PF_PACKET, socket.SOCK_RAW) + soc.bind((options.ifc, RECOVERY_ETHERTYPE)) + + #create the recovery packet + print("Loading packet with ip address:", options.new_ip) + packet = struct.pack( + '!6s6sH4s4s', + mac_addr_repr_to_binary_string(BCAST_MAC_ADDR), + mac_addr_repr_to_binary_string(BCAST_MAC_ADDR), + RECOVERY_ETHERTYPE, + IP_RECOVERY_CODE, + socket.inet_aton(options.new_ip), + ) + + print("Sending packet (%d bytes)"%len(packet)) + soc.send(packet) + print("Done") diff --git a/host/utils/usrp_burn_db_eeprom.cpp b/host/utils/usrp_burn_db_eeprom.cpp new file mode 100644 index 000000000..b6b2dc4d6 --- /dev/null +++ b/host/utils/usrp_burn_db_eeprom.cpp @@ -0,0 +1,100 @@ +// +// Copyright 2010-2011 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <boost/assign.hpp> +#include <iostream> + +using namespace uhd; +using namespace uhd::usrp; +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + //command line variables + std::string args, slot, unit; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("slot", po::value<std::string>(&slot)->default_value(""), "dboard slot name [default is blank for automatic]") + ("unit", po::value<std::string>(&unit)->default_value(""), "which unit [RX, TX, or GDB]") + ("id", po::value<std::string>(), "dboard id to burn, omit for readback") + ("ser", po::value<std::string>(), "serial to burn, omit for readback") + ("rev", po::value<std::string>(), "revision to burn, omit for readback") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help")){ + std::cout << boost::format("USRP Burn Daughterboard EEPROM %s") % desc << std::endl; + std::cout << boost::format( + "Omit the ID argument to perform readback,\n" + "Or specify a new ID to burn into the EEPROM.\n" + ) << std::endl; + return ~0; + } + + //make the device and extract the dboard w/ property + device::sptr dev = device::make(args); + uhd::property_tree::sptr tree = dev->get_tree(); + const uhd::fs_path db_root = "/mboards/0/dboards"; + std::vector<std::string> dboard_names = tree->list(db_root); + if (dboard_names.size() == 1 and slot.empty()) slot = dboard_names.front(); + uhd::assert_has(dboard_names, slot, "dboard slot name"); + + std::cout << boost::format("Reading %s EEPROM on %s dboard...") % unit % slot << std::endl; + boost::to_lower(unit); + const uhd::fs_path db_path = db_root / slot / (unit + "_eeprom"); + dboard_eeprom_t db_eeprom = tree->access<dboard_eeprom_t>(db_path).get(); + + //------------- handle the dboard ID -----------------------------// + if (vm.count("id")){ + db_eeprom.id = dboard_id_t::from_string(vm["id"].as<std::string>()); + tree->access<dboard_eeprom_t>(db_path).set(db_eeprom); + } + std::cout << boost::format(" Current ID: %s") % db_eeprom.id.to_pp_string() << std::endl; + + //------------- handle the dboard serial--------------------------// + if (vm.count("ser")){ + db_eeprom.serial = vm["ser"].as<std::string>(); + tree->access<dboard_eeprom_t>(db_path).set(db_eeprom); + } + std::cout << boost::format(" Current serial: \"%s\"") % db_eeprom.serial << std::endl; + + //------------- handle the dboard revision------------------------// + if (vm.count("rev")){ + db_eeprom.revision = vm["rev"].as<std::string>(); + tree->access<dboard_eeprom_t>(db_path).set(db_eeprom); + } + std::cout << boost::format(" Current revision: \"%s\"") % db_eeprom.revision << std::endl; + + std::cout << " Done" << std::endl << std::endl; + return 0; +} diff --git a/host/utils/usrp_burn_mb_eeprom.cpp b/host/utils/usrp_burn_mb_eeprom.cpp new file mode 100644 index 000000000..ca9a6c8ba --- /dev/null +++ b/host/utils/usrp_burn_mb_eeprom.cpp @@ -0,0 +1,78 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/safe_main.hpp> +#include <uhd/device.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> + +namespace po = boost::program_options; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ + std::string args, key, val; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "help message") + ("args", po::value<std::string>(&args)->default_value(""), "device address args [default = \"\"]") + ("key", po::value<std::string>(&key), "the indentifier for a value in EEPROM") + ("val", po::value<std::string>(&val), "the new value to set, omit for readback") + ; + + po::variables_map vm; + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + + //print the help message + if (vm.count("help") or not vm.count("key")){ + std::cout << boost::format("USRP Burn Motherboard EEPROM %s") % desc << std::endl; + std::cout << boost::format( + "Omit the value argument to perform a readback,\n" + "Or specify a new value to burn into the EEPROM.\n" + ) << std::endl; + return ~0; + } + + std::cout << "Creating USRP device from address: " + args << std::endl; + uhd::device::sptr dev = uhd::device::make(args); + uhd::property_tree::sptr tree = dev->get_tree(); + std::cout << std::endl; + + if (true /*always readback*/){ + std::cout << "Fetching current settings from EEPROM..." << std::endl; + uhd::usrp::mboard_eeprom_t mb_eeprom = tree->access<uhd::usrp::mboard_eeprom_t>("/mboards/0/eeprom").get(); + if (not mb_eeprom.has_key(key)){ + std::cerr << boost::format("Cannot find value for EEPROM[%s]") % key << std::endl; + return ~0; + } + std::cout << boost::format(" EEPROM [\"%s\"] is \"%s\"") % key % mb_eeprom[key] << std::endl; + std::cout << std::endl; + } + if (vm.count("val")){ + uhd::usrp::mboard_eeprom_t mb_eeprom; mb_eeprom[key] = val; + std::cout << boost::format("Setting EEPROM [\"%s\"] to \"%s\"...") % key % val << std::endl; + tree->access<uhd::usrp::mboard_eeprom_t>("/mboards/0/eeprom").set(mb_eeprom); + std::cout << "Power-cycle the USRP device for the changes to take effect." << std::endl; + std::cout << std::endl; + } + + std::cout << "Done" << std::endl; + return 0; +} diff --git a/host/utils/usrp_cal_utils.hpp b/host/utils/usrp_cal_utils.hpp new file mode 100644 index 000000000..825d94d64 --- /dev/null +++ b/host/utils/usrp_cal_utils.hpp @@ -0,0 +1,217 @@ +// +// Copyright 2010 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/paths.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <boost/filesystem.hpp> +#include <iostream> +#include <vector> +#include <complex> +#include <cmath> +#include <fstream> + +namespace fs = boost::filesystem; + +struct result_t{double freq, real_corr, imag_corr, best, delta;}; + +typedef std::complex<float> samp_type; + +/*********************************************************************** + * Constants + **********************************************************************/ +static const double tau = 6.28318531; +static const size_t wave_table_len = 8192; +static const size_t num_search_steps = 5; +static const size_t num_search_iters = 7; +static const double default_freq_step = 7.3e6; +static const size_t default_num_samps = 10000; + +/*********************************************************************** + * Set standard defaults for devices + **********************************************************************/ +static inline void set_optimum_defaults(uhd::usrp::multi_usrp::sptr usrp){ + uhd::property_tree::sptr tree = usrp->get_device()->get_tree(); + + const uhd::fs_path mb_path = "/mboards/0"; + const std::string mb_name = tree->access<std::string>(mb_path / "name").get(); + if (mb_name.find("USRP2") != std::string::npos or mb_name.find("N200") != std::string::npos or mb_name.find("N210") != std::string::npos){ + usrp->set_tx_rate(12.5e6); + usrp->set_rx_rate(12.5e6); + } + else if (mb_name.find("B100") != std::string::npos){ + usrp->set_tx_rate(4e6); + usrp->set_rx_rate(4e6); + } + else if (mb_name.find("E100") != std::string::npos or mb_name.find("E110") != std::string::npos){ + usrp->set_tx_rate(4e6); + usrp->set_rx_rate(8e6); + } + else{ + throw std::runtime_error("self-calibration is not supported for this hardware"); + } + + const uhd::fs_path tx_fe_path = "/mboards/0/dboards/A/tx_frontends/0"; + const std::string tx_name = tree->access<std::string>(tx_fe_path / "name").get(); + if (tx_name.find("WBX") != std::string::npos or tx_name.find("SBX") != std::string::npos){ + usrp->set_tx_gain(0); + } + else{ + throw std::runtime_error("self-calibration is not supported for this hardware"); + } + + const uhd::fs_path rx_fe_path = "/mboards/0/dboards/A/tx_frontends/0"; + const std::string rx_name = tree->access<std::string>(rx_fe_path / "name").get(); + if (rx_name.find("WBX") != std::string::npos or rx_name.find("SBX") != std::string::npos){ + usrp->set_rx_gain(25); + } + else{ + throw std::runtime_error("self-calibration is not supported for this hardware"); + } + +} + +/*********************************************************************** + * Sinusoid wave table + **********************************************************************/ +class wave_table{ +public: + wave_table(const double ampl){ + _table.resize(wave_table_len); + for (size_t i = 0; i < wave_table_len; i++){ + _table[i] = samp_type(std::polar(ampl, (tau*i)/wave_table_len)); + } + } + + inline samp_type operator()(const size_t index) const{ + return _table[index % wave_table_len]; + } + +private: + std::vector<samp_type > _table; +}; + +/*********************************************************************** + * Compute power of a tone + **********************************************************************/ +static inline double compute_tone_dbrms( + const std::vector<samp_type > &samples, + const double freq //freq is fractional +){ + //shift the samples so the tone at freq is down at DC + //and average the samples to measure the DC component + samp_type average = 0; + for (size_t i = 0; i < samples.size(); i++){ + average += samp_type(std::polar(1.0, -freq*tau*i)) * samples[i]; + } + + return 20*std::log10(std::abs(average/float(samples.size()))); +} + +/*********************************************************************** + * Write a dat file + **********************************************************************/ +static inline void write_samples_to_file( + const std::vector<samp_type > &samples, const std::string &file +){ + std::ofstream outfile(file.c_str(), std::ofstream::binary); + outfile.write((const char*)&samples.front(), samples.size()*sizeof(samp_type)); + outfile.close(); +} + +/*********************************************************************** + * Store data to file + **********************************************************************/ +static void store_results( + uhd::usrp::multi_usrp::sptr usrp, + const std::vector<result_t> &results, + const std::string &XX, + const std::string &xx, + const std::string &what +){ + //extract eeprom serial + uhd::property_tree::sptr tree = usrp->get_device()->get_tree(); + const uhd::fs_path db_path = "/mboards/0/dboards/A/" + xx + "_eeprom"; + const uhd::usrp::dboard_eeprom_t db_eeprom = tree->access<uhd::usrp::dboard_eeprom_t>(db_path).get(); + if (db_eeprom.serial.empty()) throw std::runtime_error(XX + " dboard has empty serial!"); + + //make the calibration file path + fs::path cal_data_path = fs::path(uhd::get_app_path()) / ".uhd"; + fs::create_directory(cal_data_path); + cal_data_path = cal_data_path / "cal"; + fs::create_directory(cal_data_path); + cal_data_path = cal_data_path / str(boost::format("%s_%s_cal_v0.2_%s.csv") % xx % what % db_eeprom.serial); + if (fs::exists(cal_data_path)){ + fs::rename(cal_data_path, cal_data_path.string() + str(boost::format(".%d") % time(NULL))); + } + + //fill the calibration file + std::ofstream cal_data(cal_data_path.string().c_str()); + cal_data << boost::format("name, %s Frontend Calibration\n") % XX; + cal_data << boost::format("serial, %s\n") % db_eeprom.serial; + cal_data << boost::format("timestamp, %d\n") % time(NULL); + cal_data << boost::format("version, 0, 1\n"); + cal_data << boost::format("DATA STARTS HERE\n"); + cal_data << "lo_frequency, correction_real, correction_imag, measured, delta\n"; + + for (size_t i = 0; i < results.size(); i++){ + cal_data + << results[i].freq << ", " + << results[i].real_corr << ", " + << results[i].imag_corr << ", " + << results[i].best << ", " + << results[i].delta << "\n" + ; + } + + std::cout << "wrote cal data to " << cal_data_path << std::endl; +} + +/*********************************************************************** + * Data capture routine + **********************************************************************/ +static void capture_samples( + uhd::usrp::multi_usrp::sptr usrp, + uhd::rx_streamer::sptr rx_stream, + std::vector<samp_type > &buff, + const size_t nsamps_requested +){ + buff.resize(nsamps_requested); + uhd::rx_metadata_t md; + + uhd::stream_cmd_t stream_cmd(uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE); + stream_cmd.num_samps = buff.size(); + stream_cmd.stream_now = true; + usrp->issue_stream_cmd(stream_cmd); + const size_t num_rx_samps = rx_stream->recv(&buff.front(), buff.size(), md); + + //validate the received data + if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE){ + throw std::runtime_error(str(boost::format( + "Unexpected error code 0x%x" + ) % md.error_code)); + } + //we can live if all the data didnt come in + if (num_rx_samps > buff.size()/2){ + buff.resize(num_rx_samps); + return; + } + if (num_rx_samps != buff.size()){ + throw std::runtime_error("did not get all the samples requested"); + } +} diff --git a/host/utils/usrp_n2xx_net_burner.py b/host/utils/usrp_n2xx_net_burner.py new file mode 100755 index 000000000..f2cfb8ecf --- /dev/null +++ b/host/utils/usrp_n2xx_net_burner.py @@ -0,0 +1,524 @@ +#!/usr/bin/env python +# +# Copyright 2010-2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +# TODO: make it autodetect UHD devices + +import optparse +import math +import os +import re +import struct +import socket +import sys +import time +import platform +import subprocess + +######################################################################## +# constants +######################################################################## +UDP_FW_UPDATE_PORT = 49154 +UDP_MAX_XFER_BYTES = 1024 +UDP_TIMEOUT = 3 +UDP_POLL_INTERVAL = 0.10 #in seconds + +USRP2_FW_PROTO_VERSION = 7 #should be unused after r6 + +#from bootloader_utils.h + +FPGA_IMAGE_SIZE_BYTES = 1572864 +FW_IMAGE_SIZE_BYTES = 31744 +SAFE_FPGA_IMAGE_LOCATION_ADDR = 0x00000000 +SAFE_FW_IMAGE_LOCATION_ADDR = 0x003F0000 +PROD_FPGA_IMAGE_LOCATION_ADDR = 0x00180000 +PROD_FW_IMAGE_LOCATION_ADDR = 0x00300000 + +FLASH_DATA_PACKET_SIZE = 256 + +#see fw_common.h +FLASH_ARGS_FMT = '!LLLLL256s' +FLASH_INFO_FMT = '!LLLLL256x' +FLASH_IP_FMT = '!LLLL260x' +FLASH_HW_REV_FMT = '!LLLL260x' + +n2xx_revs = { + 0x0a00: ["n200_r3", "n200_r2"], + 0x0a10: ["n200_r4"], + 0x0a01: ["n210_r3", "n210_r2"], + 0x0a11: ["n210_r4"] + } + +class update_id_t: + USRP2_FW_UPDATE_ID_WAT = ord(' ') + USRP2_FW_UPDATE_ID_OHAI_LOL = ord('a') + USRP2_FW_UPDATE_ID_OHAI_OMG = ord('A') + USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL = ord('f') + USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG = ord('F') + USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL = ord('e') + USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG = ord('E') + USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL = ord('d') + USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG = ord('D') + USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG = ord('B') + USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL = ord('w') + USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG = ord('W') + USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL = ord('r') + USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG = ord('R') + USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL = ord('s') + USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG = ord('S') + USRP2_FW_UPDATE_ID_I_CAN_HAS_HW_REV_LOL = ord('v') + USRP2_FW_UPDATE_ID_HERES_TEH_HW_REV_OMG = ord('V') + USRP2_FW_UPDATE_ID_KTHXBAI = ord('~') + +_seq = -1 +def seq(): + global _seq + _seq = _seq+1 + return _seq + +######################################################################## +# helper functions +######################################################################## +def unpack_flash_args_fmt(s): + return struct.unpack(FLASH_ARGS_FMT, s) #(proto_ver, pktid, seq, flash_addr, length, data) + +def unpack_flash_info_fmt(s): + return struct.unpack(FLASH_INFO_FMT, s) #(proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes) + +def unpack_flash_ip_fmt(s): + return struct.unpack(FLASH_IP_FMT, s) #(proto_ver, pktid, seq, ip_addr) + +def unpack_flash_hw_rev_fmt(s): + return struct.unpack(FLASH_HW_REV_FMT, s) #proto_ver, pktid, seq, hw_rev + +def pack_flash_args_fmt(proto_ver, pktid, seq, flash_addr, length, data=bytes()): + return struct.pack(FLASH_ARGS_FMT, proto_ver, pktid, seq, flash_addr, length, data) + +def pack_flash_info_fmt(proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes): + return struct.pack(FLASH_INFO_FMT, proto_ver, pktid, seq, sector_size_bytes, memory_size_bytes) + +def pack_flash_hw_rev_fmt(proto_ver, pktid, seq, hw_rev): + return struct.pack(FLASH_HW_REV_FMT, proto_ver, pktid, seq, hw_rev) + +def is_valid_fpga_image(fpga_image): + for i in range(0,63): + if fpga_image[i:i+1] == bytes(b'\xFF'): continue + if fpga_image[i:i+2] == bytes(b'\xAA\x99'): return True + return False + +def is_valid_fw_image(fw_image): + return fw_image[:4] == bytes(b'\x0B\x0B\x0B\x0B') + + +######################################################################## +# interface discovery and device enumeration +######################################################################## +def command(*args): + p = subprocess.Popen( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + ret = p.wait() + verbose = p.stdout.read().decode('utf-8') + if ret != 0: raise Exception(verbose) + return verbose + +def get_interfaces(): + if(platform.system() is "Windows"): return win_get_interfaces() + else: return unix_get_interfaces() + +def unix_get_interfaces(): + ifconfig = command("/sbin/ifconfig") + ip_addr_re = "cast\D*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" + bcasts = re.findall(ip_addr_re, ifconfig) + return bcasts + +def win_get_interfaces(): + from ctypes import Structure, windll, sizeof + from ctypes import POINTER, byref + from ctypes import c_ulong, c_uint, c_ubyte, c_char + MAX_ADAPTER_DESCRIPTION_LENGTH = 128 + MAX_ADAPTER_NAME_LENGTH = 256 + MAX_ADAPTER_ADDRESS_LENGTH = 8 + class IP_ADDR_STRING(Structure): + pass + LP_IP_ADDR_STRING = POINTER(IP_ADDR_STRING) + IP_ADDR_STRING._fields_ = [ + ("next", LP_IP_ADDR_STRING), + ("ipAddress", c_char * 16), + ("ipMask", c_char * 16), + ("context", c_ulong)] + class IP_ADAPTER_INFO (Structure): + pass + LP_IP_ADAPTER_INFO = POINTER(IP_ADAPTER_INFO) + IP_ADAPTER_INFO._fields_ = [ + ("next", LP_IP_ADAPTER_INFO), + ("comboIndex", c_ulong), + ("adapterName", c_char * (MAX_ADAPTER_NAME_LENGTH + 4)), + ("description", c_char * (MAX_ADAPTER_DESCRIPTION_LENGTH + 4)), + ("addressLength", c_uint), + ("address", c_ubyte * MAX_ADAPTER_ADDRESS_LENGTH), + ("index", c_ulong), + ("type", c_uint), + ("dhcpEnabled", c_uint), + ("currentIpAddress", LP_IP_ADDR_STRING), + ("ipAddressList", IP_ADDR_STRING), + ("gatewayList", IP_ADDR_STRING), + ("dhcpServer", IP_ADDR_STRING), + ("haveWins", c_uint), + ("primaryWinsServer", IP_ADDR_STRING), + ("secondaryWinsServer", IP_ADDR_STRING), + ("leaseObtained", c_ulong), + ("leaseExpires", c_ulong)] + GetAdaptersInfo = windll.iphlpapi.GetAdaptersInfo + GetAdaptersInfo.restype = c_ulong + GetAdaptersInfo.argtypes = [LP_IP_ADAPTER_INFO, POINTER(c_ulong)] + adapterList = (IP_ADAPTER_INFO * 10)() + buflen = c_ulong(sizeof(adapterList)) + rc = GetAdaptersInfo(byref(adapterList[0]), byref(buflen)) + if rc == 0: + for a in adapterList: + adNode = a.ipAddressList + while True: + #convert ipAddr and ipMask into hex addrs that can be turned into a bcast addr + try: + ipAddr = adNode.ipAddress.decode() + ipMask = adNode.ipMask.decode() + except: ipAddr = None + if ipAddr and ipMask: + hexAddr = struct.unpack("<L", socket.inet_aton(ipAddr))[0] + hexMask = struct.unpack("<L", socket.inet_aton(ipMask))[0] + if(hexAddr and hexMask): #don't broadcast on 255.255.255.255, that's just lame + yield socket.inet_ntoa(struct.pack("<L", (hexAddr & hexMask) | (~hexMask) & 0xFFFFFFFF)) + try: adNode = adNode.next + except: break + if not adNode: break + +def enumerate_devices(): + for bcast_addr in get_interfaces(): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + sock.settimeout(0.1) + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_OHAI_LOL, 0, 0, 0) + sock.sendto(out_pkt, (bcast_addr, UDP_FW_UPDATE_PORT)) + still_goin = True + while(still_goin): + try: + pkt = sock.recv(UDP_MAX_XFER_BYTES) + (proto_ver, pktid, rxseq, ip_addr) = unpack_flash_ip_fmt(pkt) + if(pktid == update_id_t.USRP2_FW_UPDATE_ID_OHAI_OMG): + yield socket.inet_ntoa(struct.pack("<L", socket.ntohl(ip_addr))) + except socket.timeout: + still_goin = False + +######################################################################## +# Burner class, holds a socket and send/recv routines +######################################################################## +class burner_socket(object): + def __init__(self, addr): + self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self._sock.settimeout(UDP_TIMEOUT) + self._sock.connect((addr, UDP_FW_UPDATE_PORT)) + self.set_callbacks(lambda *a: None, lambda *a: None) + self.init_update() #check that the device is there + self.get_hw_rev() + + def set_callbacks(self, progress_cb, status_cb): + self._progress_cb = progress_cb + self._status_cb = status_cb + + def send_and_recv(self, pkt): + self._sock.send(pkt) + return self._sock.recv(UDP_MAX_XFER_BYTES) + + #just here to validate comms + def init_update(self): + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_OHAI_LOL, seq(), 0, 0) + try: in_pkt = self.send_and_recv(out_pkt) + except socket.timeout: raise Exception("No response from device") + (proto_ver, pktid, rxseq, ip_addr) = unpack_flash_ip_fmt(in_pkt) + if pktid == update_id_t.USRP2_FW_UPDATE_ID_OHAI_OMG: + print("USRP-N2XX found.") + else: + raise Exception("Invalid reply received from device.") + + def get_hw_rev(self): + out_pkt = pack_flash_hw_rev_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_I_CAN_HAS_HW_REV_LOL, seq(), 0) + in_pkt = self.send_and_recv(out_pkt) + (proto_ver, pktid, rxseq, hw_rev) = unpack_flash_hw_rev_fmt(in_pkt) + if(pktid != update_id_t.USRP2_FW_UPDATE_ID_HERES_TEH_HW_REV_OMG): hw_rev = 0 + return socket.ntohs(hw_rev) + + memory_size_bytes = 0 + sector_size_bytes = 0 + def get_flash_info(self): + if (self.memory_size_bytes != 0) and (self.sector_size_bytes != 0): + return (self.memory_size_bytes, self.sector_size_bytes) + + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WATS_TEH_FLASH_INFO_LOL, seq(), 0, 0) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, self.sector_size_bytes, self.memory_size_bytes) = unpack_flash_info_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_HERES_TEH_FLASH_INFO_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + return (self.memory_size_bytes, self.sector_size_bytes) + + def burn_fw(self, fw, fpga, reset, safe, check_rev=True): + (flash_size, sector_size) = self.get_flash_info() + hw_rev = self.get_hw_rev() + + if hw_rev in n2xx_revs: print("Hardware type: %s" % n2xx_revs[hw_rev][0]) + print("Flash size: %i\nSector size: %i\n" % (flash_size, sector_size)) + + if fpga: + #validate fpga image name against hardware rev + if(check_rev and hw_rev != 0 and not any(name in fpga for name in n2xx_revs[hw_rev])): + raise Exception("Error: incorrect FPGA image version. Please use the correct image for device %s" % n2xx_revs[hw_rev][0]) + + if safe: image_location = SAFE_FPGA_IMAGE_LOCATION_ADDR + else: image_location = PROD_FPGA_IMAGE_LOCATION_ADDR + + fpga_file = open(fpga, 'rb') + fpga_image = fpga_file.read() + + if len(fpga_image) > FPGA_IMAGE_SIZE_BYTES: + raise Exception("Error: FPGA image file too large.") + + if not is_valid_fpga_image(fpga_image): + raise Exception("Error: Invalid FPGA image file.") + + if (len(fpga_image) + image_location) > flash_size: + raise Exception("Error: Cannot write past end of device") + + print("Begin FPGA write: this should take about 1 minute...") + start_time = time.time() + self.erase_image(image_location, FPGA_IMAGE_SIZE_BYTES) + self.write_image(fpga_image, image_location) + self.verify_image(fpga_image, image_location) + print("Time elapsed: %f seconds"%(time.time() - start_time)) + print("\n\n") + + if fw: + if safe: image_location = SAFE_FW_IMAGE_LOCATION_ADDR + else: image_location = PROD_FW_IMAGE_LOCATION_ADDR + + fw_file = open(fw, 'rb') + fw_image = fw_file.read() + + if len(fw_image) > FW_IMAGE_SIZE_BYTES: + raise Exception("Error: Firmware image file too large.") + + if not is_valid_fw_image(fw_image): + raise Exception("Error: Invalid firmware image file.") + + if (len(fw_image) + image_location) > flash_size: + raise Exception("Error: Cannot write past end of device") + + print("Begin firmware write: this should take about 1 second...") + start_time = time.time() + self.erase_image(image_location, FW_IMAGE_SIZE_BYTES) + self.write_image(fw_image, image_location) + self.verify_image(fw_image, image_location) + print("Time elapsed: %f seconds"%(time.time() - start_time)) + print("\n\n") + + if reset: self.reset_usrp() + + def write_image(self, image, addr): + print("Writing image") + self._status_cb("Writing") + writedata = image + #we split the image into smaller (256B) bits and send them down the wire + (mem_size, sector_size) = self.get_flash_info() + if (addr + len(writedata)) > mem_size: + raise Exception("Error: Cannot write past end of device") + + while writedata: + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_WRITE_TEH_FLASHES_LOL, seq(), addr, FLASH_DATA_PACKET_SIZE, writedata[:FLASH_DATA_PACKET_SIZE]) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_WROTE_TEH_FLASHES_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + writedata = writedata[FLASH_DATA_PACKET_SIZE:] + addr += FLASH_DATA_PACKET_SIZE + self._progress_cb(float(len(image)-len(writedata))/len(image)) + + def verify_image(self, image, addr): + print("Verifying data") + self._status_cb("Verifying") + readsize = len(image) + readdata = bytes() + while readsize > 0: + if readsize < FLASH_DATA_PACKET_SIZE: thisreadsize = readsize + else: thisreadsize = FLASH_DATA_PACKET_SIZE + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL, seq(), addr, thisreadsize) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + readdata += data[:thisreadsize] + readsize -= FLASH_DATA_PACKET_SIZE + addr += FLASH_DATA_PACKET_SIZE + self._progress_cb(float(len(readdata))/len(image)) + + print("Read back %i bytes" % len(readdata)) + # print readdata + + # for i in range(256, 512): + # print "out: %i in: %i" % (ord(image[i]), ord(readdata[i])) + + if readdata != image: + raise Exception("Verify failed. Image did not write correctly.") + else: + print("Success.") + + def read_image(self, image, size, addr): + print("Reading image") + readsize = size + readdata = str() + while readsize > 0: + if readsize < FLASH_DATA_PACKET_SIZE: thisreadsize = readsize + else: thisreadsize = FLASH_DATA_PACKET_SIZE + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_READ_TEH_FLASHES_LOL, seq(), addr, thisreadsize) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_KK_READ_TEH_FLASHES_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + readdata += data[:thisreadsize] + readsize -= FLASH_DATA_PACKET_SIZE + addr += FLASH_DATA_PACKET_SIZE + + print("Read back %i bytes" % len(readdata)) + + #write to disk + f = open(image, 'w') + f.write(readdata) + f.close() + + def reset_usrp(self): + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_RESET_MAH_COMPUTORZ_LOL, seq(), 0, 0) + try: in_pkt = self.send_and_recv(out_pkt) + except socket.timeout: return + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + if pktid == update_id_t.USRP2_FW_UPDATE_ID_RESETTIN_TEH_COMPUTORZ_OMG: + raise Exception("Device failed to reset.") + + def erase_image(self, addr, length): + self._status_cb("Erasing") + #get flash info first + (flash_size, sector_size) = self.get_flash_info() + if (addr + length) > flash_size: + raise Exception("Cannot erase past end of device") + + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_ERASE_TEH_FLASHES_LOL, seq(), addr, length) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid != update_id_t.USRP2_FW_UPDATE_ID_ERASING_TEH_FLASHES_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + + print("Erasing %i bytes at %i" % (length, addr)) + start_time = time.time() + + #now wait for it to finish + while(True): + out_pkt = pack_flash_args_fmt(USRP2_FW_PROTO_VERSION, update_id_t.USRP2_FW_UPDATE_ID_R_U_DONE_ERASING_LOL, seq(), 0, 0) + in_pkt = self.send_and_recv(out_pkt) + + (proto_ver, pktid, rxseq, flash_addr, rxlength, data) = unpack_flash_args_fmt(in_pkt) + + if pktid == update_id_t.USRP2_FW_UPDATE_ID_IM_DONE_ERASING_OMG: break + elif pktid != update_id_t.USRP2_FW_UPDATE_ID_NOPE_NOT_DONE_ERASING_OMG: + raise Exception("Invalid reply %c from device." % (chr(pktid))) + time.sleep(0.01) #decrease network overhead by waiting a bit before polling + self._progress_cb(min(1.0, (time.time() - start_time)/(length/80e3))) + + +######################################################################## +# command line options +######################################################################## +def get_options(): + parser = optparse.OptionParser() + parser.add_option("--addr", type="string", help="USRP-N2XX device address", default='') + parser.add_option("--fw", type="string", help="firmware image path (optional)", default='') + parser.add_option("--fpga", type="string", help="fpga image path (optional)", default='') + parser.add_option("--reset", action="store_true", help="reset the device after writing", default=False) + parser.add_option("--read", action="store_true", help="read to file instead of write from file", default=False) + parser.add_option("--overwrite-safe", action="store_true", help="never ever use this option", default=False) + parser.add_option("--dont-check-rev", action="store_true", help="disable revision checks", default=False) + parser.add_option("--list", action="store_true", help="list possible network devices", default=False) + (options, args) = parser.parse_args() + + return options + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = get_options() + + if options.list: + print('Possible network devices:') + print(' ' + '\n '.join(enumerate_devices())) + exit() + + if not options.addr: raise Exception('no address specified') + + if not options.fpga and not options.fw and not options.reset: raise Exception('Must specify either a firmware image or FPGA image, and/or reset.') + + if options.overwrite_safe and not options.read: + print("Are you REALLY, REALLY sure you want to overwrite the safe image? This is ALMOST ALWAYS a terrible idea.") + print("If your image is faulty, your USRP2+ will become a brick until reprogrammed via JTAG.") + response = raw_input("""Type "yes" to continue, or anything else to quit: """) + if response != "yes": sys.exit(0) + + burner = burner_socket(addr=options.addr) + + if options.read: + if options.fw: + file = options.fw + if os.path.isfile(file): + response = raw_input("File already exists -- overwrite? (y/n) ") + if response != "y": sys.exit(0) + size = FW_IMAGE_SIZE_BYTES + addr = SAFE_FW_IMAGE_LOCATION_ADDR if options.overwrite_safe else PROD_FW_IMAGE_LOCATION_ADDR + burner.read_image(file, size, addr) + + if options.fpga: + file = options.fpga + if os.path.isfile(file): + response = input("File already exists -- overwrite? (y/n) ") + if response != "y": sys.exit(0) + size = FPGA_IMAGE_SIZE_BYTES + addr = SAFE_FPGA_IMAGE_LOCATION_ADDR if options.overwrite_safe else PROD_FPGA_IMAGE_LOCATION_ADDR + burner.read_image(file, size, addr) + + else: burner.burn_fw(fw=options.fw, fpga=options.fpga, reset=options.reset, safe=options.overwrite_safe, check_rev=not options.dont_check_rev) diff --git a/host/utils/usrp_n2xx_net_burner_gui.py b/host/utils/usrp_n2xx_net_burner_gui.py new file mode 100755 index 000000000..e2b79e72c --- /dev/null +++ b/host/utils/usrp_n2xx_net_burner_gui.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python +# +# Copyright 2011 Ettus Research LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import usrp_n2xx_net_burner #import implementation +try: + import tkinter, tkinter.filedialog, tkinter.font, tkinter.messagebox +except ImportError: + import tkFileDialog, tkFont, tkMessageBox + import Tkinter as tkinter + tkinter.filedialog = tkFileDialog + tkinter.font = tkFont + tkinter.messagebox = tkMessageBox +import os + +class BinFileEntry(tkinter.Frame): + """ + Simple file entry widget for getting the file path of bin files. + Combines a label, entry, and button with file dialog callback. + """ + + def __init__(self, root, what, def_path=''): + self._what = what + tkinter.Frame.__init__(self, root) + tkinter.Label(self, text=what+":").pack(side=tkinter.LEFT) + self._entry = tkinter.Entry(self, width=50) + self._entry.insert(tkinter.END, def_path) + self._entry.pack(side=tkinter.LEFT) + tkinter.Button(self, text="...", command=self._button_cb).pack(side=tkinter.LEFT) + + def _button_cb(self): + filename = tkinter.filedialog.askopenfilename( + parent=self, + filetypes=[('bin files', '*.bin'), ('all files', '*.*')], + title="Select bin file for %s"%self._what, + initialdir=os.path.dirname(self.get_filename()), + ) + + # open file on your own + if filename: + self._entry.delete(0, tkinter.END) + self._entry.insert(0, filename) + + def get_filename(self): + return self._entry.get() + +class ProgressBar(tkinter.Canvas): + """ + A simple implementation of a progress bar. + Draws rectangle that fills from left to right. + """ + + def __init__(self, root, width=500, height=20): + self._width = width + self._height = height + tkinter.Canvas.__init__(self, root, relief="sunken", borderwidth=2, width=self._width-2, height=self._height-2) + self._last_fill_pixels = None + self.set(0.0) + + def set(self, frac): + """ + Update the progress where fraction is between 0.0 and 1.0 + """ + #determine the number of pixels to draw + fill_pixels = int(round(self._width*frac)) + if fill_pixels == self._last_fill_pixels: return + self._last_fill_pixels = fill_pixels + + #draw a rectangle representing the progress + if frac: self.create_rectangle(0, 0, fill_pixels, self._height, fill="#357EC7") + else: self.create_rectangle(0, 0, self._width, self._height, fill="#E8E8E8") + +class DeviceEntryWidget(tkinter.Frame): + """ + Simple entry widget for getting the network device name. + Combines a label, entry, and helpful text box with hints. + """ + + def __init__(self, root, text=''): + tkinter.Frame.__init__(self, root) + + tkinter.Button(self, text="Rescan for Devices", command=self._reload_cb).pack() + + self._hints = tkinter.Listbox(self) + self._hints.bind("<<ListboxSelect>>", self._listbox_cb) + self._reload_cb() + self._hints.pack(expand=tkinter.YES, fill=tkinter.X) + + frame = tkinter.Frame(self) + frame.pack() + + tkinter.Label(frame, text="Network Address:").pack(side=tkinter.LEFT) + self._entry = tkinter.Entry(frame, width=50) + self._entry.insert(tkinter.END, text) + self._entry.pack(side=tkinter.LEFT) + + def _reload_cb(self): + self._hints.delete(0, tkinter.END) + for hint in usrp_n2xx_net_burner.enumerate_devices(): + self._hints.insert(tkinter.END, hint) + + def _listbox_cb(self, event): + try: + sel = self._hints.get(self._hints.curselection()[0]) + self._entry.delete(0, tkinter.END) + self._entry.insert(0, sel) + except Exception as e: print(e) + + def get_devname(self): + return self._entry.get() + +class SectionLabel(tkinter.Label): + """ + Make a text label with bold font. + """ + + def __init__(self, root, text): + tkinter.Label.__init__(self, root, text=text) + + #set the font bold + f = tkinter.font.Font(font=self['font']) + f['weight'] = 'bold' + self['font'] = f.name + +class USRPN2XXNetBurnerApp(tkinter.Frame): + """ + The top level gui application for the usrp-n2xx network burner. + Creates entry widgets and button with callback to write images. + """ + + def __init__(self, root, addr, fw, fpga): + + tkinter.Frame.__init__(self, root) + + #pack the file entry widgets + SectionLabel(self, text="Select Images").pack(pady=5) + self._fw_img_entry = BinFileEntry(self, "Firmware Image", def_path=fw) + self._fw_img_entry.pack() + self._fpga_img_entry = BinFileEntry(self, "FPGA Image", def_path=fpga) + self._fpga_img_entry.pack() + + #pack the destination entry widget + SectionLabel(self, text="Select Device").pack(pady=5) + self._net_dev_entry = DeviceEntryWidget(self, text=addr) + self._net_dev_entry.pack() + + #the do it button + SectionLabel(self, text="").pack(pady=5) + button = tkinter.Button(self, text="Burn Images", command=self._burn) + self._enable_input = lambda: button.configure(state=tkinter.NORMAL) + self._disable_input = lambda: button.configure(state=tkinter.DISABLED) + button.pack() + + #a progress bar to monitor the status + progress_frame = tkinter.Frame(self) + progress_frame.pack() + self._status = tkinter.StringVar() + tkinter.Label(progress_frame, textvariable=self._status).pack(side=tkinter.LEFT) + self._pbar = ProgressBar(progress_frame) + self._pbar.pack(side=tkinter.RIGHT, expand=True) + + def _burn(self): + #grab strings from the gui + fw = self._fw_img_entry.get_filename() + fpga = self._fpga_img_entry.get_filename() + addr = self._net_dev_entry.get_devname() + + #check input + if not addr: + tkinter.messagebox.showerror('Error:', 'No address specified!') + return + if not fw and not fpga: + tkinter.messagebox.showerror('Error:', 'No images specified!') + return + if fw and not os.path.exists(fw): + tkinter.messagebox.showerror('Error:', 'Firmware image not found!') + return + if fpga and not os.path.exists(fpga): + tkinter.messagebox.showerror('Error:', 'FPGA image not found!') + return + + self._disable_input() + try: + #make a new burner object and attempt the burner operation + burner = usrp_n2xx_net_burner.burner_socket(addr=addr) + + for (image_type, fw_img, fpga_img) in (('FPGA', '', fpga), ('Firmware', fw, '')): + #setup callbacks that update the gui + def status_cb(status): + self._pbar.set(0.0) #status change, reset the progress + self._status.set("%s %s "%(status.title(), image_type)) + self.update() + def progress_cb(progress): + self._pbar.set(progress) + self.update() + burner.set_callbacks(progress_cb=progress_cb, status_cb=status_cb) + burner.burn_fw(fw=fw_img, fpga=fpga_img, reset=False, safe=False) + + if tkinter.messagebox.askyesno("Burn was successful!", "Reset the device?"): + burner.reset_usrp() + + except Exception as e: + tkinter.messagebox.showerror('Verbose:', 'Error: %s'%str(e)) + + #reset the progress bar + self._pbar.set(0.0) + self._status.set("") + self._enable_input() + +######################################################################## +# main +######################################################################## +if __name__=='__main__': + options = usrp_n2xx_net_burner.get_options() + root = tkinter.Tk() + root.title('USRP-N2XX Net Burner') + USRPN2XXNetBurnerApp(root, addr=options.addr, fw=options.fw, fpga=options.fpga).pack() + root.mainloop() + exit() |