diff options
| -rw-r--r-- | host/docs/gpsdo.rst | 2 | ||||
| -rw-r--r-- | host/lib/usrp/b100/b100_impl.cpp | 3 | ||||
| -rw-r--r-- | host/lib/usrp/cores/rx_dsp_core_200.cpp | 11 | ||||
| -rw-r--r-- | host/lib/usrp/cores/tx_dsp_core_200.cpp | 9 | ||||
| -rw-r--r-- | host/lib/usrp/e100/e100_impl.cpp | 3 | ||||
| -rw-r--r-- | host/lib/usrp/multi_usrp.cpp | 13 | ||||
| -rw-r--r-- | host/lib/usrp/usrp1/usrp1_impl.cpp | 14 | ||||
| -rw-r--r-- | host/lib/usrp/usrp1/usrp1_impl.hpp | 2 | ||||
| -rw-r--r-- | host/lib/usrp/usrp2/usrp2_impl.cpp | 3 | ||||
| -rw-r--r-- | host/utils/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | host/utils/query_gpsdo_sensors.cpp | 120 | ||||
| -rwxr-xr-x | host/utils/usrp_n2xx_net_burner.py | 16 | ||||
| -rwxr-xr-x | host/utils/usrp_n2xx_net_burner_gui.py | 9 | 
13 files changed, 195 insertions, 11 deletions
| diff --git a/host/docs/gpsdo.rst b/host/docs/gpsdo.rst index a41e23df6..1e9019a0f 100644 --- a/host/docs/gpsdo.rst +++ b/host/docs/gpsdo.rst @@ -31,7 +31,7 @@ The GPSDO is capable of supplying a 3V for active GPS antennas or supporting pas  Installation Instructions  ------------------------------------------------------------------------  Installation instructions can be found here: -`http://www.ettus.com/content/files/gpsdo-kit_datasheet.pdf <http://www.ettus.com/content/files/gpsdo-kit_datasheet.pdf>`_ +`http://www.ettus.com/content/files/gpsdo-kit_2.pdf <http://www.ettus.com/content/files/gpsdo-kit_2.pdf>`_  ********************************************  Post-installation Task (N-Series only) diff --git a/host/lib/usrp/b100/b100_impl.cpp b/host/lib/usrp/b100/b100_impl.cpp index b226e113a..a5a0ef9b0 100644 --- a/host/lib/usrp/b100/b100_impl.cpp +++ b/host/lib/usrp/b100/b100_impl.cpp @@ -440,6 +440,9 @@ b100_impl::b100_impl(const device_addr_t &device_addr){      tx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_A);      gdb_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_A ^ 5); +    //disable rx dc offset if LFRX +    if (rx_db_eeprom.id == 0x000f) _tree->access<bool>(rx_fe_path / "dc_offset" / "enable").set(false); +      //create the properties and register subscribers      _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom")          .set(rx_db_eeprom) diff --git a/host/lib/usrp/cores/rx_dsp_core_200.cpp b/host/lib/usrp/cores/rx_dsp_core_200.cpp index a89405039..7af4923c8 100644 --- a/host/lib/usrp/cores/rx_dsp_core_200.cpp +++ b/host/lib/usrp/cores/rx_dsp_core_200.cpp @@ -184,6 +184,15 @@ public:          _iface->poke32(REG_DSP_RX_DECIM, (hb1 << 9) | (hb0 << 8) | (decim & 0xff)); +        if (decim > 1 and hb0 == 0 and hb1 == 0) +        { +            UHD_MSG(warning) << boost::format( +                "The requested decimation is odd; the user should expect CIC rolloff.\n" +                "Select an even decimation to ensure that a halfband filter is enabled.\n" +                "decimation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" +            ) % decim_rate % (_tick_rate/1e6) % (rate/1e6); +        } +          // Calculate CIC decimation (i.e., without halfband decimators)          // Calculate closest multiplier constant to reverse gain absent scale multipliers          const double rate_pow = std::pow(double(decim & 0xff), 4); @@ -194,7 +203,7 @@ public:      }      void update_scalar(void){ -        const double target_scalar = (1 << 16)*_scaling_adjustment/_dsp_extra_scaling; +        const double target_scalar = (1 << 17)*_scaling_adjustment/_dsp_extra_scaling;          const boost::int32_t actual_scalar = boost::math::iround(target_scalar);          _fxpt_scalar_correction = target_scalar/actual_scalar; //should be small          _iface->poke32(REG_DSP_RX_SCALE_IQ, actual_scalar); diff --git a/host/lib/usrp/cores/tx_dsp_core_200.cpp b/host/lib/usrp/cores/tx_dsp_core_200.cpp index 2faf7c28b..c37868b26 100644 --- a/host/lib/usrp/cores/tx_dsp_core_200.cpp +++ b/host/lib/usrp/cores/tx_dsp_core_200.cpp @@ -126,6 +126,15 @@ public:          _iface->poke32(REG_DSP_TX_INTERP, (hb1 << 9) | (hb0 << 8) | (interp & 0xff)); +        if (interp > 1 and hb0 == 0 and hb1 == 0) +        { +            UHD_MSG(warning) << boost::format( +                "The requested interpolation is odd; the user should expect CIC rolloff.\n" +                "Select an even interpolation to ensure that a halfband filter is enabled.\n" +                "interpolation = dsp_rate/samp_rate -> %d = (%f MHz)/(%f MHz)\n" +            ) % interp_rate % (_tick_rate/1e6) % (rate/1e6); +        } +          // Calculate CIC interpolation (i.e., without halfband interpolators)          // Calculate closest multiplier constant to reverse gain absent scale multipliers          const double rate_pow = std::pow(double(interp & 0xff), 3); diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp index eb8804776..a0fa6c47e 100644 --- a/host/lib/usrp/e100/e100_impl.cpp +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -399,6 +399,9 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){      tx_db_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB);      gdb_eeprom.load(*_fpga_i2c_ctrl, I2C_ADDR_TX_DB ^ 5); +    //disable rx dc offset if LFRX +    if (rx_db_eeprom.id == 0x000f) _tree->access<bool>(rx_fe_path / "dc_offset" / "enable").set(false); +      //create the properties and register subscribers      _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom")          .set(rx_db_eeprom) diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp index 1267da89c..0331cf93a 100644 --- a/host/lib/usrp/multi_usrp.cpp +++ b/host/lib/usrp/multi_usrp.cpp @@ -129,6 +129,9 @@ static tune_result_t tune_xx_subdev_and_dsp(      //------------------------------------------------------------------      double lo_offset = 0.0;      if (rf_fe_subtree->access<bool>("use_lo_offset").get()){ +        //If the frontend has lo_offset value and range properties, trust it for lo_offset +        if (rf_fe_subtree->exists("lo_offset/value")) lo_offset = rf_fe_subtree->access<double>("lo_offset/value").get(); +          //If the local oscillator will be in the passband, use an offset.          //But constrain the LO offset by the width of the filter bandwidth.          const double rate = dsp_subtree->access<double>("rate/value").get(); @@ -147,6 +150,14 @@ static tune_result_t tune_xx_subdev_and_dsp(          break;      case tune_request_t::POLICY_MANUAL: +        //If the rf_fe understands lo_offset settings, infer the desired lo_offset and set it +        //  Side effect: In TVRX2 for example, after setting the lo_offset (if_freq) with a +        //  POLICY_MANUAL, there is no way for the user to automatically get back to default +        //  if_freq without deconstruct/reconstruct the rf_fe objects. +        if (rf_fe_subtree->exists("lo_offset/value")) { +            rf_fe_subtree->access<double>("lo_offset/value").set(tune_request.rf_freq - tune_request.target_freq); +        } +          target_rf_freq = tune_request.rf_freq;          rf_fe_subtree->access<double>("freq/value").set(target_rf_freq);          break; @@ -897,7 +908,7 @@ private:          const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan);          gain_group::sptr gg = gain_group::make();          BOOST_FOREACH(const std::string &name, _tree->list(mb_root(mcp.mboard) / "tx_codecs" / spec.db_name / "gains")){ -            gg->register_fcns("ADC-"+name, make_gain_fcns_from_subtree(_tree->subtree(mb_root(mcp.mboard) / "tx_codecs" / spec.db_name / "gains" / name)), 1 /* high prio */); +            gg->register_fcns("DAC-"+name, make_gain_fcns_from_subtree(_tree->subtree(mb_root(mcp.mboard) / "tx_codecs" / spec.db_name / "gains" / name)), 1 /* high prio */);          }          BOOST_FOREACH(const std::string &name, _tree->list(tx_rf_fe_root(chan) / "gains")){              gg->register_fcns(name, make_gain_fcns_from_subtree(_tree->subtree(tx_rf_fe_root(chan) / "gains" / name)), 0 /* low prio */); diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp index ffe25b81e..a5e51b7d2 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.cpp +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -215,6 +215,12 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){          .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1));      //////////////////////////////////////////////////////////////////// +    // create user-defined control objects +    //////////////////////////////////////////////////////////////////// +    _tree->create<std::pair<boost::uint8_t, boost::uint32_t> >(mb_path / "user" / "regs") +        .subscribe(boost::bind(&usrp1_impl::set_reg, this, _1)); + +    ////////////////////////////////////////////////////////////////////      // setup the mboard eeprom      ////////////////////////////////////////////////////////////////////      const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, USRP1_EEPROM_MAP_KEY); @@ -354,6 +360,9 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){          tx_db_eeprom.load(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A) : (I2C_ADDR_TX_B));          gdb_eeprom.load(*_fx2_ctrl, (db == "A")? (I2C_ADDR_TX_A ^ 5) : (I2C_ADDR_TX_B ^ 5)); +        //disable rx dc offset if LFRX +        if (rx_db_eeprom.id == 0x000f) _tree->access<bool>(mb_path / "rx_frontends" / db / "dc_offset" / "enable").set(false); +          //create the properties and register subscribers          _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "rx_eeprom")              .set(rx_db_eeprom) @@ -497,3 +506,8 @@ std::complex<double> usrp1_impl::set_rx_dc_offset(const std::string &db, const s      return std::complex<double>(double(i_off) * (1ul << 31), double(q_off) * (1ul << 31));  } + +void usrp1_impl::set_reg(const std::pair<boost::uint8_t, boost::uint32_t> ®) +{ +    _iface->poke32(reg.first, reg.second); +} diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp index bdef50ec1..9461f0081 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.hpp +++ b/host/lib/usrp/usrp1/usrp1_impl.hpp @@ -135,6 +135,8 @@ private:      void vandal_conquest_loop(void); +    void set_reg(const std::pair<boost::uint8_t, boost::uint32_t> ®); +      //handle the enables      bool _rx_enabled, _tx_enabled;      void enable_rx(bool enb){ diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index 42b1acc4c..21f166aa1 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -666,6 +666,9 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr){          tx_db_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB);          gdb_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB ^ 5); +        //disable rx dc offset if LFRX +        if (rx_db_eeprom.id == 0x000f) _tree->access<bool>(rx_fe_path / "dc_offset" / "enable").set(false); +          //create the properties and register subscribers          _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom")              .set(rx_db_eeprom) diff --git a/host/utils/CMakeLists.txt b/host/utils/CMakeLists.txt index 413cf3d4e..763bb47bc 100644 --- a/host/utils/CMakeLists.txt +++ b/host/utils/CMakeLists.txt @@ -38,6 +38,7 @@ ENDFOREACH(util_source)  # Utilities that get installed into the share path  ########################################################################  SET(util_share_sources +    query_gpsdo_sensors.cpp      usrp_burn_db_eeprom.cpp      usrp_burn_mb_eeprom.cpp  ) diff --git a/host/utils/query_gpsdo_sensors.cpp b/host/utils/query_gpsdo_sensors.cpp new file mode 100644 index 000000000..10792db7c --- /dev/null +++ b/host/utils/query_gpsdo_sensors.cpp @@ -0,0 +1,120 @@ +// +// Copyright 2012 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/utils/paths.hpp> +#include <uhd/utils/thread_priority.hpp> +#include <uhd/utils/safe_main.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <boost/filesystem.hpp> +#include <boost/program_options.hpp> +#include <boost/format.hpp> +#include <iostream> +#include <complex> +#include <boost/thread.hpp> +#include <string> +#include <cmath> + +namespace po = boost::program_options; +namespace fs = boost::filesystem; + +int UHD_SAFE_MAIN(int argc, char *argv[]){ +    uhd::set_thread_priority_safe(); + +    std::string args; + +    //Set up program options +    po::options_description desc("Allowed options"); +    desc.add_options() +        ("help", "help message") +        ("args", po::value<std::string>(&args)->default_value(""), "Specify a single USRP.") +    ; +    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("Query GPSDO Sensors %s") % desc << std::endl; +        return ~0; +    } + +    //Create a USRP device +    std::cout << boost::format("\nCreating the USRP device with: %s...\n") % args; +    uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(args); +    std::cout << boost::format("Using Device: %s\n") % usrp->get_pp_string(); + +	//Helpful notes +	std::cout << boost::format("**************************************Helpful Notes on Clock/PPS Selection**************************************\n"); +	std::cout << boost::format("As you can see, the default 10 MHz Reference and 1 PPS signals are now from the GPSDO.\n"); +	std::cout << boost::format("If you would like to use the internal reference(TCXO) in other applications, you must configure that explicitly.\n"); +	std::cout << boost::format("You can no longer select the external SMAs for 10 MHz or 1 PPS signaling.\n"); +	std::cout << boost::format("****************************************************************************************************************\n"); + +	 +	//Verify GPS sensors are present (i.e. EEPROM has been burnt) +	std::vector<std::string> sensor_names = usrp->get_mboard_sensor_names(0); + +    if(std::find(sensor_names.begin(), sensor_names.end(), "gps_locked") == sensor_names.end()){ +        std::cout << boost::format("\ngps_locked sensor not found.  This could mean that you have not installed the GPSDO correctly.\n\n"); +        std::cout << boost::format("Visit this page if the problem persists:\n"); +        std::cout << boost::format("http://files.ettus.com/uhd_docs/manual/html/gpsdo.html\n\n"); +        exit(1); +    } + +	//Check for GPS lock +	uhd::sensor_value_t gps_locked = usrp->get_mboard_sensor("gps_locked",0); +	if(gps_locked.to_pp_string().find("unlocked") > 0){ +			std::cout << boost::format("\nGPS does not have lock. Wait a few minutes and try again.\n"); +			std::cout << boost::format("NMEA strings and device time may not be accurate until lock is achieved.\n\n"); +	} +	else std::cout << boost::format("GPS Locked\n"); +	 +	//Check for 10 MHz lock +	if(std::find(sensor_names.begin(), sensor_names.end(), "ref_locked") != sensor_names.end()){ +        uhd::sensor_value_t gps_locked = usrp->get_mboard_sensor("ref_locked",0); +		if(gps_locked.to_pp_string().find("unlocked") > 0){ +			std::cout << boost::format("USRP NOT Locked to GPSDO 10 MHz Reference.\n"); +			std::cout << boost::format("Double check installation instructions: https://www.ettus.com/content/files/gpsdo-kit_2.pdf\n\n"); +		} +		else std::cout << boost::format("USRP Locked to GPSDO 10 MHz Reference.\n"); +    } +    else std::cout << boost::format("ref_locked sensor not present on this board.\n");	 +	 +    //Check PPS and compare UHD device time to GPS time +	uhd::sensor_value_t gps_time = usrp->get_mboard_sensor("gps_time"); +	const uhd::time_spec_t last_pps_time = usrp->get_time_last_pps(); +	if(int(round(last_pps_time.get_real_secs())) == gps_time.to_int()) +	{ +		std::cout << boost::format("GPS and UHD Device time are aligned.\n"); +	} +	else +	{ +		std::cout << boost::format("\nGPS and UHD Device time are NOT aligned. Try re-running the program. Double check 1 PPS connection from GPSDO.\n\n"); +	} +	 +	//print NMEA strings +	std::cout << boost::format("Printing available NMEA strings:\n"); +	uhd::sensor_value_t gga_string = usrp->get_mboard_sensor("gps_gpgga"); +	uhd::sensor_value_t rmc_string = usrp->get_mboard_sensor("gps_gprmc"); +    std::cout << boost::format("%s\n%s\n%s\n") % gga_string.to_pp_string() % rmc_string.to_pp_string() % gps_time.to_pp_string(); +	std::cout << boost::format("UHD Device time: %.0f seconds\n") % round(last_pps_time.get_real_secs()); +	 +    //finished +    std::cout << boost::format("\nDone!\n\n"); + +    return 0; +} diff --git a/host/utils/usrp_n2xx_net_burner.py b/host/utils/usrp_n2xx_net_burner.py index f2cfb8ecf..8f16de501 100755 --- a/host/utils/usrp_n2xx_net_burner.py +++ b/host/utils/usrp_n2xx_net_burner.py @@ -222,7 +222,9 @@ def enumerate_devices():                  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))) +                    use_addr = socket.inet_ntoa(struct.pack("<L", socket.ntohl(ip_addr))) +                    burner = burner_socket(use_addr, True) +                    yield "%s (%s)" % (socket.inet_ntoa(struct.pack("<L", socket.ntohl(ip_addr))), n2xx_revs[burner.get_hw_rev()][0])              except socket.timeout:                  still_goin = False @@ -230,12 +232,13 @@ def enumerate_devices():  # Burner class, holds a socket and send/recv routines  ########################################################################  class burner_socket(object): -    def __init__(self, addr): +    def __init__(self, addr, quiet):          self._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +        self._quiet = quiet          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.init_update(quiet) #check that the device is there          self.get_hw_rev()      def set_callbacks(self, progress_cb, status_cb): @@ -247,13 +250,13 @@ class burner_socket(object):          return self._sock.recv(UDP_MAX_XFER_BYTES)      #just here to validate comms -    def init_update(self): +    def init_update(self,quiet):          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.") +            if not quiet: print("USRP-N2XX found.")          else:              raise Exception("Invalid reply received from device.") @@ -488,6 +491,7 @@ if __name__=='__main__':      if options.list:          print('Possible network devices:')          print('  ' + '\n  '.join(enumerate_devices())) +        #enumerate_devices()          exit()      if not options.addr: raise Exception('no address specified') @@ -500,7 +504,7 @@ if __name__=='__main__':          response = raw_input("""Type "yes" to continue, or anything else to quit: """)          if response != "yes": sys.exit(0) -    burner = burner_socket(addr=options.addr) +    burner = burner_socket(addr=options.addr,quiet=False)      if options.read:          if options.fw: diff --git a/host/utils/usrp_n2xx_net_burner_gui.py b/host/utils/usrp_n2xx_net_burner_gui.py index e2b79e72c..a9150bd88 100755 --- a/host/utils/usrp_n2xx_net_burner_gui.py +++ b/host/utils/usrp_n2xx_net_burner_gui.py @@ -96,7 +96,11 @@ class DeviceEntryWidget(tkinter.Frame):          tkinter.Button(self, text="Rescan for Devices", command=self._reload_cb).pack()          self._hints = tkinter.Listbox(self) +        self._hints_addrs_only = tkinter.Listbox(self) +          self._hints.bind("<<ListboxSelect>>", self._listbox_cb) +        self._hints_addrs_only.bind("<<ListboxSelect>>", self._listbox_cb) +          self._reload_cb()          self._hints.pack(expand=tkinter.YES, fill=tkinter.X) @@ -112,10 +116,11 @@ class DeviceEntryWidget(tkinter.Frame):          self._hints.delete(0, tkinter.END)          for hint in usrp_n2xx_net_burner.enumerate_devices():              self._hints.insert(tkinter.END, hint) +            self._hints_addrs_only.insert(tkinter.END, hint.split(" (")[0])      def _listbox_cb(self, event):          try: -            sel = self._hints.get(self._hints.curselection()[0]) +            sel = self._hints_addrs_only.get(self._hints.curselection()[0])              self._entry.delete(0, tkinter.END)              self._entry.insert(0, sel)          except Exception as e: print(e) @@ -196,7 +201,7 @@ class USRPN2XXNetBurnerApp(tkinter.Frame):          self._disable_input()          try:              #make a new burner object and attempt the burner operation -            burner = usrp_n2xx_net_burner.burner_socket(addr=addr) +            burner = usrp_n2xx_net_burner.burner_socket(addr=addr,quiet=False)              for (image_type, fw_img, fpga_img) in (('FPGA', '', fpga), ('Firmware', fw, '')):                  #setup callbacks that update the gui | 
