From 558a210085b61b84e3be5a420d86d2ee6a500c8e Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Thu, 8 Jul 2021 15:49:23 +0200 Subject: Add support for BladeRF devices Many thanks to Steven Rossel for the work he did during his student project. --- src/ConfigParser.cpp | 32 +++++ src/ConfigParser.h | 5 +- src/DabMod.cpp | 24 +++- src/output/BladeRF.cpp | 321 +++++++++++++++++++++++++++++++++++++++++++++++++ src/output/BladeRF.h | 112 +++++++++++++++++ src/output/SDRDevice.h | 3 +- 6 files changed, 494 insertions(+), 3 deletions(-) create mode 100755 src/output/BladeRF.cpp create mode 100755 src/output/BladeRF.h (limited to 'src') diff --git a/src/ConfigParser.cpp b/src/ConfigParser.cpp index 73e51dd..ee7acc3 100644 --- a/src/ConfigParser.cpp +++ b/src/ConfigParser.cpp @@ -342,6 +342,36 @@ static void parse_configfile( mod_settings.useLimeOutput = true; } #endif + +#if defined(HAVE_BLADERF) + else if (output_selected == "bladerf") { + auto& outputbladerf_conf = mod_settings.sdr_device_config; + outputbladerf_conf.device = pt.Get("bladerfoutput.device", ""); + outputbladerf_conf.refclk_src = pt.Get("bladerfoutput.refclk_source", ""); + outputbladerf_conf.txgain = pt.GetReal("bladerfoutput.txgain", 0.0); + outputbladerf_conf.frequency = pt.GetReal("bladerfoutput.frequency", 0); + outputbladerf_conf.bandwidth = pt.GetReal("bladerfoutput.bandwidth", 0); + std::string chan = pt.Get("bladerfoutput.channel", ""); + outputbladerf_conf.dabMode = mod_settings.dabMode; + + if (outputbladerf_conf.frequency == 0 && chan == "") { + std::cerr << " BladeRF output enabled, but neither frequency nor channel defined.\n"; + throw std::runtime_error("Configuration error"); + } + else if (outputbladerf_conf.frequency == 0) { + outputbladerf_conf.frequency = parseChannel(chan); + } + else if (outputbladerf_conf.frequency != 0 && chan != "") { + std::cerr << " BladeRF output: cannot define both frequency and channel.\n"; + throw std::runtime_error("Configuration error"); + } + + outputbladerf_conf.dpdFeedbackServerPort = pt.GetInteger("bladerfoutput.dpd_port", 0); + + mod_settings.useBladeRFOutput = true; + } +#endif + #if defined(HAVE_ZEROMQ) else if (output_selected == "zmq") { mod_settings.outputName = pt.Get("zmqoutput.listen", ""); @@ -354,6 +384,7 @@ static void parse_configfile( throw std::runtime_error("Configuration error"); } + #if defined(HAVE_OUTPUT_UHD) mod_settings.sdr_device_config.enableSync = (pt.GetInteger("delaymanagement.synchronous", 0) == 1); mod_settings.sdr_device_config.muteNoTimestamps = (pt.GetInteger("delaymanagement.mutenotimestamps", 0) == 1); @@ -378,6 +409,7 @@ static void parse_configfile( #endif + /* Read TII parameters from config file */ mod_settings.tiiConfig.enable = pt.GetInteger("tii.enable", 0); mod_settings.tiiConfig.comb = pt.GetInteger("tii.comb", 0); diff --git a/src/ConfigParser.h b/src/ConfigParser.h index 33d7824..574caa2 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -38,6 +38,7 @@ #include "output/UHD.h" #include "output/Soapy.h" #include "output/Lime.h" +#include "output/BladeRF.h" #define ZMQ_INPUT_MAX_FRAME_QUEUE 500 @@ -51,6 +52,8 @@ struct mod_settings_t { bool useUHDOutput = false; bool useSoapyOutput = false; bool useLimeOutput = false; + bool useBladeRFOutput = false; + const std::string BladeRFOutputFormat = "s16"; // to transmit SC16 IQ size_t outputRate = 2048000; size_t clockRate = 0; @@ -84,7 +87,7 @@ struct mod_settings_t { // Settings for the OFDM windowing size_t ofdmWindowOverlap = 0; -#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) || defined(HAVE_LIMESDR) +#if defined(HAVE_OUTPUT_UHD) || defined(HAVE_SOAPYSDR) || defined(HAVE_LIMESDR) || defined(HAVE_BLADERF) Output::SDRDeviceConfig sdr_device_config; #endif diff --git a/src/DabMod.cpp b/src/DabMod.cpp index 979df87..f97c05d 100644 --- a/src/DabMod.cpp +++ b/src/DabMod.cpp @@ -57,6 +57,7 @@ #include "output/UHD.h" #include "output/Soapy.h" #include "output/Lime.h" +#include "output/BladeRF.h" #include "OutputZeroMQ.h" #include "InputReader.h" #include "PcDebug.h" @@ -157,6 +158,13 @@ static void printModSettings(const mod_settings_t& mod_settings) " master_clock_rate: " << mod_settings.sdr_device_config.masterClockRate << "\n"; } +#endif +#if defined(HAVE_BLADERF) + else if (mod_settings.useBladeRFOutput) { + ss << " BladeRF\n" + " Device: " << mod_settings.sdr_device_config.device << "\n" << + " refclk: " << mod_settings.sdr_device_config.refclk_src << "\n"; + } #endif else if (mod_settings.useZeroMQOutput) { ss << " ZeroMQ\n" << @@ -251,6 +259,16 @@ static shared_ptr prepare_output( rcs.enrol((Output::SDR*)output.get()); } #endif +#if defined(HAVE_BLADERF) + else if (s.useBladeRFOutput) { + /* We normalise specifically for the BladeRF output : range [-2048; 2047] */ + s.normalise = 2047.0f / normalise_factor; + s.sdr_device_config.sampleRate = s.outputRate; + auto bladerfdevice = make_shared(s.sdr_device_config); + output = make_shared(s.sdr_device_config, bladerfdevice); + rcs.enrol((Output::SDR*)output.get()); + } +#endif #if defined(HAVE_ZEROMQ) else if (s.useZeroMQOutput) { /* We normalise the same way as for the UHD output */ @@ -301,7 +319,8 @@ int launch_modulator(int argc, char* argv[]) mod_settings.useUHDOutput or mod_settings.useZeroMQOutput or mod_settings.useSoapyOutput or - mod_settings.useLimeOutput)) { + mod_settings.useLimeOutput or + mod_settings.useBladeRFOutput)) { throw std::runtime_error("Configuration error: Output not specified"); } @@ -314,6 +333,9 @@ int launch_modulator(int argc, char* argv[]) mod_settings.fileOutputFormat == "s16")) { format_converter = make_shared(mod_settings.fileOutputFormat); } + else if (mod_settings.useBladeRFOutput) { + format_converter = make_shared(mod_settings.BladeRFOutputFormat); + } auto output = prepare_output(mod_settings); diff --git a/src/output/BladeRF.cpp b/src/output/BladeRF.cpp new file mode 100755 index 0000000..a6ad0cc --- /dev/null +++ b/src/output/BladeRF.cpp @@ -0,0 +1,321 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + Copyright (C) 2021 + Steven Rossel, steven.rossel@bluewin.ch + + 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 . + */ + +#include "BladeRF.h" + +#ifdef HAVE_BLADERF + +#include +#include +#include +#include +#include +#include + +#include "Log.h" +#include "Utils.h" + +using namespace std; + +namespace Output +{ + +BladeRF::BladeRF(SDRDeviceConfig &config) : SDRDevice(), m_conf(config) +{ + + etiLog.level(info) << "BladeRF:Creating the device with: " << m_conf.device; + + struct bladerf_devinfo devinfo; + + // init device infos + bladerf_init_devinfo(&devinfo); // this function does not return a status + + int status = bladerf_open_with_devinfo(&m_device, &devinfo); // open device with info + if (status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot open BladeRF output device"); + } + + if (m_conf.refclk_src == "pps") + { + status = bladerf_set_vctcxo_tamer_mode(m_device, BLADERF_VCTCXO_TAMER_1_PPS); // 1 PPS tames the clock + if (status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot set BladeRF refclk to pps"); + } + } + else if (m_conf.refclk_src == "10mhz") + { + status = bladerf_set_vctcxo_tamer_mode(m_device, BLADERF_VCTCXO_TAMER_10_MHZ); // 10 MHz tames the clock + if (status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot set BladeRF refclk to 10 MHz"); + } + } + + status = bladerf_set_sample_rate(m_device, m_channel, (bladerf_sample_rate)m_conf.sampleRate, NULL); + if (status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot set BladeRF sample rate"); + } + + bladerf_sample_rate host_sample_rate = 0; + status = bladerf_get_sample_rate(m_device, m_channel, &host_sample_rate); + if (status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot get BladeRF sample rate"); + } + etiLog.level(info) << "BladeRF sample rate set to " << std::to_string(host_sample_rate / 1000.0) << " kHz"; + + tune(m_conf.lo_offset, m_conf.frequency); + + bladerf_frequency cur_frequency = 0; + + status = bladerf_get_frequency(m_device, m_channel, &cur_frequency); + if(status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot get BladeRF frequency"); + } + etiLog.level(info) << "BladeRF:Actual frequency: " << fixed << setprecision(3) << cur_frequency / 1000.0 << " kHz."; + + status = bladerf_set_gain(m_device, m_channel, (bladerf_gain)m_conf.txgain); // gain in [dB] + if (status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot set BladeRF gain"); + } + + bladerf_bandwidth cur_bandwidth = 0; + status = bladerf_set_bandwidth(m_device, m_channel, (bladerf_bandwidth)m_conf.bandwidth, &cur_bandwidth); + if (status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot set BladeRF bandwidth"); + } + + /* ---------------------------- Streaming Config ---------------------------- */ + const unsigned int num_buffers = 16; // Number of buffers to use in the underlying data stream + const unsigned int buffer_size = 8192; // "to hold 2048 samples for one channel, a buffer must be at least 8192 bytes large" + const unsigned int num_transfers = 8; // active USB transfers + const unsigned int timeout_ms = 3500; + + /* Configure the device's x1 TX (SISO) channel for use with the + * synchronous interface. SC16 Q11 samples *without* metadata are used. */ + status = bladerf_sync_config(m_device, BLADERF_TX_X1, BLADERF_FORMAT_SC16_Q11, num_buffers, + buffer_size, num_transfers, timeout_ms); + if (status != 0) { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot setup BladeRF stream"); + } + + status = bladerf_enable_module(m_device, m_channel, true); + if(status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + throw runtime_error("Cannot enable BladeRF channel"); + } + +} + +BladeRF::~BladeRF() +{ + if (m_device != nullptr) + { + //bladerf_deinit_stream(m_stream); /* Asynchronous API function*/ + bladerf_enable_module(m_device, m_channel, false); + bladerf_close(m_device); + } +} + +void BladeRF::tune(double lo_offset, double frequency) +{ + int status; + if (not m_device) + throw runtime_error("BladeRF device not set up"); + + if (lo_offset != 0) + { + etiLog.level(info) << "lo_offset cannot be set at "<< std::to_string(lo_offset) << " with BladeRF output, has to be 0" + << "\nlo_offset is now set to 0"; + m_conf.lo_offset = 0; + } + + status = bladerf_set_frequency(m_device, m_channel, (bladerf_frequency)m_conf.frequency); + if (status < 0) + { + etiLog.level(error) << "Error setting BladeRF TX frequency: %s " << bladerf_strerror(status); + } +} + +double BladeRF::get_tx_freq(void) const +{ + if (not m_device) + throw runtime_error("Lime device not set up"); + + int status; + bladerf_frequency cur_frequency = 0; + + status = bladerf_get_frequency(m_device, m_channel, &cur_frequency); + if (status < 0) + { + etiLog.level(error) << "Error getting BladeRF TX frequency: %s " << bladerf_strerror(status); + } + + return (double)cur_frequency; +} + +void BladeRF::set_txgain(double txgain) +{ + m_conf.txgain = txgain; + if (not m_device) + throw runtime_error("Lime device not set up"); + + int status; + status = bladerf_set_gain(m_device, m_channel, (bladerf_gain)m_conf.txgain); // gain in [dB] + if (status < 0) + { + etiLog.level(error) << "Error making BladeRF device: %s " << bladerf_strerror(status); + } +} + +double BladeRF::get_txgain(void) const +{ + if (not m_device) + throw runtime_error("BladeRF device not set up"); + + bladerf_gain txgain = 0; + int status; + + status = bladerf_get_gain(m_device, m_channel, &txgain); + + if (status < 0) + { + etiLog.level(error) << "Error getting BladeRF TX gain: %s " << bladerf_strerror(status); + } + return (double)txgain; +} + +void BladeRF::set_bandwidth(double bandwidth) +{ + bladerf_set_bandwidth(m_device, m_channel, (bladerf_bandwidth)m_conf.bandwidth, NULL); +} + +double BladeRF::get_bandwidth(void) const +{ + bladerf_bandwidth bw; + bladerf_get_bandwidth(m_device, m_channel, &bw); + return (double)bw; +} + +SDRDevice::RunStatistics BladeRF::get_run_statistics(void) const +{ + RunStatistics rs; + rs.num_underruns = underflows; + rs.num_overruns = overflows; + rs.num_late_packets = late_packets; + rs.num_frames_modulated = num_frames_modulated; + return rs; +} + +double BladeRF::get_real_secs(void) const +{ + // TODO + return 0.0; +} + +void BladeRF::set_rxgain(double rxgain) +{ + // TODO +} + +double BladeRF::get_rxgain(void) const +{ + // TODO + return 0.0; +} + +size_t BladeRF::receive_frame( + complexf *buf, + size_t num_samples, + struct frame_timestamp &ts, + double timeout_secs) +{ + // TODO + return 0; +} + +bool BladeRF::is_clk_source_ok() const +{ + // TODO + return true; +} + +const char *BladeRF::device_name(void) const +{ + return "BladeRF"; +} + +double BladeRF::get_temperature(void) const +{ + if (not m_device) + throw runtime_error("BladeRF device not set up"); + + float temp = 0.0; + + int status = bladerf_get_rfic_temperature(m_device, &temp); + if (status < 0) + { + etiLog.level(error) << "Error getting BladeRF temperature: %s " << bladerf_strerror(status); + } + + return (double)temp; +} + + +void BladeRF::transmit_frame(const struct FrameData &frame) // SC16 frames +{ + const size_t num_samples = frame.buf.size() / (2*sizeof(int16_t)); + + const int status = bladerf_sync_tx(m_device, frame.buf.data(), num_samples, NULL, 0); + if (status < 0) { + etiLog.level(error) << "Error transmitting samples with BladeRF: %s " << bladerf_strerror(status); + throw runtime_error("Cannot transmit TX samples"); + } + + num_frames_modulated++; +} +} // namespace Output + +#endif // HAVE_BLADERF diff --git a/src/output/BladeRF.h b/src/output/BladeRF.h new file mode 100755 index 0000000..bc6db38 --- /dev/null +++ b/src/output/BladeRF.h @@ -0,0 +1,112 @@ +/* + Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the + Queen in Right of Canada (Communications Research Center Canada) + + Copyright (C) 2019 + Matthias P. Braendli, matthias.braendli@mpb.li + + Copyright (C) 2021 + Steven Rossel, steven.rossel@bluewin.ch + + http://opendigitalradio.org + +DESCRIPTION: + It is an output driver for the BladeRF family of devices, and uses the libbladerf + library. +*/ + +/* + 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 . + */ + + #pragma once + + #ifdef HAVE_CONFIG_H + # include + #endif + + +#ifdef HAVE_BLADERF + #define SAMPLES_LEN 10000 // for transmit_frame() purpose, may be any (reasonable) size + + #include + #include + #include + #include + #include + #include + + #include "Log.h" + #include "output/SDR.h" + #include "TimestampDecoder.h" + #include "RemoteControl.h" + #include "ThreadsafeQueue.h" + + #include + #include + +namespace Output { + +class BladeRF : public Output::SDRDevice +{ + public: + BladeRF(SDRDeviceConfig& config); + BladeRF(const BladeRF& other) = delete; + BladeRF& operator=(const BladeRF& other) = delete; + ~BladeRF(); + + virtual void tune(double lo_offset, double frequency) override; + virtual double get_tx_freq(void) const override; + virtual void set_txgain(double txgain) override; + virtual double get_txgain(void) const override; + virtual void set_bandwidth(double bandwidth) override; + virtual double get_bandwidth(void) const override; + virtual void transmit_frame(const struct FrameData& frame) override; + virtual RunStatistics get_run_statistics(void) const override; + virtual double get_real_secs(void) const override; + + virtual void set_rxgain(double rxgain) override; + virtual double get_rxgain(void) const 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) const override; + virtual const char* device_name(void) const override; + + virtual double get_temperature(void) const override; + + + private: + SDRDeviceConfig& m_conf; + struct bladerf *m_device; + bladerf_channel m_channel = BLADERF_CHANNEL_TX(0); // channel TX0 + //struct bladerf_stream* m_stream; /* used for asynchronous api */ + + size_t underflows = 0; + size_t overflows = 0; + size_t late_packets = 0; + size_t num_frames_modulated = 0; + //size_t num_underflows_previous = 0; + //size_t num_late_packets_previous = 0; +}; + +} // namespace Output + +#endif // HAVE_BLADERF \ No newline at end of file diff --git a/src/output/SDRDevice.h b/src/output/SDRDevice.h index d84ebf9..bb63f60 100644 --- a/src/output/SDRDevice.h +++ b/src/output/SDRDevice.h @@ -72,7 +72,8 @@ struct SDRDeviceConfig { unsigned dabMode = 0; unsigned maxGPSHoldoverTime = 0; - /* allowed values for UHD : auto, int, sma, mimo */ + /* allowed values for UHD : auto, int, sma, mimo */ + /* allowed values for BladeRF : pps, 10mhz */ std::string refclk_src; /* allowed values for UHD : int, sma, mimo */ -- cgit v1.2.3