summaryrefslogtreecommitdiffstats
path: root/host/utils
diff options
context:
space:
mode:
Diffstat (limited to 'host/utils')
-rw-r--r--host/utils/CMakeLists.txt87
-rw-r--r--host/utils/fx2_init_eeprom.cpp93
-rw-r--r--host/utils/uhd-usrp.rules19
-rw-r--r--host/utils/uhd_find_devices.cpp60
-rw-r--r--host/utils/uhd_usrp_probe.cpp214
-rwxr-xr-xhost/utils/usrp2_card_burner.py260
-rwxr-xr-xhost/utils/usrp2_card_burner_gui.py176
-rwxr-xr-xhost/utils/usrp2_recovery.py68
-rw-r--r--host/utils/usrp_burn_db_eeprom.cpp100
-rw-r--r--host/utils/usrp_burn_mb_eeprom.cpp78
-rw-r--r--host/utils/usrp_cal_utils.hpp85
-rw-r--r--host/utils/usrp_gen_rx_fe_cal_table.cpp310
-rw-r--r--host/utils/usrp_gen_tx_fe_cal_table.cpp312
-rwxr-xr-xhost/utils/usrp_n2xx_net_burner.py522
-rwxr-xr-xhost/utils/usrp_n2xx_net_burner_gui.py233
15 files changed, 2617 insertions, 0 deletions
diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt
new file mode 100644
index 000000000..1185b06cc
--- /dev/null
+++ b/host/utils/CMakeLists.txt
@@ -0,0 +1,87 @@
+#
+# 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
+)
+
+#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
+ usrp_gen_rx_fe_cal_table.cpp
+ usrp_gen_tx_fe_cal_table.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_DATA_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_DATA_DIR}/utils COMPONENT utilities)
+ENDFOREACH(util_source)
+
+IF(ENABLE_USRP2)
+ IF(WIN32 AND UHD_RELEASE_MODE) #include dd.exe
+ FILE(DOWNLOAD
+ "http://www.ettus.com/downloads/dd.exe"
+ ${CMAKE_CURRENT_BINARY_DIR}/dd.exe
+ )
+ INSTALL(FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/dd.exe
+ DESTINATION ${PKG_DATA_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_DATA_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..cd4529c39
--- /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/>.
+#
+
+ACTION=="add", BUS=="usb", SYSFS{idVendor}=="fffe", SYSFS{idProduct}=="0002", MODE:="0666"
+ACTION=="add", BUS=="usb", SYSFS{idVendor}=="2500", SYSFS{idProduct}=="0002", MODE:="0666"
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..43689dd20
--- /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()
+ 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://www.ettus.com/downloads/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..3e2aa6ae0
--- /dev/null
+++ b/host/utils/usrp_cal_utils.hpp
@@ -0,0 +1,85 @@
+//
+// 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 <vector>
+#include <complex>
+#include <cmath>
+#include <fstream>
+
+struct result_t{double freq, real_corr, imag_corr, sup;};
+
+/***********************************************************************
+ * Constants
+ **********************************************************************/
+static const double tau = 6.28318531;
+static const double alpha = 0.0001; //very tight iir filter
+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 size_t skip_initial_samps = 20;
+
+/***********************************************************************
+ * Sinusoid wave table
+ **********************************************************************/
+static inline std::vector<std::complex<float> > gen_table(void){
+ std::vector<std::complex<float> > wave_table(wave_table_len);
+ for (size_t i = 0; i < wave_table_len; i++){
+ wave_table[i] = std::polar<float>(1.0, (tau*i)/wave_table_len);
+ }
+ return wave_table;
+}
+
+static inline std::complex<float> wave_table_lookup(const size_t index){
+ static const std::vector<std::complex<float> > wave_table = gen_table();
+ return wave_table[index % wave_table_len];
+}
+
+/***********************************************************************
+ * Compute power of a tone
+ **********************************************************************/
+static inline double compute_tone_dbrms(
+ const std::vector<std::complex<float> > &samples,
+ const double freq //freq is fractional
+){
+ //shift the samples so the tone at freq is down at DC
+ std::vector<std::complex<double> > shifted(samples.size() - skip_initial_samps);
+ for (size_t i = 0; i < shifted.size(); i++){
+ shifted[i] = std::complex<double>(samples[i+skip_initial_samps]) * std::polar<double>(1.0, -freq*tau*i);
+ }
+
+ //filter the samples with a narrow low pass
+ std::complex<double> iir_output = 0, iir_last = 0;
+ double output = 0;
+ for (size_t i = 0; i < shifted.size(); i++){
+ iir_output = alpha * shifted[i] + (1-alpha)*iir_last;
+ iir_last = iir_output;
+ output += std::abs(iir_output);
+ }
+
+ return 20*std::log10(output/shifted.size());
+}
+
+/***********************************************************************
+ * Write a dat file
+ **********************************************************************/
+static inline void write_samples_to_file(
+ const std::vector<std::complex<float> > &samples, const std::string &file
+){
+ std::ofstream outfile(file.c_str(), std::ofstream::binary);
+ outfile.write((const char*)&samples.front(), samples.size()*sizeof(std::complex<float>));
+ outfile.close();
+}
diff --git a/host/utils/usrp_gen_rx_fe_cal_table.cpp b/host/utils/usrp_gen_rx_fe_cal_table.cpp
new file mode 100644
index 000000000..4d5d4168e
--- /dev/null
+++ b/host/utils/usrp_gen_rx_fe_cal_table.cpp
@@ -0,0 +1,310 @@
+//
+// 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/property_tree.hpp>
+#include <uhd/usrp/multi_usrp.hpp>
+#include <uhd/usrp/dboard_eeprom.hpp>
+#include <boost/program_options.hpp>
+#include <boost/format.hpp>
+#include <boost/thread/thread.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <iostream>
+#include <fstream>
+#include <complex>
+#include <cmath>
+#include <ctime>
+
+namespace po = boost::program_options;
+namespace fs = boost::filesystem;
+
+/***********************************************************************
+ * 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<std::complex<float> > 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();
+}
+
+/***********************************************************************
+ * Data capture routine
+ **********************************************************************/
+static void capture_samples(uhd::usrp::multi_usrp::sptr usrp, uhd::rx_streamer::sptr rx_stream, std::vector<std::complex<float> > &buff){
+ 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);
+ uhd::rx_metadata_t md;
+ 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));
+ }
+ if (num_rx_samps != buff.size()){
+ throw std::runtime_error("did not get all the samples requested");
+ }
+}
+
+/***********************************************************************
+ * Store data to file
+ **********************************************************************/
+static void store_results(uhd::usrp::multi_usrp::sptr usrp, const std::vector<result_t> &results){
+ //extract eeprom serial
+ uhd::property_tree::sptr tree = usrp->get_device()->get_tree();
+ const uhd::fs_path db_path = "/mboards/0/dboards/A/rx_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("RX 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 / ("rx_fe_cal_v0.1_" + db_eeprom.serial + ".csv");
+ 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, RX Frontend Calibration\n");
+ 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 << "rx_lo_frequency, rx_iq_correction_real, rx_iq_correction_imag, measured_suppression\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].sup << "\n"
+ ;
+ }
+
+ std::cout << "wrote cal data to " << cal_data_path << std::endl;
+}
+
+/***********************************************************************
+ * Main
+ **********************************************************************/
+int UHD_SAFE_MAIN(int argc, char *argv[]){
+ std::string args;
+ double rate, tx_wave_ampl, tx_offset, freq_step, tx_gain, rx_gain;
+ 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 = \"\"]")
+ ("rate", po::value<double>(&rate)->default_value(12.5e6), "RX and TX sample rate in Hz")
+ ("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")
+ ("tx_gain", po::value<double>(&tx_gain)->default_value(0), "TX gain in dB")
+ ("rx_gain", po::value<double>(&rx_gain)->default_value(0), "RX gain in dB")
+ ("freq_step", po::value<double>(&freq_step)->default_value(10e6), "Step size for LO sweep in Hz")
+ ("nsamps", po::value<size_t>(&nsamps)->default_value(10000), "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 Frontend 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 the sample rates
+ usrp->set_rx_rate(rate);
+ usrp->set_tx_rate(rate);
+
+ //set midrange rx gain, default 0 tx gain
+ usrp->set_tx_gain(tx_gain);
+ usrp->set_rx_gain(rx_gain);
+
+ //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<std::complex<float> > buff(nsamps);
+
+ //store the results here
+ std::vector<result_t> results;
+
+ const uhd::meta_range_t freq_range = usrp->get_rx_freq_range();
+ for (double rx_lo_i = freq_range.start()+50e6; rx_lo_i < freq_range.stop()-50e6; rx_lo_i += freq_step){
+ const double rx_lo = tune_rx_and_tx(usrp, rx_lo_i, tx_offset);
+
+ //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 = std::polar(ampl_corr+1, phase_corr*tau);
+ usrp->set_rx_iq_balance(correction);
+
+ //receive some samples
+ capture_samples(usrp, rx_stream, buff);
+
+ 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;
+
+ 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;
+
+ //std::cout << "bb_tone_freq " << bb_tone_freq << std::endl;
+ //std::cout << "bb_imag_freq " << bb_imag_freq << std::endl;
+ //std::cout << "tone_dbrms " << tone_dbrms << std::endl;
+ //std::cout << "imag_dbrms " << imag_dbrms << std::endl;
+ //std::cout << "suppression " << (tone_dbrms - imag_dbrms) << std::endl;
+
+ 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.sup = best_suppression;
+ results.push_back(result);
+ }
+ if (vm.count("verbose")){
+ std::cout << boost::format("%f MHz: best suppression %fdB") % (rx_lo/1e6) % best_suppression << std::endl;
+ }
+ else std::cout << "." << std::flush;
+
+ }
+ std::cout << std::endl;
+
+ //stop the transmitter
+ threads.interrupt_all();
+ threads.join_all();
+
+ store_results(usrp, results);
+
+ return 0;
+}
diff --git a/host/utils/usrp_gen_tx_fe_cal_table.cpp b/host/utils/usrp_gen_tx_fe_cal_table.cpp
new file mode 100644
index 000000000..1e4a9d11f
--- /dev/null
+++ b/host/utils/usrp_gen_tx_fe_cal_table.cpp
@@ -0,0 +1,312 @@
+//
+// 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/property_tree.hpp>
+#include <uhd/usrp/multi_usrp.hpp>
+#include <uhd/usrp/dboard_eeprom.hpp>
+#include <boost/program_options.hpp>
+#include <boost/format.hpp>
+#include <boost/thread/thread.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/math/special_functions/round.hpp>
+#include <iostream>
+#include <fstream>
+#include <complex>
+#include <ctime>
+
+namespace po = boost::program_options;
+namespace fs = boost::filesystem;
+
+/***********************************************************************
+ * 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<std::complex<float> > 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);
+
+ //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) * wave_table_lookup(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();
+}
+
+/***********************************************************************
+ * Data capture routine
+ **********************************************************************/
+static void capture_samples(uhd::usrp::multi_usrp::sptr usrp, uhd::rx_streamer::sptr rx_stream, std::vector<std::complex<float> > &buff){
+ 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);
+ uhd::rx_metadata_t md;
+ 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));
+ }
+ if (num_rx_samps != buff.size()){
+ throw std::runtime_error("did not get all the samples requested");
+ }
+}
+
+/***********************************************************************
+ * Store data to file
+ **********************************************************************/
+static void store_results(uhd::usrp::multi_usrp::sptr usrp, const std::vector<result_t> &results){
+ //extract eeprom serial
+ uhd::property_tree::sptr tree = usrp->get_device()->get_tree();
+ const uhd::fs_path db_path = "/mboards/0/dboards/A/tx_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("TX 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 / ("tx_fe_cal_v0.1_" + db_eeprom.serial + ".csv");
+ 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, TX Frontend Calibration\n");
+ 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 << "tx_lo_frequency, tx_iq_correction_real, tx_iq_correction_imag, measured_suppression\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].sup << "\n"
+ ;
+ }
+
+ std::cout << "wrote cal data to " << cal_data_path << std::endl;
+}
+
+/***********************************************************************
+ * Main
+ **********************************************************************/
+int UHD_SAFE_MAIN(int argc, char *argv[]){
+ std::string args;
+ double rate, tx_wave_freq, tx_wave_ampl, rx_offset, freq_step, tx_gain, rx_gain;
+ 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 = \"\"]")
+ ("rate", po::value<double>(&rate)->default_value(12.5e6), "RX and TX sample rate in Hz")
+ ("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")
+ ("tx_gain", po::value<double>(&tx_gain)->default_value(0), "TX gain in dB")
+ ("rx_gain", po::value<double>(&rx_gain)->default_value(0), "RX gain in dB")
+ ("freq_step", po::value<double>(&freq_step)->default_value(10e6), "Step size for LO sweep in Hz")
+ ("nsamps", po::value<size_t>(&nsamps)->default_value(10000), "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 Frontend 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 the sample rates
+ usrp->set_rx_rate(rate);
+ usrp->set_tx_rate(rate);
+
+ //set midrange rx gain, default 0 tx gain
+ usrp->set_tx_gain(tx_gain);
+ usrp->set_rx_gain(rx_gain);
+
+ //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<std::complex<float> > buff(nsamps);
+
+ //store the results here
+ std::vector<result_t> results;
+
+ const uhd::meta_range_t freq_range = usrp->get_tx_freq_range();
+ for (double tx_lo_i = freq_range.start()+50e6; tx_lo_i < freq_range.stop()-50e6; tx_lo_i += freq_step){
+ const double tx_lo = tune_rx_and_tx(usrp, tx_lo_i, rx_offset);
+
+ //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 = std::polar(ampl_corr+1, phase_corr*tau);
+ usrp->set_tx_iq_balance(correction);
+
+ //receive some samples
+ capture_samples(usrp, rx_stream, buff);
+
+ 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;
+
+ 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;
+
+ //std::cout << "bb_tone_freq " << bb_tone_freq << std::endl;
+ //std::cout << "bb_imag_freq " << bb_imag_freq << std::endl;
+ //std::cout << "tone_dbrms " << tone_dbrms << std::endl;
+ //std::cout << "imag_dbrms " << imag_dbrms << std::endl;
+ //std::cout << "suppression " << (tone_dbrms - imag_dbrms) << std::endl;
+
+ 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.sup = best_suppression;
+ results.push_back(result);
+ }
+ if (vm.count("verbose")){
+ std::cout << boost::format("%f MHz: best suppression %fdB") % (tx_lo/1e6) % best_suppression << std::endl;
+ }
+ else std::cout << "." << std::flush;
+
+ }
+ std::cout << std::endl;
+
+ //stop the transmitter
+ threads.interrupt_all();
+ threads.join_all();
+
+ store_results(usrp, results);
+
+ return 0;
+}
diff --git a/host/utils/usrp_n2xx_net_burner.py b/host/utils/usrp_n2xx_net_burner.py
new file mode 100755
index 000000000..e8fc3d197
--- /dev/null
+++ b/host/utils/usrp_n2xx_net_burner.py
@@ -0,0 +1,522 @@
+#!/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()
+ 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
+ ipAddr = adNode.ipAddress.decode()
+ ipMask = adNode.ipMask.decode()
+ 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))
+ adNode = adNode.next
+ 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 != 0): 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()