aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am6
-rw-r--r--doc/example.ini20
-rwxr-xr-xdoc/zmq-ctrl/zmq_remote.py6
-rw-r--r--dpd/README.md20
-rw-r--r--dpd/dpd.ini44
-rwxr-xr-xdpd/show_spectrum.py180
-rwxr-xr-xdpd/store_received.py157
-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
18 files changed, 1315 insertions, 62 deletions
diff --git a/Makefile.am b/Makefile.am
index f4e8e00..12dfe6e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,7 +1,7 @@
# Copyright (C) 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
@@ -62,6 +62,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \
src/FicSource.h \
src/FIRFilter.cpp \
src/FIRFilter.h \
+ src/MemlessPoly.cpp \
+ src/MemlessPoly.h \
src/PuncturingRule.cpp \
src/PuncturingRule.h \
src/PuncturingEncoder.cpp \
@@ -80,6 +82,8 @@ odr_dabmod_SOURCES = src/DabMod.cpp \
src/TimestampDecoder.cpp \
src/OutputUHD.cpp \
src/OutputUHD.h \
+ src/OutputUHDFeedback.cpp \
+ src/OutputUHDFeedback.h \
src/OutputSoapy.cpp \
src/OutputSoapy.h \
src/InputMemory.cpp \
diff --git a/doc/example.ini b/doc/example.ini
index 1b4a2b4..425dfa4 100644
--- a/doc/example.ini
+++ b/doc/example.ini
@@ -136,6 +136,21 @@ enabled=1
; If filtertapsfile is not given, the default taps are used.
;filtertapsfile=simple_taps.txt
+[poly]
+;Predistortion using memoryless polynom
+enabled=1
+polycoeffile=polyCoefs
+;eg:
+;echo "8
+;0.1
+;0
+;0
+;0
+;0
+;0
+;0
+;0" > polyCoefs
+
[output]
; choose output: possible values: uhd, file, zmq, soapysdr
output=uhd
@@ -245,6 +260,11 @@ behaviour_refclk_lock_lost=ignore
; default value: 0
max_gps_holdover_time=600
+; Enable the TCP server to communicate TX and RX feedback for
+; digital predistortion.
+; Set to 0 to disable
+dpd_port=50055
+
; section defining ZeroMQ output properties
[zmqoutput]
diff --git a/doc/zmq-ctrl/zmq_remote.py b/doc/zmq-ctrl/zmq_remote.py
index dffe53e..3a54b20 100755
--- a/doc/zmq-ctrl/zmq_remote.py
+++ b/doc/zmq-ctrl/zmq_remote.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python
import sys
import zmq
@@ -18,7 +18,7 @@ message_parts = sys.argv[2:]
# first do a ping test
print("ping")
-sock.send("ping")
+sock.send(b"ping")
data = sock.recv_multipart()
print("Received: {}".format(len(data)))
for i,part in enumerate(data):
@@ -32,7 +32,7 @@ for i, part in enumerate(message_parts):
print("Send {}({}): '{}'".format(i, f, part))
- sock.send(part, flags=f)
+ sock.send(part.encode(), flags=f)
data = sock.recv_multipart()
diff --git a/dpd/README.md b/dpd/README.md
new file mode 100644
index 0000000..ec7cec2
--- /dev/null
+++ b/dpd/README.md
@@ -0,0 +1,20 @@
+Digital Predistortion for ODR-DabMod
+====================================
+
+This folder contains work in progress for digital predistortion. It requires:
+
+- USRP B200.
+- Power amplifier.
+- A feedback connection from the power amplifier output, at an appropriate power level for the B200.
+ Usually this is done with a directional coupler.
+- ODR-DabMod with enabled dpd_port, and with a samplerate of 8192000 samples per second.
+- Synchronous=1 so that the USRP has the timestamping set properly, internal refclk and pps
+ are sufficient for this example.
+- A live mux source with TIST enabled.
+
+See dpd/dpd.ini for an example.
+
+TODO
+----
+
+Implement a PA model that updates the predistorter.
diff --git a/dpd/dpd.ini b/dpd/dpd.ini
new file mode 100644
index 0000000..5e809e5
--- /dev/null
+++ b/dpd/dpd.ini
@@ -0,0 +1,44 @@
+[remotecontrol]
+telnet=1
+telnetport=2121
+zmqctrl=1
+zmqctrlendpoint=tcp://127.0.0.1:9400
+
+[log]
+syslog=0
+filelog=0
+filename=/dev/stderr
+
+[input]
+transport=tcp
+source=localhost:9200
+
+[modulator]
+digital_gain=0.8
+rate=8192000
+
+[firfilter]
+enabled=0
+
+[output]
+output=uhd
+
+[uhdoutput]
+device=
+master_clock_rate=32768000
+type=b200
+txgain=75
+channel=13C
+refclk_source=internal
+pps_source=none
+behaviour_refclk_lock_lost=ignore
+max_gps_holdover_time=600
+dpd_port=50055
+rxgain=0
+
+[delaymanagement]
+; Use synchronous=1 so that the USRP time is set. This works
+; even in the absence of a reference clk and PPS
+synchronous=1
+mutenotimestamps=1
+offset=4.0
diff --git a/dpd/show_spectrum.py b/dpd/show_spectrum.py
new file mode 100755
index 0000000..e92c1d0
--- /dev/null
+++ b/dpd/show_spectrum.py
@@ -0,0 +1,180 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# This is an example tool that shows how to connect to ODR-DabMod's dpd TCP server
+# and get samples from there.
+#
+# Since the TX and RX samples are not perfectly aligned, the tool has to align them properly,
+# which is done in two steps: First on sample-level using a correlation, then with subsample
+# accuracy using a FFT approach.
+#
+# It requires SciPy and matplotlib.
+#
+# Copyright (C) 2017 Matthias P. Braendli
+# http://www.opendigitalradio.org
+# Licence: The MIT License, see notice at the end of this file
+
+import sys
+import socket
+import struct
+import numpy as np
+import matplotlib.pyplot as pp
+from matplotlib.animation import FuncAnimation
+import argparse
+
+SIZEOF_SAMPLE = 8 # complex floats
+
+def main():
+ parser = argparse.ArgumentParser(description="Plot the spectrum of ODR-DabMod's DPD feedback")
+ parser.add_argument('--samps', default='10240', help='Number of samples to request at once',
+ required=False)
+ parser.add_argument('--port', default='50055',
+ help='port to connect to ODR-DabMod DPD (default: 50055)',
+ required=False)
+
+ parser.add_argument('--animated', action='store_true', help='Enable real-time animation')
+
+ cli_args = parser.parse_args()
+
+ port = int(cli_args.port)
+ num_samps_to_request = int(cli_args.samps)
+
+ if cli_args.animated:
+ plot_spectrum_animated(port, num_samps_to_request)
+ else:
+ plot_spectrum_once(port, num_samps_to_request)
+
+def recv_exact(sock, num_bytes):
+ bufs = []
+ while num_bytes > 0:
+ b = sock.recv(num_bytes)
+ if len(b) == 0:
+ break
+ num_bytes -= len(b)
+ bufs.append(b)
+ return b''.join(bufs)
+
+def get_samples(port, num_samps_to_request):
+ """Connect to ODR-DabMod, retrieve TX and RX samples, load
+ into numpy arrays, and return a tuple
+ (tx_timestamp, tx_samples, rx_timestamp, rx_samples)
+ where the timestamps are doubles, and the samples are numpy
+ arrays of complex floats, both having the same size
+ """
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect(('localhost', port))
+
+ print("Send version");
+ s.sendall(b"\x01")
+
+ print("Send request for {} samples".format(num_samps_to_request))
+ s.sendall(struct.pack("=I", num_samps_to_request))
+
+ print("Wait for TX metadata")
+ num_samps, tx_second, tx_pps = struct.unpack("=III", recv_exact(s, 12))
+ tx_ts = tx_second + tx_pps / 16384000.0
+
+ if num_samps > 0:
+ print("Receiving {} TX samples".format(num_samps))
+ txframe_bytes = recv_exact(s, num_samps * SIZEOF_SAMPLE)
+ txframe = np.fromstring(txframe_bytes, dtype=np.complex64)
+ else:
+ txframe = np.array([], dtype=np.complex64)
+
+ print("Wait for RX metadata")
+ rx_second, rx_pps = struct.unpack("=II", recv_exact(s, 8))
+ rx_ts = rx_second + rx_pps / 16384000.0
+
+ if num_samps > 0:
+ print("Receiving {} RX samples".format(num_samps))
+ rxframe_bytes = recv_exact(s, num_samps * SIZEOF_SAMPLE)
+ rxframe = np.fromstring(rxframe_bytes, dtype=np.complex64)
+ else:
+ rxframe = np.array([], dtype=np.complex64)
+
+ print("Disconnecting")
+ s.close()
+
+ return (tx_ts, txframe, rx_ts, rxframe)
+
+def get_spectrum(port, num_samps_to_request):
+ tx_ts, txframe, rx_ts, rxframe = get_samples(port, num_samps_to_request)
+
+ # convert to complex doubles for more dynamic range
+ txframe = txframe.astype(np.complex128)
+ rxframe = rxframe.astype(np.complex128)
+
+ print("Received {} & {} frames at {} and {}".format(
+ len(txframe), len(rxframe), tx_ts, rx_ts))
+
+ print("Calculate TX and RX spectrum assuming 8192000 samples per second")
+ tx_spectrum = np.fft.fftshift(np.fft.fft(txframe, fft_size))
+ tx_power = 20*np.log10(np.abs(tx_spectrum))
+
+ rx_spectrum = np.fft.fftshift(np.fft.fft(rxframe, fft_size))
+ rx_power = 20*np.log10(np.abs(rx_spectrum))
+ return tx_power, rx_power
+
+
+sampling_rate = 8192000
+fft_size = 4096
+freqs = np.fft.fftshift(np.fft.fftfreq(fft_size, d=1./sampling_rate))
+
+def plot_spectrum_once(port, num_samps_to_request):
+ tx_power, rx_power = get_spectrum(port, num_samps_to_request)
+ fig = pp.figure()
+
+ fig.suptitle("TX and RX spectrum")
+ ax1 = fig.add_subplot(211)
+ ax1.set_title("TX")
+ ax1.plot(freqs, tx_power, 'r')
+ ax2 = fig.add_subplot(212)
+ ax2.set_title("RX")
+ ax2.plot(freqs, rx_power, 'b')
+ pp.show()
+
+def plot_spectrum_animated(port, num_samps_to_request):
+ fig, axes = pp.subplots(2, sharex=True)
+ line1, = axes[0].plot(freqs, np.ones(len(freqs)), 'r', animated=True)
+ axes[0].set_title("TX")
+ line2, = axes[1].plot(freqs, np.ones(len(freqs)), 'b', animated=True)
+ axes[1].set_title("RX")
+ lines = [line1, line2]
+
+ axes[0].set_ylim(-30, 50)
+ axes[1].set_ylim(-60, 40)
+
+ def update(frame):
+ tx_power, rx_power = get_spectrum(port, num_samps_to_request)
+
+ lines[0].set_ydata(tx_power)
+ lines[1].set_ydata(rx_power)
+ return lines
+
+ ani = FuncAnimation(fig, update, blit=True)
+ pp.show()
+
+main()
+
+# The MIT License (MIT)
+#
+# Copyright (c) 2017 Matthias P. Braendli
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
diff --git a/dpd/store_received.py b/dpd/store_received.py
new file mode 100755
index 0000000..902f607
--- /dev/null
+++ b/dpd/store_received.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# This is an example tool that shows how to connect to ODR-DabMod's dpd TCP server
+# and get samples from there.
+#
+# Since the TX and RX samples are not perfectly aligned, the tool has to align them properly,
+# which is done in two steps: First on sample-level using a correlation, then with subsample
+# accuracy using a FFT approach.
+#
+# It requires SciPy and matplotlib.
+#
+# Copyright (C) 2017 Matthias P. Braendli
+# http://www.opendigitalradio.org
+# Licence: The MIT License, see notice at the end of this file
+
+import sys
+import socket
+import struct
+import numpy as np
+import matplotlib.pyplot as pp
+from matplotlib.animation import FuncAnimation
+import argparse
+import os
+import time
+import src.dab_util as du
+
+SIZEOF_SAMPLE = 8 # complex floats
+
+def main():
+ parser = argparse.ArgumentParser(description="Plot the spectrum of ODR-DabMod's DPD feedback")
+ parser.add_argument('--samps', default='10240', help='Number of samples to request at once',
+ required=False)
+ parser.add_argument('--port', default='50055',
+ help='port to connect to ODR-DabMod DPD (default: 50055)',
+ required=False)
+ parser.add_argument('--out_dir', default='/tmp/record', help='Output directory',
+ required=False)
+ parser.add_argument('--count', default='1', help='Number of recordings',
+ required=False)
+ parser.add_argument('--verbose', type=int, default=0, help='Level of verbosity',
+ required=False)
+
+ parser.add_argument('--animated', action='store_true', help='Enable real-time animation')
+
+
+ cli_args = parser.parse_args()
+
+ if not os.path.isdir(cli_args.out_dir):
+ os.mkdir(cli_args.out_dir)
+
+ port = int(cli_args.port)
+ num_samps_to_request = int(cli_args.samps)
+
+ for i in range(int(cli_args.count)):
+ if i>0:
+ time.sleep(0.1)
+
+ tx_ts, txframe, rx_ts, rxframe = get_samples(port, num_samps_to_request)
+
+ txframe_aligned, rxframe_aligned = du.subsample_align(txframe, rxframe)
+
+ if cli_args.verbose >= 1:
+ n_up = 32
+ lag = du.lag_upsampling(txframe, rxframe, n_up)
+ lag_aligned = du.lag_upsampling(txframe_aligned, rxframe_aligned, n_up)
+ print("Lag from %d times oversampled signal:" % n_up)
+ print("Before alignment: %.2f" % lag)
+ print("After alignment: %.2f" % lag_aligned)
+ print("")
+
+ txframe_aligned.tofile("%s/%d_tx_record.iq" % (cli_args.out_dir, i))
+ rxframe_aligned.tofile("%s/%d_rx_record.iq" % (cli_args.out_dir, i))
+
+
+def recv_exact(sock, num_bytes):
+ bufs = []
+ while num_bytes > 0:
+ b = sock.recv(num_bytes)
+ if len(b) == 0:
+ break
+ num_bytes -= len(b)
+ bufs.append(b)
+ return b''.join(bufs)
+
+def get_samples(port, num_samps_to_request):
+ """Connect to ODR-DabMod, retrieve TX and RX samples, load
+ into numpy arrays, and return a tuple
+ (tx_timestamp, tx_samples, rx_timestamp, rx_samples)
+ where the timestamps are doubles, and the samples are numpy
+ arrays of complex floats, both having the same size
+ """
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect(('localhost', port))
+
+ print("Send version");
+ s.sendall(b"\x01")
+
+ print("Send request for {} samples".format(num_samps_to_request))
+ s.sendall(struct.pack("=I", num_samps_to_request))
+
+ print("Wait for TX metadata")
+ num_samps, tx_second, tx_pps = struct.unpack("=III", recv_exact(s, 12))
+ tx_ts = tx_second + tx_pps / 16384000.0
+
+ if num_samps > 0:
+ print("Receiving {} TX samples".format(num_samps))
+ txframe_bytes = recv_exact(s, num_samps * SIZEOF_SAMPLE)
+ txframe = np.fromstring(txframe_bytes, dtype=np.complex64)
+ else:
+ txframe = np.array([], dtype=np.complex64)
+
+ print("Wait for RX metadata")
+ rx_second, rx_pps = struct.unpack("=II", recv_exact(s, 8))
+ rx_ts = rx_second + rx_pps / 16384000.0
+
+ if num_samps > 0:
+ print("Receiving {} RX samples".format(num_samps))
+ rxframe_bytes = recv_exact(s, num_samps * SIZEOF_SAMPLE)
+ rxframe = np.fromstring(rxframe_bytes, dtype=np.complex64)
+ else:
+ rxframe = np.array([], dtype=np.complex64)
+
+ print("Disconnecting")
+ s.close()
+
+ return (tx_ts, txframe, rx_ts, rxframe)
+
+
+sampling_rate = 8192000
+fft_size = 4096
+freqs = np.fft.fftshift(np.fft.fftfreq(fft_size, d=1./sampling_rate))
+
+main()
+
+# The MIT License (MIT)
+#
+# Copyright (c) 2017 Matthias P. Braendli
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
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