diff --git a/plugins/samplesource/airspy/CMakeLists.txt b/plugins/samplesource/airspy/CMakeLists.txt new file mode 100644 index 000000000..d3161d347 --- /dev/null +++ b/plugins/samplesource/airspy/CMakeLists.txt @@ -0,0 +1,52 @@ +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(-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..8d5066a09 --- /dev/null +++ b/plugins/samplesource/airspy/airspygui.cpp @@ -0,0 +1,386 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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, BLADERF_FREQUENCY_MIN_XB200/1000, BLADERF_FREQUENCY_MAX/1000); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateHardware())); + displaySettings(); + + m_sampleSource = new AirspyInput(); + 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)) + { + displaySettings(); + return true; + } + else + { + return false; + } +} + +void AirspyGui::displaySettings() +{ + ui->centerFrequency->setValue(m_settings.m_centerFrequency / 1000); + + ui->samplerateText->setText(tr("%1k").arg(m_settings.m_devSampleRate / 1000)); + unsigned int sampleRateIndex = AirspySampleRates::getRateIndex(m_settings.m_devSampleRate); + ui->samplerate->setValue(sampleRateIndex); + + ui->bandwidthText->setText(tr("%1k").arg(m_settings.m_bandwidth / 1000)); + unsigned int bandwidthIndex = AirspyBandwidths::getBandwidthIndex(m_settings.m_bandwidth); + ui->bandwidth->setValue(bandwidthIndex); + + 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*3)); + ui->lna->setValue(m_settings.m_lnaGain); + + ui->vga1Text->setText(tr("%1dB").arg(m_settings.m_vga1)); + ui->vga1->setValue(m_settings.m_vga1); + + ui->vga2Text->setText(tr("%1dB").arg(m_settings.m_vga2)); + ui->vga2->setValue(m_settings.m_vga2); + + ui->xb200->setCurrentIndex(getXb200Index(m_settings.m_xb200, m_settings.m_xb200Path, m_settings.m_xb200Filter)); +} + +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_samplerate_valueChanged(int value) +{ + int newrate = AirspySampleRates::getRate(value); + ui->samplerateText->setText(tr("%1k").arg(newrate)); + m_settings.m_devSampleRate = newrate * 1000; + sendSettings(); +} + +void AirspyGui::on_bandwidth_valueChanged(int value) +{ + int newbw = AirspyBandwidths::getBandwidth(value); + ui->bandwidthText->setText(tr("%1k").arg(newbw)); + m_settings.m_bandwidth = newbw * 1000; + sendSettings(); +} + +void AirspyGui::on_decim_valueChanged(int value) +{ + if ((value <0) || (value > 5)) + return; + ui->decimText->setText(tr("%1").arg(1< BLADERF_RXVGA1_GAIN_MAX)) + return; + + ui->vga1Text->setText(tr("%1dB").arg(value)); + m_settings.m_vga1 = value; + sendSettings(); +} + +void AirspyGui::on_vga2_valueChanged(int value) +{ + if ((value < BLADERF_RXVGA2_GAIN_MIN) || (value > BLADERF_RXVGA2_GAIN_MAX)) + return; + + ui->vga2Text->setText(tr("%1dB").arg(value)); + m_settings.m_vga2 = value; + sendSettings(); +} + +void AirspyGui::on_xb200_currentIndexChanged(int index) +{ + if (index == 1) // bypass + { + m_settings.m_xb200 = true; + m_settings.m_xb200Path = BLADERF_XB200_BYPASS; + } + else if (index == 2) // Auto 1dB + { + m_settings.m_xb200 = true; + m_settings.m_xb200Path = BLADERF_XB200_MIX; + m_settings.m_xb200Filter = BLADERF_XB200_AUTO_1DB; + } + else if (index == 3) // Auto 3dB + { + m_settings.m_xb200 = true; + m_settings.m_xb200Path = BLADERF_XB200_MIX; + m_settings.m_xb200Filter = BLADERF_XB200_AUTO_3DB; + } + else if (index == 4) // Custom + { + m_settings.m_xb200 = true; + m_settings.m_xb200Path = BLADERF_XB200_MIX; + m_settings.m_xb200Filter = BLADERF_XB200_CUSTOM; + } + else if (index == 5) // 50 MHz + { + m_settings.m_xb200 = true; + m_settings.m_xb200Path = BLADERF_XB200_MIX; + m_settings.m_xb200Filter = BLADERF_XB200_50M; + } + else if (index == 6) // 144 MHz + { + m_settings.m_xb200 = true; + m_settings.m_xb200Path = BLADERF_XB200_MIX; + m_settings.m_xb200Filter = BLADERF_XB200_144M; + } + else if (index == 7) // 222 MHz + { + m_settings.m_xb200 = true; + m_settings.m_xb200Path = BLADERF_XB200_MIX; + m_settings.m_xb200Filter = BLADERF_XB200_222M; + } + else // no xb200 + { + m_settings.m_xb200 = false; + } + + if (m_settings.m_xb200) + { + ui->centerFrequency->setValueRange(7, BLADERF_FREQUENCY_MIN_XB200/1000, BLADERF_FREQUENCY_MAX/1000); + } + else + { + ui->centerFrequency->setValueRange(7, BLADERF_FREQUENCY_MIN/1000, BLADERF_FREQUENCY_MAX/1000); + } + + sendSettings(); +} + +void AirspyGui::updateHardware() +{ + qDebug() << "AirspyGui::updateHardware"; + AirspyInput::MsgConfigureAirspy* message = AirspyInput::MsgConfigureAirspy::create( m_settings); + m_sampleSource->getInputMessageQueue()->push(message); + m_updateTimer.stop(); +} + +unsigned int AirspyGui::getXb200Index(bool xb_200, bladerf_xb200_path xb200Path, bladerf_xb200_filter xb200Filter) +{ + if (xb_200) + { + if (xb200Path == BLADERF_XB200_BYPASS) + { + return 1; + } + else + { + if (xb200Filter == BLADERF_XB200_AUTO_1DB) + { + return 2; + } + else if (xb200Filter == BLADERF_XB200_AUTO_3DB) + { + return 3; + } + else if (xb200Filter == BLADERF_XB200_CUSTOM) + { + return 4; + } + else if (xb200Filter == BLADERF_XB200_50M) + { + return 5; + } + else if (xb200Filter == BLADERF_XB200_144M) + { + return 6; + } + else if (xb200Filter == BLADERF_XB200_222M) + { + return 7; + } + else + { + return 0; + } + } + } + else + { + return 0; + } +} + +unsigned int AirspySampleRates::m_rates[] = {2500, 10000}; +unsigned int AirspySampleRates::m_nb_rates = 2; + +unsigned int AirspySampleRates::getRate(unsigned int rate_index) +{ + if (rate_index < m_nb_rates) + { + return m_rates[rate_index]; + } + else + { + return m_rates[0]; + } +} + +unsigned int AirspySampleRates::getRateIndex(unsigned int rate) +{ + for (unsigned int i=0; i < m_nb_rates; i++) + { + if (rate/1000 == m_rates[i]) + { + return i; + } + } + + return 0; +} + +unsigned int AirspyBandwidths::m_halfbw[] = {}; +unsigned int AirspyBandwidths::m_nb_halfbw = 0; + +unsigned int AirspyBandwidths::getBandwidth(unsigned int bandwidth_index) +{ + if (bandwidth_index < m_nb_halfbw) + { + return m_halfbw[bandwidth_index] * 2; + } + else + { + return m_halfbw[0] * 2; + } +} + +unsigned int AirspyBandwidths::getBandwidthIndex(unsigned int bandwidth) +{ + for (unsigned int i=0; i < m_nb_halfbw; i++) + { + if (bandwidth/2000 == m_halfbw[i]) + { + return i; + } + } + + return 0; +} diff --git a/plugins/samplesource/airspy/airspygui.h b/plugins/samplesource/airspy/airspygui.h new file mode 100644 index 000000000..a39d1aeb2 --- /dev/null +++ b/plugins/samplesource/airspy/airspygui.h @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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); + +private: + Ui::AirspyGui* ui; + + PluginAPI* m_pluginAPI; + AirspyInput::Settings m_settings; + QTimer m_updateTimer; + std::vector m_gains; + SampleSource* m_sampleSource; + + void displaySettings(); + void sendSettings(); + unsigned int getXb200Index(bool xb_200, bladerf_xb200_path xb200Path, bladerf_xb200_filter xb200Filter); + +private slots: + void on_centerFrequency_changed(quint64 value); + void on_samplerate_valueChanged(int value); + void on_bandwidth_valueChanged(int value); + void on_decim_valueChanged(int value); + void on_lna_valueChanged(int value); + void on_vga1_valueChanged(int value); + void on_vga2_valueChanged(int value); + void on_xb200_currentIndexChanged(int index); + void on_fcPos_currentIndexChanged(int index); + void updateHardware(); +}; + +class AirspySampleRates { +public: + static unsigned int getRate(unsigned int rate_index); + static unsigned int getRateIndex(unsigned int rate); +private: + static unsigned int m_rates[14]; + static unsigned int m_nb_rates; +}; + +class AirspyBandwidths { +public: + static unsigned int getBandwidth(unsigned int bandwidth_index); + static unsigned int getBandwidthIndex(unsigned int bandwidth); +private: + static unsigned int m_halfbw[16]; + static unsigned int m_nb_halfbw; +}; + +#endif // INCLUDE_AIRSPYGUI_H diff --git a/plugins/samplesource/airspy/airspygui.ui b/plugins/samplesource/airspy/airspygui.ui new file mode 100644 index 000000000..8659ba029 --- /dev/null +++ b/plugins/samplesource/airspy/airspygui.ui @@ -0,0 +1,505 @@ + + + BladerfGui + + + + 0 + 0 + 198 + 239 + + + + + 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 + + + + + Device Samplerate + + + 1 + + + 1 + + + 0 + + + 0 + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + Rate + + + + + + + + 40 + 0 + + + + --- + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 3 + + + + + Dec. + + + + + + + 5 + + + 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 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 3 + + + + + Mixer gain dB + + + 0 + + + 15 + + + 1 + + + 15 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Mixer + + + + + + + + + 3 + + + + + VGA + + + + + + + VGA gain dB + + + 15 + + + 1 + + + 1 + + + 9 + + + Qt::Horizontal + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + Bias Tee + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 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..f1e5581bf --- /dev/null +++ b/plugins/samplesource/airspy/airspyinput.cpp @@ -0,0 +1,429 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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_devSampleRate(2500000), + 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_devSampleRate = 2500000; + 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_sampleRate = m_devSampleRate; + data.m_log2Decim = m_log2Decim; + data.m_fcPos = 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_devSampleRate = data.m_sampleRate; + m_log2Decim = data.m_log2Decim; + m_fcPos = 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") +{ +} + +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_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) // TODO: fix; Open first available device as there is no proper handling for multiple devices + { + qCritical("AirspyInput::start: could not open Airspy"); + return false; + } + +#ifdef LIBAIRSPY_OLD + m_sampleRates.push_back(2500000); + m_sampleRates.push_back(10000000) +#else + 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); + + for (int i=0; ipush(message); + + if((m_airspyThread = new AirspyThread(m_dev, &m_sampleFifo)) == NULL) { + qFatal("out of memory"); + goto failed; + } + + m_airspyThread->startWork(); + + mutexLocker.unlock(); + applySettings(m_settings, true); + + qDebug("AirspyInput::startInput: started"); + + return true; +} + +void 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_settings.m_devSampleRate; + return (rate / (1<setSamplerate(m_settings.m_devSampleRate); + } + } + } + + 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; + } + } + + if (m_settings.m_centerFrequency != settings.m_centerFrequency) + { + forwardChange = true; + } + + m_settings.m_centerFrequency = settings.m_centerFrequency; + + qint64 deviceCenterFrequency = m_settings.m_centerFrequency; + qint64 f_img = deviceCenterFrequency; + qint64 f_cut = deviceCenterFrequency + m_settings.m_bandwidth/2; + + if ((m_settings.m_log2Decim == 0) || (m_settings.m_fcPos == FC_POS_CENTER)) + { + deviceCenterFrequency = m_settings.m_centerFrequency; + f_img = deviceCenterFrequency; + f_cut = deviceCenterFrequency + m_settings.m_bandwidth/2; + } + else + { + if (m_settings.m_fcPos == FC_POS_INFRA) + { + deviceCenterFrequency = m_settings.m_centerFrequency + (m_settings.m_devSampleRate / 4); + f_img = deviceCenterFrequency + m_settings.m_devSampleRate/2; + f_cut = deviceCenterFrequency + m_settings.m_bandwidth/2; + } + else if (m_settings.m_fcPos == FC_POS_SUPRA) + { + deviceCenterFrequency = m_settings.m_centerFrequency - (m_settings.m_devSampleRate / 4); + f_img = deviceCenterFrequency - m_settings.m_devSampleRate/2; + f_cut = deviceCenterFrequency - m_settings.m_bandwidth/2; + } + } + + if (m_dev != NULL) + { + if (bladerf_set_frequency( m_dev, BLADERF_MODULE_RX, deviceCenterFrequency ) != 0) + { + qDebug("bladerf_set_frequency(%lld) failed", m_settings.m_centerFrequency); + } + } + + if (forwardChange) + { + int sampleRate = m_settings.m_devSampleRate/(1<push(notif); + } + + qDebug() << "AirspyInput::applySettings: center freq: " << m_settings.m_centerFrequency << " Hz" + << " device center freq: " << deviceCenterFrequency << " Hz" + << " device sample rate: " << m_settings.m_devSampleRate << "Hz" + << " Actual sample rate: " << m_settings.m_devSampleRate/(1<. // +/////////////////////////////////////////////////////////////////////////////////// + +#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; + int m_devSampleRate; + 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; + + virtual bool handleMessage(const Message& message); + +private: + bool applySettings(const Settings& settings, bool force); + struct airspy_device *open_airspy_from_sequence(int sequence); + + 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..a5cd2cf0c --- /dev/null +++ b/plugins/samplesource/airspy/airspyplugin.cpp @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#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 = 0; + airspy_error rc; + + rc = airspy_init(); + + if (rc != AIRSPY_SUCCESS) + { + qCritical() << "AirspyPlugin::enumSampleSources: failed to initiate Airspy library " + << airspy_error_name(rc) << std::endl; + } + + for (int i=0; i < AIRSPY_MAX_DEVICE; i++) + { + rc = airspy_open(&devinfo); + + if (rc == AIRSPY_SUCCESS) + { + rc = airspy_board_partid_serialno_read(devinfo, &read_partid_serialno); + + if (rc != AIRSPY_SUCCESS) + { + continue; // next + } + + QString serial_str = QString::number(read_partid_serialno.serial_no[2], 16) + QString::number(read_partid_serialno.serial_no[3], 16); + uint64_t serial_num = (read_partid_serialno.serial_no[2] << 32) + read_partid_serialno.serial_no[3]; + QString displayedName(QString("Airspy #%1 0x%2").arg(i+1).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())); + } + else + { + break; // finished + } + } + + airspy_exit(); + + 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..d2a54ef48 --- /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.writeS32(3, data.m_sampleRate); + s.writeU32(4, data.m_log2Decim); + s.writeU32(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.readS32(3, &data.m_sampleRate, 0); + d.readU32(4, &data.m_log2Decim, 0); + d.readU32(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_sampleRate = 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..6c0580fe1 --- /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; + qint32 m_sampleRate; + quint32 m_log2Decim; + quint32 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..a8d16d4b5 --- /dev/null +++ b/plugins/samplesource/airspy/airspythread.cpp @@ -0,0 +1,175 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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(struct bladerf* 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) +{ +} + +AirspyThread::~AirspyThread() +{ + stopWork(); +} + +void AirspyThread::startWork() +{ + m_startWaitMutex.lock(); + start(); + while(!m_running) + m_startWaiter.wait(&m_startWaitMutex, 100); + m_startWaitMutex.unlock(); +} + +void AirspyThread::stopWork() +{ + m_running = false; + wait(); +} + +void AirspyThread::setSamplerate(int 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() +{ + int res; + + m_running = true; + m_startWaiter.wakeAll(); + + while(m_running) { + if((res = bladerf_sync_rx(m_dev, m_buf, AIRSPY_BLOCKSIZE, NULL, 10000)) < 0) { + qCritical("AirspyThread: sync error: %s", strerror(errno)); + break; + } + + callback(m_buf, 2 * AIRSPY_BLOCKSIZE); + } + + 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; + 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; + 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; + default: + break; + } + } + } + + + m_sampleFifo->write(m_convertBuffer.begin(), it); +} diff --git a/plugins/samplesource/airspy/airspythread.h b/plugins/samplesource/airspy/airspythread.h new file mode 100644 index 000000000..8849a928e --- /dev/null +++ b/plugins/samplesource/airspy/airspythread.h @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////////////////// +// 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<<14) + +class AirspyThread : public QThread { + Q_OBJECT + +public: + AirspyThread(struct bladerf* dev, SampleFifo* sampleFifo, QObject* parent = NULL); + ~AirspyThread(); + + void startWork(); + void stopWork(); + void setSamplerate(int samplerate); + void setLog2Decimation(unsigned int log2_decim); + void setFcPos(int fcPos); + +private: + QMutex m_startWaitMutex; + QWaitCondition m_startWaiter; + bool m_running; + + struct bladerf* m_dev; + qint16 m_buf[2*AIRSPY_BLOCKSIZE]; + SampleVector m_convertBuffer; + SampleFifo* m_sampleFifo; + + int m_samplerate; + unsigned int m_log2Decim; + int m_fcPos; + + Decimators m_decimators; + + void run(); + void callback(const qint16* buf, qint32 len); +}; + +#endif // INCLUDE_AIRSPYTHREAD_H