SelfCal: Expose config parameters

Exposes the following ADC self-cal parameters on X4xx devices to be used
in the argument string:
-cal_freq
-cal_dac_mux_i
-cal_dac_mux_q
-cal_tone_duration
-cal_ch_list
-cal_delay
This commit is contained in:
Martin Anderseck 2023-07-04 13:37:15 +02:00 committed by michael-west
parent 51c1a74eee
commit b4c24091ed
10 changed files with 132 additions and 12 deletions

View File

@ -8,7 +8,10 @@
#include <uhd/config.hpp>
#include <uhd/features/discoverable_feature.hpp>
#include <uhd/types/device_addr.hpp>
#include <map>
#include <memory>
#include <vector>
namespace uhd { namespace features {
@ -41,6 +44,10 @@ public:
// signal chain, use the rx_codec/<n>/calibration_frozen property on the
// motherboard's property tree.
virtual void run(const size_t chan) = 0;
//! Runs calibration on the specified channel but takes custom calibration
// parameters.
virtual void run(const size_t chan, const uhd::device_addr_t params) = 0;
};
}} // namespace uhd::features

View File

@ -10,6 +10,7 @@
#include <uhd/config.hpp>
#include <uhd/exception.hpp>
#include <iomanip>
#include <sstream>
#include <string>
@ -29,6 +30,22 @@ inline T hexstr_cast(const std::string& in)
return x;
}
//! Convert hexadecimal, decimal, octal or other strings that support the >>
//! operator into a value depending on the prefix.
//
// Example:
// 10, 0x10, 010 get parsed to decimal 10, 16, 8.
// uint32_t x = fromstr_cast<uint32_t>("0xaffe");
// Uses istringstream.
template <typename T>
inline T fromstr_cast(const std::string& in)
{
T x;
std::istringstream is(in);
is >> std::setbase(0) >> x;
return x;
}
//! Generic cast-from-string function
template <typename data_t>
data_t from_str(const std::string&)

View File

@ -57,6 +57,7 @@ public:
0,
0,
"calib_mode1",
2000,
};
}

View File

@ -79,13 +79,16 @@ public:
uhd::usrp::x400::adc_self_cal_params_t get_adc_self_cal_params(const double) override
{
// FBX uses a fixed low freq for both TX and RX
return {100e6, // rx_freq
return {
100e6, // rx_freq
100e6, // tx_freq
{0x7FFF, 0}, // output full scale dac mux
100, // delay
4000, // under
4192, // over
"calib_mode2"};
"calib_mode2",
2000, // 2 seconds were found to be sufficient
};
}
bool select_adc_self_cal_gain(size_t) override;

View File

@ -45,6 +45,7 @@ public:
0,
0,
"calib_mode1",
2000,
};
}

View File

@ -27,6 +27,7 @@ struct adc_self_cal_params_t
uint32_t threshold_under;
uint32_t threshold_over;
std::string calibration_mode;
uint32_t calibration_time;
};
/*! Interface for daughterboards which support being plugged into a X400 motherboard.

View File

@ -107,6 +107,7 @@ public:
8192, // over, Set the threshold to detect half-scale
// setup_threshold call uses 14-bit ADC values
"calib_mode2", // calibration_mode
2000, // 2 seconds were found to be sufficent
};
}

View File

@ -5,6 +5,7 @@
//
#include "adc_self_calibration.hpp"
#include <uhd/utils/cast.hpp>
#include <uhd/utils/log.hpp>
#include <uhd/utils/scope_exit.hpp>
#include <chrono>
@ -27,7 +28,7 @@ adc_self_calibration::adc_self_calibration(uhd::usrp::x400_rpc_iface::sptr rpcc,
{
}
void adc_self_calibration::run(size_t chan)
void adc_self_calibration::run(size_t chan, uhd::device_addr_t params)
{
const auto tx_gain_profile =
_daughterboard->get_tx_gain_profile_api()->get_gain_profile(chan);
@ -38,13 +39,81 @@ void adc_self_calibration::run(size_t chan)
"profile for RX or TX is not 'default'.");
}
// The frequency that we need to feed into the ADC is, by decree,
// 13109 / 32768 times the ADC sample rate. (approx. 1.2GHz for a 3Gsps rate)
// This is at 0.4 * Fs, right on the boundary between modes 1 and 2 for the
// ADC self-cal.
// The frequency that we need to feed into the ADC is, by decree and if not specified
// differently by the user, 13109 / 32768 times the ADC sample rate for X410.
// (approx. 1.2GHz for a 3Gsps rate) This is at 0.4 * Fs, right on the boundary
// between modes 1 and 2 for the ADC self-cal. But devices may also specify different
// values in their ADC self cal params.
const double cal_tone_freq = _daughterboard->get_converter_rate() * 13109.0 / 32768.0;
const auto cal_params = _daughterboard->get_adc_self_cal_params(cal_tone_freq);
auto cal_params = _daughterboard->get_adc_self_cal_params(cal_tone_freq);
if (params.has_key("cal_freq")) {
const double cal_freq = params.cast<double>("cal_freq", cal_params.rx_freq);
if (cal_freq < 0 or cal_freq > 0.4 * _daughterboard->get_converter_rate()) {
RFNOC_LOG_WARNING("Invalid value for `cal_freq`: "
<< cal_freq
<< ". Valid values are "
"between 0 Hz and 0.4 x converter_rate. Using default "
<< cal_params.rx_freq / 1e6 << " MHz.");
} else {
cal_params.rx_freq = cal_freq;
cal_params.tx_freq = cal_freq;
// 0.4 * Fs/2 as limit between cal mode and 2 according to PG269
if (cal_freq <= (0.4 * _daughterboard->get_converter_rate())) {
cal_params.calibration_mode = "calib_mode2";
} else {
// This will never be reached as long as we want to limit ourselves to
// calib_mode2
cal_params.calibration_mode = "calib_mode1";
}
RFNOC_LOG_DEBUG("Custom cal_freq: " << cal_freq << " => Calibration Mode: "
<< cal_params.calibration_mode);
}
}
if (params.has_key("cal_dac_mux_i")) {
const int32_t dac_mux_i =
uhd::cast::fromstr_cast<int32_t>(params.get("cal_dac_mux_i"));
if (dac_mux_i < 0 or dac_mux_i > 0xFFFF) {
RFNOC_LOG_WARNING("Invalid value for `cal_dac_mux_i`: "
<< dac_mux_i << ". Using default value "
<< cal_params.dac_iq_values.real());
} else {
cal_params.dac_iq_values.real(dac_mux_i);
RFNOC_LOG_DEBUG("Custom cal_dac_mux_i: (" << dac_mux_i);
}
}
if (params.has_key("cal_dac_mux_q")) {
const int32_t dac_mux_q =
uhd::cast::fromstr_cast<int32_t>(params.get("cal_dac_mux_q"));
if (dac_mux_q < 0 or dac_mux_q > 0xFFFF) {
RFNOC_LOG_WARNING("Invalid value for `cal_dac_mux_q`: "
<< dac_mux_q << ". Using default value "
<< cal_params.dac_iq_values.imag());
} else {
cal_params.dac_iq_values.imag(dac_mux_q);
RFNOC_LOG_DEBUG("Custom cal_dac_mux_q: (" << dac_mux_q);
}
}
if (params.has_key("cal_tone_duration")) {
const uint32_t cal_time =
params.cast<uint32_t>("cal_tone_duration", cal_params.calibration_time);
if (cal_time > MAX_CAL_DURATION) {
RFNOC_LOG_WARNING("Invalid value for `cal_tone_duration`: "
<< cal_time << ". Maximum value: " << MAX_CAL_DURATION
<< ". Using default " << cal_params.calibration_time
<< " ms.");
} else {
cal_params.calibration_time = cal_time;
RFNOC_LOG_DEBUG("Custom cal_tone_duration: " << cal_time << " ms");
}
}
if (params.has_key("cal_delay")) {
const uint32_t threshold_delay =
params.cast<uint32_t>("cal_delay", cal_params.threshold_delay);
cal_params.threshold_delay = threshold_delay;
RFNOC_LOG_DEBUG("Custom cal_delay: " << cal_params.threshold_delay << " ms");
}
// Switch to CAL_LOOPBACK and save the current antenna
const auto rx_antenna = _daughterboard->get_rx_antenna(chan);
@ -153,8 +222,14 @@ void adc_self_calibration::run(size_t chan)
// Let the ADC calibrate
// 2000ms was found experimentally to be sufficient
constexpr auto calibration_time = 2000ms;
auto calibration_time = std::chrono::milliseconds(cal_params.calibration_time);
std::this_thread::sleep_for(calibration_time);
}
void adc_self_calibration::run(size_t chan)
{
const uhd::device_addr_t params;
adc_self_calibration::run(chan, params);
}
}} // namespace uhd::features

View File

@ -26,6 +26,13 @@ public:
void run(const size_t channel) override;
void run(const size_t chan, const uhd::device_addr_t params) override;
// Maximum duration for self-cal in ms. From tests we found that 2 seconds are good,
// so we limit the maximum value to twice 2 seconds to keep the overall calibration
// duration at a convenient level.
const uint32_t MAX_CAL_DURATION = 4000;
private:
//! Reference to the RPC client
uhd::usrp::x400_rpc_iface::sptr _rpcc;

View File

@ -266,14 +266,21 @@ x400_radio_control_impl::x400_radio_control_impl(make_args_ptr make_args)
if (has_feature<uhd::features::adc_self_calibration_iface>()) {
RFNOC_LOG_INFO("Clocking reconfigured, running ADC Self Cal on DB"
<< get_block_id().get_block_count() << "...");
auto args = get_block_args();
std::string ch_list = args.get("cal_ch_list", "");
size_t num_calibrations = 0;
auto& self_cal =
get_feature<uhd::features::adc_self_calibration_iface>();
const size_t num_channels = get_num_output_ports();
for (size_t i = 0; i < num_channels; i++) {
RFNOC_LOG_INFO("Calibrating channel " << i << "...");
self_cal.run(i);
num_calibrations++;
auto abs_ch = get_block_id().get_block_count() * num_channels + i;
if (ch_list.size() == 0
or ch_list.find(std::to_string(abs_ch))
!= std::string::npos) {
RFNOC_LOG_INFO("Calibrating channel " << abs_ch << "...");
self_cal.run(i, args);
num_calibrations++;
}
}
if (num_calibrations > 0) {
RFNOC_LOG_DEBUG(