diff --git a/host/docs/usrp_e3xx.dox b/host/docs/usrp_e3xx.dox index f2758b98f..05ae18fc5 100644 --- a/host/docs/usrp_e3xx.dox +++ b/host/docs/usrp_e3xx.dox @@ -599,6 +599,15 @@ The following daughterboard sensors are always available: - `ad9361_temperature`: Temperature sensor of the RFIC die - `rssi`: RSSI value as returned from the AD9361 (RX only) +\subsection e3xx_power_api Power API + +The E3xx series support the UHD power calibration API (see: \ref page_power). +The TX path and the two RX paths per channel each have their own calibration +data, resulting in 6 sets of calibration data per E3xx device. + +Devices have to be manually calibrated using a calibrated power meter or +signal generator. + \section e3xx_rasm Remote Management \subsection e3xx_rasm_mender Mender: Remote update capability diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp index 036be4b08..04d09615b 100644 --- a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp +++ b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_impl.cpp @@ -215,6 +215,9 @@ void e3xx_radio_control_impl::set_rx_antenna(const std::string& ant, const size_ radio_control_impl::set_rx_antenna(ant, chan); _set_atr_bits(chan); + + // Upon changing antenna, update gain setting if in power tracking mode + _rx_pwr_mgr.at(chan)->update_power(); } double e3xx_radio_control_impl::set_tx_frequency(const double freq, const size_t chan) @@ -230,6 +233,8 @@ double e3xx_radio_control_impl::set_tx_frequency(const double freq, const size_t // frequency, we change the other, too for (size_t chan_idx = 0; chan_idx < E3XX_NUM_CHANS; ++chan_idx) { radio_control_impl::set_tx_frequency(coerced_freq, chan_idx); + // Upon retune, update gain setting if in power tracking mode + _tx_pwr_mgr.at(chan_idx)->update_power(); } // Front-end switching @@ -251,6 +256,8 @@ double e3xx_radio_control_impl::set_rx_frequency(const double freq, const size_t // frequency, we change the other, too for (size_t chan_idx = 0; chan_idx < E3XX_NUM_CHANS; ++chan_idx) { radio_control_impl::set_rx_frequency(coerced_freq, chan_idx); + // Upon retune, update gain setting if in power tracking mode + _rx_pwr_mgr.at(chan_idx)->update_power(); } // Front-end switching _set_atr_bits(chan); @@ -263,6 +270,10 @@ void e3xx_radio_control_impl::set_rx_agc(const bool enb, const size_t chan) std::lock_guard l(_set_lock); RFNOC_LOG_TRACE("set_rx_agc(enb=" << enb << ", chan=" << chan << ")"); const std::string rx_fe = get_which_ad9361_chain(RX_DIRECTION, chan); + // Upon enabling AGC, stop tracking by power + if (enb) { + _rx_pwr_mgr.at(chan)->set_tracking_mode(pwr_cal_mgr::tracking_mode::TRACK_GAIN); + } _ad9361->set_agc(rx_fe, enb); } @@ -291,6 +302,9 @@ double e3xx_radio_control_impl::set_tx_gain(const double gain, const size_t chan double clip_gain = uhd::clip(gain, AD9361_MIN_TX_GAIN, AD9361_MAX_TX_GAIN); _ad9361->set_gain(get_which_ad9361_chain(TX_DIRECTION, chan, _fe_swap), clip_gain); radio_control_impl::set_tx_gain(clip_gain, chan); + // Upon setting gain, stop tracking by power. Power manager also invokes + // this function and will clobber its mode, but then restores TRACK_POWER + _tx_pwr_mgr.at(chan)->set_tracking_mode(pwr_cal_mgr::tracking_mode::TRACK_GAIN); return clip_gain; } @@ -302,6 +316,9 @@ double e3xx_radio_control_impl::set_rx_gain(const double gain, const size_t chan double clip_gain = uhd::clip(gain, AD9361_MIN_RX_GAIN, AD9361_MAX_RX_GAIN); _ad9361->set_gain(get_which_ad9361_chain(RX_DIRECTION, chan, _fe_swap), clip_gain); radio_control_impl::set_rx_gain(clip_gain, chan); + // Upon setting gain, stop tracking by power. Power manager also invokes + // this function and will clobber its mode, but then restores TRACK_POWER + _rx_pwr_mgr.at(chan)->set_tracking_mode(pwr_cal_mgr::tracking_mode::TRACK_GAIN); return clip_gain; } diff --git a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp index 95f6301f2..bd805b52d 100644 --- a/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp +++ b/host/lib/usrp/dboard/e3xx/e3xx_radio_control_init.cpp @@ -128,9 +128,14 @@ void e3xx_radio_control_impl::_init_frontend_subtree( { const fs_path tx_fe_path = fs_path("tx_frontends") / chan_idx; const fs_path rx_fe_path = fs_path("rx_frontends") / chan_idx; + constexpr char HW_GAIN_STAGE[] = "hw"; + uhd::eeprom_map_t eeprom_map = get_db_eeprom(); + const std::string db_serial(eeprom_map["serial"].begin(), eeprom_map["serial"].end()); + RFNOC_LOG_TRACE("Adding non-RFNoC block properties for channel " << chan_idx << " to prop tree path " << tx_fe_path << " and " << rx_fe_path); + // TX Standard attributes subtree->create(tx_fe_path / "name").set("E3xx"); subtree->create(tx_fe_path / "connection").set("IQ"); @@ -159,6 +164,56 @@ void e3xx_radio_control_impl::_init_frontend_subtree( .add_coerced_subscriber([](const std::vector&) { throw uhd::runtime_error("Attempting to update antenna options!"); }); + // TX Power Calibration + auto tx_cal_ggroup = uhd::gain_group::make(); + tx_cal_ggroup->register_fcns(HW_GAIN_STAGE, + {[this, chan_idx]() { return get_tx_gain_range(chan_idx); }, + [this, chan_idx]() { return get_tx_gain(chan_idx); }, + [this, chan_idx](const double gain) { this->set_tx_gain(gain, chan_idx); }}, + 10 /* High priority */); + _tx_pwr_mgr.insert(_tx_pwr_mgr.begin() + chan_idx, + uhd::usrp::pwr_cal_mgr::make( + // Cal serial is the DB serial plus the FE name + db_serial + "#" + subtree->access(tx_fe_path / "name").get(), + // Logger prefix + "E3XX-CAL-TX", + // Current frequency getter + [this, chan_idx]() { return get_tx_frequency(chan_idx); }, + // Current calibration key getter (format is "e3xx_pwr_$dir_$chan_$ant") + [this, chan_idx]() { + const std::string ant = get_tx_antenna(chan_idx); + return std::string("e3xx_pwr_tx_") + std::to_string(chan_idx) + "_" + + pwr_cal_mgr::sanitize_antenna_name(ant); + }, + tx_cal_ggroup)); + // Provide current FE temperature to power manager + _tx_pwr_mgr.at(chan_idx)->set_temperature( + get_tx_sensor("ad9361_temperature", chan_idx).to_int()); + // RX Power Calibration + auto rx_cal_ggroup = uhd::gain_group::make(); + rx_cal_ggroup->register_fcns(HW_GAIN_STAGE, + {[this, chan_idx]() { return get_rx_gain_range(chan_idx); }, + [this, chan_idx]() { return get_rx_gain(chan_idx); }, + [this, chan_idx](const double gain) { this->set_rx_gain(gain, chan_idx); }}, + 10 /* High priority */); + _rx_pwr_mgr.insert(_rx_pwr_mgr.begin() + chan_idx, + uhd::usrp::pwr_cal_mgr::make( + // Cal serial is the DB serial plus the FE name + db_serial + "#" + subtree->access(rx_fe_path / "name").get(), + // Logger prefix + "E3XX-CAL-RX", + // Current frequency getter + [this, chan_idx]() { return get_rx_frequency(chan_idx); }, + // Current calibration key getter (format is "e3xx_pwr_$dir_$chan_$ant") + [this, chan_idx]() { + const std::string ant = get_rx_antenna(chan_idx); + return std::string("e3xx_pwr_rx_") + std::to_string(chan_idx) + "_" + + pwr_cal_mgr::sanitize_antenna_name(ant); + }, + rx_cal_ggroup)); + // Provide current FE temperature to power manager + _rx_pwr_mgr.at(chan_idx)->set_temperature( + get_rx_sensor("ad9361_temperature", chan_idx).to_int()); // TX frequency subtree->create(tx_fe_path / "freq" / "value") .set_coercer([this, chan_idx](const double freq) { diff --git a/host/python/uhd/usrp/cal/usrp_calibrator.py b/host/python/uhd/usrp/cal/usrp_calibrator.py index c129fb17d..df143b255 100644 --- a/host/python/uhd/usrp/cal/usrp_calibrator.py +++ b/host/python/uhd/usrp/cal/usrp_calibrator.py @@ -407,6 +407,41 @@ class X410Calibrator(USRPCalibratorBase): # time to clear the command queue tune_settling_time = .5 +class E3XXCalibrator(USRPCalibratorBase): + """ + E3XX calibration + """ + mboard_ids = ('e320', 'e310', 'e31x', 'e3xx') + # Choosing 4 MHz: It is a small rate, but carries enough bandwidth to receive + # a tone. Compatible with the default MCR of 16 MHz. + default_rate = 4e6 + # Choosing an LO offset of 8 MHz: At 4 Msps, the LO will never be within + # our estimate. E3XX generally has good DC offset / IQ balance performance, + # but we still try and avoid DC as much as possible. + lo_offset = 8e6 + tune_settling_time = .01 # Probably not needed?? + + def __init__(self, usrp, meas_dev, direction, **kwargs): + super().__init__(usrp, meas_dev, direction, **kwargs) + self._start_temp = None + self._stop_temp = None + + def start(self): + # query and remember starting temperature + get_sensor_fn = getattr(self._usrp, 'get_{}_sensor'.format(self._dir)) + self._start_temp = get_sensor_fn("ad9361_temperature").to_real() + super().start() + + def stop(self, store=True): + # save average temperature over the calibration cycle + get_sensor_fn = getattr(self._usrp, 'get_{}_sensor'.format(self._dir)) + self._stop_temp = get_sensor_fn("ad9361_temperature").to_real() + print("===== Temperature range was {:.1f} to {:.1f} C" + .format(self._start_temp, self._stop_temp)) + self.temp = round((self._start_temp + self._stop_temp) / 2) + super().stop(store) + + ############################################################################### # The dispatch function ###############################################################################