diff options
Diffstat (limited to 'host/lib/usrp/common')
19 files changed, 1695 insertions, 266 deletions
diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index 9dabc4e0b..9f4cf09b5 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -29,7 +29,8 @@ INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver") LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/adf4001_ctrl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/adf435x_common.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adf435x.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adf5355.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad936x_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp @@ -37,4 +38,5 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/fifo_ctrl_excelsior.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usrp3_fw_ctrl_iface.cpp ) diff --git a/host/lib/usrp/common/ad9361_ctrl.cpp b/host/lib/usrp/common/ad9361_ctrl.cpp index 54f0fcdbf..654311424 100644 --- a/host/lib/usrp/common/ad9361_ctrl.cpp +++ b/host/lib/usrp/common/ad9361_ctrl.cpp @@ -93,18 +93,30 @@ class ad9361_ctrl_impl : public ad9361_ctrl { public: ad9361_ctrl_impl(ad9361_params::sptr client_settings, ad9361_io::sptr io_iface): - _device(client_settings, io_iface) + _device(client_settings, io_iface), _safe_spi(io_iface), _timed_spi(io_iface) { _device.initialize(); } + void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) + { + _timed_spi = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num); + _use_timed_spi(); + } + + void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) + { + _safe_spi = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num); + } + double set_gain(const std::string &which, const double value) { boost::lock_guard<boost::mutex> lock(_mutex); ad9361_device_t::direction_t direction = _get_direction_from_antenna(which); ad9361_device_t::chain_t chain =_get_chain_from_antenna(which); - return _device.set_gain(direction, chain, value); + double return_val = _device.set_gain(direction, chain, value); + return return_val; } void set_agc(const std::string &which, bool enable) @@ -112,7 +124,7 @@ public: boost::lock_guard<boost::mutex> lock(_mutex); ad9361_device_t::chain_t chain =_get_chain_from_antenna(which); - _device.set_agc(chain, enable); + _device.set_agc(chain, enable); } void set_agc_mode(const std::string &which, const std::string &mode) @@ -133,6 +145,9 @@ public: { boost::lock_guard<boost::mutex> lock(_mutex); + // Changing clock rate will disrupt AD9361's sample clock + _use_safe_spi(); + //clip to known bounds const meta_range_t clock_rate_range = ad9361_ctrl::get_clock_rate_range(); const double clipped_rate = clock_rate_range.clip(rate); @@ -144,7 +159,11 @@ public: ) % (rate/1e6) % (clipped_rate/1e6) << std::endl; } - return _device.set_clock_rate(clipped_rate); + double return_rate = _device.set_clock_rate(clipped_rate); + + _use_timed_spi(); + + return return_rate; } //! set which RX and TX chains/antennas are active @@ -152,7 +171,11 @@ public: { boost::lock_guard<boost::mutex> lock(_mutex); + // If both RX chains are disabled then the AD9361's sample clock is disabled + _use_safe_spi(); _device.set_active_chains(tx1, tx2, rx1, rx2); + _use_timed_spi(); + } //! tune the given frontend, return the exact value @@ -166,7 +189,8 @@ public: const double value = ad9361_ctrl::get_rf_freq_range().clip(clipped_freq); ad9361_device_t::direction_t direction = _get_direction_from_antenna(which); - return _device.tune(direction, value); + double return_val = _device.tune(direction, value); + return return_val; } //! get the current frequency for the given frontend @@ -283,16 +307,28 @@ private: return ad9361_device_t::CHAIN_1; } - ad9361_device_t _device; - boost::mutex _mutex; + void _use_safe_spi() { + _device.set_io_iface(_safe_spi); + } + + void _use_timed_spi() { + _device.set_io_iface(_timed_spi); + } + + ad9361_device_t _device; + ad9361_io::sptr _safe_spi; // SPI core that uses an always available clock + ad9361_io::sptr _timed_spi; // SPI core that has a dependency on the AD9361's sample clock (i.e. radio clk) + boost::mutex _mutex; }; //---------------------------------------------------------------------- // Make an instance of the AD9361 Control interface //---------------------------------------------------------------------- ad9361_ctrl::sptr ad9361_ctrl::make_spi( - ad9361_params::sptr client_settings, uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) -{ + ad9361_params::sptr client_settings, + uhd::spi_iface::sptr spi_iface, + boost::uint32_t slave_num +) { boost::shared_ptr<ad9361_io_spi> spi_io_iface = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num); return sptr(new ad9361_ctrl_impl(client_settings, spi_io_iface)); } diff --git a/host/lib/usrp/common/ad9361_ctrl.hpp b/host/lib/usrp/common/ad9361_ctrl.hpp index 5c438ee9c..5770c3ec4 100644 --- a/host/lib/usrp/common/ad9361_ctrl.hpp +++ b/host/lib/usrp/common/ad9361_ctrl.hpp @@ -56,7 +56,13 @@ public: //! make a new codec control object static sptr make_spi( - ad9361_params::sptr client_settings, uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num); + ad9361_params::sptr client_settings, + uhd::spi_iface::sptr spi_iface, + boost::uint32_t slave_num + ); + + virtual void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) = 0; + virtual void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) = 0; //! Get a list of gain names for RX or TX static std::vector<std::string> get_gain_names(const std::string &/*which*/) @@ -89,8 +95,10 @@ public: //! get the clock rate range for the frontend static uhd::meta_range_t get_clock_rate_range(void) { - //return uhd::meta_range_t(220e3, 61.44e6); - return uhd::meta_range_t(5e6, ad9361_device_t::AD9361_MAX_CLOCK_RATE); //5 MHz DCM low end + return uhd::meta_range_t( + ad9361_device_t::AD9361_MIN_CLOCK_RATE, + ad9361_device_t::AD9361_MAX_CLOCK_RATE + ); } //! set the filter bandwidth for the frontend's analog low pass diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp index 0a8a61575..095017bb6 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp @@ -91,6 +91,7 @@ int get_num_taps(int max_num_taps) { } const double ad9361_device_t::AD9361_MAX_GAIN = 89.75; +const double ad9361_device_t::AD9361_MIN_CLOCK_RATE = 220e3; const double ad9361_device_t::AD9361_MAX_CLOCK_RATE = 61.44e6; const double ad9361_device_t::AD9361_CAL_VALID_WINDOW = 100e6; // Max bandwdith is due to filter rolloff in analog filter stage @@ -770,7 +771,7 @@ void ad9361_device_t::_calibrate_rf_dc_offset() size_t count = 0; _io_iface->poke8(0x016, 0x02); while (_io_iface->peek8(0x016) & 0x02) { - if (count > 100) { + if (count > 200) { throw uhd::runtime_error("[ad9361_device_t] RF DC Offset Calibration Failure"); break; } @@ -821,7 +822,7 @@ void ad9361_device_t::_calibrate_rx_quadrature() size_t count = 0; _io_iface->poke8(0x016, 0x20); while (_io_iface->peek8(0x016) & 0x20) { - if (count > 100) { + if (count > 1000) { throw uhd::runtime_error("[ad9361_device_t] Rx Quadrature Calibration Failure"); break; } @@ -1564,6 +1565,12 @@ void ad9361_device_t::initialize() _io_iface->poke8(0x000, 0x00); boost::this_thread::sleep(boost::posix_time::milliseconds(20)); + /* Check device ID to make sure iface works */ + boost::uint32_t device_id = (_io_iface->peek8(0x037) & 0x8); + if (device_id != 0x8) { + throw uhd::runtime_error(str(boost::format("[ad9361_device_t::initialize] Device ID readback failure. Expected: 0x8, Received: 0x%x") % device_id)); + } + /* There is not a WAT big enough for this. */ _io_iface->poke8(0x3df, 0x01); @@ -1773,6 +1780,10 @@ void ad9361_device_t::initialize() _io_iface->poke8(0x014, 0x21); } +void ad9361_device_t::set_io_iface(ad9361_io::sptr io_iface) +{ + _io_iface = io_iface; +} /* This function sets the RX / TX rate between AD9361 and the FPGA, and * thus determines the interpolation / decimation required in the FPGA to diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.h b/host/lib/usrp/common/ad9361_driver/ad9361_device.h index 66bc2e8b9..d0e8a7e39 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.h +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.h @@ -75,6 +75,9 @@ public: /* Initialize the AD9361 codec. */ void initialize(); + /* Set SPI interface */ + void set_io_iface(ad9361_io::sptr io_iface); + /* This function sets the RX / TX rate between AD9361 and the FPGA, and * thus determines the interpolation / decimation required in the FPGA to * achieve the user's requested rate. @@ -157,6 +160,7 @@ public: //Constants static const double AD9361_MAX_GAIN; static const double AD9361_MAX_CLOCK_RATE; + static const double AD9361_MIN_CLOCK_RATE; static const double AD9361_CAL_VALID_WINDOW; static const double AD9361_RECOMMENDED_MAX_BANDWIDTH; static const double DEFAULT_RX_FREQ; diff --git a/host/lib/usrp/common/ad936x_manager.cpp b/host/lib/usrp/common/ad936x_manager.cpp index de8c4c7ab..e7af411fa 100644 --- a/host/lib/usrp/common/ad936x_manager.cpp +++ b/host/lib/usrp/common/ad936x_manager.cpp @@ -93,14 +93,12 @@ class ad936x_manager_impl : public ad936x_manager // worst case conditions to stress the interface. // void loopback_self_test( - wb_iface::sptr iface, - wb_iface::wb_addr_type codec_idle_addr, - wb_iface::wb_addr_type codec_readback_addr + boost::function<void(uint32_t)> poker_functor, + boost::function<uint64_t()> peeker_functor ) { // Put AD936x in loopback mode _codec_ctrl->data_port_loopback(true); UHD_MSG(status) << "Performing CODEC loopback test... " << std::flush; - UHD_ASSERT_THROW(bool(iface)); size_t hash = size_t(time(NULL)); // Allow some time for AD936x to enter loopback mode. @@ -118,10 +116,10 @@ class ad936x_manager_impl : public ad936x_manager const boost::uint32_t word32 = boost::uint32_t(hash) & 0xfff0fff0; // Write test word to codec_idle idle register (on TX side) - iface->poke32(codec_idle_addr, word32); + poker_functor(word32); // Read back values - TX is lower 32-bits and RX is upper 32-bits - const boost::uint64_t rb_word64 = iface->peek64(codec_readback_addr); + const boost::uint64_t rb_word64 = peeker_functor(); const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32); const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff); @@ -136,7 +134,7 @@ class ad936x_manager_impl : public ad936x_manager UHD_MSG(status) << "pass" << std::endl; // Zero out the idle data. - iface->poke32(codec_idle_addr, 0); + poker_functor(0); // Take AD936x out of loopback mode _codec_ctrl->data_port_loopback(false); @@ -211,11 +209,11 @@ class ad936x_manager_impl : public ad936x_manager // Sensors subtree->create<sensor_value_t>("sensors/temp") - .publish(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl)) + .set_publisher(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl)) ; if (dir == RX_DIRECTION) { subtree->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key)) + .set_publisher(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key)) ; } @@ -226,7 +224,7 @@ class ad936x_manager_impl : public ad936x_manager .set(ad9361_ctrl::get_gain_range(key)); subtree->create<double>(uhd::fs_path("gains") / name / "value") .set(ad936x_manager::DEFAULT_GAIN) - .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) + .set_coercer(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) ; } @@ -238,19 +236,19 @@ class ad936x_manager_impl : public ad936x_manager // Analog Bandwidths subtree->create<double>("bandwidth/value") .set(ad936x_manager::DEFAULT_BANDWIDTH) - .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) + .set_coercer(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) ; subtree->create<meta_range_t>("bandwidth/range") - .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)) + .set_publisher(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)) ; // LO Tuning subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range)) + .set_publisher(boost::bind(&ad9361_ctrl::get_rf_freq_range)) ; subtree->create<double>("freq/value") - .publish(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key)) - .coerce(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) + .set_publisher(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key)) + .set_coercer(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) ; // Frontend corrections @@ -258,21 +256,21 @@ class ad936x_manager_impl : public ad936x_manager { subtree->create<bool>("dc_offset/enable" ) .set(ad936x_manager::DEFAULT_AUTO_DC_OFFSET) - .subscribe(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1)) ; subtree->create<bool>("iq_balance/enable" ) .set(ad936x_manager::DEFAULT_AUTO_IQ_BALANCE) - .subscribe(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1)) ; // AGC setup const std::list<std::string> mode_strings = boost::assign::list_of("slow")("fast"); subtree->create<bool>("gain/agc/enable") .set(DEFAULT_AGC_ENABLE) - .subscribe(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1)) ; subtree->create<std::string>("gain/agc/mode/value") - .subscribe(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front()) + .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front()) ; subtree->create< std::list<std::string> >("gain/agc/mode/options") .set(mode_strings) @@ -282,8 +280,8 @@ class ad936x_manager_impl : public ad936x_manager // Frontend filters BOOST_FOREACH(const std::string &filter_name, _codec_ctrl->get_filter_names(key)) { subtree->create<filter_info_base::sptr>(uhd::fs_path("filters") / filter_name / "value" ) - .publish(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name)) - .subscribe(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1)); + .set_publisher(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1)); } } diff --git a/host/lib/usrp/common/ad936x_manager.hpp b/host/lib/usrp/common/ad936x_manager.hpp index 9b4a351c6..c456715e3 100644 --- a/host/lib/usrp/common/ad936x_manager.hpp +++ b/host/lib/usrp/common/ad936x_manager.hpp @@ -80,9 +80,8 @@ public: * \throws a uhd::runtime_error if the loopback value didn't match. */ virtual void loopback_self_test( - wb_iface::sptr iface, - wb_iface::wb_addr_type codec_idle_addr, - wb_iface::wb_addr_type codec_readback_addr + boost::function<void(uint32_t)> poker_functor, + boost::function<uint64_t()> peeker_functor ) = 0; /*! Determine a tick rate that will work with a given sampling rate diff --git a/host/lib/usrp/common/adf435x.cpp b/host/lib/usrp/common/adf435x.cpp new file mode 100644 index 000000000..f1ba6ad05 --- /dev/null +++ b/host/lib/usrp/common/adf435x.cpp @@ -0,0 +1,34 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "adf435x.hpp" + +using namespace uhd; + +adf435x_iface::~adf435x_iface() +{ +} + +adf435x_iface::sptr adf435x_iface::make_adf4350(write_fn_t write) +{ + return sptr(new adf435x_impl<adf4350_regs_t>(write)); +} + +adf435x_iface::sptr adf435x_iface::make_adf4351(write_fn_t write) +{ + return sptr(new adf435x_impl<adf4351_regs_t>(write)); +} diff --git a/host/lib/usrp/common/adf435x.hpp b/host/lib/usrp/common/adf435x.hpp new file mode 100644 index 000000000..d08c6b9dd --- /dev/null +++ b/host/lib/usrp/common/adf435x.hpp @@ -0,0 +1,364 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_ADF435X_HPP +#define INCLUDED_ADF435X_HPP + +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/log.hpp> +#include <boost/function.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <vector> +#include "adf4350_regs.hpp" +#include "adf4351_regs.hpp" + +class adf435x_iface +{ +public: + typedef boost::shared_ptr<adf435x_iface> sptr; + typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn_t; + + static sptr make_adf4350(write_fn_t write); + static sptr make_adf4351(write_fn_t write); + + virtual ~adf435x_iface() = 0; + + enum output_t { RF_OUTPUT_A, RF_OUTPUT_B }; + + enum prescaler_t { PRESCALER_4_5, PRESCALER_8_9 }; + + enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED }; + + enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM }; + + enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD }; + + virtual void set_reference_freq(double fref) = 0; + + virtual void set_prescaler(prescaler_t prescaler) = 0; + + virtual void set_feedback_select(feedback_sel_t fb_sel) = 0; + + virtual void set_output_power(output_power_t power) = 0; + + virtual void set_output_enable(output_t output, bool enable) = 0; + + virtual void set_muxout_mode(muxout_t mode) = 0; + + virtual uhd::range_t get_int_range() = 0; + + virtual double set_frequency(double target_freq, bool int_n_mode, bool flush = false) = 0; + + virtual void commit(void) = 0; +}; + +template <typename adf435x_regs_t> +class adf435x_impl : public adf435x_iface +{ +public: + adf435x_impl(write_fn_t write_fn) : + _write_fn(write_fn), + _regs(), + _fb_after_divider(false), + _reference_freq(0.0), + _N_min(-1) + {} + + virtual ~adf435x_impl() {}; + + void set_reference_freq(double fref) + { + _reference_freq = fref; + } + + void set_feedback_select(feedback_sel_t fb_sel) + { + _fb_after_divider = (fb_sel == FB_SEL_DIVIDED); + } + + void set_prescaler(prescaler_t prescaler) + { + if (prescaler == PRESCALER_8_9) { + _regs.prescaler = adf435x_regs_t::PRESCALER_8_9; + _N_min = 75; + } else { + _regs.prescaler = adf435x_regs_t::PRESCALER_4_5; + _N_min = 23; + } + } + + void set_output_power(output_power_t power) + { + switch (power) { + case OUTPUT_POWER_M4DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M4DBM; break; + case OUTPUT_POWER_M1DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M1DBM; break; + case OUTPUT_POWER_2DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_2DBM; break; + case OUTPUT_POWER_5DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_5DBM; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + void set_output_enable(output_t output, bool enable) + { + switch (output) { + case RF_OUTPUT_A: _regs.rf_output_enable = enable ? adf435x_regs_t::RF_OUTPUT_ENABLE_ENABLED: + adf435x_regs_t::RF_OUTPUT_ENABLE_DISABLED; + break; + case RF_OUTPUT_B: _regs.aux_output_enable = enable ? adf435x_regs_t::AUX_OUTPUT_ENABLE_ENABLED: + adf435x_regs_t::AUX_OUTPUT_ENABLE_DISABLED; + break; + } + } + + void set_muxout_mode(muxout_t mode) + { + switch (mode) { + case MUXOUT_3STATE: _regs.muxout = adf435x_regs_t::MUXOUT_3STATE; break; + case MUXOUT_DVDD: _regs.muxout = adf435x_regs_t::MUXOUT_DVDD; break; + case MUXOUT_DGND: _regs.muxout = adf435x_regs_t::MUXOUT_DGND; break; + case MUXOUT_RDIV: _regs.muxout = adf435x_regs_t::MUXOUT_RDIV; break; + case MUXOUT_NDIV: _regs.muxout = adf435x_regs_t::MUXOUT_NDIV; break; + case MUXOUT_ALD: _regs.muxout = adf435x_regs_t::MUXOUT_ANALOG_LD; break; + case MUXOUT_DLD: _regs.muxout = adf435x_regs_t::MUXOUT_DLD; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + uhd::range_t get_int_range() + { + if (_N_min < 0) throw uhd::runtime_error("set_prescaler must be called before get_int_range"); + return uhd::range_t(_N_min, 4095); + } + + double set_frequency(double target_freq, bool int_n_mode, bool flush = false) + { + static const double REF_DOUBLER_THRESH_FREQ = 12.5e6; + static const double PFD_FREQ_MAX = 25.0e6; + static const double BAND_SEL_FREQ_MAX = 100e3; + static const double VCO_FREQ_MIN = 2.2e9; + static const double VCO_FREQ_MAX = 4.4e9; + + //Default invalid value for actual_freq + double actual_freq = 0; + + uhd::range_t rf_divider_range = _get_rfdiv_range(); + uhd::range_t int_range = get_int_range(); + + double pfd_freq = 0; + boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0; + boost::uint16_t RFdiv = static_cast<boost::uint16_t>(rf_divider_range.start()); + bool D = false, T = false; + + //Reference doubler for 50% duty cycle + D = (_reference_freq <= REF_DOUBLER_THRESH_FREQ); + + //increase RF divider until acceptable VCO frequency + double vco_freq = target_freq; + while (vco_freq < VCO_FREQ_MIN && RFdiv < static_cast<boost::uint16_t>(rf_divider_range.stop())) { + vco_freq *= 2; + RFdiv *= 2; + } + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exits when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * from pg.21 + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_actual = f_vco/RFdiv) + */ + double feedback_freq = _fb_after_divider ? target_freq : vco_freq; + + for(R = 1; R <= 1023; R+=1){ + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = _reference_freq*(D?2:1)/(R*(T?2:1)); + + //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) + if (pfd_freq > PFD_FREQ_MAX) continue; + + //First, ignore fractional part of tuning + N = boost::uint16_t(std::floor(feedback_freq/pfd_freq)); + + //keep N > minimum int divider requirement + if (N < static_cast<boost::uint16_t>(int_range.start())) continue; + + for(BS=1; BS <= 255; BS+=1){ + //keep the band select frequency at or below band_sel_freq_max + //constraint on band select clock + if (pfd_freq/BS > BAND_SEL_FREQ_MAX) continue; + goto done_loop; + } + } done_loop: + + //Fractional-N calculation + MOD = 4095; //max fractional accuracy + FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD)); + if (int_n_mode) { + if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target + N++; + } + FRAC = 0; + } + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0) { + T = true; + R /= 2; + } + + //Typical phase resync time documented in data sheet pg.24 + static const double PHASE_RESYNC_TIME = 400e-6; + + //If feedback after divider, then compensation for the divider is pulled into the INT value + int rf_div_compensation = _fb_after_divider ? 1 : RFdiv; + + //Compute the actual frequency in terms of _reference_freq, N, FRAC, MOD, D, R and T. + actual_freq = ( + double((N + (double(FRAC)/double(MOD))) * + (_reference_freq*(D?2:1)/(R*(T?2:1)))) + ) / rf_div_compensation; + + _regs.frac_12_bit = FRAC; + _regs.int_16_bit = N; + _regs.mod_12_bit = MOD; + _regs.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD))); + _regs.feedback_select = _fb_after_divider ? + adf435x_regs_t::FEEDBACK_SELECT_DIVIDED : + adf435x_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + _regs.clock_div_mode = _fb_after_divider ? + adf435x_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : + adf435x_regs_t::CLOCK_DIV_MODE_FAST_LOCK; + _regs.r_counter_10_bit = R; + _regs.reference_divide_by_2 = T ? + adf435x_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : + adf435x_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = D ? + adf435x_regs_t::REFERENCE_DOUBLER_ENABLED : + adf435x_regs_t::REFERENCE_DOUBLER_DISABLED; + _regs.band_select_clock_div = boost::uint8_t(BS); + _regs.rf_divider_select = static_cast<typename adf435x_regs_t::rf_divider_select_t>(_get_rfdiv_setting(RFdiv)); + _regs.ldf = int_n_mode ? + adf435x_regs_t::LDF_INT_N : + adf435x_regs_t::LDF_FRAC_N; + + std::string tuning_str = (int_n_mode) ? "Integer-N" : "Fractional"; + UHD_LOGV(often) + << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f" + ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl + << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f" + ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (_reference_freq/1e6) << std::endl + << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl + << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl; + + UHD_ASSERT_THROW((_regs.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); + + UHD_ASSERT_THROW(vco_freq >= VCO_FREQ_MIN and vco_freq <= VCO_FREQ_MAX); + UHD_ASSERT_THROW(RFdiv >= static_cast<boost::uint16_t>(rf_divider_range.start())); + UHD_ASSERT_THROW(RFdiv <= static_cast<boost::uint16_t>(rf_divider_range.stop())); + UHD_ASSERT_THROW(_regs.int_16_bit >= static_cast<boost::uint16_t>(int_range.start())); + UHD_ASSERT_THROW(_regs.int_16_bit <= static_cast<boost::uint16_t>(int_range.stop())); + + if (flush) commit(); + return actual_freq; + } + + void commit() + { + //reset counters + _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_ENABLED; + std::vector<boost::uint32_t> regs; + regs.push_back(_regs.get_reg(boost::uint32_t(2))); + _write_fn(regs); + _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_DISABLED; + + //write the registers + //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) + regs.clear(); + for (int addr = 5; addr >= 0; addr--) { + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } + _write_fn(regs); + } + +protected: + uhd::range_t _get_rfdiv_range(); + int _get_rfdiv_setting(boost::uint16_t div); + + write_fn_t _write_fn; + adf435x_regs_t _regs; + double _fb_after_divider; + double _reference_freq; + int _N_min; +}; + +template <> +inline uhd::range_t adf435x_impl<adf4350_regs_t>::_get_rfdiv_range() +{ + return uhd::range_t(1, 16); +} + +template <> +inline uhd::range_t adf435x_impl<adf4351_regs_t>::_get_rfdiv_range() +{ + return uhd::range_t(1, 64); +} + +template <> +inline int adf435x_impl<adf4350_regs_t>::_get_rfdiv_setting(boost::uint16_t div) +{ + switch (div) { + case 1: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV1); + case 2: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV2); + case 4: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV4); + case 8: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV8); + case 16: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV16); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <> +inline int adf435x_impl<adf4351_regs_t>::_get_rfdiv_setting(boost::uint16_t div) +{ + switch (div) { + case 1: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV1); + case 2: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV2); + case 4: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV4); + case 8: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV8); + case 16: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV16); + case 32: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV32); + case 64: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV64); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +#endif // INCLUDED_ADF435X_HPP diff --git a/host/lib/usrp/common/adf435x_common.cpp b/host/lib/usrp/common/adf435x_common.cpp deleted file mode 100644 index 474a1c932..000000000 --- a/host/lib/usrp/common/adf435x_common.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// -// Copyright 2013-2014 Ettus Research LLC -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. -// - -#include "adf435x_common.hpp" - -#include <boost/math/special_functions/round.hpp> -#include <uhd/types/tune_request.hpp> -#include <uhd/utils/log.hpp> -#include <cmath> - - -using namespace uhd; - -/*********************************************************************** - * ADF 4350/4351 Tuning Utility - **********************************************************************/ -adf435x_tuning_settings tune_adf435x_synth( - const double target_freq, - const double ref_freq, - const adf435x_tuning_constraints& constraints, - double& actual_freq) -{ - //Default invalid value for actual_freq - actual_freq = 0; - - double pfd_freq = 0; - boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0; - boost::uint16_t RFdiv = static_cast<boost::uint16_t>(constraints.rf_divider_range.start()); - bool D = false, T = false; - - //Reference doubler for 50% duty cycle - //If ref_freq < 12.5MHz enable the reference doubler - D = (ref_freq <= constraints.ref_doubler_threshold); - - static const double MIN_VCO_FREQ = 2.2e9; - static const double MAX_VCO_FREQ = 4.4e9; - - //increase RF divider until acceptable VCO frequency - double vco_freq = target_freq; - while (vco_freq < MIN_VCO_FREQ && RFdiv < static_cast<boost::uint16_t>(constraints.rf_divider_range.stop())) { - vco_freq *= 2; - RFdiv *= 2; - } - - /* - * The goal here is to loop though possible R dividers, - * band select clock dividers, N (int) dividers, and FRAC - * (frac) dividers. - * - * Calculate the N and F dividers for each set of values. - * The loop exits when it meets all of the constraints. - * The resulting loop values are loaded into the registers. - * - * from pg.21 - * - * f_pfd = f_ref*(1+D)/(R*(1+T)) - * f_vco = (N + (FRAC/MOD))*f_pfd - * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD - * f_actual = f_vco/RFdiv) - */ - double feedback_freq = constraints.feedback_after_divider ? target_freq : vco_freq; - - for(R = 1; R <= 1023; R+=1){ - //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) - pfd_freq = ref_freq*(D?2:1)/(R*(T?2:1)); - - //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) - if (pfd_freq > constraints.pfd_freq_max) continue; - - //First, ignore fractional part of tuning - N = boost::uint16_t(std::floor(feedback_freq/pfd_freq)); - - //keep N > minimum int divider requirement - if (N < static_cast<boost::uint16_t>(constraints.int_range.start())) continue; - - for(BS=1; BS <= 255; BS+=1){ - //keep the band select frequency at or below band_sel_freq_max - //constraint on band select clock - if (pfd_freq/BS > constraints.band_sel_freq_max) continue; - goto done_loop; - } - } done_loop: - - //Fractional-N calculation - MOD = 4095; //max fractional accuracy - FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD)); - if (constraints.force_frac0) { - if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target - N++; - } - FRAC = 0; - } - - //Reference divide-by-2 for 50% duty cycle - // if R even, move one divide by 2 to to regs.reference_divide_by_2 - if(R % 2 == 0) { - T = true; - R /= 2; - } - - //Typical phase resync time documented in data sheet pg.24 - static const double PHASE_RESYNC_TIME = 400e-6; - - //If feedback after divider, then compensation for the divider is pulled into the INT value - int rf_div_compensation = constraints.feedback_after_divider ? 1 : RFdiv; - - //Compute the actual frequency in terms of ref_freq, N, FRAC, MOD, D, R and T. - actual_freq = ( - double((N + (double(FRAC)/double(MOD))) * - (ref_freq*(D?2:1)/(R*(T?2:1)))) - ) / rf_div_compensation; - - //load the settings - adf435x_tuning_settings settings; - settings.frac_12_bit = FRAC; - settings.int_16_bit = N; - settings.mod_12_bit = MOD; - settings.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD))); - settings.r_counter_10_bit = R; - settings.r_divide_by_2_en = T; - settings.r_doubler_en = D; - settings.band_select_clock_div = boost::uint8_t(BS); - settings.rf_divider = RFdiv; - - std::string tuning_str = (constraints.force_frac0) ? "Integer-N" : "Fractional"; - UHD_LOGV(often) - << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f" - ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl - << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f" - ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (ref_freq/1e6) << std::endl - << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl - << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d" - ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl; - - UHD_ASSERT_THROW((settings.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); - - UHD_ASSERT_THROW(vco_freq >= MIN_VCO_FREQ and vco_freq <= MAX_VCO_FREQ); - UHD_ASSERT_THROW(settings.rf_divider >= static_cast<boost::uint16_t>(constraints.rf_divider_range.start())); - UHD_ASSERT_THROW(settings.rf_divider <= static_cast<boost::uint16_t>(constraints.rf_divider_range.stop())); - UHD_ASSERT_THROW(settings.int_16_bit >= static_cast<boost::uint16_t>(constraints.int_range.start())); - UHD_ASSERT_THROW(settings.int_16_bit <= static_cast<boost::uint16_t>(constraints.int_range.stop())); - - return settings; -} diff --git a/host/lib/usrp/common/adf435x_common.hpp b/host/lib/usrp/common/adf435x_common.hpp deleted file mode 100644 index 617b9d97f..000000000 --- a/host/lib/usrp/common/adf435x_common.hpp +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright 2014 Ettus Research LLC -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. -// - -#ifndef INCLUDED_ADF435X_COMMON_HPP -#define INCLUDED_ADF435X_COMMON_HPP - -#include <boost/cstdint.hpp> -#include <uhd/property_tree.hpp> -#include <uhd/types/ranges.hpp> - -//Common IO Pins -#define ADF435X_CE (1 << 3) -#define ADF435X_PDBRF (1 << 2) -#define ADF435X_MUXOUT (1 << 1) // INPUT!!! -#define LOCKDET_MASK (1 << 0) // INPUT!!! - -#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control -#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control - -struct adf435x_tuning_constraints { - bool force_frac0; - bool feedback_after_divider; - double ref_doubler_threshold; - double pfd_freq_max; - double band_sel_freq_max; - uhd::range_t rf_divider_range; - uhd::range_t int_range; -}; - -struct adf435x_tuning_settings { - boost::uint16_t frac_12_bit; - boost::uint16_t int_16_bit; - boost::uint16_t mod_12_bit; - boost::uint16_t r_counter_10_bit; - bool r_doubler_en; - bool r_divide_by_2_en; - boost::uint16_t clock_divider_12_bit; - boost::uint8_t band_select_clock_div; - boost::uint16_t rf_divider; -}; - -adf435x_tuning_settings tune_adf435x_synth( - const double target_freq, - const double ref_freq, - const adf435x_tuning_constraints& constraints, - double& actual_freq -); - -#endif /* INCLUDED_ADF435X_COMMON_HPP */ diff --git a/host/lib/usrp/common/adf5355.cpp b/host/lib/usrp/common/adf5355.cpp new file mode 100644 index 000000000..bb0906724 --- /dev/null +++ b/host/lib/usrp/common/adf5355.cpp @@ -0,0 +1,376 @@ +// +// Copyright 2013-2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "adf5355.hpp" +#include "adf5355_regs.hpp" +#include <uhd/utils/math.hpp> +#include <boost/math/common_factor_rt.hpp> //gcd +#include <boost/thread.hpp> + +using namespace uhd; + +template<typename data_t> +data_t clamp(data_t val, data_t min, data_t max) { + return (val < min) ? min : ((val > max) ? max : val); +} + +template<typename data_t> +double todbl(data_t val) { + return static_cast<double>(val); +} + +static const double ADF5355_DOUBLER_MAX_REF_FREQ = 60e6; +static const double ADF5355_MAX_FREQ_PFD = 125e6; +static const double ADF5355_PRESCALER_THRESH = 7e9; + +static const double ADF5355_MIN_VCO_FREQ = 3.4e9; +static const double ADF5355_MAX_VCO_FREQ = 6.8e9; +static const double ADF5355_MAX_OUT_FREQ = 6.8e9; +static const double ADF5355_MIN_OUT_FREQ = (3.4e9 / 64); +static const double ADF5355_MAX_OUTB_FREQ = (6.8e9 * 2); +static const double ADF5355_MIN_OUTB_FREQ = (3.4e9 * 2); + +static const double ADF5355_PHASE_RESYNC_TIME = 400e-6; + +static const boost::uint32_t ADF5355_MOD1 = 16777216; +static const boost::uint32_t ADF5355_MAX_MOD2 = 16384; +static const boost::uint16_t ADF5355_MIN_INT_PRESCALER_89 = 75; + +class adf5355_impl : public adf5355_iface +{ +public: + adf5355_impl(write_fn_t write_fn) : + _write_fn(write_fn), + _regs(), + _rewrite_regs(true), + _wait_time_us(0), + _ref_freq(0.0), + _pfd_freq(0.0), + _fb_after_divider(false) + { + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED; + _regs.cp_three_state = adf5355_regs_t::CP_THREE_STATE_DISABLED; + _regs.power_down = adf5355_regs_t::POWER_DOWN_DISABLED; + _regs.pd_polarity = adf5355_regs_t::PD_POLARITY_POSITIVE; + _regs.mux_logic = adf5355_regs_t::MUX_LOGIC_3_3V; + _regs.ref_mode = adf5355_regs_t::REF_MODE_SINGLE; + _regs.muxout = adf5355_regs_t::MUXOUT_DLD; + _regs.double_buff_div = adf5355_regs_t::DOUBLE_BUFF_DIV_DISABLED; + + _regs.rf_out_a_enabled = adf5355_regs_t::RF_OUT_A_ENABLED_ENABLED; + _regs.rf_out_b_enabled = adf5355_regs_t::RF_OUT_B_ENABLED_DISABLED; + _regs.mute_till_lock_detect = adf5355_regs_t::MUTE_TILL_LOCK_DETECT_MUTE_DISABLED; + _regs.ld_mode = adf5355_regs_t::LD_MODE_FRAC_N; + _regs.frac_n_ld_precision = adf5355_regs_t::FRAC_N_LD_PRECISION_5NS; + _regs.ld_cyc_count = adf5355_regs_t::LD_CYC_COUNT_1024; + _regs.le_sync = adf5355_regs_t::LE_SYNC_LE_SYNCED_TO_REFIN; + _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED; + _regs.reference_divide_by_2 = adf5355_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = adf5355_regs_t::REFERENCE_DOUBLER_DISABLED; + + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED; + _regs.prescaler = adf5355_regs_t::PRESCALER_4_5; + _regs.charge_pump_current = adf5355_regs_t::CHARGE_PUMP_CURRENT_0_94MA; + + _regs.gated_bleed = adf5355_regs_t::GATED_BLEED_DISABLED; + _regs.negative_bleed = adf5355_regs_t::NEGATIVE_BLEED_ENABLED; + _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + _regs.output_power = adf5355_regs_t::OUTPUT_POWER_5DBM; + _regs.cp_bleed_current = 2; + _regs.r_counter_10_bit = 8; + + + _regs.ld_cyc_count = adf5355_regs_t::LD_CYC_COUNT_1024; + _regs.loss_of_lock_mode = adf5355_regs_t::LOSS_OF_LOCK_MODE_DISABLED; + _regs.frac_n_ld_precision = adf5355_regs_t::FRAC_N_LD_PRECISION_5NS; + _regs.ld_mode = adf5355_regs_t::LD_MODE_FRAC_N; + + _regs.vco_band_div = 3; + _regs.timeout = 11; + _regs.auto_level_timeout = 30; + _regs.synth_lock_timeout = 12; + + _regs.adc_clock_divider = 16; + _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED; + _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED; + + _regs.phase_resync_clk_div = 0; + } + + ~adf5355_impl() + { + _regs.power_down = adf5355_regs_t::POWER_DOWN_ENABLED; + commit(); + } + + void set_feedback_select(feedback_sel_t fb_sel) + { + _fb_after_divider = (fb_sel == FB_SEL_DIVIDED); + + if (_fb_after_divider) { + _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_DIVIDED; + } else { + _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + } + } + + void set_reference_freq(double fref, bool force = false) + { + //Skip the body if the reference frequency does not change + if (uhd::math::frequencies_are_equal(fref, _ref_freq) and (not force)) + return; + + _ref_freq = fref; + + //----------------------------------------------------------- + //Set reference settings + + //Reference doubler for 50% duty cycle + bool doubler_en = (_ref_freq <= ADF5355_DOUBLER_MAX_REF_FREQ); + + /* Calculate and maximize PFD frequency */ + // TODO Target PFD should be configurable + /* TwinRX requires PFD of 6.25 MHz or less */ + const double TWINRX_PFD_FREQ = 6.25e6; + _pfd_freq = TWINRX_PFD_FREQ; + + int ref_div_factor = 16; + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + bool div2_en = (ref_div_factor % 2 == 0); + if (div2_en) { + ref_div_factor /= 2; + } + + _regs.reference_divide_by_2 = div2_en ? + adf5355_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : + adf5355_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = doubler_en ? + adf5355_regs_t::REFERENCE_DOUBLER_ENABLED : + adf5355_regs_t::REFERENCE_DOUBLER_DISABLED; + _regs.r_counter_10_bit = ref_div_factor; + UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); + + //----------------------------------------------------------- + //Set timeouts (code from ADI driver) + _regs.timeout = clamp<boost::uint16_t>( + static_cast<boost::uint16_t>(ceil(_pfd_freq / (20e3 * 30))), 1, 1023); + UHD_ASSERT_THROW((_regs.timeout & ((boost::uint16_t)~0x3FF)) == 0); + _regs.synth_lock_timeout = + static_cast<boost::uint8_t>(ceil((_pfd_freq * 2) / (100e3 * _regs.timeout))); + UHD_ASSERT_THROW((_regs.synth_lock_timeout & ((boost::uint16_t)~0x1F)) == 0); + _regs.auto_level_timeout = + static_cast<boost::uint8_t>(ceil((_pfd_freq * 5) / (100e3 * _regs.timeout))); + + //----------------------------------------------------------- + //Set VCO band divider + _regs.vco_band_div = + static_cast<boost::uint8_t>(ceil(_pfd_freq / 2.4e6)); + + //----------------------------------------------------------- + //Set ADC delay (code from ADI driver) + _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED; + _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED; + _regs.adc_clock_divider = clamp<boost::uint8_t>( + static_cast<boost::uint8_t>(ceil(((_pfd_freq / 100e3) - 2) / 4)), 1, 255); + _wait_time_us = static_cast<boost::uint32_t>( + ceil(16e6 / (_pfd_freq / ((4 * _regs.adc_clock_divider) + 2)))); + + //----------------------------------------------------------- + //Phase resync + _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED; // Disabled during development + _regs.phase_adjust = adf5355_regs_t::PHASE_ADJUST_DISABLED; + _regs.sd_load_reset = adf5355_regs_t::SD_LOAD_RESET_ON_REG0_UPDATE; + _regs.phase_resync_clk_div = static_cast<boost::uint16_t>( + floor(ADF5355_PHASE_RESYNC_TIME * _pfd_freq)); + + _rewrite_regs = true; + } + + void set_output_power(output_power_t power) + { + adf5355_regs_t::output_power_t setting; + switch (power) { + case OUTPUT_POWER_M4DBM: setting = adf5355_regs_t::OUTPUT_POWER_M4DBM; break; + case OUTPUT_POWER_M1DBM: setting = adf5355_regs_t::OUTPUT_POWER_M1DBM; break; + case OUTPUT_POWER_2DBM: setting = adf5355_regs_t::OUTPUT_POWER_2DBM; break; + case OUTPUT_POWER_5DBM: setting = adf5355_regs_t::OUTPUT_POWER_5DBM; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + if (_regs.output_power != setting) _rewrite_regs = true; + _regs.output_power = setting; + } + + void set_output_enable(output_t output, bool enable) { + + switch (output) { + case RF_OUTPUT_A: _regs.rf_out_a_enabled = enable ? adf5355_regs_t::RF_OUT_A_ENABLED_ENABLED : + adf5355_regs_t::RF_OUT_A_ENABLED_DISABLED; + break; + case RF_OUTPUT_B: _regs.rf_out_b_enabled = enable ? adf5355_regs_t::RF_OUT_B_ENABLED_ENABLED : + adf5355_regs_t::RF_OUT_B_ENABLED_DISABLED; + break; + } + } + + void set_muxout_mode(muxout_t mode) + { + switch (mode) { + case MUXOUT_3STATE: _regs.muxout = adf5355_regs_t::MUXOUT_3STATE; break; + case MUXOUT_DVDD: _regs.muxout = adf5355_regs_t::MUXOUT_DVDD; break; + case MUXOUT_DGND: _regs.muxout = adf5355_regs_t::MUXOUT_DGND; break; + case MUXOUT_RDIV: _regs.muxout = adf5355_regs_t::MUXOUT_RDIV; break; + case MUXOUT_NDIV: _regs.muxout = adf5355_regs_t::MUXOUT_NDIV; break; + case MUXOUT_ALD: _regs.muxout = adf5355_regs_t::MUXOUT_ANALOG_LD; break; + case MUXOUT_DLD: _regs.muxout = adf5355_regs_t::MUXOUT_DLD; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + double set_frequency(double target_freq, double freq_resolution, bool flush = false) + { + if (target_freq > ADF5355_MAX_OUT_FREQ or target_freq < ADF5355_MIN_OUT_FREQ) { + throw uhd::runtime_error("requested frequency out of range."); + } + if ((boost::uint32_t) freq_resolution == 0) { + throw uhd::runtime_error("requested resolution cannot be less than 1."); + } + + /* Calculate target VCOout frequency */ + //Increase RF divider until acceptable VCO frequency + double target_vco_freq = target_freq; + boost::uint32_t rf_divider = 1; + while (target_vco_freq < ADF5355_MIN_VCO_FREQ && rf_divider < 64) { + target_vco_freq *= 2; + rf_divider *= 2; + } + + switch (rf_divider) { + case 1: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV1; break; + case 2: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV2; break; + case 4: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV4; break; + case 8: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV8; break; + case 16: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV16; break; + case 32: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV32; break; + case 64: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV64; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + + //Compute fractional PLL params + double prescaler_input_freq = target_vco_freq; + if (_fb_after_divider) { + prescaler_input_freq /= rf_divider; + } + + double N = prescaler_input_freq / _pfd_freq; + boost::uint16_t INT = static_cast<boost::uint16_t>(floor(N)); + boost::uint32_t FRAC1 = static_cast<boost::uint32_t>(floor((N - INT) * ADF5355_MOD1)); + double residue = ADF5355_MOD1 * (N - (INT + FRAC1 / ADF5355_MOD1)); + + double gcd = boost::math::gcd(static_cast<int>(_pfd_freq), static_cast<int>(freq_resolution)); + boost::uint16_t MOD2 = static_cast<boost::uint16_t>(floor(_pfd_freq / gcd)); + + if (MOD2 > ADF5355_MAX_MOD2) { + MOD2 = ADF5355_MAX_MOD2; + } + boost::uint16_t FRAC2 = ceil(residue * MOD2); + + double coerced_vco_freq = _pfd_freq * ( + todbl(INT) + ( + (todbl(FRAC1) + + (todbl(FRAC2) / todbl(MOD2))) + / todbl(ADF5355_MOD1) + ) + ); + + double coerced_out_freq = coerced_vco_freq / rf_divider; + + /* Update registers */ + _regs.int_16_bit = INT; + _regs.frac1_24_bit = FRAC1; + _regs.frac2_14_bit = FRAC2; + _regs.mod2_14_bit = MOD2; + _regs.phase_24_bit = 0; + +/* + if (_regs.int_16_bit >= ADF5355_MIN_INT_PRESCALER_89) { + _regs.prescaler = adf5355_regs_t::PRESCALER_8_9; + } else { + _regs.prescaler = adf5355_regs_t::PRESCALER_4_5; + } + + // ADI: Tests have shown that the optimal bleed set is the following: + // 4/N < IBLEED/ICP < 10/N */ +/* + boost::uint32_t cp_curr_ua = + (static_cast<boost::uint32_t>(_regs.charge_pump_current) + 1) * 315; + _regs.cp_bleed_current = clamp<boost::uint8_t>( + ceil((todbl(400)*cp_curr_ua) / (_regs.int_16_bit*375)), 1, 255); + _regs.negative_bleed = adf5355_regs_t::NEGATIVE_BLEED_ENABLED; + _regs.gated_bleed = adf5355_regs_t::GATED_BLEED_DISABLED; +*/ + + if (flush) commit(); + return coerced_out_freq; + } + + void commit() + { + if (_rewrite_regs) { + //For a full state sync write registers in reverse order 12 - 0 + addr_vtr_t regs; + for (int addr = 12; addr >= 0; addr--) { + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } + _write_fn(regs); + _rewrite_regs = false; + + } else { + //Frequency update sequence from data sheet + static const size_t ONE_REG = 1; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(6))); + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_ENABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(2))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(1))); + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_DISABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); + boost::this_thread::sleep(boost::posix_time::microsec(_wait_time_us)); + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + } + } + +private: //Members + typedef std::vector<boost::uint32_t> addr_vtr_t; + + write_fn_t _write_fn; + adf5355_regs_t _regs; + bool _rewrite_regs; + boost::uint32_t _wait_time_us; + double _ref_freq; + double _pfd_freq; + double _fb_after_divider; +}; + +adf5355_iface::sptr adf5355_iface::make(write_fn_t write) +{ + return sptr(new adf5355_impl(write)); +} diff --git a/host/lib/usrp/common/adf5355.hpp b/host/lib/usrp/common/adf5355.hpp new file mode 100644 index 000000000..fb262cc9f --- /dev/null +++ b/host/lib/usrp/common/adf5355.hpp @@ -0,0 +1,57 @@ +// +// Copyright 2015 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_ADF5355_HPP +#define INCLUDED_ADF5355_HPP + +#include <boost/function.hpp> +#include <vector> + +class adf5355_iface +{ +public: + typedef boost::shared_ptr<adf5355_iface> sptr; + typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn_t; + + static sptr make(write_fn_t write); + + virtual ~adf5355_iface() {} + + enum output_t { RF_OUTPUT_A, RF_OUTPUT_B }; + + enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED }; + + enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM }; + + enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD }; + + virtual void set_reference_freq(double fref, bool force = false) = 0; + + virtual void set_feedback_select(feedback_sel_t fb_sel) = 0; + + virtual void set_output_power(output_power_t power) = 0; + + virtual void set_output_enable(output_t output, bool enable) = 0; + + virtual void set_muxout_mode(muxout_t mode) = 0; + + virtual double set_frequency(double target_freq, double freq_resolution, bool flush = false) = 0; + + virtual void commit(void) = 0; +}; + +#endif // INCLUDED_ADF5355_HPP diff --git a/host/lib/usrp/common/apply_corrections.cpp b/host/lib/usrp/common/apply_corrections.cpp index 3d33e7d11..272e0e093 100644 --- a/host/lib/usrp/common/apply_corrections.cpp +++ b/host/lib/usrp/common/apply_corrections.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -144,6 +144,34 @@ static void apply_fe_corrections( /*********************************************************************** * Wrapper routines with nice try/catch + print **********************************************************************/ +void uhd::usrp::apply_tx_fe_corrections( //overloading to work according to rfnoc tree struct + property_tree::sptr sub_tree, //starts at mboards/x + const uhd::fs_path db_path, + const uhd::fs_path tx_fe_corr_path, + const double lo_freq //actual lo freq +){ + boost::mutex::scoped_lock l(corrections_mutex); + try{ + apply_fe_corrections( + sub_tree, + db_path + "/tx_eeprom", + tx_fe_corr_path + "/iq_balance/value", + "tx_iq_cal_v0.2_", + lo_freq + ); + apply_fe_corrections( + sub_tree, + db_path + "/tx_eeprom", + tx_fe_corr_path + "/dc_offset/value", + "tx_dc_cal_v0.2_", + lo_freq + ); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl; + } +} + void uhd::usrp::apply_tx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x const std::string &slot, //name of dboard slot @@ -171,6 +199,27 @@ void uhd::usrp::apply_tx_fe_corrections( } } +void uhd::usrp::apply_rx_fe_corrections( //overloading to work according to rfnoc tree struct + property_tree::sptr sub_tree, //starts at mboards/x + const uhd::fs_path db_path, + const uhd::fs_path rx_fe_corr_path, + const double lo_freq //actual lo freq +){ + boost::mutex::scoped_lock l(corrections_mutex); + try{ + apply_fe_corrections( + sub_tree, + db_path + "/rx_eeprom", + rx_fe_corr_path + "/iq_balance/value", + "rx_iq_cal_v0.2_", + lo_freq + ); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl; + } +} + void uhd::usrp::apply_rx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x const std::string &slot, //name of dboard slot diff --git a/host/lib/usrp/common/apply_corrections.hpp b/host/lib/usrp/common/apply_corrections.hpp index c516862d1..0ab5377f3 100644 --- a/host/lib/usrp/common/apply_corrections.hpp +++ b/host/lib/usrp/common/apply_corrections.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011-2016 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -26,16 +26,28 @@ namespace uhd{ namespace usrp{ void apply_tx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x - const std::string &slot, //name of dboard slot + const fs_path db_path, + const fs_path tx_fe_corr_path, const double tx_lo_freq //actual lo freq ); + void apply_tx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const std::string &slot, //name of dboard slot + const double tx_lo_freq //actual lo freq + ); void apply_rx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x const std::string &slot, //name of dboard slot const double rx_lo_freq //actual lo freq ); + void apply_rx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const fs_path db_path, + const fs_path rx_fe_corr_path, + const double rx_lo_freq //actual lo freq + ); }} //namespace uhd::usrp #endif /* INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP */ diff --git a/host/lib/usrp/common/constrained_device_args.hpp b/host/lib/usrp/common/constrained_device_args.hpp new file mode 100644 index 000000000..1bfd1df00 --- /dev/null +++ b/host/lib/usrp/common/constrained_device_args.hpp @@ -0,0 +1,283 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP + +#include <uhd/types/device_addr.hpp> +#include <uhd/exception.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <vector> +#include <string> + +namespace uhd { +namespace usrp { + + /*! + * constrained_device_args_t provides a base and utilities to + * map key=value pairs passed in through the device creation + * args interface (device_addr_t). + * + * Inherit from this class to create typed device specific + * arguments and use the base class methods to handle parsing + * the device_addr or any key=value string to populate the args + * + * This file contains a library of different types of args the + * the user can pass in. The library can be extended to support + * non-intrinsic types by the client. + * + */ + class constrained_device_args_t { + public: //Types + + /*! + * Base argument type. All other arguments inherit from this. + */ + class generic_arg { + public: + generic_arg(const std::string& key): _key(key) {} + inline const std::string& key() const { return _key; } + inline virtual std::string to_string() const = 0; + private: + std::string _key; + }; + + /*! + * String argument type. Can be case sensitive or insensitive + */ + template<bool case_sensitive> + class str_arg : public generic_arg { + public: + str_arg(const std::string& name, const std::string& default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const std::string& value) { + _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value); + } + inline const std::string& get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + set(str_rep); + } + inline virtual std::string to_string() const { + return key() + "=" + get(); + } + inline bool operator==(const std::string& rhs) const { + return get() == boost::algorithm::to_lower_copy(rhs); + } + private: + std::string _value; + }; + typedef str_arg<false> str_ci_arg; + typedef str_arg<true> str_cs_arg; + + /*! + * Numeric argument type. The template type data_t allows the + * client to constrain the type of the number. + */ + template<typename data_t> + class num_arg : public generic_arg { + public: + num_arg(const std::string& name, const data_t default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const data_t value) { + _value = value; + } + inline const data_t get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = boost::lexical_cast<data_t>(str_rep); + } catch (std::exception& ex) { + throw uhd::value_error(str(boost::format( + "Error parsing numeric parameter %s: %s.") % + key() % ex.what() + )); + } + } + inline virtual std::string to_string() const { + return key() + "=" + boost::lexical_cast<std::string>(get()); + } + private: + data_t _value; + }; + + /*! + * Enumeration argument type. The template type enum_t allows the + * client to use their own enum and specify a string mapping for + * the values of the enum + * + * NOTE: The constraint on enum_t is that the values must start with + * 0 and be sequential + */ + template<typename enum_t> + class enum_arg : public generic_arg { + public: + enum_arg( + const std::string& name, + const enum_t default_value, + const std::vector<std::string>& values) : + generic_arg(name), _str_values(values) + { set(default_value); } + + inline void set(const enum_t value) { + _value = value; + } + inline const enum_t get() const { + return _value; + } + inline void parse(const std::string& str_rep, bool assert_invalid = true) { + std::string valid_values_str; + for (size_t i = 0; i < _str_values.size(); i++) { + if (boost::algorithm::to_lower_copy(str_rep) == + boost::algorithm::to_lower_copy(_str_values[i])) + { + valid_values_str += ((i==0)?"":", ") + _str_values[i]; + set(static_cast<enum_t>(static_cast<int>(i))); + return; + } + } + //If we reach here then, the string enum value was invalid + if (assert_invalid) { + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s=%s (Valid: {%s})") % + key() % str_rep % valid_values_str + )); + } + } + inline virtual std::string to_string() const { + size_t index = static_cast<size_t>(static_cast<int>(_value)); + UHD_ASSERT_THROW(index < _str_values.size()); + return key() + "=" + _str_values[index]; + } + + private: + enum_t _value; + std::vector<std::string> _str_values; + }; + + /*! + * Boolean argument type. + */ + class bool_arg : public generic_arg { + public: + bool_arg(const std::string& name, const bool default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const bool value) { + _value = value; + } + inline bool get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = (boost::lexical_cast<int>(str_rep) != 0); + } catch (std::exception& ex) { + if (str_rep.empty()) { + //If str_rep is empty then the device_addr was set + //without a value which means that the user "set" the flag + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "true" || + boost::algorithm::to_lower_copy(str_rep) == "yes" || + boost::algorithm::to_lower_copy(str_rep) == "y") { + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "false" || + boost::algorithm::to_lower_copy(str_rep) == "no" || + boost::algorithm::to_lower_copy(str_rep) == "n") { + _value = false; + } else { + throw uhd::value_error(str(boost::format( + "Error parsing boolean parameter %s: %s.") % + key() % ex.what() + )); + } + } + } + inline virtual std::string to_string() const { + return key() + "=" + (get() ? "true" : "false"); + } + private: + bool _value; + }; + + public: //Methods + constrained_device_args_t() {} + virtual ~constrained_device_args_t() {} + + void parse(const std::string& str_args) { + device_addr_t dev_args(str_args); + _parse(dev_args); + } + + void parse(const device_addr_t& dev_args) { + _parse(dev_args); + } + + inline virtual std::string to_string() const = 0; + + protected: //Methods + //Override _parse to provide an implementation to parse all + //client specific device args + virtual void _parse(const device_addr_t& dev_args) = 0; + + /*! + * Utility: Ensure that the value of the device arg is between min and max + */ + template<typename num_data_t> + static inline void _enforce_range(const num_arg<num_data_t>& arg, const num_data_t& min, const num_data_t& max) { + if (arg.get() > max || arg.get() < min) { + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Minimum: %s, Maximum: %s)") % + arg.to_string() % + boost::lexical_cast<std::string>(min) % boost::lexical_cast<std::string>(max))); + } + } + + /*! + * Utility: Ensure that the value of the device arg is is contained in valid_values + */ + template<typename arg_t, typename data_t> + static inline void _enforce_discrete(const arg_t& arg, const std::vector<data_t>& valid_values) { + bool match = false; + BOOST_FOREACH(const data_t& val, valid_values) { + if (val == arg.get()) { + match = true; + break; + } + } + if (!match) { + std::string valid_values_str; + for (size_t i = 0; i < valid_values.size(); i++) { + valid_values_str += ((i==0)?"":", ") + boost::lexical_cast<std::string>(valid_values[i]); + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Valid: {%s})") % + arg.to_string() % valid_values_str + )); + } + } + } + }; +}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP */ diff --git a/host/lib/usrp/common/fw_comm_protocol.h b/host/lib/usrp/common/fw_comm_protocol.h new file mode 100644 index 000000000..14adb33a9 --- /dev/null +++ b/host/lib/usrp/common/fw_comm_protocol.h @@ -0,0 +1,102 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_FW_COMM_PROTOCOL +#define INCLUDED_FW_COMM_PROTOCOL + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +/*! + * Structs and constants for communication between firmware and host. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +#define FW_COMM_PROTOCOL_SIGNATURE 0xACE3 +#define FW_COMM_PROTOCOL_VERSION 0 +#define FW_COMM_MAX_DATA_WORDS 16 +#define FW_COMM_PROTOCOL_MTU 256 + +#define FW_COMM_FLAGS_ACK 0x00000001 +#define FW_COMM_FLAGS_CMD_MASK 0x00000FF0 +#define FW_COMM_FLAGS_ERROR_MASK 0xFF000000 + +#define FW_COMM_CMD_ECHO 0x00000000 +#define FW_COMM_CMD_POKE32 0x00000010 +#define FW_COMM_CMD_PEEK32 0x00000020 +#define FW_COMM_CMD_BLOCK_POKE32 0x00000030 +#define FW_COMM_CMD_BLOCK_PEEK32 0x00000040 + +#define FW_COMM_ERR_PKT_ERROR 0x80000000 +#define FW_COMM_ERR_CMD_ERROR 0x40000000 +#define FW_COMM_ERR_SIZE_ERROR 0x20000000 + +#define FW_COMM_GENERATE_ID(prod) ((((uint32_t) FW_COMM_PROTOCOL_SIGNATURE) << 0) | \ + (((uint32_t) prod) << 16) | \ + (((uint32_t) FW_COMM_PROTOCOL_VERSION) << 24)) + +#define FW_COMM_GET_PROTOCOL_SIG(id) ((uint16_t)(id & 0xFFFF)) +#define FW_COMM_GET_PRODUCT_ID(id) ((uint8_t)(id >> 16)) +#define FW_COMM_GET_PROTOCOL_VER(id) ((uint8_t)(id >> 24)) + +typedef struct +{ + uint32_t id; //Protocol and device identifier + uint32_t flags; //Holds commands and ack messages + uint32_t sequence; //Sequence number (specific to FW communication transactions) + uint32_t data_words; //Number of data words in payload + uint32_t addr; //Address field for the command in flags + uint32_t data[FW_COMM_MAX_DATA_WORDS]; //Data field for the command in flags +} fw_comm_pkt_t; + +#ifdef __cplusplus +} //extern "C" +#endif + +// The following definitions are only useful in firmware. Exclude in host code. +#ifndef __cplusplus + +typedef void (*poke32_func)(const uint32_t addr, const uint32_t data); +typedef uint32_t (*peek32_func)(const uint32_t addr); + +/*! + * Process a firmware communication packet and compute a response. + * Args: + * - (in) request: Pointer to the request struct + * - (out) response: Pointer to the response struct + * - (in) product_id: The 8-bit usrp3 specific product ID (for request filtering) + * - (func) poke_callback, peek_callback: Callback functions for a single peek/poke + * - return value: Send a response packet + */ +bool process_fw_comm_protocol_pkt( + const fw_comm_pkt_t* request, + fw_comm_pkt_t* response, + uint8_t product_id, + uint32_t iface_id, + poke32_func poke_callback, + peek32_func peek_callback +); + +#endif //ifdef __cplusplus + +#endif /* INCLUDED_FW_COMM_PROTOCOL */ diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp new file mode 100644 index 000000000..ef541e37f --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp @@ -0,0 +1,246 @@ +// +// Copyright 2013 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp3_fw_ctrl_iface.hpp" + +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/foreach.hpp> +#include "fw_comm_protocol.h" + +namespace uhd { namespace usrp { namespace usrp3 { + +//---------------------------------------------------------- +// Factory method +//---------------------------------------------------------- +uhd::wb_iface::sptr usrp3_fw_ctrl_iface::make( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose) +{ + return wb_iface::sptr(new usrp3_fw_ctrl_iface(udp_xport, product_id, verbose)); +} + +//---------------------------------------------------------- +// udp_fw_ctrl_iface +//---------------------------------------------------------- + +usrp3_fw_ctrl_iface::usrp3_fw_ctrl_iface( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose) : + _product_id(product_id), _verbose(verbose), _udp_xport(udp_xport), + _seq_num(0) +{ + flush(); + peek32(0); +} + +usrp3_fw_ctrl_iface::~usrp3_fw_ctrl_iface() +{ + flush(); +} + +void usrp3_fw_ctrl_iface::flush() +{ + boost::mutex::scoped_lock lock(_mutex); + _flush(); +} + +void usrp3_fw_ctrl_iface::poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + boost::mutex::scoped_lock lock(_mutex); + + for (size_t i = 1; i <= NUM_RETRIES; i++) { + try { + _poke32(addr, data); + return; + } catch(const std::exception &ex) { + const std::string error_msg = str(boost::format( + "udp fw poke32 failure #%u\n%s") % i % ex.what()); + if (_verbose) UHD_MSG(warning) << error_msg << std::endl; + if (i == NUM_RETRIES) throw uhd::io_error(error_msg); + } + } +} + +boost::uint32_t usrp3_fw_ctrl_iface::peek32(const wb_addr_type addr) +{ + boost::mutex::scoped_lock lock(_mutex); + + for (size_t i = 1; i <= NUM_RETRIES; i++) { + try { + return _peek32(addr); + } catch(const std::exception &ex) { + const std::string error_msg = str(boost::format( + "udp fw peek32 failure #%u\n%s") % i % ex.what()); + if (_verbose) UHD_MSG(warning) << error_msg << std::endl; + if (i == NUM_RETRIES) throw uhd::io_error(error_msg); + } + } + return 0; +} + +void usrp3_fw_ctrl_iface::_poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + //Load request struct + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_POKE32); + request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); + request.addr = uhd::htonx(addr); + request.data_words = 1; + request.data[0] = uhd::htonx(data); + + //Send request + _flush(); + _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //Recv reply + fw_comm_pkt_t reply; + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("udp fw poke32 - reply timed out"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); + UHD_ASSERT_THROW(nbytes == sizeof(reply)); + UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); + UHD_ASSERT_THROW(flags & FW_COMM_CMD_POKE32); + UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); + UHD_ASSERT_THROW(reply.sequence == request.sequence); + UHD_ASSERT_THROW(reply.addr == request.addr); + UHD_ASSERT_THROW(reply.data[0] == request.data[0]); +} + +boost::uint32_t usrp3_fw_ctrl_iface::_peek32(const wb_addr_type addr) +{ + //Load request struct + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_PEEK32); + request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); + request.addr = uhd::htonx(addr); + request.data_words = 1; + request.data[0] = 0; + + //Send request + _flush(); + _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //Recv reply + fw_comm_pkt_t reply; + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("udp fw peek32 - reply timed out"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); + UHD_ASSERT_THROW(nbytes == sizeof(reply)); + UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); + UHD_ASSERT_THROW(flags & FW_COMM_CMD_PEEK32); + UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); + UHD_ASSERT_THROW(reply.sequence == request.sequence); + UHD_ASSERT_THROW(reply.addr == request.addr); + + //return result! + return uhd::ntohx<boost::uint32_t>(reply.data[0]); +} + +void usrp3_fw_ctrl_iface::_flush(void) +{ + char buff[FW_COMM_PROTOCOL_MTU] = {}; + while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) { + /*NOP*/ + } +} + +std::vector<std::string> usrp3_fw_ctrl_iface::discover_devices( + const std::string& addr_hint, const std::string& port, + boost::uint16_t product_id) +{ + std::vector<std::string> addrs; + + //Create a UDP transport to communicate: + //Some devices will cause a throw when opened for a broadcast address. + //We print and recover so the caller can loop through all bcast addrs. + uhd::transport::udp_simple::sptr udp_bcast_xport; + try { + udp_bcast_xport = uhd::transport::udp_simple::make_broadcast(addr_hint, port); + } catch(const std::exception &e) { + UHD_MSG(error) << boost::format("Cannot open UDP transport on %s for discovery\n%s") + % addr_hint % e.what() << std::endl; + return addrs; + } + + //Send dummy request + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); + request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + udp_bcast_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //loop for replies until timeout + while (true) { + char buff[FW_COMM_PROTOCOL_MTU] = {}; + const size_t nbytes = udp_bcast_xport->recv(boost::asio::buffer(buff), 0.050); + if (nbytes != sizeof(fw_comm_pkt_t)) break; //No more responses or responses are invalid + + const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; + if (request.id == reply->id && + request.flags == reply->flags && + request.sequence == reply->sequence) + { + addrs.push_back(udp_bcast_xport->get_recv_addr()); + } + } + + return addrs; +} + +boost::uint32_t usrp3_fw_ctrl_iface::get_iface_id( + const std::string& addr, const std::string& port, + boost::uint16_t product_id) +{ + uhd::transport::udp_simple::sptr udp_xport = + uhd::transport::udp_simple::make_connected(addr, port); + + //Send dummy request + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); + request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //loop for replies until timeout + char buff[FW_COMM_PROTOCOL_MTU] = {}; + const size_t nbytes = udp_xport->recv(boost::asio::buffer(buff), 1.0); + + const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; + if (nbytes > 0 && + request.id == reply->id && + request.flags == reply->flags && + request.sequence == reply->sequence) + { + return uhd::ntohx<boost::uint32_t>(reply->data[0]); + } else { + throw uhd::io_error("udp get_iface_id - bad response"); + } +} + +}}} //namespace diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp new file mode 100644 index 000000000..33286861b --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp @@ -0,0 +1,72 @@ +// +// Copyright 2014 Ettus Research LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP +#define INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/thread/mutex.hpp> +#include <vector> + +namespace uhd { namespace usrp { namespace usrp3 { + +class usrp3_fw_ctrl_iface : public uhd::wb_iface +{ +public: + usrp3_fw_ctrl_iface( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose); + virtual ~usrp3_fw_ctrl_iface(); + + // -- uhd::wb_iface -- + void poke32(const wb_addr_type addr, const boost::uint32_t data); + boost::uint32_t peek32(const wb_addr_type addr); + void flush(); + + static uhd::wb_iface::sptr make( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose = true); + // -- uhd::wb_iface -- + + static std::vector<std::string> discover_devices( + const std::string& addr_hint, const std::string& port, + boost::uint16_t product_id); + + static boost::uint32_t get_iface_id( + const std::string& addr, const std::string& port, + boost::uint16_t product_id); + +private: + void _poke32(const wb_addr_type addr, const boost::uint32_t data); + boost::uint32_t _peek32(const wb_addr_type addr); + void _flush(void); + + const boost::uint16_t _product_id; + const bool _verbose; + uhd::transport::udp_simple::sptr _udp_xport; + boost::uint32_t _seq_num; + boost::mutex _mutex; + + static const size_t NUM_RETRIES = 3; +}; + +}}} //namespace + +#endif //INCLUDED_LIBUHD_USRP_USRP3_USRP3_UDP_FW_CTRL_HPP |