# # Copyright 2004,2005 Free Software Foundation, Inc. # # This file is part of GNU Radio # # GNU Radio 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, or (at your option) # any later version. # # GNU Radio 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 GNU Radio; see the file COPYING. If not, write to # the Free Software Foundation, Inc., 51 Franklin Street, # Boston, MA 02110-1301, USA. # from usrpm import usrp_prims from usrpm import usrp_dbid from gnuradio import usrp1 # usrp Rev 1 and later from gnuradio import gru from usrpm.usrp_fpga_regs import * FPGA_MODE_NORMAL = usrp1.FPGA_MODE_NORMAL FPGA_MODE_LOOPBACK = usrp1.FPGA_MODE_LOOPBACK FPGA_MODE_COUNTING = usrp1.FPGA_MODE_COUNTING SPI_FMT_xSB_MASK = usrp1.SPI_FMT_xSB_MASK SPI_FMT_LSB = usrp1.SPI_FMT_LSB SPI_FMT_MSB = usrp1.SPI_FMT_MSB SPI_FMT_HDR_MASK = usrp1.SPI_FMT_HDR_MASK SPI_FMT_HDR_0 = usrp1.SPI_FMT_HDR_0 SPI_FMT_HDR_1 = usrp1.SPI_FMT_HDR_1 SPI_FMT_HDR_2 = usrp1.SPI_FMT_HDR_2 SPI_ENABLE_FPGA = usrp1.SPI_ENABLE_FPGA SPI_ENABLE_CODEC_A = usrp1.SPI_ENABLE_CODEC_A SPI_ENABLE_CODEC_B = usrp1.SPI_ENABLE_CODEC_B SPI_ENABLE_reserved = usrp1.SPI_ENABLE_reserved SPI_ENABLE_TX_A = usrp1.SPI_ENABLE_TX_A SPI_ENABLE_RX_A = usrp1.SPI_ENABLE_RX_A SPI_ENABLE_TX_B = usrp1.SPI_ENABLE_TX_B SPI_ENABLE_RX_B = usrp1.SPI_ENABLE_RX_B # Import all the daughterboard classes we know about. # This hooks them into the auto-instantiation framework. import db_instantiator import db_basic import db_dbs_rx import db_flexrf import db_flexrf_mimo import db_tv_rx import db_wbx def _look_for_usrp(which): """ Try to open the specified usrp. @param which: int >= 0 specifying which USRP to open @type which: int @return: Returns version number, or raises RuntimeError @rtype: int """ d = usrp_prims.usrp_find_device(which) if not d: raise RuntimeError, "Unable to find USRP #%d" % (which,) return usrp_prims.usrp_hw_rev(d) def _ensure_rev2(which): v = _look_for_usrp(which) if not v in (2, 4): raise RuntimeError, "Sorry, unsupported USRP revision (rev=%d)" % (v,) class tune_result(object): """ Container for intermediate tuning information. """ def __init__(self, baseband_freq, dxc_freq, residual_freq, inverted): self.baseband_freq = baseband_freq self.dxc_freq = dxc_freq self.residual_freq = residual_freq self.inverted = inverted def tune(u, chan, subdev, target_freq): """ Set the center frequency we're interested in. @param u: instance of usrp.source_* or usrp.sink_* @param chan: DDC/DUC channel @type chan: int @param subdev: daughterboard subdevice @param target_freq: frequency in Hz @returns False if failure else tune_result Tuning is a two step process. First we ask the front-end to tune as close to the desired frequency as it can. Then we use the result of that operation and our target_frequency to determine the value for the digital down converter. """ # Does this usrp instance do Tx or Rx? rx_p = True try: u.rx_freq except AttributeError: rx_p = False ok, baseband_freq = subdev.set_freq(target_freq) dxc_freq, inverted = calc_dxc_freq(target_freq, baseband_freq, u.converter_rate()) # If the spectrum is inverted, and the daughterboard doesn't do # quadrature downconversion, we can fix the inversion by flipping the # sign of the dxc_freq... (This only happens using the basic_rx board) if subdev.spectrum_inverted(): inverted = not(inverted) if inverted and not(subdev.is_quadrature()): dxc_freq = -dxc_freq inverted = not(inverted) if rx_p: ok = ok and u.set_rx_freq(chan, dxc_freq) else: dxc_freq = -dxc_freq ok = ok and u.set_tx_freq(chan, dxc_freq) if not(ok): return False # residual_freq is the offset left over because of dxc tuning step size if rx_p: residual_freq = dxc_freq - u.rx_freq(chan) else: # FIXME 50-50 chance this has the wrong sign... residual_freq = dxc_freq - u.tx_freq(chan) return tune_result(baseband_freq, dxc_freq, residual_freq, inverted) # ------------------------------------------------------------------------ # Build subclasses of raw usrp1.* class that add the db attribute # by automatically instantiating the appropriate daughterboard classes. # [Also provides keyword args.] # ------------------------------------------------------------------------ class usrp_common(object): def __init__(self): # read capability register r = self._u._read_fpga_reg(FR_RB_CAPS) if r < 0: r += 2**32 if r == 0xaa55ff77: # value of this reg prior to being defined as cap reg r = ((2 << bmFR_RB_CAPS_NDUC_SHIFT) | (2 << bmFR_RB_CAPS_NDDC_SHIFT) | bmFR_RB_CAPS_RX_HAS_HALFBAND) self._fpga_caps = r if False: print "FR_RB_CAPS = %#08x" % (self._fpga_caps,) print "has_rx_halfband =", self.has_rx_halfband() print "nDDCs =", self.nddc() print "has_tx_halfband =", self.has_tx_halfband() print "nDUCs =", self.nduc() def __getattr__(self, name): return getattr(self._u, name) def tune(self, chan, subdev, target_freq): return tune(self, chan, subdev, target_freq) def has_rx_halfband(self): return self._fpga_caps & bmFR_RB_CAPS_RX_HAS_HALFBAND != 0 def has_tx_halfband(self): return self._fpga_caps & bmFR_RB_CAPS_TX_HAS_HALFBAND != 0 def nddc(self): """ Number of Digital Down Converters implemented in FPGA """ return (self._fpga_caps & bmFR_RB_CAPS_NDDC_MASK) >> bmFR_RB_CAPS_NDDC_SHIFT def nduc(self): """ Number of Digital Up Converters implemented in FPGA """ return (self._fpga_caps & bmFR_RB_CAPS_NDUC_MASK) >> bmFR_RB_CAPS_NDUC_SHIFT class sink_c(usrp_common): def __init__(self, which=0, interp_rate=128, nchan=1, mux=0x98, fusb_block_size=0, fusb_nblocks=0, fpga_filename="", firmware_filename=""): _ensure_rev2(which) self._u = usrp1.sink_c(which, interp_rate, nchan, mux, fusb_block_size, fusb_nblocks, fpga_filename, firmware_filename) # Add the db attribute, which contains a 2-tuple of tuples of daughterboard classes self.db = (db_instantiator.instantiate(self._u, 0), db_instantiator.instantiate(self._u, 1)) usrp_common.__init__(self) def __del__(self): self.db = None # will fire d'board destructors self._u = None # will fire usrp1.* destructor class sink_s(usrp_common): def __init__(self, which=0, interp_rate=128, nchan=1, mux=0x98, fusb_block_size=0, fusb_nblocks=0, fpga_filename="", firmware_filename=""): _ensure_rev2(which) self._u = usrp1.sink_s(which, interp_rate, nchan, mux, fusb_block_size, fusb_nblocks, fpga_filename, firmware_filename) # Add the db attribute, which contains a 2-tuple of tuples of daughterboard classes self.db = (db_instantiator.instantiate(self._u, 0), db_instantiator.instantiate(self._u, 1)) usrp_common.__init__(self) def __del__(self): self.db = None # will fire d'board destructors self._u = None # will fire usrp1.* destructor class source_c(usrp_common): def __init__(self, which=0, decim_rate=64, nchan=1, mux=0x32103210, mode=0, fusb_block_size=0, fusb_nblocks=0, fpga_filename="", firmware_filename=""): _ensure_rev2(which) self._u = usrp1.source_c(which, decim_rate, nchan, mux, mode, fusb_block_size, fusb_nblocks, fpga_filename, firmware_filename) # Add the db attribute, which contains a 2-tuple of tuples of daughterboard classes self.db = (db_instantiator.instantiate(self._u, 0), db_instantiator.instantiate(self._u, 1)) usrp_common.__init__(self) def __del__(self): self.db = None # will fire d'board destructors self._u = None # will fire usrp1.* destructor class source_s(usrp_common): def __init__(self, which=0, decim_rate=64, nchan=1, mux=0x32103210, mode=0, fusb_block_size=0, fusb_nblocks=0, fpga_filename="", firmware_filename=""): _ensure_rev2(which) self._u = usrp1.source_s(which, decim_rate, nchan, mux, mode, fusb_block_size, fusb_nblocks, fpga_filename, firmware_filename) # Add the db attribute, which contains a 2-tuple of tuples of daughterboard classes self.db = (db_instantiator.instantiate(self._u, 0), db_instantiator.instantiate(self._u, 1)) usrp_common.__init__(self) def __del__(self): self.db = None # will fire d'board destructors self._u = None # will fire usrp1.* destructor # ------------------------------------------------------------------------ # utilities # ------------------------------------------------------------------------ def determine_rx_mux_value(u, subdev_spec): """ Determine appropriate Rx mux value as a function of the subdevice choosen and the characteristics of the respective daughterboard. @param u: instance of USRP source @param subdev_spec: return value from subdev option parser. @type subdev_spec: (side, subdev), where side is 0 or 1 and subdev is 0 or 1 @returns: the Rx mux value """ # Figure out which A/D's to connect to the DDC. # # Each daughterboard consists of 1 or 2 subdevices. (At this time, # all but the Basic Rx have a single subdevice. The Basic Rx # has two independent channels, treated as separate subdevices). # subdevice 0 of a daughterboard may use 1 or 2 A/D's. We determine this # by checking the is_quadrature() method. If subdevice 0 uses only a single # A/D, it's possible that the daughterboard has a second subdevice, subdevice 1, # and it uses the second A/D. # # If the card uses only a single A/D, we wire a zero into the DDC Q input. # # (side, 0) says connect only the A/D's used by subdevice 0 to the DDC. # (side, 1) says connect only the A/D's used by subdevice 1 to the DDC. # side = subdev_spec[0] # side A = 0, side B = 1 if not(side in (0, 1)): raise ValueError, "Invalid subdev_spec: %r:" % (subdev_spec,) db = u.db[side] # This is a tuple of length 1 or 2 containing the subdevice # classes for the selected side. # compute bitmasks of used A/D's if db[0].is_quadrature(): subdev0_uses = 0x3 # uses A/D 0 and 1 else: subdev0_uses = 0x1 # uses A/D 0 only if len(db) > 1: subdev1_uses = 0x2 # uses A/D 1 only else: subdev1_uses = 0x0 # uses no A/D (doesn't exist) if subdev_spec[1] == 0: uses = subdev0_uses elif subdev_spec[1] == 1: uses = subdev1_uses else: raise ValueError, "Invalid subdev_spec: %r: " % (subdev_spec,) if uses == 0: raise RuntimeError, "Daughterboard doesn't have a subdevice 1: %r: " % (subdev_spec,) swap_iq = db[0].i_and_q_swapped() truth_table = { # (side, uses, swap_iq) : mux_val (0, 0x1, False) : 0xf0f0f0f0, (0, 0x2, False) : 0xf0f0f0f1, (0, 0x3, False) : 0x00000010, (0, 0x3, True) : 0x00000001, (1, 0x1, False) : 0xf0f0f0f2, (1, 0x2, False) : 0xf0f0f0f3, (1, 0x3, False) : 0x00000032, (1, 0x3, True) : 0x00000023 } return gru.hexint(truth_table[(side, uses, swap_iq)]) def determine_tx_mux_value(u, subdev_spec): """ Determine appropriate Tx mux value as a function of the subdevice choosen. @param u: instance of USRP source @param subdev_spec: return value from subdev option parser. @type subdev_spec: (side, subdev), where side is 0 or 1 and subdev is 0 @returns: the Rx mux value """ # This is simpler than the rx case. Either you want to talk # to side A or side B. If you want to talk to both sides at once, # determine the value manually. side = subdev_spec[0] # side A = 0, side B = 1 if not(side in (0, 1)): raise ValueError, "Invalid subdev_spec: %r:" % (subdev_spec,) return gru.hexint([0x0098, 0x9800][side]) def selected_subdev(u, subdev_spec): """ Return the user specified daughterboard subdevice. @param u: an instance of usrp.source_* or usrp.sink_* @param subdev_spec: return value from subdev option parser. @type subdev_spec: (side, subdev), where side is 0 or 1 and subdev is 0 or 1 @returns: an instance derived from db_base """ side, subdev = subdev_spec return u.db[side][subdev] def calc_dxc_freq(target_freq, baseband_freq, fs): """ Calculate the frequency to use for setting the digital up or down converter. @param target_freq: desired RF frequency (Hz) @type target_freq: number @param baseband_freq: the RF frequency that corresponds to DC in the IF. @type baseband_freq: number @param fs: converter sample rate @type fs: number @returns: 2-tuple (ddc_freq, inverted) where ddc_freq is the value for the ddc and inverted is True if we're operating in an inverted Nyquist zone. """ delta = target_freq - baseband_freq if delta >= 0: while delta > fs: delta -= fs if delta <= fs/2: return (-delta, False) # non-inverted region else: return (delta - fs, True) # inverted region else: while delta < -fs: delta += fs if delta >= -fs/2: return (-delta, False) # non-inverted region else: return (delta + fs, True) # inverted region # ------------------------------------------------------------------------ # Utilities # ------------------------------------------------------------------------ def pick_tx_subdevice(u): """ The user didn't specify a tx subdevice on the command line. Try for one of these, in order: FLEX_400, FLEX_900, FLEX_1200, FLEX_2400, BASIC_TX, whatever's on side A. @return a subdev_spec """ return pick_subdev(u, (usrp_dbid.FLEX_400_TX, usrp_dbid.FLEX_900_TX, usrp_dbid.FLEX_1200_TX, usrp_dbid.FLEX_2400_TX, usrp_dbid.BASIC_TX)) def pick_rx_subdevice(u): """ The user didn't specify an rx subdevice on the command line. Try for one of these, in order: FLEX_400, FLEX_900, FLEX_1200, FLEX_2400, TV_RX, DBS_RX, BASIC_RX, whatever's on side A. @return a subdev_spec """ return pick_subdev(u, (usrp_dbid.FLEX_400_RX, usrp_dbid.FLEX_900_RX, usrp_dbid.FLEX_1200_RX, usrp_dbid.FLEX_2400_RX, usrp_dbid.TV_RX, usrp_dbid.TV_RX_REV_2, usrp_dbid.DBS_RX, usrp_dbid.DBS_RX_REV_2_1, usrp_dbid.BASIC_RX)) def pick_subdev(u, candidates): """ @param u: usrp instance @param candidates: list of dbids @returns: subdev specification """ db0 = u.db[0][0].dbid() db1 = u.db[1][0].dbid() for c in candidates: if c == db0: return (0, 0) if c == db1: return (1, 0) if db0 >= 0: return (0, 0) if db1 >= 0: return (1, 0) raise RuntimeError, "No suitable daughterboard found!"