uhd/host/lib/usrp/dboard/db_twinrx.cpp
Martin Braun eb4bedf313 twinrx: Provide power cal ID
Note that the TwinRX has a different behaviour if two or one channel are
enabled. For that reason, TwinRX requires 8 different sets of
calibration data:
- For one vs. two channels
- For channel 0 and channel 1
- For RX1 and RX2

Since every combination of these settings is possible, that results in
2^3 == 8 combinations.

The choice of RX1 vs. RX2 is encoded in the calibration key. The choice
of one vs. two channels is also encoded in the calibration key, and is
derived using an expert node.
Channel 0 and 1 are assumed symmetric, thus, the encoding for those
happens in the calibration serial.
2020-05-19 13:30:36 -05:00

424 lines
17 KiB
C++

//
// Copyright 2014-15 Ettus Research LLC
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include "dboard_ctor_args.hpp"
#include "twinrx/twinrx_ctrl.hpp"
#include "twinrx/twinrx_experts.hpp"
#include "twinrx/twinrx_ids.hpp"
#include "twinrx/twinrx_io.hpp"
#include <uhd/types/device_addr.hpp>
#include <uhd/types/ranges.hpp>
#include <uhd/types/sensors.hpp>
#include <uhd/usrp/dboard_base.hpp>
#include <uhd/usrp/dboard_manager.hpp>
#include <uhd/utils/log.hpp>
#include <uhd/utils/static.hpp>
#include <uhdlib/experts/expert_factory.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <memory>
//#include <fstream> //Needed for _expert->to_dot() below
using namespace uhd;
using namespace uhd::usrp;
using namespace uhd::usrp::dboard::twinrx;
using namespace uhd::experts;
/*!
* twinrx_rcvr_fe is the dbaord class (dboard_base) that
* represents each front-end of a TwinRX board. UHD will
* create and hold two instances of this class per TwinRX
* dboard.
*
*/
class twinrx_rcvr_fe : public rx_dboard_base
{
public:
twinrx_rcvr_fe(
ctor_args_t args, expert_container::sptr expert, twinrx_ctrl::sptr ctrl)
: rx_dboard_base(args)
, _expert(expert)
, _ctrl(ctrl)
, _ch_name(dboard_ctor_args_t::cast(args).sd_name)
{
//---------------------------------------------------------
// Add user-visible, channel specific properties to front-end tree
//---------------------------------------------------------
// Generic
get_rx_subtree()->create<std::string>("name").set("TwinRX RX" + _ch_name);
get_rx_subtree()->create<bool>("use_lo_offset").set(false);
get_rx_subtree()
->create<std::string>("connection")
.set(_ch_name == "0" ? "II" : "QQ"); // Ch->ADC port mapping
static const double BW = 80e6;
get_rx_subtree()->create<double>("bandwidth/value").set(BW);
get_rx_subtree()
->create<meta_range_t>("bandwidth/range")
.set(freq_range_t(BW, BW));
// Command Time
expert_factory::add_data_node<time_spec_t>(
_expert, prepend_ch("time/rx_frontend", _ch_name), time_spec_t(0.0));
expert_factory::add_prop_node<time_spec_t>(_expert,
get_rx_subtree(),
"time/cmd",
prepend_ch("time/cmd", _ch_name),
time_spec_t(0.0));
// Frequency Specific
get_rx_subtree()
->create<meta_range_t>("freq/range")
.set(freq_range_t(10e6, 6.0e9));
expert_factory::add_dual_prop_node<double>(_expert,
get_rx_subtree(),
"freq/value",
prepend_ch("freq/desired", _ch_name),
prepend_ch("freq/coerced", _ch_name),
1.0e9,
AUTO_RESOLVE_ON_READ_WRITE);
get_rx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t());
static const double DEFAULT_IF_FREQ = 150e6;
meta_range_t if_freq_range;
if_freq_range.push_back(
range_t(-DEFAULT_IF_FREQ - (BW / 2), -DEFAULT_IF_FREQ + (BW / 2)));
if_freq_range.push_back(
range_t(DEFAULT_IF_FREQ - (BW / 2), DEFAULT_IF_FREQ + (BW / 2)));
get_rx_subtree()->create<meta_range_t>("if_freq/range").set(if_freq_range);
expert_factory::add_dual_prop_node<double>(_expert,
get_rx_subtree(),
"if_freq/value",
prepend_ch("if_freq/desired", _ch_name),
prepend_ch("if_freq/coerced", _ch_name),
DEFAULT_IF_FREQ,
AUTO_RESOLVE_ON_WRITE);
// LO Specific
get_rx_subtree()
->create<meta_range_t>("los/LO1/freq/range")
.set(freq_range_t(2.0e9, 6.8e9));
expert_factory::add_dual_prop_node<double>(_expert,
get_rx_subtree(),
"los/LO1/freq/value",
prepend_ch("los/LO1/freq/desired", _ch_name),
prepend_ch("los/LO1/freq/coerced", _ch_name),
0.0,
AUTO_RESOLVE_ON_READ_WRITE);
get_rx_subtree()
->create<meta_range_t>("los/LO2/freq/range")
.set(freq_range_t(1.0e9, 3.0e9));
expert_factory::add_dual_prop_node<double>(_expert,
get_rx_subtree(),
"los/LO2/freq/value",
prepend_ch("los/LO2/freq/desired", _ch_name),
prepend_ch("los/LO2/freq/coerced", _ch_name),
0.0,
AUTO_RESOLVE_ON_READ_WRITE);
get_rx_subtree()
->create<std::vector<std::string>>("los/all/source/options")
.set({"internal", "external", "companion", "disabled", "reimport"});
expert_factory::add_prop_node<std::string>(_expert,
get_rx_subtree(),
"los/all/source/value",
prepend_ch("los/all/source", _ch_name),
"internal",
AUTO_RESOLVE_ON_WRITE);
expert_factory::add_prop_node<bool>(_expert,
get_rx_subtree(),
"los/all/export",
prepend_ch("los/all/export", _ch_name),
false,
AUTO_RESOLVE_ON_WRITE);
// LO1 Charge Pump
get_rx_subtree()
->create<meta_range_t>("los/LO1/charge_pump/range")
.set(_ctrl->get_lo1_charge_pump_range());
expert_factory::add_dual_prop_node<double>(_expert,
get_rx_subtree(),
"los/LO1/charge_pump/value",
prepend_ch("los/LO1/charge_pump/desired", _ch_name),
prepend_ch("los/LO1/charge_pump/coerced", _ch_name),
(get_rx_id() == twinrx::TWINRX_REV_C_ID) ? 0.9e-6 : 0.9375e-6,
AUTO_RESOLVE_ON_READ_WRITE);
// LO2 Charge Pump
get_rx_subtree()
->create<meta_range_t>("los/LO2/charge_pump/range")
.set(_ctrl->get_lo2_charge_pump_range());
expert_factory::add_dual_prop_node<double>(_expert,
get_rx_subtree(),
"los/LO2/charge_pump/value",
prepend_ch("los/LO2/charge_pump/desired", _ch_name),
prepend_ch("los/LO2/charge_pump/coerced", _ch_name),
1.25e-6,
AUTO_RESOLVE_ON_READ_WRITE);
// Gain Specific
get_rx_subtree()
->create<meta_range_t>("gains/all/range")
.set(gain_range_t(0, 93, double(1.0)));
expert_factory::add_prop_node<double>(_expert,
get_rx_subtree(),
"gains/all/value",
prepend_ch("gain", _ch_name),
0.0,
AUTO_RESOLVE_ON_WRITE);
get_rx_subtree()
->create<std::vector<std::string>>("gains/all/profile/options")
.set({"low-noise", "low-distortion", "default"});
expert_factory::add_prop_node<std::string>(_expert,
get_rx_subtree(),
"gains/all/profile/value",
prepend_ch("gain_profile", _ch_name),
"default",
AUTO_RESOLVE_ON_WRITE);
// Antenna Specific
get_rx_subtree()
->create<std::vector<std::string>>("antenna/options")
.set({"RX1", "RX2"});
expert_factory::add_prop_node<std::string>(_expert,
get_rx_subtree(),
"antenna/value",
prepend_ch("antenna", _ch_name),
(_ch_name == "0" ? "RX1" : "RX2"),
AUTO_RESOLVE_ON_WRITE);
expert_factory::add_prop_node<bool>(_expert,
get_rx_subtree(),
"enabled",
prepend_ch("enabled", _ch_name),
false,
AUTO_RESOLVE_ON_WRITE);
// ID for cal data lookup. This depends on how many channels are enabled
expert_factory::add_prop_node<std::string>(
_expert, get_rx_subtree(), "id", prepend_ch("id", _ch_name), "twinrx");
// Readback
get_rx_subtree()
->create<sensor_value_t>("sensors/lo_locked")
.set_publisher([this]() { return this->get_lo_locked(); });
//---------------------------------------------------------
// Add internal channel-specific data nodes to expert
//---------------------------------------------------------
expert_factory::add_data_node<lo_inj_side_t>(
_expert, prepend_ch("ch/LO1/inj_side", _ch_name), INJ_LOW_SIDE);
expert_factory::add_data_node<lo_inj_side_t>(
_expert, prepend_ch("ch/LO2/inj_side", _ch_name), INJ_LOW_SIDE);
expert_factory::add_data_node<twinrx_ctrl::signal_path_t>(
_expert, prepend_ch("ch/signal_path", _ch_name), twinrx_ctrl::PATH_LOWBAND);
expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(
_expert, prepend_ch("ch/lb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1);
expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(
_expert, prepend_ch("ch/hb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1);
expert_factory::add_data_node<bool>(
_expert, prepend_ch("ch/lb_preamp_presel", _ch_name), false);
expert_factory::add_data_node<bool>(
_expert, prepend_ch("ant/lb_preamp_presel", _ch_name), false);
expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(
_expert, prepend_ch("ch/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS);
expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(
_expert, prepend_ch("ant/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS);
expert_factory::add_data_node<bool>(
_expert, prepend_ch("ch/preamp2", _ch_name), false);
expert_factory::add_data_node<bool>(
_expert, prepend_ch("ant/preamp2", _ch_name), false);
expert_factory::add_data_node<uint8_t>(
_expert, prepend_ch("ch/input_atten", _ch_name), 0);
expert_factory::add_data_node<uint8_t>(
_expert, prepend_ch("ant/input_atten", _ch_name), 0);
expert_factory::add_data_node<uint8_t>(
_expert, prepend_ch("ch/lb_atten", _ch_name), 0);
expert_factory::add_data_node<uint8_t>(
_expert, prepend_ch("ch/hb_atten", _ch_name), 0);
expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(
_expert, prepend_ch("ch/LO1/source", _ch_name), twinrx_ctrl::LO_INTERNAL);
expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(
_expert, prepend_ch("ch/LO2/source", _ch_name), twinrx_ctrl::LO_INTERNAL);
expert_factory::add_data_node<lo_synth_mapping_t>(
_expert, prepend_ch("synth/LO1/mapping", _ch_name), MAPPING_NONE);
expert_factory::add_data_node<lo_synth_mapping_t>(
_expert, prepend_ch("synth/LO2/mapping", _ch_name), MAPPING_NONE);
}
virtual ~twinrx_rcvr_fe(void) {}
sensor_value_t get_lo_locked()
{
bool locked = true;
twinrx_ctrl::channel_t ch = (_ch_name == "0") ? twinrx_ctrl::CH1
: twinrx_ctrl::CH2;
locked &= _ctrl->read_lo1_locked(ch);
locked &= _ctrl->read_lo2_locked(ch);
return sensor_value_t("LO", locked, "locked", "unlocked");
}
private:
expert_container::sptr _expert;
twinrx_ctrl::sptr _ctrl;
const std::string _ch_name;
};
/*!
* twinrx_rcvr is the top-level container for each
* TwinRX board. UHD will hold one instance of this
* class per TwinRX dboard. This class is responsible
* for owning all the control classes for the board.
*
*/
class twinrx_rcvr : public rx_dboard_base
{
public:
typedef std::shared_ptr<twinrx_rcvr> sptr;
twinrx_rcvr(ctor_args_t args) : rx_dboard_base(args)
{
_db_iface = get_iface();
twinrx_gpio::sptr gpio_iface = std::make_shared<twinrx_gpio>(_db_iface);
twinrx_cpld_regmap::sptr cpld_regs = std::make_shared<twinrx_cpld_regmap>();
cpld_regs->initialize(*gpio_iface, false);
_ctrl = twinrx_ctrl::make(_db_iface, gpio_iface, cpld_regs, get_rx_id());
_expert = expert_factory::create_container("twinrx_expert");
}
virtual ~twinrx_rcvr(void) {}
inline expert_container::sptr get_expert()
{
return _expert;
}
inline twinrx_ctrl::sptr get_ctrl()
{
return _ctrl;
}
virtual void initialize()
{
//---------------------------------------------------------
// Add internal channel-agnostic data nodes to expert
//---------------------------------------------------------
expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(
_expert, "com/LO1/export_source", twinrx_ctrl::LO_EXPORT_DISABLED);
expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(
_expert, "com/LO2/export_source", twinrx_ctrl::LO_EXPORT_DISABLED);
expert_factory::add_data_node<twinrx_ctrl::antenna_mapping_t>(
_expert, "com/ant_mapping", twinrx_ctrl::ANTX_NATIVE);
expert_factory::add_data_node<twinrx_ctrl::cal_mode_t>(
_expert, "com/cal_mode", twinrx_ctrl::CAL_DISABLED);
expert_factory::add_data_node<bool>(
_expert, "com/synth/LO1/hopping_enabled", false);
expert_factory::add_data_node<bool>(
_expert, "com/synth/LO2/hopping_enabled", false);
//---------------------------------------------------------
// Add workers to expert
//---------------------------------------------------------
// Channel (front-end) specific
for (const std::string& fe : _fe_names) {
expert_factory::add_worker_node<twinrx_freq_path_expert>(
_expert, _expert->node_retriever(), fe);
expert_factory::add_worker_node<twinrx_freq_coercion_expert>(
_expert, _expert->node_retriever(), fe);
expert_factory::add_worker_node<twinrx_chan_gain_expert>(
_expert, _expert->node_retriever(), fe);
expert_factory::add_worker_node<twinrx_scheduling_expert>(
_expert, _expert->node_retriever(), fe);
expert_factory::add_worker_node<twinrx_nyquist_expert>(
_expert, _expert->node_retriever(), fe, _db_iface);
}
// Channel (front-end) agnostic
expert_factory::add_worker_node<twinrx_lo_config_expert>(
_expert, _expert->node_retriever());
expert_factory::add_worker_node<twinrx_lo_mapping_expert>(
_expert, _expert->node_retriever(), STAGE_LO1);
expert_factory::add_worker_node<twinrx_lo_mapping_expert>(
_expert, _expert->node_retriever(), STAGE_LO2);
expert_factory::add_worker_node<twinrx_antenna_expert>(
_expert, _expert->node_retriever());
expert_factory::add_worker_node<twinrx_ant_gain_expert>(
_expert, _expert->node_retriever());
expert_factory::add_worker_node<twinrx_settings_expert>(
_expert, _expert->node_retriever(), _ctrl);
/*//Expert debug code
std::ofstream dot_file("/tmp/twinrx.dot", std::ios::out);
dot_file << _expert->to_dot();
dot_file.close();
*/
_expert->debug_audit();
_expert->resolve_all(true);
}
static dboard_base::sptr make_twinrx_fe(dboard_base::ctor_args_t args)
{
const dboard_ctor_args_t& db_args = dboard_ctor_args_t::cast(args);
sptr container = std::dynamic_pointer_cast<twinrx_rcvr>(db_args.rx_container);
if (container) {
dboard_base::sptr fe = dboard_base::sptr(
new twinrx_rcvr_fe(args, container->get_expert(), container->get_ctrl()));
container->add_twinrx_fe(db_args.sd_name);
return fe;
} else {
throw uhd::assertion_error("error creating twinrx frontend");
}
}
protected:
inline void add_twinrx_fe(const std::string& name)
{
_fe_names.push_back(name);
}
private:
typedef std::map<std::string, dboard_base::sptr> twinrx_fe_map_t;
dboard_iface::sptr _db_iface;
twinrx_ctrl::sptr _ctrl;
std::vector<std::string> _fe_names;
expert_container::sptr _expert;
};
/*!
* Initialization Sequence for each TwinRX board:
* - make_twinrx_container is called which creates an instance of twinrx_rcvr
* - twinrx_rcvr::make_twinrx_fe is called with channel "0" which creates an instance of
* twinrx_rcvr_fe
* - twinrx_rcvr::make_twinrx_fe is called with channel "1" which creates an instance of
* twinrx_rcvr_fe
* - twinrx_rcvr::initialize is called with finishes the init sequence
*
*/
static dboard_base::sptr make_twinrx_container(dboard_base::ctor_args_t args)
{
return dboard_base::sptr(new twinrx_rcvr(args));
}
UHD_STATIC_BLOCK(reg_twinrx_dboards)
{
dboard_manager::register_dboard_restricted(twinrx::TWINRX_REV_A_ID,
&twinrx_rcvr::make_twinrx_fe,
"TwinRX Rev A",
{"0", "1"},
&make_twinrx_container);
dboard_manager::register_dboard_restricted(twinrx::TWINRX_REV_B_ID,
&twinrx_rcvr::make_twinrx_fe,
"TwinRX Rev B",
{"0", "1"},
&make_twinrx_container);
dboard_manager::register_dboard_restricted(twinrx::TWINRX_REV_C_ID,
&twinrx_rcvr::make_twinrx_fe,
"TwinRX Rev C",
{"0", "1"},
&make_twinrx_container);
}