// // 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 #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); ~dbsrx2(void) override; 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; i < num_bytes; i++) { regs_vector[1 + i] = _max2112_write_regs.get_reg(start_addr + i); UHD_LOGGER_TRACE("DBSRX") << boost::format("DBSRX2: send reg 0x%02x, value 0x%04x, start_addr " "= 0x%04x, num_bytes %d") % int(start_addr + i) % int(regs_vector[1 + i]) % int(start_addr) % num_bytes; } // send the data this->get_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(std::bind(&dbsrx2::get_locked, this)); for (const std::string& name : dbsrx2_gain_ranges.keys()) { this->get_rx_subtree() ->create("gains/" + name + "/value") .set_coercer(std::bind(&dbsrx2::set_gain, this, std::placeholders::_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(std::bind(&dbsrx2::set_lo_freq, this, std::placeholders::_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(std::bind(&dbsrx2::set_bandwidth, this, std::placeholders::_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 = static_cast(std::lround((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 = static_cast(std::lround(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; }