diff options
Diffstat (limited to 'Transceiver52M/device')
-rw-r--r-- | Transceiver52M/device/Makefile.am | 11 | ||||
-rw-r--r-- | Transceiver52M/device/radioDevice.h | 160 | ||||
-rw-r--r-- | Transceiver52M/device/uhd/Makefile.am | 8 | ||||
-rw-r--r-- | Transceiver52M/device/uhd/UHDDevice.cpp | 1576 | ||||
-rw-r--r-- | Transceiver52M/device/usrp1/Makefile.am | 10 | ||||
-rw-r--r-- | Transceiver52M/device/usrp1/USRPDevice.cpp | 648 | ||||
-rw-r--r-- | Transceiver52M/device/usrp1/USRPDevice.h | 204 |
7 files changed, 2617 insertions, 0 deletions
diff --git a/Transceiver52M/device/Makefile.am b/Transceiver52M/device/Makefile.am new file mode 100644 index 0000000..8575328 --- /dev/null +++ b/Transceiver52M/device/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/Makefile.common + +noinst_HEADERS = radioDevice.h + +SUBDIRS = + +if USRP1 +SUBDIRS += usrp1 +else +SUBDIRS += uhd +endif diff --git a/Transceiver52M/device/radioDevice.h b/Transceiver52M/device/radioDevice.h new file mode 100644 index 0000000..9913de0 --- /dev/null +++ b/Transceiver52M/device/radioDevice.h @@ -0,0 +1,160 @@ +/* +* Copyright 2008 Free Software Foundation, Inc. +* +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + 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. + +*/ + +#ifndef __RADIO_DEVICE_H__ +#define __RADIO_DEVICE_H__ + +#include <string> +#include <vector> + +extern "C" { +#include "config_defs.h" +} + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define GSMRATE (1625e3/6) +#define MCBTS_SPACING 800000.0 + +/** a 64-bit virtual timestamp for radio data */ +typedef unsigned long long TIMESTAMP; + +/** A class to handle a USRP rev 4, with a two RFX900 daughterboards */ +class RadioDevice { + + public: + /* Available transport bus types */ + enum TxWindowType { TX_WINDOW_USRP1, TX_WINDOW_FIXED }; + + /* Radio interface types */ + enum InterfaceType { + NORMAL, + RESAMP_64M, + RESAMP_100M, + MULTI_ARFCN, + }; + + static RadioDevice *make(size_t tx_sps, size_t rx_sps, InterfaceType type, + size_t chans = 1, double offset = 0.0, + const std::vector<std::string>& tx_paths = std::vector<std::string>(1, ""), + const std::vector<std::string>& rx_paths = std::vector<std::string>(1, "")); + + /** Initialize the USRP */ + virtual int open(const std::string &args, int ref, bool swap_channels)=0; + + virtual ~RadioDevice() { } + + /** Start the USRP */ + virtual bool start()=0; + + /** Stop the USRP */ + virtual bool stop()=0; + + /** Get the Tx window type */ + virtual enum TxWindowType getWindowType()=0; + + /** Enable thread priority */ + virtual void setPriority(float prio = 0.5) = 0; + + /** + Read samples from the radio. + @param buf preallocated buf to contain read result + @param len number of samples desired + @param overrun Set if read buffer has been overrun, e.g. data not being read fast enough + @param timestamp The timestamp of the first samples to be read + @param underrun Set if radio does not have data to transmit, e.g. data not being sent fast enough + @param RSSI The received signal strength of the read result + @return The number of samples actually read + */ + virtual int readSamples(std::vector<short *> &bufs, int len, bool *overrun, + TIMESTAMP timestamp = 0xffffffff, bool *underrun = 0, + unsigned *RSSI = 0) = 0; + /** + Write samples to the radio. + @param buf Contains the data to be written. + @param len number of samples to write. + @param underrun Set if radio does not have data to transmit, e.g. data not being sent fast enough + @param timestamp The timestamp of the first sample of the data buffer. + @param isControl Set if data is a control packet, e.g. a ping command + @return The number of samples actually written + */ + virtual int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, + TIMESTAMP timestamp, bool isControl = false) = 0; + + /** Update the alignment between the read and write timestamps */ + virtual bool updateAlignment(TIMESTAMP timestamp)=0; + + /** Set the transmitter frequency */ + virtual bool setTxFreq(double wFreq, size_t chan = 0) = 0; + + /** Set the receiver frequency */ + virtual bool setRxFreq(double wFreq, size_t chan = 0) = 0; + + /** Returns the starting write Timestamp*/ + virtual TIMESTAMP initialWriteTimestamp(void)=0; + + /** Returns the starting read Timestamp*/ + virtual TIMESTAMP initialReadTimestamp(void)=0; + + /** returns the full-scale transmit amplitude **/ + virtual double fullScaleInputValue()=0; + + /** returns the full-scale receive amplitude **/ + virtual double fullScaleOutputValue()=0; + + /** sets the receive chan gain, returns the gain setting **/ + virtual double setRxGain(double dB, size_t chan = 0) = 0; + + /** gets the current receive gain **/ + virtual double getRxGain(size_t chan = 0) = 0; + + /** return maximum Rx Gain **/ + virtual double maxRxGain(void) = 0; + + /** return minimum Rx Gain **/ + virtual double minRxGain(void) = 0; + + /** sets the transmit chan gain, returns the gain setting **/ + virtual double setTxGain(double dB, size_t chan = 0) = 0; + + /** return maximum Tx Gain **/ + virtual double maxTxGain(void) = 0; + + /** return minimum Tx Gain **/ + virtual double minTxGain(void) = 0; + + /** sets the RX path to use, returns true if successful and false otherwise */ + virtual bool setRxAntenna(const std::string &ant, size_t chan = 0) = 0; + + /** return the used RX path */ + virtual std::string getRxAntenna(size_t chan = 0) = 0; + + /** sets the RX path to use, returns true if successful and false otherwise */ + virtual bool setTxAntenna(const std::string &ant, size_t chan = 0) = 0; + + /** return the used RX path */ + virtual std::string getTxAntenna(size_t chan = 0) = 0; + + /** Return internal status values */ + virtual double getTxFreq(size_t chan = 0) = 0; + virtual double getRxFreq(size_t chan = 0) = 0; + virtual double getSampleRate()=0; + virtual double numberRead()=0; + virtual double numberWritten()=0; + +}; + +#endif diff --git a/Transceiver52M/device/uhd/Makefile.am b/Transceiver52M/device/uhd/Makefile.am new file mode 100644 index 0000000..bb34d2f --- /dev/null +++ b/Transceiver52M/device/uhd/Makefile.am @@ -0,0 +1,8 @@ +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/.. +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(UHD_CFLAGS) + +noinst_LTLIBRARIES = libdevice.la + +libdevice_la_SOURCES = UHDDevice.cpp diff --git a/Transceiver52M/device/uhd/UHDDevice.cpp b/Transceiver52M/device/uhd/UHDDevice.cpp new file mode 100644 index 0000000..4466da4 --- /dev/null +++ b/Transceiver52M/device/uhd/UHDDevice.cpp @@ -0,0 +1,1576 @@ +/* + * Device support for Ettus Research UHD driver + * + * Copyright 2010,2011 Free Software Foundation, Inc. + * Copyright (C) 2015 Ettus Research LLC + * + * Author: Tom Tsou <tom.tsou@ettus.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * See the COPYING file in the main directory for details. + */ + +#include <map> +#include "radioDevice.h" +#include "Threads.h" +#include "Logger.h" +#include <uhd/version.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/multi_usrp.hpp> +#include <uhd/utils/thread_priority.hpp> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef USE_UHD_3_11 +#include <uhd/utils/msg.hpp> +#endif + +#define USRP_TX_AMPL 0.3 +#define UMTRX_TX_AMPL 0.7 +#define LIMESDR_TX_AMPL 0.3 +#define SAMPLE_BUF_SZ (1 << 20) + +/* + * UHD timeout value on streaming (re)start + * + * Allow some time for streaming to commence after the start command is issued, + * but consider a wait beyond one second to be a definite error condition. + */ +#define UHD_RESTART_TIMEOUT 1.0 + +/* + * UmTRX specific settings + */ +#define UMTRX_VGA1_DEF -18 + +enum uhd_dev_type { + USRP1, + USRP2, + B100, + B200, + B210, + B2XX_MCBTS, + E1XX, + E3XX, + X3XX, + UMTRX, + LIMESDR, +}; + +/* + * USRP version dependent device timings + */ +#if defined(USE_UHD_3_9) || defined(USE_UHD_3_11) +#define B2XX_TIMING_1SPS 1.7153e-4 +#define B2XX_TIMING_4SPS 1.1696e-4 +#define B2XX_TIMING_4_4SPS 6.18462e-5 +#define B2XX_TIMING_MCBTS 7e-5 +#else +#define B2XX_TIMING_1SPS 9.9692e-5 +#define B2XX_TIMING_4SPS 6.9248e-5 +#define B2XX_TIMING_4_4SPS 4.52308e-5 +#define B2XX_TIMING_MCBTS 6.42452e-5 +#endif + +/* + * Tx / Rx sample offset values. In a perfect world, there is no group delay + * though analog components, and behaviour through digital filters exactly + * matches calculated values. In reality, there are unaccounted factors, + * which are captured in these empirically measured (using a loopback test) + * timing correction values. + * + * Notes: + * USRP1 with timestamps is not supported by UHD. + */ + +/* Device Type, Tx-SPS, Rx-SPS */ +typedef std::tuple<uhd_dev_type, int, int> dev_key; + +/* Device parameter descriptor */ +struct dev_desc { + unsigned channels; + double mcr; + double rate; + double offset; + std::string str; +}; + +static const std::map<dev_key, dev_desc> dev_param_map { + { std::make_tuple(USRP2, 1, 1), { 1, 0.0, 390625, 1.2184e-4, "N2XX 1 SPS" } }, + { std::make_tuple(USRP2, 4, 1), { 1, 0.0, 390625, 7.6547e-5, "N2XX 4/1 Tx/Rx SPS" } }, + { std::make_tuple(USRP2, 4, 4), { 1, 0.0, 390625, 4.6080e-5, "N2XX 4 SPS" } }, + { std::make_tuple(B100, 1, 1), { 1, 0.0, 400000, 1.2104e-4, "B100 1 SPS" } }, + { std::make_tuple(B100, 4, 1), { 1, 0.0, 400000, 7.9307e-5, "B100 4/1 Tx/Rx SPS" } }, + { std::make_tuple(B200, 1, 1), { 1, 26e6, GSMRATE, B2XX_TIMING_1SPS, "B200 1 SPS" } }, + { std::make_tuple(B200, 4, 1), { 1, 26e6, GSMRATE, B2XX_TIMING_4SPS, "B200 4/1 Tx/Rx SPS" } }, + { std::make_tuple(B200, 4, 4), { 1, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B200 4 SPS" } }, + { std::make_tuple(B210, 1, 1), { 2, 26e6, GSMRATE, B2XX_TIMING_1SPS, "B210 1 SPS" } }, + { std::make_tuple(B210, 4, 1), { 2, 26e6, GSMRATE, B2XX_TIMING_4SPS, "B210 4/1 Tx/Rx SPS" } }, + { std::make_tuple(B210, 4, 4), { 2, 26e6, GSMRATE, B2XX_TIMING_4_4SPS, "B210 4 SPS" } }, + { std::make_tuple(E1XX, 1, 1), { 1, 52e6, GSMRATE, 9.5192e-5, "E1XX 1 SPS" } }, + { std::make_tuple(E1XX, 4, 1), { 1, 52e6, GSMRATE, 6.5571e-5, "E1XX 4/1 Tx/Rx SPS" } }, + { std::make_tuple(E3XX, 1, 1), { 2, 26e6, GSMRATE, 1.8462e-4, "E3XX 1 SPS" } }, + { std::make_tuple(E3XX, 4, 1), { 2, 26e6, GSMRATE, 1.2923e-4, "E3XX 4/1 Tx/Rx SPS" } }, + { std::make_tuple(X3XX, 1, 1), { 2, 0.0, 390625, 1.5360e-4, "X3XX 1 SPS" } }, + { std::make_tuple(X3XX, 4, 1), { 2, 0.0, 390625, 1.1264e-4, "X3XX 4/1 Tx/Rx SPS" } }, + { std::make_tuple(X3XX, 4, 4), { 2, 0.0, 390625, 5.6567e-5, "X3XX 4 SPS" } }, + { std::make_tuple(UMTRX, 1, 1), { 2, 0.0, GSMRATE, 9.9692e-5, "UmTRX 1 SPS" } }, + { std::make_tuple(UMTRX, 4, 1), { 2, 0.0, GSMRATE, 7.3846e-5, "UmTRX 4/1 Tx/Rx SPS"} }, + { std::make_tuple(UMTRX, 4, 4), { 2, 0.0, GSMRATE, 5.1503e-5, "UmTRX 4 SPS" } }, + { std::make_tuple(LIMESDR, 4, 4), { 1, GSMRATE*32, GSMRATE, 8.9e-5, "LimeSDR 4 SPS" } }, + { std::make_tuple(B2XX_MCBTS, 4, 4), { 1, 51.2e6, MCBTS_SPACING*4, B2XX_TIMING_MCBTS, "B200/B210 4 SPS Multi-ARFCN" } }, +}; + +/* + Sample Buffer - Allows reading and writing of timed samples using osmo-trx + or UHD style timestamps. Time conversions are handled + internally or accessable through the static convert calls. +*/ +class smpl_buf { +public: + /** Sample buffer constructor + @param len number of 32-bit samples the buffer should hold + @param rate sample clockrate + @param timestamp + */ + smpl_buf(size_t len, double rate); + ~smpl_buf(); + + /** Query number of samples available for reading + @param timestamp time of first sample + @return number of available samples or error + */ + ssize_t avail_smpls(TIMESTAMP timestamp) const; + ssize_t avail_smpls(uhd::time_spec_t timestamp) const; + + /** Read and write + @param buf pointer to buffer + @param len number of samples desired to read or write + @param timestamp time of first stample + @return number of actual samples read or written or error + */ + ssize_t read(void *buf, size_t len, TIMESTAMP timestamp); + ssize_t read(void *buf, size_t len, uhd::time_spec_t timestamp); + ssize_t write(void *buf, size_t len, TIMESTAMP timestamp); + ssize_t write(void *buf, size_t len, uhd::time_spec_t timestamp); + + /** Buffer status string + @return a formatted string describing internal buffer state + */ + std::string str_status(size_t ts) const; + + /** Formatted error string + @param code an error code + @return a formatted error string + */ + static std::string str_code(ssize_t code); + + enum err_code { + ERROR_TIMESTAMP = -1, + ERROR_READ = -2, + ERROR_WRITE = -3, + ERROR_OVERFLOW = -4 + }; + +private: + uint32_t *data; + size_t buf_len; + + double clk_rt; + + TIMESTAMP time_start; + TIMESTAMP time_end; + + size_t data_start; + size_t data_end; +}; + +/* + uhd_device - UHD implementation of the Device interface. Timestamped samples + are sent to and received from the device. An intermediate buffer + on the receive side collects and aligns packets of samples. + Events and errors such as underruns are reported asynchronously + by the device and received in a separate thread. +*/ +class uhd_device : public RadioDevice { +public: + uhd_device(size_t tx_sps, size_t rx_sps, InterfaceType type, + size_t chans, double offset, + const std::vector<std::string>& tx_paths, + const std::vector<std::string>& rx_paths); + ~uhd_device(); + + int open(const std::string &args, int ref, bool swap_channels); + bool start(); + bool stop(); + bool restart(); + void setPriority(float prio); + enum TxWindowType getWindowType() { return tx_window; } + + int readSamples(std::vector<short *> &bufs, int len, bool *overrun, + TIMESTAMP timestamp, bool *underrun, unsigned *RSSI); + + int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, + TIMESTAMP timestamp, bool isControl); + + bool updateAlignment(TIMESTAMP timestamp); + + bool setTxFreq(double wFreq, size_t chan); + bool setRxFreq(double wFreq, size_t chan); + + TIMESTAMP initialWriteTimestamp(); + TIMESTAMP initialReadTimestamp(); + + double fullScaleInputValue(); + double fullScaleOutputValue(); + + double setRxGain(double db, size_t chan); + double getRxGain(size_t chan); + double maxRxGain(void) { return rx_gain_max; } + double minRxGain(void) { return rx_gain_min; } + + double setTxGain(double db, size_t chan); + double maxTxGain(void) { return tx_gain_max; } + double minTxGain(void) { return tx_gain_min; } + + double getTxFreq(size_t chan); + double getRxFreq(size_t chan); + double getRxFreq(); + + bool setRxAntenna(const std::string &ant, size_t chan); + std::string getRxAntenna(size_t chan); + bool setTxAntenna(const std::string &ant, size_t chan); + std::string getTxAntenna(size_t chan); + + inline double getSampleRate() { return tx_rate; } + inline double numberRead() { return rx_pkt_cnt; } + inline double numberWritten() { return 0; } + + /** Receive and process asynchronous message + @return true if message received or false on timeout or error + */ + bool recv_async_msg(); + + enum err_code { + ERROR_TIMING = -1, + ERROR_TIMEOUT = -2, + ERROR_UNRECOVERABLE = -3, + ERROR_UNHANDLED = -4, + }; + +private: + uhd::usrp::multi_usrp::sptr usrp_dev; + uhd::tx_streamer::sptr tx_stream; + uhd::rx_streamer::sptr rx_stream; + enum TxWindowType tx_window; + enum uhd_dev_type dev_type; + + size_t tx_sps, rx_sps, chans; + double tx_rate, rx_rate; + + double tx_gain_min, tx_gain_max; + double rx_gain_min, rx_gain_max; + double offset; + + std::vector<double> tx_gains, rx_gains; + std::vector<double> tx_freqs, rx_freqs; + std::vector<std::string> tx_paths, rx_paths; + size_t tx_spp, rx_spp; + + bool started; + bool aligned; + + size_t rx_pkt_cnt; + size_t drop_cnt; + uhd::time_spec_t prev_ts; + + TIMESTAMP ts_initial, ts_offset; + std::vector<smpl_buf *> rx_buffers; + + void init_gains(); + void set_channels(bool swap); + void set_rates(); + bool set_antennas(); + bool parse_dev_type(); + bool flush_recv(size_t num_pkts); + int check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls); + + std::string str_code(uhd::rx_metadata_t metadata); + std::string str_code(uhd::async_metadata_t metadata); + + uhd::tune_request_t select_freq(double wFreq, size_t chan, bool tx); + bool set_freq(double freq, size_t chan, bool tx); + + Thread *async_event_thrd; + InterfaceType iface; + Mutex tune_lock; +}; + +void *async_event_loop(uhd_device *dev) +{ + dev->setPriority(0.43); + + while (1) { + dev->recv_async_msg(); + pthread_testcancel(); + } + + return NULL; +} + +#ifndef USE_UHD_3_11 +/* + Catch and drop underrun 'U' and overrun 'O' messages from stdout + since we already report using the logging facility. Direct + everything else appropriately. + */ +void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg) +{ + switch (type) { + case uhd::msg::status: + LOG(INFO) << msg; + break; + case uhd::msg::warning: + LOG(WARNING) << msg; + break; + case uhd::msg::error: + LOG(ERR) << msg; + break; + case uhd::msg::fastpath: + break; + } +} +#endif + +static void thread_enable_cancel(bool cancel) +{ + cancel ? pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) : + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); +} + +uhd_device::uhd_device(size_t tx_sps, size_t rx_sps, + InterfaceType iface, size_t chans, double offset, + const std::vector<std::string>& tx_paths, + const std::vector<std::string>& rx_paths) + : tx_gain_min(0.0), tx_gain_max(0.0), + rx_gain_min(0.0), rx_gain_max(0.0), + tx_spp(0), rx_spp(0), + started(false), aligned(false), rx_pkt_cnt(0), drop_cnt(0), + prev_ts(0,0), ts_initial(0), ts_offset(0), async_event_thrd(NULL) +{ + this->tx_sps = tx_sps; + this->rx_sps = rx_sps; + this->chans = chans; + this->offset = offset; + this->iface = iface; + this->tx_paths = tx_paths; + this->rx_paths = rx_paths; +} + +uhd_device::~uhd_device() +{ + stop(); + + for (size_t i = 0; i < rx_buffers.size(); i++) + delete rx_buffers[i]; +} + +void uhd_device::init_gains() +{ + uhd::gain_range_t range; + + if (dev_type == UMTRX) { + std::vector<std::string> gain_stages = usrp_dev->get_tx_gain_names(0); + if (gain_stages[0] == "VGA") { + LOG(WARNING) << "Update your UHD version for a proper Tx gain support"; + } + if (gain_stages[0] == "VGA" || gain_stages[0] == "PA") { + range = usrp_dev->get_tx_gain_range(); + tx_gain_min = range.start(); + tx_gain_max = range.stop(); + } else { + range = usrp_dev->get_tx_gain_range("VGA2"); + tx_gain_min = UMTRX_VGA1_DEF + range.start(); + tx_gain_max = UMTRX_VGA1_DEF + range.stop(); + } + } else { + range = usrp_dev->get_tx_gain_range(); + tx_gain_min = range.start(); + tx_gain_max = range.stop(); + } + LOG(INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]"; + + range = usrp_dev->get_rx_gain_range(); + rx_gain_min = range.start(); + rx_gain_max = range.stop(); + LOG(INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]"; + + for (size_t i = 0; i < tx_gains.size(); i++) { + double gain = (tx_gain_min + tx_gain_max) / 2; + LOG(INFO) << "Default setting Tx gain for channel " << i << " to " << gain; + usrp_dev->set_tx_gain(gain, i); + tx_gains[i] = usrp_dev->get_tx_gain(i); + } + + for (size_t i = 0; i < rx_gains.size(); i++) { + double gain = (rx_gain_min + rx_gain_max) / 2; + LOG(INFO) << "Default setting Rx gain for channel " << i << " to " << gain; + usrp_dev->set_rx_gain(gain, i); + rx_gains[i] = usrp_dev->get_rx_gain(i); + } + + return; + +} + +void uhd_device::set_rates() +{ + dev_desc desc = dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps)); + if (desc.mcr != 0.0) + usrp_dev->set_master_clock_rate(desc.mcr); + + tx_rate = (dev_type != B2XX_MCBTS) ? desc.rate * tx_sps : desc.rate; + rx_rate = (dev_type != B2XX_MCBTS) ? desc.rate * rx_sps : desc.rate; + + usrp_dev->set_tx_rate(tx_rate); + usrp_dev->set_rx_rate(rx_rate); + tx_rate = usrp_dev->get_tx_rate(); + rx_rate = usrp_dev->get_rx_rate(); + + ts_offset = static_cast<TIMESTAMP>(desc.offset * rx_rate); + LOG(INFO) << "Rates configured for " << desc.str; +} + +bool uhd_device::set_antennas() +{ + unsigned int i; + + for (i = 0; i < tx_paths.size(); i++) { + if (tx_paths[i] == "") + continue; + LOG(DEBUG) << "Configuring channel " << i << " with antenna " << tx_paths[i]; + if (!setTxAntenna(tx_paths[i], i)) { + LOG(ALERT) << "Failed configuring channel " << i << " with antenna " << tx_paths[i]; + return false; + } + } + + for (i = 0; i < rx_paths.size(); i++) { + if (rx_paths[i] == "") + continue; + LOG(DEBUG) << "Configuring channel " << i << " with antenna " << rx_paths[i]; + if (!setRxAntenna(rx_paths[i], i)) { + LOG(ALERT) << "Failed configuring channel " << i << " with antenna " << rx_paths[i]; + return false; + } + } + LOG(INFO) << "Antennas configured successfully"; + return true; +} + +double uhd_device::setTxGain(double db, size_t chan) +{ + if (iface == MULTI_ARFCN) + chan = 0; + + if (chan >= tx_gains.size()) { + LOG(ALERT) << "Requested non-existent channel" << chan; + return 0.0f; + } + + if (dev_type == UMTRX) { + std::vector<std::string> gain_stages = usrp_dev->get_tx_gain_names(0); + if (gain_stages[0] == "VGA" || gain_stages[0] == "PA") { + usrp_dev->set_tx_gain(db, chan); + } else { + // New UHD versions support split configuration of + // Tx gain stages. We utilize this to set the gain + // configuration, optimal for the Tx signal quality. + // From our measurements, VGA1 must be 18dB plus-minus + // one and VGA2 is the best when 23dB or lower. + usrp_dev->set_tx_gain(UMTRX_VGA1_DEF, "VGA1", chan); + usrp_dev->set_tx_gain(db-UMTRX_VGA1_DEF, "VGA2", chan); + } + } else { + usrp_dev->set_tx_gain(db, chan); + } + + tx_gains[chan] = usrp_dev->get_tx_gain(chan); + + LOG(INFO) << "Set TX gain to " << tx_gains[chan] << "dB (asked for " << db << "dB)"; + + return tx_gains[chan]; +} + +double uhd_device::setRxGain(double db, size_t chan) +{ + if (chan >= rx_gains.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + usrp_dev->set_rx_gain(db, chan); + rx_gains[chan] = usrp_dev->get_rx_gain(chan); + + LOG(INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)"; + + return rx_gains[chan]; +} + +double uhd_device::getRxGain(size_t chan) +{ + if (iface == MULTI_ARFCN) + chan = 0; + + if (chan >= rx_gains.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return 0.0f; + } + + return rx_gains[chan]; +} + +/* + Parse the UHD device tree and mboard name to find out what device we're + dealing with. We need the window type so that the transceiver knows how to + deal with the transport latency. Reject the USRP1 because UHD doesn't + support timestamped samples with it. + */ +bool uhd_device::parse_dev_type() +{ + uhd::property_tree::sptr prop_tree = usrp_dev->get_device()->get_tree(); + std::string devString = prop_tree->access<std::string>("/name").get(); + std::string mboardString = usrp_dev->get_mboard_name(); + + const std::map<std::string, std::pair<uhd_dev_type, TxWindowType>> devStringMap { + { "B100", { B100, TX_WINDOW_USRP1 } }, + { "B200", { B200, TX_WINDOW_USRP1 } }, + { "B200mini", { B200, TX_WINDOW_USRP1 } }, + { "B205mini", { B200, TX_WINDOW_USRP1 } }, + { "B210", { B210, TX_WINDOW_USRP1 } }, + { "E100", { E1XX, TX_WINDOW_FIXED } }, + { "E110", { E1XX, TX_WINDOW_FIXED } }, + { "E310", { E3XX, TX_WINDOW_FIXED } }, + { "E3XX", { E3XX, TX_WINDOW_FIXED } }, + { "X300", { X3XX, TX_WINDOW_FIXED } }, + { "X310", { X3XX, TX_WINDOW_FIXED } }, + { "USRP2", { USRP2, TX_WINDOW_FIXED } }, + { "UmTRX", { UMTRX, TX_WINDOW_FIXED } }, + { "LimeSDR", { LIMESDR, TX_WINDOW_FIXED } }, + }; + + // Compare UHD motherboard and device strings */ + auto mapIter = devStringMap.begin(); + while (mapIter != devStringMap.end()) { + if (devString.find(mapIter->first) != std::string::npos || + mboardString.find(mapIter->first) != std::string::npos) { + dev_type = std::get<0>(mapIter->second); + tx_window = std::get<1>(mapIter->second); + return true; + } + mapIter++; + } + + LOG(ALERT) << "Unsupported device " << devString; + return false; +} + +/* + * Check for UHD version > 3.9.0 for E3XX support + */ +static bool uhd_e3xx_version_chk() +{ + std::string ver = uhd::get_version_string(); + std::string major_str(ver.begin(), ver.begin() + 3); + std::string minor_str(ver.begin() + 4, ver.begin() + 7); + + int major_val = atoi(major_str.c_str()); + int minor_val = atoi(minor_str.c_str()); + + if (major_val < 3) + return false; + if (minor_val < 9) + return false; + + return true; +} + +void uhd_device::set_channels(bool swap) +{ + if (iface == MULTI_ARFCN) { + if (dev_type != B200 && dev_type != B210) + throw std::invalid_argument("Device does not support MCBTS"); + dev_type = B2XX_MCBTS; + chans = 1; + } + + if (chans > dev_param_map.at(dev_key(dev_type, tx_sps, rx_sps)).channels) + throw std::invalid_argument("Device does not support number of requested channels"); + + std::string subdev_string; + switch (dev_type) { + case B210: + case E3XX: + if (chans == 1) + subdev_string = swap ? "A:B" : "A:A"; + else if (chans == 2) + subdev_string = swap ? "A:B A:A" : "A:A A:B"; + break; + case X3XX: + case UMTRX: + if (chans == 1) + subdev_string = swap ? "B:0" : "A:0"; + else if (chans == 2) + subdev_string = swap ? "B:0 A:0" : "A:0 B:0"; + break; + default: + break; + } + + if (!subdev_string.empty()) { + uhd::usrp::subdev_spec_t spec(subdev_string); + usrp_dev->set_tx_subdev_spec(spec); + usrp_dev->set_rx_subdev_spec(spec); + } +} + +int uhd_device::open(const std::string &args, int ref, bool swap_channels) +{ + const char *refstr; + + // Find UHD devices + uhd::device_addr_t addr(args); + uhd::device_addrs_t dev_addrs = uhd::device::find(addr); + if (dev_addrs.size() == 0) { + LOG(ALERT) << "No UHD devices found with address '" << args << "'"; + return -1; + } + + // Use the first found device + LOG(INFO) << "Using discovered UHD device " << dev_addrs[0].to_string(); + try { + usrp_dev = uhd::usrp::multi_usrp::make(addr); + } catch(...) { + LOG(ALERT) << "UHD make failed, device " << args; + return -1; + } + + // Check for a valid device type and set bus type + if (!parse_dev_type()) + return -1; + + if ((dev_type == E3XX) && !uhd_e3xx_version_chk()) { + LOG(ALERT) << "E3XX requires UHD 003.009.000 or greater"; + return -1; + } + + try { + set_channels(swap_channels); + } catch (const std::exception &e) { + LOG(ALERT) << "Channel setting failed - " << e.what(); + return -1; + } + + if (!set_antennas()) { + LOG(ALERT) << "UHD antenna setting failed"; + return -1; + } + + tx_freqs.resize(chans); + rx_freqs.resize(chans); + tx_gains.resize(chans); + rx_gains.resize(chans); + rx_buffers.resize(chans); + + switch (ref) { + case REF_INTERNAL: + refstr = "internal"; + break; + case REF_EXTERNAL: + refstr = "external"; + break; + case REF_GPS: + refstr = "gpsdo"; + break; + default: + LOG(ALERT) << "Invalid reference type"; + return -1; + } + + usrp_dev->set_clock_source(refstr); + + try { + set_rates(); + } catch (const std::exception &e) { + LOG(ALERT) << "UHD rate setting failed - " << e.what(); + return -1; + } + + // Set RF frontend bandwidth + if (dev_type == UMTRX) { + // Setting LMS6002D LPF to 500kHz gives us the best signal quality + for (size_t i = 0; i < chans; i++) { + usrp_dev->set_tx_bandwidth(500*1000*2, i); + usrp_dev->set_rx_bandwidth(500*1000*2, i); + } + } else if (dev_type == LIMESDR) { + for (size_t i = 0; i < chans; i++) { + usrp_dev->set_tx_bandwidth(5e6, i); + usrp_dev->set_rx_bandwidth(5e6, i); + } + } + + /* Create TX and RX streamers */ + uhd::stream_args_t stream_args("sc16"); + for (size_t i = 0; i < chans; i++) + stream_args.channels.push_back(i); + + tx_stream = usrp_dev->get_tx_stream(stream_args); + rx_stream = usrp_dev->get_rx_stream(stream_args); + + /* Number of samples per over-the-wire packet */ + tx_spp = tx_stream->get_max_num_samps(); + rx_spp = rx_stream->get_max_num_samps(); + + // Create receive buffer + size_t buf_len = SAMPLE_BUF_SZ / sizeof(uint32_t); + for (size_t i = 0; i < rx_buffers.size(); i++) + rx_buffers[i] = new smpl_buf(buf_len, rx_rate); + + // Initialize and shadow gain values + init_gains(); + + // Print configuration + LOG(INFO) << "\n" << usrp_dev->get_pp_string(); + + if (iface == MULTI_ARFCN) + return MULTI_ARFCN; + + switch (dev_type) { + case B100: + return RESAMP_64M; + case USRP2: + case X3XX: + return RESAMP_100M; + case B200: + case B210: + case E1XX: + case E3XX: + case LIMESDR: + default: + break; + } + + return NORMAL; +} + +bool uhd_device::flush_recv(size_t num_pkts) +{ + uhd::rx_metadata_t md; + size_t num_smpls; + float timeout = UHD_RESTART_TIMEOUT; + + std::vector<std::vector<short> > + pkt_bufs(chans, std::vector<short>(2 * rx_spp)); + + std::vector<short *> pkt_ptrs; + for (size_t i = 0; i < pkt_bufs.size(); i++) + pkt_ptrs.push_back(&pkt_bufs[i].front()); + + ts_initial = 0; + while (!ts_initial || (num_pkts-- > 0)) { + num_smpls = rx_stream->recv(pkt_ptrs, rx_spp, md, + timeout, true); + if (!num_smpls) { + switch (md.error_code) { + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + LOG(ALERT) << "Device timed out"; + return false; + default: + continue; + } + } + + ts_initial = md.time_spec.to_ticks(rx_rate); + } + + LOG(INFO) << "Initial timestamp " << ts_initial << std::endl; + + return true; +} + +bool uhd_device::restart() +{ + /* Allow 100 ms delay to align multi-channel streams */ + double delay = 0.1; + + aligned = false; + + uhd::time_spec_t current = usrp_dev->get_time_now(); + + uhd::stream_cmd_t cmd = uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS; + cmd.stream_now = false; + cmd.time_spec = uhd::time_spec_t(current.get_real_secs() + delay); + + usrp_dev->issue_stream_cmd(cmd); + + return flush_recv(10); +} + +bool uhd_device::start() +{ + LOG(INFO) << "Starting USRP..."; + + if (started) { + LOG(ERR) << "Device already started"; + return false; + } + +#ifndef USE_UHD_3_11 + // Register msg handler + uhd::msg::register_handler(&uhd_msg_handler); +#endif + // Start asynchronous event (underrun check) loop + async_event_thrd = new Thread(); + async_event_thrd->start((void * (*)(void*))async_event_loop, (void*)this); + + // Start streaming + if (!restart()) + return false; + + // Display usrp time + double time_now = usrp_dev->get_time_now().get_real_secs(); + LOG(INFO) << "The current time is " << time_now << " seconds"; + + started = true; + return true; +} + +bool uhd_device::stop() +{ + if (!started) + return false; + + uhd::stream_cmd_t stream_cmd = + uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS; + + usrp_dev->issue_stream_cmd(stream_cmd); + + async_event_thrd->cancel(); + async_event_thrd->join(); + delete async_event_thrd; + + started = false; + return true; +} + +void uhd_device::setPriority(float prio) +{ + uhd::set_thread_priority_safe(prio); + return; +} + +int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls) +{ + if (!num_smpls) { + LOG(ERR) << str_code(md); + + switch (md.error_code) { + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + LOG(ALERT) << "UHD: Receive timed out"; + return ERROR_TIMEOUT; + case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: + case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND: + case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN: + case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET: + default: + return ERROR_UNHANDLED; + } + } + + // Missing timestamp + if (!md.has_time_spec) { + LOG(ALERT) << "UHD: Received packet missing timestamp"; + return ERROR_UNRECOVERABLE; + } + + // Monotonicity check + if (md.time_spec < prev_ts) { + LOG(ALERT) << "UHD: Loss of monotonic time"; + LOG(ALERT) << "Current time: " << md.time_spec.get_real_secs() << ", " + << "Previous time: " << prev_ts.get_real_secs(); + return ERROR_TIMING; + } + + // Workaround for UHD tick rounding bug + TIMESTAMP ticks = md.time_spec.to_ticks(rx_rate); + if (ticks - prev_ts.to_ticks(rx_rate) == rx_spp - 1) + md.time_spec = uhd::time_spec_t::from_ticks(++ticks, rx_rate); + + prev_ts = md.time_spec; + + return 0; +} + +int uhd_device::readSamples(std::vector<short *> &bufs, int len, bool *overrun, + TIMESTAMP timestamp, bool *underrun, unsigned *RSSI) +{ + ssize_t rc; + uhd::time_spec_t ts; + uhd::rx_metadata_t metadata; + + if (bufs.size() != chans) { + LOG(ALERT) << "Invalid channel combination " << bufs.size(); + return -1; + } + + *overrun = false; + *underrun = false; + + // Shift read time with respect to transmit clock + timestamp += ts_offset; + + ts = uhd::time_spec_t::from_ticks(timestamp, rx_rate); + LOG(DEBUG) << "Requested timestamp = " << ts.get_real_secs(); + + // Check that timestamp is valid + rc = rx_buffers[0]->avail_smpls(timestamp); + if (rc < 0) { + LOG(ERR) << rx_buffers[0]->str_code(rc); + LOG(ERR) << rx_buffers[0]->str_status(timestamp); + return 0; + } + + // Create vector buffer + std::vector<std::vector<short> > + pkt_bufs(chans, std::vector<short>(2 * rx_spp)); + + std::vector<short *> pkt_ptrs; + for (size_t i = 0; i < pkt_bufs.size(); i++) + pkt_ptrs.push_back(&pkt_bufs[i].front()); + + // Receive samples from the usrp until we have enough + while (rx_buffers[0]->avail_smpls(timestamp) < len) { + thread_enable_cancel(false); + size_t num_smpls = rx_stream->recv(pkt_ptrs, rx_spp, + metadata, 0.1, true); + thread_enable_cancel(true); + + rx_pkt_cnt++; + + // Check for errors + rc = check_rx_md_err(metadata, num_smpls); + switch (rc) { + case ERROR_UNRECOVERABLE: + LOG(ALERT) << "UHD: Version " << uhd::get_version_string(); + LOG(ALERT) << "UHD: Unrecoverable error, exiting..."; + exit(-1); + case ERROR_TIMEOUT: + // Assume stopping condition + return 0; + case ERROR_TIMING: + restart(); + case ERROR_UNHANDLED: + continue; + } + + ts = metadata.time_spec; + LOG(DEBUG) << "Received timestamp = " << ts.get_real_secs(); + + for (size_t i = 0; i < rx_buffers.size(); i++) { + rc = rx_buffers[i]->write((short *) &pkt_bufs[i].front(), + num_smpls, + metadata.time_spec); + + // Continue on local overrun, exit on other errors + if ((rc < 0)) { + LOG(ERR) << rx_buffers[i]->str_code(rc); + LOG(ERR) << rx_buffers[i]->str_status(timestamp); + if (rc != smpl_buf::ERROR_OVERFLOW) + return 0; + } + } + } + + // We have enough samples + for (size_t i = 0; i < rx_buffers.size(); i++) { + rc = rx_buffers[i]->read(bufs[i], len, timestamp); + if ((rc < 0) || (rc != len)) { + LOG(ERR) << rx_buffers[i]->str_code(rc); + LOG(ERR) << rx_buffers[i]->str_status(timestamp); + return 0; + } + } + + return len; +} + +int uhd_device::writeSamples(std::vector<short *> &bufs, int len, bool *underrun, + unsigned long long timestamp,bool isControl) +{ + uhd::tx_metadata_t metadata; + metadata.has_time_spec = true; + metadata.start_of_burst = false; + metadata.end_of_burst = false; + metadata.time_spec = uhd::time_spec_t::from_ticks(timestamp, tx_rate); + + *underrun = false; + + // No control packets + if (isControl) { + LOG(ERR) << "Control packets not supported"; + return 0; + } + + if (bufs.size() != chans) { + LOG(ALERT) << "Invalid channel combination " << bufs.size(); + return -1; + } + + // Drop a fixed number of packets (magic value) + if (!aligned) { + drop_cnt++; + + if (drop_cnt == 1) { + LOG(DEBUG) << "Aligning transmitter: stop burst"; + *underrun = true; + metadata.end_of_burst = true; + } else if (drop_cnt < 30) { + LOG(DEBUG) << "Aligning transmitter: packet advance"; + return len; + } else { + LOG(DEBUG) << "Aligning transmitter: start burst"; + metadata.start_of_burst = true; + aligned = true; + drop_cnt = 0; + } + } + + thread_enable_cancel(false); + size_t num_smpls = tx_stream->send(bufs, len, metadata); + thread_enable_cancel(true); + + if (num_smpls != (unsigned) len) { + LOG(ALERT) << "UHD: Device send timed out"; + } + + return num_smpls; +} + +bool uhd_device::updateAlignment(TIMESTAMP timestamp) +{ + return true; +} + +uhd::tune_request_t uhd_device::select_freq(double freq, size_t chan, bool tx) +{ + double rf_spread, rf_freq; + std::vector<double> freqs; + uhd::tune_request_t treq(freq); + + if (dev_type == UMTRX) { + if (offset != 0.0) + return uhd::tune_request_t(freq, offset); + + // Don't use DSP tuning, because LMS6002D PLL steps are small enough. + // We end up with DSP tuning just for 2-3Hz, which is meaningless and + // only distort the signal (because cordic is not ideal). + treq.target_freq = freq; + treq.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + treq.rf_freq = freq; + treq.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + treq.dsp_freq = 0.0; + return treq; + } else if (chans == 1) { + if (offset == 0.0) + return treq; + + return uhd::tune_request_t(freq, offset); + } else if ((dev_type != B210) || (chans > 2) || (chan > 1)) { + LOG(ALERT) << chans << " channels unsupported"; + return treq; + } + + if (tx) + freqs = tx_freqs; + else + freqs = rx_freqs; + + /* Tune directly if other channel isn't tuned */ + if (freqs[!chan] < 10.0) + return treq; + + /* Find center frequency between channels */ + rf_spread = fabs(freqs[!chan] - freq); + if (rf_spread > dev_param_map.at(dev_key(B210, tx_sps, rx_sps)).mcr) { + LOG(ALERT) << rf_spread << "Hz tuning spread not supported\n"; + return treq; + } + + rf_freq = (freqs[!chan] + freq) / 2.0f; + + treq.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL; + treq.target_freq = freq; + treq.rf_freq = rf_freq; + + return treq; +} + +bool uhd_device::set_freq(double freq, size_t chan, bool tx) +{ + std::vector<double> freqs; + uhd::tune_result_t tres; + uhd::tune_request_t treq = select_freq(freq, chan, tx); + + if (tx) { + tres = usrp_dev->set_tx_freq(treq, chan); + tx_freqs[chan] = usrp_dev->get_tx_freq(chan); + } else { + tres = usrp_dev->set_rx_freq(treq, chan); + rx_freqs[chan] = usrp_dev->get_rx_freq(chan); + } + LOG(INFO) << "\n" << tres.to_pp_string() << std::endl; + + if ((chans == 1) || ((chans == 2) && dev_type == UMTRX)) + return true; + + /* Manual RF policy means we intentionally tuned with a baseband + * offset for dual-channel purposes. Now retune the other channel + * with the opposite corresponding frequency offset + */ + if (treq.rf_freq_policy == uhd::tune_request_t::POLICY_MANUAL) { + if (tx) { + treq = select_freq(tx_freqs[!chan], !chan, true); + tres = usrp_dev->set_tx_freq(treq, !chan); + tx_freqs[!chan] = usrp_dev->get_tx_freq(!chan); + } else { + treq = select_freq(rx_freqs[!chan], !chan, false); + tres = usrp_dev->set_rx_freq(treq, !chan); + rx_freqs[!chan] = usrp_dev->get_rx_freq(!chan); + + } + LOG(INFO) << "\n" << tres.to_pp_string() << std::endl; + } + + return true; +} + +bool uhd_device::setTxFreq(double wFreq, size_t chan) +{ + if (chan >= tx_freqs.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return false; + } + ScopedLock lock(tune_lock); + + return set_freq(wFreq, chan, true); +} + +bool uhd_device::setRxFreq(double wFreq, size_t chan) +{ + if (chan >= rx_freqs.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return false; + } + ScopedLock lock(tune_lock); + + return set_freq(wFreq, chan, false); +} + +double uhd_device::getTxFreq(size_t chan) +{ + if (chan >= tx_freqs.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return 0.0; + } + + return tx_freqs[chan]; +} + +double uhd_device::getRxFreq(size_t chan) +{ + if (chan >= rx_freqs.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return 0.0; + } + + return rx_freqs[chan]; +} + +bool uhd_device::setRxAntenna(const std::string &ant, size_t chan) +{ + std::vector<std::string> avail; + if (chan >= rx_paths.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return false; + } + + avail = usrp_dev->get_rx_antennas(chan); + if (std::find(avail.begin(), avail.end(), ant) == avail.end()) { + LOG(ALERT) << "Requested non-existent Rx antenna " << ant << " on channel " << chan; + LOG(INFO) << "Available Rx antennas: "; + for (std::vector<std::string>::const_iterator i = avail.begin(); i != avail.end(); ++i) + LOG(INFO) << "- '" << *i << "'"; + return false; + } + usrp_dev->set_rx_antenna(ant, chan); + rx_paths[chan] = usrp_dev->get_rx_antenna(chan); + + if (ant != rx_paths[chan]) { + LOG(ALERT) << "Failed setting antenna " << ant << " on channel " << chan << ", got instead " << rx_paths[chan]; + return false; + } + + return true; +} + +std::string uhd_device::getRxAntenna(size_t chan) +{ + if (chan >= rx_paths.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return ""; + } + return usrp_dev->get_rx_antenna(chan); +} + +bool uhd_device::setTxAntenna(const std::string &ant, size_t chan) +{ + std::vector<std::string> avail; + if (chan >= tx_paths.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return false; + } + + avail = usrp_dev->get_tx_antennas(chan); + if (std::find(avail.begin(), avail.end(), ant) == avail.end()) { + LOG(ALERT) << "Requested non-existent Tx antenna " << ant << " on channel " << chan; + LOG(INFO) << "Available Tx antennas: "; + for (std::vector<std::string>::const_iterator i = avail.begin(); i != avail.end(); ++i) + LOG(INFO) << "- '" << *i << "'"; + return false; + } + usrp_dev->set_tx_antenna(ant, chan); + tx_paths[chan] = usrp_dev->get_tx_antenna(chan); + + if (ant != tx_paths[chan]) { + LOG(ALERT) << "Failed setting antenna " << ant << " on channel " << chan << ", got instead " << tx_paths[chan]; + return false; + } + + return true; +} + +std::string uhd_device::getTxAntenna(size_t chan) +{ + if (chan >= tx_paths.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return ""; + } + return usrp_dev->get_tx_antenna(chan); +} + +/* + * Only allow sampling the Rx path lower than Tx and not vice-versa. + * Using Tx with 4 SPS and Rx at 1 SPS is the only allowed mixed + * combination. + */ +TIMESTAMP uhd_device::initialWriteTimestamp() +{ + if ((iface == MULTI_ARFCN) || (rx_sps == tx_sps)) + return ts_initial; + else + return ts_initial * tx_sps; +} + +TIMESTAMP uhd_device::initialReadTimestamp() +{ + return ts_initial; +} + +double uhd_device::fullScaleInputValue() +{ + if (dev_type == LIMESDR) + return (double) SHRT_MAX * LIMESDR_TX_AMPL; + if (dev_type == UMTRX) + return (double) SHRT_MAX * UMTRX_TX_AMPL; + else + return (double) SHRT_MAX * USRP_TX_AMPL; +} + +double uhd_device::fullScaleOutputValue() +{ + return (double) SHRT_MAX; +} + +bool uhd_device::recv_async_msg() +{ + uhd::async_metadata_t md; + + thread_enable_cancel(false); + bool rc = usrp_dev->get_device()->recv_async_msg(md); + thread_enable_cancel(true); + if (!rc) + return false; + + // Assume that any error requires resynchronization + if (md.event_code != uhd::async_metadata_t::EVENT_CODE_BURST_ACK) { + aligned = false; + + if ((md.event_code != uhd::async_metadata_t::EVENT_CODE_UNDERFLOW) && + (md.event_code != uhd::async_metadata_t::EVENT_CODE_TIME_ERROR)) { + LOG(ERR) << str_code(md); + } + } + + return true; +} + +std::string uhd_device::str_code(uhd::rx_metadata_t metadata) +{ + std::ostringstream ost("UHD: "); + + switch (metadata.error_code) { + case uhd::rx_metadata_t::ERROR_CODE_NONE: + ost << "No error"; + break; + case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT: + ost << "No packet received, implementation timed-out"; + break; + case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND: + ost << "A stream command was issued in the past"; + break; + case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN: + ost << "Expected another stream command"; + break; + case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW: + ost << "An internal receive buffer has filled"; + break; + case uhd::rx_metadata_t::ERROR_CODE_ALIGNMENT: + ost << "Multi-channel alignment failed"; + break; + case uhd::rx_metadata_t::ERROR_CODE_BAD_PACKET: + ost << "The packet could not be parsed"; + break; + default: + ost << "Unknown error " << metadata.error_code; + } + + if (metadata.has_time_spec) + ost << " at " << metadata.time_spec.get_real_secs() << " sec."; + + return ost.str(); +} + +std::string uhd_device::str_code(uhd::async_metadata_t metadata) +{ + std::ostringstream ost("UHD: "); + + switch (metadata.event_code) { + case uhd::async_metadata_t::EVENT_CODE_BURST_ACK: + ost << "A packet was successfully transmitted"; + break; + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW: + ost << "An internal send buffer has emptied"; + break; + case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR: + ost << "Packet loss between host and device"; + break; + case uhd::async_metadata_t::EVENT_CODE_TIME_ERROR: + ost << "Packet time was too late or too early"; + break; + case uhd::async_metadata_t::EVENT_CODE_UNDERFLOW_IN_PACKET: + ost << "Underflow occurred inside a packet"; + break; + case uhd::async_metadata_t::EVENT_CODE_SEQ_ERROR_IN_BURST: + ost << "Packet loss within a burst"; + break; + default: + ost << "Unknown error " << metadata.event_code; + } + + if (metadata.has_time_spec) + ost << " at " << metadata.time_spec.get_real_secs() << " sec."; + + return ost.str(); +} + +smpl_buf::smpl_buf(size_t len, double rate) + : buf_len(len), clk_rt(rate), + time_start(0), time_end(0), data_start(0), data_end(0) +{ + data = new uint32_t[len]; +} + +smpl_buf::~smpl_buf() +{ + delete[] data; +} + +ssize_t smpl_buf::avail_smpls(TIMESTAMP timestamp) const +{ + if (timestamp < time_start) + return ERROR_TIMESTAMP; + else if (timestamp >= time_end) + return 0; + else + return time_end - timestamp; +} + +ssize_t smpl_buf::avail_smpls(uhd::time_spec_t timespec) const +{ + return avail_smpls(timespec.to_ticks(clk_rt)); +} + +ssize_t smpl_buf::read(void *buf, size_t len, TIMESTAMP timestamp) +{ + int type_sz = 2 * sizeof(short); + + // Check for valid read + if (timestamp < time_start) + return ERROR_TIMESTAMP; + if (timestamp >= time_end) + return 0; + if (len >= buf_len) + return ERROR_READ; + + // How many samples should be copied + size_t num_smpls = time_end - timestamp; + if (num_smpls > len) + num_smpls = len; + + // Starting index + size_t read_start = (data_start + (timestamp - time_start)) % buf_len; + + // Read it + if (read_start + num_smpls < buf_len) { + size_t numBytes = len * type_sz; + memcpy(buf, data + read_start, numBytes); + } else { + size_t first_cp = (buf_len - read_start) * type_sz; + size_t second_cp = len * type_sz - first_cp; + + memcpy(buf, data + read_start, first_cp); + memcpy((char*) buf + first_cp, data, second_cp); + } + + data_start = (read_start + len) % buf_len; + time_start = timestamp + len; + + if (time_start > time_end) + return ERROR_READ; + else + return num_smpls; +} + +ssize_t smpl_buf::read(void *buf, size_t len, uhd::time_spec_t ts) +{ + return read(buf, len, ts.to_ticks(clk_rt)); +} + +ssize_t smpl_buf::write(void *buf, size_t len, TIMESTAMP timestamp) +{ + int type_sz = 2 * sizeof(short); + + // Check for valid write + if ((len == 0) || (len >= buf_len)) + return ERROR_WRITE; + if ((timestamp + len) <= time_end) + return ERROR_TIMESTAMP; + + if (timestamp < time_end) { + LOG(ERR) << "Overwriting old buffer data: timestamp="<<timestamp<<" time_end="<<time_end; + uhd::time_spec_t ts = uhd::time_spec_t::from_ticks(timestamp, clk_rt); + LOG(DEBUG) << "Requested timestamp = " << timestamp << " (real_sec=" << std::fixed << ts.get_real_secs() << " = " << ts.to_ticks(clk_rt) << ") rate=" << clk_rt; + // Do not return error here, because it's a rounding error and is not fatal + } + if (timestamp > time_end && time_end != 0) { + LOG(ERR) << "Skipping buffer data: timestamp="<<timestamp<<" time_end="<<time_end; + uhd::time_spec_t ts = uhd::time_spec_t::from_ticks(timestamp, clk_rt); + LOG(DEBUG) << "Requested timestamp = " << timestamp << " (real_sec=" << std::fixed << ts.get_real_secs() << " = " << ts.to_ticks(clk_rt) << ") rate=" << clk_rt; + // Do not return error here, because it's a rounding error and is not fatal + } + + // Starting index + size_t write_start = (data_start + (timestamp - time_start)) % buf_len; + + // Write it + if ((write_start + len) < buf_len) { + size_t numBytes = len * type_sz; + memcpy(data + write_start, buf, numBytes); + } else { + size_t first_cp = (buf_len - write_start) * type_sz; + size_t second_cp = len * type_sz - first_cp; + + memcpy(data + write_start, buf, first_cp); + memcpy(data, (char*) buf + first_cp, second_cp); + } + + data_end = (write_start + len) % buf_len; + time_end = timestamp + len; + + if (!data_start) + data_start = write_start; + + if (((write_start + len) > buf_len) && (data_end > data_start)) + return ERROR_OVERFLOW; + else if (time_end <= time_start) + return ERROR_WRITE; + else + return len; +} + +ssize_t smpl_buf::write(void *buf, size_t len, uhd::time_spec_t ts) +{ + return write(buf, len, ts.to_ticks(clk_rt)); +} + +std::string smpl_buf::str_status(size_t ts) const +{ + std::ostringstream ost("Sample buffer: "); + + ost << "timestamp = " << ts; + ost << ", length = " << buf_len; + ost << ", time_start = " << time_start; + ost << ", time_end = " << time_end; + ost << ", data_start = " << data_start; + ost << ", data_end = " << data_end; + + return ost.str(); +} + +std::string smpl_buf::str_code(ssize_t code) +{ + switch (code) { + case ERROR_TIMESTAMP: + return "Sample buffer: Requested timestamp is not valid"; + case ERROR_READ: + return "Sample buffer: Read error"; + case ERROR_WRITE: + return "Sample buffer: Write error"; + case ERROR_OVERFLOW: + return "Sample buffer: Overrun"; + default: + return "Sample buffer: Unknown error"; + } +} + +RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, + InterfaceType iface, size_t chans, double offset, + const std::vector<std::string>& tx_paths, + const std::vector<std::string>& rx_paths) +{ + return new uhd_device(tx_sps, rx_sps, iface, chans, offset, tx_paths, rx_paths); +} diff --git a/Transceiver52M/device/usrp1/Makefile.am b/Transceiver52M/device/usrp1/Makefile.am new file mode 100644 index 0000000..d99874a --- /dev/null +++ b/Transceiver52M/device/usrp1/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/Makefile.common + +AM_CPPFLAGS = -Wall $(STD_DEFINES_AND_INCLUDES) -I${srcdir}/.. +AM_CXXFLAGS = -lpthread $(LIBOSMOCORE_CFLAGS) $(LIBOSMOCTRL_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(USRP_CFLAGS) + +noinst_HEADERS = USRPDevice.h + +noinst_LTLIBRARIES = libdevice.la + +libdevice_la_SOURCES = USRPDevice.cpp diff --git a/Transceiver52M/device/usrp1/USRPDevice.cpp b/Transceiver52M/device/usrp1/USRPDevice.cpp new file mode 100644 index 0000000..f7f24e9 --- /dev/null +++ b/Transceiver52M/device/usrp1/USRPDevice.cpp @@ -0,0 +1,648 @@ +/* +* Copyright 2008, 2009 Free Software Foundation, Inc. +* +* This software is distributed under the terms of the GNU Affero Public License. +* See the COPYING file in the main directory for details. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + + +/* + Compilation Flags + + SWLOOPBACK compile for software loopback testing +*/ + + +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include "Logger.h" +#include "Threads.h" +#include "USRPDevice.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +using namespace std; + +enum dboardConfigType { + TXA_RXB, + TXB_RXA, + TXA_RXA, + TXB_RXB +}; + +#ifdef SINGLEDB +const dboardConfigType dboardConfig = TXA_RXA; +#else +const dboardConfigType dboardConfig = TXA_RXB; +#endif + +const double USRPDevice::masterClockRate = 52.0e6; + +USRPDevice::USRPDevice(size_t sps) +{ + LOG(INFO) << "creating USRP device..."; + + this->sps = sps; + decimRate = (unsigned int) round(masterClockRate/((GSMRATE) * (double) sps)); + actualSampleRate = masterClockRate/decimRate; + rxGain = 0; + + /* + * Undetermined delay b/w ping response timestamp and true + * receive timestamp. Values are empirically measured. With + * split sample rate Tx/Rx - 4/1 sps we need to need to + * compensate for advance rather than delay. + */ + if (sps == 1) + pingOffset = 272; + else if (sps == 4) + pingOffset = 269 - 7500; + else + pingOffset = 0; + +#ifdef SWLOOPBACK + samplePeriod = 1.0e6/actualSampleRate; + loopbackBufferSize = 0; + gettimeofday(&lastReadTime,NULL); + firstRead = false; +#endif +} + +int USRPDevice::open(const std::string &, int, bool) +{ + writeLock.unlock(); + + LOG(INFO) << "opening USRP device.."; +#ifndef SWLOOPBACK + string rbf = "std_inband.rbf"; + //string rbf = "inband_1rxhb_1tx.rbf"; + m_uRx.reset(); + if (!skipRx) { + try { + m_uRx = usrp_standard_rx_sptr(usrp_standard_rx::make( + 0, decimRate * sps, 1, -1, + usrp_standard_rx::FPGA_MODE_NORMAL, + 1024, 16 * 8, rbf)); + m_uRx->set_fpga_master_clock_freq(masterClockRate); + } + + catch(...) { + LOG(ALERT) << "make failed on Rx"; + m_uRx.reset(); + return -1; + } + + if (m_uRx->fpga_master_clock_freq() != masterClockRate) + { + LOG(ALERT) << "WRONG FPGA clock freq = " << m_uRx->fpga_master_clock_freq() + << ", desired clock freq = " << masterClockRate; + m_uRx.reset(); + return -1; + } + } + + try { + m_uTx = usrp_standard_tx_sptr(usrp_standard_tx::make( + 0, decimRate * 2, 1, -1, + 1024, 16 * 8, rbf)); + m_uTx->set_fpga_master_clock_freq(masterClockRate); + } + + catch(...) { + LOG(ALERT) << "make failed on Tx"; + m_uTx.reset(); + return -1; + } + + if (m_uTx->fpga_master_clock_freq() != masterClockRate) + { + LOG(ALERT) << "WRONG FPGA clock freq = " << m_uTx->fpga_master_clock_freq() + << ", desired clock freq = " << masterClockRate; + m_uTx.reset(); + return -1; + } + + if (!skipRx) m_uRx->stop(); + m_uTx->stop(); + +#endif + + switch (dboardConfig) { + case TXA_RXB: + txSubdevSpec = usrp_subdev_spec(0,0); + rxSubdevSpec = usrp_subdev_spec(1,0); + break; + case TXB_RXA: + txSubdevSpec = usrp_subdev_spec(1,0); + rxSubdevSpec = usrp_subdev_spec(0,0); + break; + case TXA_RXA: + txSubdevSpec = usrp_subdev_spec(0,0); + rxSubdevSpec = usrp_subdev_spec(0,0); + break; + case TXB_RXB: + txSubdevSpec = usrp_subdev_spec(1,0); + rxSubdevSpec = usrp_subdev_spec(1,0); + break; + default: + txSubdevSpec = usrp_subdev_spec(0,0); + rxSubdevSpec = usrp_subdev_spec(1,0); + } + + m_dbTx = m_uTx->selected_subdev(txSubdevSpec); + m_dbRx = m_uRx->selected_subdev(rxSubdevSpec); + + samplesRead = 0; + samplesWritten = 0; + started = false; + + return NORMAL; +} + + + +bool USRPDevice::start() +{ + LOG(INFO) << "starting USRP..."; +#ifndef SWLOOPBACK + if (!m_uRx && !skipRx) return false; + if (!m_uTx) return false; + + if (!skipRx) m_uRx->stop(); + m_uTx->stop(); + + writeLock.lock(); + // power up and configure daughterboards + m_dbTx->set_enable(true); + m_uTx->set_mux(m_uTx->determine_tx_mux_value(txSubdevSpec)); + m_uRx->set_mux(m_uRx->determine_rx_mux_value(rxSubdevSpec)); + + if (!m_dbRx->select_rx_antenna(1)) + m_dbRx->select_rx_antenna(0); + + writeLock.unlock(); + + // Set gains to midpoint + setTxGain((minTxGain() + maxTxGain()) / 2); + setRxGain((minRxGain() + maxRxGain()) / 2); + + data = new short[currDataSize]; + dataStart = 0; + dataEnd = 0; + timeStart = 0; + timeEnd = 0; + timestampOffset = 0; + latestWriteTimestamp = 0; + lastPktTimestamp = 0; + hi32Timestamp = 0; + isAligned = false; + + + if (!skipRx) + started = (m_uRx->start() && m_uTx->start()); + else + started = m_uTx->start(); + return started; +#else + gettimeofday(&lastReadTime,NULL); + return true; +#endif +} + +bool USRPDevice::stop() +{ +#ifndef SWLOOPBACK + if (!m_uRx) return false; + if (!m_uTx) return false; + + delete[] currData; + + started = !(m_uRx->stop() && m_uTx->stop()); + return !started; +#else + return true; +#endif +} + +double USRPDevice::maxTxGain() +{ + return m_dbTx->gain_max(); +} + +double USRPDevice::minTxGain() +{ + return m_dbTx->gain_min(); +} + +double USRPDevice::maxRxGain() +{ + return m_dbRx->gain_max(); +} + +double USRPDevice::minRxGain() +{ + return m_dbRx->gain_min(); +} + +double USRPDevice::setTxGain(double dB, size_t chan) +{ + if (chan) { + LOG(ALERT) << "Invalid channel " << chan; + return 0.0; + } + + writeLock.lock(); + if (dB > maxTxGain()) + dB = maxTxGain(); + if (dB < minTxGain()) + dB = minTxGain(); + + LOG(NOTICE) << "Setting TX gain to " << dB << " dB."; + + if (!m_dbTx->set_gain(dB)) + LOG(ERR) << "Error setting TX gain"; + + writeLock.unlock(); + + return dB; +} + + +double USRPDevice::setRxGain(double dB, size_t chan) +{ + if (chan) { + LOG(ALERT) << "Invalid channel " << chan; + return 0.0; + } + + dB = 47.0; + + writeLock.lock(); + if (dB > maxRxGain()) + dB = maxRxGain(); + if (dB < minRxGain()) + dB = minRxGain(); + + LOG(NOTICE) << "Setting RX gain to " << dB << " dB."; + + if (!m_dbRx->set_gain(dB)) + LOG(ERR) << "Error setting RX gain"; + + writeLock.unlock(); + + return dB; +} + +bool USRPDevice::setRxAntenna(const std::string &ant, size_t chan) +{ + if (chan >= rx_paths.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return false; + } + LOG(ALERT) << "Not implemented"; + return true; +} + +std::string USRPDevice::getRxAntenna(size_t chan) +{ + if (chan >= rx_paths.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return ""; + } + LOG(ALERT) << "Not implemented"; + return ""; +} + +bool USRPDevice::setTxAntenna(const std::string &ant, size_t chan) +{ + if (chan >= tx_paths.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return false; + } + LOG(ALERT) << "Not implemented"; + return true; +} + +std::string USRPDevice::getTxAntenna(size_t chan) +{ + if (chan >= tx_paths.size()) { + LOG(ALERT) << "Requested non-existent channel " << chan; + return ""; + } + LOG(ALERT) << "Not implemented"; + return ""; +} + + +// NOTE: Assumes sequential reads +int USRPDevice::readSamples(std::vector<short *> &bufs, int len, bool *overrun, + TIMESTAMP timestamp, bool *underrun, unsigned *RSSI) +{ +#ifndef SWLOOPBACK + if (!m_uRx) + return 0; + + short *buf = bufs[0]; + + timestamp += timestampOffset; + + if (timestamp + len < timeStart) { + memset(buf,0,len*2*sizeof(short)); + return len; + } + + if (underrun) *underrun = false; + + uint32_t readBuf[2000]; + + while (1) { + //guestimate USB read size + int readLen=0; + { + int numSamplesNeeded = timestamp + len - timeEnd; + if (numSamplesNeeded <=0) break; + readLen = 512 * ((int) ceil((float) numSamplesNeeded/126.0)); + if (readLen > 8000) readLen= (8000/512)*512; + } + + // read USRP packets, parse and save A/D data as needed + readLen = m_uRx->read((void *)readBuf,readLen,overrun); + for(int pktNum = 0; pktNum < (readLen/512); pktNum++) { + // tmpBuf points to start of a USB packet + uint32_t* tmpBuf = (uint32_t *) (readBuf+pktNum*512/4); + TIMESTAMP pktTimestamp = usrp_to_host_u32(tmpBuf[1]); + uint32_t word0 = usrp_to_host_u32(tmpBuf[0]); + uint32_t chan = (word0 >> 16) & 0x1f; + unsigned payloadSz = word0 & 0x1ff; + LOG(DEBUG) << "first two bytes: " << hex << word0 << " " << dec << pktTimestamp; + + bool incrementHi32 = ((lastPktTimestamp & 0x0ffffffffll) > pktTimestamp); + if (incrementHi32 && (timeStart!=0)) { + LOG(DEBUG) << "high 32 increment!!!"; + hi32Timestamp++; + } + pktTimestamp = (((TIMESTAMP) hi32Timestamp) << 32) | pktTimestamp; + lastPktTimestamp = pktTimestamp; + + if (chan == 0x01f) { + // control reply, check to see if its ping reply + uint32_t word2 = usrp_to_host_u32(tmpBuf[2]); + if ((word2 >> 16) == ((0x01 << 8) | 0x02)) { + timestamp -= timestampOffset; + timestampOffset = pktTimestamp - pingTimestamp + pingOffset; + LOG(DEBUG) << "updating timestamp offset to: " << timestampOffset; + timestamp += timestampOffset; + isAligned = true; + } + continue; + } + if (chan != 0) { + LOG(DEBUG) << "chan: " << chan << ", timestamp: " << pktTimestamp << ", sz:" << payloadSz; + continue; + } + if ((word0 >> 28) & 0x04) { + if (underrun) *underrun = true; + LOG(DEBUG) << "UNDERRUN in TRX->USRP interface"; + } + if (RSSI) *RSSI = (word0 >> 21) & 0x3f; + + if (!isAligned) continue; + + unsigned cursorStart = pktTimestamp - timeStart + dataStart; + while (cursorStart*2 > currDataSize) { + cursorStart -= currDataSize/2; + } + if (cursorStart*2 + payloadSz/2 > currDataSize) { + // need to circle around buffer + memcpy(data+cursorStart*2,tmpBuf+2,(currDataSize-cursorStart*2)*sizeof(short)); + memcpy(data,tmpBuf+2+(currDataSize/2-cursorStart),payloadSz-(currDataSize-cursorStart*2)*sizeof(short)); + } + else { + memcpy(data+cursorStart*2,tmpBuf+2,payloadSz); + } + if (pktTimestamp + payloadSz/2/sizeof(short) > timeEnd) + timeEnd = pktTimestamp+payloadSz/2/sizeof(short); + + LOG(DEBUG) << "timeStart: " << timeStart << ", timeEnd: " << timeEnd << ", pktTimestamp: " << pktTimestamp; + + } + } + + // copy desired data to buf + unsigned bufStart = dataStart+(timestamp-timeStart); + if (bufStart + len < currDataSize/2) { + LOG(DEBUG) << "bufStart: " << bufStart; + memcpy(buf,data+bufStart*2,len*2*sizeof(short)); + memset(data+bufStart*2,0,len*2*sizeof(short)); + } + else { + LOG(DEBUG) << "len: " << len << ", currDataSize/2: " << currDataSize/2 << ", bufStart: " << bufStart; + unsigned firstLength = (currDataSize/2-bufStart); + LOG(DEBUG) << "firstLength: " << firstLength; + memcpy(buf,data+bufStart*2,firstLength*2*sizeof(short)); + memset(data+bufStart*2,0,firstLength*2*sizeof(short)); + memcpy(buf+firstLength*2,data,(len-firstLength)*2*sizeof(short)); + memset(data,0,(len-firstLength)*2*sizeof(short)); + } + dataStart = (bufStart + len) % (currDataSize/2); + timeStart = timestamp + len; + + return len; + +#else + if (loopbackBufferSize < 2) return 0; + int numSamples = 0; + struct timeval currTime; + gettimeofday(&currTime,NULL); + double timeElapsed = (currTime.tv_sec - lastReadTime.tv_sec)*1.0e6 + + (currTime.tv_usec - lastReadTime.tv_usec); + if (timeElapsed < samplePeriod) {return 0;} + int numSamplesToRead = (int) floor(timeElapsed/samplePeriod); + if (numSamplesToRead < len) return 0; + + if (numSamplesToRead > len) numSamplesToRead = len; + if (numSamplesToRead > loopbackBufferSize/2) { + firstRead =false; + numSamplesToRead = loopbackBufferSize/2; + } + memcpy(buf,loopbackBuffer,sizeof(short)*2*numSamplesToRead); + loopbackBufferSize -= 2*numSamplesToRead; + memcpy(loopbackBuffer,loopbackBuffer+2*numSamplesToRead, + sizeof(short)*loopbackBufferSize); + numSamples = numSamplesToRead; + if (firstRead) { + int new_usec = lastReadTime.tv_usec + (int) round((double) numSamplesToRead * samplePeriod); + lastReadTime.tv_sec = lastReadTime.tv_sec + new_usec/1000000; + lastReadTime.tv_usec = new_usec % 1000000; + } + else { + gettimeofday(&lastReadTime,NULL); + firstRead = true; + } + samplesRead += numSamples; + + return numSamples; +#endif +} + +int USRPDevice::writeSamples(std::vector<short *> &bufs, int len, + bool *underrun, unsigned long long timestamp, + bool isControl) +{ + writeLock.lock(); + +#ifndef SWLOOPBACK + if (!m_uTx) + return 0; + + short *buf = bufs[0]; + + static uint32_t outData[128*20]; + + for (int i = 0; i < len*2; i++) { + buf[i] = host_to_usrp_short(buf[i]); + } + + int numWritten = 0; + unsigned isStart = 1; + unsigned RSSI = 0; + unsigned CHAN = (isControl) ? 0x01f : 0x00; + len = len*2*sizeof(short); + int numPkts = (int) ceil((float)len/(float)504); + unsigned isEnd = (numPkts < 2); + uint32_t *outPkt = outData; + int pktNum = 0; + while (numWritten < len) { + // pkt is pointer to start of a USB packet + uint32_t *pkt = outPkt + pktNum*128; + isEnd = (len - numWritten <= 504); + unsigned payloadLen = ((len - numWritten) < 504) ? (len-numWritten) : 504; + pkt[0] = (isStart << 12 | isEnd << 11 | (RSSI & 0x3f) << 5 | CHAN) << 16 | payloadLen; + pkt[1] = timestamp & 0x0ffffffffll; + memcpy(pkt+2,buf+(numWritten/sizeof(short)),payloadLen); + numWritten += payloadLen; + timestamp += payloadLen/2/sizeof(short); + isStart = 0; + pkt[0] = host_to_usrp_u32(pkt[0]); + pkt[1] = host_to_usrp_u32(pkt[1]); + pktNum++; + } + m_uTx->write((const void*) outPkt,sizeof(uint32_t)*128*numPkts,NULL); + + samplesWritten += len/2/sizeof(short); + writeLock.unlock(); + + return len/2/sizeof(short); +#else + int retVal = len; + memcpy(loopbackBuffer+loopbackBufferSize,buf,sizeof(short)*2*len); + samplesWritten += retVal; + loopbackBufferSize += retVal*2; + + return retVal; +#endif +} + +bool USRPDevice::updateAlignment(TIMESTAMP timestamp) +{ +#ifndef SWLOOPBACK + short data[] = {0x00,0x02,0x00,0x00}; + uint32_t *wordPtr = (uint32_t *) data; + *wordPtr = host_to_usrp_u32(*wordPtr); + bool tmpUnderrun; + + std::vector<short *> buf(1, data); + if (writeSamples(buf, 1, &tmpUnderrun, timestamp & 0x0ffffffffll, true)) { + pingTimestamp = timestamp; + return true; + } + return false; +#else + return true; +#endif +} + +#ifndef SWLOOPBACK +bool USRPDevice::setTxFreq(double wFreq, size_t chan) +{ + usrp_tune_result result; + + if (chan) { + LOG(ALERT) << "Invalid channel " << chan; + return false; + } + + if (m_uTx->tune(txSubdevSpec.side, m_dbTx, wFreq, &result)) { + LOG(INFO) << "set TX: " << wFreq << std::endl + << " baseband freq: " << result.baseband_freq << std::endl + << " DDC freq: " << result.dxc_freq << std::endl + << " residual freq: " << result.residual_freq; + return true; + } + else { + LOG(ALERT) << "set TX: " << wFreq << "failed" << std::endl + << " baseband freq: " << result.baseband_freq << std::endl + << " DDC freq: " << result.dxc_freq << std::endl + << " residual freq: " << result.residual_freq; + return false; + } +} + +bool USRPDevice::setRxFreq(double wFreq, size_t chan) +{ + usrp_tune_result result; + + if (chan) { + LOG(ALERT) << "Invalid channel " << chan; + return false; + } + + if (m_uRx->tune(0, m_dbRx, wFreq, &result)) { + LOG(INFO) << "set RX: " << wFreq << std::endl + << " baseband freq: " << result.baseband_freq << std::endl + << " DDC freq: " << result.dxc_freq << std::endl + << " residual freq: " << result.residual_freq; + return true; + } + else { + LOG(ALERT) << "set RX: " << wFreq << "failed" << std::endl + << " baseband freq: " << result.baseband_freq << std::endl + << " DDC freq: " << result.dxc_freq << std::endl + << " residual freq: " << result.residual_freq; + return false; + } + +} + +#else +bool USRPDevice::setTxFreq(double wFreq) { return true;}; +bool USRPDevice::setRxFreq(double wFreq) { return true;}; +#endif + +RadioDevice *RadioDevice::make(size_t tx_sps, size_t rx_sps, + InterfaceType iface, size_t chans, double offset, + const std::vector<std::string>& tx_paths, + const std::vector<std::string>& rx_paths) +{ + return new USRPDevice(tx_sps); +} diff --git a/Transceiver52M/device/usrp1/USRPDevice.h b/Transceiver52M/device/usrp1/USRPDevice.h new file mode 100644 index 0000000..f981643 --- /dev/null +++ b/Transceiver52M/device/usrp1/USRPDevice.h @@ -0,0 +1,204 @@ +/* +* Copyright 2008 Free Software Foundation, Inc. +* +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + 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. + +*/ + +#ifndef _USRP_DEVICE_H_ +#define _USRP_DEVICE_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "radioDevice.h" + +#include <usrp/usrp_standard.h> +#include <usrp/usrp_bytesex.h> +#include <usrp/usrp_prims.h> +#include <sys/time.h> +#include <math.h> +#include <string> +#include <iostream> + +#include <boost/shared_ptr.hpp> +typedef boost::shared_ptr<usrp_standard_tx> usrp_standard_tx_sptr; +typedef boost::shared_ptr<usrp_standard_rx> usrp_standard_rx_sptr; + +/** A class to handle a USRP rev 4, with a two RFX900 daughterboards */ +class USRPDevice: public RadioDevice { + +private: + + static const double masterClockRate; ///< the USRP clock rate + double desiredSampleRate; ///< the desired sampling rate + usrp_standard_rx_sptr m_uRx; ///< the USRP receiver + usrp_standard_tx_sptr m_uTx; ///< the USRP transmitter + + db_base_sptr m_dbRx; ///< rx daughterboard + db_base_sptr m_dbTx; ///< tx daughterboard + usrp_subdev_spec rxSubdevSpec; + usrp_subdev_spec txSubdevSpec; + + int sps; + double actualSampleRate; ///< the actual USRP sampling rate + unsigned int decimRate; ///< the USRP decimation rate + + unsigned long long samplesRead; ///< number of samples read from USRP + unsigned long long samplesWritten; ///< number of samples sent to USRP + + bool started; ///< flag indicates USRP has started + bool skipRx; ///< set if USRP is transmit-only. + + static const unsigned int currDataSize_log2 = 21; + static const unsigned long currDataSize = (1 << currDataSize_log2); + short *data; + unsigned long dataStart; + unsigned long dataEnd; + TIMESTAMP timeStart; + TIMESTAMP timeEnd; + bool isAligned; + + Mutex writeLock; + + short *currData; ///< internal data buffer when reading from USRP + TIMESTAMP currTimestamp; ///< timestamp of internal data buffer + unsigned currLen; ///< size of internal data buffer + + TIMESTAMP timestampOffset; ///< timestamp offset b/w Tx and Rx blocks + TIMESTAMP latestWriteTimestamp; ///< timestamp of most recent ping command + TIMESTAMP pingTimestamp; ///< timestamp of most recent ping response + + long long pingOffset; + unsigned long hi32Timestamp; + unsigned long lastPktTimestamp; + + double rxGain; + +#ifdef SWLOOPBACK + short loopbackBuffer[1000000]; + int loopbackBufferSize; + double samplePeriod; + + struct timeval startTime; + struct timeval lastReadTime; + bool firstRead; +#endif + + public: + + /** Object constructor */ + USRPDevice(size_t sps); + + /** Instantiate the USRP */ + int open(const std::string &, int, bool); + + /** Start the USRP */ + bool start(); + + /** Stop the USRP */ + bool stop(); + + /** Set priority not supported */ + void setPriority(float prio = 0.5) { } + + enum TxWindowType getWindowType() { return TX_WINDOW_USRP1; } + + /** + Read samples from the USRP. + @param buf preallocated buf to contain read result + @param len number of samples desired + @param overrun Set if read buffer has been overrun, e.g. data not being read fast enough + @param timestamp The timestamp of the first samples to be read + @param underrun Set if USRP does not have data to transmit, e.g. data not being sent fast enough + @param RSSI The received signal strength of the read result + @return The number of samples actually read + */ + int readSamples(std::vector<short *> &buf, int len, bool *overrun, + TIMESTAMP timestamp = 0xffffffff, bool *underrun = NULL, + unsigned *RSSI = NULL); + /** + Write samples to the USRP. + @param buf Contains the data to be written. + @param len number of samples to write. + @param underrun Set if USRP does not have data to transmit, e.g. data not being sent fast enough + @param timestamp The timestamp of the first sample of the data buffer. + @param isControl Set if data is a control packet, e.g. a ping command + @return The number of samples actually written + */ + int writeSamples(std::vector<short *> &bufs, int len, bool *underrun, + TIMESTAMP timestamp = 0xffffffff, bool isControl = false); + + /** Update the alignment between the read and write timestamps */ + bool updateAlignment(TIMESTAMP timestamp); + + /** Set the transmitter frequency */ + bool setTxFreq(double wFreq, size_t chan = 0); + + /** Set the receiver frequency */ + bool setRxFreq(double wFreq, size_t chan = 0); + + /** Returns the starting write Timestamp*/ + TIMESTAMP initialWriteTimestamp(void) { return 20000;} + + /** Returns the starting read Timestamp*/ + TIMESTAMP initialReadTimestamp(void) { return 20000;} + + /** returns the full-scale transmit amplitude **/ + double fullScaleInputValue() {return 13500.0;} + + /** returns the full-scale receive amplitude **/ + double fullScaleOutputValue() {return 9450.0;} + + /** sets the receive chan gain, returns the gain setting **/ + double setRxGain(double dB, size_t chan = 0); + + /** get the current receive gain */ + double getRxGain(size_t chan = 0) { return rxGain; } + + /** return maximum Rx Gain **/ + double maxRxGain(void); + + /** return minimum Rx Gain **/ + double minRxGain(void); + + /** sets the transmit chan gain, returns the gain setting **/ + double setTxGain(double dB, size_t chan = 0); + + /** return maximum Tx Gain **/ + double maxTxGain(void); + + /** return minimum Rx Gain **/ + double minTxGain(void); + + /** sets the RX path to use, returns true if successful and false otherwise */ + bool setRxAntenna(const std::string &ant, size_t chan = 0); + + /* return the used RX path */ + std::string getRxAntenna(size_t chan = 0); + + /** sets the RX path to use, returns true if successful and false otherwise */ + bool setTxAntenna(const std::string &ant, size_t chan = 0); + + /* return the used RX path */ + std::string getTxAntenna(size_t chan = 0); + + /** Return internal status values */ + inline double getTxFreq(size_t chan = 0) { return 0; } + inline double getRxFreq(size_t chan = 0) { return 0; } + inline double getSampleRate() { return actualSampleRate; } + inline double numberRead() { return samplesRead; } + inline double numberWritten() { return samplesWritten; } + + std::vector<std::string> tx_paths, rx_paths; +}; + +#endif // _USRP_DEVICE_H_ |