diff --git a/Readme.md b/Readme.md index 6f4578fc9..5277342d5 100644 --- a/Readme.md +++ b/Readme.md @@ -43,6 +43,16 @@ Use `cmake ../ -DV4L-RTL=ON` to build the Linux kernel driver for RTL-SDR (Exper

Supported hardware

+

Airspy

+ +Airspy is supported through the libairspy library that should be installed in your system for proper build of the software and operation support. Add `libairspy-dev` to the list of dependencies to install. + +If you use your own location for libairspy install directory you need to specify library and include locations. Example with `/opt/install/libairspy` with the following defines on `cmake` command line: + +`-DLIBAIRSPY_LIBRARIES=/opt/install/libairspy/lib/libairspy.so -DLIBAIRSPY_INCLUDE_DIR=/opt/install/libairspy/include` + +Please note that if you are using a recent version of libairspy (>= 1.0.6) the dynamic retrieval of sample rates is supported. To benefit from it you should modify the `plugins/samplesource/airspy/CMakeLists.txt` and change line `add_definitions(${QT_DEFINITIONS})` by `add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES")`. In fact both lines are present with the last one commented out. +

BladeRF

BladeRF 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. diff --git a/cmake/Modules/FindLibAIRSPY.cmake b/cmake/Modules/FindLibAIRSPY.cmake new file mode 100644 index 000000000..2402f2f74 --- /dev/null +++ b/cmake/Modules/FindLibAIRSPY.cmake @@ -0,0 +1,28 @@ +if(NOT LIBAIRSPY_FOUND) + + pkg_check_modules (LIBAIRSPY_PKG libairspy) + find_path(LIBAIRSPY_INCLUDE_DIR NAMES libairspy/airspy.h + PATHS + ${LIBAIRSPY_PKG_INCLUDE_DIRS} + /usr/include + /usr/local/include + ) + + find_library(LIBAIRSPY_LIBRARIES NAMES airspy + PATHS + ${LIBAIRSPY_PKG_LIBRARY_DIRS} + /usr/lib + /usr/local/lib + ) + + if(LIBAIRSPY_INCLUDE_DIR AND LIBAIRSPY_LIBRARIES) + set(LIBAIRSPY_FOUND TRUE CACHE INTERNAL "libairspy found") + message(STATUS "Found libairspy: ${LIBAIRSPY_INCLUDE_DIR}, ${LIBAIRSPY_LIBRARIES}") + else(LIBAIRSPY_INCLUDE_DIR AND LIBAIRSPY_LIBRARIES) + set(LIBAIRSPY_FOUND FALSE CACHE INTERNAL "libairspy found") + message(STATUS "libairspy not found.") + endif(LIBAIRSPY_INCLUDE_DIR AND LIBAIRSPY_LIBRARIES) + + mark_as_advanced(LIBAIRSPY_INCLUDE_DIR LIBAIRSPY_LIBRARIES) + +endif(NOT LIBAIRSPY_FOUND) diff --git a/include-gpl/dsp/decimators.h b/include-gpl/dsp/decimators.h index dbd37d67f..2579dcb26 100644 --- a/include-gpl/dsp/decimators.h +++ b/include-gpl/dsp/decimators.h @@ -34,6 +34,8 @@ struct decimation_shifts static const uint post16 = 0; static const uint pre32 = 0; static const uint post32 = 0; + static const uint pre64 = 0; + static const uint post64 = 0; }; template<> @@ -50,6 +52,8 @@ struct decimation_shifts<16, 16> static const uint post16 = 4; static const uint pre32 = 0; static const uint post32 = 5; + static const uint pre64 = 0; + static const uint post64 = 6; }; template<> @@ -66,22 +70,26 @@ struct decimation_shifts<16, 12> static const uint post16 = 0; static const uint pre32 = 0; static const uint post32 = 1; + static const uint pre64 = 0; + static const uint post64 = 2; }; template<> struct decimation_shifts<16, 8> { - static const uint pre1 = 5; - static const uint pre2 = 4; + static const uint pre1 = 6; + static const uint pre2 = 5; static const uint post2 = 0; - static const uint pre4 = 3; + static const uint pre4 = 4; static const uint post4 = 0; - static const uint pre8 = 2; + static const uint pre8 = 3; static const uint post8 = 0; - static const uint pre16 = 1; + static const uint pre16 = 2; static const uint post16 = 0; - static const uint pre32 = 0; + static const uint pre32 = 1; static const uint post32 = 0; + static const uint pre64 = 0; + static const uint post64 = 0; }; template @@ -105,6 +113,9 @@ public: void decimate32_inf(SampleVector::iterator* it, const T* buf, qint32 len); void decimate32_sup(SampleVector::iterator* it, const T* buf, qint32 len); void decimate32_cen(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len); + void decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len); private: IntHalfbandFilter m_decimator2; // 1st stages @@ -112,6 +123,7 @@ private: IntHalfbandFilter m_decimator8; // 3rd stages IntHalfbandFilter m_decimator16; // 4th stages IntHalfbandFilter m_decimator32; // 5th stages + IntHalfbandFilter m_decimator64; // 6th stages }; template @@ -352,6 +364,57 @@ void Decimators::decimate32_cen(SampleVector::iterator* i } } + +template +void Decimators::decimate64_cen(SampleVector::iterator* it, const T* buf, qint32 len) +{ + int pos = 0; + + while (pos < len) + { + qint32 x0 = buf[pos+0] << decimation_shifts::pre64; + qint32 y0 = buf[pos+1] << decimation_shifts::pre64; + pos += 2; + + if (m_decimator2.workDecimateCenter(&x0, &y0)) + { + qint32 x1 = x0; + qint32 y1 = y0; + + if (m_decimator4.workDecimateCenter(&x1, &y1)) + { + qint32 x2 = x1; + qint32 y2 = y1; + + if (m_decimator8.workDecimateCenter(&x2, &y2)) + { + qint32 x3 = x2; + qint32 y3 = y2; + + if (m_decimator16.workDecimateCenter(&x3, &y3)) + { + qint32 x4 = x3; + qint32 y4 = y3; + + if (m_decimator32.workDecimateCenter(&x4, &y4)) + { + qint32 x5 = x4; + qint32 y5 = y4; + + if (m_decimator64.workDecimateCenter(&x4, &y4)) + { + (**it).setReal(x5 >> decimation_shifts::post64); + (**it).setImag(y5 >> decimation_shifts::post64); + ++(*it); + } + } + } + } + } + } + } +} + template void Decimators::decimate4_inf(SampleVector::iterator* it, const T* buf, qint32 len) { @@ -556,4 +619,84 @@ void Decimators::decimate32_sup(SampleVector::iterator* i } } +template +void Decimators::decimate64_inf(SampleVector::iterator* it, const T* buf, qint32 len) +{ + qint32 xreal[16], yimag[16]; + + for (int pos = 0; pos < len - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+0] - buf[pos+3] + buf[pos+7] - buf[pos+4]) << decimation_shifts::pre64; + yimag[i] = (buf[pos+1] - buf[pos+5] + buf[pos+2] - buf[pos+6]) << decimation_shifts::pre64; + pos += 16; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[3]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15] >> decimation_shifts::post64); + (**it).setImag(yimag[15] >> decimation_shifts::post64); + + ++(*it); + } +} + +template +void Decimators::decimate64_sup(SampleVector::iterator* it, const T* buf, qint32 len) +{ + qint32 xreal[16], yimag[16]; + + for (int pos = 0; pos < len - 127; ) + { + for (int i = 0; i < 16; i++) + { + xreal[i] = (buf[pos+1] - buf[pos+2] - buf[pos+5] + buf[pos+6]) << decimation_shifts::pre32; + yimag[i] = (buf[pos+4] + buf[pos+7] - buf[pos+0] - buf[pos+3]) << decimation_shifts::pre32; + pos += 16; + } + + m_decimator2.myDecimate(xreal[0], yimag[0], &xreal[1], &yimag[1]); + m_decimator2.myDecimate(xreal[2], yimag[2], &xreal[3], &yimag[3]); + m_decimator2.myDecimate(xreal[4], yimag[4], &xreal[5], &yimag[3]); + m_decimator2.myDecimate(xreal[6], yimag[6], &xreal[7], &yimag[7]); + m_decimator2.myDecimate(xreal[8], yimag[8], &xreal[9], &yimag[9]); + m_decimator2.myDecimate(xreal[10], yimag[10], &xreal[11], &yimag[11]); + m_decimator2.myDecimate(xreal[12], yimag[12], &xreal[13], &yimag[13]); + m_decimator2.myDecimate(xreal[14], yimag[14], &xreal[15], &yimag[15]); + + m_decimator4.myDecimate(xreal[1], yimag[1], &xreal[3], &yimag[3]); + m_decimator4.myDecimate(xreal[5], yimag[5], &xreal[7], &yimag[7]); + m_decimator4.myDecimate(xreal[9], yimag[9], &xreal[11], &yimag[11]); + m_decimator4.myDecimate(xreal[13], yimag[13], &xreal[15], &yimag[15]); + + m_decimator8.myDecimate(xreal[3], yimag[3], &xreal[7], &yimag[7]); + m_decimator8.myDecimate(xreal[11], yimag[11], &xreal[15], &yimag[15]); + + m_decimator16.myDecimate(xreal[7], yimag[7], &xreal[15], &yimag[15]); + + (**it).setReal(xreal[15] >> decimation_shifts::post64); + (**it).setImag(yimag[15] >> decimation_shifts::post64); + + ++(*it); + } +} + #endif /* INCLUDE_GPL_DSP_DECIMATORS_H_ */ diff --git a/plugins/samplesource/CMakeLists.txt b/plugins/samplesource/CMakeLists.txt index 150632393..ae8d2babb 100644 --- a/plugins/samplesource/CMakeLists.txt +++ b/plugins/samplesource/CMakeLists.txt @@ -32,4 +32,9 @@ if(LIBUSB_FOUND AND LIBBLADERF_FOUND) add_subdirectory(bladerf) endif(LIBUSB_FOUND AND LIBBLADERF_FOUND) +find_package(LibAIRSPY) +if(LIBUSB_FOUND AND LIBAIRSPY_FOUND) + add_subdirectory(airspy) +endif(LIBUSB_FOUND AND LIBAIRSPY_FOUND) + add_subdirectory(filesource) diff --git a/plugins/samplesource/airspy/CMakeLists.txt b/plugins/samplesource/airspy/CMakeLists.txt new file mode 100644 index 000000000..5eaeaa731 --- /dev/null +++ b/plugins/samplesource/airspy/CMakeLists.txt @@ -0,0 +1,53 @@ +project(airspy) + +set(airspy_SOURCES + airspygui.cpp + airspyinput.cpp + airspyplugin.cpp + airspyserializer.cpp + airspythread.cpp +) + +set(airspy_HEADERS + airspygui.h + airspyinput.h + airspyplugin.h + airspyserializer.h + airspythread.h +) + +set(airspy_FORMS + airspygui.ui +) + +include_directories( + . + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/include-gpl + ${LIBAIRSPY_INCLUDE_DIR} +) + +#include(${QT_USE_FILE}) +#add_definitions(${QT_DEFINITIONS}) +add_definitions("${QT_DEFINITIONS} -DLIBAIRSPY_DYN_RATES") +add_definitions(-DQT_PLUGIN) +add_definitions(-DQT_SHARED) + +#qt4_wrap_cpp(airspy_HEADERS_MOC ${airspy_HEADERS}) +qt5_wrap_ui(airspy_FORMS_HEADERS ${airspy_FORMS}) + +add_library(inputairspy SHARED + ${airspy_SOURCES} + ${airspy_HEADERS_MOC} + ${airspy_FORMS_HEADERS} +) + +target_link_libraries(inputairspy + ${QT_LIBRARIES} + ${LIBAIRSPY_LIBRARIES} + ${LIBUSB_LIBRARIES} + sdrbase +) + +qt5_use_modules(inputairspy Core Widgets OpenGL Multimedia) diff --git a/plugins/samplesource/airspy/airspygui.cpp b/plugins/samplesource/airspy/airspygui.cpp new file mode 100644 index 000000000..0c26854aa --- /dev/null +++ b/plugins/samplesource/airspy/airspygui.cpp @@ -0,0 +1,294 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "airspygui.h" +#include "ui_airspygui.h" +#include "plugin/pluginapi.h" +#include "gui/colormapper.h" +#include "dsp/dspengine.h" + +AirspyGui::AirspyGui(PluginAPI* pluginAPI, QWidget* parent) : + QWidget(parent), + ui(new Ui::AirspyGui), + m_pluginAPI(pluginAPI), + m_settings(), + m_sampleSource(NULL) +{ + ui->setupUi(this); + ui->centerFrequency->setColorMapper(ColorMapper(ColorMapper::ReverseGold)); + ui->centerFrequency->setValueRange(7, 24000U, 1900000U); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + displaySettings(); + + m_sampleSource = new AirspyInput(); + m_rates = ((AirspyInput*) m_sampleSource)->getSampleRates(); + displaySampleRates(); + connect(m_sampleSource->getOutputMessageQueueToGUI(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages())); + DSPEngine::instance()->setSource(m_sampleSource); +} + +AirspyGui::~AirspyGui() +{ + delete m_sampleSource; // Valgrind memcheck + delete ui; +} + +void AirspyGui::destroy() +{ + delete this; +} + +void AirspyGui::setName(const QString& name) +{ + setObjectName(name); +} + +QString AirspyGui::getName() const +{ + return objectName(); +} + +void AirspyGui::resetToDefaults() +{ + m_settings.resetToDefaults(); + displaySettings(); + sendSettings(); +} + +qint64 AirspyGui::getCenterFrequency() const +{ + return m_settings.m_centerFrequency; +} + +QByteArray AirspyGui::serialize() const +{ + return m_settings.serialize(); +} + +bool AirspyGui::deserialize(const QByteArray& data) +{ + if(m_settings.deserialize(data)) { + displaySettings(); + sendSettings(); + return true; + } else { + resetToDefaults(); + return false; + } +} + +bool AirspyGui::handleMessage(const Message& message) +{ + if (AirspyInput::MsgReportAirspy::match(message)) + { + qDebug() << "AirspyGui::handleMessage: MsgReportAirspy"; + m_rates = ((AirspyInput::MsgReportAirspy&) message).getSampleRates(); + displaySampleRates(); + return true; + } + else + { + return false; + } +} + +void AirspyGui::handleSourceMessages() +{ + Message* message; + + while ((message = m_sampleSource->getOutputMessageQueueToGUI()->pop()) != 0) + { + qDebug("AirspyGui::HandleSourceMessages: message: %s", message->getIdentifier()); + + if (handleMessage(*message)) + { + delete message; + } + } +} + +void AirspyGui::displaySettings() +{ + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + + ui->LOppm->setValue(m_settings.m_LOppmTenths); + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + + ui->sampleRate->setCurrentIndex(m_settings.m_devSampleRateIndex); + + ui->biasT->setChecked(m_settings.m_biasT); + + ui->decimText->setText(tr("%1").arg(1<decim->setValue(m_settings.m_log2Decim); + + ui->fcPos->setCurrentIndex((int) m_settings.m_fcPos); + + ui->lnaGainText->setText(tr("%1dB").arg(m_settings.m_lnaGain)); + ui->lna->setValue(m_settings.m_lnaGain); + + ui->mixText->setText(tr("%1dB").arg(m_settings.m_mixerGain)); + ui->mix->setValue(m_settings.m_mixerGain); + + ui->vgaText->setText(tr("%1dB").arg(m_settings.m_vgaGain)); + ui->vga->setValue(m_settings.m_vgaGain); +} + +void AirspyGui::displaySampleRates() +{ + int savedIndex = m_settings.m_devSampleRateIndex; + ui->sampleRate->blockSignals(true); + + if (m_rates.size() > 0) + { + ui->sampleRate->clear(); + + for (int i = 0; i < m_rates.size(); i++) + { + ui->sampleRate->addItem(QString("%1M").arg(QString::number(m_rates[i]/1000000.0, 'f', 3))); + } + } + + ui->sampleRate->blockSignals(false); + + if (savedIndex < m_rates.size()) + { + ui->sampleRate->setCurrentIndex(savedIndex); + } + else + { + ui->sampleRate->setCurrentIndex((int) m_rates.size()-1); + } +} + +void AirspyGui::sendSettings() +{ + if(!m_updateTimer.isActive()) + m_updateTimer.start(100); +} + +void AirspyGui::on_centerFrequency_changed(quint64 value) +{ + m_settings.m_centerFrequency = value * 1000; + sendSettings(); +} + +void AirspyGui::on_LOppm_valueChanged(int value) +{ + m_settings.m_LOppmTenths = value; + ui->LOppmText->setText(QString("%1").arg(QString::number(m_settings.m_LOppmTenths/10.0, 'f', 1))); + sendSettings(); +} + +void AirspyGui::on_sampleRate_currentIndexChanged(int index) +{ + m_settings.m_devSampleRateIndex = index; + sendSettings(); +} + +void AirspyGui::on_biasT_stateChanged(int state) +{ + m_settings.m_biasT = (state == Qt::Checked); + sendSettings(); +} + +void AirspyGui::on_decim_valueChanged(int value) +{ + if ((value <0) || (value > 6)) + return; + ui->decimText->setText(tr("%1").arg(1< 14)) + return; + + ui->lnaGainText->setText(tr("%1dB").arg(value)); + m_settings.m_lnaGain = value; + sendSettings(); +} + +void AirspyGui::on_mix_valueChanged(int value) +{ + if ((value < 0) || (value > 15)) + return; + + ui->mixText->setText(tr("%1dB").arg(value)); + m_settings.m_lnaGain = value; + sendSettings(); +} + +void AirspyGui::on_vga_valueChanged(int value) +{ + if ((value < 0) || (value > 15)) + return; + + ui->vgaText->setText(tr("%1dB").arg(value)); + m_settings.m_lnaGain = value; + sendSettings(); +} + +void AirspyGui::updateHardware() +{ + qDebug() << "AirspyGui::updateHardware"; + AirspyInput::MsgConfigureAirspy* message = AirspyInput::MsgConfigureAirspy::create( m_settings); + m_sampleSource->getInputMessageQueue()->push(message); + m_updateTimer.stop(); +} + +uint32_t AirspyGui::getDevSampleRate(unsigned int rate_index) +{ + if (rate_index < m_rates.size()) + { + return m_rates[rate_index]; + } + else + { + return m_rates[0]; + } +} + +int AirspyGui::getDevSampleRateIndex(uint32_t sampeRate) +{ + for (unsigned int i=0; i < m_rates.size(); i++) + { + if (sampeRate == m_rates[i]) + { + return i; + } + } + + return -1; +} diff --git a/plugins/samplesource/airspy/airspygui.h b/plugins/samplesource/airspy/airspygui.h new file mode 100644 index 000000000..a65f3c9a9 --- /dev/null +++ b/plugins/samplesource/airspy/airspygui.h @@ -0,0 +1,80 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AIRSPYGUI_H +#define INCLUDE_AIRSPYGUI_H + +#include + +#include "airspyinput.h" +#include "plugin/plugingui.h" + +#define AIRSPY_MAX_DEVICE (32) + +class PluginAPI; + +namespace Ui { + class AirspyGui; + class AirspySampleRates; +} + +class AirspyGui : public QWidget, public PluginGUI { + Q_OBJECT + +public: + explicit AirspyGui(PluginAPI* pluginAPI, QWidget* parent = NULL); + virtual ~AirspyGui(); + void destroy(); + + void setName(const QString& name); + QString getName() const; + + void resetToDefaults(); + qint64 getCenterFrequency() const; + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + virtual bool handleMessage(const Message& message); + uint32_t getDevSampleRate(unsigned int index); + int getDevSampleRateIndex(uint32_t sampleRate); + +private: + Ui::AirspyGui* ui; + + PluginAPI* m_pluginAPI; + AirspyInput::Settings m_settings; + QTimer m_updateTimer; + std::vector m_rates; + SampleSource* m_sampleSource; + + void displaySettings(); + void displaySampleRates(); + void sendSettings(); + +private slots: + void on_centerFrequency_changed(quint64 value); + void on_LOppm_valueChanged(int value); + void on_sampleRate_currentIndexChanged(int index); + void on_biasT_stateChanged(int state); + void on_decim_valueChanged(int value); + void on_fcPos_currentIndexChanged(int index); + void on_lna_valueChanged(int value); + void on_mix_valueChanged(int value); + void on_vga_valueChanged(int value); + void updateHardware(); + void handleSourceMessages(); +}; + +#endif // INCLUDE_AIRSPYGUI_H diff --git a/plugins/samplesource/airspy/airspygui.ui b/plugins/samplesource/airspy/airspygui.ui new file mode 100644 index 000000000..cf65fac5d --- /dev/null +++ b/plugins/samplesource/airspy/airspygui.ui @@ -0,0 +1,450 @@ + + + AirspyGui + + + + 0 + 0 + 198 + 214 + + + + + 0 + 0 + + + + BladeRF + + + + 3 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 32 + 16 + + + + + Monospace + 20 + + + + SizeVerCursor + + + Qt::StrongFocus + + + Tuner center frequency in kHz + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + 3 + + + + + LO correction ppm + + + LO ppm + + + + + + + -100 + + + 100 + + + 1 + + + Qt::Horizontal + + + + + + + 0.0 + + + + + + + + + Qt::Horizontal + + + + + + + 3 + + + + + Bias T + + + + + + + + 0 + 0 + + + + Rate + + + + + + + Device sample rate + + + + + + + + + 3 + + + + + Dec. + + + + + + + 6 + + + 1 + + + 0 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 1 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 3 + + + + + Fc pos + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + Inf + + + + + Sup + + + + + Cent + + + + + + + + + + Qt::Horizontal + + + + + + + 3 + + + + + + 0 + 0 + + + + LNA + + + + + + + true + + + LNA gain dB + + + 14 + + + 1 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 3 + + + + + Mixer gain dB + + + 0 + + + 15 + + + 1 + + + 5 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Mixer + + + + + + + + + 3 + + + + + VGA + + + + + + + VGA gain dB + + + 15 + + + 1 + + + 1 + + + 5 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0dB + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + ValueDial + QWidget +
gui/valuedial.h
+ 1 +
+
+ + +
diff --git a/plugins/samplesource/airspy/airspyinput.cpp b/plugins/samplesource/airspy/airspyinput.cpp new file mode 100644 index 000000000..8a8c50436 --- /dev/null +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -0,0 +1,495 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "airspygui.h" +#include "airspyinput.h" +#include "util/simpleserializer.h" +#include "dsp/dspcommands.h" +#include "airspyserializer.h" +#include "airspythread.h" + +MESSAGE_CLASS_DEFINITION(AirspyInput::MsgConfigureAirspy, Message) +MESSAGE_CLASS_DEFINITION(AirspyInput::MsgReportAirspy, Message) + +AirspyInput::Settings::Settings() : + m_centerFrequency(435000*1000), + m_devSampleRateIndex(0), + m_LOppmTenths(0), + m_lnaGain(1), + m_mixerGain(5), + m_vgaGain(5), + m_log2Decim(0), + m_fcPos(FC_POS_INFRA), + m_biasT(false) +{ +} + +void AirspyInput::Settings::resetToDefaults() +{ + m_centerFrequency = 435000*1000; + m_devSampleRateIndex = 0; + m_LOppmTenths = 0; + m_lnaGain = 1; + m_mixerGain = 5; + m_vgaGain = 5; + m_log2Decim = 0; + m_fcPos = FC_POS_INFRA; + m_biasT = false; +} + +QByteArray AirspyInput::Settings::serialize() const +{ + AirspySerializer::AirspyData data; + + data.m_data.m_frequency = m_centerFrequency; + data.m_LOppmTenths = m_LOppmTenths; + data.m_sampleRateIndex = m_devSampleRateIndex; + data.m_log2Decim = m_log2Decim; + data.m_fcPos = (qint32) m_fcPos; + data.m_lnaGain = m_lnaGain; + data.m_mixerGain = m_mixerGain; + data.m_vgaGain = m_vgaGain; + data.m_biasT = m_biasT; + + QByteArray byteArray; + + AirspySerializer::writeSerializedData(data, byteArray); + + return byteArray; +} + +bool AirspyInput::Settings::deserialize(const QByteArray& serializedData) +{ + AirspySerializer::AirspyData data; + + bool valid = AirspySerializer::readSerializedData(serializedData, data); + + m_centerFrequency = data.m_data.m_frequency; + m_LOppmTenths = data.m_LOppmTenths; + m_devSampleRateIndex = data.m_sampleRateIndex; + m_log2Decim = data.m_log2Decim; + m_fcPos = (fcPos_t) data.m_fcPos; + m_lnaGain = data.m_lnaGain; + m_mixerGain = data.m_mixerGain; + m_vgaGain = data.m_vgaGain; + m_biasT = data.m_biasT; + + return valid; +} + +AirspyInput::AirspyInput() : + m_settings(), + m_dev(0), + m_airspyThread(0), + m_deviceDescription("Airspy") +{ + m_sampleRates.push_back(10000000); + m_sampleRates.push_back(2500000); +} + +AirspyInput::~AirspyInput() +{ + stop(); +} + +bool AirspyInput::init(const Message& cmd) +{ + return false; +} + +bool AirspyInput::start(int device) +{ + QMutexLocker mutexLocker(&m_mutex); + airspy_error rc; + + rc = (airspy_error) airspy_init(); + + if (rc != AIRSPY_SUCCESS) + { + qCritical("AirspyInput::start: failed to initiate Airspy library %s", airspy_error_name(rc)); + } + + if (m_dev != 0) + { + stop(); + } + + if (!m_sampleFifo.setSize(96000 * 4)) + { + qCritical("AirspyInput::start: could not allocate SampleFifo"); + return false; + } + + if ((m_dev = open_airspy_from_sequence(device)) == 0) + { + qCritical("AirspyInput::start: could not open Airspy #%d", device); + return false; + } + +#ifdef LIBAIRSPY_DYN_RATES + uint32_t nbSampleRates; + uint32_t *sampleRates; + + airspy_get_samplerates(m_dev, &nbSampleRates, 0); + + sampleRates = new uint32_t[nbSampleRates]; + + airspy_get_samplerates(m_dev, sampleRates, nbSampleRates); + + if (nbSampleRates == 0) + { + qCritical("AirspyInput::start: could not obtain Airspy sample rates"); + return false; + } + + m_sampleRates.clear(); + + for (int i=0; ipush(message); + + rc = (airspy_error) airspy_set_sample_type(m_dev, AIRSPY_SAMPLE_INT16_IQ); + + if (rc != AIRSPY_SUCCESS) + { + qCritical("AirspyInput::start: could not set sample type to INT16_IQ"); + return false; + } + + if((m_airspyThread = new AirspyThread(m_dev, &m_sampleFifo)) == 0) + { + qFatal("AirspyInput::start: out of memory"); + stop(); + return false; + } + + m_airspyThread->startWork(); + + mutexLocker.unlock(); + + applySettings(m_settings, true); + + qDebug("AirspyInput::startInput: started"); + + return true; +} + +void AirspyInput::stop() +{ + qDebug("AirspyInput::stop"); + QMutexLocker mutexLocker(&m_mutex); + + if(m_airspyThread != 0) + { + m_airspyThread->stopWork(); + delete m_airspyThread; + m_airspyThread = 0; + } + + if(m_dev != 0) + { + airspy_stop_rx(m_dev); + airspy_close(m_dev); + m_dev = 0; + } + + airspy_exit(); +} + +const QString& AirspyInput::getDeviceDescription() const +{ + return m_deviceDescription; +} + +int AirspyInput::getSampleRate() const +{ + int rate = m_sampleRates[m_settings.m_devSampleRateIndex]; + return (rate / (1<(freq_hz)); + + if (rc != AIRSPY_SUCCESS) + { + qWarning("AirspyInput::setCenterFrequency: could not frequency to %llu Hz", freq_hz); + } + else + { + qWarning("AirspyInput::setCenterFrequency: frequency set to %llu Hz", freq_hz); + } +} + +bool AirspyInput::applySettings(const Settings& settings, bool force) +{ + QMutexLocker mutexLocker(&m_mutex); + + bool forwardChange = false; + airspy_error rc; + + qDebug() << "AirspyInput::applySettings"; + + if ((m_settings.m_devSampleRateIndex != settings.m_devSampleRateIndex) || force) + { + forwardChange = true; + + if (settings.m_devSampleRateIndex < m_sampleRates.size()) + { + m_settings.m_devSampleRateIndex = settings.m_devSampleRateIndex; + } + else + { + m_settings.m_devSampleRateIndex = m_sampleRates.size() - 1; + } + + if (m_dev != 0) + { + rc = (airspy_error) airspy_set_samplerate(m_dev, static_cast(m_settings.m_devSampleRateIndex)); + + if (rc != AIRSPY_SUCCESS) + { + qCritical("AirspyInput::applySettings: could not set sample rate index %u (%d S/s): %s", m_settings.m_devSampleRateIndex, m_sampleRates[m_settings.m_devSampleRateIndex], airspy_error_name(rc)); + } + else + { + qDebug("AirspyInput::applySettings: sample rate set to index: %u (%d S/s)", m_settings.m_devSampleRateIndex, m_sampleRates[m_settings.m_devSampleRateIndex]); + m_airspyThread->setSamplerate(m_sampleRates[m_settings.m_devSampleRateIndex]); + } + } + } + + if ((m_settings.m_log2Decim != settings.m_log2Decim) || force) + { + m_settings.m_log2Decim = settings.m_log2Decim; + forwardChange = true; + + if(m_dev != 0) + { + m_airspyThread->setLog2Decimation(m_settings.m_log2Decim); + qDebug() << "AirspyInput: set decimation to " << (1<setFcPos((int) m_settings.m_fcPos); + qDebug() << "AirspyInput: set fc pos (enum) to " << (int) m_settings.m_fcPos; + } + } + + qint64 deviceCenterFrequency = m_settings.m_centerFrequency; + qint64 f_img = deviceCenterFrequency; + quint32 devSampleRate = m_sampleRates[m_settings.m_devSampleRateIndex]; + + if (force || (m_settings.m_centerFrequency != settings.m_centerFrequency) || + (m_settings.m_LOppmTenths != settings.m_LOppmTenths)) + { + m_settings.m_centerFrequency = settings.m_centerFrequency; + m_settings.m_LOppmTenths = settings.m_LOppmTenths; + + if ((m_settings.m_log2Decim == 0) || (m_settings.m_fcPos == FC_POS_CENTER)) + { + deviceCenterFrequency = m_settings.m_centerFrequency; + f_img = deviceCenterFrequency; + } + else + { + if (m_settings.m_fcPos == FC_POS_INFRA) + { + deviceCenterFrequency = m_settings.m_centerFrequency + (devSampleRate / 4); + f_img = deviceCenterFrequency + devSampleRate/2; + } + else if (m_settings.m_fcPos == FC_POS_SUPRA) + { + deviceCenterFrequency = m_settings.m_centerFrequency - (devSampleRate / 4); + f_img = deviceCenterFrequency - devSampleRate/2; + } + } + + if (m_dev != 0) + { + setCenterFrequency(deviceCenterFrequency); + + qDebug() << "AirspyInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " device center freq: " << deviceCenterFrequency << " Hz" + << " device sample rate: " << devSampleRate << "Hz" + << " Actual sample rate: " << devSampleRate/(1<push(notif); + } + + return true; +} + +struct airspy_device *AirspyInput::open_airspy_from_sequence(int sequence) +{ + airspy_read_partid_serialno_t read_partid_serialno; + struct airspy_device *devinfo, *retdev = 0; + uint32_t serial_msb = 0; + uint32_t serial_lsb = 0; + airspy_error rc; + int i; + + for (int i = 0; i < AIRSPY_MAX_DEVICE; i++) + { + rc = (airspy_error) airspy_open(&devinfo); + + if (rc == AIRSPY_SUCCESS) + { + if (i == sequence) + { + return devinfo; + } + } + else + { + break; + } + } + + return 0; +} diff --git a/plugins/samplesource/airspy/airspyinput.h b/plugins/samplesource/airspy/airspyinput.h new file mode 100644 index 000000000..aa2639efd --- /dev/null +++ b/plugins/samplesource/airspy/airspyinput.h @@ -0,0 +1,118 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AIRSPYINPUT_H +#define INCLUDE_AIRSPYINPUT_H + +#include "dsp/samplesource.h" +#include +#include + +class AirspyThread; + +class AirspyInput : public SampleSource { +public: + typedef enum { + FC_POS_INFRA = 0, + FC_POS_SUPRA, + FC_POS_CENTER + } fcPos_t; + + struct Settings { + quint64 m_centerFrequency; + qint32 m_LOppmTenths; + quint32 m_devSampleRateIndex; + quint32 m_lnaGain; + quint32 m_mixerGain; + quint32 m_vgaGain; + quint32 m_log2Decim; + fcPos_t m_fcPos; + bool m_biasT; + + Settings(); + void resetToDefaults(); + QByteArray serialize() const; + bool deserialize(const QByteArray& data); + }; + + class MsgConfigureAirspy : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const Settings& getSettings() const { return m_settings; } + + static MsgConfigureAirspy* create(const Settings& settings) + { + return new MsgConfigureAirspy(settings); + } + + private: + Settings m_settings; + + MsgConfigureAirspy(const Settings& settings) : + Message(), + m_settings(settings) + { } + }; + + class MsgReportAirspy : public Message { + MESSAGE_CLASS_DECLARATION + + public: + const std::vector& getSampleRates() const { return m_sampleRates; } + + static MsgReportAirspy* create(const std::vector& sampleRates) + { + return new MsgReportAirspy(sampleRates); + } + + protected: + std::vector m_sampleRates; + + MsgReportAirspy(const std::vector& sampleRates) : + Message(), + m_sampleRates(sampleRates) + { } + }; + + AirspyInput(); + virtual ~AirspyInput(); + + virtual bool init(const Message& message); + virtual bool start(int device); + virtual void stop(); + + virtual const QString& getDeviceDescription() const; + virtual int getSampleRate() const; + virtual quint64 getCenterFrequency() const; + const std::vector& getSampleRates() const { return m_sampleRates; } + + virtual bool handleMessage(const Message& message); + +private: + bool applySettings(const Settings& settings, bool force); + struct airspy_device *open_airspy_from_sequence(int sequence); + void setCenterFrequency(quint64 freq); + + QMutex m_mutex; + Settings m_settings; + struct airspy_device* m_dev; + AirspyThread* m_airspyThread; + QString m_deviceDescription; + std::vector m_sampleRates; +}; + +#endif // INCLUDE_AIRSPYINPUT_H diff --git a/plugins/samplesource/airspy/airspyplugin.cpp b/plugins/samplesource/airspy/airspyplugin.cpp new file mode 100644 index 000000000..5651ffef8 --- /dev/null +++ b/plugins/samplesource/airspy/airspyplugin.cpp @@ -0,0 +1,134 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "airspygui.h" +#include "airspyplugin.h" +#include "plugin/pluginapi.h" +#include "util/simpleserializer.h" + +const PluginDescriptor AirspyPlugin::m_pluginDescriptor = { + QString("Airspy Input"), + QString("---"), + QString("(c) Edouard Griffiths, F4EXB"), + QString("https://github.com/f4exb/sdrangel"), + true, + QString("https://github.com/f4exb/sdrangel") +}; + +AirspyPlugin::AirspyPlugin(QObject* parent) : + QObject(parent), + m_pluginAPI(0) +{ +} + +const PluginDescriptor& AirspyPlugin::getPluginDescriptor() const +{ + return m_pluginDescriptor; +} + +void AirspyPlugin::initPlugin(PluginAPI* pluginAPI) +{ + m_pluginAPI = pluginAPI; + m_pluginAPI->registerSampleSource("org.osmocom.sdr.samplesource.airspy", this); +} + +PluginInterface::SampleSourceDevices AirspyPlugin::enumSampleSources() +{ + SampleSourceDevices result; + airspy_read_partid_serialno_t read_partid_serialno; + struct airspy_device *devinfo; + uint32_t serial_msb = 0; + uint32_t serial_lsb = 0; + airspy_error rc; + int i; + + rc = (airspy_error) airspy_init(); + + if (rc != AIRSPY_SUCCESS) + { + qCritical("AirspyPlugin::enumSampleSources: failed to initiate Airspy library: %s", airspy_error_name(rc)); + } + + for (i=0; i < AIRSPY_MAX_DEVICE; i++) + { + rc = (airspy_error) airspy_open(&devinfo); + + if (rc == AIRSPY_SUCCESS) + { + qDebug("AirspyPlugin::enumSampleSources: try to enumerate Airspy device #%d", i); + + rc = (airspy_error) airspy_board_partid_serialno_read(devinfo, &read_partid_serialno); + + if (rc != AIRSPY_SUCCESS) + { + qDebug("AirspyPlugin::enumSampleSources: failed to read serial no: %s", airspy_error_name(rc)); + airspy_close(devinfo); + continue; // next + } + + if ((read_partid_serialno.serial_no[2] != serial_msb) && (read_partid_serialno.serial_no[3] != serial_lsb)) + { + serial_msb = read_partid_serialno.serial_no[2]; + serial_lsb = read_partid_serialno.serial_no[3]; + + QString serial_str = QString::number(serial_msb, 16) + QString::number(serial_lsb, 16); + uint64_t serial_num = (((uint64_t) serial_msb)<<32) + serial_lsb; + QString displayedName(QString("Airspy #%1 0x%2").arg(i).arg(serial_str)); + SimpleSerializer s(1); + s.writeS32(1, i); + s.writeString(2, serial_str); + s.writeU64(3, serial_num); + result.append(SampleSourceDevice(displayedName, "org.osmocom.sdr.samplesource.airspy", s.final())); + qDebug("AirspyPlugin::enumSampleSources: enumerated Airspy device #%d", i); + } + + airspy_close(devinfo); + } + else + { + qDebug("AirspyPlugin::enumSampleSources: enumerated %d Airspy devices %s", i, airspy_error_name(rc)); + break; // finished + } + } + + rc = (airspy_error) airspy_exit(); + qDebug("AirspyPlugin::enumSampleSources: airspy_exit: %s", airspy_error_name(rc)); + + return result; +} + +PluginGUI* AirspyPlugin::createSampleSourcePluginGUI(const QString& sourceName, const QByteArray& address) +{ + if (!m_pluginAPI) + { + return 0; + } + + if(sourceName == "org.osmocom.sdr.samplesource.airspy") + { + AirspyGui* gui = new AirspyGui(m_pluginAPI); + m_pluginAPI->setInputGUI(gui); + return gui; + } + else + { + return 0; + } +} diff --git a/plugins/samplesource/airspy/airspyplugin.h b/plugins/samplesource/airspy/airspyplugin.h new file mode 100644 index 000000000..cada63d88 --- /dev/null +++ b/plugins/samplesource/airspy/airspyplugin.h @@ -0,0 +1,43 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AIRSPYPLUGIN_H +#define INCLUDE_AIRSPYPLUGIN_H + +#include +#include "plugin/plugininterface.h" + +class AirspyPlugin : public QObject, public PluginInterface { + Q_OBJECT + Q_INTERFACES(PluginInterface) + Q_PLUGIN_METADATA(IID "org.osmocom.sdr.samplesource.airspy") + +public: + explicit AirspyPlugin(QObject* parent = NULL); + + const PluginDescriptor& getPluginDescriptor() const; + void initPlugin(PluginAPI* pluginAPI); + + SampleSourceDevices enumSampleSources(); + PluginGUI* createSampleSourcePluginGUI(const QString& sourceName, const QByteArray& address); + +private: + static const PluginDescriptor m_pluginDescriptor; + + PluginAPI* m_pluginAPI; +}; + +#endif // INCLUDE_AIRSPYPLUGIN_H diff --git a/plugins/samplesource/airspy/airspyserializer.cpp b/plugins/samplesource/airspy/airspyserializer.cpp new file mode 100644 index 000000000..9b29d2a04 --- /dev/null +++ b/plugins/samplesource/airspy/airspyserializer.cpp @@ -0,0 +1,86 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include "airspyserializer.h" + +void AirspySerializer::writeSerializedData(const AirspyData& data, QByteArray& serializedData) +{ + QByteArray sampleSourceSerialized; + SampleSourceSerializer::writeSerializedData(data.m_data, sampleSourceSerialized); + + SimpleSerializer s(1); + + s.writeBlob(1, sampleSourceSerialized); + s.writeS32(2, data.m_LOppmTenths); + s.writeU32(3, data.m_sampleRateIndex); + s.writeU32(4, data.m_log2Decim); + s.writeS32(5, data.m_fcPos); + s.writeU32(6, data.m_lnaGain); + s.writeU32(7, data.m_mixerGain); + s.writeU32(8, data.m_vgaGain); + s.writeBool(9, data.m_biasT); + + serializedData = s.final(); +} + +bool AirspySerializer::readSerializedData(const QByteArray& serializedData, AirspyData& data) +{ + bool valid = SampleSourceSerializer::readSerializedData(serializedData, data.m_data); + + QByteArray sampleSourceSerialized; + + SimpleDeserializer d(serializedData); + + if (!d.isValid()) + { + setDefaults(data); + return false; + } + + if (d.getVersion() == SampleSourceSerializer::getSerializerVersion()) + { + int intval; + + d.readBlob(1, &sampleSourceSerialized); + d.readS32(2, &data.m_LOppmTenths, 0); + d.readU32(3, &data.m_sampleRateIndex, 0); + d.readU32(4, &data.m_log2Decim, 0); + d.readS32(5, &data.m_fcPos, 0); + d.readU32(6, &data.m_lnaGain, 1); + d.readU32(7, &data.m_mixerGain, 5); + d.readU32(8, &data.m_vgaGain, 5); + d.readBool(9, &data.m_biasT, false); + + return SampleSourceSerializer::readSerializedData(sampleSourceSerialized, data.m_data); + } + else + { + setDefaults(data); + return false; + } +} + +void AirspySerializer::setDefaults(AirspyData& data) +{ + data.m_LOppmTenths = 0; + data.m_sampleRateIndex = 0; + data.m_log2Decim = 0; + data.m_fcPos = 0; + data.m_lnaGain = 1; + data.m_mixerGain = 5; + data.m_vgaGain = 5; + data.m_biasT = false; +} diff --git a/plugins/samplesource/airspy/airspyserializer.h b/plugins/samplesource/airspy/airspyserializer.h new file mode 100644 index 000000000..eb6c1c8a2 --- /dev/null +++ b/plugins/samplesource/airspy/airspyserializer.h @@ -0,0 +1,44 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_SAMPLESOURCE_AIRSPY_AIRSPYSERIALIZER_H_ +#define PLUGINS_SAMPLESOURCE_AIRSPY_AIRSPYSERIALIZER_H_ + +#include "util/samplesourceserializer.h" + +class AirspySerializer +{ +public: + struct AirspyData + { + SampleSourceSerializer::Data m_data; + qint32 m_LOppmTenths; + quint32 m_sampleRateIndex; + quint32 m_log2Decim; + qint32 m_fcPos; + quint32 m_lnaGain; + quint32 m_mixerGain; + quint32 m_vgaGain; + bool m_biasT; + }; + + static void writeSerializedData(const AirspyData& data, QByteArray& serializedData); + static bool readSerializedData(const QByteArray& serializedData, AirspyData& data); + static void setDefaults(AirspyData& data); +}; + + +#endif /* PLUGINS_SAMPLESOURCE_AIRSPY_AIRSPYSERIALIZER_H_ */ diff --git a/plugins/samplesource/airspy/airspythread.cpp b/plugins/samplesource/airspy/airspythread.cpp new file mode 100644 index 000000000..b516f5bcd --- /dev/null +++ b/plugins/samplesource/airspy/airspythread.cpp @@ -0,0 +1,210 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "airspythread.h" +#include "dsp/samplefifo.h" + +AirspyThread *AirspyThread::m_this = 0; + +AirspyThread::AirspyThread(struct airspy_device* dev, SampleFifo* sampleFifo, QObject* parent) : + QThread(parent), + m_running(false), + m_dev(dev), + m_convertBuffer(AIRSPY_BLOCKSIZE), + m_sampleFifo(sampleFifo), + m_samplerate(10), + m_log2Decim(0), + m_fcPos(0) +{ + m_this = this; +} + +AirspyThread::~AirspyThread() +{ + stopWork(); + m_this = 0; +} + +void AirspyThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void AirspyThread::stopWork() +{ + qDebug("AirspyThread::stopWork"); + m_running = false; + wait(); +} + +void AirspyThread::setSamplerate(uint32_t samplerate) +{ + m_samplerate = samplerate; +} + +void AirspyThread::setLog2Decimation(unsigned int log2_decim) +{ + m_log2Decim = log2_decim; +} + +void AirspyThread::setFcPos(int fcPos) +{ + m_fcPos = fcPos; +} + +void AirspyThread::run() +{ + airspy_error rc; + + m_running = true; + m_startWaiter.wakeAll(); + + rc = (airspy_error) airspy_start_rx(m_dev, rx_callback, NULL); + + if (rc != AIRSPY_SUCCESS) + { + qCritical("AirspyInput::run: failed to start Airspy Rx: %s", airspy_error_name(rc)); + } + else + { + while ((m_running) && (airspy_is_streaming(m_dev) == AIRSPY_TRUE)) + { + sleep(1); + } + } + + rc = (airspy_error) airspy_stop_rx(m_dev); + + if (rc == AIRSPY_SUCCESS) + { + qDebug("AirspyInput::run: stopped Airspy Rx"); + } + else + { + qDebug("AirspyInput::run: failed to stop Airspy Rx: %s", airspy_error_name(rc)); + } + + m_running = false; +} + +// Decimate according to specified log2 (ex: log2=4 => decim=16) +void AirspyThread::callback(const qint16* buf, qint32 len) +{ + SampleVector::iterator it = m_convertBuffer.begin(); + + if (m_log2Decim == 0) + { + m_decimators.decimate1(&it, buf, len); + } + else + { + if (m_fcPos == 0) // Infra + { + switch (m_log2Decim) + { + case 1: + m_decimators.decimate2_inf(&it, buf, len); + break; + case 2: + m_decimators.decimate4_inf(&it, buf, len); + break; + case 3: + m_decimators.decimate8_inf(&it, buf, len); + break; + case 4: + m_decimators.decimate16_inf(&it, buf, len); + break; + case 5: + m_decimators.decimate32_inf(&it, buf, len); + break; + case 6: + m_decimators.decimate64_inf(&it, buf, len); + break; + default: + break; + } + } + else if (m_fcPos == 1) // Supra + { + switch (m_log2Decim) + { + case 1: + m_decimators.decimate2_sup(&it, buf, len); + break; + case 2: + m_decimators.decimate4_sup(&it, buf, len); + break; + case 3: + m_decimators.decimate8_sup(&it, buf, len); + break; + case 4: + m_decimators.decimate16_sup(&it, buf, len); + break; + case 5: + m_decimators.decimate32_sup(&it, buf, len); + break; + case 6: + m_decimators.decimate64_sup(&it, buf, len); + break; + default: + break; + } + } + else if (m_fcPos == 2) // Center + { + switch (m_log2Decim) + { + case 1: + m_decimators.decimate2_cen(&it, buf, len); + break; + case 2: + m_decimators.decimate4_cen(&it, buf, len); + break; + case 3: + m_decimators.decimate8_cen(&it, buf, len); + break; + case 4: + m_decimators.decimate16_cen(&it, buf, len); + break; + case 5: + m_decimators.decimate32_cen(&it, buf, len); + break; + case 6: + m_decimators.decimate64_cen(&it, buf, len); + break; + default: + break; + } + } + } + + m_sampleFifo->write(m_convertBuffer.begin(), it); +} + + +int AirspyThread::rx_callback(airspy_transfer_t* transfer) +{ + qint32 bytes_to_write = transfer->sample_count * sizeof(qint16); + m_this->callback((qint16 *) transfer->samples, bytes_to_write); + return 0; +} diff --git a/plugins/samplesource/airspy/airspythread.h b/plugins/samplesource/airspy/airspythread.h new file mode 100644 index 000000000..29097bf39 --- /dev/null +++ b/plugins/samplesource/airspy/airspythread.h @@ -0,0 +1,64 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2015 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDE_AIRSPYTHREAD_H +#define INCLUDE_AIRSPYTHREAD_H + +#include +#include +#include +#include +#include "dsp/samplefifo.h" +#include "dsp/decimators.h" + +#define AIRSPY_BLOCKSIZE (1<<17) + +class AirspyThread : public QThread { + Q_OBJECT + +public: + AirspyThread(struct airspy_device* dev, SampleFifo* sampleFifo, QObject* parent = NULL); + ~AirspyThread(); + + void startWork(); + void stopWork(); + void setSamplerate(uint32_t samplerate); + void setLog2Decimation(unsigned int log2_decim); + void setFcPos(int fcPos); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + struct airspy_device* m_dev; + qint16 m_buf[2*AIRSPY_BLOCKSIZE]; + SampleVector m_convertBuffer; + SampleFifo* m_sampleFifo; + + int m_samplerate; + unsigned int m_log2Decim; + int m_fcPos; + static AirspyThread *m_this; + + Decimators m_decimators; + + void run(); + void callback(const qint16* buf, qint32 len); + static int rx_callback(airspy_transfer_t* transfer); +}; + +#endif // INCLUDE_AIRSPYTHREAD_H