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() {}