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.
This commit is contained in:
David Raeman 2022-06-12 17:00:20 +00:00 committed by michael-west
parent f6f1dec85a
commit 51d4411ec1
4 changed files with 116 additions and 0 deletions

View File

@ -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

View File

@ -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<std::recursive_mutex> 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;
}

View File

@ -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<std::string>(tx_fe_path / "name").set("E3xx");
subtree->create<std::string>(tx_fe_path / "connection").set("IQ");
@ -159,6 +164,56 @@ void e3xx_radio_control_impl::_init_frontend_subtree(
.add_coerced_subscriber([](const std::vector<std::string>&) {
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<std::string>(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<std::string>(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<double>(tx_fe_path / "freq" / "value")
.set_coercer([this, chan_idx](const double freq) {

View File

@ -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
###############################################################################