// // Copyright 2010-2012 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company // Copyright 2019 Ettus Research, A National Instruments Brand // // SPDX-License-Identifier: GPL-3.0-or-later // // No RX IO Pins Used // RX IO Functions // ADC/DAC functions: // DAC 1: RF AGC // DAC 2: IF AGC // min freq: 50e6 // max freq: 860e6 // gain range: [0:1dB:115dB] #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace uhd; using namespace uhd::usrp; using namespace boost::assign; /*********************************************************************** * The tvrx constants **********************************************************************/ static const freq_range_t tvrx_freq_range(50e6, 860e6); static const std::vector tvrx_antennas = list_of("RX"); static const uhd::dict tvrx_freq_ranges = map_list_of("VHFLO", freq_range_t(50e6, 158e6))("VHFHI", freq_range_t(158e6, 454e6))( "UHF", freq_range_t(454e6, 860e6)); static const boost::array vhflo_gains_db = {{-6.00000, -6.00000, -6.00000, -4.00000, 0.00000, 5.00000, 10.00000, 17.40000, 26.30000, 36.00000, 43.00000, 48.00000, 49.50000, 50.10000, 50.30000, 50.30000, 50.30000}}; static const boost::array vhfhi_gains_db = {{-13.3000, -13.3000, -13.3000, -1.0000, 7.7000, 11.0000, 14.7000, 19.3000, 26.1000, 36.0000, 42.7000, 46.0000, 47.0000, 47.8000, 48.2000, 48.2000, 48.2000}}; static const boost::array uhf_gains_db = {{-8.0000, -8.0000, -7.0000, 4.0000, 10.2000, 14.5000, 17.5000, 20.0000, 24.5000, 30.8000, 37.0000, 39.8000, 40.7000, 41.6000, 42.6000, 43.2000, 43.8000}}; static const boost::array tvrx_if_gains_db = {{-1.50000, -1.50000, -1.50000, -1.00000, 0.20000, 2.10000, 4.30000, 6.40000, 9.00000, 12.00000, 14.80000, 18.20000, 26.10000, 32.50000, 32.50000, 32.50000, 32.50000}}; // gain linearization data // this is from the datasheet and is dB vs. volts (below) // i tried to curve fit this, but it's really just so nonlinear that you'd // need dang near as many coefficients as to just map it like this and interp. // these numbers are culled from the 4937DI5 datasheet and are probably totally inaccurate // but if it's better than the old linear fit i'm happy static const uhd::dict> tvrx_rf_gains_db = map_list_of("VHFLO", vhflo_gains_db)("VHFHI", vhfhi_gains_db)("UHF", uhf_gains_db); // sample voltages for the above points static const boost::array tvrx_gains_volts = {{0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6, 2.8, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0}}; static uhd::dict get_tvrx_gain_ranges(void) { double rfmax = 0.0, rfmin = FLT_MAX; for (const std::string& range : tvrx_rf_gains_db.keys()) { double my_max = tvrx_rf_gains_db[range].back(); // we're assuming it's monotonic double my_min = tvrx_rf_gains_db[range].front(); // if it's not this is wrong wrong wrong if (my_max > rfmax) rfmax = my_max; if (my_min < rfmin) rfmin = my_min; } double ifmin = tvrx_if_gains_db.front(); double ifmax = tvrx_if_gains_db.back(); return map_list_of("RF", gain_range_t(rfmin, rfmax, (rfmax - rfmin) / 4096.0))( "IF", gain_range_t(ifmin, ifmax, (ifmax - ifmin) / 4096.0)); } static const double opamp_gain = 1.22; // onboard DAC opamp gain static const double tvrx_if_freq = 43.75e6; // IF freq of TVRX module static const uint16_t reference_divider = 640; // clock reference divider to use static const double reference_freq = 4.0e6; /*********************************************************************** * The tvrx dboard class **********************************************************************/ class tvrx : public rx_dboard_base { public: tvrx(ctor_args_t args); ~tvrx(void) override; private: uhd::dict _gains; double _lo_freq; tuner_4937di5_regs_t _tuner_4937di5_regs; uint8_t _tuner_4937di5_addr(void) { return (this->get_iface()->get_special_props().mangle_i2c_addrs) ? 0x61 : 0x60; // ok really? we could rename that call }; double set_gain(double gain, const std::string& name); double set_freq(double freq); void update_regs(void) { byte_vector_t regs_vector(4); // get the register data for (int i = 0; i < 4; i++) { regs_vector[i] = _tuner_4937di5_regs.get_reg(i); UHD_LOGGER_TRACE("TVRX") << boost::format("tvrx: send reg 0x%02x, value 0x%04x") % int(i) % int(regs_vector[i]); } // send the data this->get_iface()->write_i2c(_tuner_4937di5_addr(), regs_vector); } }; /*********************************************************************** * Register the tvrx dboard **********************************************************************/ static dboard_base::sptr make_tvrx(dboard_base::ctor_args_t args) { return dboard_base::sptr(new tvrx(args)); } UHD_STATIC_BLOCK(reg_tvrx_dboard) { // register the factory function for the rx dbid dboard_manager::register_dboard(0x0040, &make_tvrx, "TVRX"); } /*********************************************************************** * Structors **********************************************************************/ tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args) { //////////////////////////////////////////////////////////////////// // Register properties //////////////////////////////////////////////////////////////////// this->get_rx_subtree()->create("name").set("TVRX"); this->get_rx_subtree()->create("sensors"); // phony property so this dir exists for (const std::string& name : get_tvrx_gain_ranges().keys()) { this->get_rx_subtree() ->create("gains/" + name + "/value") .set_coercer(std::bind(&tvrx::set_gain, this, std::placeholders::_1, name)); this->get_rx_subtree() ->create("gains/" + name + "/range") .set(get_tvrx_gain_ranges()[name]); } this->get_rx_subtree() ->create("freq/value") .set_coercer(std::bind(&tvrx::set_freq, this, std::placeholders::_1)); this->get_rx_subtree()->create("freq/range").set(tvrx_freq_range); this->get_rx_subtree()->create("antenna/value").set(tvrx_antennas.at(0)); this->get_rx_subtree() ->create>("antenna/options") .set(tvrx_antennas); this->get_rx_subtree()->create("connection").set("I"); this->get_rx_subtree()->create("enabled").set(true); // always enabled this->get_rx_subtree()->create("use_lo_offset").set(false); this->get_rx_subtree()->create("bandwidth/value").set(6.0e6); this->get_rx_subtree() ->create("bandwidth/range") .set(freq_range_t(6.0e6, 6.0e6)); // enable only the clocks we need this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); // set the gpio directions and atr controls (identically) this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr if (this->get_iface()->get_special_props().soft_clock_divider) { this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x1); // GPIO0 is clock } else { this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs } // send initial register settings if necessary // set default freq _lo_freq = tvrx_freq_range.start() + tvrx_if_freq; // init _lo_freq to a sane default this->get_rx_subtree()->access("freq/value").set(tvrx_freq_range.start()); // set default gains for (const std::string& name : get_tvrx_gain_ranges().keys()) { this->get_rx_subtree() ->access("gains/" + name + "/value") .set(get_tvrx_gain_ranges()[name].start()); } } tvrx::~tvrx(void) {} /*! Return a string corresponding to the relevant band * \param freq the frequency of interest * \return a string corresponding to the band */ static std::string get_band(double freq) { for (const std::string& band : tvrx_freq_ranges.keys()) { if (freq >= tvrx_freq_ranges[band].start() && freq <= tvrx_freq_ranges[band].stop()) { UHD_LOGGER_TRACE("TVRX") << "Band: " << band; return band; } } UHD_THROW_INVALID_CODE_PATH(); } /*********************************************************************** * Gain Handling **********************************************************************/ /*! * Execute a linear interpolation to find the voltage corresponding to a desired gain * \param gain the desired gain in dB * \param db_vector the vector of dB readings * \param volts_vector the corresponding vector of voltages db_vector was sampled at * \return a voltage to feed the TVRX analog gain */ static double gain_interp(double gain, const boost::array& db_vector, const boost::array& volts_vector) { double volts; gain = uhd::clip( gain, db_vector.front(), db_vector.back()); // let's not get carried away here uint8_t gain_step = 0; // find which bin we're in for (size_t i = 0; i < db_vector.size() - 1; i++) { if (gain >= db_vector[i] && gain <= db_vector[i + 1]) { gain_step = uhd::narrow_cast(i); } } // find the current slope for linear interpolation double slope = (volts_vector[gain_step + 1] - volts_vector[gain_step]) / (db_vector[gain_step + 1] - db_vector[gain_step]); // the problem here is that for gains approaching the maximum, the voltage slope // becomes infinite i.e., a small change in gain requires an infinite change in // voltage to cope, we limit the slope if (slope == std::numeric_limits::infinity()) return volts_vector[gain_step]; // use the volts per dB slope to find the final interpolated voltage volts = volts_vector[gain_step] + (slope * (gain - db_vector[gain_step])); UHD_LOGGER_TRACE("TVRX") << "Gain interp: gain: " << gain << ", gain_step: " << int(gain_step) << ", slope: " << slope << ", volts: " << volts; return volts; } /*! * Convert a requested gain for the RF gain into a DAC voltage. * The gain passed into the function will be set to the actual value. * \param gain the requested gain in dB * \return dac voltage value */ static double rf_gain_to_voltage(double gain, double lo_freq) { // clip the input gain = get_tvrx_gain_ranges()["RF"].clip(gain); // first we need to find out what band we're in, because gains are different across // different bands std::string band = get_band(lo_freq - tvrx_if_freq); // this is the voltage at the TVRX gain input double gain_volts = gain_interp(gain, tvrx_rf_gains_db[band], tvrx_gains_volts); // this is the voltage at the USRP DAC output double dac_volts = gain_volts / opamp_gain; dac_volts = uhd::clip(dac_volts, 0.0, 3.3); UHD_LOGGER_TRACE("TVRX") << boost::format("tvrx RF AGC gain: %f dB, dac_volts: %f V") % gain % dac_volts; return dac_volts; } /*! * Convert a requested gain for the IF gain into a DAC voltage. * The gain passed into the function will be set to the actual value. * \param gain the requested gain in dB * \return dac voltage value */ static double if_gain_to_voltage(double gain) { // clip the input gain = get_tvrx_gain_ranges()["IF"].clip(gain); double gain_volts = gain_interp(gain, tvrx_if_gains_db, tvrx_gains_volts); double dac_volts = gain_volts / opamp_gain; dac_volts = uhd::clip(dac_volts, 0.0, 3.3); UHD_LOGGER_TRACE("TVRX") << boost::format("tvrx IF AGC gain: %f dB, dac_volts: %f V") % gain % dac_volts; return dac_volts; } double tvrx::set_gain(double gain, const std::string& name) { assert_has(get_tvrx_gain_ranges().keys(), name, "tvrx gain name"); if (name == "RF") { this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_B, rf_gain_to_voltage(gain, _lo_freq)); } else if (name == "IF") { this->get_iface()->write_aux_dac( dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, if_gain_to_voltage(gain)); } else UHD_THROW_INVALID_CODE_PATH(); _gains[name] = gain; return gain; } /*! * Set the tuner to center the desired frequency at 43.75MHz * \param freq the requested frequency */ double tvrx::set_freq(double freq) { freq = tvrx_freq_range.clip(freq); std::string prev_band = get_band(_lo_freq - tvrx_if_freq); std::string new_band = get_band(freq); double target_lo_freq = freq + tvrx_if_freq; // the desired LO freq for high-side mixing double f_ref = reference_freq / double(reference_divider); // your tuning step size int divisor = int((target_lo_freq + (f_ref * 4.0)) / (f_ref * 8)); // the divisor we'll use double actual_lo_freq = (f_ref * 8 * divisor); // the LO freq we'll actually get if ((divisor & ~0x7fff)) UHD_THROW_INVALID_CODE_PATH(); // now we update the registers _tuner_4937di5_regs.db1 = (divisor >> 8) & 0xff; _tuner_4937di5_regs.db2 = divisor & 0xff; if (new_band == "VHFLO") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFLO; else if (new_band == "VHFHI") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFHI; else if (new_band == "UHF") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_UHF; else UHD_THROW_INVALID_CODE_PATH(); _tuner_4937di5_regs.power = tuner_4937di5_regs_t::POWER_OFF; update_regs(); // ok don't forget to reset RF gain here if the new band != the old band // we do this because the gains are different for different band settings // not FAR off, but we do this to be consistent if (prev_band != new_band) set_gain(_gains["RF"], "RF"); UHD_LOGGER_TRACE("TVRX") << boost::format("set_freq: target LO: %f f_ref: %f divisor: %i actual LO: %f") % target_lo_freq % f_ref % divisor % actual_lo_freq; _lo_freq = actual_lo_freq; // for rx props // Check the the IF if larger than the dsp rate and apply a corrective adjustment // so that the cordic will be tuned to a possible rate within its range. const double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); if (tvrx_if_freq >= codec_rate / 2) { return _lo_freq - codec_rate; } return _lo_freq; }