// // Copyright 2010 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 . // // 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 using namespace uhd; using namespace uhd::usrp; using namespace boost::assign; /*********************************************************************** * The tvrx constants **********************************************************************/ static const bool tvrx_debug = true; static const freq_range_t tvrx_freq_range(50e6, 860e6); static const prop_names_t tvrx_antennas = ""; //only got one static const uhd::dict tvrx_gain_ranges = map_list_of ("RF", gain_range_t(-13.3, 50.3, float(50.3/4096))) //both gains are analog and controlled by DAC ("IF", gain_range_t(-1.5, 32.5, float(32.5/4096))) //the old driver used 1dB for both; i don't think that's right? ; 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)) ; //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", std::vector(-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)) ("VHFHI", std::vector(-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)) ("UHF" , std::vector(-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 std::vector tvrx_if_gains_db = std::vector(-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) ; //sample voltages for the above points static const std::vector tvrx_rf_gains_volts = std::vector(0.6, 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); /*! * 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 float gain_interp(float gain, std::vector db_vector, std::vector volts_vector) { float volts; gain = std::clip(gain, db_vector.front(), db_vector.back()); //let's not get carried away here boost::uint8_t gain_step = 0; for(int i = 0; i < db_vector.size()-1; i++) { if(gain > db_vector[i] && gain < db_vector.[i+1]) gain_step = i; } return volts; } static const double opamp_gain = 1.22; //onboard DAC opamp gain static const double tvrx_lo_freq = 43.75e6; //LO freq of TVRX module static const boost::uint16_t reference_divider = 640; //clock reference divider to use /*********************************************************************** * The tvrx dboard class **********************************************************************/ class tvrx : public rx_dboard_base{ public: tvrx(ctor_args_t args); ~tvrx(void); void rx_get(const wax::obj &key, wax::obj &val); void rx_set(const wax::obj &key, const wax::obj &val); private: uhd::dict _gains; tuner_4937di5_regs_t _tuner_4937di5_regs; boost::uint8_t _tuner_4937di5_addr(void){ return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x60 : 0x61; //ok really? we could rename that call }; void set_gain(float gain, const std::string &name); 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); if(tvrx_debug) std::cerr << boost::format( "tvrx: send reg 0x%02x, value 0x%04x" ) % int(i) % int(regs_vector[i]) << std::endl; } //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(0x0003, &make_tvrx, "tvrx"); } /*********************************************************************** * Structors **********************************************************************/ tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args){ 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 //HAAAAY GUYYYYYSSS //set default gains BOOST_FOREACH(const std::string &name, tvrx_gain_ranges.keys()){ set_gain(tvrx_gain_ranges[name].min, name); } } 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) { BOOST_FOREACH(const std::string &band, tvrx_freq_ranges.keys()) { if(freq >= tvrx_freq_ranges[band].min && freq <= tvrx_freq_ranges[band].max) return name; } UHD_THROW_INVALID_CODE_PATH(); } /*********************************************************************** * Gain Handling **********************************************************************/ /* * Gain accuracy on this thing is basically a crap shoot. In other words, there is none. * The old driver basically picked a linear approximation of the median gain, but the actual * gain slope is heavily nonlinear and varies both with gain and with frequency. * It also probably varies markedly over temperature, but there's only so much we can do. * The best approximation to the RF gain data is fifth-order. If we ignore the curve portions * below 0dB (yes, it's a pad below a certain gain value) and if we ignore the curve portions * near the max-gain asymptotes, it looks pretty close to third-order. This is less insane to * approximate. */ /*! * 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 float rf_gain_to_voltage(float &gain){ //clip the input gain = std::clip(gain, tvrx_gain_ranges["RF"].min, tvrx_gain_ranges["RF"].max); //voltage level constants static const float max_volts = float(4.0), min_volts = float(0.6); static const float slope = (max_volts-min_volts)/tvrx_gain_ranges["RF"].max; //this is the voltage at the TVRX gain input float gain_volts = gain*slope + min_volts //this is the voltage at the USRP DAC output float dac_volts = gain_volts / opamp_gain; //the actual gain setting gain = (dac_volts - min_volts)/slope; if (tvrx_debug) std::cerr << boost::format( "tvrx RF AGC gain: %f dB, dac_volts: %f V" ) % gain % dac_volts << std::endl; 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 float if_gain_to_voltage(float &gain){ //clip the input gain = std::clip(gain, tvrx_gain_ranges["IF"].min, tvrx_gain_ranges["IF"].max); //voltage level constants static const float max_volts = float(4.0), min_volts = float(0.0); static const float slope = (max_volts-min_volts)/tvrx_gain_ranges["IF"].max; //this is the voltage at the TVRX gain input float gain_volts = gain*slope + 1.25; //this is the voltage at the USRP DAC output float dac_volts = gain_volts / opamp_gain; //the actual gain setting gain = (dac_volts - min_volts)/slope; if (tvrx_debug) std::cerr << boost::format( "tvrx IF AGC gain: %f dB, dac_volts: %f V" ) % gain % dac_volts << std::endl; return dac_volts; } void tvrx::set_gain(float gain, const std::string &name){ assert_has(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_A, rf_gain_to_voltage(gain)); } else if(name == "IF"){ this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_B, if_gain_to_voltage(gain)); } else UHD_THROW_INVALID_CODE_PATH(); _gains[name] = gain; } /*! * Set the tuner to center the desired frequency at 43.75MHz * \param freq the requested frequency */ void tvrx::set_freq(double freq) { } /*********************************************************************** * RX Get and Set **********************************************************************/ void tvrx::rx_get(const wax::obj &key_, wax::obj &val){ named_prop_t key = named_prop_t::extract(key_); //handle the get request conditioned on the key switch(key.as()){ case SUBDEV_PROP_NAME: val = get_rx_id().to_pp_string(); return; case SUBDEV_PROP_OTHERS: val = prop_names_t(); //empty return; case SUBDEV_PROP_GAIN: assert_has(_gains.keys(), key.name, "tvrx gain name"); val = _gains[key.name]; return; case SUBDEV_PROP_GAIN_RANGE: assert_has(tvrx_gain_ranges.keys(), key.name, "tvrx gain name"); val = tvrx_gain_ranges[key.name]; return; case SUBDEV_PROP_GAIN_NAMES: val = prop_names_t(tvrx_gain_ranges.keys()); return; case SUBDEV_PROP_FREQ: val = tvrx_lo_freq; return; case SUBDEV_PROP_FREQ_RANGE: val = tvrx_freq_range; return; case SUBDEV_PROP_ANTENNA: val = std::string(tvrx_antennas); return; case SUBDEV_PROP_ANTENNA_NAMES: val = tvrx_antennas; return; case SUBDEV_PROP_CONNECTION: val = SUBDEV_CONN_COMPLEX_IQ; return; case SUBDEV_PROP_USE_LO_OFFSET: val = false; return; case SUBDEV_PROP_LO_LOCKED: val = true; return; default: UHD_THROW_PROP_GET_ERROR(); } } void tvrx::rx_set(const wax::obj &key_, const wax::obj &val){ named_prop_t key = named_prop_t::extract(key_); //handle the get request conditioned on the key switch(key.as()){ case SUBDEV_PROP_GAIN: this->set_gain(val.as(), key.name); return; case SUBDEV_PROP_FREQ: this->set_freq(val.as()); return; default: UHD_THROW_PROP_SET_ERROR(); } }