diff options
| author | Nick Foster <nick@nerdnetworks.org> | 2010-10-05 11:13:25 -0700 | 
|---|---|---|
| committer | Nick Foster <nick@nerdnetworks.org> | 2010-10-05 11:14:45 -0700 | 
| commit | e4d088fa9169eef81473cb7d83bda7c82bc5d243 (patch) | |
| tree | 0798f4c0863bcb27ff4164f9e2fcc2ccd7bf80d3 | |
| parent | 6dd502737bcf6e59933be01720672db9a496803b (diff) | |
| parent | bd3bd0dfbc1a87af5839c9b23450434cfb9c763c (diff) | |
| download | uhd-e4d088fa9169eef81473cb7d83bda7c82bc5d243.tar.gz uhd-e4d088fa9169eef81473cb7d83bda7c82bc5d243.tar.bz2 uhd-e4d088fa9169eef81473cb7d83bda7c82bc5d243.zip | |
Merge branch 'master' of ettus.sourcerepo.com:ettus/uhdpriv into usrp2p
Conflicts:
	host/lib/ic_reg_maps/CMakeLists.txt
	host/lib/usrp/usrp2/io_impl.cpp
23 files changed, 698 insertions, 65 deletions
| diff --git a/host/AUTHORS b/host/AUTHORS index e0775f3a1..512d4752e 100644 --- a/host/AUTHORS +++ b/host/AUTHORS @@ -24,6 +24,7 @@ Tom Tsou - ttsou@vt.edu      USRP1 host code      USRP1 firmware -Nick Foster - nick@nerdnetworks.org +Nick Foster - nick@ettus.com      LIBUSB host code      USRP1 host code +    TVRX host code diff --git a/host/README b/host/README index 5018ef541..cab1e0b10 100644 --- a/host/README +++ b/host/README @@ -6,7 +6,8 @@ The hardware driver for Ettus Research products.  ########################################################################  # Supported USRP Motherboards  ######################################################################## -USRP2 - udp over gigabit ethernet +USRP1 +USRP2  ########################################################################  # Supported USRP Daughterboards @@ -19,6 +20,7 @@ RFX Series  XCVR 2450  WBX Series  DBSRX +TVRX  ########################################################################  # Documentation diff --git a/host/include/uhd/transport/usb_device_handle.hpp b/host/include/uhd/transport/usb_device_handle.hpp index 9bb7db9c4..6f8d868be 100644 --- a/host/include/uhd/transport/usb_device_handle.hpp +++ b/host/include/uhd/transport/usb_device_handle.hpp @@ -38,7 +38,7 @@ namespace uhd { namespace transport {   *       a true descriptor serial number string. This interface returns the   *       actual string descriptor.   */ -class usb_device_handle : boost::noncopyable { +class UHD_API usb_device_handle : boost::noncopyable {  public:      typedef boost::shared_ptr<usb_device_handle> sptr; diff --git a/host/include/uhd/transport/zero_copy.hpp b/host/include/uhd/transport/zero_copy.hpp index 513291b63..8ecafd3fb 100644 --- a/host/include/uhd/transport/zero_copy.hpp +++ b/host/include/uhd/transport/zero_copy.hpp @@ -122,9 +122,10 @@ namespace uhd{ namespace transport{          /*!           * Get a new receive buffer from this transport object. +         * \param timeout_ms the timeout to get the buffer in ms           * \return a managed buffer, or null sptr on timeout/error           */ -        virtual managed_recv_buffer::sptr get_recv_buff(void) = 0; +        virtual managed_recv_buffer::sptr get_recv_buff(size_t timeout_ms) = 0;          /*!           * Get the maximum number of receive frames: @@ -171,16 +172,19 @@ namespace uhd{ namespace transport{          /*!           * Get a new receive buffer from this transport object. +         * \param timeout_ms the timeout to get the buffer in ms +         * \return a managed buffer, or null sptr on timeout/error           */ -        managed_recv_buffer::sptr get_recv_buff(void); +        managed_recv_buffer::sptr get_recv_buff(size_t timeout_ms);      private:          /*!           * Perform a private copying recv.           * \param buff the buffer to write data into +         * \param timeout_ms the timeout to get the buffer in ms           * \return the number of bytes written to buff, 0 for timeout, negative for error           */ -        virtual ssize_t recv(const boost::asio::mutable_buffer &buff) = 0; +        virtual ssize_t recv(const boost::asio::mutable_buffer &buff, size_t timeout_ms) = 0;          UHD_PIMPL_DECL(impl) _impl;      }; @@ -204,6 +208,7 @@ namespace uhd{ namespace transport{          /*!           * Get a new send buffer from this transport object. +         * \return a managed buffer, or null sptr on timeout/error           */          managed_send_buffer::sptr get_send_buff(void); diff --git a/host/include/uhd/usrp/dboard_iface.hpp b/host/include/uhd/usrp/dboard_iface.hpp index c7db244f2..c430ecd3f 100644 --- a/host/include/uhd/usrp/dboard_iface.hpp +++ b/host/include/uhd/usrp/dboard_iface.hpp @@ -242,6 +242,15 @@ public:       * \param enb true for enabled       */      virtual void set_clock_enabled(unit_t unit, bool enb) = 0; + +    /*! +     * Get the rate of the codec. +     * For rx, this is the rate the ADC feeds the DSP. +     * For tx, this is the rate the DSP feeds the DAC. +     * \param unit which unit rx or tx +     * \return the codec rate in Hz +     */ +    virtual double get_codec_rate(unit_t unit) = 0;  };  }} //namespace diff --git a/host/lib/device.cpp b/host/lib/device.cpp index d575ebaab..386588a08 100644 --- a/host/lib/device.cpp +++ b/host/lib/device.cpp @@ -26,6 +26,7 @@  #include <boost/functional/hash.hpp>  #include <boost/tuple/tuple.hpp>  #include <stdexcept> +#include <iostream>  using namespace uhd; @@ -73,12 +74,17 @@ device_addrs_t device::find(const device_addr_t &hint){      device_addrs_t device_addrs;      BOOST_FOREACH(const dev_fcn_reg_t &fcn, get_dev_fcn_regs()){ -        device_addrs_t discovered_addrs = fcn.get<0>()(hint); -        device_addrs.insert( -            device_addrs.begin(), -            discovered_addrs.begin(), -            discovered_addrs.end() -        ); +        try{ +            device_addrs_t discovered_addrs = fcn.get<0>()(hint); +            device_addrs.insert( +                device_addrs.begin(), +                discovered_addrs.begin(), +                discovered_addrs.end() +            ); +        } +        catch(const std::exception &e){ +            std::cerr << "Device discovery error: " << e.what() << std::endl; +        }      }      return device_addrs; diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt index dd188d4c0..507f214c9 100644 --- a/host/lib/ic_reg_maps/CMakeLists.txt +++ b/host/lib/ic_reg_maps/CMakeLists.txt @@ -70,6 +70,11 @@ LIBUHD_PYTHON_GEN_SOURCE(  )  LIBUHD_PYTHON_GEN_SOURCE( +<<<<<<< HEAD      ${CMAKE_SOURCE_DIR}/lib/ic_reg_maps/gen_ads62p44_regs.py      ${CMAKE_BINARY_DIR}/lib/ic_reg_maps/ads62p44_regs.hpp +======= +    ${CMAKE_SOURCE_DIR}/lib/ic_reg_maps/gen_tuner_4937di5_regs.py +    ${CMAKE_BINARY_DIR}/lib/ic_reg_maps/tuner_4937di5_regs.hpp +>>>>>>> bd3bd0dfbc1a87af5839c9b23450434cfb9c763c  ) diff --git a/host/lib/ic_reg_maps/gen_max2118_regs.py b/host/lib/ic_reg_maps/gen_max2118_regs.py index 506fbaec8..506fbaec8 100644..100755 --- a/host/lib/ic_reg_maps/gen_max2118_regs.py +++ b/host/lib/ic_reg_maps/gen_max2118_regs.py diff --git a/host/lib/ic_reg_maps/gen_tuner_4937di5_regs.py b/host/lib/ic_reg_maps/gen_tuner_4937di5_regs.py new file mode 100644 index 000000000..73f7aa3db --- /dev/null +++ b/host/lib/ic_reg_maps/gen_tuner_4937di5_regs.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# +# Copyright 2010 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/>. +# + +######################################################################## +# Template for raw text data describing registers +# name addr[bit range inclusive] default optional enums +######################################################################## +REGS_TMPL="""\ +######################################################################## +## Note: offsets given from perspective of data bits (excludes address) +######################################################################## +## Divider byte 1 +######################################################################## +db1                   0[0:6]        0x00 +######################################################################## +## Divider byte 2 +######################################################################## +db2                   1[0:7]        0x00 +######################################################################## +## Control byte 1 +######################################################################## +cb7                   2[7]          0x01 +cp                    2[6]          0x00     low,high +os                    2[0]          0x00     on,off +rs                    2[1:2]        0x00     d512=3,d640=0,d1024=1 +test                  2[3:5]        0x01     normal=0x01,cpoff=0x02,cpsink=0x06,cpsrc=0x07,cptest1=0x04,cptest2=0x05 +######################################################################## +## Control byte 2 +######################################################################## +bandsel               3[4:7]        0x03     uhf=0x03,vhfhi=0x09,vhflo=0x0a +power                 3[3]          0x00     on,off +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +boost::uint8_t get_reg(boost::uint8_t addr){ +    boost::uint8_t reg = 0; +    switch(addr){ +    #for $addr in sorted(set(map(lambda r: r.get_addr(), $regs))) +    case $addr: +        #for $reg in filter(lambda r: r.get_addr() == addr, $regs) +        reg |= (boost::uint8_t($reg.get_name()) & $reg.get_mask()) << $reg.get_shift(); +        #end for +        break; +    #end for +    } +    return boost::uint8_t(reg); +} + +""" + +if __name__ == '__main__': +    import common; common.generate( +        name='tuner_4937di5_regs', +        regs_tmpl=REGS_TMPL, +        body_tmpl=BODY_TMPL, +        file=__file__, +    ) diff --git a/host/lib/transport/libusb1_base.cpp b/host/lib/transport/libusb1_base.cpp index 49f524a32..1dd0b48e8 100644 --- a/host/lib/transport/libusb1_base.cpp +++ b/host/lib/transport/libusb1_base.cpp @@ -35,7 +35,9 @@ public:      libusb_session_impl(void){          UHD_ASSERT_THROW(libusb_init(&_context) == 0);          libusb_set_debug(_context, debug_level); +        _mutex.lock();          _thread_group.create_thread(boost::bind(&libusb_session_impl::run_event_loop, this)); +        _mutex.lock();      }      ~libusb_session_impl(void){ @@ -52,11 +54,13 @@ private:      libusb_context *_context;      boost::thread_group _thread_group;      bool _running; +    boost::mutex _mutex;      void run_event_loop(void){          set_thread_priority_safe();          _running = true;          timeval tv; +        _mutex.unlock();          while(_running){              tv.tv_sec = 0;              tv.tv_usec = 100000; //100ms @@ -213,15 +217,21 @@ private:  libusb::device_handle::sptr libusb::device_handle::get_cached_handle(device::sptr dev){      static uhd::dict<libusb_device *, boost::weak_ptr<device_handle> > handles; -    //not expired -> get existing session +    //not expired -> get existing handle      if (handles.has_key(dev->get()) and not handles[dev->get()].expired()){          return handles[dev->get()].lock();      } -    //create a new global session -    sptr new_handle(new libusb_device_handle_impl(dev)); -    handles[dev->get()] = new_handle; -    return new_handle; +    //create a new cached handle +    try{ +        sptr new_handle(new libusb_device_handle_impl(dev)); +        handles[dev->get()] = new_handle; +        return new_handle; +    } +    catch(const std::exception &e){ +        std::cerr << "USB open failed: see the application notes for your device." << std::endl; +        throw std::runtime_error(e.what()); +    }  }  /*********************************************************************** diff --git a/host/lib/transport/libusb1_zero_copy.cpp b/host/lib/transport/libusb1_zero_copy.cpp index f2dcff6b5..f9beb0b4c 100644 --- a/host/lib/transport/libusb1_zero_copy.cpp +++ b/host/lib/transport/libusb1_zero_copy.cpp @@ -308,8 +308,7 @@ public:      }  private: -    const boost::asio::const_buffer &get() const -    { +    const boost::asio::const_buffer &get(void) const{          return _buff;      } @@ -369,8 +368,7 @@ public:      }  private: -    const boost::asio::mutable_buffer &get() const -    { +    const boost::asio::mutable_buffer &get(void) const{          return _buff;      } @@ -395,13 +393,13 @@ public:      typedef boost::shared_ptr<libusb_zero_copy_impl> sptr;      libusb_zero_copy_impl( -		libusb::device_handle::sptr handle, -		unsigned int recv_endpoint, unsigned int send_endpoint, -		size_t recv_xfer_size, size_t recv_num_xfers, -		size_t send_xfer_size, size_t send_num_xfers -	); +        libusb::device_handle::sptr handle, +        unsigned int recv_endpoint, unsigned int send_endpoint, +        size_t recv_xfer_size, size_t recv_num_xfers, +        size_t send_xfer_size, size_t send_num_xfers +    ); -    managed_recv_buffer::sptr get_recv_buff(void); +    managed_recv_buffer::sptr get_recv_buff(size_t timeout_ms);      managed_send_buffer::sptr get_send_buff(void);      size_t get_num_recv_frames(void) const { return _recv_num_frames; } @@ -419,23 +417,23 @@ libusb_zero_copy_impl::libusb_zero_copy_impl(      size_t recv_xfer_size, size_t recv_num_xfers,      size_t send_xfer_size, size_t send_num_xfers  ){ -	_handle = handle; +    _handle = handle; -	//if the sizes are left at 0 (automatic) -> use the defaults -	if (recv_xfer_size == 0) recv_xfer_size = DEFAULT_XFER_SIZE; -	if (recv_num_xfers == 0) recv_num_xfers = DEFAULT_NUM_XFERS; -	if (send_xfer_size == 0) send_xfer_size = DEFAULT_XFER_SIZE; -	if (send_num_xfers == 0) send_num_xfers = DEFAULT_NUM_XFERS; +    //if the sizes are left at 0 (automatic) -> use the defaults +    if (recv_xfer_size == 0) recv_xfer_size = DEFAULT_XFER_SIZE; +    if (recv_num_xfers == 0) recv_num_xfers = DEFAULT_NUM_XFERS; +    if (send_xfer_size == 0) send_xfer_size = DEFAULT_XFER_SIZE; +    if (send_num_xfers == 0) send_num_xfers = DEFAULT_NUM_XFERS;      //sanity check the transfer sizes      UHD_ASSERT_THROW(recv_xfer_size % 512 == 0);      UHD_ASSERT_THROW(send_xfer_size % 512 == 0); -	//store the num xfers for the num frames count -	_recv_num_frames = recv_num_xfers; -	_send_num_frames = send_num_xfers; +    //store the num xfers for the num frames count +    _recv_num_frames = recv_num_xfers; +    _send_num_frames = send_num_xfers; -	_handle->claim_interface(2 /*in interface*/); +    _handle->claim_interface(2 /*in interface*/);      _handle->claim_interface(1 /*out interface*/);      _recv_ep = usb_endpoint::sptr(new usb_endpoint( @@ -461,8 +459,8 @@ libusb_zero_copy_impl::libusb_zero_copy_impl(   * Return empty pointer if no transfer is available (timeout or error).   * \return pointer to a managed receive buffer   */ -managed_recv_buffer::sptr libusb_zero_copy_impl::get_recv_buff(void){ -    libusb_transfer *lut = _recv_ep->get_lut_with_wait(/* TODO timeout API */); +managed_recv_buffer::sptr libusb_zero_copy_impl::get_recv_buff(size_t timeout_ms){ +    libusb_transfer *lut = _recv_ep->get_lut_with_wait(timeout_ms);      if (lut == NULL) {          return managed_recv_buffer::sptr();      } diff --git a/host/lib/transport/udp_zero_copy_asio.cpp b/host/lib/transport/udp_zero_copy_asio.cpp index ee989ee2b..0a6c9f2af 100644 --- a/host/lib/transport/udp_zero_copy_asio.cpp +++ b/host/lib/transport/udp_zero_copy_asio.cpp @@ -35,7 +35,6 @@ static const size_t MIN_RECV_SOCK_BUFF_SIZE = size_t(sizeof(boost::uint32_t) * 2  //Perhaps this is due to the kernel scheduling,  //but may change with host-based flow control.  static const size_t MIN_SEND_SOCK_BUFF_SIZE = size_t(10e3); -static const double RECV_TIMEOUT = 0.1; //100 ms  /***********************************************************************   * Zero Copy UDP implementation with ASIO: @@ -110,11 +109,11 @@ private:      boost::asio::io_service        _io_service;      int                            _sock_fd; -    ssize_t recv(const boost::asio::mutable_buffer &buff){ +    ssize_t recv(const boost::asio::mutable_buffer &buff, size_t timeout_ms){          //setup timeval for timeout          timeval tv;          tv.tv_sec = 0; -        tv.tv_usec = int(RECV_TIMEOUT*1e6); +        tv.tv_usec = timeout_ms*1000;          //setup rset for timeout          fd_set rset; diff --git a/host/lib/transport/zero_copy.cpp b/host/lib/transport/zero_copy.cpp index 8a1cde694..1fcf846a0 100644 --- a/host/lib/transport/zero_copy.cpp +++ b/host/lib/transport/zero_copy.cpp @@ -68,12 +68,12 @@ phony_zero_copy_recv_if::~phony_zero_copy_recv_if(void){      /* NOP */  } -managed_recv_buffer::sptr phony_zero_copy_recv_if::get_recv_buff(void){ +managed_recv_buffer::sptr phony_zero_copy_recv_if::get_recv_buff(size_t timeout_ms){      //allocate memory      boost::uint8_t *recv_mem = new boost::uint8_t[_impl->max_buff_size];      //call recv() with timeout option -    ssize_t num_bytes = this->recv(boost::asio::buffer(recv_mem, _impl->max_buff_size)); +    ssize_t num_bytes = this->recv(boost::asio::buffer(recv_mem, _impl->max_buff_size), timeout_ms);      if (num_bytes <= 0) return managed_recv_buffer::sptr(); //NULL sptr diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index 3e995009e..8d3d11530 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -24,5 +24,6 @@ LIBUHD_APPEND_SOURCES(      ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_wbx.cpp      ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_dbsrx.cpp      ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_unknown.cpp +    ${CMAKE_SOURCE_DIR}/lib/usrp/dboard/db_tvrx.cpp  ) diff --git a/host/lib/usrp/dboard/db_tvrx.cpp b/host/lib/usrp/dboard/db_tvrx.cpp new file mode 100644 index 000000000..41c52fbf5 --- /dev/null +++ b/host/lib/usrp/dboard/db_tvrx.cpp @@ -0,0 +1,477 @@ +// +// Copyright 2010 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/>. +// + +// No RX IO Pins Used + +// RX IO Functions + +//ADC/DAC functions: +//DAC 1: RF AGC +//DAC 2: IF AGC + +//min freq: 50e6 +//max freq: 860e6 +//gain range: [0:1dB:115dB] + +#include <uhd/utils/static.hpp> +#include <uhd/utils/assert.hpp> +#include <uhd/utils/algorithm.hpp> +#include <uhd/utils/warning.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/subdev_props.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/array.hpp> +#include <boost/math/special_functions/round.hpp> +#include <utility> +#include <cmath> +#include <cfloat> +#include <limits> +#include <tuner_4937di5_regs.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace boost::assign; + +/*********************************************************************** + * The tvrx constants + **********************************************************************/ +static const bool tvrx_debug = false; + +static const freq_range_t tvrx_freq_range(50e6, 860e6); + +static const prop_names_t tvrx_antennas = list_of(""); //only got one + +static const uhd::dict<std::string, freq_range_t> tvrx_freq_ranges = map_list_of +    ("VHFLO", freq_range_t(50e6, 158e6)) +    ("VHFHI", freq_range_t(158e6, 454e6)) +    ("UHF"  , freq_range_t(454e6, 860e6)) +; + +static const boost::array<float, 17> vhflo_gains_db =  +    {{-6.00000, -6.00000, -6.00000, -4.00000, 0.00000, +     5.00000, 10.00000, 17.40000, 26.30000, 36.00000, +     43.00000, 48.00000, 49.50000, 50.10000, 50.30000, +     50.30000, 50.30000}}; +                                  +static const boost::array<float, 17> vhfhi_gains_db =  +    {{13.3000,  -13.3000,  -13.3000,   -1.0000,    7.7000, +    11.0000,   14.7000,   19.3000,   26.1000,   36.0000, +    42.7000,   46.0000,   47.0000,   47.8000,   48.2000, +    48.2000,   48.2000}}; +     +static const boost::array<float, 17> uhf_gains_db = +    {{-8.0000,   -8.0000,   -7.0000,    4.0000,   10.2000, +     14.5000,   17.5000,   20.0000,   24.5000,   30.8000, +     37.0000,   39.8000,   40.7000,   41.6000,   42.6000, +     43.2000,   43.8000}}; +      +static const boost::array<float, 17> tvrx_if_gains_db =  +    {{-1.50000,   -1.50000,   -1.50000,   -1.00000,    0.20000, +     2.10000,    4.30000,    6.40000,    9.00000,   12.00000, +     14.80000,   18.20000,   26.10000,   32.50000,  32.50000, +     32.50000,   32.50000}}; + +//gain linearization data +//this is from the datasheet and is dB vs. volts (below) +//i tried to curve fit this, but it's really just so nonlinear that you'd +//need dang near as many coefficients as to just map it like this and interp. +//these numbers are culled from the 4937DI5 datasheet and are probably totally inaccurate +//but if it's better than the old linear fit i'm happy +static const uhd::dict<std::string, boost::array<float, 17> > tvrx_rf_gains_db = map_list_of +    ("VHFLO", vhflo_gains_db) +    ("VHFHI", vhfhi_gains_db) +    ("UHF"  , uhf_gains_db) +; + +//sample voltages for the above points +static const boost::array<float, 17> tvrx_gains_volts =  +    {{0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6, 2.8, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0}}; + +static uhd::dict<std::string, gain_range_t> get_tvrx_gain_ranges(void) { +    float rfmax = 0.0, rfmin = FLT_MAX; +    BOOST_FOREACH(const std::string range, tvrx_rf_gains_db.keys()) { +        float my_max = tvrx_rf_gains_db[range].back(); //we're assuming it's monotonic +        float my_min = tvrx_rf_gains_db[range].front(); //if it's not this is wrong wrong wrong +        if(my_max > rfmax) rfmax = my_max; +        if(my_min < rfmin) rfmin = my_min; +    } +     +    float ifmin = tvrx_if_gains_db.front(); +    float ifmax = tvrx_if_gains_db.back(); +     +    return map_list_of +        ("RF", gain_range_t(rfmin, rfmax, (rfmax-rfmin)/4096.0)) +        ("IF", gain_range_t(ifmin, ifmax, (ifmax-ifmin)/4096.0)) +        ; +} + +static const double opamp_gain = 1.22; //onboard DAC opamp gain +static const double tvrx_if_freq = 43.75e6; //IF freq of TVRX module +static const boost::uint16_t reference_divider = 640; //clock reference divider to use +static const double reference_freq = 4.0e6; + +/*********************************************************************** + * The tvrx dboard class + **********************************************************************/ +class tvrx : public rx_dboard_base{ +public: +    tvrx(ctor_args_t args); +    ~tvrx(void); +     +    void rx_get(const wax::obj &key, wax::obj &val); +    void rx_set(const wax::obj &key, const wax::obj &val); + +private: +    uhd::dict<std::string, float> _gains; +    double _lo_freq; +    tuner_4937di5_regs_t _tuner_4937di5_regs; +    boost::uint8_t _tuner_4937di5_addr(void){ +        return (this->get_iface()->get_special_props().mangle_i2c_addrs)? 0x61 : 0x60; //ok really? we could rename that call +    }; + +    void set_gain(float gain, const std::string &name); +    void set_freq(double freq); + +    void update_regs(void){ +        byte_vector_t regs_vector(4); + +        //get the register data +        for(int i=0; i<4; i++){ +            regs_vector[i] = _tuner_4937di5_regs.get_reg(i); +            if(tvrx_debug) std::cerr << boost::format( +                "tvrx: send reg 0x%02x, value 0x%04x" +            ) % int(i) % int(regs_vector[i]) << std::endl; +        } + +        //send the data +        this->get_iface()->write_i2c( +            _tuner_4937di5_addr(), regs_vector +        ); +    } + +}; + +/*********************************************************************** + * Register the tvrx dboard + **********************************************************************/ +static dboard_base::sptr make_tvrx(dboard_base::ctor_args_t args){ +    return dboard_base::sptr(new tvrx(args)); +} + +UHD_STATIC_BLOCK(reg_tvrx_dboard){ +    //register the factory function for the rx dbid +    dboard_manager::register_dboard(0x0040, &make_tvrx, "tvrx"); +} + +/*********************************************************************** + * Structors + **********************************************************************/ +tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args){ +    this->get_iface()->set_clock_enabled(dboard_iface::UNIT_RX, true); + +    //set the gpio directions and atr controls (identically) +    this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, 0x0); // All unused in atr +    if (this->get_iface()->get_special_props().soft_clock_divider){ +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x1); // GPIO0 is clock +    } +    else{ +        this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, 0x0); // All Inputs +    } + +    //send initial register settings if necessary +     +    //set default freq +    _lo_freq = tvrx_freq_range.min + tvrx_if_freq; //init _lo_freq to a sane default +    set_freq(tvrx_freq_range.min); +     +    //set default gains +    BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){ +        set_gain(get_tvrx_gain_ranges()[name].min, name); +    } +} + +tvrx::~tvrx(void){ +} + +/*! Return a string corresponding to the relevant band + * \param freq the frequency of interest + * \return a string corresponding to the band + */ + +static std::string get_band(double freq) { +    BOOST_FOREACH(const std::string &band, tvrx_freq_ranges.keys()) { +        if(freq >= tvrx_freq_ranges[band].min && freq <= tvrx_freq_ranges[band].max){ +            if(tvrx_debug) std::cout << "Band: " << band << std::endl; +            return band; +        } +    } +    UHD_THROW_INVALID_CODE_PATH(); +} + +/*********************************************************************** + * Gain Handling + **********************************************************************/ +/*! + * Execute a linear interpolation to find the voltage corresponding to a desired gain + * \param gain the desired gain in dB + * \param db_vector the vector of dB readings + * \param volts_vector the corresponding vector of voltages db_vector was sampled at + * \return a voltage to feed the TVRX analog gain + */ + +static float gain_interp(float gain, boost::array<float, 17> db_vector, boost::array<float, 17> volts_vector) { +    float volts; +    gain = std::clip<float>(gain, db_vector.front(), db_vector.back()); //let's not get carried away here +     +    boost::uint8_t gain_step = 0; +    //find which bin we're in +    for(size_t i = 0; i < db_vector.size()-1; i++) { +        if(gain >= db_vector[i] && gain <= db_vector[i+1]) gain_step = i; +    } +     +    //find the current slope for linear interpolation +    float slope = (volts_vector[gain_step + 1] - volts_vector[gain_step])  +                / (db_vector[gain_step + 1] - db_vector[gain_step]); +                 +    //the problem here is that for gains approaching the maximum, the voltage slope becomes infinite +    //i.e., a small change in gain requires an infinite change in voltage +    //to cope, we limit the slope +     +    if(slope == std::numeric_limits<float>::infinity()) +        return volts_vector[gain_step]; + +    //use the volts per dB slope to find the final interpolated voltage +    volts = volts_vector[gain_step] + (slope * (gain - db_vector[gain_step])); +         +    if(tvrx_debug) +        std::cout << "Gain interp: gain: " << gain << ", gain_step: " << int(gain_step) << ", slope: " << slope << ", volts: " << volts << std::endl; +     +    return volts; +} + +/*! + * Convert a requested gain for the RF gain into a DAC voltage. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ + +static float rf_gain_to_voltage(float gain, double lo_freq){ +    //clip the input +    gain = std::clip<float>(gain, get_tvrx_gain_ranges()["RF"].min, get_tvrx_gain_ranges()["RF"].max); + +    //first we need to find out what band we're in, because gains are different across different bands +    std::string band = get_band(lo_freq + tvrx_if_freq); + +    //this is the voltage at the TVRX gain input +    float gain_volts = gain_interp(gain, tvrx_rf_gains_db[band], tvrx_gains_volts); +    //this is the voltage at the USRP DAC output +    float dac_volts = gain_volts / opamp_gain; +     +    dac_volts = std::clip<float>(dac_volts, 0.0, 3.3); + +    if (tvrx_debug) std::cerr << boost::format( +        "tvrx RF AGC gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    return dac_volts; +} + +/*! + * Convert a requested gain for the IF gain into a DAC voltage. + * The gain passed into the function will be set to the actual value. + * \param gain the requested gain in dB + * \return dac voltage value + */ + +static float if_gain_to_voltage(float gain){ +    //clip the input +    gain = std::clip<float>(gain, get_tvrx_gain_ranges()["IF"].min, get_tvrx_gain_ranges()["IF"].max); +     +    float gain_volts = gain_interp(gain, tvrx_if_gains_db, tvrx_gains_volts); +    float dac_volts = gain_volts / opamp_gain; +     +    dac_volts = std::clip<float>(dac_volts, 0.0, 3.3); + +    if (tvrx_debug) std::cerr << boost::format( +        "tvrx IF AGC gain: %f dB, dac_volts: %f V" +    ) % gain % dac_volts << std::endl; + +    return dac_volts; +} + +void tvrx::set_gain(float gain, const std::string &name){ +    assert_has(get_tvrx_gain_ranges().keys(), name, "tvrx gain name"); +    if (name == "RF"){ +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_B, rf_gain_to_voltage(gain, _lo_freq)); +    } +    else if(name == "IF"){ +        this->get_iface()->write_aux_dac(dboard_iface::UNIT_RX, dboard_iface::AUX_DAC_A, if_gain_to_voltage(gain)); +    } +    else UHD_THROW_INVALID_CODE_PATH(); +    _gains[name] = gain; +} + +/*! + * Set the tuner to center the desired frequency at 43.75MHz + * \param freq the requested frequency + */ + +void tvrx::set_freq(double freq) { +    freq = std::clip<float>(freq, tvrx_freq_range.min, tvrx_freq_range.max); +    std::string prev_band = get_band(_lo_freq - tvrx_if_freq); +    std::string new_band = get_band(freq); +     +    double target_lo_freq = freq + tvrx_if_freq; //the desired LO freq for high-side mixing +    double f_ref = reference_freq / double(reference_divider); //your tuning step size +     +    int divisor = int((target_lo_freq + (f_ref * 4.0)) / (f_ref * 8)); //the divisor we'll use +    double actual_lo_freq = (f_ref * 8 * divisor); //the LO freq we'll actually get +     +    if((divisor & ~0x7fff)) UHD_THROW_INVALID_CODE_PATH(); +     +    //now we update the registers +    _tuner_4937di5_regs.db1 = (divisor >> 8) & 0xff; +    _tuner_4937di5_regs.db2 = divisor & 0xff; +     +    if(new_band == "VHFLO") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFLO; +    else if(new_band == "VHFHI") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_VHFHI; +    else if(new_band == "UHF") _tuner_4937di5_regs.bandsel = tuner_4937di5_regs_t::BANDSEL_UHF; +    else UHD_THROW_INVALID_CODE_PATH(); +     +    _tuner_4937di5_regs.power = tuner_4937di5_regs_t::POWER_OFF; +    update_regs(); + +    //ok don't forget to reset RF gain here if the new band != the old band +    //we do this because the gains are different for different band settings +    //not FAR off, but we do this to be consistent +    if(prev_band != new_band) set_gain(_gains["RF"], "RF"); +     +    if(tvrx_debug)  +        std::cout << boost::format("set_freq: target LO: %f f_ref: %f divisor: %i actual LO: %f") % target_lo_freq % f_ref % divisor % actual_lo_freq << std::endl; +     +    _lo_freq = actual_lo_freq; //for rx props +} + +/*********************************************************************** + * Get the alias frequency of frequency freq when sampled at fs. + * \param freq the frequency of interest + * \param fs the sample rate + * \return the alias frequency + **********************************************************************/ + +static double get_alias(double freq, double fs) { +    double alias; +    freq = fmod(freq, fs); +    if(freq >= (fs/2)) { +        alias = fs - freq; +    } else { +        alias = freq; +    } +    return alias; +} + +/*********************************************************************** + * RX Get and Set + **********************************************************************/ +void tvrx::rx_get(const wax::obj &key_, wax::obj &val){ +    named_prop_t key = named_prop_t::extract(key_); +    double codec_rate; + +    //handle the get request conditioned on the key +    switch(key.as<subdev_prop_t>()){ +    case SUBDEV_PROP_NAME: +        val = get_rx_id().to_pp_string(); +        return; + +    case SUBDEV_PROP_OTHERS: +        val = prop_names_t(); //empty +        return; + +    case SUBDEV_PROP_GAIN: +        assert_has(_gains.keys(), key.name, "tvrx gain name"); +        val = _gains[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_RANGE: +        assert_has(get_tvrx_gain_ranges().keys(), key.name, "tvrx gain name"); +        val = get_tvrx_gain_ranges()[key.name]; +        return; + +    case SUBDEV_PROP_GAIN_NAMES: +        val = prop_names_t(get_tvrx_gain_ranges().keys()); +        return; + +    case SUBDEV_PROP_FREQ: +    /*  +     * so here we have to do some magic. because the TVRX uses a relatively high IF, +     * we have to watch the sample rate to see if the IF will be aliased +     * or if it will fall within Nyquist. +     */ +        codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); +        val = (_lo_freq - tvrx_if_freq) + get_alias(tvrx_if_freq, codec_rate); +        return; + +    case SUBDEV_PROP_FREQ_RANGE: +        val = tvrx_freq_range; +        return; + +    case SUBDEV_PROP_ANTENNA: +        val = std::string(""); +        return; + +    case SUBDEV_PROP_ANTENNA_NAMES: +        val = tvrx_antennas; +        return; + +    case SUBDEV_PROP_CONNECTION: +        val = SUBDEV_CONN_COMPLEX_IQ; +        return; + +    case SUBDEV_PROP_USE_LO_OFFSET: +        val = false; +        return; + +    case SUBDEV_PROP_LO_LOCKED: +        val = true; +        return; + +    default: UHD_THROW_PROP_GET_ERROR(); +    } +} + +void tvrx::rx_set(const wax::obj &key_, const 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_GAIN: +        this->set_gain(val.as<float>(), key.name); +        return; +    case SUBDEV_PROP_FREQ: +        this->set_freq(val.as<double>()); +        return; + +    default: UHD_THROW_PROP_SET_ERROR(); +    } +} + diff --git a/host/lib/usrp/usrp1/codec_ctrl.cpp b/host/lib/usrp/usrp1/codec_ctrl.cpp index ad16f6b3a..4aa730573 100644 --- a/host/lib/usrp/usrp1/codec_ctrl.cpp +++ b/host/lib/usrp/usrp1/codec_ctrl.cpp @@ -61,6 +61,9 @@ public:      float get_tx_pga_gain(void);      void set_rx_pga_gain(float, char);      float get_rx_pga_gain(char); +     +    //rx adc buffer control +    void bypass_adc_buffers(bool bypass);  private:      usrp1_iface::sptr _iface; @@ -419,6 +422,17 @@ void usrp1_codec_ctrl_impl::set_duc_freq(double freq)  }  /*********************************************************************** + * Codec Control ADC buffer bypass + * Disable this for AC-coupled daughterboards (TVRX) + * By default it is initialized TRUE. + **********************************************************************/ +void usrp1_codec_ctrl_impl::bypass_adc_buffers(bool bypass) { +    _ad9862_regs.byp_buffer_a = bypass; +    _ad9862_regs.byp_buffer_b = bypass; +    this->send_reg(2); +} + +/***********************************************************************   * Codec Control Make   **********************************************************************/  usrp1_codec_ctrl::sptr usrp1_codec_ctrl::make(usrp1_iface::sptr iface, diff --git a/host/lib/usrp/usrp1/codec_ctrl.hpp b/host/lib/usrp/usrp1/codec_ctrl.hpp index 259d10ef4..e2e8a010d 100644 --- a/host/lib/usrp/usrp1/codec_ctrl.hpp +++ b/host/lib/usrp/usrp1/codec_ctrl.hpp @@ -92,6 +92,9 @@ public:      //! Set the TX modulator frequency      virtual void set_duc_freq(double freq) = 0; +     +    //! Enable or disable ADC buffer bypass +    virtual void bypass_adc_buffers(bool bypass) = 0;  };  #endif /* INCLUDED_USRP1_CODEC_CTRL_HPP */ diff --git a/host/lib/usrp/usrp1/dboard_iface.cpp b/host/lib/usrp/usrp1/dboard_iface.cpp index 4791b55ce..1ac15a46a 100644 --- a/host/lib/usrp/usrp1/dboard_iface.cpp +++ b/host/lib/usrp/usrp1/dboard_iface.cpp @@ -32,6 +32,8 @@ using namespace uhd;  using namespace uhd::usrp;  using namespace boost::assign; +static const dboard_id_t tvrx_id(0x0040); +  class usrp1_dboard_iface : public dboard_iface {  public: @@ -51,6 +53,10 @@ public:          //init the clock rate shadows          this->set_clock_rate(UNIT_RX, this->get_clock_rates(UNIT_RX).front());          this->set_clock_rate(UNIT_TX, this->get_clock_rates(UNIT_TX).front()); +         +        //yes this is evil but it's necessary for TVRX to work on USRP1 +        if(_rx_dboard_id == tvrx_id) _codec->bypass_adc_buffers(false); +        //else _codec->bypass_adc_buffers(false); //don't think this is necessary      }      ~usrp1_dboard_iface() @@ -93,6 +99,7 @@ public:      std::vector<double> get_clock_rates(unit_t);      double get_clock_rate(unit_t);      void set_clock_enabled(unit_t, bool); +    double get_codec_rate(unit_t);  private:      usrp1_iface::sptr _iface; @@ -170,6 +177,10 @@ void usrp1_dboard_iface::set_clock_enabled(unit_t, bool)      //TODO we can only enable for special case anyway...  } +double usrp1_dboard_iface::get_codec_rate(unit_t){ +    return _clock->get_master_clock_freq(); +} +  /***********************************************************************   * GPIO   **********************************************************************/ diff --git a/host/lib/usrp/usrp1/io_impl.cpp b/host/lib/usrp/usrp1/io_impl.cpp index 73974f2d6..aee760a83 100644 --- a/host/lib/usrp/usrp1/io_impl.cpp +++ b/host/lib/usrp/usrp1/io_impl.cpp @@ -272,18 +272,18 @@ static void usrp1_bs_vrt_unpacker(  }  static bool get_recv_buffs( -    zero_copy_if::sptr zc_if, +    zero_copy_if::sptr zc_if, size_t timeout_ms,      vrt_packet_handler::managed_recv_buffs_t &buffs  ){      UHD_ASSERT_THROW(buffs.size() == 1); -    buffs[0] = zc_if->get_recv_buff(); +    buffs[0] = zc_if->get_recv_buff(timeout_ms);      return buffs[0].get() != NULL;  }  size_t usrp1_impl::recv(      const std::vector<void *> &buffs, size_t num_samps,      rx_metadata_t &metadata, const io_type_t &io_type, -    recv_mode_t recv_mode, size_t /*timeout_ms TODO*/ +    recv_mode_t recv_mode, size_t timeout_ms  ){      size_t num_samps_recvd = vrt_packet_handler::recv(          _io_impl->packet_handler_recv_state,       //last state of the recv handler @@ -292,7 +292,7 @@ size_t usrp1_impl::recv(          io_type, _rx_otw_type,                     //input and output types to convert          _clock_ctrl->get_master_clock_freq(),      //master clock tick rate          &usrp1_bs_vrt_unpacker, -        boost::bind(&get_recv_buffs, _data_transport, _1), +        boost::bind(&get_recv_buffs, _data_transport, timeout_ms, _1),          &vrt_packet_handler::handle_overflow_nop,          0,                                         //vrt header offset          _rx_subdev_spec.size()                     //num channels diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp index 793e3027d..156fc119f 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.cpp +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -83,9 +83,7 @@ static device_addrs_t usrp1_find(const device_addr_t &hint)      //find the usrps and load firmware      BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { -            usb_control::sptr ctrl_transport = usb_control::make(handle); -            usrp_ctrl::sptr usrp_ctrl = usrp_ctrl::make(ctrl_transport); -            usrp_ctrl->usrp_load_firmware(usrp1_fw_image); +        usrp_ctrl::make(usb_control::make(handle))->usrp_load_firmware(usrp1_fw_image);      }      //get descriptors again with serial number, but using the initialized VID/PID now since we have firmware @@ -93,13 +91,13 @@ static device_addrs_t usrp1_find(const device_addr_t &hint)      pid = USRP1_PRODUCT_ID;      BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) { -            device_addr_t new_addr; -            new_addr["type"] = "usrp1"; -            new_addr["serial"] = handle->get_serial(); -            //this is a found usrp1 when a hint serial is not specified or it matches -            if (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]){ -                usrp1_addrs.push_back(new_addr); -            } +        device_addr_t new_addr; +        new_addr["type"] = "usrp1"; +        new_addr["serial"] = handle->get_serial(); +        //this is a found usrp1 when a hint serial is not specified or it matches +        if (not hint.has_key("serial") or hint["serial"] == new_addr["serial"]){ +            usrp1_addrs.push_back(new_addr); +        }      }      return usrp1_addrs; @@ -109,12 +107,12 @@ static device_addrs_t usrp1_find(const device_addr_t &hint)   * Make   **********************************************************************/  template<typename output_type> static output_type cast_from_dev_addr( -	const device_addr_t &device_addr, -	const std::string &key, -	output_type def_val +    const device_addr_t &device_addr, +    const std::string &key, +    output_type def_val  ){ -	return (device_addr.has_key(key))? -		boost::lexical_cast<output_type>(device_addr[key]) : def_val; +    return (device_addr.has_key(key))? +        boost::lexical_cast<output_type>(device_addr[key]) : def_val;  }  static device::sptr usrp1_make(const device_addr_t &device_addr) diff --git a/host/lib/usrp/usrp2/dboard_iface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp index c79044bce..ab5f62355 100644 --- a/host/lib/usrp/usrp2/dboard_iface.cpp +++ b/host/lib/usrp/usrp2/dboard_iface.cpp @@ -61,6 +61,7 @@ public:      double get_clock_rate(unit_t);      std::vector<double> get_clock_rates(unit_t);      void set_clock_enabled(unit_t, bool); +    double get_codec_rate(unit_t);      void write_spi(          unit_t unit, @@ -158,6 +159,9 @@ void usrp2_dboard_iface::set_clock_enabled(unit_t unit, bool enb){      }  } +double usrp2_dboard_iface::get_codec_rate(unit_t){ +    return _clock_ctrl->get_master_clock_rate(); +}  /***********************************************************************   * GPIO   **********************************************************************/ diff --git a/host/lib/usrp/usrp2/io_impl.cpp b/host/lib/usrp/usrp2/io_impl.cpp index b0603f951..7d9a426a3 100644 --- a/host/lib/usrp/usrp2/io_impl.cpp +++ b/host/lib/usrp/usrp2/io_impl.cpp @@ -33,6 +33,7 @@ using namespace uhd::transport;  namespace asio = boost::asio;  static const int underflow_flags = async_metadata_t::EVENT_CODE_UNDERFLOW | async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET; +static const double RECV_TIMEOUT_MS = 100;  /***********************************************************************   * io impl details (internal to this file) @@ -90,7 +91,7 @@ void usrp2_impl::io_impl::recv_pirate_loop(      size_t next_packet_seq = 0;      while(recv_pirate_crew_raiding){ -        managed_recv_buffer::sptr buff = zc_if->get_recv_buff(); +        managed_recv_buffer::sptr buff = zc_if->get_recv_buff(RECV_TIMEOUT_MS);          if (not buff.get()) continue; //ignore timeout/error buffers          try{ @@ -151,7 +152,7 @@ void usrp2_impl::io_init(void){          send_buff->commit(sizeof(data));          //drain the recv buffers (may have junk) -        while (data_transport->get_recv_buff().get()){}; +        while (data_transport->get_recv_buff(RECV_TIMEOUT_MS).get()){};      }      //the number of recv frames is the number for the first transport diff --git a/host/lib/version.cpp b/host/lib/version.cpp index 5edbca09b..93fdecb1a 100644 --- a/host/lib/version.cpp +++ b/host/lib/version.cpp @@ -21,3 +21,17 @@  std::string uhd::get_version_string(void){      return UHD_VERSION_STRING;  } + +#include <uhd/utils/static.hpp> +#include <boost/version.hpp> +#include <iostream> + +UHD_STATIC_BLOCK(print_system_info){ +    std::cout +        << BOOST_PLATFORM << "; " +        << BOOST_COMPILER << "; " +        << "Boost_" << BOOST_VERSION << "; " +        << "UHD_" << uhd::get_version_string() +        << std::endl << std::endl +    ; +} | 
