From f7af0a4ac283aa2d6e92ff8f86d225a0cf38a56a Mon Sep 17 00:00:00 2001 From: f4exb <f4exb06@gmail.com> Date: Sun, 23 Sep 2018 19:56:24 +0200 Subject: [PATCH] BladerRF2 input support (2) --- Readme.md | 26 +- devices/bladerf2/CMakeLists.txt | 2 + devices/bladerf2/devicebladerf2.cpp | 198 ++++- devices/bladerf2/devicebladerf2.h | 18 +- devices/bladerf2/devicebladerf2shared.cpp | 28 + devices/bladerf2/devicebladerf2shared.h | 62 ++ ...ut_plugin.png => BladeRF1Input_plugin.png} | Bin ...ut_plugin.xcf => BladeRF1Input_plugin.xcf} | Bin ...t_plugin.png => BladeRF1Output_plugin.png} | Bin ...t_plugin.xcf => BladeRF1Output_plugin.xcf} | Bin ...g => BladeRF1Output_plugin_fifodly_32.png} | Bin ...> BladeRF1Output_plugin_fifodly_other.png} | Bin plugins/samplesink/bladerf1output/readme.md | 16 +- plugins/samplesource/CMakeLists.txt | 2 + plugins/samplesource/bladerf1input/readme.md | 10 +- .../samplesource/bladerf2input/CMakeLists.txt | 79 ++ .../bladerf2input/bladerf2input.cpp | 220 ++++++ .../bladerf2input/bladerf2input.h | 154 ++++ .../bladerf2input/bladerf2inputgui.ui | 727 ++++++++++++++++++ .../bladerf2input/bladerf2inputplugin.cpp | 25 +- .../bladerf2input/bladerf2inputthread.cpp | 264 +++++++ .../bladerf2input/bladerf2inputthread.h | 90 +++ .../limesdrinput/limesdrinput.cpp | 14 + 23 files changed, 1904 insertions(+), 31 deletions(-) create mode 100644 devices/bladerf2/devicebladerf2shared.cpp create mode 100644 devices/bladerf2/devicebladerf2shared.h rename doc/img/{BladeRFInput_plugin.png => BladeRF1Input_plugin.png} (100%) rename doc/img/{BladeRFInput_plugin.xcf => BladeRF1Input_plugin.xcf} (100%) rename doc/img/{BladeRFOutput_plugin.png => BladeRF1Output_plugin.png} (100%) rename doc/img/{BladeRFOutput_plugin.xcf => BladeRF1Output_plugin.xcf} (100%) rename doc/img/{BladeRFOutput_plugin_fifodly_32.png => BladeRF1Output_plugin_fifodly_32.png} (100%) rename doc/img/{BladeRFOutput_plugin_fifodly_other.png => BladeRF1Output_plugin_fifodly_other.png} (100%) create mode 100644 plugins/samplesource/bladerf2input/CMakeLists.txt create mode 100644 plugins/samplesource/bladerf2input/bladerf2input.cpp create mode 100644 plugins/samplesource/bladerf2input/bladerf2input.h create mode 100644 plugins/samplesource/bladerf2input/bladerf2inputgui.ui create mode 100644 plugins/samplesource/bladerf2input/bladerf2inputthread.cpp create mode 100644 plugins/samplesource/bladerf2input/bladerf2inputthread.h diff --git a/Readme.md b/Readme.md index fe8969996..4a08d8ce5 100644 --- a/Readme.md +++ b/Readme.md @@ -36,7 +36,7 @@ Since version 2 SDRangel can integrate more than one hardware device running con Since version 3 transmission or signal generation is supported for BladeRF, HackRF (since version 3.1), LimeSDR (since version 3.4) and PlutoSDR (since version 3.7.8) using a sample sink plugin. These plugins are: - - [BladeRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf1output) + - [BladeRF1 output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf1output) - [HackRF output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/hackrfoutput) - [LimeSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/limesdroutput) - [PlutoSDR output plugin](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/plutosdroutput) @@ -96,15 +96,31 @@ It is recommended to add `-DRX_SAMPLE_24BIT=ON` on the cmake command line to act ☞ From version 3.12.0 the Linux binaries are built with the 24 bit Rx option. -<h2>BladeRF</h2> +<h2>BladeRF classic (v.1)</h2> -[BladeRF](https://www.nuand.com/) is supported through the libbladerf library that should be installed in your system for proper build of the software and operation support. Add `libbladerf-dev` to the list of dependencies to install. +Linux only. -If you use your own location for libbladeRF install directory you need to specify library and include locations. Example with `/opt/install/libbladerf` with the following defines on `cmake` command line: +[BladeRF1](https://www.nuand.com/bladerf-1) is supported through the libbladeRF library that should be installed in your system for proper build of the software and operation support. Add `libbladerf-dev` to the list of dependencies to install. Note that libbladeRF v2 is used since version 4.2.0 (git tag 2018.08). + +If you compile and use your own location for libbladeRF install directory you need to specify library and include locations. Example with `/opt/install/libbladerf` with the following defines on `cmake` command line: `-DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so -DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include` -☞ Please note that if you use your own library the FPGA image `hostedx40.rbf` or `hostedx115.rbf` shoud be placed in e.g. `/opt/install/libbladeRF/share/Nuand/bladeRF` +☞ Please note that if you use your own library the FPGA image `hostedx40.rbf` or `hostedx115.rbf` shoud be placed in e.g. `/opt/install/libbladeRF/share/Nuand/bladeRF` unless you have flashed the FPGA image inside the BladeRF. + +The plugins used to support BladeRF classic are specific to this version of the BladeRF: + - [BladeRF1 input](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/bladerf1input) + - [BladeRF1 output](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf1output) + +<h2>BladeRF micro (v.2)</h2> + +Linux only. From version 4.2.0 + +[BladeRF 2 micro](https://www.nuand.com/bladerf-2-0-micro/) is also supported using libbladeRF library that should be installed and configured in the same way as for BladeRF1. + +The plugins used to support BladeRF 2 micro are specific to this version of the BladeRF: + - [BladeRF2 input](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesource/bladerf2input) + - [BladeRF2 output](https://github.com/f4exb/sdrangel/tree/dev/plugins/samplesink/bladerf2output) <h2>FunCube Dongle</h2> diff --git a/devices/bladerf2/CMakeLists.txt b/devices/bladerf2/CMakeLists.txt index f92ad7686..2b0c96490 100644 --- a/devices/bladerf2/CMakeLists.txt +++ b/devices/bladerf2/CMakeLists.txt @@ -2,10 +2,12 @@ project(bladerf2device) set(bladerf2device_SOURCES devicebladerf2.cpp + devicebladerf2shared.cpp ) set(bladerf2device_HEADERS devicebladerf2.h + devicebladerf2shared.h ) if (BUILD_DEBIAN) diff --git a/devices/bladerf2/devicebladerf2.cpp b/devices/bladerf2/devicebladerf2.cpp index a82e9f493..7b7e4a772 100644 --- a/devices/bladerf2/devicebladerf2.cpp +++ b/devices/bladerf2/devicebladerf2.cpp @@ -22,7 +22,11 @@ #include "devicebladerf2.h" DeviceBladeRF2::DeviceBladeRF2() : - m_dev(0) + m_dev(0), + m_nbRxChannels(0), + m_nbTxChannels(0), + m_rxOpen(0), + m_txOpen(0) {} DeviceBladeRF2::~DeviceBladeRF2() @@ -32,6 +36,14 @@ DeviceBladeRF2::~DeviceBladeRF2() bladerf_close(m_dev); m_dev = 0; } + + if (m_rxOpen) { + delete[] m_rxOpen; + } + + if (m_txOpen) { + delete[] m_txOpen; + } } bool DeviceBladeRF2::open(const char *serial) @@ -58,6 +70,12 @@ bool DeviceBladeRF2::open(const char *serial) return false; } + m_nbRxChannels = bladerf_get_channel_count(m_dev, BLADERF_RX); + m_nbTxChannels = bladerf_get_channel_count(m_dev, BLADERF_TX); + + m_rxOpen = new bool[m_nbRxChannels]; + m_txOpen = new bool[m_nbTxChannels]; + return true; } @@ -100,7 +118,157 @@ struct bladerf *DeviceBladeRF2::open_bladerf_from_serial(const char *serial) } } -void DeviceBladeRF2::getFrequencyRangeRx(int& min, int& max, int& step) +bool DeviceBladeRF2::openRx(int channel) +{ + if (!m_dev) { + return false; + } + + if ((channel < 0) || (channel >= m_nbRxChannels)) + { + qCritical("DeviceBladeRF2::openRx: invalid Rx channel index %d", channel); + return false; + } + + int status; + + if (!m_rxOpen[channel]) + { + status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_RX(channel), true); + + if (status < 0) + { + qCritical("DeviceBladeRF2::openRx: Failed to enable Rx channel %d: %s", + channel, bladerf_strerror(status)); + return false; + } + else + { + qDebug("DeviceBladeRF2::openRx: Rx channel %d enabled", channel); + m_rxOpen[channel] = true; + return true; + } + } + else + { + qCritical("DeviceBladeRF2::openRx: Rx channel %d already opened", channel); + return false; + } +} + +bool DeviceBladeRF2::openTx(int channel) +{ + if (!m_dev) { + return false; + } + + if ((channel < 0) || (channel >= m_nbTxChannels)) + { + qCritical("DeviceBladeRF2::openTx: invalid Tx channel index %d", channel); + return false; + } + + int status; + + if (!m_txOpen[channel]) + { + status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_TX(0), true); + + if (status < 0) + { + qCritical("DeviceBladeRF2::openTx: Failed to enable Tx channel %d: %s", + channel, bladerf_strerror(status)); + return false; + } + else + { + qDebug("DeviceBladeRF2::openTx: Tx channel %d enabled", channel); + m_txOpen[channel] = true; + return true; + } + } + else + { + qCritical("DeviceBladeRF2::openTx: Tx channel %d already opened", channel); + return false; + } +} + +void DeviceBladeRF2::closeRx(int channel) +{ + if (!m_dev) { + return; + } + + if ((channel < 0) || (channel >= m_nbRxChannels)) + { + qCritical("DeviceBladeRF2::closeRx: invalid Rx channel index %d", channel); + return; + } + + if (m_rxOpen[channel]) + { + for (int i = 0; i < m_nbRxChannels; i++) + { + if ((i != channel) && (m_rxOpen[i])) + { + qDebug("DeviceBladeRF2::closeRx: not closing channel %d as %d is still open", channel, i); + } + } + + int status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_RX(channel), false); + m_rxOpen[channel] = false; + + if (status < 0) { + qCritical("DeviceBladeRF2::closeRx: cannot close channel %d: %s", channel, bladerf_strerror(status)); + } else { + qDebug("DeviceBladeRF2::closeRx: channel %d closed", channel); + } + } + else + { + qCritical("DeviceBladeRF2::closeRx: Rx channel %d already closed", channel); + } +} + +void DeviceBladeRF2::closeTx(int channel) +{ + if (!m_dev) { + return; + } + + if ((channel < 0) || (channel >= m_nbTxChannels)) + { + qCritical("DeviceBladeRF2::closeTx: invalid Tx channel index %d", channel); + return; + } + + if (m_txOpen[channel]) + { + for (int i = 0; i < m_nbTxChannels; i++) + { + if ((i != channel) && (m_txOpen[i])) + { + qDebug("DeviceBladeRF2::closeTx: not closing channel %d as %d is still open", channel, i); + } + } + + int status = bladerf_enable_module(m_dev, BLADERF_CHANNEL_TX(channel), false); + m_txOpen[channel] = false; + + if (status < 0) { + qCritical("DeviceBladeRF2::closeTx: cannot close channel %d: %s", channel, bladerf_strerror(status)); + } else { + qDebug("DeviceBladeRF2::closeTx: channel %d closed", channel); + } + } + else + { + qCritical("DeviceBladeRF2::closeTx: Rx channel %d already closed", channel); + } +} + +void DeviceBladeRF2::getFrequencyRangeRx(uint64_t& min, uint64_t& max, int& step) { if (m_dev) { @@ -123,7 +291,7 @@ void DeviceBladeRF2::getFrequencyRangeRx(int& min, int& max, int& step) } } -void DeviceBladeRF2::getFrequencyRangeTx(int& min, int& max, int& step) +void DeviceBladeRF2::getFrequencyRangeTx(uint64_t& min, uint64_t& max, int& step) { if (m_dev) { @@ -284,3 +452,27 @@ void DeviceBladeRF2::getGlobalGainRangeTx(int& min, int& max, int& step) } } } + +void DeviceBladeRF2::setBiasTeeRx(bool enable) +{ + if (m_dev) + { + int status = bladerf_set_bias_tee(m_dev, BLADERF_CHANNEL_RX(0), enable); + + if (status < 0) { + qCritical("DeviceBladeRF2::setBiasTeeRx: Failed to set Rx bias tee: %s", bladerf_strerror(status)); + } + } +} + +void DeviceBladeRF2::setBiasTeeTx(bool enable) +{ + if (m_dev) + { + int status = bladerf_set_bias_tee(m_dev, BLADERF_CHANNEL_TX(0), enable); + + if (status < 0) { + qCritical("DeviceBladeRF2::setBiasTeeTx: Failed to set Tx bias tee: %s", bladerf_strerror(status)); + } + } +} diff --git a/devices/bladerf2/devicebladerf2.h b/devices/bladerf2/devicebladerf2.h index 76e8c4815..30b2cf6a8 100644 --- a/devices/bladerf2/devicebladerf2.h +++ b/devices/bladerf2/devicebladerf2.h @@ -31,17 +31,31 @@ public: bool open(const char *serial); void close(); - void getFrequencyRangeRx(int& min, int& max, int& step); - void getFrequencyRangeTx(int& min, int& max, int& step); + bool openRx(int channel); + bool openTx(int channel); + void closeRx(int channel); + void closeTx(int channel); + + void getFrequencyRangeRx(uint64_t& min, uint64_t& max, int& step); + void getFrequencyRangeTx(uint64_t& min, uint64_t& max, int& step); void getSampleRateRangeRx(int& min, int& max, int& step); void getSampleRateRangeTx(int& min, int& max, int& step); void getBandwidthRangeRx(int& min, int& max, int& step); void getBandwidthRangeTx(int& min, int& max, int& step); void getGlobalGainRangeRx(int& min, int& max, int& step); void getGlobalGainRangeTx(int& min, int& max, int& step); + void setBiasTeeRx(bool enable); + void setBiasTeeTx(bool enable); + + static const unsigned int blockSize = (1<<14); private: bladerf *m_dev; + int m_nbRxChannels; + int m_nbTxChannels; + bool *m_rxOpen; + bool *m_txOpen; + static struct bladerf *open_bladerf_from_serial(const char *serial); }; diff --git a/devices/bladerf2/devicebladerf2shared.cpp b/devices/bladerf2/devicebladerf2shared.cpp new file mode 100644 index 000000000..4bb577367 --- /dev/null +++ b/devices/bladerf2/devicebladerf2shared.cpp @@ -0,0 +1,28 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program 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 as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#include "devicebladerf2shared.h" + +DeviceBladeRF2Shared::DeviceBladeRF2Shared() : + m_dev(0), + m_channel(-1) +{} + +DeviceBladeRF2Shared::~DeviceBladeRF2Shared() +{} + + + diff --git a/devices/bladerf2/devicebladerf2shared.h b/devices/bladerf2/devicebladerf2shared.h new file mode 100644 index 000000000..0f6378191 --- /dev/null +++ b/devices/bladerf2/devicebladerf2shared.h @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program 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 as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ +#define DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ + +#include "devicebladerf2.h" + +class SampleSinkFifo; +class SampleSourceFifo; + +/** + * Structure shared by a buddy with other buddies + */ +class DEVICES_API DeviceBladeRF2Shared +{ +public: + class InputThreadInterface + { + public: + virtual void startWork() = 0; + virtual void stopWork() = 0; + virtual bool isRunning() const = 0; + virtual void setFifo(unsigned int channel, SampleSinkFifo *fifo) = 0; + virtual SampleSinkFifo *getFifo(unsigned int channel) = 0; + }; + + class OutputThreadInterface + { + public: + virtual void startWork() = 0; + virtual void stopWork() = 0; + virtual bool isRunning() = 0; + virtual void setFifo(unsigned int channel, SampleSourceFifo *fifo) = 0; + virtual SampleSourceFifo *getFifo(unsigned int channel) = 0; + }; + + DeviceBladeRF2Shared(); + ~DeviceBladeRF2Shared(); + + DeviceBladeRF2 *m_dev; + int m_channel; //!< allocated channel (-1 if none) + InputThreadInterface *m_inputThread; //!< The SISO/MIMO input thread + OutputThreadInterface *m_outputThread; //!< The SISO/MIMO output thread +}; + + + +#endif /* DEVICES_BLADERF2_DEVICEBLADERF2SHARED_H_ */ diff --git a/doc/img/BladeRFInput_plugin.png b/doc/img/BladeRF1Input_plugin.png similarity index 100% rename from doc/img/BladeRFInput_plugin.png rename to doc/img/BladeRF1Input_plugin.png diff --git a/doc/img/BladeRFInput_plugin.xcf b/doc/img/BladeRF1Input_plugin.xcf similarity index 100% rename from doc/img/BladeRFInput_plugin.xcf rename to doc/img/BladeRF1Input_plugin.xcf diff --git a/doc/img/BladeRFOutput_plugin.png b/doc/img/BladeRF1Output_plugin.png similarity index 100% rename from doc/img/BladeRFOutput_plugin.png rename to doc/img/BladeRF1Output_plugin.png diff --git a/doc/img/BladeRFOutput_plugin.xcf b/doc/img/BladeRF1Output_plugin.xcf similarity index 100% rename from doc/img/BladeRFOutput_plugin.xcf rename to doc/img/BladeRF1Output_plugin.xcf diff --git a/doc/img/BladeRFOutput_plugin_fifodly_32.png b/doc/img/BladeRF1Output_plugin_fifodly_32.png similarity index 100% rename from doc/img/BladeRFOutput_plugin_fifodly_32.png rename to doc/img/BladeRF1Output_plugin_fifodly_32.png diff --git a/doc/img/BladeRFOutput_plugin_fifodly_other.png b/doc/img/BladeRF1Output_plugin_fifodly_other.png similarity index 100% rename from doc/img/BladeRFOutput_plugin_fifodly_other.png rename to doc/img/BladeRF1Output_plugin_fifodly_other.png diff --git a/plugins/samplesink/bladerf1output/readme.md b/plugins/samplesink/bladerf1output/readme.md index 5b724dec5..fde65d313 100644 --- a/plugins/samplesink/bladerf1output/readme.md +++ b/plugins/samplesink/bladerf1output/readme.md @@ -1,20 +1,22 @@ -<h1>BladeRF output plugin</h1> +<h1>BladeRF classic (v1) output plugin</h1> <h2>Introduction</h2> -This output sample sink plugin sends its samples to a [BladeRF device](https://www.nuand.com/). +This output sample sink plugin sends its samples to a [BladeRF1 device](https://www.nuand.com/bladerf-1). -Warning to Windows users: concurrent use of Rx and Tx does not work correctly hence full duplex is not fully operational. For best results use BladeRF as a half duplex device like HackRF i.e. do not run Tx and Rx concurrently. +Warning to Windows users: concurrent use of Rx and Tx does not work correctly hence full duplex is not fully operational. For best results use BladeRF as a half duplex device like HackRF i.e. do not run Tx and Rx concurrently. Anyway from version 4.2.0 using LibbladeRF v.2 this is available in Linux distributions only. <h2>Build</h2> The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. -The BladeRF Host library is also provided by many Linux distributions and is built in the SDRangel binary releases. +Note that since version 4.2.0 the libbladeRF v2 (specifically the git tag 2018.08) is used. + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases. <h2>Interface</h2> - + <h3>1: Start/Stop</h3> @@ -32,11 +34,11 @@ Transmission latency depends essentially in the delay in the sample FIFO. The FI For interpolation by 32 the size is fixed at 150000 samples, Delay is 150000 / B where B is the baseband sample rate. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 500 kS/s: - + For lower interpolation rates the size is calculated to give a fixed delay of 250 ms or 75000 samples whichever is bigger. Below is the delay in seconds vs baseband sample rate in kS/s from 48 to 400 kS/s. The 250 ms delay is reached at 300 kS/s: - + <h3>3: Frequency</h3> diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index d4698339d..c3c54d52a 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -26,6 +26,7 @@ endif(LIBUSB_FOUND AND LIBAIRSPYHF_FOUND) find_package(LibBLADERF) if(LIBUSB_FOUND AND LIBBLADERF_FOUND) add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) if(LIBUSB_FOUND AND UNIX) @@ -80,6 +81,7 @@ if (BUILD_DEBIAN) add_subdirectory(airspy) add_subdirectory(airspyhf) add_subdirectory(bladerf1input) + add_subdirectory(bladerf2input) add_subdirectory(hackrfinput) add_subdirectory(limesdrinput) add_subdirectory(perseus) diff --git a/plugins/samplesource/bladerf1input/readme.md b/plugins/samplesource/bladerf1input/readme.md index 2f0e8f01e..cdfe764a2 100644 --- a/plugins/samplesource/bladerf1input/readme.md +++ b/plugins/samplesource/bladerf1input/readme.md @@ -1,18 +1,20 @@ -<h1>BladeRF input plugin</h1> +<h1>BladeRF classic (v1) input plugin</h1> <h2>Introduction</h2> -This input sample source plugin gets its samples from a [BladeRF device](https://www.nuand.com/). +This input sample source plugin gets its samples from a [BladeRF1 device](https://www.nuand.com/bladerf-1). From version 4.2.0 using LibbladeRF v.2 this is available in Linux distributions only. <h2>Build</h2> The plugin will be built only if the [BladeRF host library](https://github.com/Nuand/bladeRF) is installed in your system. If you build it from source and install it in a custom location say: `/opt/install/libbladeRF` you will have to add `-DLIBBLADERF_INCLUDE_DIR=/opt/install/libbladeRF/include -DLIBBLADERF_LIBRARIES=/opt/install/libbladeRF/lib/libbladeRF.so` to the cmake command line. -The BladeRF Host library is also provided by many Linux distributions and is built in the SDRangel binary releases. +Note that since version 4.2.0 the libbladeRF v2 (specifically the git tag 2018.08) is used. + +The BladeRF Host library is also provided by many Linux distributions (check its version) and is built in the SDRangel binary releases. <h2>Interface</h2> - + <h3>1: Common stream parameters</h3> diff --git a/plugins/samplesource/bladerf2input/CMakeLists.txt b/plugins/samplesource/bladerf2input/CMakeLists.txt new file mode 100644 index 000000000..6594e1cff --- /dev/null +++ b/plugins/samplesource/bladerf2input/CMakeLists.txt @@ -0,0 +1,79 @@ +project(bladerf2input) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +set(bladerf2input_SOURCES + #bladerf2inputgui.cpp + bladerf2input.cpp + #bladerf2inputplugin.cpp + bladerf2inputsettings.cpp + bladerf2inputthread.cpp +) + +set(bladerf2input_HEADERS + #bladerf2inputgui.h + bladerf2input.h + #bladerf2inputplugin.h + bladerf2inputsettings.h + bladerf2inputthread.h +) + +set(bladerf2input_FORMS + bladerf2inputgui.ui +) + +if (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERFLIBSRC}/include + ${LIBBLADERFLIBSRC}/src +) +else (BUILD_DEBIAN) +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client + ${CMAKE_SOURCE_DIR}/devices + ${LIBBLADERF_INCLUDE_DIR} +) +endif (BUILD_DEBIAN) + +#include(${QT_USE_FILE}) +add_definitions(${QT_DEFINITIONS}) +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +qt5_wrap_ui(bladerf2input_FORMS_HEADERS ${bladerf2input_FORMS}) + +add_library(inputbladerf2 SHARED + ${bladerf2input_SOURCES} + ${bladerf2input_HEADERS_MOC} + ${bladerf2input_FORMS_HEADERS} +) + +if (BUILD_DEBIAN) +target_link_libraries(inputbladerf2 + ${QT_LIBRARIES} + bladerf + sdrbase + sdrgui + swagger + bladerf2device +) +else (BUILD_DEBIAN) +target_link_libraries(inputbladerf2 + ${QT_LIBRARIES} + ${LIBBLADERF_LIBRARIES} + sdrbase + sdrgui + swagger + bladerf2device +) +endif (BUILD_DEBIAN) + +target_link_libraries(inputbladerf2 Qt5::Core Qt5::Widgets) + +install(TARGETS inputbladerf2 DESTINATION lib/plugins/samplesource) diff --git a/plugins/samplesource/bladerf2input/bladerf2input.cpp b/plugins/samplesource/bladerf2input/bladerf2input.cpp new file mode 100644 index 000000000..ebbee96a3 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2input.cpp @@ -0,0 +1,220 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program 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 as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#include <QDebug> + +#include "SWGDeviceSettings.h" +#include "SWGBladeRF2InputSettings.h" +#include "SWGDeviceState.h" +#include "SWGDeviceReport.h" +#include "SWGBladeRF2InputReport.h" + +#include "device/devicesourceapi.h" +#include "device/devicesinkapi.h" +#include "dsp/dspcommands.h" +#include "dsp/filerecord.h" +#include "dsp/dspengine.h" + +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2/devicebladerf2.h" +#include "bladerf2input.h" + + +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgConfigureBladeRF2, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgFileRecord, Message) +MESSAGE_CLASS_DEFINITION(BladeRF2Input::MsgStartStop, Message) + +BladeRF2Input::BladeRF2Input(DeviceSourceAPI *deviceAPI) : + m_deviceAPI(deviceAPI), + m_settings(), + m_deviceDescription("BladeRF2Input"), + m_running(false) +{ + openDevice(); + + m_fileSink = new FileRecord(QString("test_%1.sdriq").arg(m_deviceAPI->getDeviceUID())); + m_deviceAPI->addSink(m_fileSink); +} + +BladeRF2Input::~BladeRF2Input() +{ + if (m_running) { + stop(); + } + + m_deviceAPI->removeSink(m_fileSink); + delete m_fileSink; + closeDevice(); +} + +void BladeRF2Input::destroy() +{ + delete this; +} + +bool BladeRF2Input::openDevice() +{ + if (!m_sampleFifo.setSize(96000 * 4)) + { + qCritical("BladeRF2Input::openDevice: could not allocate SampleFifo"); + return false; + } + else + { + qDebug("BladeRF2Input::openDevice: allocated SampleFifo"); + } + + // look for Rx buddies and get reference to the device object + // if there is a channel left take the first available + if (m_deviceAPI->getSourceBuddies().size() > 0) // look source sibling first + { + qDebug("BladeRF2Input::openDevice: look in Rx buddies"); + + DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Input::openDevice: the source buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Input::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + int requestedChannel = m_deviceAPI->getItemIndex(); + + if (requestedChannel == deviceBladeRF2Shared->m_channel) + { + qCritical("BladeRF2Input::openDevice: channel %u already in use", requestedChannel); + return false; + } + + if (!device->openRx(requestedChannel)) + { + qCritical("BladeRF2Input::openDevice: channel %u cannot be enabled", requestedChannel); + return false; + } + else + { + m_deviceShared.m_channel = requestedChannel; + qDebug("BladeRF2Input::openDevice: channel %u enabled", requestedChannel); + } + } + // look for Tx buddies and get reference to the device object + // allocate the Rx channel unconditionally + else if (m_deviceAPI->getSinkBuddies().size() > 0) // then sink + { + qDebug("BladeRF2Input::openDevice: look in Tx buddies"); + + DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; + DeviceBladeRF2Shared *deviceBladeRF2Shared = (DeviceBladeRF2Shared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceBladeRF2Shared == 0) + { + qCritical("BladeRF2Input::openDevice: the sink buddy shared pointer is null"); + return false; + } + + DeviceBladeRF2 *device = deviceBladeRF2Shared->m_dev; + + if (device == 0) + { + qCritical("BladeRF2Input::openDevice: cannot get device pointer from Rx buddy"); + return false; + } + + m_deviceShared.m_dev = device; + int requestedChannel = m_deviceAPI->getItemIndex(); + + if (!device->openRx(requestedChannel)) + { + qCritical("BladeRF2Input::openDevice: channel %u cannot be enabled", requestedChannel); + return false; + } + else + { + m_deviceShared.m_channel = requestedChannel; + qDebug("BladeRF2Input::openDevice: channel %u enabled", requestedChannel); + } + } + // There are no buddies then create the first BladeRF2 device + // allocate the Rx channel unconditionally + else + { + qDebug("BladeRF2Input::openDevice: open device here"); + + m_deviceShared.m_dev = new DeviceBladeRF2(); + char serial[256]; + strcpy(serial, qPrintable(m_deviceAPI->getSampleSourceSerial())); + + if (!m_deviceShared.m_dev->open(serial)) + { + qCritical("BladeRF2Input::openDevice: cannot open BladeRF2 device"); + return false; + } + + int requestedChannel = m_deviceAPI->getItemIndex(); + + if (!m_deviceShared.m_dev->openRx(requestedChannel)) + { + qCritical("BladeRF2Input::openDevice: channel %u cannot be enabled", requestedChannel); + return false; + } + else + { + m_deviceShared.m_channel = requestedChannel; + qDebug("BladeRF2Input::openDevice: channel %u enabled", requestedChannel); + } + } + + m_deviceAPI->setBuddySharedPtr(&m_deviceShared); // propagate common parameters to API + return true; +} + +void BladeRF2Input::closeDevice() +{ + if (m_deviceShared.m_dev == 0) { // was never open + return; + } + + if (m_running) { + stop(); + } + + m_deviceShared.m_channel = -1; + + // No buddies so effectively close the device + + if ((m_deviceAPI->getSinkBuddies().size() == 0) && (m_deviceAPI->getSourceBuddies().size() == 0)) + { + m_deviceShared.m_dev->close(); + delete m_deviceShared.m_dev; + m_deviceShared.m_dev = 0; + } +} + +void BladeRF2Input::init() +{ + applySettings(m_settings, true); +} + diff --git a/plugins/samplesource/bladerf2input/bladerf2input.h b/plugins/samplesource/bladerf2input/bladerf2input.h new file mode 100644 index 000000000..5798caf25 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2input.h @@ -0,0 +1,154 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program 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 as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUT_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUT_H_ + +#include <QString> +#include <QByteArray> +#include <stdint.h> + +#include "dsp/devicesamplesource.h" +#include "bladerf2/devicebladerf2shared.h" +#include "bladerf2inputsettings.h" + +class DeviceSourceAPI; +class LimeSDRInputThread; +class FileRecord; + +class BladeRF2Input : public DeviceSampleSource +{ +public: + class MsgConfigureBladeRF2 : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const BladeRF2InputSettings& getSettings() const { return m_settings; } + bool getForce() const { return m_force; } + + static MsgConfigureBladeRF2* create(const BladeRF2InputSettings& settings, bool force) + { + return new MsgConfigureBladeRF2(settings, force); + } + + private: + BladeRF2InputSettings m_settings; + bool m_force; + + MsgConfigureBladeRF2(const BladeRF2InputSettings& settings, bool force) : + Message(), + m_settings(settings), + m_force(force) + { } + }; + + class MsgFileRecord : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgFileRecord* create(bool startStop) { + return new MsgFileRecord(startStop); + } + + protected: + bool m_startStop; + + MsgFileRecord(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + class MsgStartStop : public Message { + MESSAGE_CLASS_DECLARATION + + public: + bool getStartStop() const { return m_startStop; } + + static MsgStartStop* create(bool startStop) { + return new MsgStartStop(startStop); + } + + protected: + bool m_startStop; + + MsgStartStop(bool startStop) : + Message(), + m_startStop(startStop) + { } + }; + + BladeRF2Input(DeviceSourceAPI *deviceAPI); + virtual ~BladeRF2Input(); + virtual void destroy(); + + virtual void init(); + virtual bool start(); + virtual void stop(); + + virtual QByteArray serialize() const; + virtual bool deserialize(const QByteArray& data); + + virtual void setMessageQueueToGUI(MessageQueue *queue) { m_guiMessageQueue = queue; } + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + virtual void setCenterFrequency(qint64 centerFrequency); + + virtual bool handleMessage(const Message& message); + + virtual int webapiSettingsGet( + SWGSDRangel::SWGDeviceSettings& response, + QString& errorMessage); + + virtual int webapiSettingsPutPatch( + bool force, + const QStringList& deviceSettingsKeys, + SWGSDRangel::SWGDeviceSettings& response, // query + response + QString& errorMessage); + + virtual int webapiReportGet( + SWGSDRangel::SWGDeviceReport& response, + QString& errorMessage); + + virtual int webapiRunGet( + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + + virtual int webapiRun( + bool run, + SWGSDRangel::SWGDeviceState& response, + QString& errorMessage); + +private: + DeviceSourceAPI *m_deviceAPI; + QMutex m_mutex; + BladeRF2InputSettings m_settings; + QString m_deviceDescription; + bool m_running; + DeviceBladeRF2Shared m_deviceShared; + FileRecord *m_fileSink; //!< File sink to record device I/Q output + + bool openDevice(); + void closeDevice(); + bool applySettings(const BladeRF2InputSettings& settings, bool force = false); + void webapiFormatDeviceSettings(SWGSDRangel::SWGDeviceSettings& response, const BladeRF2InputSettings& settings); + void webapiFormatDeviceReport(SWGSDRangel::SWGDeviceReport& response); +}; + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUT_H_ */ diff --git a/plugins/samplesource/bladerf2input/bladerf2inputgui.ui b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui new file mode 100644 index 000000000..527110d64 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputgui.ui @@ -0,0 +1,727 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Bladerf2InputGui</class> + <widget class="QWidget" name="Bladerf2InputGui"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>310</width> + <height>265</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>310</width> + <height>250</height> + </size> + </property> + <property name="font"> + <font> + <family>Liberation Sans</family> + <pointsize>9</pointsize> + </font> + </property> + <property name="windowTitle"> + <string>BladeRF2</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <property name="leftMargin"> + <number>2</number> + </property> + <property name="topMargin"> + <number>2</number> + </property> + <property name="rightMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_freq"> + <property name="topMargin"> + <number>4</number> + </property> + <item> + <layout class="QVBoxLayout" name="deviceUILayout"> + <item> + <layout class="QHBoxLayout" name="deviceButtonsLayout"> + <item> + <widget class="ButtonSwitch" name="startStop"> + <property name="toolTip"> + <string>start/stop acquisition</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../../../sdrgui/resources/res.qrc"> + <normaloff>:/play.png</normaloff> + <normalon>:/stop.png</normalon>:/play.png</iconset> + </property> + </widget> + </item> + <item> + <widget class="ButtonSwitch" name="record"> + <property name="toolTip"> + <string>Toggle record I/Q samples from device</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../../../sdrgui/resources/res.qrc"> + <normaloff>:/record_off.png</normaloff> + <normalon>:/record_on.png</normalon>:/record_off.png</iconset> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="deviceRateLayout"> + <item> + <widget class="QLabel" name="deviceRateLabel"> + <property name="toolTip"> + <string>I/Q sample rate kS/s</string> + </property> + <property name="text"> + <string>00000k</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <spacer name="freqLeftSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="ValueDial" name="centerFrequency" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>16</height> + </size> + </property> + <property name="font"> + <font> + <family>Liberation Mono</family> + <pointsize>20</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>PointingHandCursor</cursorShape> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Tuner center frequency in kHz</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="freqUnits"> + <property name="text"> + <string> kHz</string> + </property> + </widget> + </item> + <item> + <spacer name="freqRightlSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_corr"> + <item row="0" column="2"> + <widget class="ButtonSwitch" name="iqImbalance"> + <property name="toolTip"> + <string>Automatic IQ imbalance correction</string> + </property> + <property name="text"> + <string>IQ</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="ButtonSwitch" name="dcOffset"> + <property name="toolTip"> + <string>Automatic DC offset removal</string> + </property> + <property name="text"> + <string>DC</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="corrLabel"> + <property name="text"> + <string>Auto</string> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QLabel" name="xb200Label"> + <property name="text"> + <string>xb200</string> + </property> + </widget> + </item> + <item row="0" column="5"> + <widget class="QComboBox" name="xb200"> + <property name="toolTip"> + <string>XB200 board mode</string> + </property> + <property name="currentText"> + <string>None</string> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <property name="maxVisibleItems"> + <number>5</number> + </property> + <item> + <property name="text"> + <string>None</string> + </property> + </item> + <item> + <property name="text"> + <string>Bypass</string> + </property> + </item> + <item> + <property name="text"> + <string>Auto 1dB</string> + </property> + </item> + <item> + <property name="text"> + <string>Auto 3dB</string> + </property> + </item> + <item> + <property name="text"> + <string>Custom</string> + </property> + </item> + <item> + <property name="text"> + <string>50M</string> + </property> + </item> + <item> + <property name="text"> + <string>144M</string> + </property> + </item> + <item> + <property name="text"> + <string>222M</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_freq"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="sampleRateLayout"> + <property name="topMargin"> + <number>2</number> + </property> + <property name="bottomMargin"> + <number>2</number> + </property> + <item> + <widget class="QLabel" name="samplerateLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>SR</string> + </property> + </widget> + </item> + <item> + <widget class="ValueDial" name="sampleRate" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>32</width> + <height>16</height> + </size> + </property> + <property name="font"> + <font> + <family>Liberation Mono</family> + <pointsize>12</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>PointingHandCursor</cursorShape> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="samplerateUnit"> + <property name="text"> + <string>S/s</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_decim"> + <property name="text"> + <string>Dec</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="decim"> + <property name="maximumSize"> + <size> + <width>50</width> + <height>16777215</height> + </size> + </property> + <property name="toolTip"> + <string>Decimation factor</string> + </property> + <property name="currentIndex"> + <number>0</number> + </property> + <item> + <property name="text"> + <string>1</string> + </property> + </item> + <item> + <property name="text"> + <string>2</string> + </property> + </item> + <item> + <property name="text"> + <string>4</string> + </property> + </item> + <item> + <property name="text"> + <string>8</string> + </property> + </item> + <item> + <property name="text"> + <string>16</string> + </property> + </item> + <item> + <property name="text"> + <string>32</string> + </property> + </item> + <item> + <property name="text"> + <string>64</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_decim" columnstretch="0,0,0,0,0,0,0,0,0,0,0,0"> + <property name="spacing"> + <number>3</number> + </property> + <item row="0" column="4"> + <spacer name="fcPosRightSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="5"> + <widget class="QLabel" name="bandwidthLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>BW </string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="label_fcPos"> + <property name="text"> + <string>Fp</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QComboBox" name="fcPos"> + <property name="toolTip"> + <string>Relative position of device center frequency</string> + </property> + <item> + <property name="text"> + <string>Inf</string> + </property> + </item> + <item> + <property name="text"> + <string>Sup</string> + </property> + </item> + <item> + <property name="text"> + <string>Cen</string> + </property> + </item> + </widget> + </item> + <item row="0" column="9"> + <widget class="QLabel" name="lnaGainLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>LNA </string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="10"> + <widget class="QComboBox" name="lna"> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <item> + <property name="text"> + <string>0</string> + </property> + </item> + <item> + <property name="text"> + <string>3</string> + </property> + </item> + <item> + <property name="text"> + <string>6</string> + </property> + </item> + </widget> + </item> + <item row="0" column="11"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>dB</string> + </property> + </widget> + </item> + <item row="0" column="7"> + <widget class="QLabel" name="bandwidthUnit"> + <property name="text"> + <string>kHz</string> + </property> + </widget> + </item> + <item row="0" column="6"> + <widget class="QComboBox" name="bandwidth"> + <property name="maximumSize"> + <size> + <width>70</width> + <height>16777215</height> + </size> + </property> + <property name="toolTip"> + <string>IF bandwidth in kHz</string> + </property> + </widget> + </item> + <item row="0" column="8"> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_lna"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_vga1"> + <property name="spacing"> + <number>3</number> + </property> + <item row="0" column="1"> + <widget class="QSlider" name="vga1"> + <property name="toolTip"> + <string>Amplifier before filtering gain (dB)</string> + </property> + <property name="minimum"> + <number>5</number> + </property> + <property name="maximum"> + <number>30</number> + </property> + <property name="pageStep"> + <number>1</number> + </property> + <property name="value"> + <number>20</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="vga1Text"> + <property name="minimumSize"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>20</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="vga1Label"> + <property name="text"> + <string>VGA1</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_vga1"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout_vga2" columnstretch="0,0,0"> + <property name="spacing"> + <number>3</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="vga2Label"> + <property name="text"> + <string>VGA2</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSlider" name="vga2"> + <property name="toolTip"> + <string>Amplifier before ADC gain (dB)</string> + </property> + <property name="maximum"> + <number>30</number> + </property> + <property name="singleStep"> + <number>3</number> + </property> + <property name="pageStep"> + <number>3</number> + </property> + <property name="value"> + <number>9</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="vga2Text"> + <property name="minimumSize"> + <size> + <width>40</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>9</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="padLayout"> + <item> + <spacer name="verticalPadSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line_vga2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ValueDial</class> + <extends>QWidget</extends> + <header>gui/valuedial.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ButtonSwitch</class> + <extends>QToolButton</extends> + <header>gui/buttonswitch.h</header> + </customwidget> + </customwidgets> + <resources> + <include location="../../../sdrgui/resources/res.qrc"/> + </resources> + <connections/> +</ui> diff --git a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp index 340069cda..db6b282c4 100644 --- a/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp +++ b/plugins/samplesource/bladerf2input/bladerf2inputplugin.cpp @@ -85,17 +85,22 @@ PluginInterface::SamplingDevices Blderf2InputPlugin::enumSampleSources() if (strcmp(boardName, "bladerf2") == 0) { - QString displayedName(QString("BladeRF2[%1] %2").arg(devinfo[i].instance).arg(devinfo[i].serial)); + unsigned int nbRxChannels = bladerf_get_channel_count(dev, BLADERF_RX); - result.append(SamplingDevice(displayedName, - m_hardwareID, - m_deviceTypeID, - QString(devinfo[i].serial), - i, - PluginInterface::SamplingDevice::PhysicalDevice, - true, - 1, - 0)); + for (int j = 0; j < nbRxChannels; j++) + { + qDebug("Blderf2InputPlugin::enumSampleSources: device #%d (%s) channel %u", i, devinfo[i].serial, j); + QString displayedName(QString("BladeRF2[%1:%2] %3").arg(devinfo[i].instance).arg(j).arg(devinfo[i].serial)); + result.append(SamplingDevice(displayedName, + m_hardwareID, + m_deviceTypeID, + QString(devinfo[i].serial), + i, + PluginInterface::SamplingDevice::PhysicalDevice, + true, + 1, + j)); + } } bladerf_close(dev); diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp new file mode 100644 index 000000000..c84e02fc3 --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.cpp @@ -0,0 +1,264 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program 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 as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#include "bladerf2inputthread.h" + +Bladerf2InputThread::Bladerf2InputThread(struct bladerf* dev, unsigned int nbRxChannels, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_nbChannels(nbRxChannels) +{ + m_channels = new Channel[nbRxChannels]; + m_buf = new qint16[2*DeviceBladeRF2::blockSize*nbRxChannels]; +} + +Bladerf2InputThread::~Bladerf2InputThread() +{ + if (m_running) { + stopWork(); + } + + delete[] m_buf; + delete[] m_channels; +} + +void Bladerf2InputThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + + while(!m_running) { + m_startWaiter.wait(&m_startWaitMutex, 100); + } + + m_startWaitMutex.unlock(); +} + +void Bladerf2InputThread::stopWork() +{ + m_running = false; + wait(); +} + +void Bladerf2InputThread::run() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + unsigned int nbFifos = getNbFifos(); + + if (nbFifos > 0) + { + int status; + + if (nbFifos > 1) { + status = bladerf_sync_config(m_dev, BLADERF_RX_X2, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + } else { + status = bladerf_sync_config(m_dev, BLADERF_RX_X1, BLADERF_FORMAT_SC16_Q11, 64, 8192, 32, 10000); + } + + if (status < 0) + { + qCritical("Bladerf2InputThread::run: cannot configure streams: %s", bladerf_strerror(status)); + } + else + { + while (m_running) + { + res = bladerf_sync_rx(m_dev, m_buf, DeviceBladeRF2::blockSize, NULL, 10000); + + if (res < 0) + { + qCritical("BladerfThread::run sync Rx error: %s", bladerf_strerror(res)); + break; + } + + if (nbFifos > 1) { + callbackMI(m_buf, DeviceBladeRF2::blockSize); + } else { + callbackSI(m_buf, 2*DeviceBladeRF2::blockSize); + } + } + } + } + else + { + qWarning("Bladerf2InputThread::run: no sample FIFOs registered. Aborting"); + } + + + m_running = false; +} + +unsigned int Bladerf2InputThread::getNbFifos() +{ + unsigned int fifoCount = 0; + + for (int i = 0; i < m_nbChannels; i++) + { + if (m_channels[i].m_sampleFifo) { + fifoCount++; + } + } + + return fifoCount; +} + +void Bladerf2InputThread::setLog2Decimation(unsigned int channel, unsigned int log2_decim) +{ + if ((channel >= 0) && (channel < m_nbChannels)) { + m_channels[channel].m_log2Decim = log2_decim; + } +} + +void Bladerf2InputThread::setFcPos(unsigned int channel, int fcPos) +{ + if ((channel >= 0) && (channel < m_nbChannels)) { + m_channels[channel].m_fcPos = fcPos; + } +} + +void Bladerf2InputThread::setFifo(unsigned int channel, SampleSinkFifo *sampleFifo) +{ + if ((channel >= 0) && (channel < m_nbChannels)) { + m_channels[channel].m_sampleFifo = sampleFifo; + } +} + +SampleSinkFifo *Bladerf2InputThread::getFifo(unsigned int channel) +{ + if ((channel >= 0) && (channel < m_nbChannels)) { + return m_channels[channel].m_sampleFifo; + } else { + return 0; + } +} + +void Bladerf2InputThread::callbackMI(const qint16* buf, qint32 samplesPerChannel) +{ + // TODO: write a set of decimators that can take interleaved samples in input directly + int status = bladerf_deinterleave_stream_buffer(BLADERF_RX_X2, BLADERF_FORMAT_SC16_Q11 , samplesPerChannel*m_nbChannels, (void *) buf); + + if (status < 0) + { + qCritical("Bladerf2InputThread::callbackMI: cannot de-interleave buffer: %s", bladerf_strerror(status)); + return; + } + + for (unsigned int channel = 0; channel < m_nbChannels; channel++) + { + if (m_channels[channel].m_sampleFifo) { + callbackSI(&buf[2*samplesPerChannel*channel], 2*samplesPerChannel, channel); + } + } +} + +void Bladerf2InputThread::callbackSI(const qint16* buf, qint32 len, unsigned int channel) +{ + SampleVector::iterator it = m_channels[channel].m_convertBuffer.begin(); + + if (m_channels[channel].m_log2Decim == 0) + { + m_channels[channel].m_decimators.decimate1(&it, buf, len); + } + else + { + if (m_channels[channel].m_fcPos == 0) // Infra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators.decimate2_inf(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators.decimate4_inf(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators.decimate8_inf(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators.decimate16_inf(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators.decimate32_inf(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 1) // Supra + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators.decimate2_sup(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators.decimate4_sup(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators.decimate8_sup(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators.decimate16_sup(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators.decimate32_sup(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_channels[channel].m_fcPos == 2) // Center + { + switch (m_channels[channel].m_log2Decim) + { + case 1: + m_channels[channel].m_decimators.decimate2_cen(&it, buf, len); + break; + case 2: + m_channels[channel].m_decimators.decimate4_cen(&it, buf, len); + break; + case 3: + m_channels[channel].m_decimators.decimate8_cen(&it, buf, len); + break; + case 4: + m_channels[channel].m_decimators.decimate16_cen(&it, buf, len); + break; + case 5: + m_channels[channel].m_decimators.decimate32_cen(&it, buf, len); + break; + case 6: + m_channels[channel].m_decimators.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_channels[channel].m_sampleFifo->write(m_channels[channel].m_convertBuffer.begin(), it); +} + diff --git a/plugins/samplesource/bladerf2input/bladerf2inputthread.h b/plugins/samplesource/bladerf2input/bladerf2inputthread.h new file mode 100644 index 000000000..23c929f0b --- /dev/null +++ b/plugins/samplesource/bladerf2input/bladerf2inputthread.h @@ -0,0 +1,90 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2018 Edouard Griffiths, F4EXB // +// // +// This program 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 as version 3 of the License, or // +// // +// This program is distributed in the hope that it will be useful, // +// but WITHOUT ANY WARRANTY; without even the implied warranty of // +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // +// GNU General Public License V3 for more details. // +// // +// You should have received a copy of the GNU General Public License // +// along with this program. If not, see <http://www.gnu.org/licenses/>. // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTTHREAD_H_ +#define PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTTHREAD_H_ + +// BladerRF2 is a SISO/MIMO device with a single stream supporting one or two Rx +// Therefore only one thread can be allocated for the Rx side +// All FIFOs must be registered before calling startWork() else SISO/MIMO switch will not work properly +// with unpredicatable results + +#include <QThread> +#include <QMutex> +#include <QWaitCondition> + +#include <libbladeRF.h> + +#include "bladerf2/devicebladerf2shared.h" +#include "dsp/samplesinkfifo.h" +#include "dsp/decimators.h" + +class Bladerf2InputThread : public QThread, public DeviceBladeRF2Shared::InputThreadInterface { + Q_OBJECT + +public: + Bladerf2InputThread(struct bladerf* dev, unsigned int nbRxChannels, QObject* parent = NULL); + virtual ~Bladerf2InputThread(); + + virtual void startWork(); + virtual void stopWork(); + virtual bool isRunning() const { return m_running; } + void setLog2Decimation(unsigned int channel, unsigned int log2_decim); + void setFcPos(unsigned int channel, int fcPos); + virtual void setFifo(unsigned int channel, SampleSinkFifo *sampleFifo); + virtual SampleSinkFifo *getFifo(unsigned int channel); + +private: + struct Channel + { + SampleVector m_convertBuffer; + SampleSinkFifo* m_sampleFifo; + unsigned int m_log2Decim; + int m_fcPos; + Decimators<qint32, qint16, SDR_RX_SAMP_SZ, 12> m_decimators; + + Channel() : + m_sampleFifo(0), + m_log2Decim(0), + m_fcPos(0) + {} + + ~Channel() + { + if (m_sampleFifo) { + delete[] m_sampleFifo; + } + } + }; + + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + struct bladerf* m_dev; + + Channel *m_channels; //!< Array of channels dynamically allocated for the given number of Rx channels + qint16 *m_buf; //!< Full buffer for SISO or MIMO operation + unsigned int m_nbChannels; + + void run(); + unsigned int getNbFifos(); + void callbackSI(const qint16* buf, qint32 len, unsigned int channel = 0); + void callbackMI(const qint16* buf, qint32 samplesPerChannel); +}; + + + +#endif /* PLUGINS_SAMPLESOURCE_BLADERF2INPUT_BLADERF2INPUTTHREAD_H_ */ diff --git a/plugins/samplesource/limesdrinput/limesdrinput.cpp b/plugins/samplesource/limesdrinput/limesdrinput.cpp index 8a7aa88fe..f623e31e0 100644 --- a/plugins/samplesource/limesdrinput/limesdrinput.cpp +++ b/plugins/samplesource/limesdrinput/limesdrinput.cpp @@ -103,6 +103,13 @@ bool LimeSDRInput::openDevice() DeviceSourceAPI *sourceBuddy = m_deviceAPI->getSourceBuddies()[0]; //m_deviceShared = *((DeviceLimeSDRShared *) sourceBuddy->getBuddySharedPtr()); // copy shared data DeviceLimeSDRShared *deviceLimeSDRShared = (DeviceLimeSDRShared*) sourceBuddy->getBuddySharedPtr(); + + if (deviceLimeSDRShared == 0) + { + qCritical("LimeSDRInput::openDevice: the source buddy shared pointer is null"); + return false; + } + m_deviceShared.m_deviceParams = deviceLimeSDRShared->m_deviceParams; DeviceLimeSDRParams *deviceParams = m_deviceShared.m_deviceParams; // get device parameters @@ -152,6 +159,13 @@ bool LimeSDRInput::openDevice() DeviceSinkAPI *sinkBuddy = m_deviceAPI->getSinkBuddies()[0]; //m_deviceShared = *((DeviceLimeSDRShared *) sinkBuddy->getBuddySharedPtr()); // copy parameters DeviceLimeSDRShared *deviceLimeSDRShared = (DeviceLimeSDRShared*) sinkBuddy->getBuddySharedPtr(); + + if (deviceLimeSDRShared == 0) + { + qCritical("LimeSDRInput::openDevice: the sink buddy shared pointer is null"); + return false; + } + m_deviceShared.m_deviceParams = deviceLimeSDRShared->m_deviceParams; if (m_deviceShared.m_deviceParams == 0)