/*********************************************************************** * WBX Common Implementation **********************************************************************/ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// this->get_rx_subtree()->create<std::string>("name").set("WBXv4 RX"); this->get_rx_subtree()->create<double>("freq/value") .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); //////////////////////////////////////////////////////////////////// // Register TX properties //////////////////////////////////////////////////////////////////// this->get_tx_subtree()->create<std::string>("name").set("WBXv4 TX"); BOOST_FOREACH(const std::string &name, wbx_v4_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") .coerce(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name)) .set(wbx_v4_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v4_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); this->get_tx_subtree()->create<bool>("enabled") .subscribe(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits int v4_iobits = TX_ATTN_MASK; int v4_tx_mod = ADF4351_PDBRF; //set the gpio directions and atr controls self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v4_tx_mod|v4_iobits); self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4351_PDBRF); self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v4_tx_mod|v4_iobits); self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4351_CE|RXBB_PDB|ADF4351_PDBRF|RX_ATTN_MASK); //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) //set TX gain iobits to min gain (max attenuation) when RX_ONLY or IDLE to suppress LO leakage self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v4_tx_mod, TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); }
/*********************************************************************** * Structors **********************************************************************/ wbx_xcvr::wbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ //enable the clocks that we need this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); 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_TX, TXIO_MASK); this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TXIO_MASK); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RXIO_MASK); if (wbx_debug) std::cerr << boost::format( "WBX GPIO Direction: RX: 0x%08x, TX: 0x%08x" ) % RXIO_MASK % TXIO_MASK << std::endl; //set some default values set_rx_lo_freq((wbx_freq_range.start() + wbx_freq_range.stop())/2.0); set_tx_lo_freq((wbx_freq_range.start() + wbx_freq_range.stop())/2.0); set_rx_ant("RX2"); BOOST_FOREACH(const std::string &name, wbx_tx_gain_ranges.keys()){ set_tx_gain(wbx_tx_gain_ranges[name].start(), name); } BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ set_rx_gain(wbx_rx_gain_ranges[name].start(), name); } }
/*********************************************************************** * Structors **********************************************************************/ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ //enable the clocks that we need this->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); 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_TX, (TXIO_MASK|TX_LED_IO)); this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); //flash LEDs flash_leds(); UHD_LOGV(often) << boost::format( "SBX GPIO Direction: RX: 0x%08x, TX: 0x%08x" ) % RXIO_MASK % TXIO_MASK << std::endl; //set some default values set_rx_lo_freq((sbx_freq_range.start() + sbx_freq_range.stop())/2.0); set_tx_lo_freq((sbx_freq_range.start() + sbx_freq_range.stop())/2.0); set_rx_ant("RX2"); BOOST_FOREACH(const std::string &name, sbx_tx_gain_ranges.keys()){ set_tx_gain(sbx_tx_gain_ranges[name].start(), name); } BOOST_FOREACH(const std::string &name, sbx_rx_gain_ranges.keys()){ set_rx_gain(sbx_rx_gain_ranges[name].start(), name); } }
/*********************************************************************** * Structors **********************************************************************/ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ //set the gpio directions and atr controls (antenna switches all under ATR) this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, ANTSW_IO, ANTSW_IO); this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, ANTSW_IO, ANTSW_IO); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO); //setup ATR for the antenna switches (constant) this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, ANT_RX, ANTSW_IO); this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO); this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO); this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); //set some default values set_rx_lo_freq((wbx_freq_range.start() + wbx_freq_range.stop())/2.0); set_tx_lo_freq((wbx_freq_range.start() + wbx_freq_range.stop())/2.0); set_rx_ant("RX2"); }
/*********************************************************************** * Tuning **********************************************************************/ void dbsrx::set_lo_freq(double target_freq){ target_freq = dbsrx_freq_range.clip(target_freq); double actual_freq=0.0, pfd_freq=0.0, ref_clock=0.0; int R=0, N=0, r=0, m=0; bool update_filter_settings = false; //choose refclock std::vector<double> clock_rates = this->get_iface()->get_clock_rates(dboard_iface::UNIT_RX); const double max_clock_rate = std::sorted(clock_rates).back(); BOOST_FOREACH(ref_clock, std::reversed(std::sorted(clock_rates))){ if (ref_clock > 27.0e6) continue; if (size_t(max_clock_rate/ref_clock)%2 == 1) continue; //reject asymmetric clocks (odd divisors) //choose m_divider such that filter tuning constraint is met m = 31; while ((ref_clock/m < 1e6 or ref_clock/m > 2.5e6) and m > 0){ m--; } if(dbsrx_debug) std::cerr << boost::format( "DBSRX: trying ref_clock %f and m_divider %d" ) % (ref_clock) % m << std::endl; if (m >= 32) continue; //choose R for(r = 0; r <= 6; r += 1) { //compute divider from setting R = 1 << (r+1); if (dbsrx_debug) std::cerr << boost::format("DBSRX R:%d\n") % R << std::endl; //compute PFD compare frequency = ref_clock/R pfd_freq = ref_clock / R; //constrain the PFD frequency to specified range if ((pfd_freq < dbsrx_pfd_freq_range.start()) or (pfd_freq > dbsrx_pfd_freq_range.stop())) continue; //compute N N = int(std::floor(target_freq/pfd_freq)); //constrain N to specified range if ((N < 256) or (N > 32768)) continue; goto done_loop; } } done_loop: //Assert because we failed to find a suitable combination of ref_clock, R and N UHD_ASSERT_THROW(ref_clock <= 27.0e6 and ref_clock >= 0.0); UHD_ASSERT_THROW(ref_clock/m >= 1e6 and ref_clock/m <= 2.5e6); UHD_ASSERT_THROW((pfd_freq >= dbsrx_pfd_freq_range.start()) and (pfd_freq <= dbsrx_pfd_freq_range.stop())); UHD_ASSERT_THROW((N >= 256) and (N <= 32768)); if(dbsrx_debug) std::cerr << boost::format( "DBSRX: choose ref_clock (current: %f, new: %f) and m_divider %d" ) % (this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX)) % ref_clock % m << std::endl; //if ref_clock or m divider changed, we need to update the filter settings if (ref_clock != this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX) or m != _max2118_write_regs.m_divider) update_filter_settings = true; //compute resulting output frequency actual_freq = pfd_freq * N; //apply ref_clock, R, and N settings this->get_iface()->set_clock_rate(dboard_iface::UNIT_RX, ref_clock); ref_clock = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX); _max2118_write_regs.m_divider = m; _max2118_write_regs.r_divider = (max2118_write_regs_t::r_divider_t) r; _max2118_write_regs.set_n_divider(N); _max2118_write_regs.ade_vco_ade_read = max2118_write_regs_t::ADE_VCO_ADE_READ_ENABLED; //compute prescaler variables int scaler = actual_freq > 1125e6 ? 2 : 4; _max2118_write_regs.div2 = scaler == 4 ? max2118_write_regs_t::DIV2_DIV4 : max2118_write_regs_t::DIV2_DIV2; if(dbsrx_debug) std::cerr << boost::format( "DBSRX: scaler %d, actual_freq %f MHz, register bit: %d" ) % scaler % (actual_freq/1e6) % int(_max2118_write_regs.div2) << std::endl; //compute vco frequency and select vco double vco_freq = actual_freq * scaler; if (vco_freq < 2433e6) _max2118_write_regs.osc_band = 0; else if (vco_freq < 2711e6) _max2118_write_regs.osc_band = 1; else if (vco_freq < 3025e6) _max2118_write_regs.osc_band = 2; else if (vco_freq < 3341e6) _max2118_write_regs.osc_band = 3; else if (vco_freq < 3727e6) _max2118_write_regs.osc_band = 4; else if (vco_freq < 4143e6) _max2118_write_regs.osc_band = 5; else if (vco_freq < 4493e6) _max2118_write_regs.osc_band = 6; else _max2118_write_regs.osc_band = 7; //send settings over i2c send_reg(0x0, 0x4); //check vtune for lock condition read_reg(0x0, 0x0); if(dbsrx_debug) std::cerr << boost::format( "DBSRX: initial guess for vco %d, vtune adc %d" ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; //if we are out of lock for chosen vco, change vco while ((_max2118_read_regs.adc == 0) or (_max2118_read_regs.adc == 7)){ //vtune is too low, try lower frequency vco if (_max2118_read_regs.adc == 0){ if (_max2118_write_regs.osc_band == 0){ uhd::warning::post( str(boost::format( "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n" ) % int(_max2118_write_regs.osc_band)) ); UHD_ASSERT_THROW(_max2118_read_regs.adc != 0); //just to cause a throw } if (_max2118_write_regs.osc_band <= 0) break; _max2118_write_regs.osc_band -= 1; } //vtune is too high, try higher frequency vco if (_max2118_read_regs.adc == 7){ if (_max2118_write_regs.osc_band == 7){ uhd::warning::post( str(boost::format( "DBSRX: Tuning exceeded vco range, _max2118_write_regs.osc_band == %d\n" ) % int(_max2118_write_regs.osc_band)) ); UHD_ASSERT_THROW(_max2118_read_regs.adc != 7); //just to cause a throw } if (_max2118_write_regs.osc_band >= 7) break; _max2118_write_regs.osc_band += 1; } if(dbsrx_debug) std::cerr << boost::format( "DBSRX: trying vco %d, vtune adc %d" ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; //update vco selection and check vtune send_reg(0x2, 0x2); read_reg(0x0, 0x0); //allow for setup time before checking condition again boost::this_thread::sleep(boost::posix_time::milliseconds(10)); } if(dbsrx_debug) std::cerr << boost::format( "DBSRX: final vco %d, vtune adc %d" ) % int(_max2118_write_regs.osc_band) % int(_max2118_read_regs.adc) << std::endl; //select charge pump bias current if (_max2118_read_regs.adc <= 2) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_100UA; else if (_max2118_read_regs.adc >= 5) _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_400UA; else _max2118_write_regs.cp_current = max2118_write_regs_t::CP_CURRENT_I_CP_200UA; //update charge pump bias current setting send_reg(0x2, 0x2); //compute actual tuned frequency _lo_freq = this->get_iface()->get_clock_rate(dboard_iface::UNIT_RX) / std::pow(2.0,(1 + _max2118_write_regs.r_divider)) * _max2118_write_regs.get_n_divider(); //debug output of calculated variables if (dbsrx_debug) std::cerr << boost::format("DBSRX tune:\n") << boost::format(" VCO=%d, CP=%d, PFD Freq=%fMHz\n") % int(_max2118_write_regs.osc_band) % _max2118_write_regs.cp_current % (pfd_freq/1e6) << boost::format(" R=%d, N=%f, scaler=%d, div2=%d\n") % R % N % scaler % int(_max2118_write_regs.div2) << boost::format(" Ref Freq=%fMHz\n") % (ref_clock/1e6) << boost::format(" Target Freq=%fMHz\n") % (target_freq/1e6) << boost::format(" Actual Freq=%fMHz\n") % (_lo_freq/1e6) << boost::format(" VCO Freq=%fMHz\n") % (vco_freq/1e6) << std::endl; if (update_filter_settings) set_bandwidth(_bandwidth); get_locked(); }