diff options
Diffstat (limited to 'host/lib/usrp/common')
-rw-r--r-- | host/lib/usrp/common/CMakeLists.txt | 3 | ||||
-rw-r--r-- | host/lib/usrp/common/ad9361_ctrl.hpp | 6 | ||||
-rw-r--r-- | host/lib/usrp/common/ad9361_driver/ad9361_device.cpp | 5 | ||||
-rw-r--r-- | host/lib/usrp/common/ad9361_driver/ad9361_device.h | 1 | ||||
-rw-r--r-- | host/lib/usrp/common/ad936x_manager.cpp | 28 | ||||
-rw-r--r-- | host/lib/usrp/common/adf435x.cpp | 34 | ||||
-rw-r--r-- | host/lib/usrp/common/adf435x.hpp | 330 | ||||
-rw-r--r-- | host/lib/usrp/common/adf435x_common.cpp | 161 | ||||
-rw-r--r-- | host/lib/usrp/common/adf435x_common.hpp | 63 | ||||
-rw-r--r-- | host/lib/usrp/common/constrained_device_args.hpp | 283 | ||||
-rw-r--r-- | host/lib/usrp/common/fw_comm_protocol.h | 102 | ||||
-rw-r--r-- | host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp | 246 | ||||
-rw-r--r-- | host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp | 72 |
13 files changed, 1091 insertions, 243 deletions
diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index 9dabc4e0b..55dc92023 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -29,7 +29,7 @@ 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}/ad9361_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad936x_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp @@ -37,4 +37,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.hpp b/host/lib/usrp/common/ad9361_ctrl.hpp index 5c438ee9c..8cd75d539 100644 --- a/host/lib/usrp/common/ad9361_ctrl.hpp +++ b/host/lib/usrp/common/ad9361_ctrl.hpp @@ -89,8 +89,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..bb25379c0 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; } diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.h b/host/lib/usrp/common/ad9361_driver/ad9361_device.h index 66bc2e8b9..73b1d9a35 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.h +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.h @@ -157,6 +157,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..58893395e 100644 --- a/host/lib/usrp/common/ad936x_manager.cpp +++ b/host/lib/usrp/common/ad936x_manager.cpp @@ -211,11 +211,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 +226,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 +238,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 +258,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 +282,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/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..16557b514 --- /dev/null +++ b/host/lib/usrp/common/adf435x.hpp @@ -0,0 +1,330 @@ +// +// 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 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 }; + + 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 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(); + } + } + + 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/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 |