diff options
| author | Matthias P. Braendli <matthias.braendli@mpb.li> | 2017-11-04 08:36:03 +0100 | 
|---|---|---|
| committer | Matthias P. Braendli <matthias.braendli@mpb.li> | 2017-11-04 08:36:03 +0100 | 
| commit | 4d8310ae0ffe1f78a2b8623d55f63ae504ff1aa8 (patch) | |
| tree | eb6a7ad2b63b2887725159f53904c7a471429269 | |
| parent | 34afa3a0632817c30e4e5427ee67138d59c4ede3 (diff) | |
| download | dabmod-4d8310ae0ffe1f78a2b8623d55f63ae504ff1aa8.tar.gz dabmod-4d8310ae0ffe1f78a2b8623d55f63ae504ff1aa8.tar.bz2 dabmod-4d8310ae0ffe1f78a2b8623d55f63ae504ff1aa8.zip | |
Make DPD Feedback server SDRDevice-agnostic
| -rw-r--r-- | Makefile.am | 17 | ||||
| -rw-r--r-- | TODO | 21 | ||||
| -rw-r--r-- | src/ConfigParser.cpp | 8 | ||||
| -rw-r--r-- | src/TimestampDecoder.h | 6 | ||||
| -rw-r--r-- | src/output/Feedback.cpp | 356 | ||||
| -rw-r--r-- | src/output/Feedback.h | 116 | ||||
| -rw-r--r-- | src/output/SDR.cpp | 22 | ||||
| -rw-r--r-- | src/output/SDR.h | 85 | ||||
| -rw-r--r-- | src/output/SDRDevice.h | 136 | ||||
| -rw-r--r-- | src/output/Soapy.cpp | 57 | ||||
| -rw-r--r-- | src/output/Soapy.h | 11 | ||||
| -rw-r--r-- | src/output/UHD.cpp | 40 | ||||
| -rw-r--r-- | src/output/UHD.h | 9 | 
13 files changed, 771 insertions, 113 deletions
| diff --git a/Makefile.am b/Makefile.am index 8ce5f89..6e5c1a3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -81,20 +81,21 @@ odr_dabmod_SOURCES  = src/DabMod.cpp \  					  src/OutputZeroMQ.h \  					  src/TimestampDecoder.h \  					  src/TimestampDecoder.cpp \ -					  src/output/UHD.cpp \ -					  src/output/UHD.h \ -					  src/OutputUHDFeedback.cpp \ -					  src/OutputUHDFeedback.h \ +					  src/output/Feedback.cpp \ +					  src/output/Feedback.h \ +					  src/output/SDR.cpp \ +					  src/output/SDR.h \ +					  src/output/SDRDevice.h \  					  src/output/Soapy.cpp \  					  src/output/Soapy.h \ -					  src/output/SDR.h \ -					  src/output/SDR.cpp \ +					  src/output/UHD.cpp \ +					  src/output/UHD.h \ +					  src/InputFileReader.cpp \  					  src/InputMemory.cpp \  					  src/InputMemory.h \ -					  src/InputFileReader.cpp \ +					  src/InputReader.h \  					  src/InputTcpReader.cpp \  					  src/InputZeroMQReader.cpp \ -					  src/InputReader.h \  					  src/OutputFile.cpp \  					  src/OutputFile.h \  					  src/FrameMultiplexer.cpp \ @@ -6,6 +6,13 @@ Unless written, no activity has been started on the topics.  TODOs for ongoing SDR output refactoring  ---------------------------------------- +Currently, all the frontend tuning and timestamping settings are UHD-specific. +To make it possible to run with synchronised=1 using Soapy, refactoring the +output to share the parts that are common. + +This would enable SFN support with LimeSDR devices. + +  Clean up and separate GPS and refclk checks. @@ -13,10 +20,13 @@ Add antenna selection to config.  Add refclk stuff and timestamps to Soapy. -Make an abstraction for the DPD feedback server, use it for Soapy and UHD. +*done* Make an abstraction for the DPD feedback server, use it for Soapy and UHD.  Move staticdelay into a new process block +Double-check all #includes + +Move other non SDR outputs to the output folder.  Tests, both with B200 and LimeSDR:  - No timestamps @@ -41,15 +51,6 @@ However, when enabled, some receivers are not able to lock on the signal.  Is the power of the TII too strong? Synchronisation wrong? -Rework Soapy and UHD outputs ----------------------------- -Currently, all the frontend tuning and timestamping settings are UHD-specific. -To make it possible to run with synchronised=1 using Soapy, refactoring the -output to share the parts that are common. - -This would enable SFN support with LimeSDR devices. - -  Finalise EDI input  ------------------  The EDI input, based on work started in http://git.mpb.li/git/odr-edilib/ diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index c532ab5..8facec3 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -29,15 +29,17 @@  #   include "config.h"  #endif +#include <cstdint> +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/ini_parser.hpp> +  #include "ConfigParser.h"  #include "porting.h"  #include "Utils.h"  #include "Log.h"  #include "DabModulator.h" +#include "output/SDR.h" -#include <unistd.h> -#include <boost/property_tree/ptree.hpp> -#include <boost/property_tree/ini_parser.hpp>  using namespace std; diff --git a/src/TimestampDecoder.h b/src/TimestampDecoder.h index 34d862f..1ef493e 100644 --- a/src/TimestampDecoder.h +++ b/src/TimestampDecoder.h @@ -94,6 +94,12 @@ struct frame_timestamp          return t;      } +    long long int get_ns() const { +        long long int ns = timestamp_sec * 1000000000ull; +        ns += llrint((double)timestamp_pps / 0.016384); +        return ns; +    } +      void print(const char* t)      {          fprintf(stderr, diff --git a/src/output/Feedback.cpp b/src/output/Feedback.cpp new file mode 100644 index 0000000..f0bbd98 --- /dev/null +++ b/src/output/Feedback.cpp @@ -0,0 +1,356 @@ +/* +   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the +   Queen in Right of Canada (Communications Research Center Canada) + +   Copyright (C) 2017 +   Matthias P. Braendli, matthias.braendli@mpb.li + +    http://opendigitalradio.org + +DESCRIPTION: +   This presents a TCP socket to an external tool which calculates +   a Digital Predistortion model from a short sequence of transmit +   samples and corresponding receive samples. +*/ + +/* +   This file is part of ODR-DabMod. + +   ODR-DabMod 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. + +   ODR-DabMod 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 ODR-DabMod.  If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#include <vector> +#include <complex> +#include <cstring> +#include <sys/socket.h> +#include <errno.h> +#include <poll.h> +#include <boost/date_time/posix_time/posix_time.hpp> +#include "output/Feedback.h" +#include "Utils.h" +#include "Socket.h" + +using namespace std; + +namespace Output { + +DPDFeedbackServer::DPDFeedbackServer( +        std::shared_ptr<SDRDevice> device, +        uint16_t port, +        uint32_t sampleRate) : +    m_port(port), +    m_sampleRate(sampleRate), +    m_device(device) +{ +    if (m_port) { +        m_running.store(true); + +        rx_burst_thread = boost::thread( +                &DPDFeedbackServer::ReceiveBurstThread, this); + +        burst_tcp_thread = boost::thread( +                &DPDFeedbackServer::ServeFeedbackThread, this); +    } +} + +DPDFeedbackServer::~DPDFeedbackServer() +{ +    m_running.store(false); + +    rx_burst_thread.interrupt(); +    if (rx_burst_thread.joinable()) { +        rx_burst_thread.join(); +    } + +    burst_tcp_thread.interrupt(); +    if (burst_tcp_thread.joinable()) { +        burst_tcp_thread.join(); +    } +} + +void DPDFeedbackServer::set_tx_frame( +        const std::vector<uint8_t> &buf, +        const struct frame_timestamp &buf_ts) +{ +    if (not m_running) { +        throw runtime_error("DPDFeedbackServer not running"); +    } + +    boost::mutex::scoped_lock lock(burstRequest.mutex); + +    if (buf.size() % sizeof(complexf) != 0) { +        throw std::logic_error("Buffer for tx frame has incorrect size"); +    } + +    if (burstRequest.state == BurstRequestState::SaveTransmitFrame) { +        const size_t n = std::min( +                burstRequest.num_samples * sizeof(complexf), buf.size()); + +        burstRequest.num_samples = n / sizeof(complexf); + +        burstRequest.tx_samples.clear(); +        burstRequest.tx_samples.resize(n); +        // A frame will always begin with the NULL symbol, which contains +        // no power. Instead of taking n samples at the beginning of the +        // frame, we take them at the end and adapt the timestamp accordingly. + +        const size_t start_ix = buf.size() - n; +        copy(buf.begin() + start_ix, buf.end(), burstRequest.tx_samples.begin()); + +        frame_timestamp ts = buf_ts; +        ts += (1.0 * start_ix) / (sizeof(complexf) * m_sampleRate); + +        burstRequest.tx_second = ts.timestamp_sec; +        burstRequest.tx_pps = ts.timestamp_pps; + +        // Prepare the next state +        burstRequest.rx_second = ts.timestamp_sec; +        burstRequest.rx_pps = ts.timestamp_pps; +        burstRequest.state = BurstRequestState::SaveReceiveFrame; + +        lock.unlock(); +        burstRequest.mutex_notification.notify_one(); +    } +    else { +        lock.unlock(); +    } +} + +void DPDFeedbackServer::ReceiveBurstThread() +{ +    try { +        set_thread_name("dpdreceiveburst"); + +        while (m_running) { +            boost::mutex::scoped_lock lock(burstRequest.mutex); +            while (burstRequest.state != BurstRequestState::SaveReceiveFrame) { +                if (not m_running) break; +                burstRequest.mutex_notification.wait(lock); +            } + +            if (not m_running) break; + +            const size_t num_samps = burstRequest.num_samples; + +            frame_timestamp ts; +            ts.timestamp_sec = burstRequest.rx_second; +            ts.timestamp_pps = burstRequest.rx_pps; +            ts.timestamp_valid = true; + +            // We need to free the mutex while we recv(), because otherwise we block the +            // TX thread +            lock.unlock(); + +            const double device_time = m_device->get_real_secs(); +            const double cmd_time = ts.get_real_secs(); + +            std::vector<uint8_t> buf(num_samps * sizeof(complexf)); + +            const double timeout = 60; +            size_t samples_read = m_device->receive_frame( +                    reinterpret_cast<complexf*>(buf.data()), +                    num_samps, ts, timeout); + +            lock.lock(); +            burstRequest.rx_samples = std::move(buf); +            burstRequest.rx_samples.resize(samples_read * sizeof(complexf)); + +            // The recv might have happened at another time than requested +            burstRequest.rx_second = ts.timestamp_sec; +            burstRequest.rx_pps = ts.timestamp_pps; + +            etiLog.level(debug) << "DPD: acquired " << samples_read << +                " RX feedback samples " << +                "at time " << burstRequest.tx_second << " + " << +                std::fixed << burstRequest.tx_pps / 16384000.0 << +                " Delta=" << cmd_time - device_time; + +            burstRequest.state = BurstRequestState::Acquired; + +            lock.unlock(); +            burstRequest.mutex_notification.notify_one(); +        } +    } +    catch (const runtime_error &e) { +        etiLog.level(error) << "DPD Feedback RX runtime error: " << e.what(); +    } +    catch (const std::exception &e) { +        etiLog.level(error) << "DPD Feedback RX exception: " << e.what(); +    } +    catch (...) { +        etiLog.level(error) << "DPD Feedback RX unknown exception!"; +    } + +    m_running.store(false); +} + +void DPDFeedbackServer::ServeFeedback() +{ +    TCPSocket m_server_sock; +    m_server_sock.listen(m_port); + +    etiLog.level(info) << "DPD Feedback server listening on port " << m_port; + +    while (m_running) { +        struct sockaddr_in client; +        TCPSocket client_sock = m_server_sock.accept_with_timeout(1000, &client); + +        if (not client_sock.valid()) { +            // No connection request received +            continue; +        } + +        uint8_t request_version = 0; +        ssize_t read = client_sock.recv(&request_version, 1, 0); +        if (!read) break; // done reading +        if (read < 0) { +            etiLog.level(info) << +                "DPD Feedback Server Client read request version failed: " << strerror(errno); +            break; +        } + +        if (request_version != 1) { +            etiLog.level(info) << "DPD Feedback Server wrong request version"; +            break; +        } + +        uint32_t num_samples = 0; +        read = client_sock.recv(&num_samples, 4, 0); +        if (!read) break; // done reading +        if (read < 0) { +            etiLog.level(info) << +                "DPD Feedback Server Client read num samples failed"; +            break; +        } + +        // We are ready to issue the request now +        { +            boost::mutex::scoped_lock lock(burstRequest.mutex); +            burstRequest.num_samples = num_samples; +            burstRequest.state = BurstRequestState::SaveTransmitFrame; + +            lock.unlock(); +        } + +        // Wait for the result to be ready +        boost::mutex::scoped_lock lock(burstRequest.mutex); +        while (burstRequest.state != BurstRequestState::Acquired) { +            if (not m_running) break; +            burstRequest.mutex_notification.wait(lock); +        } + +        burstRequest.state = BurstRequestState::None; +        lock.unlock(); + +        burstRequest.num_samples = std::min(burstRequest.num_samples, +                std::min( +                    burstRequest.tx_samples.size() / sizeof(complexf), +                    burstRequest.rx_samples.size() / sizeof(complexf))); + +        uint32_t num_samples_32 = burstRequest.num_samples; +        if (client_sock.sendall(&num_samples_32, sizeof(num_samples_32)) < 0) { +            etiLog.level(info) << +                "DPD Feedback Server Client send num_samples failed"; +            break; +        } + +        if (client_sock.sendall( +                    &burstRequest.tx_second, +                    sizeof(burstRequest.tx_second)) < 0) { +            etiLog.level(info) << +                "DPD Feedback Server Client send tx_second failed"; +            break; +        } + +        if (client_sock.sendall( +                    &burstRequest.tx_pps, +                    sizeof(burstRequest.tx_pps)) < 0) { +            etiLog.level(info) << +                "DPD Feedback Server Client send tx_pps failed"; +            break; +        } + +        const size_t frame_bytes = burstRequest.num_samples * sizeof(complexf); + +        if (burstRequest.tx_samples.size() < frame_bytes) { +            throw logic_error("DPD Feedback burstRequest invalid: not enough TX samples"); +        } + +        if (client_sock.sendall( +                    &burstRequest.tx_samples[0], +                    frame_bytes) < 0) { +            etiLog.level(info) << +                "DPD Feedback Server Client send tx_frame failed"; +            break; +        } + +        if (client_sock.sendall( +                    &burstRequest.rx_second, +                    sizeof(burstRequest.rx_second)) < 0) { +            etiLog.level(info) << +                "DPD Feedback Server Client send rx_second failed"; +            break; +        } + +        if (client_sock.sendall( +                    &burstRequest.rx_pps, +                    sizeof(burstRequest.rx_pps)) < 0) { +            etiLog.level(info) << +                "DPD Feedback Server Client send rx_pps failed"; +            break; +        } + +        if (burstRequest.rx_samples.size() < frame_bytes) { +            throw logic_error("DPD Feedback burstRequest invalid: not enough RX samples"); +        } + +        if (client_sock.sendall( +                    &burstRequest.rx_samples[0], +                    frame_bytes) < 0) { +            etiLog.level(info) << +                "DPD Feedback Server Client send rx_frame failed"; +            break; +        } +    } +} + +void DPDFeedbackServer::ServeFeedbackThread() +{ +    set_thread_name("dpdfeedbackserver"); + +    while (m_running) { +        try { +            ServeFeedback(); +        } +        catch (const runtime_error &e) { +            etiLog.level(error) << "DPD Feedback Server runtime error: " << e.what(); +        } +        catch (const std::exception &e) { +            etiLog.level(error) << "DPD Feedback Server exception: " << e.what(); +        } +        catch (...) { +            etiLog.level(error) << "DPD Feedback Server unknown exception!"; +        } + +        boost::this_thread::sleep(boost::posix_time::seconds(5)); +    } + +    m_running.store(false); +} + +} // namespace Output diff --git a/src/output/Feedback.h b/src/output/Feedback.h new file mode 100644 index 0000000..2cad508 --- /dev/null +++ b/src/output/Feedback.h @@ -0,0 +1,116 @@ +/* +   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the +   Queen in Right of Canada (Communications Research Center Canada) + +   Copyright (C) 2017 +   Matthias P. Braendli, matthias.braendli@mpb.li + +    http://opendigitalradio.org + +DESCRIPTION: +   This presents a TCP socket to an external tool which calculates +   a Digital Predistortion model from a short sequence of transmit +   samples and corresponding receive samples. +*/ + +/* +   This file is part of ODR-DabMod. + +   ODR-DabMod 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. + +   ODR-DabMod 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 ODR-DabMod.  If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#include <boost/thread.hpp> +#include <memory> +#include <string> +#include <atomic> + +#include "Log.h" +#include "TimestampDecoder.h" +#include "output/SDRDevice.h" + +namespace Output { + +enum class BurstRequestState { +    None, // To pending request +    SaveTransmitFrame, // The TX thread has to save an outgoing frame +    SaveReceiveFrame, // The RX thread has to save an incoming frame +    Acquired, // Both TX and RX frames are ready +}; + +struct FeedbackBurstRequest { +    // All fields in this struct are protected +    mutable boost::mutex mutex; +    boost::condition_variable mutex_notification; + +    BurstRequestState state = BurstRequestState::None; + +    // In the SaveTransmit states, num_samples complexf samples are saved into +    // the vectors +    size_t num_samples = 0; + +    // The timestamp of the first sample of the TX buffers +    uint32_t tx_second = 0; +    uint32_t tx_pps = 0; // in units of 1/16384000s + +    // Samples contain complexf, but since our internal representation is uint8_t +    // we keep it like that +    std::vector<uint8_t> tx_samples; + +    // The timestamp of the first sample of the RX buffers +    uint32_t rx_second = 0; +    uint32_t rx_pps = 0; + +    std::vector<uint8_t> rx_samples; // Also, actually complexf +}; + +// Serve TX samples and RX feedback samples over a TCP connection +class DPDFeedbackServer { +    public: +        DPDFeedbackServer( +                std::shared_ptr<SDRDevice> device, +                uint16_t port, // Set to 0 to disable the Feedbackserver +                uint32_t sampleRate); +        DPDFeedbackServer(const DPDFeedbackServer& other) = delete; +        DPDFeedbackServer& operator=(const DPDFeedbackServer& other) = delete; +        ~DPDFeedbackServer(); + +        void set_tx_frame(const std::vector<uint8_t> &buf, +                const struct frame_timestamp& ts); + +    private: +        // Thread that reacts to burstRequests and receives from the SDR device +        void ReceiveBurstThread(void); + +        // Thread that listens for requests over TCP to get TX and RX feedback +        void ServeFeedbackThread(void); +        void ServeFeedback(void); + +        boost::thread rx_burst_thread; +        boost::thread burst_tcp_thread; + +        FeedbackBurstRequest burstRequest; + +        std::atomic_bool m_running; +        uint16_t m_port = 0; +        uint32_t m_sampleRate = 0; +        std::shared_ptr<SDRDevice> m_device; +}; + +} // namespace Output diff --git a/src/output/SDR.cpp b/src/output/SDR.cpp index 2ad8e57..66e93e2 100644 --- a/src/output/SDR.cpp +++ b/src/output/SDR.cpp @@ -67,6 +67,13 @@ SDR::SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device) :      m_config.muting = true;      m_device_thread = std::thread(&SDR::process_thread_entry, this); + +    if (m_config.dpdFeedbackServerPort != 0) { +        m_dpd_feedback_server = make_shared<DPDFeedbackServer>( +                m_device, +                m_config.dpdFeedbackServerPort, +                m_config.sampleRate); +    }  }  SDR::~SDR() @@ -108,10 +115,21 @@ int SDR::process(Buffer *dataIn)          if (frame.ts.fct == -1) {              etiLog.level(info) << -                "OutputUHD: dropping one frame with invalid FCT"; +                "SDR output: dropping one frame with invalid FCT";          }          else { -            // TODO setup Feedback and set tx_frame +            try { +                m_dpd_feedback_server->set_tx_frame(frame.buf, frame.ts); +            } +            catch (const runtime_error& e) { +                etiLog.level(warn) << +                    "SDR output: Feedback server failed, restarting..."; + +                m_dpd_feedback_server = std::make_shared<DPDFeedbackServer>( +                        m_device, +                        m_config.dpdFeedbackServerPort, +                        m_config.sampleRate); +            }              size_t num_frames = m_queue.push_wait_if_full(frame,                      FRAMES_MAX_SIZE); diff --git a/src/output/SDR.h b/src/output/SDR.h index 370bc38..5593ce3 100644 --- a/src/output/SDR.h +++ b/src/output/SDR.h @@ -36,92 +36,13 @@ DESCRIPTION:  #include "ModPlugin.h"  #include "EtiReader.h" +#include "output/SDRDevice.h" +#include "output/Feedback.h"  namespace Output {  using complexf = std::complex<float>; -enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; - -/* This structure is used as initial configuration for all SDR devices. - * It must also contain all remote-controllable settings, otherwise - * they will get lost on a modulator restart. */ -struct SDRDeviceConfig { -    std::string device; -    std::string subDevice; // For UHD - -    long masterClockRate = 32768000; -    unsigned sampleRate = 2048000; -    double frequency = 0.0; -    double lo_offset = 0.0; -    double txgain = 0.0; -    double rxgain = 0.0; -    bool enableSync = false; - -    // When working with timestamps, mute the frames that -    // do not have a timestamp -    bool muteNoTimestamps = false; -    unsigned dabMode = 0; -    unsigned maxGPSHoldoverTime = 0; - -    /* allowed values for UHD : auto, int, sma, mimo */ -    std::string refclk_src; - -    /* allowed values for UHD : int, sma, mimo */ -    std::string pps_src; - -    /* allowed values for UHD : pos, neg */ -    std::string pps_polarity; - -    /* What to do when the reference clock PLL loses lock */ -    refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; - -    // muting can only be changed using the remote control -    bool muting = false; - -    // TCP port on which to serve TX and RX samples for the -    // digital pre distortion learning tool -    uint16_t dpdFeedbackServerPort = 0; -}; - -// Each frame contains one OFDM frame, and its -// associated timestamp -struct FrameData { -    // Buffer holding frame data -    std::vector<uint8_t> buf; - -    // A full timestamp contains a TIST according to standard -    // and time information within MNSC with tx_second. -    struct frame_timestamp ts; -}; - - -// All SDR Devices must implement the SDRDevice interface -class SDRDevice { -    public: -        struct RunStatistics { -            size_t num_underruns; -            size_t num_late_packets; -            size_t num_overruns; -            size_t num_frames_modulated; //TODO increment -        }; - -        // TODO make some functions const -        virtual void tune(double lo_offset, double frequency) = 0; -        virtual double get_tx_freq(void) = 0; -        virtual void set_txgain(double txgain) = 0; -        virtual double get_txgain(void) = 0; -        virtual void transmit_frame(const struct FrameData& frame) = 0; -        virtual RunStatistics get_run_statistics(void) = 0; -        virtual double get_real_secs(void) = 0; - - -        // Return true if GPS and reference clock inputs are ok -        virtual bool is_clk_source_ok(void) = 0; - -        virtual const char* device_name(void) = 0; -}; -  class SDR : public ModOutput, public RemoteControllable {      public:          SDR(SDRDeviceConfig& config, std::shared_ptr<SDRDevice> device); @@ -159,6 +80,8 @@ class SDR : public ModOutput, public RemoteControllable {          std::shared_ptr<SDRDevice> m_device;          std::string m_name; +        std::shared_ptr<DPDFeedbackServer> m_dpd_feedback_server; +          EtiSource *m_eti_source = nullptr;          bool     sourceContainsTimestamp = false;          bool     last_tx_time_initialised = false; diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h new file mode 100644 index 0000000..856233b --- /dev/null +++ b/src/output/SDRDevice.h @@ -0,0 +1,136 @@ +/* +   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the +   Queen in Right of Canada (Communications Research Center Canada) + +   Copyright (C) 2017 +   Matthias P. Braendli, matthias.braendli@mpb.li + +    http://opendigitalradio.org + +DESCRIPTION: +   Common interface for all SDR outputs +*/ + +/* +   This file is part of ODR-DabMod. + +   ODR-DabMod 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. + +   ODR-DabMod 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 ODR-DabMod.  If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#   include <config.h> +#endif + +#include <cstdint> +#include <string> +#include <vector> +#include <complex> + +#include "TimestampDecoder.h" + +namespace Output { + +enum refclk_lock_loss_behaviour_t { CRASH, IGNORE }; + +using complexf = std::complex<float>; + +/* This structure is used as initial configuration for all SDR devices. + * It must also contain all remote-controllable settings, otherwise + * they will get lost on a modulator restart. */ +struct SDRDeviceConfig { +    std::string device; +    std::string subDevice; // For UHD + +    long masterClockRate = 32768000; +    unsigned sampleRate = 2048000; +    double frequency = 0.0; +    double lo_offset = 0.0; +    double txgain = 0.0; +    double rxgain = 0.0; +    bool enableSync = false; + +    // When working with timestamps, mute the frames that +    // do not have a timestamp +    bool muteNoTimestamps = false; +    unsigned dabMode = 0; +    unsigned maxGPSHoldoverTime = 0; + +    /* allowed values for UHD : auto, int, sma, mimo */ +    std::string refclk_src; + +    /* allowed values for UHD : int, sma, mimo */ +    std::string pps_src; + +    /* allowed values for UHD : pos, neg */ +    std::string pps_polarity; + +    /* What to do when the reference clock PLL loses lock */ +    refclk_lock_loss_behaviour_t refclk_lock_loss_behaviour; + +    // muting can only be changed using the remote control +    bool muting = false; + +    // TCP port on which to serve TX and RX samples for the +    // digital pre distortion learning tool +    uint16_t dpdFeedbackServerPort = 0; +}; + +// Each frame contains one OFDM frame, and its +// associated timestamp +struct FrameData { +    // Buffer holding frame data +    std::vector<uint8_t> buf; + +    // A full timestamp contains a TIST according to standard +    // and time information within MNSC with tx_second. +    struct frame_timestamp ts; +}; + + +// All SDR Devices must implement the SDRDevice interface +class SDRDevice { +    public: +        struct RunStatistics { +            size_t num_underruns; +            size_t num_late_packets; +            size_t num_overruns; +            size_t num_frames_modulated; //TODO increment +        }; + +        // TODO make some functions const +        virtual void tune(double lo_offset, double frequency) = 0; +        virtual double get_tx_freq(void) = 0; +        virtual void set_txgain(double txgain) = 0; +        virtual double get_txgain(void) = 0; +        virtual void transmit_frame(const struct FrameData& frame) = 0; +        virtual RunStatistics get_run_statistics(void) = 0; +        virtual double get_real_secs(void) = 0; +        virtual void set_rxgain(double rxgain) = 0; +        virtual double get_rxgain(void) = 0; +        virtual size_t receive_frame( +                complexf *buf, +                size_t num_samples, +                struct frame_timestamp& ts, +                double timeout_secs) = 0; + + +        // Return true if GPS and reference clock inputs are ok +        virtual bool is_clk_source_ok(void) = 0; + +        virtual const char* device_name(void) = 0; +}; + +} // namespace Output diff --git a/src/output/Soapy.cpp b/src/output/Soapy.cpp index d6b5953..e0a6f10 100644 --- a/src/output/Soapy.cpp +++ b/src/output/Soapy.cpp @@ -88,17 +88,18 @@ Soapy::Soapy(SDRDeviceConfig& config) :      etiLog.level(info) << "SoapySDR:Actual tx gain: " <<          m_device->getGain(SOAPY_SDR_TX, 0); -    std::vector<size_t> channels; -    channels.push_back(0); -    m_stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels); -    m_device->activateStream(m_stream); +    const std::vector<size_t> channels({0}); +    m_tx_stream = m_device->setupStream(SOAPY_SDR_TX, "CF32", channels); +    m_device->activateStream(m_tx_stream); + +    m_rx_stream = m_device->setupStream(SOAPY_SDR_RX, "CF32", channels);  }  Soapy::~Soapy()  {      if (m_device != nullptr) { -        if (m_stream != nullptr) { -            m_device->closeStream(m_stream); +        if (m_tx_stream != nullptr) { +            m_device->closeStream(m_tx_stream);          }          SoapySDR::Device::unmake(m_device);      } @@ -156,6 +157,46 @@ double Soapy::get_real_secs(void)      }  } +void Soapy::set_rxgain(double rxgain) +{ +    m_device->setGain(SOAPY_SDR_RX, 0, m_conf.rxgain); +    m_conf.rxgain = m_device->getGain(SOAPY_SDR_RX, 0); +} + +double Soapy::get_rxgain(void) +{ +    return m_device->getGain(SOAPY_SDR_RX, 0); +} + +size_t Soapy::receive_frame( +        complexf *buf, +        size_t num_samples, +        struct frame_timestamp& ts, +        double timeout_secs) +{ +    int flags = 0; +    long long timeNs = ts.get_ns(); +    const size_t numElems = num_samples; + +    void *buffs[1]; +    buffs[0] = buf; + +    m_device->activateStream(m_rx_stream, flags, timeNs, numElems); + +    auto ret = m_device->readStream(m_tx_stream, buffs, num_samples, flags, timeNs); + +    m_device->deactivateStream(m_rx_stream); + +    // TODO update effective receive ts + +    if (ret < 0) { +        throw runtime_error("Soapy readStream error: " + to_string(ret)); +    } + +    return ret; +} + +  bool Soapy::is_clk_source_ok()  {      // TODO @@ -181,7 +222,7 @@ void Soapy::transmit_frame(const struct FrameData& frame)      }      // Stream MTU is in samples, not bytes. -    const size_t mtu = m_device->getStreamMTU(m_stream); +    const size_t mtu = m_device->getStreamMTU(m_tx_stream);      size_t num_acc_samps = 0;      while (num_acc_samps < numSamples) { @@ -192,7 +233,7 @@ void Soapy::transmit_frame(const struct FrameData& frame)          int flags = 0; -        auto ret = m_device->writeStream(m_stream, buffs, samps_to_send, flags); +        auto ret = m_device->writeStream(m_tx_stream, buffs, samps_to_send, flags);          if (ret == SOAPY_SDR_TIMEOUT) {              continue; diff --git a/src/output/Soapy.h b/src/output/Soapy.h index 97076b5..c603193 100644 --- a/src/output/Soapy.h +++ b/src/output/Soapy.h @@ -68,6 +68,14 @@ class Soapy : public Output::SDRDevice          virtual RunStatistics get_run_statistics(void) override;          virtual double get_real_secs(void) override; +        virtual void set_rxgain(double rxgain) override; +        virtual double get_rxgain(void) override; +        virtual size_t receive_frame( +                complexf *buf, +                size_t num_samples, +                struct frame_timestamp& ts, +                double timeout_secs) override; +          // Return true if GPS and reference clock inputs are ok          virtual bool is_clk_source_ok(void) override;          virtual const char* device_name(void) override; @@ -75,7 +83,8 @@ class Soapy : public Output::SDRDevice      private:          SDRDeviceConfig& m_conf;          SoapySDR::Device *m_device = nullptr; -        SoapySDR::Stream *m_stream = nullptr; +        SoapySDR::Stream *m_tx_stream = nullptr; +        SoapySDR::Stream *m_rx_stream = nullptr;          size_t underflows = 0;          size_t overflows = 0; diff --git a/src/output/UHD.cpp b/src/output/UHD.cpp index 13a0a57..3769c60 100644 --- a/src/output/UHD.cpp +++ b/src/output/UHD.cpp @@ -235,6 +235,10 @@ UHD::UHD(              m_usrp, m_conf.dpdFeedbackServerPort, m_conf.sampleRate);      */ +    const uhd::stream_args_t stream_args("fc32"); //complex floats +    m_rx_stream = m_usrp->get_rx_stream(stream_args); +    m_tx_stream = m_usrp->get_tx_stream(stream_args); +      MDEBUG("OutputUHD:UHD ready.\n");  } @@ -342,6 +346,42 @@ double UHD::get_real_secs(void)      return m_usrp->get_time_now().get_real_secs();  } +void UHD::set_rxgain(double rxgain) +{ +    m_usrp->set_rx_gain(m_conf.rxgain); +    m_conf.rxgain = m_usrp->get_rx_gain(); +} + +double UHD::get_rxgain() +{ +    return m_usrp->get_rx_gain(); +} + +size_t UHD::receive_frame( +        complexf *buf, +        size_t num_samples, +        struct frame_timestamp& ts, +        double timeout_secs) +{ +    uhd::stream_cmd_t cmd( +            uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE); +    cmd.num_samps = num_samples; +    cmd.stream_now = false; +    cmd.time_spec = uhd::time_spec_t(ts.timestamp_sec, ts.pps_offset()); + +    m_rx_stream->issue_stream_cmd(cmd); + +    uhd::rx_metadata_t md; + +    constexpr double timeout = 60; +    size_t samples_read = m_rx_stream->recv(buf, num_samples, md, timeout); + +    // Update the ts with the effective receive TS +    ts.timestamp_sec = md.time_spec.get_full_secs(); +    ts.timestamp_pps = md.time_spec.get_frac_secs() * 16384000.0; +    return samples_read; +} +  // Return true if GPS and reference clock inputs are ok  bool UHD::is_clk_source_ok(void)  { diff --git a/src/output/UHD.h b/src/output/UHD.h index 220e4c8..3742924 100644 --- a/src/output/UHD.h +++ b/src/output/UHD.h @@ -84,6 +84,14 @@ class UHD : public Output::SDRDevice          virtual RunStatistics get_run_statistics(void) override;          virtual double get_real_secs(void) override; +        virtual void set_rxgain(double rxgain) override; +        virtual double get_rxgain(void) override; +        virtual size_t receive_frame( +                complexf *buf, +                size_t num_samples, +                struct frame_timestamp& ts, +                double timeout_secs) override; +          // Return true if GPS and reference clock inputs are ok          virtual bool is_clk_source_ok(void) override;          virtual const char* device_name(void) override; @@ -92,6 +100,7 @@ class UHD : public Output::SDRDevice          SDRDeviceConfig& m_conf;          uhd::usrp::multi_usrp::sptr m_usrp;          uhd::tx_streamer::sptr m_tx_stream; +        uhd::rx_streamer::sptr m_rx_stream;          size_t num_underflows = 0;          size_t num_overflows = 0; | 
