// // Copyright 2010-2012 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company // // SPDX-License-Identifier: GPL-3.0-or-later // // No RX IO Pins Used #include "max2112_regs.hpp" #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 DBSRX2 constants **********************************************************************/ static const freq_range_t dbsrx2_freq_range(0.8e9, 2.3e9); //Multiplied by 2.0 for conversion to complex bandpass from lowpass static const freq_range_t dbsrx2_bandwidth_range(2.0*4.0e6, 2.0*40.0e6); static const int dbsrx2_ref_divider = 4; // Hitachi HMC426 divider (U7) static const std::vector dbsrx2_antennas = list_of("J3"); static const uhd::dict dbsrx2_gain_ranges = map_list_of ("GC1", gain_range_t(0, 73, 0.05)) ("BBG", gain_range_t(0, 15, 1)) ; /*********************************************************************** * The DBSRX2 dboard class **********************************************************************/ class dbsrx2 : public rx_dboard_base{ public: dbsrx2(ctor_args_t args); virtual ~dbsrx2(void); private: double _lo_freq; double _bandwidth; uhd::dict _gains; max2112_write_regs_t _max2112_write_regs; max2112_read_regs_t _max2112_read_regs; uint8_t _max2112_addr(){ //0x60 or 0x61 depending on which side return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x60 : 0x61; } double set_lo_freq(double target_freq); double set_gain(double gain, const std::string &name); double set_bandwidth(double bandwidth); void send_reg(uint8_t start_reg, uint8_t stop_reg){ start_reg = uint8_t(uhd::clip(int(start_reg), 0x0, 0xB)); stop_reg = uint8_t(uhd::clip(int(stop_reg), 0x0, 0xB)); for(uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(uint32_t) - 1){ int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(uint32_t)) - 1 ? sizeof(uint32_t) - 1 : stop_reg - start_addr + 1; //create buffer for register data (+1 for start address) byte_vector_t regs_vector(num_bytes + 1); //first byte is the address of first register regs_vector[0] = start_addr; //get the register data for(int i=0; iget_iface()->write_i2c( _max2112_addr(), regs_vector ); } } void read_reg(uint8_t start_reg, uint8_t stop_reg){ static const uint8_t status_addr = 0xC; start_reg = uint8_t(uhd::clip(int(start_reg), 0x0, 0xD)); stop_reg = uint8_t(uhd::clip(int(stop_reg), 0x0, 0xD)); for(uint8_t start_addr=start_reg; start_addr <= stop_reg; start_addr += sizeof(uint32_t)){ int num_bytes = int(stop_reg - start_addr + 1) > int(sizeof(uint32_t)) ? sizeof(uint32_t) : stop_reg - start_addr + 1; //create address to start reading register data byte_vector_t address_vector(1); address_vector[0] = start_addr; //send the address this->get_iface()->write_i2c( _max2112_addr(), address_vector ); //create buffer for register data byte_vector_t regs_vector(num_bytes); //read from i2c regs_vector = this->get_iface()->read_i2c( _max2112_addr(), num_bytes ); for(uint8_t i=0; i < num_bytes; i++){ if (i + start_addr >= status_addr){ _max2112_read_regs.set_reg(i + start_addr, regs_vector[i]); } UHD_LOGGER_TRACE("DBSRX") << boost::format( "DBSRX2: read reg 0x%02x, value 0x%04x, start_addr = 0x%04x, num_bytes %d" ) % int(start_addr+i) % int(regs_vector[i]) % int(start_addr) % num_bytes ; } } } /*! * Get the lock detect status of the LO. * \return sensor for locked */ sensor_value_t get_locked(void){ read_reg(0xC, 0xD); //mask and return lock detect bool locked = (_max2112_read_regs.ld & _max2112_read_regs.vasa & _max2112_read_regs.vase) != 0; UHD_LOGGER_TRACE("DBSRX") << boost::format( "DBSRX2 locked: %d" ) % locked ; return sensor_value_t("LO", locked, "locked", "unlocked"); } }; /*********************************************************************** * Register the DBSRX2 dboard **********************************************************************/ // FIXME 0x67 is the default i2c address on USRP2 // need to handle which side for USRP1 with different address static dboard_base::sptr make_dbsrx2(dboard_base::ctor_args_t args){ return dboard_base::sptr(new dbsrx2(args)); } UHD_STATIC_BLOCK(reg_dbsrx2_dboard){ //register the factory function for the rx dbid dboard_manager::register_dboard(0x0012, &make_dbsrx2, "DBSRX2"); } /*********************************************************************** * Structors **********************************************************************/ dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){ //send initial register settings send_reg(0x0, 0xB); //for (uint8_t addr=0; addr<=12; addr++) this->send_reg(addr, addr); //////////////////////////////////////////////////////////////////// // Register properties //////////////////////////////////////////////////////////////////// this->get_rx_subtree()->create("name") .set("DBSRX"); this->get_rx_subtree()->create("sensors/lo_locked") .set_publisher(boost::bind(&dbsrx2::get_locked, this)); for(const std::string &name: dbsrx2_gain_ranges.keys()){ this->get_rx_subtree()->create("gains/"+name+"/value") .set_coercer(boost::bind(&dbsrx2::set_gain, this, _1, name)) .set(dbsrx2_gain_ranges[name].start()); this->get_rx_subtree()->create("gains/"+name+"/range") .set(dbsrx2_gain_ranges[name]); } this->get_rx_subtree()->create("freq/value") .set_coercer(boost::bind(&dbsrx2::set_lo_freq, this, _1)) .set(dbsrx2_freq_range.start()); this->get_rx_subtree()->create("freq/range") .set(dbsrx2_freq_range); this->get_rx_subtree()->create("antenna/value") .set(dbsrx2_antennas.at(0)); this->get_rx_subtree()->create >("antenna/options") .set(dbsrx2_antennas); this->get_rx_subtree()->create("connection") .set("QI"); this->get_rx_subtree()->create("enabled") .set(true); //always enabled this->get_rx_subtree()->create("use_lo_offset") .set(false); double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); this->get_rx_subtree()->create("bandwidth/value") .set_coercer(boost::bind(&dbsrx2::set_bandwidth, this, _1)) .set(2.0*(0.8*codec_rate/2.0)); //bandwidth in lowpass, convert to complex bandpass //default to anti-alias at different codec_rate this->get_rx_subtree()->create("bandwidth/range") .set(dbsrx2_bandwidth_range); //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 this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs get_locked(); } dbsrx2::~dbsrx2(void){ } /*********************************************************************** * Tuning **********************************************************************/ double dbsrx2::set_lo_freq(double target_freq){ //target_freq = dbsrx2_freq_range.clip(target_freq); //variables used in the calculation below int scaler = target_freq >= 1125e6 ? 2 : 4; double ref_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); int R, intdiv, fracdiv, ext_div; double N; //compute tuning variables ext_div = dbsrx2_ref_divider; // 12MHz < ref_freq/ext_divider < 30MHz R = 1; //Divide by 1 is the only tested value N = (target_freq*R*ext_div)/(ref_freq); //actual spec range is (19, 251) intdiv = int(std::floor(N)); // if (intdiv < 19 or intdiv > 251) continue; fracdiv = boost::math::iround((N - intdiv)*double(1 << 20)); //calculate the actual freq from the values above N = double(intdiv) + double(fracdiv)/double(1 << 20); _lo_freq = (N*ref_freq)/(R*ext_div); //load new counters into registers _max2112_write_regs.set_n_divider(intdiv); _max2112_write_regs.set_f_divider(fracdiv); _max2112_write_regs.r_divider = R; _max2112_write_regs.d24 = scaler == 4 ? max2112_write_regs_t::D24_DIV4 : max2112_write_regs_t::D24_DIV2; //debug output of calculated variables UHD_LOGGER_TRACE("DBSRX") << boost::format("DBSRX2 tune:\n") << boost::format(" R=%d, N=%f, scaler=%d, ext_div=%d\n") % R % N % scaler % ext_div << boost::format(" int=%d, frac=%d, d24=%d\n") % intdiv % fracdiv % int(_max2112_write_regs.d24) << boost::format(" Ref Freq=%fMHz\n") % (ref_freq/1e6) << boost::format(" Target Freq=%fMHz\n") % (target_freq/1e6) << boost::format(" Actual Freq=%fMHz\n") % (_lo_freq/1e6) ; //send the registers 0x0 through 0x7 //writing register 0x4 (F divider LSB) starts the VCO auto seletion so it must be written last send_reg(0x5, 0x7); send_reg(0x0, 0x4); //FIXME: probably unnecessary to call get_locked here //get_locked(); return _lo_freq; } /*********************************************************************** * Gain Handling **********************************************************************/ /*! * Convert a requested gain for the BBG vga into the integer register value. * The gain passed into the function will be set to the actual value. * \param gain the requested gain in dB * \return 4 bit the register value */ static int gain_to_bbg_vga_reg(double &gain){ int reg = boost::math::iround(dbsrx2_gain_ranges["BBG"].clip(gain)); gain = double(reg); UHD_LOGGER_TRACE("DBSRX") << boost::format("DBSRX2 BBG Gain:\n") << boost::format(" %f dB, bbg: %d") % gain % reg ; return reg; } /*! * Convert a requested gain for the GC1 rf vga into the dac_volts value. * 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 gain_to_gc1_rfvga_dac(double &gain){ //clip the input gain = dbsrx2_gain_ranges["GC1"].clip(gain); //voltage level constants static const double max_volts = 0.5, min_volts = 2.7; static const double slope = (max_volts-min_volts)/dbsrx2_gain_ranges["GC1"].stop(); //calculate the voltage for the aux dac double dac_volts = gain*slope + min_volts; UHD_LOGGER_TRACE("DBSRX") << boost::format("DBSRX2 GC1 Gain:\n") << boost::format(" %f dB, dac_volts: %f V") % gain % dac_volts ; //the actual gain setting gain = (dac_volts - min_volts)/slope; return dac_volts; } double dbsrx2::set_gain(double gain, const std::string &name){ assert_has(dbsrx2_gain_ranges.keys(), name, "dbsrx2 gain name"); if (name == "BBG"){ _max2112_write_regs.bbg = gain_to_bbg_vga_reg(gain); send_reg(0x9, 0x9); } else if(name == "GC1"){ //write the new voltage to the aux dac this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, gain_to_gc1_rfvga_dac(gain)); } else UHD_THROW_INVALID_CODE_PATH(); _gains[name] = gain; return gain; } /*********************************************************************** * Bandwidth Handling **********************************************************************/ double dbsrx2::set_bandwidth(double bandwidth){ //clip the input bandwidth = dbsrx2_bandwidth_range.clip(bandwidth); //convert complex bandpass to lowpass bandwidth bandwidth = bandwidth/2.0; _max2112_write_regs.lp = int((bandwidth/1e6 - 4)/0.29 + 12); _bandwidth = double(4 + (_max2112_write_regs.lp - 12) * 0.29)*1e6; UHD_LOGGER_TRACE("DBSRX") << boost::format("DBSRX2 Bandwidth:\n") << boost::format(" %f MHz, lp: %f V") % (_bandwidth/1e6) % int(_max2112_write_regs.lp) ; this->send_reg(0x8, 0x8); //convert lowpass back to complex bandpass bandwidth return 2.0*_bandwidth; }