diff options
| -rwxr-xr-x | host/lib/ic_reg_maps/gen_adf4350_regs.py | 1 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/db_wbx_common.cpp | 486 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/db_wbx_common.hpp | 172 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/db_wbx_simple.cpp | 47 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/db_wbx_version2.cpp | 394 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/db_wbx_version3.cpp | 401 | ||||
| -rw-r--r-- | host/lib/usrp/dboard/db_wbx_version4.cpp | 404 | 
8 files changed, 1447 insertions, 461 deletions
| diff --git a/host/lib/ic_reg_maps/gen_adf4350_regs.py b/host/lib/ic_reg_maps/gen_adf4350_regs.py index bccdb2edf..fce2f569b 100755 --- a/host/lib/ic_reg_maps/gen_adf4350_regs.py +++ b/host/lib/ic_reg_maps/gen_adf4350_regs.py @@ -51,6 +51,7 @@ reference_divide_by_2   2[24]       1       disabled, enabled  reference_doubler       2[25]       0       disabled, enabled  muxout                  2[26:28]    1       3state, dvdd, dgnd, rdiv, ndiv, analog_ld, dld, reserved  low_noise_and_spur      2[29:30]    3       low_noise, reserved0, reserved1, low_spur +##reserved              2[31]       0  ########################################################################  ## address 3  ######################################################################## diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index a865a7858..744285ac3 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -27,6 +27,9 @@ LIBUHD_APPEND_SOURCES(      #${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_version3.cpp      #${CMAKE_CURRENT_SOURCE_DIR}/db_sbx_version4.cpp      #${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_common.cpp +    #${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version2.cpp +    #${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version3.cpp +    #${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_version4.cpp      #${CMAKE_CURRENT_SOURCE_DIR}/db_wbx_simple.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx.cpp      ${CMAKE_CURRENT_SOURCE_DIR}/db_unknown.cpp diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp index c7f8ba55d..63f70e3d5 100644 --- a/host/lib/usrp/dboard/db_wbx_common.cpp +++ b/host/lib/usrp/dboard/db_wbx_common.cpp @@ -15,151 +15,22 @@  // along with this program.  If not, see <http://www.gnu.org/licenses/>.  // -// Common IO Pins -#define ADF4350_CE      (1 << 3) -#define ADF4350_PDBRF   (1 << 2) -#define ADF4350_MUXOUT  (1 << 1)                // INPUT!!! -#define LOCKDET_MASK    (1 << 0)                // INPUT!!! - -// TX IO Pins -#define TX_PUP_5V       (1 << 7)                // enables 5.0V power supply -#define TX_PUP_3V       (1 << 6)                // enables 3.3V supply -#define TXMOD_EN        (1 << 4)                // on UNIT_TX, 1 enables TX Modulator - -// RX IO Pins -#define RX_PUP_5V       (1 << 7)                // enables 5.0V power supply -#define RX_PUP_3V       (1 << 6)                // enables 3.3V supply -#define RXBB_PDB        (1 << 4)                // on UNIT_RX, 1 powers up RX baseband - -// RX Attenuator Pins -#define RX_ATTN_SHIFT   8                       // lsb of RX Attenuator Control -#define RX_ATTN_MASK    (63 << RX_ATTN_SHIFT)      // valid bits of RX Attenuator Control - -// TX Attenuator Pins (v3 only) -#define TX_ATTN_16      (1 << 14) -#define TX_ATTN_8       (1 << 5) -#define TX_ATTN_4       (1 << 4) -#define TX_ATTN_2       (1 << 3) -#define TX_ATTN_1       (1 << 1) -#define TX_ATTN_MASK    (TX_ATTN_16|TX_ATTN_8|TX_ATTN_4|TX_ATTN_2|TX_ATTN_1)      // valid bits of TX Attenuator Control - -// Mixer functions -#define TX_MIXER_ENB    (TXMOD_EN|ADF4350_PDBRF)    // for v3, TXMOD_EN tied to ADF4350_PDBRF rather than separate -#define TX_MIXER_DIS    0 - -#define RX_MIXER_ENB    (RXBB_PDB|ADF4350_PDBRF) -#define RX_MIXER_DIS    0 - -// Power functions -#define TX_POWER_UP     (TX_PUP_5V|TX_PUP_3V) // high enables power supply -#define TX_POWER_DOWN   0 - -#define RX_POWER_UP     (RX_PUP_5V|RX_PUP_3V|ADF4350_CE) // high enables power supply -#define RX_POWER_DOWN   0 -  #include "db_wbx_common.hpp"  #include "adf4350_regs.hpp" -#include <uhd/utils/log.hpp>  #include <uhd/types/dict.hpp>  #include <uhd/types/ranges.hpp>  #include <uhd/types/sensors.hpp>  #include <uhd/utils/assert_has.hpp>  #include <uhd/utils/algorithm.hpp>  #include <uhd/utils/msg.hpp> -#include <uhd/usrp/dboard_base.hpp> -#include <boost/assign/list_of.hpp> -#include <boost/format.hpp> -#include <boost/math/special_functions/round.hpp>  using namespace uhd;  using namespace uhd::usrp;  using namespace boost::assign; -/*********************************************************************** - * The WBX Common dboard constants - **********************************************************************/ -static const uhd::dict<std::string, gain_range_t> wbx_tx_gain_ranges = map_list_of -    ("PGA0", gain_range_t(0, 25, 0.05)) -; - -static const uhd::dict<std::string, gain_range_t> wbx_v3_tx_gain_ranges = map_list_of -    ("PGA0", gain_range_t(0, 31, 1.0)) -; - -static const uhd::dict<std::string, gain_range_t> wbx_rx_gain_ranges = map_list_of -    ("PGA0", gain_range_t(0, 31.5, 0.5)) -; - -/*********************************************************************** - * WBX Common Implementation - **********************************************************************/ -wbx_base::wbx_base(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); - -    //v3 has different io bits for attenuator control -    int v3_iobits = is_v3() ? TX_ATTN_MASK : ADF4350_CE; -    int v3_tx_mod = is_v3() ? ADF4350_PDBRF : TXMOD_EN|ADF4350_PDBRF; - -    //set the gpio directions and atr controls -    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v3_tx_mod); -    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4350_PDBRF); -    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v3_tx_mod|v3_iobits); -    this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4350_CE|RXBB_PDB|ADF4350_PDBRF|RX_ATTN_MASK); - -    //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) -    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); -    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); -    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); -    this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); - -    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE,        RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); -    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY,     RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); -    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY,     RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); -    this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - -    //set some default values -    if (is_v3()) { -        BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){ -            set_tx_gain(wbx_v3_tx_gain_ranges[name].start(), name); -        } -    } -    else { -        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); -    } -    set_rx_enabled(false); -    set_tx_enabled(false); -} - -wbx_base::~wbx_base(void){ -    /* NOP */ -} - -/*********************************************************************** - * Enables - **********************************************************************/ -void wbx_base::set_rx_enabled(bool enb){ -    this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, -        (enb)? RX_POWER_UP : RX_POWER_DOWN, RX_POWER_UP | RX_POWER_DOWN -    ); -} - -void wbx_base::set_tx_enabled(bool enb){ -    this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, -        (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | (is_v3() ? 0 : ADF4350_CE) -    ); -}  /*********************************************************************** - * Gain Handling + * Gain-related functions   **********************************************************************/  static int rx_pga0_gain_to_iobits(double &gain){      //clip the input @@ -182,79 +53,50 @@ static int rx_pga0_gain_to_iobits(double &gain){      return iobits;  } -//v3 TX gains -static int tx_pga0_gain_to_iobits(double &gain){ -    //clip the input -    gain = wbx_v3_tx_gain_ranges["PGA0"].clip(gain); - -    //convert to attenuation -    double attn = wbx_v3_tx_gain_ranges["PGA0"].stop() - gain; -    //calculate the attenuation -    int attn_code = boost::math::iround(attn); -    int iobits = ( -            (attn_code & 16 ? 0 : TX_ATTN_16) | -            (attn_code &  8 ? 0 : TX_ATTN_8) | -            (attn_code &  4 ? 0 : TX_ATTN_4) | -            (attn_code &  2 ? 0 : TX_ATTN_2) | -            (attn_code &  1 ? 0 : TX_ATTN_1)  -        ) & TX_ATTN_MASK; - -    UHD_LOGV(often) << boost::format( -        "WBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" -    ) % attn % attn_code % (iobits & TX_ATTN_MASK) % TX_ATTN_MASK << std::endl; - -    //the actual gain setting -    gain = wbx_v3_tx_gain_ranges["PGA0"].stop() - double(attn_code); - -    return iobits; +/*********************************************************************** + * WBX Common Implementation + **********************************************************************/ +wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ +    switch(get_rx_id().to_uint16()) { +        case 0x053: +            db_actual = wbx_versionx_sptr(new wbx_version2(this)); +            return; +        case 0x057: +            db_actual = wbx_versionx_sptr(new wbx_version3(this)); +            return; +        case 0x063: +            db_actual = wbx_versionx_sptr(new wbx_version4(this)); +            return; +        default: +            /* We didn't recognize the version of the board... */ +            UHD_THROW_INVALID_CODE_PATH(); +    }  } -//Pre v3 TX gains -static double tx_pga0_gain_to_dac_volts(double &gain){ -    //clip the input -    gain = wbx_tx_gain_ranges["PGA0"].clip(gain); - -    //voltage level constants -    static const double max_volts = 0.5, min_volts = 1.4; -    static const double slope = (max_volts-min_volts)/wbx_tx_gain_ranges["PGA0"].stop(); - -    //calculate the voltage for the aux dac -    double dac_volts = gain*slope + min_volts; -    UHD_LOGV(often) << boost::format( -        "WBX TX Gain: %f dB, dac_volts: %f V" -    ) % gain % dac_volts << std::endl; +wbx_base::~wbx_base(void){ +    /* NOP */ +} -    //the actual gain setting -    gain = (dac_volts - min_volts)/slope; +/*********************************************************************** + * Enables + **********************************************************************/ +void wbx_base::set_rx_enabled(bool enb){ +    this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, +        (enb)? RX_POWER_UP : RX_POWER_DOWN, RX_POWER_UP | RX_POWER_DOWN +    ); +} -    return dac_volts; +void wbx_base::set_tx_enabled(bool enb){ +    db_actual->set_tx_enabled(enb);  } +/*********************************************************************** + * Gain Handling + **********************************************************************/  void wbx_base::set_tx_gain(double gain, const std::string &name){ -    if (is_v3()) { -        assert_has(wbx_v3_tx_gain_ranges.keys(), name, "wbx tx gain name"); -        if(name == "PGA0"){ -            boost::uint16_t io_bits = tx_pga0_gain_to_iobits(gain); -            _tx_gains[name] = gain; - -            //write the new gain to tx gpio outputs -            this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, io_bits, TX_ATTN_MASK); -        } -        else UHD_THROW_INVALID_CODE_PATH(); -    } -    else { -        assert_has(wbx_tx_gain_ranges.keys(), name, "wbx tx gain name"); -        if(name == "PGA0"){ -            double dac_volts = tx_pga0_gain_to_dac_volts(gain); -            _tx_gains[name] = gain; - -            //write the new voltage to the aux dac -            this->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts); -        } -        else UHD_THROW_INVALID_CODE_PATH(); -    } +    db_actual->set_tx_gain(gain, name);  }  void wbx_base::set_rx_gain(double gain, const std::string &name){ @@ -272,183 +114,18 @@ void wbx_base::set_rx_gain(double gain, const std::string &name){  /***********************************************************************   * Tuning   **********************************************************************/ -double wbx_base::set_lo_freq( -    dboard_iface::unit_t unit, -    double target_freq -){ -    UHD_LOGV(often) << boost::format( -        "WBX tune: target frequency %f Mhz" -    ) % (target_freq/1e6) << std::endl; - -    //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) -    static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of -        (0,23) //adf4350_regs_t::PRESCALER_4_5 -        (1,75) //adf4350_regs_t::PRESCALER_8_9 -    ; - -    //map rf divider select output dividers to enums -    static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of -        (1,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) -        (2,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) -        (4,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) -        (8,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) -        (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) -    ; - -    double actual_freq, pfd_freq; -    double ref_freq = this->get_iface()->get_clock_rate(unit); -    int R=0, BS=0, N=0, FRAC=0, MOD=0; -    int RFdiv = 1; -    adf4350_regs_t::reference_divide_by_2_t T     = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; -    adf4350_regs_t::reference_doubler_t     D     = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;     - -    //Reference doubler for 50% duty cycle -    // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 -    if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; - -    //increase RF divider until acceptable VCO frequency -    //start with target_freq*2 because mixer has divide by 2 -    double vco_freq = target_freq*2; -    while (vco_freq < 2.2e9) { -        vco_freq *= 2; -        RFdiv *= 2; -    } - -    //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) -    adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; - -    /* -     * The goal here is to loop though possible R dividers, -     * band select clock dividers, N (int) dividers, and FRAC  -     * (frac) dividers. -     * -     * Calculate the N and F dividers for each set of values. -     * The loop exists when it meets all of the constraints. -     * The resulting loop values are loaded into the registers. -     * -     * from pg.21 -     * -     * f_pfd = f_ref*(1+D)/(R*(1+T)) -     * f_vco = (N + (FRAC/MOD))*f_pfd -     *    N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD -     * f_rf = f_vco/RFdiv) -     * f_actual = f_rf/2 -     */ -    for(R = 1; R <= 1023; R+=1){ -        //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) -        pfd_freq = ref_freq*(1+D)/(R*(1+T)); - -        //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) -        if (pfd_freq > 25e6) continue; - -        //ignore fractional part of tuning -        N = int(std::floor(vco_freq/pfd_freq)); - -        //keep N > minimum int divider requirement -        if (N < prescaler_to_min_int_div[prescaler]) continue; - -        for(BS=1; BS <= 255; BS+=1){ -            //keep the band select frequency at or below 100KHz -            //constraint on band select clock -            if (pfd_freq/BS > 100e3) continue; -            goto done_loop; -        } -    } done_loop: - -    //Fractional-N calculation -    MOD = 4095; //max fractional accuracy -    FRAC = int((vco_freq/pfd_freq - N)*MOD); - -    //Reference divide-by-2 for 50% duty cycle -    // if R even, move one divide by 2 to to regs.reference_divide_by_2 -    if(R % 2 == 0){ -        T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; -        R /= 2; -    } - -    //actual frequency calculation -    actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2); - - -    UHD_LOGV(often) -        << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl - -        << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%d" -            ) % R % BS % N % FRAC % MOD % T % D % RFdiv % get_locked(unit)<< std::endl -        << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" -            ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; - -    //load the register values -    adf4350_regs_t regs; - -    regs.frac_12_bit = FRAC; -    regs.int_16_bit = N; -    regs.mod_12_bit = MOD; -    regs.prescaler = prescaler; -    regs.r_counter_10_bit = R; -    regs.reference_divide_by_2 = T; -    regs.reference_doubler = D; -    regs.band_select_clock_div = BS; -    UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); -    regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; - -    if (unit == dboard_iface::UNIT_RX) { -        freq_range_t rx_lo_5dbm = list_of -            (range_t(0.05e9, 1.4e9)) -        ; - -        freq_range_t rx_lo_2dbm = list_of -            (range_t(1.4e9, 2.2e9)) -        ; - -        if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; - -        if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; - -    } else if (unit == dboard_iface::UNIT_TX) { -        freq_range_t tx_lo_5dbm = list_of -            (range_t(0.05e9, 1.7e9)) -            (range_t(1.9e9, 2.2e9)) -        ; - -        freq_range_t tx_lo_m1dbm = list_of -            (range_t(1.7e9, 1.9e9)) -        ; - -        if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; - -        if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM; - -    } - -    //write the registers -    //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) -    int addr; - -    for(addr=5; addr>=0; addr--){ -        UHD_LOGV(often) << boost::format( -            "WBX SPI Reg (0x%02x): 0x%08x" -        ) % addr % regs.get_reg(addr) << std::endl; -        this->get_iface()->write_spi( -            unit, spi_config_t::EDGE_RISE, -            regs.get_reg(addr), 32 -        ); -    } +freq_range_t wbx_base::get_freq_range(void) { +    return db_actual->get_freq_range(); +} -    //return the actual frequency -    UHD_LOGV(often) << boost::format( -        "WBX tune: actual frequency %f Mhz" -    ) % (actual_freq/1e6) << std::endl; -    return actual_freq; +double wbx_base::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { +    return db_actual->set_lo_freq(unit, target_freq);  }  bool wbx_base::get_locked(dboard_iface::unit_t unit){      return (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0;  } -bool wbx_base::is_v3(void){ -    return get_rx_id().to_uint16() == 0x057; -}  /***********************************************************************   * RX Get and Set @@ -552,84 +229,7 @@ void wbx_base::rx_set(const wax::obj &key_, const wax::obj &val){   * TX Get and Set   **********************************************************************/  void wbx_base::tx_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<subdev_prop_t>()){ -    case SUBDEV_PROP_NAME: -        val = get_tx_id().to_pp_string(); -        return; - -    case SUBDEV_PROP_OTHERS: -        val = prop_names_t(); //empty -        return; - -    case SUBDEV_PROP_GAIN: -        assert_has(_tx_gains.keys(), key.name, "wbx tx gain name"); -        val = _tx_gains[key.name]; -        return; - -    case SUBDEV_PROP_GAIN_RANGE: -        if (is_v3()) { -            assert_has(wbx_v3_tx_gain_ranges.keys(), key.name, "wbx tx gain name"); -            val = wbx_v3_tx_gain_ranges[key.name]; -        } -        else { -            assert_has(wbx_tx_gain_ranges.keys(), key.name, "wbx tx gain name"); -            val = wbx_tx_gain_ranges[key.name]; -        } -        return; - -    case SUBDEV_PROP_GAIN_NAMES: -        if (is_v3()) -            val = prop_names_t(wbx_v3_tx_gain_ranges.keys()); -        else -            val = prop_names_t(wbx_tx_gain_ranges.keys()); -        return; - -    case SUBDEV_PROP_FREQ: -        val = 0.0; -        return; - -    case SUBDEV_PROP_FREQ_RANGE: -        val = freq_range_t(0.0, 0.0, 0.0); -        return; - -    case SUBDEV_PROP_ANTENNA: -        val = std::string(""); -        return; - -    case SUBDEV_PROP_ANTENNA_NAMES: -        val = prop_names_t(1, ""); -        return; - -    case SUBDEV_PROP_CONNECTION: -        val = SUBDEV_CONN_COMPLEX_IQ; -        return; - -    case SUBDEV_PROP_ENABLED: -        val = _tx_enabled; -        return; - -    case SUBDEV_PROP_USE_LO_OFFSET: -        val = false; -        return; - -    case SUBDEV_PROP_SENSOR: -        UHD_ASSERT_THROW(key.name == "lo_locked"); -        val = sensor_value_t("LO", this->get_locked(dboard_iface::UNIT_TX), "locked", "unlocked"); -        return; - -    case SUBDEV_PROP_SENSOR_NAMES: -        val = prop_names_t(1, "lo_locked"); -        return; - -    case SUBDEV_PROP_BANDWIDTH: -        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided -        return; - -    default: UHD_THROW_PROP_GET_ERROR(); -    } +    db_actual->tx_get(key_, val);  }  void wbx_base::tx_set(const wax::obj &key_, const wax::obj &val){ @@ -654,3 +254,5 @@ void wbx_base::tx_set(const wax::obj &key_, const wax::obj &val){      default: UHD_THROW_PROP_SET_ERROR();      }  } + + diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp index 5d33ddce9..57e2a0fe6 100644 --- a/host/lib/usrp/dboard/db_wbx_common.hpp +++ b/host/lib/usrp/dboard/db_wbx_common.hpp @@ -18,14 +18,74 @@  #ifndef INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP  #define INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP -#include "adf4350_regs.hpp" +// Common IO Pins +#define ADF4350_CE      (1 << 3) +#define ADF4350_PDBRF   (1 << 2) +#define ADF4350_MUXOUT  (1 << 1)                // INPUT!!! +#define ADF4351_CE      (1 << 3) +#define ADF4351_PDBRF   (1 << 2) +#define ADF4351_MUXOUT  (1 << 1)                // INPUT!!! + +#define LOCKDET_MASK    (1 << 0)                // INPUT!!! + +// TX IO Pins +#define TX_PUP_5V       (1 << 7)                // enables 5.0V power supply +#define TX_PUP_3V       (1 << 6)                // enables 3.3V supply +#define TXMOD_EN        (1 << 4)                // on UNIT_TX, 1 enables TX Modulator + +// RX IO Pins +#define RX_PUP_5V       (1 << 7)                // enables 5.0V power supply +#define RX_PUP_3V       (1 << 6)                // enables 3.3V supply +#define RXBB_PDB        (1 << 4)                // on UNIT_RX, 1 powers up RX baseband + +// RX Attenuator Pins +#define RX_ATTN_SHIFT   8                       // lsb of RX Attenuator Control +#define RX_ATTN_MASK    (63 << RX_ATTN_SHIFT)      // valid bits of RX Attenuator Control + +// TX Attenuator Pins (v3 only) +#define TX_ATTN_16      (1 << 14) +#define TX_ATTN_8       (1 << 5) +#define TX_ATTN_4       (1 << 4) +#define TX_ATTN_2       (1 << 3) +#define TX_ATTN_1       (1 << 1) +#define TX_ATTN_MASK    (TX_ATTN_16|TX_ATTN_8|TX_ATTN_4|TX_ATTN_2|TX_ATTN_1)      // valid bits of TX Attenuator Control + +// Mixer functions +#define TX_MIXER_ENB    (TXMOD_EN|ADF4350_PDBRF)    // for v3, TXMOD_EN tied to ADF4350_PDBRF rather than separate +#define TX_MIXER_DIS    0 + +#define RX_MIXER_ENB    (RXBB_PDB|ADF4350_PDBRF) +#define RX_MIXER_DIS    0 + +// Power functions +#define TX_POWER_UP     (TX_PUP_5V|TX_PUP_3V) // high enables power supply +#define TX_POWER_DOWN   0 + +#define RX_POWER_UP     (RX_PUP_5V|RX_PUP_3V|ADF4350_CE) // high enables power supply +#define RX_POWER_DOWN   0 + +  #include <uhd/types/dict.hpp>  #include <uhd/types/ranges.hpp> +#include <uhd/utils/log.hpp>  #include <uhd/utils/props.hpp> +#include <uhd/utils/static.hpp>  #include <uhd/usrp/dboard_base.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/math/special_functions/round.hpp>  namespace uhd{ namespace usrp{ + +/*********************************************************************** + * The WBX Common dboard constants + **********************************************************************/ +static const uhd::dict<std::string, gain_range_t> wbx_rx_gain_ranges = boost::assign::map_list_of +    ("PGA0", gain_range_t(0, 31.5, 0.5)); + +  /***********************************************************************   * The WBX dboard base class   **********************************************************************/ @@ -41,11 +101,17 @@ protected:      virtual void set_rx_enabled(bool enb);      virtual void set_tx_enabled(bool enb); -    void rx_get(const wax::obj &key, wax::obj &val); -    void rx_set(const wax::obj &key, const wax::obj &val); +    virtual void rx_get(const wax::obj &key, wax::obj &val); +    virtual void rx_set(const wax::obj &key, const wax::obj &val); -    void tx_get(const wax::obj &key, wax::obj &val); -    void tx_set(const wax::obj &key, const wax::obj &val); +    virtual void tx_get(const wax::obj &key, wax::obj &val); +    virtual void tx_set(const wax::obj &key, const wax::obj &val); + +    /*! +     * Retrieve the frequency range of the board. +     * \return the frequency range as a freq_range_t +     */ +    virtual freq_range_t get_freq_range(void);      /*!       * Set the LO frequency for the particular dboard unit. @@ -57,22 +123,110 @@ protected:      /*!       * Get the lock detect status of the LO. +     * +     * This operation is identical for all versions of the WBX board.       * \param unit which unit rx or tx       * \return true for locked       */      virtual bool get_locked(dboard_iface::unit_t unit); +      /*! -     * Detect if this a v3 WBX -     * \return true for locked +     * Version-agnostic ABC that wraps version-specific implementations of the +     * WBX base daughterboard. +     * +     * This class is an abstract base class, and thus is impossible to +     * instantiate.       */ -    virtual bool is_v3(void); +    class wbx_versionx { +    public: +        wbx_versionx() {} +        ~wbx_versionx(void) {} + +        virtual void set_tx_gain(double gain, const std::string &name) = 0; +        virtual void set_tx_enabled(bool enb) = 0; +        virtual void tx_get(const wax::obj &key, wax::obj &val) = 0; +        virtual freq_range_t get_freq_range(void) = 0; +        virtual double set_lo_freq(dboard_iface::unit_t unit, double target_freq) = 0; +    }; + + +    /*! +     * Version 2 of the WBX Daughterboard +     * +     * Basically the original release of the DB. +     */ +    class wbx_version2 : public wbx_versionx { +    public: +        wbx_version2(wbx_base *_self_wbx_base); +        ~wbx_version2(void); + +        void set_tx_gain(double gain, const std::string &name); +        void set_tx_enabled(bool enb); +        void tx_get(const wax::obj &key, wax::obj &val); +        freq_range_t get_freq_range(void); +        double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + +        /*! This is the registered instance of the wrapper class, wbx_base. */ +        wbx_base *self_base; +    }; + +    /*! +     * Version 3 of the WBX Daughterboard +     * +     * Fixed a problem with the AGC from Version 2. +     */ +    class wbx_version3 : public wbx_versionx { +    public: +        wbx_version3(wbx_base *_self_wbx_base); +        ~wbx_version3(void); + +        void set_tx_gain(double gain, const std::string &name); +        void set_tx_enabled(bool enb); +        void tx_get(const wax::obj &key, wax::obj &val); +        freq_range_t get_freq_range(void); +        double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + +        /*! This is the registered instance of the wrapper class, wbx_base. */ +        wbx_base *self_base; +    }; + +    /*! +     * Version 4 of the WBX Daughterboard +     * +     * Upgrades the Frequnecy Synthensizer from ADF4350 to ADF4351. +     */ +    class wbx_version4 : public wbx_versionx { +    public: +        wbx_version4(wbx_base *_self_wbx_base); +        ~wbx_version4(void); + +        void set_tx_gain(double gain, const std::string &name); +        void set_tx_enabled(bool enb); +        void tx_get(const wax::obj &key, wax::obj &val); +        freq_range_t get_freq_range(void); +        double set_lo_freq(dboard_iface::unit_t unit, double target_freq); + +        /*! This is the registered instance of the wrapper class, wbx_base. */ +        wbx_base *self_base; +    }; + +    /*! +     * Handle to the version-specific implementation of the WBX. +     * +     * Since many of this class's functions are dependent on the version of the +     * WBX board, this class will instantiate an object of the appropriate +     * wbx_version_* subclass, and invoke any relevant functions through that +     * object.  This pointer is set to the proper object at construction time. +     */ +    typedef boost::shared_ptr<wbx_versionx> wbx_versionx_sptr; +    wbx_versionx_sptr db_actual; -private:      uhd::dict<std::string, double> _tx_gains, _rx_gains;      bool _rx_enabled, _tx_enabled;  }; +  }} //namespace uhd::usrp  #endif /* INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP */ diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp index 990bacbc8..789146854 100644 --- a/host/lib/usrp/dboard/db_wbx_simple.cpp +++ b/host/lib/usrp/dboard/db_wbx_simple.cpp @@ -32,15 +32,17 @@ using namespace uhd;  using namespace uhd::usrp;  using namespace boost::assign; +  /***********************************************************************   * The WBX Simple dboard constants   **********************************************************************/ -static const freq_range_t wbx_freq_range(68.75e6, 2.2e9); -  static const prop_names_t wbx_tx_antennas = list_of("TX/RX");  static const prop_names_t wbx_rx_antennas = list_of("TX/RX")("RX2"); +//this will get set by a version-specific function call +static freq_range_t wbx_freq_range(0.0, 0.0); +  /***********************************************************************   * The WBX simple implementation   **********************************************************************/ @@ -72,13 +74,19 @@ static dboard_base::sptr make_wbx_simple(dboard_base::ctor_args_t args){      return dboard_base::sptr(new wbx_simple(args));  } +/*********************************************************************** + * ID Numbers for WBX daughterboard combinations. + **********************************************************************/  UHD_STATIC_BLOCK(reg_wbx_simple_dboards){      dboard_manager::register_dboard(0x0053, 0x0052, &make_wbx_simple, "WBX");      dboard_manager::register_dboard(0x0053, 0x004f, &make_wbx_simple, "WBX + Simple GDB");      dboard_manager::register_dboard(0x0057, 0x0056, &make_wbx_simple, "WBX v3");      dboard_manager::register_dboard(0x0057, 0x004f, &make_wbx_simple, "WBX v3 + Simple GDB"); +    dboard_manager::register_dboard(0x0063, 0x0062, &make_wbx_simple, "WBX v4"); +    dboard_manager::register_dboard(0x0063, 0x004f, &make_wbx_simple, "WBX v4 + Simple GDB");  } +  /***********************************************************************   * Structors   **********************************************************************/ @@ -100,6 +108,9 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){      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); +    //get the frequency range of our WBX db +    wbx_freq_range = this->get_freq_range(); +      //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); @@ -149,11 +160,19 @@ void wbx_simple::rx_get(const wax::obj &key_, wax::obj &val){      //handle the get request conditioned on the key      switch(key.as<subdev_prop_t>()){      case SUBDEV_PROP_NAME: -        if (is_v3()) -            val = std::string("WBX v3 RX + Simple GDB"); -        else +        switch(get_rx_id().to_uint16()) { +        case 0x053:              val = std::string("WBX RX + Simple GDB"); -        return; +            return; +        case 0x057: +            val = std::string("WBX v3 RX + Simple GDB"); +            return; +        case 0x063: +            val = std::string("WBX v4 RX + Simple GDB"); +            return; +        default: +            UHD_THROW_INVALID_CODE_PATH(); +        }      case SUBDEV_PROP_FREQ:          val = _rx_lo_freq; @@ -206,11 +225,19 @@ void wbx_simple::tx_get(const wax::obj &key_, wax::obj &val){      //handle the get request conditioned on the key      switch(key.as<subdev_prop_t>()){      case SUBDEV_PROP_NAME: -        if (is_v3()) -            val = std::string("WBX v3 TX + Simple GDB"); -        else +        switch(get_rx_id().to_uint16()) { +        case 0x053:              val = std::string("WBX TX + Simple GDB"); -        return; +            return; +        case 0x057: +            val = std::string("WBX v3 TX + Simple GDB"); +            return; +        case 0x063: +            val = std::string("WBX v4 TX + Simple GDB"); +            return; +        default: +            UHD_THROW_INVALID_CODE_PATH(); +        }      case SUBDEV_PROP_FREQ:          val = _tx_lo_freq; diff --git a/host/lib/usrp/dboard/db_wbx_version2.cpp b/host/lib/usrp/dboard/db_wbx_version2.cpp new file mode 100644 index 000000000..0b57a6e2d --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_version2.cpp @@ -0,0 +1,394 @@ +// +// Copyright 2011 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 <http://www.gnu.org/licenses/>. +// + +#include "db_wbx_common.hpp" +#include "adf4350_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * WBX Version 2 Constants + **********************************************************************/ +static const uhd::dict<std::string, gain_range_t> wbx_v2_tx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 25, 0.05)) +; + +static const freq_range_t wbx_v2_freq_range(68.75e6, 2.2e9); + +/*********************************************************************** + * Gain-related functions + **********************************************************************/ +static double tx_pga0_gain_to_dac_volts(double &gain){ +    //clip the input +    gain = wbx_v2_tx_gain_ranges["PGA0"].clip(gain); + +    //voltage level constants +    static const double max_volts = 0.5, min_volts = 1.4; +    static const double slope = (max_volts-min_volts)/wbx_v2_tx_gain_ranges["PGA0"].stop(); + +    //calculate the voltage for the aux dac +    double dac_volts = gain*slope + min_volts; + +    UHD_LOGV(often) << boost::format( +        "WBX TX Gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    //the actual gain setting +    gain = (dac_volts - min_volts)/slope; + +    return dac_volts; +} + + +/*********************************************************************** + * WBX Version 2 Implementation + **********************************************************************/ +wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { +    //register our handle on the primary wbx_base instance +    self_base = _self_wbx_base; + +    //enable the clocks that we need +    self_base->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); +    self_base->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set attenuator control bits +    int v2_iobits = ADF4350_CE; +    int v2_tx_mod = TXMOD_EN|ADF4350_PDBRF; + +    //set the gpio directions and atr controls +    self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v2_tx_mod); +    self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4350_PDBRF); +    self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v2_tx_mod|v2_iobits); +    self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4350_CE|RXBB_PDB|ADF4350_PDBRF|RX_ATTN_MASK); + +    //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) +    self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); +    self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); +    self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); +    self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_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); + +    BOOST_FOREACH(const std::string &name, wbx_v2_tx_gain_ranges.keys()) { +        set_tx_gain(wbx_v2_tx_gain_ranges[name].start(), name); +    } + +    BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()) { +        self_base->set_rx_gain(wbx_rx_gain_ranges[name].start(), name); +    } + +    self_base->set_rx_enabled(false); +    set_tx_enabled(false); +} + +wbx_base::wbx_version2::~wbx_version2(void){ +    /* NOP */ +} + +/*********************************************************************** + * Enables + **********************************************************************/ + +void wbx_base::wbx_version2::set_tx_enabled(bool enb){ +    self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, +        (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | ADF4350_CE); +} + + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +void wbx_base::wbx_version2::set_tx_gain(double gain, const std::string &name){ +    assert_has(wbx_v2_tx_gain_ranges.keys(), name, "wbx tx gain name"); +    if(name == "PGA0"){ +        double dac_volts = tx_pga0_gain_to_dac_volts(gain); +        self_base->_tx_gains[name] = gain; + +        //write the new voltage to the aux dac +        self_base->get_iface()->write_aux_dac(dboard_iface::UNIT_TX, dboard_iface::AUX_DAC_A, dac_volts); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +freq_range_t wbx_base::wbx_version2::get_freq_range(void) { +    return wbx_v2_freq_range; +} + +double wbx_base::wbx_version2::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { +    UHD_LOGV(often) << boost::format( +        "WBX tune: target frequency %f Mhz" +    ) % (target_freq/1e6) << std::endl; + +    //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) +    static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of +        (0,23) //adf4350_regs_t::PRESCALER_4_5 +        (1,75) //adf4350_regs_t::PRESCALER_8_9 +    ; + +    //map rf divider select output dividers to enums +    static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of +        (1,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) +        (2,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) +        (4,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) +        (8,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) +        (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) +    ; + +    double actual_freq, pfd_freq; +    double ref_freq = self_base->get_iface()->get_clock_rate(unit); +    int R=0, BS=0, N=0, FRAC=0, MOD=0; +    int RFdiv = 1; +    adf4350_regs_t::reference_divide_by_2_t T     = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; +    adf4350_regs_t::reference_doubler_t     D     = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;     + +    //Reference doubler for 50% duty cycle +    // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 +    if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; + +    //increase RF divider until acceptable VCO frequency +    //start with target_freq*2 because mixer has divide by 2 +    double vco_freq = target_freq*2; +    while (vco_freq < 2.2e9) { +        vco_freq *= 2; +        RFdiv *= 2; +    } + +    //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) +    adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + +    /* +     * The goal here is to loop though possible R dividers, +     * band select clock dividers, N (int) dividers, and FRAC  +     * (frac) dividers. +     * +     * Calculate the N and F dividers for each set of values. +     * The loop exists when it meets all of the constraints. +     * The resulting loop values are loaded into the registers. +     * +     * from pg.21 +     * +     * f_pfd = f_ref*(1+D)/(R*(1+T)) +     * f_vco = (N + (FRAC/MOD))*f_pfd +     *    N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD +     * f_rf = f_vco/RFdiv) +     * f_actual = f_rf/2 +     */ +    for(R = 1; R <= 1023; R+=1){ +        //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) +        pfd_freq = ref_freq*(1+D)/(R*(1+T)); + +        //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) +        if (pfd_freq > 25e6) continue; + +        //ignore fractional part of tuning +        N = int(std::floor(vco_freq/pfd_freq)); + +        //keep N > minimum int divider requirement +        if (N < prescaler_to_min_int_div[prescaler]) continue; + +        for(BS=1; BS <= 255; BS+=1){ +            //keep the band select frequency at or below 100KHz +            //constraint on band select clock +            if (pfd_freq/BS > 100e3) continue; +            goto done_loop; +        } +    } done_loop: + +    //Fractional-N calculation +    MOD = 4095; //max fractional accuracy +    FRAC = int((vco_freq/pfd_freq - N)*MOD); + +    //Reference divide-by-2 for 50% duty cycle +    // if R even, move one divide by 2 to to regs.reference_divide_by_2 +    if(R % 2 == 0){ +        T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; +        R /= 2; +    } + +    //actual frequency calculation +    actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2); + + +    UHD_LOGV(often) +        << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + +        << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%d" +            ) % R % BS % N % FRAC % MOD % T % D % RFdiv % self_base->get_locked(unit)<< std::endl +        << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" +            ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + +    //load the register values +    adf4350_regs_t regs; + +    regs.frac_12_bit = FRAC; +    regs.int_16_bit = N; +    regs.mod_12_bit = MOD; +    regs.prescaler = prescaler; +    regs.r_counter_10_bit = R; +    regs.reference_divide_by_2 = T; +    regs.reference_doubler = D; +    regs.band_select_clock_div = BS; +    UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); +    regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + +    if (unit == dboard_iface::UNIT_RX) { +        freq_range_t rx_lo_5dbm = list_of +            (range_t(0.05e9, 1.4e9)) +        ; + +        freq_range_t rx_lo_2dbm = list_of +            (range_t(1.4e9, 2.2e9)) +        ; + +        if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + +        if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; + +    } else if (unit == dboard_iface::UNIT_TX) { +        freq_range_t tx_lo_5dbm = list_of +            (range_t(0.05e9, 1.7e9)) +            (range_t(1.9e9, 2.2e9)) +        ; + +        freq_range_t tx_lo_m1dbm = list_of +            (range_t(1.7e9, 1.9e9)) +        ; + +        if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + +        if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM; + +    } + +    //write the registers +    //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) +    int addr; + +    for(addr=5; addr>=0; addr--){ +        UHD_LOGV(often) << boost::format( +            "WBX SPI Reg (0x%02x): 0x%08x" +        ) % addr % regs.get_reg(addr) << std::endl; +        self_base->get_iface()->write_spi( +            unit, spi_config_t::EDGE_RISE, +            regs.get_reg(addr), 32 +        ); +    } + +    //return the actual frequency +    UHD_LOGV(often) << boost::format( +        "WBX tune: actual frequency %f Mhz" +    ) % (actual_freq/1e6) << std::endl; +    return actual_freq; +} + + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void wbx_base::wbx_version2::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = self_base->get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(self_base->_tx_gains.keys(), key.name, "wbx tx gain name"); +        val = self_base->_tx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(wbx_v2_tx_gain_ranges.keys(), key.name, "wbx tx gain name"); +        val = wbx_v2_tx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(wbx_v2_tx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = 0.0; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(0.0, 0.0, 0.0); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = self_base->_tx_enabled; +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", self_base->get_locked(dboard_iface::UNIT_TX), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + diff --git a/host/lib/usrp/dboard/db_wbx_version3.cpp b/host/lib/usrp/dboard/db_wbx_version3.cpp new file mode 100644 index 000000000..3c5cdf5ba --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_version3.cpp @@ -0,0 +1,401 @@ +// +// Copyright 2011 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 <http://www.gnu.org/licenses/>. +// + +#include "db_wbx_common.hpp" +#include "adf4350_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * WBX Version 3 Constants + **********************************************************************/ +static const uhd::dict<std::string, gain_range_t> wbx_v3_tx_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 31, 1.0)) +; + +static const freq_range_t wbx_v3_freq_range(68.75e6, 2.2e9); + +/*********************************************************************** + * Gain-related functions + **********************************************************************/ +static int tx_pga0_gain_to_iobits(double &gain){ +    //clip the input +    gain = wbx_v3_tx_gain_ranges["PGA0"].clip(gain); + +    //convert to attenuation +    double attn = wbx_v3_tx_gain_ranges["PGA0"].stop() - gain; + +    //calculate the attenuation +    int attn_code = boost::math::iround(attn); +    int iobits = ( +            (attn_code & 16 ? 0 : TX_ATTN_16) | +            (attn_code &  8 ? 0 : TX_ATTN_8) | +            (attn_code &  4 ? 0 : TX_ATTN_4) | +            (attn_code &  2 ? 0 : TX_ATTN_2) | +            (attn_code &  1 ? 0 : TX_ATTN_1)  +        ) & TX_ATTN_MASK; + +    UHD_LOGV(often) << boost::format( +        "WBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" +    ) % attn % attn_code % (iobits & TX_ATTN_MASK) % TX_ATTN_MASK << std::endl; + +    //the actual gain setting +    gain = wbx_v3_tx_gain_ranges["PGA0"].stop() - double(attn_code); + +    return iobits; +} + + +/*********************************************************************** + * WBX Common Implementation + **********************************************************************/ +wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { +    //register our handle on the primary wbx_base instance +    self_base = _self_wbx_base; + +    //enable the clocks that we need +    self_base->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); +    self_base->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set attenuator control bits +    int v3_iobits = TX_ATTN_MASK; +    int v3_tx_mod = ADF4350_PDBRF; + +    //set the gpio directions and atr controls +    self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, v3_tx_mod); +    self_base->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXBB_PDB|ADF4350_PDBRF); +    self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, TX_PUP_5V|TX_PUP_3V|v3_tx_mod|v3_iobits); +    self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF4350_CE|RXBB_PDB|ADF4350_PDBRF|RX_ATTN_MASK); + +    //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) +    self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); +    self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY,     v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); +    self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY,     v3_tx_mod, TX_MIXER_DIS | v3_tx_mod); +    self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v3_tx_mod, TX_MIXER_DIS | v3_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); + +    //set some default values +    BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){ +        set_tx_gain(wbx_v3_tx_gain_ranges[name].start(), name); +    } + +    BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ +        self_base->set_rx_gain(wbx_rx_gain_ranges[name].start(), name); +    } + +    self_base->set_rx_enabled(false); +    set_tx_enabled(false); +} + +wbx_base::wbx_version3::~wbx_version3(void){ +    /* NOP */ +} + + +/*********************************************************************** + * Enables + **********************************************************************/ +void wbx_base::wbx_version3::set_tx_enabled(bool enb){ +    self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, +        (enb)? TX_POWER_UP | ADF4350_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0); +} + + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +void wbx_base::wbx_version3::set_tx_gain(double gain, const std::string &name){ +    assert_has(wbx_v3_tx_gain_ranges.keys(), name, "wbx tx gain name"); +    if(name == "PGA0"){ +        boost::uint16_t io_bits = tx_pga0_gain_to_iobits(gain); +        self_base->_tx_gains[name] = gain; + +        //write the new gain to tx gpio outputs +        self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, io_bits, TX_ATTN_MASK); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +freq_range_t wbx_base::wbx_version3::get_freq_range(void) { +    return wbx_v3_freq_range; +} + +double wbx_base::wbx_version3::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { +    UHD_LOGV(often) << boost::format( +        "WBX tune: target frequency %f Mhz" +    ) % (target_freq/1e6) << std::endl; + +    //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) +    static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of +        (0,23) //adf4350_regs_t::PRESCALER_4_5 +        (1,75) //adf4350_regs_t::PRESCALER_8_9 +    ; + +    //map rf divider select output dividers to enums +    static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of +        (1,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) +        (2,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) +        (4,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) +        (8,  adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) +        (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) +    ; + +    double actual_freq, pfd_freq; +    double ref_freq = self_base->get_iface()->get_clock_rate(unit); +    int R=0, BS=0, N=0, FRAC=0, MOD=0; +    int RFdiv = 1; +    adf4350_regs_t::reference_divide_by_2_t T     = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; +    adf4350_regs_t::reference_doubler_t     D     = adf4350_regs_t::REFERENCE_DOUBLER_DISABLED;     + +    //Reference doubler for 50% duty cycle +    // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 +    if(ref_freq <= 12.5e6) D = adf4350_regs_t::REFERENCE_DOUBLER_ENABLED; + +    //increase RF divider until acceptable VCO frequency +    //start with target_freq*2 because mixer has divide by 2 +    double vco_freq = target_freq*2; +    while (vco_freq < 2.2e9) { +        vco_freq *= 2; +        RFdiv *= 2; +    } + +    //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) +    adf4350_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + +    /* +     * The goal here is to loop though possible R dividers, +     * band select clock dividers, N (int) dividers, and FRAC  +     * (frac) dividers. +     * +     * Calculate the N and F dividers for each set of values. +     * The loop exists when it meets all of the constraints. +     * The resulting loop values are loaded into the registers. +     * +     * from pg.21 +     * +     * f_pfd = f_ref*(1+D)/(R*(1+T)) +     * f_vco = (N + (FRAC/MOD))*f_pfd +     *    N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD +     * f_rf = f_vco/RFdiv) +     * f_actual = f_rf/2 +     */ +    for(R = 1; R <= 1023; R+=1){ +        //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) +        pfd_freq = ref_freq*(1+D)/(R*(1+T)); + +        //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) +        if (pfd_freq > 25e6) continue; + +        //ignore fractional part of tuning +        N = int(std::floor(vco_freq/pfd_freq)); + +        //keep N > minimum int divider requirement +        if (N < prescaler_to_min_int_div[prescaler]) continue; + +        for(BS=1; BS <= 255; BS+=1){ +            //keep the band select frequency at or below 100KHz +            //constraint on band select clock +            if (pfd_freq/BS > 100e3) continue; +            goto done_loop; +        } +    } done_loop: + +    //Fractional-N calculation +    MOD = 4095; //max fractional accuracy +    FRAC = int((vco_freq/pfd_freq - N)*MOD); + +    //Reference divide-by-2 for 50% duty cycle +    // if R even, move one divide by 2 to to regs.reference_divide_by_2 +    if(R % 2 == 0){ +        T = adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; +        R /= 2; +    } + +    //actual frequency calculation +    actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2); + + +    UHD_LOGV(often) +        << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + +        << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%d" +            ) % R % BS % N % FRAC % MOD % T % D % RFdiv % self_base->get_locked(unit)<< std::endl +        << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" +            ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + +    //load the register values +    adf4350_regs_t regs; + +    regs.frac_12_bit = FRAC; +    regs.int_16_bit = N; +    regs.mod_12_bit = MOD; +    regs.prescaler = prescaler; +    regs.r_counter_10_bit = R; +    regs.reference_divide_by_2 = T; +    regs.reference_doubler = D; +    regs.band_select_clock_div = BS; +    UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); +    regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + +    if (unit == dboard_iface::UNIT_RX) { +        freq_range_t rx_lo_5dbm = list_of +            (range_t(0.05e9, 1.4e9)) +        ; + +        freq_range_t rx_lo_2dbm = list_of +            (range_t(1.4e9, 2.2e9)) +        ; + +        if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + +        if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; + +    } else if (unit == dboard_iface::UNIT_TX) { +        freq_range_t tx_lo_5dbm = list_of +            (range_t(0.05e9, 1.7e9)) +            (range_t(1.9e9, 2.2e9)) +        ; + +        freq_range_t tx_lo_m1dbm = list_of +            (range_t(1.7e9, 1.9e9)) +        ; + +        if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; + +        if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4350_regs_t::OUTPUT_POWER_M1DBM; + +    } + +    //write the registers +    //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) +    int addr; + +    for(addr=5; addr>=0; addr--){ +        UHD_LOGV(often) << boost::format( +            "WBX SPI Reg (0x%02x): 0x%08x" +        ) % addr % regs.get_reg(addr) << std::endl; +        self_base->get_iface()->write_spi( +            unit, spi_config_t::EDGE_RISE, +            regs.get_reg(addr), 32 +        ); +    } + +    //return the actual frequency +    UHD_LOGV(often) << boost::format( +        "WBX tune: actual frequency %f Mhz" +    ) % (actual_freq/1e6) << std::endl; +    return actual_freq; +} + + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void wbx_base::wbx_version3::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = self_base->get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(self_base->_tx_gains.keys(), key.name, "wbx tx gain name"); +        val = self_base->_tx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(wbx_v3_tx_gain_ranges.keys(), key.name, "wbx tx gain name"); +        val = wbx_v3_tx_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(wbx_v3_tx_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = 0.0; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(0.0, 0.0, 0.0); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = self_base->_tx_enabled; +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", self_base->get_locked(dboard_iface::UNIT_TX), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + diff --git a/host/lib/usrp/dboard/db_wbx_version4.cpp b/host/lib/usrp/dboard/db_wbx_version4.cpp new file mode 100644 index 000000000..b5104c9f1 --- /dev/null +++ b/host/lib/usrp/dboard/db_wbx_version4.cpp @@ -0,0 +1,404 @@ +// +// Copyright 2011 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 <http://www.gnu.org/licenses/>. +// + +#include "db_wbx_common.hpp" +#include "adf4351_regs.hpp" +#include <uhd/utils/log.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/utils/assert_has.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + + +/*********************************************************************** + * WBX Version 3 Constants + **********************************************************************/ +static const uhd::dict<std::string, gain_range_t> wbx_v4_gain_ranges = map_list_of +    ("PGA0", gain_range_t(0, 31, 1.0)) +; + +static const freq_range_t wbx_v4_freq_range(50.0e6, 2.2e9); + + +/*********************************************************************** + * Gain-related functions + **********************************************************************/ +static int tx_pga0_gain_to_iobits(double &gain){ +    //clip the input +    gain = wbx_v4_gain_ranges["PGA0"].clip(gain); + +    //convert to attenuation +    double attn = wbx_v4_gain_ranges["PGA0"].stop() - gain; + +    //calculate the attenuation +    int attn_code = boost::math::iround(attn); +    int iobits = ( +            (attn_code & 16 ? 0 : TX_ATTN_16) | +            (attn_code &  8 ? 0 : TX_ATTN_8) | +            (attn_code &  4 ? 0 : TX_ATTN_4) | +            (attn_code &  2 ? 0 : TX_ATTN_2) | +            (attn_code &  1 ? 0 : TX_ATTN_1)  +        ) & TX_ATTN_MASK; + +    UHD_LOGV(often) << boost::format( +        "WBX TX Attenuation: %f dB, Code: %d, IO Bits %x, Mask: %x" +    ) % attn % attn_code % (iobits & TX_ATTN_MASK) % TX_ATTN_MASK << std::endl; + +    //the actual gain setting +    gain = wbx_v4_gain_ranges["PGA0"].stop() - double(attn_code); + +    return iobits; +} + + +/*********************************************************************** + * 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; + +    //enable the clocks that we need +    self_base->get_iface()->set_clock_enabled(dboard_iface::UNIT_TX, true); +    self_base->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //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); +    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) +    self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE,        v4_tx_mod, 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_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_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_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); + +    //set some default values +    BOOST_FOREACH(const std::string &name, wbx_v4_gain_ranges.keys()){ +        set_tx_gain(wbx_v4_gain_ranges[name].start(), name); +    } + +    BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ +        self_base->set_rx_gain(wbx_rx_gain_ranges[name].start(), name); +    } + +    self_base->set_rx_enabled(false); +    set_tx_enabled(false); +} + +wbx_base::wbx_version4::~wbx_version4(void){ +    /* NOP */ +} + + +/*********************************************************************** + * Enables + **********************************************************************/ +void wbx_base::wbx_version4::set_tx_enabled(bool enb) { +    self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, +        (enb)? TX_POWER_UP | ADF4351_CE : TX_POWER_DOWN, TX_POWER_UP | TX_POWER_DOWN | 0); +} + + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +void wbx_base::wbx_version4::set_tx_gain(double gain, const std::string &name) { +    assert_has(wbx_v4_gain_ranges.keys(), name, "wbx tx gain name"); +    if(name == "PGA0"){ +        boost::uint16_t io_bits = tx_pga0_gain_to_iobits(gain); +        self_base->_tx_gains[name] = gain; + +        //write the new gain to tx gpio outputs +        self_base->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, io_bits, TX_ATTN_MASK); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +} + + +/*********************************************************************** + * Tuning + **********************************************************************/ +freq_range_t wbx_base::wbx_version4::get_freq_range(void) { +    return wbx_v4_freq_range; +} + +double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double target_freq) { +    UHD_LOGV(often) << boost::format( +        "WBX tune: target frequency %f Mhz" +    ) % (target_freq/1e6) << std::endl; + +    //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) +    static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of +        (0,23) //adf4351_regs_t::PRESCALER_4_5 +        (1,75) //adf4351_regs_t::PRESCALER_8_9 +    ; + +    //map rf divider select output dividers to enums +    static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of +        (1,  adf4351_regs_t::RF_DIVIDER_SELECT_DIV1) +        (2,  adf4351_regs_t::RF_DIVIDER_SELECT_DIV2) +        (4,  adf4351_regs_t::RF_DIVIDER_SELECT_DIV4) +        (8,  adf4351_regs_t::RF_DIVIDER_SELECT_DIV8) +        (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16) +        (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32) +        (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) +    ; + +    double actual_freq, pfd_freq; +    double ref_freq = self_base->get_iface()->get_clock_rate(unit); +    int R=0, BS=0, N=0, FRAC=0, MOD=0; +    int RFdiv = 1; +    adf4351_regs_t::reference_divide_by_2_t T     = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; +    adf4351_regs_t::reference_doubler_t     D     = adf4351_regs_t::REFERENCE_DOUBLER_DISABLED;     + +    //Reference doubler for 50% duty cycle +    // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 +    if(ref_freq <= 12.5e6) D = adf4351_regs_t::REFERENCE_DOUBLER_ENABLED; + +    //increase RF divider until acceptable VCO frequency +    //start with target_freq*2 because mixer has divide by 2 +    double vco_freq = target_freq*2; +    while (vco_freq < 2.2e9) { +        vco_freq *= 2; +        RFdiv *= 2; +    } + +    //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) +    adf4351_regs_t::prescaler_t prescaler = vco_freq > 3e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; + +    /* +     * The goal here is to loop though possible R dividers, +     * band select clock dividers, N (int) dividers, and FRAC  +     * (frac) dividers. +     * +     * Calculate the N and F dividers for each set of values. +     * The loop exits when it meets all of the constraints. +     * The resulting loop values are loaded into the registers. +     * +     * from pg.21 +     * +     * f_pfd = f_ref*(1+D)/(R*(1+T)) +     * f_vco = (N + (FRAC/MOD))*f_pfd +     *    N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD +     * f_rf = f_vco/RFdiv) +     * f_actual = f_rf/2 +     */ +    for(R = 1; R <= 1023; R+=1){ +        //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) +        pfd_freq = ref_freq*(1+D)/(R*(1+T)); + +        //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) +        if (pfd_freq > 25e6) continue; + +        //ignore fractional part of tuning +        N = int(std::floor(vco_freq/pfd_freq)); + +        //keep N > minimum int divider requirement +        if (N < prescaler_to_min_int_div[prescaler]) continue; + +        for(BS=1; BS <= 255; BS+=1){ +            //keep the band select frequency at or below 100KHz +            //constraint on band select clock +            if (pfd_freq/BS > 100e3) continue; +            goto done_loop; +        } +    } done_loop: + +    //Fractional-N calculation +    MOD = 4095; //max fractional accuracy +    FRAC = int((vco_freq/pfd_freq - N)*MOD); + +    //Reference divide-by-2 for 50% duty cycle +    // if R even, move one divide by 2 to to regs.reference_divide_by_2 +    if(R % 2 == 0){ +        T = adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; +        R /= 2; +    } + +    //actual frequency calculation +    actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv/2); + + +    UHD_LOGV(often) +        << boost::format("WBX Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f") % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + +        << boost::format("WBX tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, LD=%d" +            ) % R % BS % N % FRAC % MOD % T % D % RFdiv % self_base->get_locked(unit)<< std::endl +        << boost::format("WBX Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" +            ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + +    //load the register values +    adf4351_regs_t regs; + +    regs.frac_12_bit = FRAC; +    regs.int_16_bit = N; +    regs.mod_12_bit = MOD; +    regs.prescaler = prescaler; +    regs.r_counter_10_bit = R; +    regs.reference_divide_by_2 = T; +    regs.reference_doubler = D; +    regs.band_select_clock_div = BS; +    UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); +    regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + +    if (unit == dboard_iface::UNIT_RX) { +        freq_range_t rx_lo_5dbm = list_of +            (range_t(0.05e9, 1.4e9)) +        ; + +        freq_range_t rx_lo_2dbm = list_of +            (range_t(1.4e9, 2.2e9)) +        ; + +        if (actual_freq == rx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; + +        if (actual_freq == rx_lo_2dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM; + +    } else if (unit == dboard_iface::UNIT_TX) { +        freq_range_t tx_lo_5dbm = list_of +            (range_t(0.05e9, 1.7e9)) +            (range_t(1.9e9, 2.2e9)) +        ; + +        freq_range_t tx_lo_m1dbm = list_of +            (range_t(1.7e9, 1.9e9)) +        ; + +        if (actual_freq == tx_lo_5dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; + +        if (actual_freq == tx_lo_m1dbm.clip(actual_freq)) regs.output_power = adf4351_regs_t::OUTPUT_POWER_M1DBM; + +    } + +    //write the registers +    //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) +    int addr; + +    for(addr=5; addr>=0; addr--){ +        UHD_LOGV(often) << boost::format( +            "WBX SPI Reg (0x%02x): 0x%08x" +        ) % addr % regs.get_reg(addr) << std::endl; +        self_base->get_iface()->write_spi( +            unit, spi_config_t::EDGE_RISE, +            regs.get_reg(addr), 32 +        ); +    } + +    //return the actual frequency +    UHD_LOGV(often) << boost::format( +        "WBX tune: actual frequency %f Mhz" +    ) % (actual_freq/1e6) << std::endl; +    return actual_freq; +} + + +/*********************************************************************** + * TX Get and Set + **********************************************************************/ +void wbx_base::wbx_version4::tx_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<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = self_base->get_tx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(self_base->_tx_gains.keys(), key.name, "wbx tx gain name"); +        val = self_base->_tx_gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(wbx_v4_gain_ranges.keys(), key.name, "wbx tx gain name"); +        val = wbx_v4_gain_ranges[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(wbx_v4_gain_ranges.keys()); +        return; + +    case SUBDEV_PROP_FREQ: +        val = 0.0; +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = freq_range_t(0.0, 0.0, 0.0); +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = prop_names_t(1, ""); +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_ENABLED: +        val = self_base->_tx_enabled; +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_SENSOR: +        UHD_ASSERT_THROW(key.name == "lo_locked"); +        val = sensor_value_t("LO", self_base->get_locked(dboard_iface::UNIT_TX), "locked", "unlocked"); +        return; + +    case SUBDEV_PROP_SENSOR_NAMES: +        val = prop_names_t(1, "lo_locked"); +        return; + +    case SUBDEV_PROP_BANDWIDTH: +        val = 2*20.0e6; //20MHz low-pass, we want complex double-sided +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + | 
