From 51d4411ec13b5677af2d9aa3ed4c5fd5f1dab7df Mon Sep 17 00:00:00 2001 From: David Raeman Date: Sun, 12 Jun 2022 17:00:20 +0000 Subject: [PATCH] e3xx: add support for power calibration api This commit adds support for the UHD power calibration API on the E3xx family of radios. It also updates the documentation to reflect this support, and it adds support to the uhd_power_cal.py utility by adding a "calibrator" subclass for E3xx radios. --- host/docs/usrp_e3xx.dox | 9 +++ .../dboard/e3xx/e3xx_radio_control_impl.cpp | 17 ++++++ .../dboard/e3xx/e3xx_radio_control_init.cpp | 55 +++++++++++++++++++ host/python/uhd/usrp/cal/usrp_calibrator.py | 35 ++++++++++++ 4 files changed, 116 insertions(+) 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 ###############################################################################