summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorandreas128 <Andreas>2017-06-07 21:11:20 +0100
committerandreas128 <Andreas>2017-06-07 21:11:20 +0100
commite37567081d00778aea70ab42ad294679dff328be (patch)
treec2dfbfb8f6f0a9cd942a28713cf850fcf62a8984 /src
parent02aa4b6c14506b72eb2fe9d3f4e99b751e2e91c8 (diff)
parent418eb33d0948bb12b7b2ed2179d43ad66258aa72 (diff)
downloaddabmod-e37567081d00778aea70ab42ad294679dff328be.tar.gz
dabmod-e37567081d00778aea70ab42ad294679dff328be.tar.bz2
dabmod-e37567081d00778aea70ab42ad294679dff328be.zip
Merge branch 'next_memless' into next
Diffstat (limited to 'src')
-rw-r--r--src/ConfigParser.cpp9
-rw-r--r--src/ConfigParser.h2
-rw-r--r--src/DabMod.cpp6
-rw-r--r--src/DabModulator.cpp52
-rw-r--r--src/DabModulator.h4
-rw-r--r--src/MemlessPoly.cpp187
-rw-r--r--src/MemlessPoly.h78
-rw-r--r--src/OutputUHD.cpp95
-rw-r--r--src/OutputUHD.h16
-rw-r--r--src/OutputUHDFeedback.cpp377
-rw-r--r--src/OutputUHDFeedback.h118
11 files changed, 886 insertions, 58 deletions
diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp
index 393f58a..459811f 100644
--- a/src/ConfigParser.cpp
+++ b/src/ConfigParser.cpp
@@ -168,6 +168,12 @@ static void parse_configfile(
pt.get<std::string>("firfilter.filtertapsfile", "default");
}
+ // Poly coefficients:
+ if (pt.get("poly.enabled", 0) == 1) {
+ mod_settings.polyCoefFilename =
+ pt.get<std::string>("poly.polycoeffile", "default");
+ }
+
// Output options
std::string output_selected;
try {
@@ -212,6 +218,7 @@ static void parse_configfile(
}
outputuhd_conf.txgain = pt.get("uhdoutput.txgain", 0.0);
+ outputuhd_conf.rxgain = pt.get("uhdoutput.rxgain", 0.0);
outputuhd_conf.frequency = pt.get<double>("uhdoutput.frequency", 0);
std::string chan = pt.get<std::string>("uhdoutput.channel", "");
outputuhd_conf.dabMode = mod_settings.dabMode;
@@ -249,6 +256,8 @@ static void parse_configfile(
outputuhd_conf.maxGPSHoldoverTime = pt.get("uhdoutput.max_gps_holdover_time", 0);
+ outputuhd_conf.dpdFeedbackServerPort = pt.get<long>("uhdoutput.dpd_port", 0);
+
mod_settings.outputuhd_conf = outputuhd_conf;
mod_settings.useUHDOutput = 1;
}
diff --git a/src/ConfigParser.h b/src/ConfigParser.h
index 2f49595..22a4fc5 100644
--- a/src/ConfigParser.h
+++ b/src/ConfigParser.h
@@ -74,6 +74,8 @@ struct mod_settings_t {
std::string filterTapsFilename = "";
+ std::string polyCoefFilename = "";
+
#if defined(HAVE_OUTPUT_UHD)
OutputUHDConfig outputuhd_conf;
diff --git a/src/DabMod.cpp b/src/DabMod.cpp
index 88c7524..7c342a2 100644
--- a/src/DabMod.cpp
+++ b/src/DabMod.cpp
@@ -327,7 +327,8 @@ int launch_modulator(int argc, char* argv[])
mod_settings.digitalgain,
mod_settings.normalise,
mod_settings.gainmodeVariance,
- mod_settings.filterTapsFilename);
+ mod_settings.filterTapsFilename,
+ mod_settings.polyCoefFilename);
if (format_converter) {
flowgraph.connect(modulator, format_converter);
@@ -431,7 +432,8 @@ int launch_modulator(int argc, char* argv[])
mod_settings.digitalgain,
mod_settings.normalise,
mod_settings.gainmodeVariance,
- mod_settings.filterTapsFilename);
+ mod_settings.filterTapsFilename,
+ mod_settings.polyCoefFilename);
if (format_converter) {
flowgraph.connect(modulator, format_converter);
diff --git a/src/DabModulator.cpp b/src/DabModulator.cpp
index c41b8fc..4e0bc33 100644
--- a/src/DabModulator.cpp
+++ b/src/DabModulator.cpp
@@ -47,6 +47,7 @@
#include "Resampler.h"
#include "ConvEncoder.h"
#include "FIRFilter.h"
+#include "MemlessPoly.h"
#include "TII.h"
#include "PuncturingEncoder.h"
#include "TimeInterleaver.h"
@@ -61,7 +62,8 @@ DabModulator::DabModulator(
unsigned dabMode, GainMode gainMode,
float& digGain, float normalise,
float gainmodeVariance,
- const std::string& filterTapsFilename
+ const std::string& filterTapsFilename,
+ const std::string& polyCoefFilename
) :
ModInput(),
myOutputRate(outputRate),
@@ -74,6 +76,7 @@ DabModulator::DabModulator(
myEtiSource(etiSource),
myFlowgraph(NULL),
myFilterTapsFilename(filterTapsFilename),
+ myPolyCoefFilename(polyCoefFilename),
myTiiConfig(tiiConfig)
{
PDEBUG("DabModulator::DabModulator(%u, %u, %u, %zu) @ %p\n",
@@ -203,7 +206,8 @@ int DabModulator::process(Buffer* dataOut)
(1 + myNbSymbols), myNbCarriers, mySpacing);
auto cifGain = make_shared<GainControl>(
- mySpacing, myGainMode, myDigGain, myNormalise, myGainmodeVariance);
+ mySpacing, myGainMode, myDigGain, myNormalise,
+ myGainmodeVariance);
rcs.enrol(cifGain.get());
@@ -215,6 +219,19 @@ int DabModulator::process(Buffer* dataOut)
cifFilter = make_shared<FIRFilter>(myFilterTapsFilename);
rcs.enrol(cifFilter.get());
}
+
+ shared_ptr<MemlessPoly> cifPoly;
+ if (not myPolyCoefFilename.empty()) {
+ cifPoly = make_shared<MemlessPoly>(myPolyCoefFilename);
+ etiLog.level(debug) << myPolyCoefFilename << "\n";
+ etiLog.level(debug) << cifPoly->m_coefs[0] << " " <<
+ cifPoly->m_coefs[1] << " "<< cifPoly->m_coefs[2] << " "<<
+ cifPoly->m_coefs[3] << " "<< cifPoly->m_coefs[4] << " "<<
+ cifPoly->m_coefs[5] << " "<< cifPoly->m_coefs[6] << " "<<
+ cifPoly->m_coefs[7] << "\n";
+ rcs.enrol(cifPoly.get());
+ }
+
auto myOutput = make_shared<OutputMemory>(dataOut);
shared_ptr<Resampler> cifRes;
@@ -306,7 +323,8 @@ int DabModulator::process(Buffer* dataOut)
auto subchConv = make_shared<ConvEncoder>(subchSizeIn);
// Configuring puncturing encoder
- auto subchPunc = make_shared<PuncturingEncoder>(subchannel->framesizeCu());
+ auto subchPunc =
+ make_shared<PuncturingEncoder>(subchannel->framesizeCu());
for (const auto& rule : subchannel->get_rules()) {
PDEBUG(" Adding rule:\n");
@@ -342,34 +360,44 @@ int DabModulator::process(Buffer* dataOut)
if (useCicEq) {
myFlowgraph->connect(cifSig, cifCicEq);
myFlowgraph->connect(cifCicEq, cifOfdm);
- } else {
+ }
+ else {
myFlowgraph->connect(cifSig, cifOfdm);
}
myFlowgraph->connect(cifOfdm, cifGain);
myFlowgraph->connect(cifGain, cifGuard);
+ auto cifOut = cifPoly ?
+ static_pointer_cast<ModPlugin>(cifPoly) :
+ static_pointer_cast<ModPlugin>(myOutput);
+
if (cifFilter) {
myFlowgraph->connect(cifGuard, cifFilter);
if (cifRes) {
myFlowgraph->connect(cifFilter, cifRes);
- myFlowgraph->connect(cifRes, myOutput);
- } else {
- myFlowgraph->connect(cifFilter, myOutput);
+ myFlowgraph->connect(cifRes, cifOut);
+ }
+ else {
+ myFlowgraph->connect(cifFilter, cifOut);
}
}
- else { //no filtering
+ else {
if (cifRes) {
myFlowgraph->connect(cifGuard, cifRes);
- myFlowgraph->connect(cifRes, myOutput);
- } else {
- myFlowgraph->connect(cifGuard, myOutput);
+ myFlowgraph->connect(cifRes, cifOut);
}
+ else {
+ myFlowgraph->connect(cifGuard, cifOut);
+ }
+ }
+ if (cifPoly) {
+ myFlowgraph->connect(cifPoly, myOutput);
}
}
////////////////////////////////////////////////////////////////////
- // Proccessing data
+ // Processing data
////////////////////////////////////////////////////////////////////
return myFlowgraph->run();
}
diff --git a/src/DabModulator.h b/src/DabModulator.h
index c9bdbe1..0c691dd 100644
--- a/src/DabModulator.h
+++ b/src/DabModulator.h
@@ -55,7 +55,8 @@ public:
unsigned dabMode, GainMode gainMode,
float& digGain, float normalise,
float gainmodeVariance,
- const std::string& filterTapsFilename);
+ const std::string& filterTapsFilename,
+ const std::string& polyCoefFilename);
DabModulator(const DabModulator& other) = delete;
DabModulator& operator=(const DabModulator& other) = delete;
virtual ~DabModulator();
@@ -80,6 +81,7 @@ protected:
Flowgraph* myFlowgraph;
OutputMemory* myOutput;
std::string myFilterTapsFilename;
+ std::string myPolyCoefFilename;
tii_config_t& myTiiConfig;
size_t myNbSymbols;
diff --git a/src/MemlessPoly.cpp b/src/MemlessPoly.cpp
new file mode 100644
index 0000000..7e074eb
--- /dev/null
+++ b/src/MemlessPoly.cpp
@@ -0,0 +1,187 @@
+/*
+ Copyright (C) 2007, 2008, 2009, 2010, 2011 Her Majesty the Queen in
+ Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2017
+ Matthias P. Braendli, matthias.braendli@mpb.li
+ Andreas Steger, andreas.steger@digris.ch
+
+ http://opendigitalradio.org
+
+ This block implements a memoryless polynom for digital predistortion.
+ For better performance, multiplying is done in another thread, leading
+ to a pipeline delay of two calls to MemlessPoly::process
+ */
+/*
+ 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/>.
+ */
+
+#include "MemlessPoly.h"
+#include "PcDebug.h"
+#include "Utils.h"
+
+#include <stdio.h>
+#include <stdexcept>
+
+#include <array>
+#include <iostream>
+#include <fstream>
+#include <memory>
+
+using namespace std;
+
+
+// By default the signal is unchanged
+static const std::array<float, 8> default_coefficients({
+ 1, 0.0, 0.0, 0.0,
+ 0.0, 0.0, 0.0, 0.0
+ });
+
+
+MemlessPoly::MemlessPoly(const std::string& coefs_file) :
+ PipelinedModCodec(),
+ RemoteControllable("memlesspoly"),
+ m_coefs_file(coefs_file)
+{
+ PDEBUG("MemlessPoly::MemlessPoly(%s) @ %p\n",
+ coefs_file.c_str(), this);
+
+ RC_ADD_PARAMETER(ncoefs, "(Read-only) number of coefficients.");
+ RC_ADD_PARAMETER(coeffile, "Filename containing coefficients. When written to, the new file gets automatically loaded.");
+
+ load_coefficients(m_coefs_file);
+
+ start_pipeline_thread();
+}
+
+void MemlessPoly::load_coefficients(const std::string &coefFile)
+{
+ std::vector<float> coefs;
+ if (coefFile == "default") {
+ std::copy(default_coefficients.begin(), default_coefficients.end(),
+ std::back_inserter(coefs));
+ }
+ else {
+ std::ifstream coef_fstream(coefFile.c_str());
+ if(!coef_fstream) {
+ fprintf(stderr, "MemlessPoly: file %s could not be opened !\n", coefFile.c_str());
+ throw std::runtime_error("MemlessPoly: Could not open file with coefs! ");
+ }
+ int n_coefs;
+ coef_fstream >> n_coefs;
+
+ if (n_coefs <= 0) {
+ fprintf(stderr, "MemlessPoly: warning: coefs file has invalid format\n");
+ throw std::runtime_error("MemlessPoly: coefs file has invalid format.");
+ }
+
+ if (n_coefs != 8) {
+ throw std::runtime_error( "MemlessPoly: error: coefs file does not have 8 coefs\n");
+ }
+
+ fprintf(stderr, "MemlessPoly: Reading %d coefs...\n", n_coefs);
+
+ coefs.resize(n_coefs);
+
+ int n;
+ for (n = 0; n < n_coefs; n++) {
+ coef_fstream >> coefs[n];
+ PDEBUG("MemlessPoly: coef: %f\n", coefs[n] );
+ if (coef_fstream.eof()) {
+ fprintf(stderr, "MemlessPoly: file %s should contains %d coefs, but EOF reached "\
+ "after %d coefs !\n", coefFile.c_str(), n_coefs, n);
+ throw std::runtime_error("MemlessPoly: coefs file invalid ! ");
+ }
+ }
+ }
+
+ {
+ std::lock_guard<std::mutex> lock(m_coefs_mutex);
+
+ m_coefs = coefs;
+ }
+}
+
+
+int MemlessPoly::internal_process(Buffer* const dataIn, Buffer* dataOut)
+{
+ const float* in = reinterpret_cast<const float*>(dataIn->getData());
+ float* out = reinterpret_cast<float*>(dataOut->getData());
+ size_t sizeIn = dataIn->getLength() / sizeof(float);
+
+ {
+ std::lock_guard<std::mutex> lock(m_coefs_mutex);
+ for (size_t i = 0; i < sizeIn; i += 1) {
+ float mag = std::abs(in[i]);
+ //out[i] = in[i];
+ out[i] = in[i] * (
+ m_coefs[0] +
+ m_coefs[1] * mag +
+ m_coefs[2] * mag*mag +
+ m_coefs[3] * mag*mag*mag +
+ m_coefs[4] * mag*mag*mag*mag +
+ m_coefs[5] * mag*mag*mag*mag*mag +
+ m_coefs[6] * mag*mag*mag*mag*mag*mag +
+ m_coefs[7] * mag*mag*mag*mag*mag*mag*mag
+ );
+ }
+ }
+
+ return dataOut->getLength();
+}
+
+void MemlessPoly::set_parameter(const string& parameter, const string& value)
+{
+ stringstream ss(value);
+ ss.exceptions ( stringstream::failbit | stringstream::badbit );
+
+ if (parameter == "ncoefs") {
+ throw ParameterError("Parameter 'ncoefs' is read-only");
+ }
+ else if (parameter == "coeffile") {
+ try {
+ load_coefficients(value);
+ m_coefs_file = value;
+ }
+ catch (std::runtime_error &e) {
+ throw ParameterError(e.what());
+ }
+ }
+ else {
+ stringstream ss;
+ ss << "Parameter '" << parameter <<
+ "' is not exported by controllable " << get_rc_name();
+ throw ParameterError(ss.str());
+ }
+}
+
+const string MemlessPoly::get_parameter(const string& parameter) const
+{
+ stringstream ss;
+ if (parameter == "ncoefs") {
+ ss << m_coefs.size();
+ }
+ else if (parameter == "coefFile") {
+ ss << m_coefs_file;
+ }
+ else {
+ ss << "Parameter '" << parameter <<
+ "' is not exported by controllable " << get_rc_name();
+ throw ParameterError(ss.str());
+ }
+ return ss.str();
+}
+
diff --git a/src/MemlessPoly.h b/src/MemlessPoly.h
new file mode 100644
index 0000000..210b4b4
--- /dev/null
+++ b/src/MemlessPoly.h
@@ -0,0 +1,78 @@
+/*
+ Copyright (C) 2007, 2008, 2009, 2010, 2011 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
+ */
+/*
+ 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 "RemoteControl.h"
+#include "ModPlugin.h"
+#include "PcDebug.h"
+#include "ThreadsafeQueue.h"
+
+#include <sys/types.h>
+#include <complex>
+#include <thread>
+#include <vector>
+#include <time.h>
+#include <cstdio>
+#include <string>
+#include <memory>
+
+#define MEMLESSPOLY_PIPELINE_DELAY 1
+
+typedef std::complex<float> complexf;
+
+class MemlessPoly : public PipelinedModCodec, public RemoteControllable
+{
+public:
+ MemlessPoly(const std::string& coefs_file);
+
+ virtual const char* name() { return "MemlessPoly"; }
+
+ /******* REMOTE CONTROL ********/
+ virtual void set_parameter(const std::string& parameter,
+ const std::string& value);
+
+ virtual const std::string get_parameter(
+ const std::string& parameter) const;
+
+//TODO to protected
+ std::vector<float> m_coefs;
+
+
+protected:
+ int internal_process(Buffer* const dataIn, Buffer* dataOut);
+ void load_coefficients(const std::string &coefFile);
+
+ std::string m_coefs_file;
+
+ mutable std::mutex m_coefs_mutex;
+};
+
diff --git a/src/OutputUHD.cpp b/src/OutputUHD.cpp
index 3a5225b..c2f985b 100644
--- a/src/OutputUHD.cpp
+++ b/src/OutputUHD.cpp
@@ -50,7 +50,7 @@
using namespace std;
// Maximum number of frames that can wait in uwd.frames
-static const size_t FRAMES_MAX_SIZE = 2;
+static const size_t FRAMES_MAX_SIZE = 8;
typedef std::complex<float> complexf;
@@ -80,6 +80,36 @@ void uhd_msg_handler(uhd::msg::type_t type, const std::string &msg)
}
}
+static void tune_usrp_to(
+ uhd::usrp::multi_usrp::sptr usrp,
+ double lo_offset,
+ double frequency)
+{
+ if (lo_offset != 0.0) {
+ etiLog.level(info) << std::fixed << std::setprecision(3) <<
+ "OutputUHD:Setting freq to " << frequency <<
+ " with LO offset " << lo_offset << "...";
+
+ const auto tr = uhd::tune_request_t(frequency, lo_offset);
+ uhd::tune_result_t result = usrp->set_tx_freq(tr);
+
+ etiLog.level(debug) << "OutputUHD:" <<
+ std::fixed << std::setprecision(0) <<
+ " Target RF: " << result.target_rf_freq <<
+ " Actual RF: " << result.actual_rf_freq <<
+ " Target DSP: " << result.target_dsp_freq <<
+ " Actual DSP: " << result.actual_dsp_freq;
+ }
+ else {
+ //set the centre frequency
+ etiLog.level(info) << std::fixed << std::setprecision(3) <<
+ "OutputUHD:Setting freq to " << frequency << "...";
+ usrp->set_tx_freq(frequency);
+ }
+
+ usrp->set_rx_freq(frequency);
+}
+
// Check function for GPS TIMELOCK sensor from the ODR LEA-M8F board GPSDO
bool check_gps_timelock(uhd::usrp::multi_usrp::sptr usrp)
{
@@ -165,6 +195,7 @@ OutputUHD::OutputUHD(
/* register the parameters that can be remote controlled */
RC_ADD_PARAMETER(txgain, "UHD analog daughterboard TX gain");
+ RC_ADD_PARAMETER(rxgain, "UHD analog daughterboard RX gain for DPD feedback");
RC_ADD_PARAMETER(freq, "UHD transmission frequency");
RC_ADD_PARAMETER(muting, "Mute the output by stopping the transmitter");
RC_ADD_PARAMETER(staticdelay, "Set static delay (uS) between 0 and 96000");
@@ -223,31 +254,14 @@ OutputUHD::OutputUHD(
throw std::runtime_error("Cannot set USRP sample rate. Aborted.");
}
- if (myConf.lo_offset != 0.0) {
- etiLog.level(info) << std::fixed << std::setprecision(3) <<
- "OutputUHD:Setting freq to " << myConf.frequency <<
- " with LO offset " << myConf.lo_offset << "...";
-
- const auto tr = uhd::tune_request_t(myConf.frequency, myConf.lo_offset);
- uhd::tune_result_t result = myUsrp->set_tx_freq(tr);
-
- etiLog.level(debug) << "OutputUHD:" <<
- std::fixed << std::setprecision(0) <<
- " Target RF: " << result.target_rf_freq <<
- " Actual RF: " << result.actual_rf_freq <<
- " Target DSP: " << result.target_dsp_freq <<
- " Actual DSP: " << result.actual_dsp_freq;
- }
- else {
- //set the centre frequency
- etiLog.level(info) << std::fixed << std::setprecision(3) <<
- "OutputUHD:Setting freq to " << myConf.frequency << "...";
- myUsrp->set_tx_freq(myConf.frequency);
- }
+ tune_usrp_to(myUsrp, myConf.lo_offset, myConf.frequency);
myConf.frequency = myUsrp->get_tx_freq();
etiLog.level(info) << std::fixed << std::setprecision(3) <<
- "OutputUHD:Actual frequency: " << myConf.frequency;
+ "OutputUHD:Actual TX frequency: " << myConf.frequency;
+
+ etiLog.level(info) << std::fixed << std::setprecision(3) <<
+ "OutputUHD:Actual RX frequency: " << myUsrp->get_tx_freq();
myUsrp->set_tx_gain(myConf.txgain);
MDEBUG("OutputUHD:Actual TX Gain: %f ...\n", myUsrp->get_tx_gain());
@@ -284,13 +298,18 @@ OutputUHD::OutputUHD(
SetDelayBuffer(myConf.dabMode);
- MDEBUG("OutputUHD:UHD ready.\n");
-}
+ myUsrp->set_rx_rate(myConf.sampleRate);
+ MDEBUG("OutputUHD:Actual RX Rate: %f sps...\n", myUsrp->get_rx_rate());
+ myUsrp->set_rx_antenna("RX2");
+ MDEBUG("OutputUHD:Set RX Antenna: %s ...\n", myUsrp->get_rx_antenna().c_str());
-OutputUHD::~OutputUHD()
-{
- MDEBUG("OutputUHD::~OutputUHD() @ %p\n", this);
+ myUsrp->set_rx_gain(myConf.rxgain);
+ MDEBUG("OutputUHD:Actual RX Gain: %f ...\n", myUsrp->get_rx_gain());
+
+ uhdFeedback.setup(myUsrp, myConf.dpdFeedbackServerPort, myConf.sampleRate);
+
+ MDEBUG("OutputUHD:UHD ready.\n");
}
@@ -424,12 +443,11 @@ int OutputUHD::process(Buffer* dataIn)
"OutputUHD: dropping one frame with invalid FCT";
}
else {
- while (true) {
- size_t num_frames = uwd.frames.push_wait_if_full(frame,
- FRAMES_MAX_SIZE);
- etiLog.log(trace, "UHD,push %zu", num_frames);
- break;
- }
+ uhdFeedback.set_tx_frame(frame.buf, frame.ts);
+
+ size_t num_frames = uwd.frames.push_wait_if_full(frame,
+ FRAMES_MAX_SIZE);
+ etiLog.log(trace, "UHD,push %zu", num_frames);
}
}
@@ -915,9 +933,13 @@ void OutputUHD::set_parameter(const string& parameter, const string& value)
ss >> myConf.txgain;
myUsrp->set_tx_gain(myConf.txgain);
}
+ else if (parameter == "rxgain") {
+ ss >> myConf.rxgain;
+ myUsrp->set_rx_gain(myConf.rxgain);
+ }
else if (parameter == "freq") {
ss >> myConf.frequency;
- myUsrp->set_tx_freq(myConf.frequency);
+ tune_usrp_to(myUsrp, myConf.lo_offset, myConf.frequency);
myConf.frequency = myUsrp->get_tx_freq();
}
else if (parameter == "muting") {
@@ -956,6 +978,9 @@ const string OutputUHD::get_parameter(const string& parameter) const
if (parameter == "txgain") {
ss << myConf.txgain;
}
+ else if (parameter == "rxgain") {
+ ss << myConf.rxgain;
+ }
else if (parameter == "freq") {
ss << myConf.frequency;
}
diff --git a/src/OutputUHD.h b/src/OutputUHD.h
index d42245f..c966c7e 100644
--- a/src/OutputUHD.h
+++ b/src/OutputUHD.h
@@ -2,7 +2,7 @@
Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
Queen in Right of Canada (Communications Research Center Canada)
- Copyright (C) 2016
+ Copyright (C) 2017
Matthias P. Braendli, matthias.braendli@mpb.li
http://opendigitalradio.org
@@ -56,6 +56,7 @@ DESCRIPTION:
#include "TimestampDecoder.h"
#include "RemoteControl.h"
#include "ThreadsafeQueue.h"
+#include "OutputUHDFeedback.h"
#include <stdio.h>
#include <sys/types.h>
@@ -188,6 +189,7 @@ struct OutputUHDConfig {
double frequency = 0.0;
double lo_offset = 0.0;
double txgain = 0.0;
+ double rxgain = 0.0;
bool enableSync = false;
bool muteNoTimestamps = false;
unsigned dabMode = 0;
@@ -210,14 +212,15 @@ struct OutputUHDConfig {
// static delay in microseconds
int staticDelayUs = 0;
-};
+ // TCP port on which to serve TX and RX samples for the
+ // digital pre distortion learning tool
+ uint16_t dpdFeedbackServerPort = 0;
+};
class OutputUHD: public ModOutput, public RemoteControllable {
public:
-
OutputUHD(OutputUHDConfig& config);
- ~OutputUHD();
int process(Buffer* dataIn);
@@ -235,11 +238,7 @@ class OutputUHD: public ModOutput, public RemoteControllable {
virtual const std::string get_parameter(
const std::string& parameter) const;
-
protected:
- OutputUHD(const OutputUHD& other) = delete;
- OutputUHD& operator=(const OutputUHD& other) = delete;
-
EtiSource *myEtiSource;
OutputUHDConfig& myConf;
uhd::usrp::multi_usrp::sptr myUsrp;
@@ -248,6 +247,7 @@ class OutputUHD: public ModOutput, public RemoteControllable {
bool gps_fix_verified;
struct UHDWorkerData uwd;
UHDWorker worker;
+ OutputUHDFeedback uhdFeedback;
private:
// Resize the internal delay buffer according to the dabMode and
diff --git a/src/OutputUHDFeedback.cpp b/src/OutputUHDFeedback.cpp
new file mode 100644
index 0000000..2a99e6b
--- /dev/null
+++ b/src/OutputUHDFeedback.cpp
@@ -0,0 +1,377 @@
+/*
+ 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
+
+#ifdef HAVE_OUTPUT_UHD
+
+#include <vector>
+#include <complex>
+#include <cstring>
+#include <uhd/types/stream_cmd.hpp>
+#include <sys/socket.h>
+#include <errno.h>
+#include <poll.h>
+#include "OutputUHDFeedback.h"
+#include "Utils.h"
+
+using namespace std;
+typedef std::complex<float> complexf;
+
+OutputUHDFeedback::OutputUHDFeedback()
+{
+ m_running.store(false);
+}
+
+void OutputUHDFeedback::setup(uhd::usrp::multi_usrp::sptr usrp, uint16_t port, uint32_t sampleRate)
+{
+ m_usrp = usrp;
+ m_sampleRate = sampleRate;
+ burstRequest.state = BurstRequestState::None;
+
+ if (port) {
+ m_port = port;
+ m_running.store(true);
+
+ rx_burst_thread = boost::thread(&OutputUHDFeedback::ReceiveBurstThread, this);
+ burst_tcp_thread = boost::thread(&OutputUHDFeedback::ServeFeedbackThread, this);
+ }
+}
+
+OutputUHDFeedback::~OutputUHDFeedback()
+{
+ m_running.store(false);
+
+ rx_burst_thread.interrupt();
+ burst_tcp_thread.interrupt();
+
+ rx_burst_thread.join();
+ burst_tcp_thread.join();
+}
+
+void OutputUHDFeedback::set_tx_frame(
+ const std::vector<uint8_t> &buf,
+ const struct frame_timestamp &buf_ts)
+{
+ boost::mutex::scoped_lock lock(burstRequest.mutex);
+
+ assert(buf.size() % sizeof(complexf) == 0);
+
+ 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 OutputUHDFeedback::ReceiveBurstThread()
+{
+ set_thread_name("uhdreceiveburst");
+
+ uhd::stream_args_t stream_args("fc32"); //complex floats
+ auto rxStream = m_usrp->get_rx_stream(stream_args);
+
+ 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;
+
+ uhd::stream_cmd_t cmd(
+ uhd::stream_cmd_t::stream_mode_t::STREAM_MODE_NUM_SAMPS_AND_DONE);
+ cmd.num_samps = burstRequest.num_samples;
+ cmd.stream_now = false;
+
+ double pps = burstRequest.rx_pps / 16384000.0;
+ cmd.time_spec = uhd::time_spec_t(burstRequest.rx_second, pps);
+
+ // We need to free the mutex while we recv(), because otherwise we block the
+ // TX thread
+ lock.unlock();
+
+ const double usrp_time = m_usrp->get_time_now().get_real_secs();
+ const double cmd_time = cmd.time_spec.get_real_secs();
+
+ rxStream->issue_stream_cmd(cmd);
+
+ uhd::rx_metadata_t md;
+
+ std::vector<uint8_t> buf(cmd.num_samps * sizeof(complexf));
+
+ const double timeout = 60;
+ size_t samples_read = rxStream->recv(&buf[0], cmd.num_samps, md, 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 = md.time_spec.get_full_secs();
+ burstRequest.rx_pps = md.time_spec.get_frac_secs() * 16384000.0;
+
+ 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 - usrp_time;
+
+ burstRequest.state = BurstRequestState::Acquired;
+
+ lock.unlock();
+ burstRequest.mutex_notification.notify_one();
+ }
+}
+
+static int accept_with_timeout(int server_socket, int timeout_ms, struct sockaddr_in *client)
+{
+ struct pollfd fds[1];
+ fds[0].fd = server_socket;
+ fds[0].events = POLLIN | POLLOUT;
+
+ int retval = poll(fds, 1, timeout_ms);
+
+ if (retval == -1) {
+ throw std::runtime_error("TCP Socket accept error: " + to_string(errno));
+ }
+ else if (retval) {
+ socklen_t client_len = sizeof(struct sockaddr_in);
+ return accept(server_socket, (struct sockaddr*)&client, &client_len);
+ }
+ else {
+ return -2;
+ }
+}
+
+static ssize_t sendall(int socket, const void *buffer, size_t buflen)
+{
+ uint8_t *buf = (uint8_t*)buffer;
+ while (buflen > 0) {
+ ssize_t sent = send(socket, buf, buflen, 0);
+ if (sent < 0) {
+ return -1;
+ }
+ else {
+ buf += sent;
+ buflen -= sent;
+ }
+ }
+ return buflen;
+}
+
+void OutputUHDFeedback::ServeFeedbackThread()
+{
+ set_thread_name("uhdservefeedback");
+
+ try {
+ if ((m_server_sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
+ throw std::runtime_error("Can't create TCP socket");
+ }
+
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(m_port);
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ if (bind(m_server_sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+ throw std::runtime_error("Can't bind TCP socket");
+ }
+
+ if (listen(m_server_sock, 1) < 0) {
+ throw std::runtime_error("Can't listen TCP socket");
+ }
+
+ etiLog.level(info) << "DPD Feedback server listening on port " << m_port;
+
+ while (m_running) {
+ struct sockaddr_in client;
+ int client_sock = accept_with_timeout(m_server_sock, 1000, &client);
+
+ if (client_sock == -1) {
+ throw runtime_error("Could not establish new connection");
+ }
+ else if (client_sock == -2) {
+ continue;
+ }
+
+ uint8_t request_version = 0;
+ ssize_t read = recv(client_sock, &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 = recv(client_sock, &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 (sendall(client_sock, &num_samples_32, sizeof(num_samples_32)) < 0) {
+ etiLog.level(info) <<
+ "DPD Feedback Server Client send num_samples failed";
+ break;
+ }
+
+ if (sendall(client_sock,
+ &burstRequest.tx_second,
+ sizeof(burstRequest.tx_second)) < 0) {
+ etiLog.level(info) <<
+ "DPD Feedback Server Client send tx_second failed";
+ break;
+ }
+
+ if (sendall(client_sock,
+ &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);
+
+ assert(burstRequest.tx_samples.size() >= frame_bytes);
+ if (sendall(client_sock,
+ &burstRequest.tx_samples[0],
+ frame_bytes) < 0) {
+ etiLog.level(info) <<
+ "DPD Feedback Server Client send tx_frame failed";
+ break;
+ }
+
+ if (sendall(client_sock,
+ &burstRequest.rx_second,
+ sizeof(burstRequest.rx_second)) < 0) {
+ etiLog.level(info) <<
+ "DPD Feedback Server Client send rx_second failed";
+ break;
+ }
+
+ if (sendall(client_sock,
+ &burstRequest.rx_pps,
+ sizeof(burstRequest.rx_pps)) < 0) {
+ etiLog.level(info) <<
+ "DPD Feedback Server Client send rx_pps failed";
+ break;
+ }
+
+ assert(burstRequest.rx_samples.size() >= frame_bytes);
+ if (sendall(client_sock,
+ &burstRequest.rx_samples[0],
+ frame_bytes) < 0) {
+ etiLog.level(info) <<
+ "DPD Feedback Server Client send rx_frame failed";
+ break;
+ }
+
+ close(client_sock);
+ }
+ }
+ catch (runtime_error &e) {
+ etiLog.level(error) << "DPD Feedback Server fault: " << e.what();
+ }
+
+ m_running = false;
+
+ if (m_server_sock != -1) {
+ close(m_server_sock);
+ m_server_sock = -1;
+ }
+}
+
+#endif
diff --git a/src/OutputUHDFeedback.h b/src/OutputUHDFeedback.h
new file mode 100644
index 0000000..32668b6
--- /dev/null
+++ b/src/OutputUHDFeedback.h
@@ -0,0 +1,118 @@
+/*
+ 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
+
+#ifdef HAVE_OUTPUT_UHD
+
+#include <uhd/utils/thread_priority.hpp>
+#include <uhd/utils/safe_main.hpp>
+#include <uhd/usrp/multi_usrp.hpp>
+#include <boost/thread.hpp>
+#include <memory>
+#include <string>
+#include <atomic>
+
+#include "Log.h"
+#include "TimestampDecoder.h"
+
+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 UHDReceiveBurstRequest {
+ // All fields in this struct are protected
+ mutable boost::mutex mutex;
+ boost::condition_variable mutex_notification;
+
+ BurstRequestState state;
+
+ // In the SaveTransmit states, num_samples complexf samples are saved into
+ // the vectors
+ size_t num_samples;
+
+ // The timestamp of the first sample of the TX buffers
+ uint32_t tx_second;
+ uint32_t tx_pps; // 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;
+ uint32_t rx_pps;
+
+ std::vector<uint8_t> rx_samples; // Also, actually complexf
+};
+
+// Serve TX samples and RX feedback samples over a TCP connection
+class OutputUHDFeedback {
+ public:
+ OutputUHDFeedback();
+ OutputUHDFeedback(const OutputUHDFeedback& other) = delete;
+ OutputUHDFeedback& operator=(const OutputUHDFeedback& other) = delete;
+ ~OutputUHDFeedback();
+
+ void setup(uhd::usrp::multi_usrp::sptr usrp, uint16_t port, uint32_t sampleRate);
+
+ 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 USRP
+ void ReceiveBurstThread(void);
+
+ // Thread that listens for requests over TCP to get TX and RX feedback
+ void ServeFeedbackThread(void);
+
+ boost::thread rx_burst_thread;
+ boost::thread burst_tcp_thread;
+
+ UHDReceiveBurstRequest burstRequest;
+
+ std::atomic_bool m_running;
+ int m_server_sock = -1;
+ uint16_t m_port = 0;
+ uint32_t m_sampleRate = 0;
+ uhd::usrp::multi_usrp::sptr m_usrp;
+};
+
+
+#endif // HAVE_OUTPUT_UHD