diff --git a/packages/ad936x/CMakeLists.txt b/packages/ad936x/CMakeLists.txt index d2a1435471..251ff7641b 100644 --- a/packages/ad936x/CMakeLists.txt +++ b/packages/ad936x/CMakeLists.txt @@ -32,6 +32,7 @@ message(STATUS "building package: " ${SCOPY_MODULE}) project(scopy-package-${SCOPY_MODULE} VERSION 0.1 LANGUAGES CXX) configure_file(manifest.json.cmakein ${SCOPY_PACKAGE_BUILD_PATH}/${SCOPY_MODULE}/MANIFEST.json @ONLY) +include_emu_xml(${CMAKE_CURRENT_SOURCE_DIR}/emu-xml ${SCOPY_PACKAGE_BUILD_PATH}/${SCOPY_MODULE}/emu-xml) include_resources(${CMAKE_CURRENT_SOURCE_DIR}/resources ${SCOPY_PACKAGE_BUILD_PATH}/${SCOPY_MODULE}/resources) message("Including plugins") if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/plugins) diff --git a/packages/ad936x/emu-xml/emu_setup.json b/packages/ad936x/emu-xml/emu_setup.json new file mode 100644 index 0000000000..432f1910dc --- /dev/null +++ b/packages/ad936x/emu-xml/emu_setup.json @@ -0,0 +1,7 @@ +[ + { + "device": "fmcomms5", + "xml_path": "fmcomms5.xml", + "uri": "ip:127.0.0.1" + } +] diff --git a/packages/ad936x/emu-xml/fmcomms5.xml b/packages/ad936x/emu-xml/fmcomms5.xml new file mode 100644 index 0000000000..29f8101747 --- /dev/null +++ b/packages/ad936x/emu-xml/fmcomms5.xml @@ -0,0 +1,5 @@ +]> diff --git a/packages/ad936x/plugins/ad936x/CMakeLists.txt b/packages/ad936x/plugins/ad936x/CMakeLists.txt index de0df60bd5..81b20aff7f 100644 --- a/packages/ad936x/plugins/ad936x/CMakeLists.txt +++ b/packages/ad936x/plugins/ad936x/CMakeLists.txt @@ -47,8 +47,26 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE) -file(GLOB SRC_LIST src/*.cpp src/*.cc) -file(GLOB HEADER_LIST include/${SCOPY_MODULE}/*.h include/${SCOPY_MODULE}/*.hpp) +file( + GLOB + SRC_LIST + src/*.cpp + src/*.cc + src/ad936x/*.cpp + src/ad936x/*.cc + src/fmcomms5/*.cpp + src/ad936x/*.cc +) +file( + GLOB + HEADER_LIST + include/${SCOPY_MODULE}/*.h + include/${SCOPY_MODULE}/*.hpp + include/${SCOPY_MODULE}/ad936x/*.h + include/${SCOPY_MODULE}/ad936x/*.hpp + include/${SCOPY_MODULE}/fmcomms5/*.h + include/${SCOPY_MODULE}/fmcomms5/*.hpp +) file(GLOB UI_LIST ui/*.ui) set(ENABLE_TESTING ON) @@ -59,6 +77,9 @@ endif() set(PROJECT_SOURCES ${SRC_LIST} ${HEADER_LIST} ${UI_LIST}) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS REQUIRED Widgets Core) +find_library(AD9361_LIBRARIES NAMES ad9361 libad9361 REQUIRED) +find_path(AD9361_INCLUDE_DIRS ad9361.h REQUIRED) + set(AD936X_FILTERS_BUILD_PATH ${CMAKE_CURRENT_BINARY_DIR}/resources/ad936x-filters) file(GLOB FIR_FILTERS ${CMAKE_CURRENT_SOURCE_DIR}/resources/ad936x-filters/*) @@ -92,6 +113,7 @@ target_link_libraries( scopy-iioutil scopy-iio-widgets scopy-pkg-manager + ${AD9361_LIBRARIES} ) set(PLUTO_TARGET_NAME ${PROJECT_NAME} PARENT_SCOPE) diff --git a/packages/ad936x/plugins/ad936x/include/ad936x/ad936x.h b/packages/ad936x/plugins/ad936x/include/ad936x/ad936x/ad936x.h old mode 100644 new mode 100755 similarity index 82% rename from packages/ad936x/plugins/ad936x/include/ad936x/ad936x.h rename to packages/ad936x/plugins/ad936x/include/ad936x/ad936x/ad936x.h index ab0a874ee1..68dc29de9c --- a/packages/ad936x/plugins/ad936x/include/ad936x/ad936x.h +++ b/packages/ad936x/plugins/ad936x/include/ad936x/ad936x/ad936x.h @@ -30,9 +30,11 @@ #include #include +#include namespace scopy { namespace ad936x { + class SCOPY_AD936X_EXPORT AD936X : public QWidget { Q_OBJECT @@ -51,13 +53,10 @@ class SCOPY_AD936X_EXPORT AD936X : public QWidget QWidget *m_blockDiagramWidget; AnimatedRefreshBtn *m_refreshButton; - QWidget *generateGlobalSettingsWidget(QWidget *parent); - - QWidget *generateRxChainWidget(QWidget *parent); - QWidget *generateRxWidget(iio_channel *chn, QString title, QWidget *parent); + QWidget *generateRxChainWidget(iio_device *dev, QString title, QWidget *parent); + QWidget *generateTxChainWidget(iio_device *dev, QString title, QWidget *parent); - QWidget *generateTxChainWidget(QWidget *parent); - QWidget *generateTxWidget(iio_channel *chn, QString title, QWidget *parent); + AD936xHelper *m_helper; }; } // namespace ad936x } // namespace scopy diff --git a/packages/ad936x/plugins/ad936x/include/ad936x/ad963xadvanced.h b/packages/ad936x/plugins/ad936x/include/ad936x/ad936x/ad963xadvanced.h old mode 100644 new mode 100755 similarity index 100% rename from packages/ad936x/plugins/ad936x/include/ad936x/ad963xadvanced.h rename to packages/ad936x/plugins/ad936x/include/ad936x/ad936x/ad963xadvanced.h diff --git a/packages/ad936x/plugins/ad936x/include/ad936x/ad936xhelper.h b/packages/ad936x/plugins/ad936x/include/ad936x/ad936xhelper.h new file mode 100644 index 0000000000..369c49c51a --- /dev/null +++ b/packages/ad936x/plugins/ad936x/include/ad936x/ad936xhelper.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef AD936X_HELPER_H +#define AD936X_HELPER_H + +#include "scopy-ad936x_export.h" +#include +#include + +namespace scopy { +namespace ad936x { + +class SCOPY_AD936X_EXPORT AD936xHelper : public QWidget +{ + Q_OBJECT +public: + AD936xHelper(QWidget *parent = nullptr); + + QWidget *generateGlobalSettingsWidget(iio_device *dev, QString title, QWidget *parent); + + QWidget *generateRxDeviceWidget(iio_device *dev, QString title, QWidget *parent); + QWidget *generateRxChannelWidget(iio_channel *chn, QString title, QWidget *parent); + + QWidget *generateTxDeviceWidget(iio_device *dev, QString title, QWidget *parent); + QWidget *generateTxChannelWidget(iio_channel *chn, QString title, QWidget *parent); + +Q_SIGNALS: + void readRequested(); +}; +} // namespace ad936x +} // namespace scopy +#endif // AD936X_HELPER_H diff --git a/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5.h b/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5.h new file mode 100644 index 0000000000..520268502f --- /dev/null +++ b/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef FMCOMMS5_H +#define FMCOMMS5_H + +#include "scopy-ad936x_export.h" +#include +#include +#include + +#include + +#include +#include + +namespace scopy { +namespace ad936x { +class SCOPY_AD936X_EXPORT FMCOMMS5 : public QWidget +{ + Q_OBJECT +public: + explicit FMCOMMS5(iio_context *ctx, QWidget *parent = nullptr); + ~FMCOMMS5(); + +Q_SIGNALS: + void readRequested(); + +private: + iio_context *m_ctx = nullptr; + ToolTemplate *m_tool; + QVBoxLayout *m_mainLayout; + QWidget *m_controlsWidget; + QWidget *m_blockDiagramWidget; + AnimatedRefreshBtn *m_refreshButton; + + QWidget *generateRxChainWidget(iio_device *dev, QString title, QWidget *parent); + QWidget *generateTxChainWidget(iio_device *dev, QString title, QWidget *parent); + + AD936xHelper *m_helper; +}; +} // namespace ad936x +} // namespace scopy +#endif // FMCOMMS5_H diff --git a/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5advanced.h b/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5advanced.h new file mode 100644 index 0000000000..dd6625bad7 --- /dev/null +++ b/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5advanced.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef FMCOMMS5ADVANCED_H +#define FMCOMMS5ADVANCED_H + +#include "scopy-ad936x_export.h" +#include +#include +#include +#include + +#include "auxadcdaciowidget.h" +#include "elnawidget.h" +#include "ensmmodeclockswidget.h" +#include "gainwidget.h" +#include "rssiwidget.h" +#include "txmonitorwidget.h" +#include "miscwidget.h" +#include "bistwidget.h" +#include "fmcomms5/fmcomms5tab.h" + +namespace scopy { +namespace ad936x { +class SCOPY_AD936X_EXPORT Fmcomms5Advanced : public QWidget +{ + Q_OBJECT +public: + explicit Fmcomms5Advanced(iio_context *ctx, QWidget *parent = nullptr); + ~Fmcomms5Advanced(); + +Q_SIGNALS: + void readRequested(); + +private: + void init(); + + QPushButton *m_ensmModeClocksBtn = nullptr; + QPushButton *m_eLnaBtn = nullptr; + QPushButton *m_rssiBtn = nullptr; + QPushButton *m_gainBtn = nullptr; + QPushButton *m_txMonitorBtn = nullptr; + QPushButton *m_auxAdcDacIioBtn = nullptr; + QPushButton *m_miscBtn = nullptr; + QPushButton *m_bistBtn = nullptr; + QPushButton *m_fmcomms5Btn = nullptr; + + QPushButton *m_syncBtn = nullptr; + void ad9361MultichipSync(); + + iio_context *m_ctx = nullptr; + ToolTemplate *m_tool; + QVBoxLayout *m_mainLayout; + AnimatedRefreshBtn *m_refreshButton; + + EnsmModeClocksWidget *m_ensmModeClocks; + ElnaWidget *m_elna; + RssiWidget *m_rssi; + GainWidget *m_gainWidget; + TxMonitorWidget *m_txMonitor; + AuxAdcDacIoWidget *m_auxAdcDacIo; + MiscWidget *m_misc; + BistWidget *m_bist; + Fmcomms5Tab *m_fmcomms5; + + iio_device *m_mainDevice = nullptr; + iio_device *m_secondDevice = nullptr; + QStackedWidget *m_centralWidget = nullptr; + + bool m_isToolInitialized; + void showEvent(QShowEvent *event) override; +}; + +} // namespace ad936x +} // namespace scopy +#endif // FMCOMMS5ADVANCED_H diff --git a/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5calibration.h b/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5calibration.h new file mode 100644 index 0000000000..8122138642 --- /dev/null +++ b/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5calibration.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef FMCOMMS5CALIBRATION_H +#define FMCOMMS5CALIBRATION_H + +#include +#include + +namespace scopy { +namespace ad936x { + +struct MarkerResult +{ + double magnitude; + double offset; + int chA; + int chB; +}; + +// used devices +#define CAP_DEVICE "cf-ad9361-lpc" +#define CAP_DEVICE_ALT "cf-ad9361-A" +#define CAP_SLAVE_DEVICE "cf-ad9361-B" +#define DDS_DEVICE "cf-ad9361-dds-core-lpc" +#define DDS_SLAVE_DEVICE "cf-ad9361-dds-core-B" + +/* 1MHZ tone */ +#define CAL_TONE 1000000 +#define CAL_SCALE 0.12500 +#define MARKER_AVG 3 + +class Fmcomms5Calibration : public QObject +{ + Q_OBJECT +public: + explicit Fmcomms5Calibration(iio_context *ctx, QObject *parent = nullptr); + + void calibrate(); + void resetCalibration(); + void callSwitchPortsEnableCb(int val); + +Q_SIGNALS: + void calibrationFailed(); + void updateCalibrationProgress(double progress); + +private: + iio_context *m_ctx; + iio_device *m_mainDevice = nullptr; + iio_device *m_secondDevice = nullptr; + iio_device *m_cf_ad9361_lpc = nullptr; + iio_device *m_cf_ad9361_hpc = nullptr; + iio_device *m_ddsMain = nullptr; + iio_device *m_ddsSecond = nullptr; + + const char *ddsChannelNames[8] = {"altvoltage0", "altvoltage1", "altvoltage2", "altvoltage3", + "altvoltage4", "altvoltage5", "altvoltage6", "altvoltage7"}; + + void calibrationFail(int ret); + + double tuneTrxPhaseOffset(iio_device *ldev, int *ret, long long cal_freq, long long cal_tone, double sign, + std::function tune); + + void getMarkers(double *offset, double *mag); + std::vector getMarkersFromCrossCorrelation(); + double calcPhaseOffset(double fsample, double dds_freq, double offset, double mag); + double scalePhase0360(double val); + + int defaultDds(long long freq, double scale); + void ddsTxPhaseRotation(struct iio_device *dev, double val); + void trxPhaseRottation(iio_device *dev, double val); + unsigned int getCalTone(); + + int getCalSamples(long long calTone, long long calFreq); + + void nearEndLoopbackCtrl(unsigned channel, bool enable); +}; +} // namespace ad936x +} // namespace scopy +#endif // FMCOMMS5CALIBRATION_H diff --git a/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5tab.h b/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5tab.h new file mode 100644 index 0000000000..3f31f3b1e2 --- /dev/null +++ b/packages/ad936x/plugins/ad936x/include/ad936x/fmcomms5/fmcomms5tab.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef FMCOMMS5TAB_H +#define FMCOMMS5TAB_H + +#include +#include +#include +#include +#include +#include + +namespace scopy { +namespace ad936x { +class Fmcomms5Tab : public QWidget +{ + Q_OBJECT +public: + explicit Fmcomms5Tab(iio_context *ctx, QWidget *parent = nullptr); + ~Fmcomms5Tab(); + +Q_SIGNALS: + void readRequested(); + +private: + iio_context *m_ctx; + QVBoxLayout *m_layout; + QProgressBar *m_calibProgressBar; + QPushButton *m_calibrateBtn; + QPushButton *m_resetCalibrationBtn; +}; +} // namespace ad936x +} // namespace scopy +#endif // FMCOMMS5TAB_H diff --git a/packages/ad936x/plugins/ad936x/resources/AD_FMCOMMS5_EBZ.jpg b/packages/ad936x/plugins/ad936x/resources/AD_FMCOMMS5_EBZ.jpg new file mode 100644 index 0000000000..a14918e1f8 Binary files /dev/null and b/packages/ad936x/plugins/ad936x/resources/AD_FMCOMMS5_EBZ.jpg differ diff --git a/packages/ad936x/plugins/ad936x/resources/resources.qrc b/packages/ad936x/plugins/ad936x/resources/resources.qrc index 2d8d7fe7ee..72f819f2b4 100644 --- a/packages/ad936x/plugins/ad936x/resources/resources.qrc +++ b/packages/ad936x/plugins/ad936x/resources/resources.qrc @@ -5,5 +5,6 @@ ad936x-filters/AD936x_LP_256kHz_768kSPS.ftr ad936x-filters/AD936x_LP_333kHz_1MSPS.ftr ad936x-filters/AD936x_LP_666kHz_2MSPS.ftr + AD_FMCOMMS5_EBZ.jpg diff --git a/packages/ad936x/plugins/ad936x/src/ad936x.cpp b/packages/ad936x/plugins/ad936x/src/ad936x.cpp deleted file mode 100644 index e1fcf5db00..0000000000 --- a/packages/ad936x/plugins/ad936x/src/ad936x.cpp +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Copyright (c) 2025 Analog Devices Inc. - * - * This file is part of Scopy - * (see https://www.github.com/analogdevicesinc/scopy). - * - * 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, either version 3 of the License, or - * (at your option) any later version. - * - * 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 for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include "ad936x.h" - -#include "fastlockprofileswidget.h" -#include "firfilterqwidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -Q_LOGGING_CATEGORY(CAT_AD936X, "AD936X"); - -using namespace scopy; -using namespace ad936x; - -AD936X::AD936X(iio_context *ctx, QWidget *parent) - : QWidget(parent) - , m_ctx(ctx) -{ - - m_mainLayout = new QVBoxLayout(this); - m_mainLayout->setMargin(0); - m_mainLayout->setContentsMargins(0, 0, 0, 0); - - m_tool = new ToolTemplate(this); - m_tool->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - m_tool->topContainer()->setVisible(true); - m_tool->topContainerMenuControl()->setVisible(false); - - m_mainLayout->addWidget(m_tool); - - m_refreshButton = new AnimatedRefreshBtn(false, this); - m_tool->addWidgetToTopContainerHelper(m_refreshButton, TTA_RIGHT); - - connect(m_refreshButton, &QPushButton::clicked, this, [this]() { - m_refreshButton->startAnimation(); - - QFutureWatcher *watcher = new QFutureWatcher(this); - connect( - watcher, &QFutureWatcher::finished, this, - [this, watcher]() { - m_refreshButton->stopAnimation(); - watcher->deleteLater(); - }, - Qt::QueuedConnection); - - QFuture future = QtConcurrent::run([this]() { Q_EMIT readRequested(); }); - - watcher->setFuture(future); - }); - - QStackedWidget *centralWidget = new QStackedWidget(this); - - m_controlsWidget = new QWidget(this); - QVBoxLayout *controlsLayout = new QVBoxLayout(m_controlsWidget); - controlsLayout->setMargin(0); - controlsLayout->setContentsMargins(0, 0, 0, 0); - m_controlsWidget->setLayout(controlsLayout); - - QWidget *controlsWidget = new QWidget(this); - QVBoxLayout *controlWidgetLayout = new QVBoxLayout(controlsWidget); - controlWidgetLayout->setMargin(0); - controlWidgetLayout->setContentsMargins(0, 0, 0, 0); - controlsWidget->setLayout(controlWidgetLayout); - - QScrollArea *scrollArea = new QScrollArea(this); - scrollArea->setWidgetResizable(true); - scrollArea->setWidget(controlsWidget); - - controlsLayout->addWidget(scrollArea); - - if(m_ctx != nullptr) { - /// first widget the global settings can be created with iiowigets only - controlWidgetLayout->addWidget(generateGlobalSettingsWidget(controlsWidget)); - - /// second is Rx ( receive chain) - controlWidgetLayout->addWidget(generateRxChainWidget(controlsWidget)); - - /// third is Tx (transimt chain) - controlWidgetLayout->addWidget(generateTxChainWidget(controlsWidget)); - - controlWidgetLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); - } - - m_blockDiagramWidget = new QWidget(this); - Style::setBackgroundColor(m_blockDiagramWidget, json::theme::background_primary); - QVBoxLayout *blockDiagramLayout = new QVBoxLayout(m_blockDiagramWidget); - m_blockDiagramWidget->setLayout(blockDiagramLayout); - - QWidget *blockDiagramWidget = new QWidget(this); - QVBoxLayout *blockDiagramWidgetLayout = new QVBoxLayout(blockDiagramWidget); - blockDiagramWidget->setLayout(blockDiagramWidgetLayout); - - QScrollArea *blockDiagramWidgetScrollArea = new QScrollArea(this); - blockDiagramWidgetScrollArea->setWidgetResizable(true); - blockDiagramWidgetScrollArea->setWidget(blockDiagramWidget); - - blockDiagramLayout->addWidget(blockDiagramWidgetScrollArea); - - QLabel *blockDiagram = new QLabel(m_blockDiagramWidget); - blockDiagramWidgetLayout->addWidget(blockDiagram); - blockDiagram->setAlignment(Qt::AlignCenter); - QPixmap pixmap(":/pluto/ad936x.svg"); - blockDiagram->setPixmap(pixmap); - - centralWidget->addWidget(m_controlsWidget); - centralWidget->addWidget(m_blockDiagramWidget); - - m_tool->addWidgetToCentralContainerHelper(centralWidget); - - QButtonGroup *centralWidgetButtons = new QButtonGroup(this); - centralWidgetButtons->setExclusive(true); - - QPushButton *ad963xBtn = new QPushButton("Controls", this); - ad963xBtn->setCheckable(true); - ad963xBtn->setChecked(true); - Style::setStyle(ad963xBtn, style::properties::button::blueGrayButton); - connect(ad963xBtn, &QPushButton::clicked, this, - [=, this]() { centralWidget->setCurrentWidget(m_controlsWidget); }); - - QPushButton *blockDiagramBtn = new QPushButton("Block Diagram", this); - blockDiagramBtn->setCheckable(true); - Style::setStyle(blockDiagramBtn, style::properties::button::blueGrayButton); - connect(blockDiagramBtn, &QPushButton::clicked, this, - [=, this]() { centralWidget->setCurrentWidget(m_blockDiagramWidget); }); - - centralWidgetButtons->addButton(ad963xBtn); - centralWidgetButtons->addButton(blockDiagramBtn); - - m_tool->addWidgetToTopContainerHelper(ad963xBtn, TTA_LEFT); - m_tool->addWidgetToTopContainerHelper(blockDiagramBtn, TTA_LEFT); -} - -AD936X::~AD936X() {} - -QWidget *AD936X::generateGlobalSettingsWidget(QWidget *parent) -{ - QWidget *globalSettingsWidget = new QWidget(parent); - Style::setBackgroundColor(globalSettingsWidget, json::theme::background_primary); - Style::setStyle(globalSettingsWidget, style::properties::widget::border_interactive); - - QVBoxLayout *layout = new QVBoxLayout(globalSettingsWidget); - globalSettingsWidget->setLayout(layout); - - QLabel *title = new QLabel("AD9361 / AD9364 Global Settings", globalSettingsWidget); - Style::setStyle(title, style::properties::label::menuBig); - layout->addWidget(title); - - iio_device *plutoDevice = nullptr; - int device_count = iio_context_get_devices_count(m_ctx); - for(int i = 0; i < device_count; ++i) { - iio_device *dev = iio_context_get_device(m_ctx, i); - const char *dev_name = iio_device_get_name(dev); - if(dev_name && QString(dev_name).contains("ad936", Qt::CaseInsensitive)) { - plutoDevice = dev; - break; - } - } - - if(plutoDevice == nullptr) { - qWarning(CAT_AD936X) << "No AD936X device found"; - return globalSettingsWidget; - } - - QHBoxLayout *hlayout = new QHBoxLayout(); - - //// ensm_mode - IIOWidget *ensmMode = IIOWidgetBuilder(globalSettingsWidget) - .device(plutoDevice) - .attribute("ensm_mode") - .optionsAttribute("ensm_mode_available") - .title("ENSM Mode") - .uiStrategy(IIOWidgetBuilder::ComboUi) - .buildSingle(); - - hlayout->addWidget(ensmMode); - - connect(this, &AD936X::readRequested, ensmMode, &IIOWidget::readAsync); - - ////calib_mode - IIOWidget *calibMode = IIOWidgetBuilder(globalSettingsWidget) - .device(plutoDevice) - .attribute("calib_mode") - .optionsAttribute("calib_mode_available") - .title("Calibration Mode") - .uiStrategy(IIOWidgetBuilder::ComboUi) - .buildSingle(); - hlayout->addWidget(calibMode); - connect(this, &AD936X::readRequested, calibMode, &IIOWidget::readAsync); - - Style::setStyle(calibMode, style::properties::widget::basicBackground, true, true); - - // trx_rate_governor - IIOWidget *trxRateGovernor = IIOWidgetBuilder(globalSettingsWidget) - .device(plutoDevice) - .attribute("trx_rate_governor") - .optionsAttribute("trx_rate_governor_available") - .title("TRX Rate Governor") - .uiStrategy(IIOWidgetBuilder::ComboUi) - .buildSingle(); - hlayout->addWidget(trxRateGovernor); - connect(this, &AD936X::readRequested, trxRateGovernor, &IIOWidget::readAsync); - - FirFilterQWidget *firFilter = new FirFilterQWidget(plutoDevice, nullptr, globalSettingsWidget); - hlayout->addWidget(firFilter); - - layout->addLayout(hlayout); - - // rx_path_rates - IIOWidget *rxPathRates = IIOWidgetBuilder(globalSettingsWidget) - .device(plutoDevice) - .attribute("rx_path_rates") - .title("RX Path Rates") - .buildSingle(); - layout->addWidget(rxPathRates); - rxPathRates->setEnabled(false); - connect(this, &AD936X::readRequested, rxPathRates, &IIOWidget::readAsync); - - // tx_path_rates - IIOWidget *txPathRates = IIOWidgetBuilder(globalSettingsWidget) - .device(plutoDevice) - .attribute("tx_path_rates") - .title("Tx Path Rates") - .buildSingle(); - layout->addWidget(txPathRates); - txPathRates->setEnabled(false); - connect(this, &AD936X::readRequested, txPathRates, &IIOWidget::readAsync); - - connect(firFilter, &FirFilterQWidget::filterChanged, this, [=, this]() { - rxPathRates->read(); - txPathRates->read(); - }); - - // xo_correction - IIOWidget *xoCorrection = IIOWidgetBuilder(globalSettingsWidget) - .device(plutoDevice) - .attribute("xo_correction") - .optionsAttribute("xo_correction_available") - .title("XO Correction") - .uiStrategy(IIOWidgetBuilder::RangeUi) - .buildSingle(); - layout->addWidget(xoCorrection); - connect(this, &AD936X::readRequested, xoCorrection, &IIOWidget::readAsync); - - layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); - - return globalSettingsWidget; -} - -QWidget *AD936X::generateRxChainWidget(QWidget *parent) -{ - QWidget *rxChainWidget = new QWidget(parent); - Style::setBackgroundColor(rxChainWidget, json::theme::background_primary); - Style::setStyle(rxChainWidget, style::properties::widget::border_interactive); - - QVBoxLayout *mainLayout = new QVBoxLayout(rxChainWidget); - rxChainWidget->setLayout(mainLayout); - - QLabel *title = new QLabel("AD9361 / AD9364 Receive Chain", rxChainWidget); - Style::setStyle(title, style::properties::label::menuBig); - mainLayout->addWidget(title); - - iio_device *plutoDevice = nullptr; - int device_count = iio_context_get_devices_count(m_ctx); - for(int i = 0; i < device_count; ++i) { - iio_device *dev = iio_context_get_device(m_ctx, i); - const char *dev_name = iio_device_get_name(dev); - if(dev_name && QString(dev_name).contains("ad936", Qt::CaseInsensitive)) { - plutoDevice = dev; - break; - } - } - - if(plutoDevice == nullptr) { - qWarning(CAT_AD936X) << "No AD936X device found"; - return rxChainWidget; - } - - QGridLayout *layout = new QGridLayout(); - - bool isOutput = false; - - iio_channel *voltage0 = iio_device_find_channel(plutoDevice, "voltage0", isOutput); - - // voltage0: rf_bandwidth - IIOWidget *rfBandwidth = IIOWidgetBuilder(rxChainWidget) - .channel(voltage0) - .attribute("rf_bandwidth") - .optionsAttribute("rf_bandwidth_available") - .title("RF Bandwidth(MHz)") - .uiStrategy(IIOWidgetBuilder::RangeUi) - .buildSingle(); - layout->addWidget(rfBandwidth, 0, 0, 2, 1); - connect(this, &AD936X::readRequested, rfBandwidth, &IIOWidget::readAsync); - - // voltage0: sampling_frequency - IIOWidget *samplingFrequency = IIOWidgetBuilder(rxChainWidget) - .channel(voltage0) - .attribute("sampling_frequency") - .optionsAttribute("sampling_frequency_available") - .title("Sampling Rate(MSPS)") - .uiStrategy(IIOWidgetBuilder::RangeUi) - .buildSingle(); - layout->addWidget(samplingFrequency, 0, 1, 2, 1); - connect(this, &AD936X::readRequested, samplingFrequency, &IIOWidget::readAsync); - - // voltage 0 : rf_port_select - IIOWidget *rfPortSelect = IIOWidgetBuilder(rxChainWidget) - .channel(voltage0) - .attribute("rf_port_select") - .optionsAttribute("rf_port_select_available") - .title("RF Port Select") - .uiStrategy(IIOWidgetBuilder::ComboUi) - .buildSingle(); - layout->addWidget(rfPortSelect, 0, 2, 2, 1); - connect(this, &AD936X::readRequested, rfPortSelect, &IIOWidget::readAsync); - - // because this channel is marked as output by libiio we need to mark altvoltage0 as output - iio_channel *altVoltage0 = iio_device_find_channel(plutoDevice, "altvoltage0", true); - - // altvoltage0: RX_LO // frequency - IIOWidget *altVoltage0Frequency = IIOWidgetBuilder(rxChainWidget) - .channel(altVoltage0) - .attribute("frequency") - .optionsAttribute("frequency_available") - .title("RX LO Frequency(MHz)") - .uiStrategy(IIOWidgetBuilder::RangeUi) - .buildSingle(); - connect(this, &AD936X::readRequested, altVoltage0Frequency, &IIOWidget::readAsync); - - MenuOnOffSwitch *useExternalRxLo = new MenuOnOffSwitch("External Rx LO", rxChainWidget, false); - useExternalRxLo->onOffswitch()->setChecked(true); - - layout->addWidget(altVoltage0Frequency, 0, 3, 2, 1); - layout->addWidget(useExternalRxLo, 2, 3, 1, 1); - - // fastlock profile - FastlockProfilesWidget *fastlockProfile = new FastlockProfilesWidget(altVoltage0, rxChainWidget); - - connect(useExternalRxLo->onOffswitch(), &QAbstractButton::toggled, this, - [=, this](bool toggled) { fastlockProfile->setEnabled(toggled); }); - - layout->addWidget(fastlockProfile, 0, 4, 3, 1); - - connect(fastlockProfile, &FastlockProfilesWidget::recallCalled, this, - [=, this] { altVoltage0Frequency->read(); }); - - // quadrature_tracking_en - IIOWidget *quadratureTrackingEn = IIOWidgetBuilder(rxChainWidget) - .channel(voltage0) - .attribute("quadrature_tracking_en") - .uiStrategy(IIOWidgetBuilder::CheckBoxUi) - .title("Quadrature") - .buildSingle(); - layout->addWidget(quadratureTrackingEn, 0, 5); - quadratureTrackingEn->showProgressBar(false); - connect(this, &AD936X::readRequested, quadratureTrackingEn, &IIOWidget::readAsync); - - // rf_dc_offset_tracking_en - IIOWidget *rcDcOffsetTrackingEn = IIOWidgetBuilder(rxChainWidget) - .channel(voltage0) - .attribute("rf_dc_offset_tracking_en") - .uiStrategy(IIOWidgetBuilder::CheckBoxUi) - .title("RF DC") - .buildSingle(); - layout->addWidget(rcDcOffsetTrackingEn, 1, 5); - rcDcOffsetTrackingEn->showProgressBar(false); - connect(this, &AD936X::readRequested, rcDcOffsetTrackingEn, &IIOWidget::readAsync); - - // bb_dc_offset_tracking_en - IIOWidget *bbDcOffsetTrackingEn = IIOWidgetBuilder(rxChainWidget) - .channel(voltage0) - .attribute("bb_dc_offset_tracking_en") - .title("BB DC") - .uiStrategy(IIOWidgetBuilder::CheckBoxUi) - .buildSingle(); - layout->addWidget(bbDcOffsetTrackingEn, 2, 5); - bbDcOffsetTrackingEn->showProgressBar(false); - connect(this, &AD936X::readRequested, bbDcOffsetTrackingEn, &IIOWidget::readAsync); - - mainLayout->addLayout(layout); - - QHBoxLayout *rxWidgetsLayout = new QHBoxLayout(); - rxWidgetsLayout->addWidget(generateRxWidget(voltage0, "RX1", rxChainWidget)); - - iio_channel *voltage1 = iio_device_find_channel(plutoDevice, "voltage1", isOutput); - if(voltage1 && iio_channel_find_attr(voltage1, "hardwaregain")) { - rxWidgetsLayout->addWidget(generateRxWidget(voltage1, "RX2", rxChainWidget)); - } - - rxWidgetsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Preferred)); - mainLayout->addLayout(rxWidgetsLayout); - - mainLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); - - return rxChainWidget; -} - -QWidget *AD936X::generateRxWidget(iio_channel *chn, QString title, QWidget *parent) -{ - QWidget *rxWidget = new QWidget(parent); - Style::setStyle(rxWidget, style::properties::widget::border_interactive); - - QVBoxLayout *layout = new QVBoxLayout(rxWidget); - rxWidget->setLayout(layout); - - QLabel *titleLabel = new QLabel(title, rxWidget); - Style::setStyle(titleLabel, style::properties::label::menuBig); - layout->addWidget(titleLabel); - - // voltage0: hardwaregain - IIOWidget *hardwaregain = IIOWidgetBuilder(rxWidget) - .channel(chn) - .attribute("hardwaregain") - .uiStrategy(IIOWidgetBuilder::RangeUi) - .optionsAttribute("hardwaregain_available") - .title("Hardware Gain(dB)") - .buildSingle(); - layout->addWidget(hardwaregain); - connect(this, &AD936X::readRequested, hardwaregain, &IIOWidget::readAsync); - - hardwaregain->setDataToUIConversion([this](QString data) { - // data has dB as string in the value - auto result = data.split(" "); - return result.first(); - }); - - hardwaregain->lastReturnCode(); - - // voltage: rssi - IIOWidget *rssi = IIOWidgetBuilder(rxWidget).channel(chn).attribute("rssi").title("RSSI(dB)").buildSingle(); - layout->addWidget(rssi); - rssi->setEnabled(false); - - QTimer timer; - - QObject::connect(&timer, &QTimer::timeout, [&]() { rssi->readAsync(); }); - - timer.start(1000); - - // voltage: gain_control_mode - IIOWidget *gainControlMode = IIOWidgetBuilder(rxWidget) - .channel(chn) - .attribute("gain_control_mode") - .uiStrategy(IIOWidgetBuilder::ComboUi) - .optionsAttribute("gain_control_mode_available") - .title("Gain Control Mode") - .buildSingle(); - layout->addWidget(gainControlMode); - connect(this, &AD936X::readRequested, gainControlMode, &IIOWidget::readAsync); - - connect(dynamic_cast(gainControlMode->getUiStrategy()), &ComboAttrUi::displayedNewData, this, - [this, hardwaregain, rssi](QString data, QString optionalData) { - if(data == "manual") { - hardwaregain->setEnabled(true); - } else { - hardwaregain->setEnabled(false); - } - hardwaregain->readAsync(); - rssi->readAsync(); - }); - - return rxWidget; -} - -QWidget *AD936X::generateTxChainWidget(QWidget *parent) -{ - QWidget *txChainWidget = new QWidget(parent); - Style::setBackgroundColor(txChainWidget, json::theme::background_primary); - Style::setStyle(txChainWidget, style::properties::widget::border_interactive); - - QVBoxLayout *layout = new QVBoxLayout(txChainWidget); - txChainWidget->setLayout(layout); - - QLabel *title = new QLabel("AD9361 / AD9364 Transmit Chain", txChainWidget); - Style::setStyle(title, style::properties::label::menuBig); - layout->addWidget(title); - - iio_device *plutoDevice = nullptr; - int device_count = iio_context_get_devices_count(m_ctx); - for(int i = 0; i < device_count; ++i) { - iio_device *dev = iio_context_get_device(m_ctx, i); - const char *dev_name = iio_device_get_name(dev); - if(dev_name && QString(dev_name).contains("ad936", Qt::CaseInsensitive)) { - plutoDevice = dev; - break; - } - } - - if(plutoDevice == nullptr) { - qWarning(CAT_AD936X) << "No AD936X device found"; - return txChainWidget; - } - - QGridLayout *lay = new QGridLayout(); - - bool isOutput = true; - iio_channel *voltage0 = iio_device_find_channel(plutoDevice, "voltage0", isOutput); - - // voltage0: rf_bandwidth - IIOWidget *rfBandwidth = IIOWidgetBuilder(txChainWidget) - .channel(voltage0) - .attribute("rf_bandwidth") - .optionsAttribute("rf_bandwidth_available") - .uiStrategy(IIOWidgetBuilder::RangeUi) - .title("RF Bandwidth(MHz)") - .buildSingle(); - lay->addWidget(rfBandwidth, 0, 0, 2, 1); - connect(this, &AD936X::readRequested, rfBandwidth, &IIOWidget::readAsync); - - // voltage0: sampling_frequency - IIOWidget *samplingFrequency = IIOWidgetBuilder(txChainWidget) - .channel(voltage0) - .attribute("sampling_frequency") - .optionsAttribute("sampling_frequency_available") - .uiStrategy(IIOWidgetBuilder::RangeUi) - .title("Sampling Rate(MSPS)") - .buildSingle(); - lay->addWidget(samplingFrequency, 0, 1, 2, 1); - connect(this, &AD936X::readRequested, samplingFrequency, &IIOWidget::readAsync); - - // voltage0: rf_port_select - IIOWidget *rfPortSelect = IIOWidgetBuilder(txChainWidget) - .channel(voltage0) - .attribute("rf_port_select") - .optionsAttribute("rf_port_select_available") - .uiStrategy(IIOWidgetBuilder::ComboUi) - .title("RF Port Select") - .buildSingle(); - lay->addWidget(rfPortSelect, 0, 2, 2, 1); - connect(this, &AD936X::readRequested, rfPortSelect, &IIOWidget::readAsync); - - iio_channel *altVoltage1 = iio_device_find_channel(plutoDevice, "altvoltage1", isOutput); - - // altvoltage1: TX_LO // frequency - IIOWidget *altVoltage1Frequency = IIOWidgetBuilder(txChainWidget) - .channel(altVoltage1) - .attribute("frequency") - .optionsAttribute("frequency_available") - .uiStrategy(IIOWidgetBuilder::RangeUi) - .title("TX LO Frequency(MHz)") - .buildSingle(); - connect(this, &AD936X::readRequested, altVoltage1Frequency, &IIOWidget::readAsync); - - MenuOnOffSwitch *useExternalTxLo = new MenuOnOffSwitch("External Tx LO", txChainWidget, false); - useExternalTxLo->onOffswitch()->setChecked(true); - - lay->addWidget(altVoltage1Frequency, 0, 3, 2, 1); - lay->addWidget(useExternalTxLo, 2, 3, 1, 1); - - // fastlock profile - FastlockProfilesWidget *fastlockProfile = new FastlockProfilesWidget(altVoltage1, txChainWidget); - - connect(useExternalTxLo->onOffswitch(), &QAbstractButton::toggled, this, - [=, this](bool toggled) { fastlockProfile->setEnabled(toggled); }); - - lay->addWidget(fastlockProfile, 0, 4, 3, 1); - - connect(fastlockProfile, &FastlockProfilesWidget::recallCalled, this, - [=, this] { altVoltage1Frequency->read(); }); - - layout->addLayout(lay); - - QHBoxLayout *txWidgetsLayout = new QHBoxLayout(); - - txWidgetsLayout->addWidget(generateTxWidget(voltage0, "TX 1", txChainWidget)); - - iio_channel *voltage1 = iio_device_find_channel(plutoDevice, "voltage1", isOutput); - if(voltage1 && iio_channel_find_attr(voltage1, "hardwaregain")) { - txWidgetsLayout->addWidget(generateTxWidget(voltage1, "TX 2", txChainWidget)); - } - - txWidgetsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Preferred)); - layout->addLayout(txWidgetsLayout); - - layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); - - return txChainWidget; -} - -QWidget *AD936X::generateTxWidget(iio_channel *chn, QString title, QWidget *parent) -{ - QWidget *txWidget = new QWidget(parent); - Style::setStyle(txWidget, style::properties::widget::border_interactive); - - QVBoxLayout *layout = new QVBoxLayout(txWidget); - txWidget->setLayout(layout); - - QLabel *titleLabel = new QLabel(title, txWidget); - Style::setStyle(titleLabel, style::properties::label::menuBig); - layout->addWidget(titleLabel); - - // adi,tx-attenuation-mdB - IIOWidget *txAttenuation = IIOWidgetBuilder(txWidget) - .channel(chn) - .attribute("hardwaregain") - .uiStrategy(IIOWidgetBuilder::RangeUi) - .optionsAttribute("hardwaregain_available") - .title("Attenuation(dB)") - .buildSingle(); - layout->addWidget(txAttenuation); - connect(this, &AD936X::readRequested, txAttenuation, &IIOWidget::readAsync); - - txAttenuation->setDataToUIConversion([this](QString data) { - // data has dB as string in the value - auto result = data.split(" "); - return result.first(); - }); - - IIOWidget *rssi = IIOWidgetBuilder(txWidget).channel(chn).attribute("rssi").title("RSSI(dB)").buildSingle(); - layout->addWidget(rssi); - rssi->setEnabled(false); - connect(this, &AD936X::readRequested, rssi, &IIOWidget::readAsync); - - return txWidget; -} diff --git a/packages/ad936x/plugins/ad936x/src/ad936x/ad936x.cpp b/packages/ad936x/plugins/ad936x/src/ad936x/ad936x.cpp new file mode 100755 index 0000000000..7eb99b3fa4 --- /dev/null +++ b/packages/ad936x/plugins/ad936x/src/ad936x/ad936x.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "ad936x/ad936x.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(CAT_AD936X, "AD936X"); + +using namespace scopy; +using namespace ad936x; + +AD936X::AD936X(iio_context *ctx, QWidget *parent) + : QWidget(parent) + , m_ctx(ctx) +{ + + m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setMargin(0); + m_mainLayout->setContentsMargins(0, 0, 0, 0); + + m_tool = new ToolTemplate(this); + m_tool->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_tool->topContainer()->setVisible(true); + m_tool->topContainerMenuControl()->setVisible(false); + + m_mainLayout->addWidget(m_tool); + + m_refreshButton = new AnimatedRefreshBtn(false, this); + m_tool->addWidgetToTopContainerHelper(m_refreshButton, TTA_RIGHT); + + connect(m_refreshButton, &QPushButton::clicked, this, [this]() { + m_refreshButton->startAnimation(); + + QFutureWatcher *watcher = new QFutureWatcher(this); + connect( + watcher, &QFutureWatcher::finished, this, + [this, watcher]() { + m_refreshButton->stopAnimation(); + watcher->deleteLater(); + }, + Qt::QueuedConnection); + + QFuture future = QtConcurrent::run([this]() { Q_EMIT readRequested(); }); + + watcher->setFuture(future); + }); + + QStackedWidget *centralWidget = new QStackedWidget(this); + + m_controlsWidget = new QWidget(this); + QVBoxLayout *controlsLayout = new QVBoxLayout(m_controlsWidget); + controlsLayout->setMargin(0); + controlsLayout->setContentsMargins(0, 0, 0, 0); + m_controlsWidget->setLayout(controlsLayout); + + QWidget *controlsWidget = new QWidget(this); + QVBoxLayout *controlWidgetLayout = new QVBoxLayout(controlsWidget); + controlWidgetLayout->setMargin(0); + controlWidgetLayout->setContentsMargins(0, 0, 0, 0); + controlsWidget->setLayout(controlWidgetLayout); + + QScrollArea *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setWidget(controlsWidget); + + controlsLayout->addWidget(scrollArea); + + if(m_ctx != nullptr) { + + iio_device *plutoDevice = nullptr; + int device_count = iio_context_get_devices_count(m_ctx); + for(int i = 0; i < device_count; ++i) { + iio_device *dev = iio_context_get_device(m_ctx, i); + const char *dev_name = iio_device_get_name(dev); + if(dev_name && QString(dev_name).contains("ad936", Qt::CaseInsensitive)) { + plutoDevice = dev; + break; + } + } + + m_helper = new AD936xHelper(); + connect(this, &AD936X::readRequested, m_helper, &AD936xHelper::readRequested); + + /// first widget the global settings can be created with iiowigets only + controlWidgetLayout->addWidget(m_helper->generateGlobalSettingsWidget( + plutoDevice, "AD9361 / AD9364 Global Settings", controlsWidget)); + + /// second is Rx ( receive chain) + controlWidgetLayout->addWidget( + generateRxChainWidget(plutoDevice, "AD9361 / AD9364 Receive Chain", controlsWidget)); + + /// third is Tx (transmit chain) + controlWidgetLayout->addWidget( + generateTxChainWidget(plutoDevice, "AD9361 / AD9364 Transmit Chain", controlsWidget)); + + controlWidgetLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); + } + + m_blockDiagramWidget = new QWidget(this); + Style::setBackgroundColor(m_blockDiagramWidget, json::theme::background_primary); + QVBoxLayout *blockDiagramLayout = new QVBoxLayout(m_blockDiagramWidget); + m_blockDiagramWidget->setLayout(blockDiagramLayout); + + QWidget *blockDiagramWidget = new QWidget(this); + QVBoxLayout *blockDiagramWidgetLayout = new QVBoxLayout(blockDiagramWidget); + blockDiagramWidget->setLayout(blockDiagramWidgetLayout); + + QScrollArea *blockDiagramWidgetScrollArea = new QScrollArea(this); + blockDiagramWidgetScrollArea->setWidgetResizable(true); + blockDiagramWidgetScrollArea->setWidget(blockDiagramWidget); + + blockDiagramLayout->addWidget(blockDiagramWidgetScrollArea); + + QLabel *blockDiagram = new QLabel(m_blockDiagramWidget); + blockDiagramWidgetLayout->addWidget(blockDiagram); + blockDiagram->setAlignment(Qt::AlignCenter); + QPixmap pixmap(":/pluto/ad936x.svg"); + blockDiagram->setPixmap(pixmap); + + centralWidget->addWidget(m_controlsWidget); + centralWidget->addWidget(m_blockDiagramWidget); + + m_tool->addWidgetToCentralContainerHelper(centralWidget); + + QButtonGroup *centralWidgetButtons = new QButtonGroup(this); + centralWidgetButtons->setExclusive(true); + + QPushButton *ad963xBtn = new QPushButton("Controls", this); + ad963xBtn->setCheckable(true); + ad963xBtn->setChecked(true); + Style::setStyle(ad963xBtn, style::properties::button::blueGrayButton); + connect(ad963xBtn, &QPushButton::clicked, this, + [=, this]() { centralWidget->setCurrentWidget(m_controlsWidget); }); + + QPushButton *blockDiagramBtn = new QPushButton("Block Diagram", this); + blockDiagramBtn->setCheckable(true); + Style::setStyle(blockDiagramBtn, style::properties::button::blueGrayButton); + connect(blockDiagramBtn, &QPushButton::clicked, this, + [=, this]() { centralWidget->setCurrentWidget(m_blockDiagramWidget); }); + + centralWidgetButtons->addButton(ad963xBtn); + centralWidgetButtons->addButton(blockDiagramBtn); + + m_tool->addWidgetToTopContainerHelper(ad963xBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(blockDiagramBtn, TTA_LEFT); +} + +AD936X::~AD936X() {} + +QWidget *AD936X::generateRxChainWidget(iio_device *dev, QString title, QWidget *parent) +{ + QWidget *widget = new QWidget(parent); + Style::setBackgroundColor(widget, json::theme::background_primary); + Style::setStyle(widget, style::properties::widget::border_interactive); + + QVBoxLayout *mainLayout = new QVBoxLayout(widget); + widget->setLayout(mainLayout); + + QLabel *titleLabel = new QLabel(title, widget); + Style::setStyle(titleLabel, style::properties::label::menuBig); + mainLayout->addWidget(titleLabel); + + if(dev == nullptr) { + qWarning(CAT_AD936X) << "No AD936X device found"; + } + + QGridLayout *layout = new QGridLayout(); + + bool isOutput = false; + + iio_channel *voltage0 = iio_device_find_channel(dev, "voltage0", isOutput); + + // voltage0: rf_bandwidth + IIOWidget *rfBandwidth = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_bandwidth") + .optionsAttribute("rf_bandwidth_available") + .title("RF Bandwidth(MHz)") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .buildSingle(); + layout->addWidget(rfBandwidth, 0, 0, 2, 1); + connect(this, &AD936X::readRequested, rfBandwidth, &IIOWidget::readAsync); + + // voltage0: sampling_frequency + IIOWidget *samplingFrequency = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("sampling_frequency") + .optionsAttribute("sampling_frequency_available") + .title("Sampling Rate(MSPS)") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .buildSingle(); + layout->addWidget(samplingFrequency, 0, 1, 2, 1); + connect(this, &AD936X::readRequested, samplingFrequency, &IIOWidget::readAsync); + + // voltage 0 : rf_port_select + IIOWidget *rfPortSelect = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_port_select") + .optionsAttribute("rf_port_select_available") + .title("RF Port Select") + .uiStrategy(IIOWidgetBuilder::ComboUi) + .buildSingle(); + layout->addWidget(rfPortSelect, 0, 2, 2, 1); + connect(this, &AD936X::readRequested, rfPortSelect, &IIOWidget::readAsync); + + // quadrature_tracking_en + IIOWidget *quadratureTrackingEn = IIOWidgetBuilder(this) + .channel(voltage0) + .attribute("quadrature_tracking_en") + .uiStrategy(IIOWidgetBuilder::CheckBoxUi) + .title("Quadrature") + .buildSingle(); + layout->addWidget(quadratureTrackingEn, 0, 5); + quadratureTrackingEn->showProgressBar(false); + connect(this, &AD936X::readRequested, quadratureTrackingEn, &IIOWidget::readAsync); + + // rf_dc_offset_tracking_en + IIOWidget *rcDcOffsetTrackingEn = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_dc_offset_tracking_en") + .uiStrategy(IIOWidgetBuilder::CheckBoxUi) + .title("RF DC") + .buildSingle(); + layout->addWidget(rcDcOffsetTrackingEn, 1, 5); + rcDcOffsetTrackingEn->showProgressBar(false); + connect(this, &AD936X::readRequested, rcDcOffsetTrackingEn, &IIOWidget::readAsync); + + // bb_dc_offset_tracking_en + IIOWidget *bbDcOffsetTrackingEn = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("bb_dc_offset_tracking_en") + .title("BB DC") + .uiStrategy(IIOWidgetBuilder::CheckBoxUi) + .buildSingle(); + layout->addWidget(bbDcOffsetTrackingEn, 2, 5); + bbDcOffsetTrackingEn->showProgressBar(false); + connect(this, &AD936X::readRequested, bbDcOffsetTrackingEn, &IIOWidget::readAsync); + + mainLayout->addLayout(layout); + + QHBoxLayout *rxDeviceLayout = new QHBoxLayout(); + rxDeviceLayout->setMargin(0); + rxDeviceLayout->setSpacing(10); + + QWidget *rxDeviceWidget = m_helper->generateRxDeviceWidget(dev, "ad9361-phy", widget); + + rxDeviceLayout->addWidget(rxDeviceWidget); + rxDeviceWidget->layout()->addWidget(m_helper->generateRxChannelWidget(voltage0, "RX 1", rxDeviceWidget)); + iio_channel *voltage1 = iio_device_find_channel(dev, "voltage1", isOutput); + if(voltage1 && iio_channel_find_attr(voltage1, "hardwaregain")) { + rxDeviceWidget->layout()->addWidget( + m_helper->generateRxChannelWidget(voltage1, "RX 2", rxDeviceWidget)); + } + + rxDeviceLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); + + mainLayout->addLayout(rxDeviceLayout); + + mainLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); + + return widget; +} + +QWidget *AD936X::generateTxChainWidget(iio_device *dev, QString title, QWidget *parent) +{ + QWidget *widget = new QWidget(parent); + Style::setBackgroundColor(widget, json::theme::background_primary); + Style::setStyle(widget, style::properties::widget::border_interactive); + + QVBoxLayout *layout = new QVBoxLayout(widget); + widget->setLayout(layout); + + QLabel *titleLabel = new QLabel(title, widget); + Style::setStyle(titleLabel, style::properties::label::menuBig); + layout->addWidget(titleLabel); + + QGridLayout *lay = new QGridLayout(); + + bool isOutput = true; + iio_channel *voltage0 = iio_device_find_channel(dev, "voltage0", isOutput); + + // voltage0: rf_bandwidth + IIOWidget *rfBandwidth = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_bandwidth") + .optionsAttribute("rf_bandwidth_available") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .title("RF Bandwidth(MHz)") + .buildSingle(); + lay->addWidget(rfBandwidth, 0, 0, 2, 1); + connect(this, &AD936X::readRequested, rfBandwidth, &IIOWidget::readAsync); + + // voltage0: sampling_frequency + IIOWidget *samplingFrequency = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("sampling_frequency") + .optionsAttribute("sampling_frequency_available") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .title("Sampling Rate(MSPS)") + .buildSingle(); + lay->addWidget(samplingFrequency, 0, 1, 2, 1); + connect(this, &AD936X::readRequested, samplingFrequency, &IIOWidget::readAsync); + + // voltage0: rf_port_select + IIOWidget *rfPortSelect = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_port_select") + .optionsAttribute("rf_port_select_available") + .uiStrategy(IIOWidgetBuilder::ComboUi) + .title("RF Port Select") + .buildSingle(); + lay->addWidget(rfPortSelect, 0, 2, 2, 1); + connect(this, &AD936X::readRequested, rfPortSelect, &IIOWidget::readAsync); + + layout->addLayout(lay); + + QHBoxLayout *txWidgetsLayout = new QHBoxLayout(); + + QWidget *txDeviceWidget = m_helper->generateTxDeviceWidget(dev, "ad9361-phy", widget); + + txWidgetsLayout->addWidget(txDeviceWidget); + txDeviceWidget->layout()->addWidget(m_helper->generateTxChannelWidget(voltage0, "TX 1", txDeviceWidget)); + iio_channel *voltage1 = iio_device_find_channel(dev, "voltage1", isOutput); + if(voltage1 && iio_channel_find_attr(voltage1, "hardwaregain")) { + txDeviceWidget->layout()->addWidget( + m_helper->generateTxChannelWidget(voltage1, "TX 2", txDeviceWidget)); + } + + txWidgetsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Preferred)); + layout->addWidget(txDeviceWidget); + + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); + + return widget; +} diff --git a/packages/ad936x/plugins/ad936x/src/ad936xadvanced.cpp b/packages/ad936x/plugins/ad936x/src/ad936x/ad936xadvanced.cpp old mode 100644 new mode 100755 similarity index 99% rename from packages/ad936x/plugins/ad936x/src/ad936xadvanced.cpp rename to packages/ad936x/plugins/ad936x/src/ad936x/ad936xadvanced.cpp index 7431fedcc8..c3ff108b88 --- a/packages/ad936x/plugins/ad936x/src/ad936xadvanced.cpp +++ b/packages/ad936x/plugins/ad936x/src/ad936x/ad936xadvanced.cpp @@ -19,7 +19,7 @@ * */ -#include "ad963xadvanced.h" +#include "ad936x/ad963xadvanced.h" #include #include diff --git a/packages/ad936x/plugins/ad936x/src/ad936xhelper.cpp b/packages/ad936x/plugins/ad936x/src/ad936xhelper.cpp new file mode 100644 index 0000000000..2c83fc07e8 --- /dev/null +++ b/packages/ad936x/plugins/ad936x/src/ad936xhelper.cpp @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "ad936xhelper.h" + +#include "fastlockprofileswidget.h" +#include "firfilterqwidget.h" + +#include +#include + +#include + +Q_LOGGING_CATEGORY(CAT_AD936X_HELPER, "AD936X_HELPER"); + +using namespace scopy; +using namespace ad936x; + +AD936xHelper::AD936xHelper(QWidget *parent) + : QWidget(parent) +{} + +QWidget *AD936xHelper::generateGlobalSettingsWidget(iio_device *dev, QString title, QWidget *parent) +{ + QWidget *globalSettingsWidget = new QWidget(parent); + Style::setBackgroundColor(globalSettingsWidget, json::theme::background_primary); + Style::setStyle(globalSettingsWidget, style::properties::widget::border_interactive); + + QVBoxLayout *layout = new QVBoxLayout(globalSettingsWidget); + globalSettingsWidget->setLayout(layout); + + QLabel *titleLabel = new QLabel(title, globalSettingsWidget); + Style::setStyle(titleLabel, style::properties::label::menuBig); + layout->addWidget(titleLabel); + + QHBoxLayout *hlayout = new QHBoxLayout(); + + //// ensm_mode + IIOWidget *ensmMode = IIOWidgetBuilder(globalSettingsWidget) + .device(dev) + .attribute("ensm_mode") + .optionsAttribute("ensm_mode_available") + .title("ENSM Mode") + .uiStrategy(IIOWidgetBuilder::ComboUi) + .buildSingle(); + + hlayout->addWidget(ensmMode); + + connect(this, &AD936xHelper::readRequested, ensmMode, &IIOWidget::readAsync); + + ////calib_mode + IIOWidget *calibMode = IIOWidgetBuilder(globalSettingsWidget) + .device(dev) + .attribute("calib_mode") + .optionsAttribute("calib_mode_available") + .title("Calibration Mode") + .uiStrategy(IIOWidgetBuilder::ComboUi) + .buildSingle(); + hlayout->addWidget(calibMode); + connect(this, &AD936xHelper::readRequested, calibMode, &IIOWidget::readAsync); + + Style::setStyle(calibMode, style::properties::widget::basicBackground, true, true); + + // trx_rate_governor + IIOWidget *trxRateGovernor = IIOWidgetBuilder(globalSettingsWidget) + .device(dev) + .attribute("trx_rate_governor") + .optionsAttribute("trx_rate_governor_available") + .title("TRX Rate Governor") + .uiStrategy(IIOWidgetBuilder::ComboUi) + .buildSingle(); + hlayout->addWidget(trxRateGovernor); + connect(this, &AD936xHelper::readRequested, trxRateGovernor, &IIOWidget::readAsync); + + FirFilterQWidget *firFilter = new FirFilterQWidget(dev, nullptr, globalSettingsWidget); + hlayout->addWidget(firFilter); + + layout->addLayout(hlayout); + + // rx_path_rates + IIOWidget *rxPathRates = IIOWidgetBuilder(globalSettingsWidget) + .device(dev) + .attribute("rx_path_rates") + .title("RX Path Rates") + .buildSingle(); + layout->addWidget(rxPathRates); + rxPathRates->setEnabled(false); + connect(this, &AD936xHelper::readRequested, rxPathRates, &IIOWidget::readAsync); + + // tx_path_rates + IIOWidget *txPathRates = IIOWidgetBuilder(globalSettingsWidget) + .device(dev) + .attribute("tx_path_rates") + .title("Tx Path Rates") + .buildSingle(); + layout->addWidget(txPathRates); + txPathRates->setEnabled(false); + connect(this, &AD936xHelper::readRequested, txPathRates, &IIOWidget::readAsync); + + connect(firFilter, &FirFilterQWidget::filterChanged, this, [=, this]() { + rxPathRates->read(); + txPathRates->read(); + }); + + // xo_correction + IIOWidget *xoCorrection = IIOWidgetBuilder(globalSettingsWidget) + .device(dev) + .attribute("xo_correction") + .optionsAttribute("xo_correction_available") + .title("XO Correction") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .buildSingle(); + layout->addWidget(xoCorrection); + connect(this, &AD936xHelper::readRequested, xoCorrection, &IIOWidget::readAsync); + + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); + + return globalSettingsWidget; +} + +QWidget *AD936xHelper::generateRxDeviceWidget(iio_device *dev, QString title, QWidget *parent) +{ + QWidget *widget = new QWidget(parent); + Style::setStyle(widget, style::properties::widget::border_interactive); + + QGridLayout *layout = new QGridLayout(widget); + layout->setSpacing(10); + widget->setLayout(layout); + + QLabel *titleLabel = new QLabel(title, widget); + Style::setStyle(titleLabel, style::properties::label::menuBig); + layout->addWidget(titleLabel, 0, 0); + + // because this channel is marked as output by libiio we need to mark altvoltage0 as output + iio_channel *altVoltage0 = iio_device_find_channel(dev, "altvoltage0", true); + + // altvoltage0: RX_LO // frequency + IIOWidget *altVoltage0Frequency = IIOWidgetBuilder(widget) + .channel(altVoltage0) + .attribute("frequency") + .optionsAttribute("frequency_available") + .title("RX LO Frequency(MHz)") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .buildSingle(); + connect(this, &AD936xHelper::readRequested, altVoltage0Frequency, &IIOWidget::readAsync); + + MenuOnOffSwitch *useExternalRxLo = new MenuOnOffSwitch("External Rx LO", widget, false); + useExternalRxLo->onOffswitch()->setChecked(true); + + layout->addWidget(altVoltage0Frequency, 1, 0, 2, 1); + layout->addWidget(useExternalRxLo, 2, 0, 1, 1); + + // fastlock profile + FastlockProfilesWidget *fastlockProfile = new FastlockProfilesWidget(altVoltage0, this); + + connect(useExternalRxLo->onOffswitch(), &QAbstractButton::toggled, this, + [=, this](bool toggled) { fastlockProfile->setEnabled(toggled); }); + + layout->addWidget(fastlockProfile, 1, 1, 2, 1); + + connect(fastlockProfile, &FastlockProfilesWidget::recallCalled, this, + [=, this] { altVoltage0Frequency->read(); }); + + return widget; +} + +QWidget *AD936xHelper::generateRxChannelWidget(iio_channel *chn, QString title, QWidget *parent) +{ + QWidget *rxWidget = new QWidget(parent); + Style::setStyle(rxWidget, style::properties::widget::border_interactive); + + QVBoxLayout *layout = new QVBoxLayout(rxWidget); + rxWidget->setLayout(layout); + + QLabel *titleLabel = new QLabel(title, rxWidget); + Style::setStyle(titleLabel, style::properties::label::menuBig); + layout->addWidget(titleLabel); + + // voltage0: hardwaregain + IIOWidget *hardwaregain = IIOWidgetBuilder(rxWidget) + .channel(chn) + .attribute("hardwaregain") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .optionsAttribute("hardwaregain_available") + .title("Hardware Gain(dB)") + .buildSingle(); + layout->addWidget(hardwaregain); + connect(this, &AD936xHelper::readRequested, hardwaregain, &IIOWidget::readAsync); + + hardwaregain->setDataToUIConversion([this](QString data) { + // data has dB as string in the value + auto result = data.split(" "); + return result.first(); + }); + + hardwaregain->lastReturnCode(); + + // voltage: rssi + IIOWidget *rssi = IIOWidgetBuilder(rxWidget).channel(chn).attribute("rssi").title("RSSI(dB)").buildSingle(); + layout->addWidget(rssi); + rssi->setEnabled(false); + + QTimer timer; + + QObject::connect(&timer, &QTimer::timeout, [&]() { rssi->readAsync(); }); + + timer.start(1000); + + // voltage: gain_control_mode + IIOWidget *gainControlMode = IIOWidgetBuilder(rxWidget) + .channel(chn) + .attribute("gain_control_mode") + .uiStrategy(IIOWidgetBuilder::ComboUi) + .optionsAttribute("gain_control_mode_available") + .title("Gain Control Mode") + .buildSingle(); + layout->addWidget(gainControlMode); + connect(this, &AD936xHelper::readRequested, gainControlMode, &IIOWidget::readAsync); + + connect(dynamic_cast(gainControlMode->getUiStrategy()), &ComboAttrUi::displayedNewData, this, + [this, hardwaregain, rssi](QString data, QString optionalData) { + if(data == "manual") { + hardwaregain->setEnabled(true); + } else { + hardwaregain->setEnabled(false); + } + hardwaregain->readAsync(); + rssi->readAsync(); + }); + + return rxWidget; +} + +QWidget *AD936xHelper::generateTxDeviceWidget(iio_device *dev, QString title, QWidget *parent) +{ + QWidget *widget = new QWidget(parent); + Style::setStyle(widget, style::properties::widget::border_interactive); + + QGridLayout *layout = new QGridLayout(widget); + layout->setSpacing(10); + widget->setLayout(layout); + + QLabel *titleLabel = new QLabel(title, widget); + Style::setStyle(titleLabel, style::properties::label::menuBig); + layout->addWidget(titleLabel, 0, 0); + + bool isOutput = true; + iio_channel *altVoltage1 = iio_device_find_channel(dev, "altvoltage1", isOutput); + + // altvoltage1: TX_LO // frequency + IIOWidget *altVoltage1Frequency = IIOWidgetBuilder(widget) + .channel(altVoltage1) + .attribute("frequency") + .optionsAttribute("frequency_available") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .title("TX LO Frequency(MHz)") + .buildSingle(); + connect(this, &AD936xHelper::readRequested, altVoltage1Frequency, &IIOWidget::readAsync); + + MenuOnOffSwitch *useExternalTxLo = new MenuOnOffSwitch("External Tx LO", widget, false); + useExternalTxLo->onOffswitch()->setChecked(true); + + layout->addWidget(altVoltage1Frequency, 1, 0, 2, 1); + layout->addWidget(useExternalTxLo, 2, 0, 1, 1); + + // fastlock profile + FastlockProfilesWidget *fastlockProfile = new FastlockProfilesWidget(altVoltage1, widget); + + connect(useExternalTxLo->onOffswitch(), &QAbstractButton::toggled, this, + [=, this](bool toggled) { fastlockProfile->setEnabled(toggled); }); + + connect(fastlockProfile, &FastlockProfilesWidget::recallCalled, this, + [=, this] { altVoltage1Frequency->read(); }); + + layout->addWidget(fastlockProfile, 1, 1, 2, 1); + + return widget; +} + +QWidget *AD936xHelper::generateTxChannelWidget(iio_channel *chn, QString title, QWidget *parent) +{ + QWidget *txWidget = new QWidget(parent); + Style::setStyle(txWidget, style::properties::widget::border_interactive); + + QVBoxLayout *layout = new QVBoxLayout(txWidget); + txWidget->setLayout(layout); + + QLabel *titleLabel = new QLabel(title, txWidget); + Style::setStyle(titleLabel, style::properties::label::menuBig); + layout->addWidget(titleLabel); + + // adi,tx-attenuation-mdB + IIOWidget *txAttenuation = IIOWidgetBuilder(txWidget) + .channel(chn) + .attribute("hardwaregain") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .optionsAttribute("hardwaregain_available") + .title("Attenuation(dB)") + .buildSingle(); + layout->addWidget(txAttenuation); + connect(this, &AD936xHelper::readRequested, txAttenuation, &IIOWidget::readAsync); + + txAttenuation->setDataToUIConversion([this](QString data) { + // data has dB as string in the value + auto result = data.split(" "); + return result.first(); + }); + + IIOWidget *rssi = IIOWidgetBuilder(txWidget).channel(chn).attribute("rssi").title("RSSI(dB)").buildSingle(); + layout->addWidget(rssi); + rssi->setEnabled(false); + connect(this, &AD936xHelper::readRequested, rssi, &IIOWidget::readAsync); + + return txWidget; +} diff --git a/packages/ad936x/plugins/ad936x/src/ad936xplugin.cpp b/packages/ad936x/plugins/ad936x/src/ad936xplugin.cpp index 196cff1c3b..ee90e2b84b 100644 --- a/packages/ad936x/plugins/ad936x/src/ad936xplugin.cpp +++ b/packages/ad936x/plugins/ad936x/src/ad936xplugin.cpp @@ -26,11 +26,13 @@ #include #include #include "scopy-ad936x_config.h" +#include -#include "ad936x.h" -#include "ad963xadvanced.h" +#include "ad936x/ad936x.h" +#include "ad936x/ad963xadvanced.h" -#include +#include +#include Q_LOGGING_CATEGORY(CAT_AD936XPLUGIN, "Ad936xPlugin") using namespace scopy::ad936x; @@ -106,15 +108,42 @@ bool Ad936xPlugin::onConnect() return false; } - AD936X *ad936X = new AD936X(conn->context()); - m_toolList[0]->setTool(ad936X); - m_toolList[0]->setEnabled(true); - m_toolList[0]->setRunBtnVisible(true); + bool isFmcomms5 = false; + int device_count = iio_context_get_devices_count(conn->context()); + for(int i = 0; i < device_count; ++i) { + iio_device *dev = iio_context_get_device(conn->context(), i); + const char *dev_name = iio_device_get_name(dev); + if(dev_name && QString(dev_name).contains("ad9361-phy-B", Qt::CaseInsensitive)) { + isFmcomms5 = true; + break; + } + } + + if(isFmcomms5) { + FMCOMMS5 *fmcomms5 = new FMCOMMS5(conn->context()); + m_toolList[0]->setTool(fmcomms5); + m_toolList[0]->setName("FMCOMMS5"); + m_toolList[0]->setEnabled(true); + m_toolList[0]->setRunBtnVisible(true); + + Fmcomms5Advanced *fmcomms5Advanced = new Fmcomms5Advanced(conn->context()); + m_toolList[1]->setTool(fmcomms5Advanced); + m_toolList[1]->setName("FMCOMMS5 Advanced"); + m_toolList[1]->setEnabled(true); + m_toolList[1]->setRunBtnVisible(true); + + } else { + AD936X *ad936X = new AD936X(conn->context()); + m_toolList[0]->setTool(ad936X); + m_toolList[0]->setEnabled(true); + m_toolList[0]->setRunBtnVisible(true); + + AD936XAdvanced *ad936XAdvanced = new AD936XAdvanced(conn->context()); + m_toolList[1]->setTool(ad936XAdvanced); + m_toolList[1]->setEnabled(true); + m_toolList[1]->setRunBtnVisible(true); + } - AD936XAdvanced *ad936XAdvanced = new AD936XAdvanced(conn->context()); - m_toolList[1]->setTool(ad936XAdvanced); - m_toolList[1]->setEnabled(true); - m_toolList[1]->setRunBtnVisible(true); return true; } diff --git a/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5.cpp b/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5.cpp new file mode 100644 index 0000000000..d452561673 --- /dev/null +++ b/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5.cpp @@ -0,0 +1,439 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "fmcomms5/fmcomms5.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(CAT_FMCOMMS5, "FMCOMMS5"); + +using namespace scopy; +using namespace ad936x; + +FMCOMMS5::FMCOMMS5(iio_context *ctx, QWidget *parent) + : QWidget(parent) + , m_ctx(ctx) +{ + m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setMargin(0); + m_mainLayout->setContentsMargins(0, 0, 0, 0); + + m_tool = new ToolTemplate(this); + m_tool->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_tool->topContainer()->setVisible(true); + m_tool->topContainerMenuControl()->setVisible(false); + + m_mainLayout->addWidget(m_tool); + + m_refreshButton = new AnimatedRefreshBtn(false, this); + m_tool->addWidgetToTopContainerHelper(m_refreshButton, TTA_RIGHT); + + connect(m_refreshButton, &QPushButton::clicked, this, [this]() { + m_refreshButton->startAnimation(); + + QFutureWatcher *watcher = new QFutureWatcher(this); + connect( + watcher, &QFutureWatcher::finished, this, + [this, watcher]() { + m_refreshButton->stopAnimation(); + watcher->deleteLater(); + }, + Qt::QueuedConnection); + + QFuture future = QtConcurrent::run([this]() { Q_EMIT readRequested(); }); + + watcher->setFuture(future); + }); + + QStackedWidget *centralWidget = new QStackedWidget(this); + + m_controlsWidget = new QWidget(this); + QVBoxLayout *controlsLayout = new QVBoxLayout(m_controlsWidget); + controlsLayout->setMargin(0); + controlsLayout->setContentsMargins(0, 0, 0, 0); + m_controlsWidget->setLayout(controlsLayout); + + QWidget *controlsWidget = new QWidget(this); + QVBoxLayout *controlWidgetLayout = new QVBoxLayout(controlsWidget); + controlWidgetLayout->setMargin(0); + controlWidgetLayout->setContentsMargins(0, 0, 0, 0); + controlsWidget->setLayout(controlWidgetLayout); + + QScrollArea *scrollArea = new QScrollArea(this); + scrollArea->setWidgetResizable(true); + scrollArea->setWidget(controlsWidget); + + controlsLayout->addWidget(scrollArea); + + if(m_ctx != nullptr) { + + iio_device *mainDevice = nullptr; + int device_count = iio_context_get_devices_count(m_ctx); + for(int i = 0; i < device_count; ++i) { + iio_device *dev = iio_context_get_device(m_ctx, i); + const char *dev_name = iio_device_get_name(dev); + if(dev_name && QString(dev_name).compare("ad9361-phy", Qt::CaseInsensitive) == 0) { + mainDevice = dev; + break; + } + } + + m_helper = new AD936xHelper(); + connect(this, &FMCOMMS5::readRequested, m_helper, &AD936xHelper::readRequested); + + /// first widget the global settings can be created with iiowigets only + controlWidgetLayout->addWidget( + m_helper->generateGlobalSettingsWidget(mainDevice, "FMCOMMS5 Global Settings", controlsWidget)); + + /// second is Rx ( receive chain) + controlWidgetLayout->addWidget( + generateRxChainWidget(mainDevice, "FMCOMMS5 Receive Chain", controlsWidget)); + + /// third is Tx (transmit chain) + controlWidgetLayout->addWidget( + generateTxChainWidget(mainDevice, "FMCOMMS5 Transmit Chain", controlsWidget)); + + controlWidgetLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); + } + + // Block Diagram Stack Widget with Next/Prev buttons + QWidget *blockDiagramStackWidget = new QWidget(this); + Style::setBackgroundColor(blockDiagramStackWidget, json::theme::background_primary); + QVBoxLayout *blockDiagramStackLayout = new QVBoxLayout(blockDiagramStackWidget); + blockDiagramStackWidget->setLayout(blockDiagramStackLayout); + + QStackedWidget *imageStack = new QStackedWidget(blockDiagramStackWidget); + + // Add images as QLabel widgets + QLabel *imageLabel1 = new QLabel(blockDiagramStackWidget); + imageLabel1->setAlignment(Qt::AlignCenter); + imageLabel1->setPixmap(QPixmap(":/pluto/ad936x.svg")); + imageStack->addWidget(imageLabel1); + + QLabel *imageLabel2 = new QLabel(blockDiagramStackWidget); + imageLabel2->setAlignment(Qt::AlignCenter); + imageLabel2->setPixmap(QPixmap(":/pluto/AD_FMCOMMS5_EBZ.jpg")); + imageStack->addWidget(imageLabel2); + + blockDiagramStackLayout->addWidget(imageStack); + + QHBoxLayout *buttonLayout = new QHBoxLayout(); + QPushButton *prevBtn = new QPushButton("Prev", blockDiagramStackWidget); + Style::setStyle(prevBtn, style::properties::button::basicButton); + QPushButton *nextBtn = new QPushButton("Next", blockDiagramStackWidget); + Style::setStyle(nextBtn, style::properties::button::basicButton); + buttonLayout->addWidget(prevBtn); + buttonLayout->addWidget(nextBtn); + blockDiagramStackLayout->addLayout(buttonLayout); + + // Navigation logic + connect(prevBtn, &QPushButton::clicked, this, [imageStack]() { + int idx = imageStack->currentIndex(); + if(idx > 0) + imageStack->setCurrentIndex(idx - 1); + }); + connect(nextBtn, &QPushButton::clicked, this, [imageStack]() { + int idx = imageStack->currentIndex(); + if(idx < imageStack->count() - 1) + imageStack->setCurrentIndex(idx + 1); + }); + + centralWidget->addWidget(m_controlsWidget); + centralWidget->addWidget(blockDiagramStackWidget); + + m_tool->addWidgetToCentralContainerHelper(centralWidget); + + QButtonGroup *centralWidgetButtons = new QButtonGroup(this); + centralWidgetButtons->setExclusive(true); + + QPushButton *controlsBtn = new QPushButton("Controls", this); + controlsBtn->setCheckable(true); + controlsBtn->setChecked(true); + Style::setStyle(controlsBtn, style::properties::button::blueGrayButton); + connect(controlsBtn, &QPushButton::clicked, this, + [=, this]() { centralWidget->setCurrentWidget(m_controlsWidget); }); + + QPushButton *blockDiagramBtn = new QPushButton("Block Diagram", this); + blockDiagramBtn->setCheckable(true); + Style::setStyle(blockDiagramBtn, style::properties::button::blueGrayButton); + connect(blockDiagramBtn, &QPushButton::clicked, this, + [=, this]() { centralWidget->setCurrentWidget(blockDiagramStackWidget); }); + + centralWidgetButtons->addButton(controlsBtn); + centralWidgetButtons->addButton(blockDiagramBtn); + + m_tool->addWidgetToTopContainerHelper(controlsBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(blockDiagramBtn, TTA_LEFT); +} + +FMCOMMS5::~FMCOMMS5() {} + +QWidget *FMCOMMS5::generateRxChainWidget(iio_device *dev, QString title, QWidget *parent) +{ + QWidget *widget = new QWidget(parent); + Style::setBackgroundColor(widget, json::theme::background_primary); + Style::setStyle(widget, style::properties::widget::border_interactive); + + QVBoxLayout *mainLayout = new QVBoxLayout(widget); + widget->setLayout(mainLayout); + + QLabel *titleLabel = new QLabel(title, widget); + Style::setStyle(titleLabel, style::properties::label::menuBig); + mainLayout->addWidget(titleLabel); + + if(dev == nullptr) { + qWarning(CAT_FMCOMMS5) << "No FMCOMMS5 device found"; + } + + QGridLayout *layout = new QGridLayout(); + + bool isOutput = false; + + iio_channel *voltage0 = iio_device_find_channel(dev, "voltage0", isOutput); + + // voltage0: rf_bandwidth + IIOWidget *rfBandwidth = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_bandwidth") + .optionsAttribute("rf_bandwidth_available") + .title("RF Bandwidth(MHz)") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .buildSingle(); + layout->addWidget(rfBandwidth, 0, 0, 2, 1); + connect(this, &FMCOMMS5::readRequested, rfBandwidth, &IIOWidget::readAsync); + + // voltage0: sampling_frequency + IIOWidget *samplingFrequency = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("sampling_frequency") + .optionsAttribute("sampling_frequency_available") + .title("Sampling Rate(MSPS)") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .buildSingle(); + layout->addWidget(samplingFrequency, 0, 1, 2, 1); + connect(this, &FMCOMMS5::readRequested, samplingFrequency, &IIOWidget::readAsync); + + // voltage 0 : rf_port_select + IIOWidget *rfPortSelect = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_port_select") + .optionsAttribute("rf_port_select_available") + .title("RF Port Select") + .uiStrategy(IIOWidgetBuilder::ComboUi) + .buildSingle(); + layout->addWidget(rfPortSelect, 0, 2, 2, 1); + connect(this, &FMCOMMS5::readRequested, rfPortSelect, &IIOWidget::readAsync); + + // quadrature_tracking_en + IIOWidget *quadratureTrackingEn = IIOWidgetBuilder(this) + .channel(voltage0) + .attribute("quadrature_tracking_en") + .uiStrategy(IIOWidgetBuilder::CheckBoxUi) + .title("Quadrature") + .buildSingle(); + layout->addWidget(quadratureTrackingEn, 0, 5); + quadratureTrackingEn->showProgressBar(false); + connect(this, &FMCOMMS5::readRequested, quadratureTrackingEn, &IIOWidget::readAsync); + + // rf_dc_offset_tracking_en + IIOWidget *rcDcOffsetTrackingEn = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_dc_offset_tracking_en") + .uiStrategy(IIOWidgetBuilder::CheckBoxUi) + .title("RF DC") + .buildSingle(); + layout->addWidget(rcDcOffsetTrackingEn, 1, 5); + rcDcOffsetTrackingEn->showProgressBar(false); + connect(this, &FMCOMMS5::readRequested, rcDcOffsetTrackingEn, &IIOWidget::readAsync); + + // bb_dc_offset_tracking_en + IIOWidget *bbDcOffsetTrackingEn = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("bb_dc_offset_tracking_en") + .title("BB DC") + .uiStrategy(IIOWidgetBuilder::CheckBoxUi) + .buildSingle(); + layout->addWidget(bbDcOffsetTrackingEn, 2, 5); + bbDcOffsetTrackingEn->showProgressBar(false); + connect(this, &FMCOMMS5::readRequested, bbDcOffsetTrackingEn, &IIOWidget::readAsync); + + mainLayout->addLayout(layout); + + QHBoxLayout *rxDeviceLayout = new QHBoxLayout(); + rxDeviceLayout->setMargin(0); + rxDeviceLayout->setSpacing(10); + + QWidget *rxDeviceWidget = m_helper->generateRxDeviceWidget(dev, "ad9361-phy", widget); + + rxDeviceLayout->addWidget(rxDeviceWidget); + rxDeviceWidget->layout()->addWidget(m_helper->generateRxChannelWidget(voltage0, "RX 1", rxDeviceWidget)); + iio_channel *voltage1 = iio_device_find_channel(dev, "voltage1", isOutput); + if(voltage1 && iio_channel_find_attr(voltage1, "hardwaregain")) { + rxDeviceWidget->layout()->addWidget( + m_helper->generateRxChannelWidget(voltage1, "RX 2", rxDeviceWidget)); + } + + //////// second device for fmcomms5 RX + + iio_device *dev2 = nullptr; + int device_count = iio_context_get_devices_count(m_ctx); + for(int i = 0; i < device_count; ++i) { + iio_device *aux = iio_context_get_device(m_ctx, i); + const char *dev_name = iio_device_get_name(aux); + if(dev_name && QString(dev_name).contains("ad9361-phy-B", Qt::CaseInsensitive)) { + dev2 = aux; + break; + } + } + + QWidget *rxDevice2Widget = m_helper->generateRxDeviceWidget(dev2, "ad9361-phy-B", widget); + + rxDeviceLayout->addWidget(rxDevice2Widget); + + iio_channel *voltage0B = iio_device_find_channel(dev2, "voltage0", isOutput); + + rxDevice2Widget->layout()->addWidget(m_helper->generateRxChannelWidget(voltage0B, "RX 3", rxDevice2Widget)); + iio_channel *voltage1B = iio_device_find_channel(dev2, "voltage1", isOutput); + if(voltage1 && iio_channel_find_attr(voltage1B, "hardwaregain")) { + rxDevice2Widget->layout()->addWidget( + m_helper->generateRxChannelWidget(voltage1B, "RX 4", rxDevice2Widget)); + } + + ///////////////////////// + + mainLayout->addLayout(rxDeviceLayout); + mainLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); + + return widget; +} + +QWidget *FMCOMMS5::generateTxChainWidget(iio_device *dev, QString title, QWidget *parent) +{ + QWidget *widget = new QWidget(parent); + Style::setBackgroundColor(widget, json::theme::background_primary); + Style::setStyle(widget, style::properties::widget::border_interactive); + + QVBoxLayout *layout = new QVBoxLayout(widget); + widget->setLayout(layout); + + QLabel *titleLabel = new QLabel(title, widget); + Style::setStyle(titleLabel, style::properties::label::menuBig); + layout->addWidget(titleLabel); + + QGridLayout *lay = new QGridLayout(); + + bool isOutput = true; + iio_channel *voltage0 = iio_device_find_channel(dev, "voltage0", isOutput); + + // voltage0: rf_bandwidth + IIOWidget *rfBandwidth = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_bandwidth") + .optionsAttribute("rf_bandwidth_available") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .title("RF Bandwidth(MHz)") + .buildSingle(); + lay->addWidget(rfBandwidth, 0, 0, 2, 1); + connect(this, &FMCOMMS5::readRequested, rfBandwidth, &IIOWidget::readAsync); + + // voltage0: sampling_frequency + IIOWidget *samplingFrequency = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("sampling_frequency") + .optionsAttribute("sampling_frequency_available") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .title("Sampling Rate(MSPS)") + .buildSingle(); + lay->addWidget(samplingFrequency, 0, 1, 2, 1); + connect(this, &FMCOMMS5::readRequested, samplingFrequency, &IIOWidget::readAsync); + + // voltage0: rf_port_select + IIOWidget *rfPortSelect = IIOWidgetBuilder(widget) + .channel(voltage0) + .attribute("rf_port_select") + .optionsAttribute("rf_port_select_available") + .uiStrategy(IIOWidgetBuilder::ComboUi) + .title("RF Port Select") + .buildSingle(); + lay->addWidget(rfPortSelect, 0, 2, 2, 1); + connect(this, &FMCOMMS5::readRequested, rfPortSelect, &IIOWidget::readAsync); + + layout->addLayout(lay); + + QHBoxLayout *txWidgetsLayout = new QHBoxLayout(); + txWidgetsLayout->setMargin(0); + txWidgetsLayout->setSpacing(10); + + QWidget *txDeviceWidget = m_helper->generateTxDeviceWidget(dev, "ad9361-phy", widget); + + txWidgetsLayout->addWidget(txDeviceWidget); + + txDeviceWidget->layout()->addWidget(m_helper->generateTxChannelWidget(voltage0, "TX 1", txDeviceWidget)); + iio_channel *voltage1 = iio_device_find_channel(dev, "voltage1", isOutput); + if(voltage1 && iio_channel_find_attr(voltage1, "hardwaregain")) { + txDeviceWidget->layout()->addWidget( + m_helper->generateTxChannelWidget(voltage1, "TX 2", txDeviceWidget)); + } + + //////// second device for fmcomms5 TX + + iio_device *dev2 = nullptr; + int device_count = iio_context_get_devices_count(m_ctx); + for(int i = 0; i < device_count; ++i) { + iio_device *aux = iio_context_get_device(m_ctx, i); + const char *dev_name = iio_device_get_name(aux); + if(dev_name && QString(dev_name).contains("ad9361-phy-b", Qt::CaseInsensitive)) { + dev2 = aux; + break; + } + } + + QWidget *txDevice2Widget = m_helper->generateTxDeviceWidget(dev2, "ad9361-phy-B", widget); + + txWidgetsLayout->addWidget(txDevice2Widget); + + iio_channel *voltage0B = iio_device_find_channel(dev2, "voltage0", isOutput); + + txDevice2Widget->layout()->addWidget(m_helper->generateTxChannelWidget(voltage0B, "TX 3", txDevice2Widget)); + iio_channel *voltage1B = iio_device_find_channel(dev2, "voltage1", isOutput); + if(voltage1 && iio_channel_find_attr(voltage1B, "hardwaregain")) { + txDevice2Widget->layout()->addWidget( + m_helper->generateTxChannelWidget(voltage1B, "TX 4", txDevice2Widget)); + } + + ///////////////////////// + + layout->addLayout(txWidgetsLayout); + layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); + + return widget; +} diff --git a/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5advanced.cpp b/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5advanced.cpp new file mode 100644 index 0000000000..f3eeba5446 --- /dev/null +++ b/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5advanced.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "fmcomms5/fmcomms5advanced.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(CAT_FMCOMMS5_ADVANCED, "FMCOMMS5_ADVANCED") + +using namespace scopy; +using namespace ad936x; + +Fmcomms5Advanced::Fmcomms5Advanced(iio_context *ctx, QWidget *parent) + : m_ctx(ctx) + , QWidget{parent} +{ + m_mainLayout = new QVBoxLayout(this); + m_mainLayout->setMargin(0); + m_mainLayout->setContentsMargins(0, 0, 0, 0); + + m_tool = new ToolTemplate(this); + m_tool->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_tool->topContainer()->setVisible(true); + m_tool->bottomContainer()->setVisible(true); + m_tool->topContainerMenuControl()->setVisible(false); + + m_mainLayout->addWidget(m_tool); + + m_refreshButton = new AnimatedRefreshBtn(false, this); + m_tool->addWidgetToTopContainerHelper(m_refreshButton, TTA_RIGHT); + + connect(m_refreshButton, &QPushButton::clicked, this, [this]() { + m_refreshButton->startAnimation(); + + QFutureWatcher *watcher = new QFutureWatcher(this); + connect( + watcher, &QFutureWatcher::finished, this, + [this, watcher]() { + m_refreshButton->stopAnimation(); + watcher->deleteLater(); + }, + Qt::QueuedConnection); + + QFuture future = QtConcurrent::run([this]() { Q_EMIT readRequested(); }); + + watcher->setFuture(future); + }); + + // main widget body + + m_isToolInitialized = false; + + QStackedWidget *centralWidget = new QStackedWidget(this); + m_tool->addWidgetToCentralContainerHelper(centralWidget); + + QButtonGroup *navigationButtons = new QButtonGroup(this); + navigationButtons->setExclusive(true); + + if(m_ctx != nullptr) { + iio_device *mainDevice = nullptr; + iio_device *secondDevice = nullptr; + int foundDevices = 0; + int device_count = iio_context_get_devices_count(m_ctx); + for(int i = 0; i < device_count; ++i) { + iio_device *dev = iio_context_get_device(m_ctx, i); + const char *dev_name = iio_device_get_name(dev); + if(dev_name && QString(dev_name).compare("ad9361-phy", Qt::CaseInsensitive) == 0) { + mainDevice = dev; + foundDevices++; + } + + if(dev_name && QString(dev_name).compare("ad9361-phy-B", Qt::CaseInsensitive) == 0) { + secondDevice = dev; + foundDevices++; + } + + if(foundDevices == 2) { + break; + } + } + if(mainDevice == nullptr) { + qWarning(CAT_FMCOMMS5_ADVANCED) << "No ad9361-phy device found in context!"; + return; + } + + if(secondDevice == nullptr) { + qWarning(CAT_FMCOMMS5_ADVANCED) << "No ad9361-phy-B device found in context!"; + return; + } + + m_mainDevice = mainDevice; + m_secondDevice = secondDevice; + m_centralWidget = centralWidget; + + // Create buttons + m_ensmModeClocksBtn = new QPushButton("ENSM/Mode/Clocks", this); + Style::setStyle(m_ensmModeClocksBtn, style::properties::button::blueGrayButton); + m_ensmModeClocksBtn->setCheckable(true); + m_ensmModeClocksBtn->setChecked(true); + m_eLnaBtn = new QPushButton("eLNA", this); + Style::setStyle(m_eLnaBtn, style::properties::button::blueGrayButton); + m_eLnaBtn->setCheckable(true); + m_rssiBtn = new QPushButton("RSSI", this); + Style::setStyle(m_rssiBtn, style::properties::button::blueGrayButton); + m_rssiBtn->setCheckable(true); + m_gainBtn = new QPushButton("GAIN", this); + Style::setStyle(m_gainBtn, style::properties::button::blueGrayButton); + m_gainBtn->setCheckable(true); + m_txMonitorBtn = new QPushButton("TX MONITOR", this); + Style::setStyle(m_txMonitorBtn, style::properties::button::blueGrayButton); + m_txMonitorBtn->setCheckable(true); + m_auxAdcDacIioBtn = new QPushButton("Aux ADC/DAC/IIO", this); + Style::setStyle(m_auxAdcDacIioBtn, style::properties::button::blueGrayButton); + m_auxAdcDacIioBtn->setCheckable(true); + m_miscBtn = new QPushButton("MISC", this); + Style::setStyle(m_miscBtn, style::properties::button::blueGrayButton); + m_miscBtn->setCheckable(true); + m_bistBtn = new QPushButton("BIST", this); + Style::setStyle(m_bistBtn, style::properties::button::blueGrayButton); + m_bistBtn->setCheckable(true); + m_fmcomms5Btn = new QPushButton("FMCOMMS5", this); + Style::setStyle(m_fmcomms5Btn, style::properties::button::blueGrayButton); + m_fmcomms5Btn->setCheckable(true); + + navigationButtons->addButton(m_ensmModeClocksBtn); + navigationButtons->addButton(m_eLnaBtn); + navigationButtons->addButton(m_rssiBtn); + navigationButtons->addButton(m_gainBtn); + navigationButtons->addButton(m_txMonitorBtn); + navigationButtons->addButton(m_auxAdcDacIioBtn); + navigationButtons->addButton(m_miscBtn); + navigationButtons->addButton(m_bistBtn); + navigationButtons->addButton(m_fmcomms5Btn); + + m_tool->addWidgetToTopContainerHelper(m_ensmModeClocksBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(m_eLnaBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(m_rssiBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(m_gainBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(m_txMonitorBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(m_auxAdcDacIioBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(m_miscBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(m_bistBtn, TTA_LEFT); + m_tool->addWidgetToTopContainerHelper(m_fmcomms5Btn, TTA_LEFT); + + m_syncBtn = new QPushButton("MSC Sync", this); + Style::setStyle(m_syncBtn, style::properties::button::basicButton); + connect(m_syncBtn, &QPushButton::clicked, this, [=]() { + // call to lib ad9361 + ad9361_multichip_sync(m_mainDevice, &m_secondDevice, 1, + FIXUP_INTERFACE_TIMING | CHECK_SAMPLE_RATES); + }); + + m_tool->addWidgetToBottomContainerHelper(m_syncBtn, TTA_LEFT); + + bool useLazyLoading = scopy::Preferences::get("iiowidgets_use_lazy_loading").toBool(); + if(!useLazyLoading) { + init(); + } + } +} + +Fmcomms5Advanced::~Fmcomms5Advanced() {} + +void Fmcomms5Advanced::showEvent(QShowEvent *event) +{ + + if(!m_isToolInitialized) { + bool useLazyLoading = scopy::Preferences::get("iiowidgets_use_lazy_loading").toBool(); + if(useLazyLoading) { + init(); + } + } + QWidget::showEvent(event); +} + +void Fmcomms5Advanced::init() +{ + + // ENSM Mode Clocks + m_ensmModeClocks = new EnsmModeClocksWidget(m_mainDevice, m_centralWidget); + m_centralWidget->addWidget(m_ensmModeClocks); + connect(this, &Fmcomms5Advanced::readRequested, m_ensmModeClocks, &EnsmModeClocksWidget::readRequested); + connect(m_ensmModeClocksBtn, &QPushButton::clicked, this, + [=, this]() { m_centralWidget->setCurrentWidget(m_ensmModeClocks); }); + // eLNA + m_elna = new ElnaWidget(m_mainDevice, m_centralWidget); + connect(this, &Fmcomms5Advanced::readRequested, m_elna, &ElnaWidget::readRequested); + m_centralWidget->addWidget(m_elna); + connect(m_eLnaBtn, &QPushButton::clicked, this, [=, this]() { m_centralWidget->setCurrentWidget(m_elna); }); + // RSSI + m_rssi = new RssiWidget(m_mainDevice, m_centralWidget); + connect(this, &Fmcomms5Advanced::readRequested, m_rssi, &RssiWidget::readRequested); + m_centralWidget->addWidget(m_rssi); + connect(m_rssiBtn, &QPushButton::clicked, this, [=, this]() { m_centralWidget->setCurrentWidget(m_rssi); }); + // GAIN + m_gainWidget = new GainWidget(m_mainDevice, m_centralWidget); + connect(this, &Fmcomms5Advanced::readRequested, m_gainWidget, &GainWidget::readRequested); + m_centralWidget->addWidget(m_gainWidget); + connect(m_gainBtn, &QPushButton::clicked, this, + [=, this]() { m_centralWidget->setCurrentWidget(m_gainWidget); }); + // TX MONITOR + m_txMonitor = new TxMonitorWidget(m_mainDevice, m_centralWidget); + connect(this, &Fmcomms5Advanced::readRequested, m_txMonitor, &TxMonitorWidget::readRequested); + m_centralWidget->addWidget(m_txMonitor); + connect(m_txMonitorBtn, &QPushButton::clicked, this, + [=, this]() { m_centralWidget->setCurrentWidget(m_txMonitor); }); + // AUX ADC/DAC/IIO + m_auxAdcDacIo = new AuxAdcDacIoWidget(m_mainDevice, m_centralWidget); + connect(this, &Fmcomms5Advanced::readRequested, m_auxAdcDacIo, &AuxAdcDacIoWidget::readRequested); + m_centralWidget->addWidget(m_auxAdcDacIo); + connect(m_auxAdcDacIioBtn, &QPushButton::clicked, this, + [=, this]() { m_centralWidget->setCurrentWidget(m_auxAdcDacIo); }); + // MISC + m_misc = new MiscWidget(m_mainDevice, m_centralWidget); + connect(this, &Fmcomms5Advanced::readRequested, m_misc, &MiscWidget::readRequested); + m_centralWidget->addWidget(m_misc); + connect(m_miscBtn, &QPushButton::clicked, this, [=, this]() { m_centralWidget->setCurrentWidget(m_misc); }); + // BIST + m_bist = new BistWidget(m_mainDevice, m_centralWidget); + connect(this, &Fmcomms5Advanced::readRequested, m_bist, &BistWidget::readRequested); + m_centralWidget->addWidget(m_bist); + connect(m_bistBtn, &QPushButton::clicked, this, [=, this]() { m_centralWidget->setCurrentWidget(m_bist); }); + + // FMCOMMS5 + m_fmcomms5 = new Fmcomms5Tab(m_ctx, m_centralWidget); + connect(this, &Fmcomms5Advanced::readRequested, m_fmcomms5, &Fmcomms5Tab::readRequested); + m_centralWidget->addWidget(m_fmcomms5); + connect(m_fmcomms5Btn, &QPushButton::clicked, this, + [=, this]() { m_centralWidget->setCurrentWidget(m_fmcomms5); }); + + m_isToolInitialized = true; +} + +void Fmcomms5Advanced::ad9361MultichipSync() {} diff --git a/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5calibration.cpp b/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5calibration.cpp new file mode 100644 index 0000000000..b72abbf631 --- /dev/null +++ b/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5calibration.cpp @@ -0,0 +1,611 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "fmcomms5/fmcomms5calibration.h" +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +Q_LOGGING_CATEGORY(CAT_FMCOMMS5_CALIBRATION, "CAT_FMCOMMS5_CALIBRATION") + +using namespace scopy; +using namespace ad936x; + +Fmcomms5Calibration::Fmcomms5Calibration(iio_context *ctx, QObject *parent) + : m_ctx(ctx) + , QObject{parent} +{ + m_mainDevice = iio_context_find_device(m_ctx, "ad9361-phy"); + m_secondDevice = iio_context_find_device(m_ctx, "ad9361-phy-B"); + + if(!m_mainDevice) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "No ad9361-phy device found in context!"; + return; + } + + if(!m_secondDevice) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "No ad9361-phy-B device found in context!"; + return; + } + + // find capture core + m_cf_ad9361_lpc = iio_context_find_device(m_ctx, CAP_DEVICE_ALT); + m_cf_ad9361_hpc = iio_context_find_device(m_ctx, CAP_SLAVE_DEVICE); + + if(!m_cf_ad9361_lpc || !m_cf_ad9361_hpc) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Could not find capture cores"; + return; + } + + // find DDS main and second devices + m_ddsMain = iio_context_find_device(m_ctx, DDS_DEVICE); + m_ddsSecond = iio_context_find_device(m_ctx, DDS_SLAVE_DEVICE); + + if(!m_ddsMain || !m_ddsSecond) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Could not find dds cores"; + return; + } +} + +void Fmcomms5Calibration::calibrate() +{ + int ret = 0; + double rx_phase_lpc = 0, rx_phase_hpc = 0, tx_phase_hpc = 0; + long long cal_tone = 0, cal_freq = 0; + int samples = 0; + + iio_channel *in0 = iio_device_find_channel(m_mainDevice, "voltage0", false); + iio_channel *in0B = iio_device_find_channel(m_secondDevice, "voltage0", false); + + if(!in0 || !in0B) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Could not find channels"; + ret = -ENODEV; + calibrationFail(ret); + return; + } + + if(!m_cf_ad9361_lpc || !m_cf_ad9361_hpc) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Could not find capture cores"; + ret = -ENODEV; + calibrationFail(ret); + return; + } + + if(!m_ddsMain || !m_ddsSecond) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Could not find dds cores"; + ret = -ENODEV; + calibrationFail(ret); + return; + } + + Q_EMIT updateCalibrationProgress(0); + + ////////////////set some logical defaults / assumptions /////////////////// + ret = defaultDds(getCalTone(), CAL_SCALE); + + if(ret < 0) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Could not set dds cores"; + calibrationFail(ret); + } + //////////////////////////////// + + // Read calibration tone and frequency + iio_channel *dds_ch = iio_device_find_channel(m_ddsMain, "altvoltage0", true); + if(!dds_ch) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Could not find DDS channel"; + calibrationFail(ret); + return; + } + iio_channel_attr_read_longlong(dds_ch, "frequency", &cal_tone); + iio_channel_attr_read_longlong(dds_ch, "sampling_frequency", &cal_freq); + + samples = getCalSamples(cal_tone, cal_freq); + + // Turn off quadrature tracking + iio_channel_attr_write(in0, "quadrature_tracking_en", "0"); + iio_channel_attr_write(in0B, "quadrature_tracking_en", "0"); + + // Reset any Tx rotation to zero + trxPhaseRottation(m_cf_ad9361_lpc, 0.0); + trxPhaseRottation(m_cf_ad9361_hpc, 0.0); + + Q_EMIT updateCalibrationProgress(16); + + // Calibration sequence + // 1. Calibrate RX: TX1B_B (HPC) -> RX1C_B (HPC) + callSwitchPortsEnableCb(1); + rx_phase_hpc = + tuneTrxPhaseOffset(m_cf_ad9361_hpc, // cf_ad9361_hpc + &ret, // Error/status pointer + cal_freq, // Calibration frequency + cal_tone, // Calibration tone + 1.0, // Sign + [this](iio_device *dev, double phase) { this->trxPhaseRottation(dev, phase); }); + + if(ret < 0) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Failed to tune phase"; + calibrationFail(ret); + return; + } + + Q_EMIT updateCalibrationProgress(40); + + // 2. Calibrate RX: TX1B_B (HPC) -> RX1C_A (LPC) + callSwitchPortsEnableCb(3); + trxPhaseRottation(m_mainDevice, 0.0); + rx_phase_lpc = + tuneTrxPhaseOffset(m_cf_ad9361_lpc, // cf_ad9361_lpc + &ret, // Error/status pointer + cal_freq, // Calibration frequency + cal_tone, // Calibration tone + 1.0, // Sign + [this](iio_device *dev, double phase) { this->trxPhaseRottation(dev, phase); }); + + if(ret < 0) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Failed to tune phase"; + calibrationFail(ret); + return; + } + + Q_EMIT updateCalibrationProgress(64); + + // 3. Calibrate TX: TX1B_A (LPC) -> RX1C_A (LPC) + callSwitchPortsEnableCb(4); + trxPhaseRottation(m_mainDevice, 0.0); + tx_phase_hpc = + tuneTrxPhaseOffset(m_ddsSecond, // m_ddsSecond + &ret, // Error/status pointer + cal_freq, // Calibration frequency + cal_tone, // Calibration tone + -1.0, // Sign + [this](iio_device *dev, double phase) { this->trxPhaseRottation(dev, phase); }); + + if(ret < 0) { + qWarning(CAT_FMCOMMS5_CALIBRATION) << "Failed to tune phase"; + calibrationFail(ret); + return; + } + + Q_EMIT updateCalibrationProgress(88); + + // Restore phase rotation + trxPhaseRottation(m_cf_ad9361_hpc, rx_phase_hpc); + + // Restore calibration switch matrix to default + callSwitchPortsEnableCb(0); + + // Re-enable quadrature tracking + iio_channel_attr_write(in0, "quadrature_tracking_en", "1"); + iio_channel_attr_write(in0B, "quadrature_tracking_en", "1"); + + Q_EMIT updateCalibrationProgress(100); +} + +void Fmcomms5Calibration::resetCalibration() +{ + iio_channel *in0 = iio_device_find_channel(m_mainDevice, "voltage0", false); + iio_channel *in0B = iio_device_find_channel(m_secondDevice, "voltage0", false); + + // Reset calibration corrections to zero/default + iio_channel_attr_write(in0, "calibphase", "0"); + iio_channel_attr_write(in0B, "calibphase", "0"); + iio_channel_attr_write(in0, "calibscale", "0"); + iio_channel_attr_write(in0B, "calibscale", "0"); + + // Reset calibration switch matrix as well + iio_device_attr_write(m_mainDevice, "calibration_switch_control", "0"); +} + +void Fmcomms5Calibration::calibrationFail(int ret) +{ + // Restore calibration switch matrix to default + callSwitchPortsEnableCb(0); + + // Re-enable quadrature tracking + iio_channel *in0 = iio_device_find_channel(m_mainDevice, "voltage0", false); + iio_channel *in0B = iio_device_find_channel(m_secondDevice, "voltage0", false); + if(in0) + iio_channel_attr_write(in0, "quadrature_tracking_en", "1"); + if(in0B) + iio_channel_attr_write(in0B, "quadrature_tracking_en", "1"); + // Reset progress + Q_EMIT updateCalibrationProgress(0); + + qWarning(CAT_FMCOMMS5_CALIBRATION) << QString("Calibration failed with error code: %1").arg(ret); + Q_EMIT calibrationFailed(); +} + +double Fmcomms5Calibration::tuneTrxPhaseOffset(iio_device *ldev, int *ret, long long cal_freq, long long cal_tone, + double sign, std::function tune) +{ + // https://github.com/analogdevicesinc/iio-oscilloscope/blob/7a672e3e3e86aeb4fea2e594acff844010afe6fa/plugins/fmcomms2_adv.c#L755 + double offset = 0.0, mag = 0.0; + double phase = 0.0, increment = 0.0; + + for(int i = 0; i < 10; i++) { + getMarkers(&offset, &mag); + getMarkers(&offset, &mag); + + increment = calcPhaseOffset(cal_freq, cal_tone, offset, mag); + increment *= sign; + + phase += increment; + + phase = scalePhase0360(phase); + tune(ldev, phase); + + qDebug(CAT_FMCOMMS5_CALIBRATION) << "Step:" << i << "increment" << increment << "Phase:" << phase; + + if(std::fabs(offset) < 0.001) + break; + } + + if(std::fabs(offset) > 0.1) + *ret = -EFAULT; + else + *ret = 0; + + return phase * sign; +} + +void Fmcomms5Calibration::getMarkers(double *offset, double *mag) +{ + /// https://github.com/analogdevicesinc/iio-oscilloscope/blob/7a672e3e3e86aeb4fea2e594acff844010afe6fa/plugins/fmcomms2_adv.c#L641 + constexpr int avg_count = MARKER_AVG; + double acc_offset = 0; + double acc_mag = 0; + + if(offset) + *offset = 0; + if(mag) + *mag = 0; + + for(int sum = 0; sum < avg_count; ++sum) { + auto markers = getMarkersFromCrossCorrelation(); + if(!markers.empty()) { + acc_offset += markers[0].offset; + acc_mag += markers[0].magnitude; + } + } + + if(offset) + *offset = acc_offset / avg_count; + if(mag) + *mag = acc_mag / avg_count; + + qDebug(CAT_FMCOMMS5_CALIBRATION) << "getMarkers: averaged offset =" << *offset << ", mag =" << *mag; +} + +std::vector Fmcomms5Calibration::getMarkersFromCrossCorrelation() +{ + const char *channel_names[] = {"voltage0", "voltage1", "voltage4", "voltage5"}; + constexpr size_t BUFFER_SAMPLES = 2048; + std::vector results; + + iio_device *dev = m_cf_ad9361_lpc; + if(!dev) { + qWarning() << "Device not found!"; + return results; + } + + // Find and enable channels + std::vector channels; + for(const char *chname : channel_names) { + iio_channel *ch = iio_device_find_channel(dev, chname, false); + if(!ch) { + qWarning() << "Channel" << chname << "not found!"; + return results; + } + iio_channel_enable(ch); + channels.push_back(ch); + } + + // Create buffer + iio_buffer *buf = iio_device_create_buffer(dev, BUFFER_SAMPLES, false); + if(!buf) { + qWarning() << "Failed to create buffer:" << strerror(errno) << "(errno:" << errno << ")"; + return results; + } + + // Refill buffer + ssize_t nbytes = iio_buffer_refill(buf); + if(nbytes <= 0) { + qWarning() << "Buffer refill failed:" << strerror(errno) << "(errno:" << errno << ")"; + iio_buffer_destroy(buf); + return results; + } + + // Extract data for each channel + std::vector> data(4, std::vector(BUFFER_SAMPLES)); + for(int c = 0; c < 4; ++c) { + int16_t *samples = (int16_t *)iio_buffer_first(buf, channels[c]); + ptrdiff_t step = iio_buffer_step(buf) / sizeof(int16_t); + for(size_t i = 0; i < BUFFER_SAMPLES; ++i) { + data[c][i] = (double)samples[i * step]; + } + } + + // Compute cross-correlation for pairs: (0,1) and (2,3) + for(int pair = 0; pair < 2; ++pair) { + const std::vector &dataA = data[pair * 2]; + const std::vector &dataB = data[pair * 2 + 1]; + std::vector xcorr(BUFFER_SAMPLES, 0.0); + + for(size_t lag = 0; lag < BUFFER_SAMPLES; ++lag) { + double sum = 0.0; + for(size_t n = 0; n < BUFFER_SAMPLES - lag; ++n) { + sum += dataA[n] * dataB[n + lag]; + } + xcorr[lag] = sum; + } + + // Find peak in cross-correlation + double max_val = 0.0; + size_t max_idx = 0; + for(size_t j = 0; j < BUFFER_SAMPLES; ++j) { + double val = std::abs(xcorr[j]); + if(val > max_val) { + max_val = val; + max_idx = j; + } + } + + double marker_offset = static_cast(max_idx) / BUFFER_SAMPLES; + double marker_mag = max_val; + + results.push_back({marker_mag, marker_offset, pair * 2, pair * 2 + 1}); + qInfo() << "Marker" << pair << ": magnitude =" << marker_mag << "offset =" << marker_offset + << "channels:" << channel_names[pair * 2] << "vs" << channel_names[pair * 2 + 1]; + } + + iio_buffer_destroy(buf); + return results; +} + +double Fmcomms5Calibration::calcPhaseOffset(double fsample, double dds_freq, double offset, double mag) +{ + double val = 360.0 / ((fsample / dds_freq) / offset); + + if(mag < 0) + val += 180.0; + + return scalePhase0360(val); +} + +double Fmcomms5Calibration::scalePhase0360(double val) +{ + if(val >= 360.0) + val -= 360.0; + + if(val < 0) + val += 360.0; + + return val; +} + +int Fmcomms5Calibration::defaultDds(long long freq, double scale) +{ + int ret = 0; + iio_device *dds_devs[2] = {m_ddsMain, m_ddsSecond}; + for(int i = 0; i < 2; ++i) { + iio_device *dev = dds_devs[i]; + if(!dev) { + qWarning(CAT_FMCOMMS5_CALIBRATION) + << "DDS device" << (i == 0 ? DDS_DEVICE : DDS_SLAVE_DEVICE) << "not found!"; + ret |= -ENODEV; + continue; + } + for(int j = 0; j < 8; ++j) { + iio_channel *ch = iio_device_find_channel(dev, ddsChannelNames[j], true); + if(!ch) { + qWarning(CAT_FMCOMMS5_CALIBRATION) + << "DDS channel" << ddsChannelNames[j] << "not found on device" + << (i == 0 ? DDS_DEVICE : DDS_SLAVE_DEVICE); + ret |= -ENODEV; + continue; + } + ret |= iio_channel_attr_write_longlong(ch, "frequency", freq); + ret |= iio_channel_attr_write_double(ch, "scale", scale); + } + ddsTxPhaseRotation(dev, 0.0); + trxPhaseRottation(dev, 0.0); + } + return ret; +} + +void Fmcomms5Calibration::ddsTxPhaseRotation(iio_device *dev, double val) +{ + // Calculate I and Q phases (scaled by 1000) + long long i = scalePhase0360(val + 90.0) * 1000; + long long q = scalePhase0360(val) * 1000; + + // Loop over 8 DDS outputs + for(int j = 0; j < 8; ++j) { + iio_channel *ch = iio_device_find_channel(dev, ddsChannelNames[j], true); + if(!ch) + continue; + + if(j == 0 || j == 1 || j == 4 || j == 5) { + iio_channel_attr_write_longlong(ch, "phase", i); + } else { + iio_channel_attr_write_longlong(ch, "phase", q); + } + } +} + +void Fmcomms5Calibration::trxPhaseRottation(iio_device *dev, double val) +{ + double phase = val * 2 * M_PI / 360.0; + double vcos = std::cos(phase); + double vsin = std::sin(phase); + + bool output = (dev == m_mainDevice) || (dev == m_secondDevice); + + // Correction factor for output devices + if(output) { + double corr = 1.0 / + std::fmax(std::fabs(std::sin(phase) + std::cos(phase)), + std::fabs(std::cos(phase) - std::sin(phase))); + vcos *= corr; + vsin *= corr; + } + + // Set both RX1 and RX2 + for(unsigned offset = 0; offset <= 2; offset += 2) { + iio_channel *out0 = iio_device_find_channel(dev, offset == 2 ? "voltage2" : "voltage0", output); + iio_channel *out1 = iio_device_find_channel(dev, offset == 2 ? "voltage3" : "voltage1", output); + + if(out0 && out1) { + iio_channel_attr_write_double(out0, "calibscale", vcos); + iio_channel_attr_write_double(out0, "calibphase", -1.0 * vsin); + iio_channel_attr_write_double(out1, "calibscale", vcos); + iio_channel_attr_write_double(out1, "calibphase", vsin); + } + } +} + +unsigned int Fmcomms5Calibration::getCalTone() +{ + unsigned freq; + const char *cal_tone = getenv("CAL_TONE"); + + if(!cal_tone) + return CAL_TONE; + + freq = atoi(cal_tone); + + if(freq > 0 && freq < 31000000) + return freq; + + return CAL_TONE; +} + +int Fmcomms5Calibration::getCalSamples(long long calTone, long long calFreq) +{ + int samples, env_samples; + const char *cal_samples = getenv("CAL_SAMPLES"); + + samples = std::exp2(std::ceil(log2(calFreq / calTone)) + 2); + + if(!cal_samples) + return samples; + + env_samples = std::atoi(cal_samples); + + if(env_samples < samples) + return samples; + + return env_samples; +} + +void Fmcomms5Calibration::callSwitchPortsEnableCb(int val) +{ + // Map input value to switch settings + unsigned lp_slave = 0, lp_master = 0, sw = 0; + QString tx_port = "A"; + QString rx_port = "A_BALANCED"; + + /* + * 0 DISABLE + * 1 TX1B_B (HPC) -> RX1C_B (HPC) : BIST_LOOPBACK on A + * 2 TX1B_A (LPC) -> RX1C_B (HPC) : BIST_LOOPBACK on A + * 3 TX1B_B (HPC) -> RX1C_A (LPC) : BIST_LOOPBACK on B + * 4 TX1B_A (LPC) -> RX1C_A (LPC) : BIST_LOOPBACK on B + * + */ + + switch(val) { + default: + case 0: + lp_slave = 0; + lp_master = 0; + sw = 0; + tx_port = "A"; + rx_port = "A_BALANCED"; + break; + case 1: + case 2: + lp_slave = 0; + lp_master = 1; + sw = val - 1; + tx_port = "B"; + rx_port = "C_BALANCED"; + break; + case 3: + case 4: + lp_slave = 1; + lp_master = 0; + sw = val - 1; + tx_port = "B"; + rx_port = "C_BALANCED"; + break; + } + + nearEndLoopbackCtrl(0, lp_slave); // HPC + nearEndLoopbackCtrl(1, lp_slave); // HPC + nearEndLoopbackCtrl(4, lp_master); // LPC + nearEndLoopbackCtrl(5, lp_master); // LPC + + // Set calibration switch control + iio_device_attr_write(m_mainDevice, "calibration_switch_control", QString::number(sw).toUtf8().constData()); + + // Set RF port select for RX and TX on master device + iio_channel *rx_ch = iio_device_find_channel(m_mainDevice, "voltage0", false); + iio_channel *tx_ch = iio_device_find_channel(m_mainDevice, "voltage0", true); + if(rx_ch) + iio_channel_attr_write(rx_ch, "rf_port_select", rx_port.toUtf8().constData()); + if(tx_ch) + iio_channel_attr_write(tx_ch, "rf_port_select", tx_port.toUtf8().constData()); + + // Set RF port select for RX and TX on slave device + if(m_secondDevice) { + iio_channel *rx_chB = iio_device_find_channel(m_secondDevice, "voltage0", false); + iio_channel *tx_chB = iio_device_find_channel(m_secondDevice, "voltage0", true); + if(rx_chB) + iio_channel_attr_write(rx_chB, "rf_port_select", rx_port.toUtf8().constData()); + if(tx_chB) + iio_channel_attr_write(tx_chB, "rf_port_select", tx_port.toUtf8().constData()); + } +} + +void Fmcomms5Calibration::nearEndLoopbackCtrl(unsigned int channel, bool enable) +{ + unsigned tmp; + struct iio_device *dev = (channel > 3) ? m_cf_ad9361_lpc : m_cf_ad9361_hpc; + if(!dev) + return; + + if(channel > 3) + channel -= 4; + + if(iio_device_reg_read(dev, 0x80000418 + channel * 0x40, &tmp)) + return; + + if(enable) + tmp |= 0x1; + else + tmp &= ~0xF; + + iio_device_reg_write(dev, 0x80000418 + channel * 0x40, tmp); +} diff --git a/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5tab.cpp b/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5tab.cpp new file mode 100644 index 0000000000..c73a1b1b32 --- /dev/null +++ b/packages/ad936x/plugins/ad936x/src/fmcomms5/fmcomms5tab.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2025 Analog Devices Inc. + * + * This file is part of Scopy + * (see https://www.github.com/analogdevicesinc/scopy). + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "fmcomms5/fmcomms5tab.h" + +#include +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(CAT_FMCOMMS5_TAB, "FMCOMMS5_TAB") + +using namespace scopy; +using namespace ad936x; + +Fmcomms5Tab::Fmcomms5Tab(iio_context *ctx, QWidget *parent) + : m_ctx(ctx) + , QWidget{parent} +{ + + Style::setBackgroundColor(this, json::theme::background_primary); + + m_layout = new QVBoxLayout(this); + m_layout->setMargin(0); + m_layout->setContentsMargins(0, 0, 0, 0); + setLayout(m_layout); + + QWidget *widget = new QWidget(this); + QVBoxLayout *layout = new QVBoxLayout(widget); + widget->setLayout(layout); + layout->setContentsMargins(5, 5, 5, 5); + layout->setSpacing(10); + + m_layout->addWidget(widget); + + Style::setStyle(widget, style::properties::widget::border_interactive); + + QLabel *title = new QLabel("CAL Switch Control", widget); + Style::setStyle(title, style::properties::label::menuBig); + layout->addWidget(title); + + Fmcomms5Calibration *calibration = new Fmcomms5Calibration(ctx, this); + + QComboBox *calSwitchControl = new QComboBox(widget); + + calSwitchControl->addItem("DISABLE"); + calSwitchControl->addItem("TX1B_B->RX1C_B"); + calSwitchControl->addItem("TX1B_A->RX1C_B"); + calSwitchControl->addItem("TX1B_B->RX1C_A"); + calSwitchControl->addItem("TX1B_A->RX1C_A"); + + layout->addWidget(calSwitchControl); + + connect(calSwitchControl, QOverload::of(&QComboBox::currentIndexChanged), this, + [=](int idx) { calibration->callSwitchPortsEnableCb(idx); }); + + m_calibrateBtn = new QPushButton("Calibrate", this); + Style::setStyle(m_calibrateBtn, style::properties::button::basicButton); + + m_resetCalibrationBtn = new QPushButton("Reset Calibration", this); + Style::setStyle(m_resetCalibrationBtn, style::properties::button::basicButton); + connect(m_resetCalibrationBtn, &QPushButton::clicked, calibration, &Fmcomms5Calibration::resetCalibration); + + QHBoxLayout *calibBtnLayout = new QHBoxLayout(); + calibBtnLayout->setSpacing(10); + + calibBtnLayout->addWidget(m_calibrateBtn); + calibBtnLayout->addWidget(m_resetCalibrationBtn); + layout->addLayout(calibBtnLayout); + + m_calibProgressBar = new QProgressBar(this); + m_calibProgressBar->setRange(0, 100); + m_calibProgressBar->setValue(0); + + connect(calibration, &Fmcomms5Calibration::updateCalibrationProgress, m_calibProgressBar, + &QProgressBar::setValue); + + layout->addWidget(m_calibProgressBar); + + iio_device *mainDevice = iio_context_find_device(m_ctx, "ad9361-phy"); + + if(!mainDevice) { + qWarning(CAT_FMCOMMS5_TAB) << "No ad9361-phy device found in context!"; + return; + } + + // TX Phase + IIOWidget *txPhase = IIOWidgetBuilder(widget) + .device(mainDevice) + .attribute("calibration_switch_control") + .uiStrategy(IIOWidgetBuilder::RangeUi) + .optionsValues("[0 0.1 360]") + .title("TX Phase") + .buildSingle(); + layout->addWidget(txPhase); + + connect(m_calibrateBtn, &QPushButton::clicked, this, [=]() { + m_calibrateBtn->setEnabled(false); + calibration->calibrate(); + m_calibrateBtn->setEnabled(true); + txPhase->readAsync(); + }); + + m_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Preferred, QSizePolicy::Expanding)); +} + +Fmcomms5Tab::~Fmcomms5Tab() {}