// // Copyright 2018 Ettus Research, a National Instruments Company // Copyright 2019 Ettus Research, a National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // #include "rhodium_constants.hpp" #include "rhodium_radio_control.hpp" #include #include #include #include #include #include #include #include #include using namespace uhd; using namespace uhd::usrp; using namespace uhd::rfnoc; namespace { enum slave_select_t { SEN_CPLD = 8, SEN_TX_LO = 1, SEN_RX_LO = 2, SEN_LO_DIST = 4 /* Unused */ }; constexpr double RHODIUM_DEFAULT_FREQ = 2.5e9; // Hz // An invalid default index ensures that set gain will apply settings // the first time it is called constexpr double RHODIUM_DEFAULT_INVALID_GAIN = -1; // gain index constexpr double RHODIUM_DEFAULT_GAIN = 0; // gain index constexpr double RHODIUM_DEFAULT_LO_GAIN = 30; // gain index constexpr char RHODIUM_DEFAULT_RX_ANTENNA[] = "RX2"; constexpr char RHODIUM_DEFAULT_TX_ANTENNA[] = "TX/RX"; constexpr auto RHODIUM_DEFAULT_MASH_ORDER = lmx2592_iface::mash_order_t::THIRD; //! Returns the SPI config used by the CPLD spi_config_t _get_cpld_spi_config() { spi_config_t spi_config; spi_config.use_custom_divider = true; spi_config.divider = 10; spi_config.mosi_edge = spi_config_t::EDGE_RISE; spi_config.miso_edge = spi_config_t::EDGE_FALL; return spi_config; } //! Returns the SPI config used by the TX LO spi_config_t _get_tx_lo_spi_config() { spi_config_t spi_config; spi_config.use_custom_divider = true; spi_config.divider = 10; spi_config.mosi_edge = spi_config_t::EDGE_RISE; spi_config.miso_edge = spi_config_t::EDGE_FALL; return spi_config; } //! Returns the SPI config used by the RX LO spi_config_t _get_rx_lo_spi_config() { spi_config_t spi_config; spi_config.use_custom_divider = true; spi_config.divider = 10; spi_config.mosi_edge = spi_config_t::EDGE_RISE; spi_config.miso_edge = spi_config_t::EDGE_FALL; return spi_config; } std::function _generate_write_spi( uhd::spi_iface::sptr spi, slave_select_t slave, spi_config_t config) { return [spi, slave, config](const uint32_t transaction) { spi->write_spi(slave, config, transaction, 24); }; } std::function _generate_read_spi( uhd::spi_iface::sptr spi, slave_select_t slave, spi_config_t config) { return [spi, slave, config](const uint32_t transaction) { return spi->read_spi(slave, config, transaction, 24); }; } } // namespace void rhodium_radio_control_impl::_init_defaults() { RFNOC_LOG_TRACE("Initializing defaults..."); const size_t num_rx_chans = get_num_output_ports(); const size_t num_tx_chans = get_num_input_ports(); UHD_ASSERT_THROW(num_tx_chans == RHODIUM_NUM_CHANS); UHD_ASSERT_THROW(num_rx_chans == RHODIUM_NUM_CHANS); for (size_t chan = 0; chan < num_rx_chans; chan++) { radio_control_impl::set_rx_frequency(RHODIUM_DEFAULT_FREQ, chan); radio_control_impl::set_rx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); radio_control_impl::set_rx_antenna(RHODIUM_DEFAULT_RX_ANTENNA, chan); radio_control_impl::set_rx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan); } for (size_t chan = 0; chan < num_tx_chans; chan++) { radio_control_impl::set_tx_frequency(RHODIUM_DEFAULT_FREQ, chan); radio_control_impl::set_tx_gain(RHODIUM_DEFAULT_INVALID_GAIN, chan); radio_control_impl::set_tx_antenna(RHODIUM_DEFAULT_TX_ANTENNA, chan); radio_control_impl::set_tx_bandwidth(RHODIUM_DEFAULT_BANDWIDTH, chan); } register_property(&_spur_dodging_mode); register_property(&_spur_dodging_threshold); register_property(&_highband_spur_reduction_mode); // Update configurable block arguments from the device arguments provided const auto block_args = get_block_args(); if (block_args.has_key(SPUR_DODGING_PROP_NAME)) { _spur_dodging_mode.set(block_args.get(SPUR_DODGING_PROP_NAME)); } if (block_args.has_key(SPUR_DODGING_THRESHOLD_PROP_NAME)) { _spur_dodging_threshold.set(block_args.cast( SPUR_DODGING_THRESHOLD_PROP_NAME, RHODIUM_DEFAULT_SPUR_DOGING_THRESHOLD)); } if (block_args.has_key(HIGHBAND_SPUR_REDUCTION_PROP_NAME)) { _highband_spur_reduction_mode.set( block_args.get(HIGHBAND_SPUR_REDUCTION_PROP_NAME)); } } void rhodium_radio_control_impl::_init_peripherals() { RFNOC_LOG_TRACE("Initializing SPI core..."); _spi = spi_core_3000::make( [this](uint32_t addr, uint32_t data) { regs().poke32(addr, data, get_command_time(0)); }, [this](uint32_t addr) { return regs().peek32(addr, get_command_time(0)); }, regmap::REG_SPI_W, 8, regmap::REG_SPI_R); _wb_iface = RFNOC_MAKE_WB_IFACE(0, 0); RFNOC_LOG_TRACE("Initializing CPLD..."); _cpld = std::make_shared( _generate_write_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config()), _generate_read_spi(this->_spi, SEN_CPLD, _get_cpld_spi_config())); RFNOC_LOG_TRACE("Initializing TX frontend DSP core...") _tx_fe_core = tx_frontend_core_200::make(_wb_iface, n320_regs::SR_TX_FE_BASE); _tx_fe_core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); _tx_fe_core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); _tx_fe_core->populate_subtree(get_tree()->subtree(FE_PATH / "tx_fe_corrections" / 0)); RFNOC_LOG_TRACE("Initializing RX frontend DSP core...") _rx_fe_core = rx_frontend_core_3000::make(_wb_iface, n320_regs::SR_TX_FE_BASE); _rx_fe_core->set_adc_rate(_master_clock_rate); _rx_fe_core->set_dc_offset(rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE); _rx_fe_core->set_dc_offset_auto(rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE); _rx_fe_core->set_iq_balance(rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE); _rx_fe_core->populate_subtree(get_tree()->subtree(FE_PATH / "rx_fe_corrections" / 0)); RFNOC_LOG_TRACE("Writing initial gain values..."); set_tx_gain(RHODIUM_DEFAULT_GAIN, 0); set_tx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); set_rx_gain(RHODIUM_DEFAULT_GAIN, 0); set_rx_lo_gain(RHODIUM_DEFAULT_LO_GAIN, RHODIUM_LO1, 0); RFNOC_LOG_TRACE("Initializing TX LO..."); _tx_lo = lmx2592_iface::make( _generate_write_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config()), _generate_read_spi(this->_spi, SEN_TX_LO, _get_tx_lo_spi_config())); RFNOC_LOG_TRACE("Writing initial TX LO state..."); _tx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); _tx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); RFNOC_LOG_TRACE("Initializing RX LO..."); _rx_lo = lmx2592_iface::make( _generate_write_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config()), _generate_read_spi(this->_spi, SEN_RX_LO, _get_rx_lo_spi_config())); RFNOC_LOG_TRACE("Writing initial RX LO state..."); _rx_lo->set_reference_frequency(RHODIUM_LO1_REF_FREQ); _rx_lo->set_mash_order(RHODIUM_DEFAULT_MASH_ORDER); RFNOC_LOG_TRACE("Initializing GPIOs..."); // DB GPIOs _gpio = usrp::gpio_atr::gpio_atr_3000::make(_wb_iface, n320_regs::SR_DB_GPIO, n320_regs::RB_DB_GPIO, n320_regs::PERIPH_REG_OFFSET); _gpio->set_atr_mode(usrp::gpio_atr::MODE_ATR, // Enable ATR mode for Rhodium bits RHODIUM_GPIO_MASK); _gpio->set_atr_mode(usrp::gpio_atr::MODE_GPIO, // Disable ATR mode for unused bits ~RHODIUM_GPIO_MASK); _gpio->set_gpio_ddr(usrp::gpio_atr::DDR_OUTPUT, // Make all GPIOs outputs usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); _fp_gpio = gpio_atr::gpio_atr_3000::make(_wb_iface, n320_regs::SR_FP_GPIO, n320_regs::RB_FP_GPIO, n320_regs::PERIPH_REG_OFFSET); RFNOC_LOG_TRACE("Set initial ATR values..."); _update_atr(RHODIUM_DEFAULT_TX_ANTENNA, TX_DIRECTION); _update_atr(RHODIUM_DEFAULT_RX_ANTENNA, RX_DIRECTION); // Updating the TX frequency path may include an update to SW10, which is // GPIO controlled, so this must follow CPLD and GPIO initialization RFNOC_LOG_TRACE("Writing initial switch values..."); _update_tx_freq_switches(RHODIUM_DEFAULT_FREQ); _update_rx_freq_switches(RHODIUM_DEFAULT_FREQ); // Antenna setting requires both CPLD and GPIO control RFNOC_LOG_TRACE("Setting initial antenna settings"); _update_tx_output_switches(RHODIUM_DEFAULT_TX_ANTENNA); _update_rx_input_switches(RHODIUM_DEFAULT_RX_ANTENNA); RFNOC_LOG_TRACE("Checking for existence of LO Distribution board"); _lo_dist_present = _rpcc->request_with_token(_rpc_prefix + "is_lo_dist_present"); RFNOC_LOG_DEBUG( "LO distribution board is" << (_lo_dist_present ? "" : " NOT") << " present"); RFNOC_LOG_TRACE("Reading EEPROM content..."); const size_t db_idx = get_block_id().get_block_count(); _db_eeprom = this->_rpcc->request_with_token("get_db_eeprom", db_idx); } // Reminder: The property must not own any properties, it can only interact with // the API of this block! void rhodium_radio_control_impl::_init_frontend_subtree(uhd::property_tree::sptr subtree) { const fs_path tx_fe_path = fs_path("tx_frontends") / 0; const fs_path rx_fe_path = fs_path("rx_frontends") / 0; RFNOC_LOG_TRACE("Adding non-RFNoC block properties for channel 0" << " to prop tree path " << tx_fe_path << " and " << rx_fe_path); // TX Standard attributes subtree->create(tx_fe_path / "name").set(RHODIUM_FE_NAME); subtree->create(tx_fe_path / "connection") .add_coerced_subscriber( [this](const std::string& conn) { this->_set_tx_fe_connection(conn); }) .set_publisher([this]() { return this->_get_tx_fe_connection(); }); subtree->create(tx_fe_path / "tune_args") .set(device_addr_t()) .add_coerced_subscriber( [this](const device_addr_t& args) { set_tx_tune_args(args, 0); }) .set_publisher([this]() { return _tune_args.at(uhd::TX_DIRECTION); }); // RX Standard attributes subtree->create(rx_fe_path / "name").set(RHODIUM_FE_NAME); subtree->create(rx_fe_path / "connection") .add_coerced_subscriber( [this](const std::string& conn) { this->_set_rx_fe_connection(conn); }) .set_publisher([this]() { return this->_get_rx_fe_connection(); }); subtree->create(rx_fe_path / "tune_args") .set(device_addr_t()) .add_coerced_subscriber( [this](const device_addr_t& args) { set_rx_tune_args(args, 0); }) .set_publisher([this]() { return _tune_args.at(uhd::RX_DIRECTION); }); ; // TX Antenna subtree->create(tx_fe_path / "antenna" / "value") .add_coerced_subscriber( [this](const std::string& ant) { this->set_tx_antenna(ant, 0); }) .set_publisher([this]() { return this->get_tx_antenna(0); }); subtree->create>(tx_fe_path / "antenna" / "options") .add_coerced_subscriber([](const std::vector&) { throw uhd::runtime_error("Attempting to update antenna options!"); }) .set_publisher([this]() { return get_tx_antennas(0); }); // RX Antenna subtree->create(rx_fe_path / "antenna" / "value") .add_coerced_subscriber( [this](const std::string& ant) { this->set_rx_antenna(ant, 0); }) .set_publisher([this]() { return this->get_rx_antenna(0); }); subtree->create>(rx_fe_path / "antenna" / "options") .add_coerced_subscriber([](const std::vector&) { throw uhd::runtime_error("Attempting to update antenna options!"); }) .set_publisher([this]() { return get_rx_antennas(0); }); // TX frequency subtree->create(tx_fe_path / "freq" / "value") .set_coercer( [this](const double freq) { return this->set_tx_frequency(freq, 0); }) .set_publisher([this]() { return this->get_tx_frequency(0); }); subtree->create(tx_fe_path / "freq" / "range") .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update freq range!"); }) .set_publisher([this]() { return get_tx_frequency_range(0); }); // RX frequency subtree->create(rx_fe_path / "freq" / "value") .set_coercer( [this](const double freq) { return this->set_rx_frequency(freq, 0); }) .set_publisher([this]() { return this->get_rx_frequency(0); }); subtree->create(rx_fe_path / "freq" / "range") .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update freq range!"); }) .set_publisher([this]() { return get_rx_frequency_range(0); }); // TX bandwidth subtree->create(tx_fe_path / "bandwidth" / "value") .set_coercer([this](const double bw) { return this->set_tx_bandwidth(bw, 0); }) .set_publisher([this]() { return this->get_tx_bandwidth(0); }); subtree->create(tx_fe_path / "bandwidth" / "range") .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update bandwidth range!"); }) .set_publisher([this]() { return get_tx_bandwidth_range(0); }); // RX bandwidth subtree->create(rx_fe_path / "bandwidth" / "value") .set_coercer([this](const double bw) { return this->set_rx_bandwidth(bw, 0); }) .set_publisher([this]() { return this->get_rx_bandwidth(0); }); subtree->create(rx_fe_path / "bandwidth" / "range") .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update bandwidth range!"); }) .set_publisher([this]() { return get_rx_bandwidth_range(0); }); // TX gains subtree->create(tx_fe_path / "gains" / "all" / "value") .set_coercer([this](const double gain) { return this->set_tx_gain(gain, 0); }) .set_publisher([this]() { return radio_control_impl::get_tx_gain(0); }); subtree->create(tx_fe_path / "gains" / "all" / "range") .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update gain range!"); }) .set_publisher([this]() { return get_tx_gain_range(0); }); // RX gains subtree->create(rx_fe_path / "gains" / "all" / "value") .set_coercer([this](const double gain) { return this->set_rx_gain(gain, 0); }) .set_publisher([this]() { return radio_control_impl::get_rx_gain(0); }); subtree->create(rx_fe_path / "gains" / "all" / "range") .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update gain range!"); }) .set_publisher([this]() { return get_rx_gain_range(0); }); // LO Specific // RX LO // RX LO1 Frequency subtree->create(rx_fe_path / "los" / RHODIUM_LO1 / "freq/value") .set_publisher([this]() { return this->get_rx_lo_freq(RHODIUM_LO1, 0); }) .set_coercer([this](const double freq) { return this->set_rx_lo_freq(freq, RHODIUM_LO1, 0); }); subtree->create(rx_fe_path / "los" / RHODIUM_LO1 / "freq/range") .set_publisher([this]() { return this->get_rx_lo_freq_range(RHODIUM_LO1, 0); }); // RX LO1 Source subtree ->create>( rx_fe_path / "los" / RHODIUM_LO1 / "source/options") .set_publisher([this]() { return this->get_rx_lo_sources(RHODIUM_LO1, 0); }); subtree->create(rx_fe_path / "los" / RHODIUM_LO1 / "source/value") .add_coerced_subscriber([this](const std::string& src) { this->set_rx_lo_source(src, RHODIUM_LO1, 0); }) .set_publisher([this]() { return this->get_rx_lo_source(RHODIUM_LO1, 0); }); // RX LO1 Export subtree->create(rx_fe_path / "los" / RHODIUM_LO1 / "export") .add_coerced_subscriber([this](bool enabled) { this->set_rx_lo_export_enabled(enabled, RHODIUM_LO1, 0); }); // RX LO1 Gain subtree ->create( rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "value") .set_publisher([this]() { return this->get_rx_lo_gain(RHODIUM_LO1, 0); }) .set_coercer([this](const double gain) { return this->set_rx_lo_gain(gain, RHODIUM_LO1, 0); }); subtree ->create( rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "range") .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_gain_range(); }) .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update LO gain range!"); }); // RX LO1 Output Power subtree ->create( rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "value") .set_publisher([this]() { return this->get_rx_lo_power(RHODIUM_LO1, 0); }) .set_coercer([this](const double gain) { return this->set_rx_lo_power(gain, RHODIUM_LO1, 0); }); subtree ->create( rx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "range") .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_power_range(); }) .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update LO output power range!"); }); // RX LO2 Frequency subtree->create(rx_fe_path / "los" / RHODIUM_LO2 / "freq/value") .set_publisher([this]() { return this->get_rx_lo_freq(RHODIUM_LO2, 0); }) .set_coercer( [this](double freq) { return this->set_rx_lo_freq(freq, RHODIUM_LO2, 0); }); subtree->create(rx_fe_path / "los" / RHODIUM_LO2 / "freq/range") .set_publisher([this]() { return this->get_rx_lo_freq_range(RHODIUM_LO2, 0); }); // RX LO2 Source subtree ->create>( rx_fe_path / "los" / RHODIUM_LO2 / "source/options") .set_publisher([this]() { return this->get_rx_lo_sources(RHODIUM_LO2, 0); }); subtree->create(rx_fe_path / "los" / RHODIUM_LO2 / "source/value") .add_coerced_subscriber( [this](std::string src) { this->set_rx_lo_source(src, RHODIUM_LO2, 0); }) .set_publisher([this]() { return this->get_rx_lo_source(RHODIUM_LO2, 0); }); // RX LO2 Export subtree->create(rx_fe_path / "los" / RHODIUM_LO2 / "export") .add_coerced_subscriber([this](bool enabled) { this->set_rx_lo_export_enabled(enabled, RHODIUM_LO2, 0); }); // RX ALL LOs subtree->create(rx_fe_path / "los" / ALL_LOS / "source/value") .add_coerced_subscriber( [this](std::string src) { this->set_rx_lo_source(src, ALL_LOS, 0); }) .set_publisher([this]() { return this->get_rx_lo_source(ALL_LOS, 0); }); subtree ->create>( rx_fe_path / "los" / ALL_LOS / "source/options") .set_publisher([this]() { return this->get_rx_lo_sources(ALL_LOS, 0); }); subtree->create(rx_fe_path / "los" / ALL_LOS / "export") .add_coerced_subscriber( [this](bool enabled) { this->set_rx_lo_export_enabled(enabled, ALL_LOS, 0); }) .set_publisher([this]() { return this->get_rx_lo_export_enabled(ALL_LOS, 0); }); // TX LO // TX LO1 Frequency subtree->create(tx_fe_path / "los" / RHODIUM_LO1 / "freq/value ") .set_publisher([this]() { return this->get_tx_lo_freq(RHODIUM_LO1, 0); }) .set_coercer( [this](double freq) { return this->set_tx_lo_freq(freq, RHODIUM_LO1, 0); }); subtree->create(tx_fe_path / "los" / RHODIUM_LO1 / "freq/range") .set_publisher([this]() { return this->get_rx_lo_freq_range(RHODIUM_LO1, 0); }); // TX LO1 Source subtree ->create>( tx_fe_path / "los" / RHODIUM_LO1 / "source/options") .set_publisher([this]() { return this->get_tx_lo_sources(RHODIUM_LO1, 0); }); subtree->create(tx_fe_path / "los" / RHODIUM_LO1 / "source/value") .add_coerced_subscriber( [this](std::string src) { this->set_tx_lo_source(src, RHODIUM_LO1, 0); }) .set_publisher([this]() { return this->get_tx_lo_source(RHODIUM_LO1, 0); }); // TX LO1 Export subtree->create(tx_fe_path / "los" / RHODIUM_LO1 / "export") .add_coerced_subscriber([this](bool enabled) { this->set_tx_lo_export_enabled(enabled, RHODIUM_LO1, 0); }); // TX LO1 Gain subtree ->create( tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "value") .set_publisher([this]() { return this->get_tx_lo_gain(RHODIUM_LO1, 0); }) .set_coercer([this](const double gain) { return this->set_tx_lo_gain(gain, RHODIUM_LO1, 0); }); subtree ->create( tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_GAIN / "range") .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_gain_range(); }) .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update LO gain range!"); }); // TX LO1 Output Power subtree ->create( tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "value") .set_publisher([this]() { return this->get_tx_lo_power(RHODIUM_LO1, 0); }) .set_coercer([this](const double gain) { return this->set_tx_lo_power(gain, RHODIUM_LO1, 0); }); subtree ->create( tx_fe_path / "los" / RHODIUM_LO1 / "gains" / RHODIUM_LO_POWER / "range") .set_publisher([]() { return rhodium_radio_control_impl::_get_lo_power_range(); }) .add_coerced_subscriber([](const meta_range_t&) { throw uhd::runtime_error("Attempting to update LO output power range!"); }); // TX LO2 Frequency subtree->create(tx_fe_path / "los" / RHODIUM_LO2 / "freq/value") .set_publisher([this]() { return this->get_tx_lo_freq(RHODIUM_LO2, 0); }) .set_coercer( [this](double freq) { return this->set_tx_lo_freq(freq, RHODIUM_LO2, 0); }); subtree->create(tx_fe_path / "los" / RHODIUM_LO2 / "freq/range") .set_publisher([this]() { return this->get_tx_lo_freq_range(RHODIUM_LO2, 0); }); // TX LO2 Source subtree ->create>( tx_fe_path / "los" / RHODIUM_LO2 / "source/options") .set_publisher([this]() { return this->get_tx_lo_sources(RHODIUM_LO2, 0); }); subtree->create(tx_fe_path / "los" / RHODIUM_LO2 / "source/value") .add_coerced_subscriber( [this](std::string src) { this->set_tx_lo_source(src, RHODIUM_LO2, 0); }) .set_publisher([this]() { return this->get_tx_lo_source(RHODIUM_LO2, 0); }); // TX LO2 Export subtree->create(tx_fe_path / "los" / RHODIUM_LO2 / "export") .add_coerced_subscriber([this](bool enabled) { this->set_tx_lo_export_enabled(enabled, RHODIUM_LO2, 0); }); // TX ALL LOs subtree->create(tx_fe_path / "los" / ALL_LOS / "source/value") .add_coerced_subscriber( [this](std::string src) { this->set_tx_lo_source(src, ALL_LOS, 0); }) .set_publisher([this]() { return this->get_tx_lo_source(ALL_LOS, 0); }); subtree ->create>( tx_fe_path / "los" / ALL_LOS / "source/options") .set_publisher([this]() { return this->get_tx_lo_sources(ALL_LOS, 0); }); subtree->create(tx_fe_path / "los" / ALL_LOS / "export") .add_coerced_subscriber( [this](bool enabled) { this->set_tx_lo_export_enabled(enabled, ALL_LOS, 0); }) .set_publisher([this]() { return this->get_tx_lo_export_enabled(ALL_LOS, 0); }); // LO Distribution Output Ports if (_lo_dist_present) { for (const auto& port : LO_OUTPUT_PORT_NAMES) { subtree ->create(tx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port / "export") .add_coerced_subscriber([this, port](bool enabled) { this->set_tx_lo_output_enabled(enabled, port, 0); }) .set_publisher( [this, port]() { return this->get_tx_lo_output_enabled(port, 0); }); subtree ->create(rx_fe_path / "los" / RHODIUM_LO1 / "lo_distribution" / port / "export") .add_coerced_subscriber([this, port](bool enabled) { this->set_rx_lo_output_enabled(enabled, port, 0); }) .set_publisher( [this, port]() { return this->get_rx_lo_output_enabled(port, 0); }); } } // Sensors auto rx_sensor_names = get_rx_sensor_names(0); for (const auto& sensor_name : rx_sensor_names) { RFNOC_LOG_TRACE("Adding RX sensor " << sensor_name); get_tree() ->create(rx_fe_path / "sensors" / sensor_name) .add_coerced_subscriber([](const sensor_value_t&) { throw uhd::runtime_error("Attempting to write to sensor!"); }) .set_publisher( [this, sensor_name]() { return get_rx_sensor(sensor_name, 0); }); } auto tx_sensor_names = get_tx_sensor_names(0); for (const auto& sensor_name : tx_sensor_names) { RFNOC_LOG_TRACE("Adding TX sensor " << sensor_name); get_tree() ->create(tx_fe_path / "sensors" / sensor_name) .add_coerced_subscriber([](const sensor_value_t&) { throw uhd::runtime_error("Attempting to write to sensor!"); }) .set_publisher( [this, sensor_name]() { return get_tx_sensor(sensor_name, 0); }); } } void rhodium_radio_control_impl::_init_prop_tree() { this->_init_frontend_subtree(get_tree()->subtree(DB_PATH)); get_tree()->create(fs_path("rx_codecs") / "name").set("ad9695-625"); get_tree()->create(fs_path("tx_codecs") / "name").set("dac37j82"); } void rhodium_radio_control_impl::_init_mpm() { auto block_args = get_block_args(); if (block_args.has_key("identify")) { const std::string identify_val = block_args.get("identify"); int identify_duration = std::atoi(identify_val.c_str()); if (identify_duration == 0) { identify_duration = 5; } RFNOC_LOG_INFO("Running LED identification process for " << identify_duration << " seconds."); _identify_with_leds(identify_duration); } // Note: MCR gets set during the init() call (prior to this), which takes // in arguments from the device args. So if block_args contains a // master_clock_rate key, then it should better be whatever the device is // configured to do. _master_clock_rate = _rpcc->request_with_token(_rpc_prefix + "get_master_clock_rate"); if (block_args.cast("master_clock_rate", _master_clock_rate) != _master_clock_rate) { throw uhd::runtime_error( std::string("Master clock rate mismatch. Device returns ") + std::to_string(_master_clock_rate) + " MHz, " "but should have been " + std::to_string( block_args.cast("master_clock_rate", _master_clock_rate)) + " MHz."); } RFNOC_LOG_DEBUG("Master Clock Rate is: " << (_master_clock_rate / 1e6) << " MHz."); set_tick_rate(_master_clock_rate); _n3xx_timekeeper->update_tick_rate(_master_clock_rate); radio_control_impl::set_rate(_master_clock_rate); // Unlike N310, N320 does not have any MPM sensors. }