diff --git a/gui/style/json/Harmonic dark.json b/gui/style/json/Harmonic dark.json
index f0faaf4051..4e002173fd 100644
--- a/gui/style/json/Harmonic dark.json
+++ b/gui/style/json/Harmonic dark.json
@@ -10,10 +10,12 @@
"interactive_primary_hover": "#C6D8F6",
"interactive_primary_pressed": "#abc4ed",
"interactive_primary_disabled": "#0067B9",
+"interactive_primary_faint": "#00549A",
"interactive_secondary_idle": "#92BEFC",
"interactive_secondary_hover": "#C6D8F6",
"interactive_secondary_pressed": "#abc4ed",
"interactive_secondary_disabled": "#4B545D",
+"interactive_silent_hover": "#3A424B",
"content_default": "#FFFFFF",
"content_subtle": "#B7BBC3",
"content_silent": "#848B95",
diff --git a/gui/style/json/Harmonic light.json b/gui/style/json/Harmonic light.json
index ecf2f8de2d..e0a89ee08f 100644
--- a/gui/style/json/Harmonic light.json
+++ b/gui/style/json/Harmonic light.json
@@ -10,10 +10,12 @@
"interactive_primary_hover": "#00427A",
"interactive_primary_pressed": "#003969",
"interactive_primary_disabled": "#92BEFC",
+"interactive_primary_faint": "#C6D8F6",
"interactive_secondary_idle": "#0067B9",
"interactive_secondary_hover": "#00427A",
"interactive_secondary_pressed": "#003969",
-"interactive_secondary_disabled": "#d5d7db",
+"interactive_secondary_disabled": "#c1c3c7",
+"interactive_silent_hover": "#D5D8DC",
"content_default": "#101820",
"content_subtle": "#5E6773",
"content_silent": "#9FA4AD",
diff --git a/gui/style/json/Scopy.json b/gui/style/json/Scopy.json
index 35ea15834f..c0cf475cb5 100644
--- a/gui/style/json/Scopy.json
+++ b/gui/style/json/Scopy.json
@@ -10,10 +10,12 @@
"interactive_primary_hover": "#697EFF",
"interactive_primary_pressed": "#7084fa",
"interactive_primary_disabled": "#2E3874",
+"interactive_primary_faint": "#00549A",
"interactive_secondary_idle": "#5b5b6b",
"interactive_secondary_hover": "#535361",
"interactive_secondary_pressed": "#4c4c59",
"interactive_secondary_disabled": "#35353d",
+"interactive_silent_hover": "#3A424B",
"content_default": "#e1e1e3",
"content_subtle": "#bac2cc",
"content_silent": "#848491",
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 62b15af1a2..cfad0c99ad 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -33,6 +33,7 @@ option(ENABLE_PLUGIN_SWIOT "Enable SWIOT plugin" ON)
option(ENABLE_PLUGIN_PQM "Enable PQM plugin" ON)
option(ENABLE_PLUGIN_DATALOGGER "Enable DATALOGGER plugin" ON)
option(ENABLE_PLUGIN_DAC "Enable DAC plugin" ON)
+option(ENABLE_PLUGIN_ADMT "Enable ADMT plugin" ON)
if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${SCOPY_PLUGIN_BUILD_PATH})
@@ -125,6 +126,11 @@ if(ENABLE_PLUGIN_M2K)
endif()
endif()
+if(ENABLE_PLUGIN_ADMT)
+ add_subdirectory(admt)
+ list(APPEND PLUGINS ${ADMT_TARGET_NAME})
+endif()
+
install(TARGETS ${PLUGINS} LIBRARY DESTINATION ${SCOPY_PLUGIN_INSTALL_PATH})
set(PLUGINS ${PLUGINS} PARENT_SCOPE)
diff --git a/plugins/admt/.gitignore b/plugins/admt/.gitignore
new file mode 100644
index 0000000000..a4793c0f9c
--- /dev/null
+++ b/plugins/admt/.gitignore
@@ -0,0 +1,2 @@
+include/admt/scopy-admt_export.h
+include/admt/scopy-admt_config.h
\ No newline at end of file
diff --git a/plugins/admt/CMakeLists.txt b/plugins/admt/CMakeLists.txt
new file mode 100644
index 0000000000..ef93fc4933
--- /dev/null
+++ b/plugins/admt/CMakeLists.txt
@@ -0,0 +1,113 @@
+#
+# Copyright (c) 2024 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 .
+#
+
+cmake_minimum_required(VERSION 3.9)
+
+set(SCOPY_MODULE admt)
+
+message(STATUS "building plugin: " ${SCOPY_MODULE})
+
+project(scopy-${SCOPY_MODULE} VERSION 0.1 LANGUAGES CXX)
+
+set(PLUGIN_DISPLAY_NAME "ADMT")
+set(PLUGIN_DESCRIPTION "Plugin for ADMT Harmonic Calibration")
+
+include(GenerateExportHeader)
+
+# TODO: split stylesheet/resources and add here TODO: export header files correctly
+
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/ui)
+set(CMAKE_AUTOUIC ON)
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+
+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
+ src/widgets/*.cpp
+)
+file(
+ GLOB
+ HEADER_LIST
+ include/${SCOPY_MODULE}/*.h
+ include/${SCOPY_MODULE}/*.hpp
+ include/${SCOPY_MODULE}/widgets/*.h
+)
+
+set(ENABLE_TESTING ON)
+if(ENABLE_TESTING)
+ add_subdirectory(test)
+endif()
+
+set(PROJECT_SOURCES ${SRC_LIST} ${HEADER_LIST})
+find_package(Qt${QT_VERSION_MAJOR} COMPONENTS REQUIRED Widgets Core)
+
+qt_add_resources(PROJECT_RESOURCES res/resources.qrc)
+
+add_library(${PROJECT_NAME} SHARED ${PROJECT_SOURCES} ${PROJECT_RESOURCES})
+
+generate_export_header(
+ ${PROJECT_NAME} EXPORT_FILE_NAME ${CMAKE_CURRENT_SOURCE_DIR}/include/${SCOPY_MODULE}/${PROJECT_NAME}_export.h
+)
+
+include(ScopyStyle)
+generate_style("--plugin" ${CMAKE_CURRENT_SOURCE_DIR}/style ${CMAKE_CURRENT_SOURCE_DIR}/include/admt)
+
+set(INCLUDE_DIRECTORIES
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/${SCOPY_MODULE} ${CMAKE_CURRENT_SOURCE_DIR}/include/${SCOPY_MODULE}
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/${SCOPY_MODULE} ${CMAKE_CURRENT_SOURCE_DIR}/include/${SCOPY_MODULE}/widgets
+)
+
+configure_file(
+ include/${SCOPY_MODULE}/scopy-${SCOPY_MODULE}_config.h.cmakein
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/${SCOPY_MODULE}/scopy-${SCOPY_MODULE}_config.h @ONLY
+)
+
+target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIRECTORIES})
+
+target_include_directories(${PROJECT_NAME} PUBLIC scopy-pluginbase scopy-gui)
+target_include_directories(${PROJECT_NAME} PRIVATE ${IIO_INCLUDE_DIRS} scopy-gui scopy-iioutil)
+
+target_link_libraries(
+ ${PROJECT_NAME}
+ PUBLIC Qt::Widgets
+ Qt::Core
+ scopy-pluginbase
+ scopy-gui
+ scopy-iioutil
+)
+
+if(${CMAKE_SYSTEM_NAME} MATCHES "Windows")
+ set(INSTALLER_DESCRIPTION "${PLUGIN_DISPLAY_NAME} ${PLUGIN_DESCRIPTION}")
+ configureinstallersettings(${SCOPY_MODULE} ${INSTALLER_DESCRIPTION} TRUE)
+endif()
+
+set(ADMT_TARGET_NAME ${PROJECT_NAME} PARENT_SCOPE)
diff --git a/plugins/admt/include/admt/admtcontroller.h b/plugins/admt/include/admt/admtcontroller.h
new file mode 100644
index 0000000000..2c5fe94b0a
--- /dev/null
+++ b/plugins/admt/include/admt/admtcontroller.h
@@ -0,0 +1,298 @@
+/*
+ * 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 ADMTCONTROLLER_H
+#define ADMTCONTROLLER_H
+
+#include "scopy-admt_export.h"
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+namespace scopy::admt {
+class SCOPY_ADMT_EXPORT ADMTController : public QObject
+{
+ Q_OBJECT
+public:
+ ADMTController(QString uri, QObject *parent = nullptr);
+ ~ADMTController();
+
+ int HAR_MAG_1, HAR_MAG_2, HAR_MAG_3, HAR_MAG_8, HAR_PHASE_1, HAR_PHASE_2, HAR_PHASE_3, HAR_PHASE_8,
+ sampleCount = 0;
+
+ QAtomicInt stopStream = false;
+
+ double streamedValue = 0.0;
+ QVector streamBufferedValues;
+ QMap streamedChannelDataMap;
+
+ QElapsedTimer elapsedStreamTimer;
+
+ vector angle_errors_fft_pre, angle_errors_fft_phase_pre, angle_errors_fft_post,
+ angle_errors_fft_phase_post, calibration_samples_sine, calibration_samples_cosine,
+ calibration_samples_sine_scaled, calibration_samples_cosine_scaled, angleError, FFTAngleErrorMagnitude,
+ FFTAngleErrorPhase, correctedError, FFTCorrectedErrorMagnitude, FFTCorrectedErrorPhase;
+
+ enum Channel
+ {
+ ROTATION,
+ ANGL,
+ COUNT,
+ TEMPERATURE,
+ CHANNEL_COUNT
+ };
+
+ enum Device
+ {
+ ADMT4000,
+ TMC5240,
+ DEVICE_COUNT
+ };
+
+ enum DeviceAttribute
+ {
+ PAGE,
+ SEQUENCER_MODE,
+ ANGLE_FILT,
+ CONVERSION_MODE,
+ H8_CTRL,
+ SDP_GPIO_CTRL,
+ SDP_GPIO0_BUSY,
+ SDP_COIL_RS,
+ REGMAP_DUMP,
+ DEVICE_ATTR_COUNT
+ };
+
+ enum MotorAttribute
+ {
+ AMAX,
+ ROTATE_VMAX,
+ DMAX,
+ DISABLE,
+ TARGET_POS,
+ CURRENT_POS,
+ RAMP_MODE,
+ MOTOR_ATTR_COUNT
+ };
+
+ enum MotorRampMode
+ {
+ POSITION,
+ RAMP_MODE_1
+ };
+
+ enum HarmonicRegister
+ {
+ H1MAG,
+ H1PH,
+ H2MAG,
+ H2PH,
+ H3MAG,
+ H3PH,
+ H8MAG,
+ H8PH,
+ HARMONIC_REGISTER_COUNT
+ };
+
+ enum ConfigurationRegister
+ {
+ CNVPAGE,
+ DIGIO,
+ FAULT,
+ GENERAL,
+ DIGIOEN,
+ ANGLECK,
+ ECCCDE,
+ ECCDIS,
+ CONFIGURATION_REGISTER_COUNT
+ };
+
+ enum SensorRegister
+ {
+ ABSANGLE,
+ ANGLE,
+ ANGLESEC,
+ SINE,
+ COSINE,
+ SECANGLI,
+ SECANGLQ,
+ RADIUS,
+ DIAG1,
+ DIAG2,
+ TMP0,
+ TMP1,
+ CNVCNT,
+ SENSOR_REGISTER_COUNT
+ };
+
+ enum UniqueIDRegister
+ {
+ UNIQID0,
+ UNIQID1,
+ UNIQID2,
+ UNIQID3,
+ UNIQID_REGISTER_COUNT
+ };
+
+ enum RampGeneratorDriverFeatureControlRegister
+ {
+ VDCMIN,
+ SW_MODE,
+ RAMP_STAT,
+ XLATCH,
+ RAMP_GENERATOR_DRIVER_FEATURE_CONTROL_REGISTER_COUNT
+ };
+
+ const char *ChannelIds[CHANNEL_COUNT] = {"rot", "angl", "count", "temp"};
+ const char *DeviceIds[DEVICE_COUNT] = {"admt4000", "tmc5240"};
+ const char *DeviceAttributes[DEVICE_ATTR_COUNT] = {
+ "page", "sequencer_mode", "angle_filt", "conversion_mode", "h8_ctrl",
+ "sdp_gpio_ctrl", "sdp_gpio0_busy", "sdp_coil_rs", "regmap_dump"};
+ const char *MotorAttributes[MOTOR_ATTR_COUNT] = {"amax", "rotate_vmax", "dmax", "disable",
+ "target_pos", "current_pos", "ramp_mode"};
+ const uint32_t ConfigurationRegisters[CONFIGURATION_REGISTER_COUNT] = {0x01, 0x04, 0x06, 0x10,
+ 0x12, 0x13, 0x1D, 0x23};
+ const uint8_t ConfigurationPages[CONFIGURATION_REGISTER_COUNT] = {UINT8_MAX, UINT8_MAX, UINT8_MAX, 0x02,
+ 0x02, 0x02, 0x02, 0x02};
+ const uint32_t UniqueIdRegisters[UNIQID_REGISTER_COUNT] = {0x1E, 0x1F, 0x20, 0x21};
+ const uint8_t UniqueIdPages[UNIQID_REGISTER_COUNT] = {0x02, 0x02, 0x02, 0x02};
+ const uint32_t HarmonicRegisters[HARMONIC_REGISTER_COUNT] = {0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C};
+ const uint8_t HarmonicPages[HARMONIC_REGISTER_COUNT] = {0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02};
+ const uint32_t SensorRegisters[SENSOR_REGISTER_COUNT] = {0x03, 0x05, 0x08, 0x10, 0x11, 0x12, 0x13,
+ 0x18, 0x1D, 0x1E, 0x20, 0x23, 0x14};
+ const uint8_t SensorPages[SENSOR_REGISTER_COUNT] = {0x00, UINT8_MAX, UINT8_MAX, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02};
+
+ const uint32_t
+ RampGeneratorDriverFeatureControlRegisters[RAMP_GENERATOR_DRIVER_FEATURE_CONTROL_REGISTER_COUNT] = {
+ 0x33, 0x34, 0x35, 0x36};
+
+ const char *getChannelId(Channel channel);
+ const char *getDeviceId(Device device);
+ const char *getDeviceAttribute(DeviceAttribute attribute);
+ const char *getMotorAttribute(MotorAttribute attribute);
+ const uint32_t getConfigurationRegister(ConfigurationRegister registerID);
+ const uint8_t getConfigurationPage(ConfigurationRegister registerID);
+ const uint32_t getUniqueIdRegister(UniqueIDRegister registerID);
+ const uint32_t getHarmonicRegister(HarmonicRegister registerID);
+ const uint8_t getHarmonicPage(HarmonicRegister registerID);
+ const uint8_t getUniqueIdPage(UniqueIDRegister registerID);
+ const uint32_t getSensorRegister(SensorRegister registerID);
+ const uint8_t getSensorPage(SensorRegister registerID);
+
+ const uint32_t
+ getRampGeneratorDriverFeatureControlRegister(RampGeneratorDriverFeatureControlRegister registerID);
+
+ void connectADMT();
+ void disconnectADMT();
+ int getChannelIndex(const char *deviceName, const char *channelName);
+ double getChannelValue(const char *deviceName, const char *channelName, int bufferSize = 1);
+ int getDeviceAttributeValue(const char *deviceName, const char *attributeName, double *returnValue);
+ int getDeviceAttributeValueString(const char *deviceName, const char *attributeName, char *returnValue,
+ size_t byteLength = 512);
+ int setDeviceAttributeValue(const char *deviceName, const char *attributeName, double writeValue);
+ QString calibrate(vector PANG, int cycles, int samplesPerCycle, bool CCW);
+ int writeDeviceRegistry(const char *deviceName, uint32_t address, uint32_t value);
+ int readDeviceRegistry(const char *deviceName, uint32_t address, uint32_t *returnValue);
+ int readDeviceRegistry(const char *deviceName, uint32_t address, uint8_t page, uint32_t *returnValue);
+ void computeSineCosineOfAngles(const vector &angles);
+ uint16_t calculateHarmonicCoefficientMagnitude(uint16_t harmonicCoefficient, uint16_t originalValue,
+ const string &key);
+ uint16_t calculateHarmonicCoefficientPhase(uint16_t harmonicPhase, uint16_t originalValue);
+ double getActualHarmonicRegisterValue(uint16_t registerValue, const string key);
+ map getFaultRegisterBitMapping(uint16_t registerValue);
+ map getGeneralRegisterBitMapping(uint16_t registerValue);
+ map getDIGIOENRegisterBitMapping(uint16_t registerValue);
+ map getDIGIORegisterBitMapping(uint16_t registerValue);
+ map getDiag1RegisterBitMapping_Register(uint16_t registerValue);
+ map getDiag1RegisterBitMapping_Afe(uint16_t registerValue, bool is5V);
+ map getDiag2RegisterBitMapping(uint16_t registerValue);
+ uint16_t setGeneralRegisterBitMapping(uint16_t currentRegisterValue, map settings);
+ void postcalibrate(vector PANG, int cycleCount, int samplesPerCycle, bool CCW);
+ int getAbsAngleTurnCount(uint16_t registerValue);
+ double getAbsAngle(uint16_t registerValue);
+ double getAngle(uint16_t registerValue);
+ double getTemperature(uint16_t registerValue);
+ uint16_t setDIGIOENRegisterBitMapping(uint16_t currentRegisterValue, map settings);
+ uint16_t setDIGIORegisterBitMapping(uint16_t currentRegisterValue, map settings);
+ void unwrapAngles(vector &angles_rad);
+ map getUNIQID3RegisterMapping(uint16_t registerValue);
+ map getSineRegisterBitMapping(uint16_t registerValue);
+ map getCosineRegisterBitMapping(uint16_t registerValue);
+ map getRadiusRegisterBitMapping(uint16_t registerValue);
+ map getAngleSecRegisterBitMapping(uint16_t registerValue);
+ map getSecAnglQRegisterBitMapping(uint16_t registerValue);
+ map getSecAnglIRegisterBitMapping(uint16_t registerValue);
+ map getTmp1RegisterBitMapping(uint16_t registerValue, bool is5V);
+ bool checkRegisterFault(uint16_t registerValue, bool isMode1);
+ int streamIO();
+ void bufferedStreamIO(int totalSamples, int targetSampleRate, int bufferSize);
+ void registryStream(int totalSamples, int targetSampleRate);
+ bool checkVelocityReachedFlag(uint16_t registerValue);
+ uint16_t changeCNVPage(uint16_t registerValue, uint8_t page);
+ uint16_t convertStart(bool start, uint16_t registerValue);
+ int streamChannel(const char *deviceName, const QVector channelNames, int bufferSize, int sampleRate);
+public Q_SLOTS:
+ void handleStreamData(double value);
+ void handleStreamChannelData(QMap dataMap);
+ void handleStreamBufferedData(const QVector &value);
+Q_SIGNALS:
+ void streamData(double value);
+ void streamChannelData(QMap dataMap);
+ void streamBufferedData(const QVector &value);
+ void requestDisconnect();
+
+private:
+ QWidget *m_page;
+ iio_context *m_iioCtx;
+ iio_buffer *m_iioBuffer;
+ Connection *m_conn;
+ QString uri;
+
+ unsigned int bitReverse(unsigned int x, int log2n);
+ template
+ void fft(Iter_T a, Iter_T b, int log2n);
+ void performFFT(const vector &angle_errors, vector &angle_errors_fft,
+ vector &angle_errors_fft_phase, int cycleCount);
+ int linear_fit(vector x, vector y, double *slope, double *intercept);
+ int calculate_angle_error(vector angle_meas, vector &angle_error_ret, double *max_angle_err);
+ void getPreCalibrationFFT(const vector &PANG, vector &angle_errors_fft_pre,
+ vector &angle_errors_fft_phase_pre, int cycleCount, int samplesPerCycle);
+ void getPostCalibrationFFT(const vector &updated_PANG, vector &angle_errors_fft_post,
+ vector &angle_errors_fft_phase_post, int cycleCount, int samplesPerCycle);
+};
+} // namespace scopy::admt
+
+#endif // ADMTCONTROLLER_H
diff --git a/plugins/admt/include/admt/admtplugin.h b/plugins/admt/include/admt/admtplugin.h
new file mode 100644
index 0000000000..35d3d830c9
--- /dev/null
+++ b/plugins/admt/include/admt/admtplugin.h
@@ -0,0 +1,71 @@
+/*
+ * 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 ADMTPLUGIN_H
+#define ADMTPLUGIN_H
+
+#define SCOPY_PLUGIN_NAME ADMTPlugin
+
+#include "admtcontroller.h"
+#include "scopy-admt_export.h"
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+namespace scopy {
+namespace admt {
+
+class SCOPY_ADMT_EXPORT ADMTPlugin : public QObject, public PluginBase
+{
+ Q_OBJECT
+ SCOPY_PLUGIN;
+
+public:
+ bool compatible(QString m_param, QString category) override;
+ bool loadPage() override;
+ bool loadIcon() override;
+ void loadToolList() override;
+ void unload() override;
+ void initMetadata() override;
+ QString description() override;
+ QString version() override;
+
+public Q_SLOTS:
+ bool onConnect() override;
+ bool onDisconnect() override;
+
+private:
+ iio_context *m_ctx;
+ QWidget *harmonicCalibration;
+ QLineEdit *edit;
+
+ ADMTController *m_admtController;
+};
+} // namespace admt
+} // namespace scopy
+
+#endif // ADMTPLUGIN_H
diff --git a/plugins/admt/include/admt/harmoniccalibration.h b/plugins/admt/include/admt/harmoniccalibration.h
new file mode 100644
index 0000000000..1d2f69036e
--- /dev/null
+++ b/plugins/admt/include/admt/harmoniccalibration.h
@@ -0,0 +1,437 @@
+/*
+ * 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 HARMONICCALIBRATION_H
+#define HARMONICCALIBRATION_H
+
+#include "scopy-admt_export.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+enum SensorData
+{
+ ABSANGLE,
+ ANGLE,
+ ANGLESEC,
+ SINE,
+ COSINE,
+ SECANGLI,
+ SECANGLQ,
+ RADIUS,
+ DIAG1,
+ DIAG2,
+ TMP0,
+ TMP1,
+ CNVCNT
+};
+
+namespace scopy {
+namespace admt {
+
+class SCOPY_ADMT_EXPORT HarmonicCalibration : public QWidget
+{
+ Q_OBJECT
+public:
+ HarmonicCalibration(ADMTController *m_admtController, bool isDebug = false, QWidget *parent = nullptr);
+ ~HarmonicCalibration();
+ bool running() const;
+ void setRunning(bool newRunning);
+ void requestDisconnect();
+
+public Q_SLOTS:
+ void run(bool);
+ void stop();
+ void start();
+ void restart();
+ void updateAcquisitionData(QMap sensorDataMap);
+ void updateAcquisitionGraph();
+ void updateCalibrationGraph();
+ void calibrationLogWrite(QString message = "");
+ void commandLogWrite(QString message = "");
+ void updateFaultStatus(bool value);
+ void updateMotorPosition(double position);
+ void updateDIGIOUI(quint16 registerValue);
+ void updateFaultRegisterUI(quint16 registerValue);
+ void updateMTDiagnosticRegisterUI(quint16 registerValue);
+ void updateMTDiagnosticsUI(quint16 registerValue);
+ void handleStreamChannelData(QMap dataMap);
+ void handleStreamCalibrationData(double value);
+Q_SIGNALS:
+ void runningChanged(bool);
+ void acquisitionDataChanged(QMap sensorDataMap);
+ void acquisitionGraphChanged();
+ void calibrationGraphChanged();
+ void canCalibrateChanged(bool);
+ void updateUtilityUI();
+ void calibrationLogWriteSignal(QString message);
+ void commandLogWriteSignal(QString message);
+ void updateFaultStatusSignal(bool value);
+ void motorPositionChanged(double position);
+ void DIGIORegisterChanged(quint16 registerValue);
+ void FaultRegisterChanged(quint16 registerValue);
+ void DIAG1RegisterChanged(quint16 registerValue);
+ void DIAG2RegisterChanged(quint16 registerValue);
+
+private:
+ ADMTController *m_admtController;
+ iio_context *m_ctx;
+ bool m_running, isDebug;
+ ToolTemplate *tool;
+ GearBtn *settingsButton;
+ InfoBtn *infoButton;
+ RunBtn *runButton;
+
+ const char *rotationChannelName, *angleChannelName, *countChannelName, *temperatureChannelName;
+
+ double rotation = 0, angle = 0, count = 0, temp = 0, motor_rpm, amax, rotate_vmax, dmax, disable, target_pos,
+ current_pos, ramp_mode, afeDiag0, afeDiag1, afeDiag2;
+
+ QPen channel0Pen, channel1Pen, channel2Pen, channel3Pen, channel4Pen, channel5Pen, channel6Pen, channel7Pen;
+
+ QMap deviceRegisterMap;
+ QMap GENERALRegisterMap;
+ QMap DIGIOENRegisterMap, FAULTRegisterMap, DIAG1RegisterMap;
+ QMap DIAG2RegisterMap, DIAG1AFERegisterMap;
+
+ QVector acquisitionAngleList, acquisitionABSAngleList, acquisitionTmp0List, acquisitionTmp1List,
+ acquisitionSineList, acquisitionCosineList, acquisitionRadiusList, acquisitionAngleSecList,
+ acquisitionSecAnglIList, acquisitionSecAnglQList, graphDataList, graphPostDataList;
+
+ QPushButton *openLastMenuButton, *calibrationStartMotorButton, *calibrateDataButton, *importSamplesButton,
+ *extractDataButton, *clearCalibrateDataButton, *clearCommandLogButton, *applySequenceButton,
+ *readAllRegistersButton;
+
+ QButtonGroup *rightMenuButtonGroup;
+
+ QLineEdit *acquisitionMotorRPMLineEdit, *calibrationCycleCountLineEdit, *calibrationTotalSamplesLineEdit,
+ *calibrationMotorRPMLineEdit, *motorTargetPositionLineEdit, *displayLengthLineEdit,
+ *dataGraphSamplesLineEdit, *tempGraphSamplesLineEdit, *acquisitionMotorCurrentPositionLineEdit,
+ *calibrationH1MagLineEdit, *calibrationH2MagLineEdit, *calibrationH3MagLineEdit,
+ *calibrationH8MagLineEdit, *calibrationH1PhaseLineEdit, *calibrationH2PhaseLineEdit,
+ *calibrationH3PhaseLineEdit, *calibrationH8PhaseLineEdit, *calibrationMotorCurrentPositionLineEdit,
+ *AFEDIAG0LineEdit, *AFEDIAG1LineEdit, *AFEDIAG2LineEdit;
+
+ QLabel *rawAngleValueLabel, *rotationValueLabel, *angleValueLabel, *countValueLabel, *tempValueLabel,
+ *motorAmaxValueLabel, *motorRotateVmaxValueLabel, *motorDmaxValueLabel, *motorDisableValueLabel,
+ *motorTargetPosValueLabel, *motorCurrentPosValueLabel, *motorRampModeValueLabel, *calibrationH1MagLabel,
+ *calibrationH1PhaseLabel, *calibrationH2MagLabel, *calibrationH2PhaseLabel, *calibrationH3MagLabel,
+ *calibrationH3PhaseLabel, *calibrationH8MagLabel, *calibrationH8PhaseLabel;
+
+ MenuHeaderWidget *header;
+
+ MenuSectionWidget *rightMenuSectionWidget;
+ MenuCollapseSection *rotationCollapse, *angleCollapse, *countCollapse, *tempCollapse;
+ MenuCombo *m_dataGraphChannelMenuCombo, *m_dataGraphDirectionMenuCombo, *m_tempGraphDirectionMenuCombo,
+ *m_calibrationMotorRampModeMenuCombo, *sequenceModeMenuCombo, *conversionModeMenuCombo,
+ *cnvSourceMenuCombo, *convertSynchronizationMenuCombo, *angleFilterMenuCombo, *eighthHarmonicMenuCombo,
+ *calibrationModeMenuCombo;
+
+ QTabWidget *tabWidget, *calibrationDataGraphTabWidget, *resultDataTabWidget;
+
+ QListWidget *rawDataListWidget;
+
+ QPlainTextEdit *logsPlainTextEdit, *commandLogPlainTextEdit;
+
+ QCheckBox *angleCheckBox, *absAngleCheckBox, *temp0CheckBox, *sineCheckBox, *cosineCheckBox, *radiusCheckBox,
+ *angleSecCheckBox, *secAnglQCheckBox, *secAnglICheckBox, *temp1CheckBox,
+ *acquisitionFaultRegisterLEDWidget, *calibrationFaultRegisterLEDWidget, *DIGIOBusyStatusLED,
+ *DIGIOCNVStatusLED, *DIGIOSENTStatusLED, *DIGIOACALCStatusLED, *DIGIOFaultStatusLED,
+ *DIGIOBootloaderStatusLED, *R0StatusLED, *R1StatusLED, *R2StatusLED, *R3StatusLED, *R4StatusLED,
+ *R5StatusLED, *R6StatusLED, *R7StatusLED, *VDDUnderVoltageStatusLED, *VDDOverVoltageStatusLED,
+ *VDRIVEUnderVoltageStatusLED, *VDRIVEOverVoltageStatusLED, *AFEDIAGStatusLED, *NVMCRCFaultStatusLED,
+ *ECCDoubleBitErrorStatusLED, *OscillatorDriftStatusLED, *CountSensorFalseStateStatusLED,
+ *AngleCrossCheckStatusLED, *TurnCountSensorLevelsStatusLED, *MTDIAGStatusLED,
+ *TurnCounterCrossCheckStatusLED, *RadiusCheckStatusLED, *SequencerWatchdogStatusLED;
+
+ QScrollArea *MTDiagnosticsScrollArea;
+
+ QWidget *acquisitionGraphChannelWidget;
+
+ QGridLayout *acquisitionGraphChannelGridLayout;
+
+ PlotWidget *acquisitionGraphPlotWidget, *angleErrorPlotWidget, *calibrationRawDataPlotWidget,
+ *FFTAngleErrorMagnitudePlotWidget, *FFTAngleErrorPhasePlotWidget, *correctedErrorPlotWidget,
+ *postCalibrationRawDataPlotWidget, *FFTCorrectedErrorMagnitudePlotWidget,
+ *FFTCorrectedErrorPhasePlotWidget;
+ PlotAxis *acquisitionXPlotAxis, *acquisitionYPlotAxis, *calibrationRawDataXPlotAxis,
+ *calibrationRawDataYPlotAxis, *angleErrorXPlotAxis, *angleErrorYPlotAxis,
+ *FFTAngleErrorMagnitudeXPlotAxis, *FFTAngleErrorMagnitudeYPlotAxis, *FFTAngleErrorPhaseXPlotAxis,
+ *FFTAngleErrorPhaseYPlotAxis, *correctedErrorXPlotAxis, *correctedErrorYPlotAxis,
+ *FFTCorrectedErrorMagnitudeXPlotAxis, *FFTCorrectedErrorMagnitudeYPlotAxis,
+ *FFTCorrectedErrorPhaseXPlotAxis, *FFTCorrectedErrorPhaseYPlotAxis, *postCalibrationRawDataXPlotAxis,
+ *postCalibrationRawDataYPlotAxis;
+ PlotChannel *acquisitionAnglePlotChannel, *acquisitionABSAnglePlotChannel, *acquisitionTmp0PlotChannel,
+ *acquisitionTmp1PlotChannel, *acquisitionSinePlotChannel, *acquisitionCosinePlotChannel,
+ *acquisitionRadiusPlotChannel, *acquisitionSecAnglQPlotChannel, *acquisitionSecAnglIPlotChannel,
+ *acquisitionAngleSecPlotChannel, *angleErrorPlotChannel, *preCalibrationFFTPhasePlotChannel,
+ *calibrationRawDataPlotChannel, *calibrationSineDataPlotChannel, *calibrationCosineDataPlotChannel,
+ *FFTAngleErrorMagnitudeChannel, *FFTAngleErrorPhaseChannel, *correctedErrorPlotChannel,
+ *postCalibrationRawDataPlotChannel, *postCalibrationSineDataPlotChannel,
+ *postCalibrationCosineDataPlotChannel, *FFTCorrectedErrorMagnitudeChannel,
+ *FFTCorrectedErrorPhaseChannel;
+
+ CustomSwitch *acquisitionMotorDirectionSwitch, *calibrationMotorDirectionSwitch,
+ *calibrationDisplayFormatSwitch, *DIGIO0ENToggleSwitch, *DIGIO0FNCToggleSwitch, *DIGIO1ENToggleSwitch,
+ *DIGIO1FNCToggleSwitch, *DIGIO2ENToggleSwitch, *DIGIO2FNCToggleSwitch, *DIGIO3ENToggleSwitch,
+ *DIGIO3FNCToggleSwitch, *DIGIO4ENToggleSwitch, *DIGIO4FNCToggleSwitch, *DIGIO5ENToggleSwitch,
+ *DIGIO5FNCToggleSwitch, *DIGIOALLToggleSwitch;
+
+ RegisterBlockWidget *cnvPageRegisterBlock, *digIORegisterBlock, *faultRegisterBlock, *generalRegisterBlock,
+ *digIOEnRegisterBlock, *angleCkRegisterBlock, *eccCdeRegisterBlock, *eccDisRegisterBlock,
+ *absAngleRegisterBlock, *angleRegisterBlock, *angleSecRegisterBlock, *sineRegisterBlock,
+ *cosineRegisterBlock, *secAnglIRegisterBlock, *secAnglQRegisterBlock, *radiusRegisterBlock,
+ *diag1RegisterBlock, *diag2RegisterBlock, *tmp0RegisterBlock, *tmp1RegisterBlock, *cnvCntRegisterBlock,
+ *uniqID0RegisterBlock, *uniqID1RegisterBlock, *uniqID2RegisterBlock, *uniqID3RegisterBlock,
+ *h1MagRegisterBlock, *h1PhRegisterBlock, *h2MagRegisterBlock, *h2PhRegisterBlock, *h3MagRegisterBlock,
+ *h3PhRegisterBlock, *h8MagRegisterBlock, *h8PhRegisterBlock;
+
+ QFuture m_deviceStatusThread, m_currentMotorPositionThread, m_acquisitionUIThread,
+ m_acquisitionDataThread, m_acquisitionGraphThread, m_calibrationUIThread, m_calibrationStreamThread,
+ m_calibrationWaitVelocityThread, m_calibrationContinuousThread, m_resetMotorToZeroThread,
+ m_utilityUIThread, m_utilityThread;
+ QFutureWatcher m_deviceStatusWatcher, m_currentMotorPositionWatcher, m_acquisitionUIWatcher,
+ m_acquisitionDataWatcher, m_acquisitionGraphWatcher, m_calibrationUIWatcher, m_calibrationStreamWatcher,
+ m_calibrationWaitVelocityWatcher, m_calibrationContinuousWatcher, m_resetMotorToZeroWatcher,
+ m_utilityUIWatcher, m_utilityWatcher;
+
+ ToolTemplate *createAcquisitionWidget();
+ ToolTemplate *createCalibrationWidget();
+ ToolTemplate *createRegistersWidget();
+ ToolTemplate *createUtilityWidget();
+
+ bool readDeviceProperties();
+ void initializeADMT();
+ bool readSequence();
+ bool writeSequence(QMap settings);
+ void applySequence();
+ bool changeCNVPage(uint8_t page);
+ bool convertStart(bool start);
+ bool convertRestart();
+ void initializeMotor();
+ void startDeviceStatusMonitor();
+ void stopDeviceStatusMonitor();
+ void getDeviceFaultStatus(int sampleRate);
+ void currentMotorPositionTask(int sampleRate);
+ bool resetGENERAL();
+ void stopTasks();
+
+#pragma region Acquisition Methods
+ bool updateChannelValues();
+ bool updateSensorValue(ADMTController::SensorRegister sensor);
+ void updateLineEditValues();
+ void startAcquisition();
+ void stopAcquisition();
+ void restartAcquisition();
+ void updateAcquisitionMotorRPM();
+ void updateAcquisitionMotorRotationDirection();
+ void getAcquisitionSamples(QMap dataMap);
+ double calculateABSAngle(double &absAngle, double &turnCount);
+ double getTurnCount(double rawAbsAngleValue);
+ double getABSAngle(double rawAbsAngleValue);
+ double getSensorDataAcquisitionValue(const ADMTController::SensorRegister &key);
+ void plotAcquisition(QVector list, PlotChannel *channel);
+ void appendAcquisitionData(double data, QVector &list);
+ void resetAcquisitionYAxisScale();
+ void updateSequenceWidget();
+ void updateCapturedDataCheckBoxes();
+ void applySequenceAndUpdate();
+ void toggleAcquisitionControls(bool value);
+ void connectCheckBoxToAcquisitionGraph(QCheckBox *widget, PlotChannel *channel, SensorData key);
+ void GMRReset();
+#pragma endregion
+
+#pragma region Calibration Methods
+ void startCalibrationUITask();
+ void stopCalibrationUITask();
+ void calibrationUITask(int sampleRate);
+ void updateCalibrationMotorRPM();
+ void updateCalibrationMotorRotationDirection();
+ void getCalibrationSamples();
+ void startCalibration();
+ void stopCalibration();
+ void startContinuousCalibration();
+ void stopContinuousCalibration();
+ void startCalibrationStreamThread();
+ void stopCalibrationStreamThread();
+ void startWaitForVelocityReachedThread(int mode);
+ void stopWaitForVelocityReachedThread();
+ void waitForVelocityReached(int mode, int sampleRate);
+ int calculateContinuousCalibrationSampleRate(double motorRPS, int samplesPerCycle);
+ void configureCalibrationSequenceSettings(int conversionMode);
+ void startOneShotCalibration();
+ void postCalibrateData();
+ void resetAllCalibrationState();
+ void computeSineCosineOfAngles(QVector graphDataList);
+ void populateAngleErrorGraphs();
+ void populateCorrectedAngleErrorGraphs();
+ bool clearHarmonicRegisters();
+ bool flashHarmonicValues();
+ void calculateHarmonicValues();
+ void updateCalculatedCoeffAngle();
+ void updateCalculatedCoeffHex();
+ void resetCalculatedCoeffAngle();
+ void resetCalculatedCoeffHex();
+ void displayCalculatedCoeff();
+ void importCalibrationData();
+ void extractCalibrationData();
+ void toggleTabSwitching(bool value);
+ void toggleCalibrationButtonState(int state);
+ void canStartMotor(bool value);
+ void canCalibrate(bool);
+ void toggleCalibrationControls(bool value);
+ void clearCalibrationSamples();
+ void clearCalibrationSineCosine();
+ void clearPostCalibrationSamples();
+ void clearAngleErrorGraphs();
+ void clearFFTAngleErrorGraphs();
+#pragma endregion
+
+#pragma region Motor Methods
+ bool moveMotorToPosition(double &position, bool validate = true);
+ void moveMotorContinuous();
+ bool resetCurrentPositionToZero();
+ void stopMotor();
+ int readMotorAttributeValue(ADMTController::MotorAttribute attribute, double &value);
+ int writeMotorAttributeValue(ADMTController::MotorAttribute attribute, double value);
+ int readMotorRegisterValue(uint32_t address, uint32_t *value);
+ void setRampMode(bool motorRotationClockwise);
+ void getRampMode();
+ void startResetMotorToZero(bool enableWatcher = false);
+ void stopResetMotorToZero();
+ void resetMotorToZero();
+#pragma endregion
+
+#pragma region Utility Methods
+ void startUtilityTask();
+ void stopUtilityTask();
+ void utilityTask(int sampleRate);
+ void toggleUtilityTask(bool run);
+ void getDIGIOENRegister();
+ void updateDIGIOMonitorUI();
+ void updateDIGIOControlUI();
+ void getDIAG2Register();
+ void getDIAG1Register();
+ void getFAULTRegister();
+ bool toggleDIGIOEN(string DIGIOENName, bool value);
+ void toggleMTDiagnostics(int mode);
+ void toggleFaultRegisterMode(int mode);
+ bool resetDIGIO();
+ bool disableECC(bool disable);
+ void clearCommandLog();
+#pragma endregion
+
+#pragma region Register Methods
+ void readAllRegisters();
+ void toggleRegisters(int mode);
+#pragma endregion
+
+#pragma region UI Helper Methods
+ bool updateChannelValue(int channelIndex);
+ void updateLineEditValue(QLineEdit *lineEdit, double value);
+ void toggleWidget(QPushButton *widget, bool value);
+ void changeCustomSwitchLabel(CustomSwitch *customSwitch, QString onLabel, QString offLabel);
+ QCheckBox *createStatusLEDWidget(const QString &text, QVariant variant = true, bool checked = false,
+ QWidget *parent = nullptr);
+ void configureCoeffRow(QWidget *container, QHBoxLayout *layout, QLabel *hLabel, QLabel *hMagLabel,
+ QLabel *hPhaseLabel);
+ void initializeChannelColors();
+#pragma endregion
+
+#pragma region Connect Methods
+ void connectLineEditToNumber(QLineEdit *lineEdit, int &variable, int min, int max);
+ void connectLineEditToNumber(QLineEdit *lineEdit, double &variable, QString unit = "");
+ void connectLineEditToDouble(QLineEdit *lineEdit, double &variable);
+ void connectLineEditToNumberWrite(QLineEdit *lineEdit, double &variable,
+ ADMTController::MotorAttribute attribute);
+ void connectLineEditToMotorTurnCount(QLineEdit *lineEdit, int &variable, int min, int max);
+ void connectMenuComboToNumber(MenuCombo *menuCombo, double &variable);
+ void connectMenuComboToNumber(MenuCombo *menuCombo, int &variable);
+ void connectLineEditToRPSConversion(QLineEdit *lineEdit, double &vmax);
+ void connectLineEditToAMAXConversion(QLineEdit *lineEdit, double &amax);
+ void connectRegisterBlockToRegistry(RegisterBlockWidget *widget);
+ void connectLineEditToRPM(QLineEdit *lineEdit, double &variable, double min, double max);
+#pragma endregion
+
+#pragma region Convert Methods
+ double convertRPStoVMAX(double rps);
+ double convertVMAXtoRPS(double vmax);
+ double convertAccelTimetoAMAX(double accelTime);
+ double convertAMAXtoAccelTime(double amax);
+ double convertRPMtoRPS(double rpm);
+ double convertRPStoRPM(double rps);
+#pragma endregion
+
+#pragma region Debug Methods
+ QString readRegmapDumpAttributeValue();
+#pragma endregion
+};
+} // namespace admt
+} // namespace scopy
+#endif // HARMONICCALIBRATION_H
diff --git a/plugins/admt/include/admt/scopy-admt_config.h.cmakein b/plugins/admt/include/admt/scopy-admt_config.h.cmakein
new file mode 100644
index 0000000000..8e4f35c573
--- /dev/null
+++ b/plugins/admt/include/admt/scopy-admt_config.h.cmakein
@@ -0,0 +1,10 @@
+#ifndef SCOPY_ADMT_CONFIG_H_CMAKEIN
+#define SCOPY_ADMT_CONFIG_H_CMAKEIN
+
+#define ADMT_PLUGIN_DISPLAY_NAME "@PLUGIN_DISPLAY_NAME@"
+#define ADMT_PLUGIN_SCOPY_MODULE "@SCOPY_MODULE@"
+#define ADMT_PLUGIN_DESCRIPTION "@PLUGIN_DESCRIPTION@"
+
+#cmakedefine ENABLE_SCOPYJS
+
+#endif // SCOPY_ADMT_CONFIG_H_CMAKEIN
\ No newline at end of file
diff --git a/plugins/admt/include/admt/widgets/registerblockwidget.h b/plugins/admt/include/admt/widgets/registerblockwidget.h
new file mode 100644
index 0000000000..039328e73e
--- /dev/null
+++ b/plugins/admt/include/admt/widgets/registerblockwidget.h
@@ -0,0 +1,85 @@
+/*
+ * 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 REGISTERBLOCKWIDGET_H
+#define REGISTERBLOCKWIDGET_H
+
+#include "../scopy-admt_export.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace scopy::admt {
+class SCOPY_ADMT_EXPORT RegisterBlockWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ enum ACCESS_PERMISSION
+ {
+ READ,
+ WRITE,
+ READWRITE
+ };
+
+ QPushButton *m_readButton, *m_writeButton;
+
+ RegisterBlockWidget(QString header, QString description, uint32_t address, uint8_t cnvPage,
+ RegisterBlockWidget::ACCESS_PERMISSION accessPermission, QWidget *parent = nullptr);
+ virtual ~RegisterBlockWidget();
+ QPushButton *readButton();
+ QPushButton *writeButton();
+ uint32_t getAddress();
+ uint32_t getCnvPage();
+ uint32_t getValue();
+ void setValue(uint32_t value);
+ RegisterBlockWidget::ACCESS_PERMISSION getAccessPermission();
+public Q_SLOTS:
+ void onValueChanged(int);
+
+private:
+ uint32_t m_address, m_value, m_cnvPage;
+ RegisterBlockWidget::ACCESS_PERMISSION m_accessPermission;
+
+ QSpinBox *m_spinBox;
+
+ void addReadButton(QWidget *parent);
+ void addWriteButton(QWidget *parent);
+};
+
+class SCOPY_ADMT_EXPORT PaddedSpinBox : public QSpinBox
+{
+ Q_OBJECT
+public:
+ PaddedSpinBox(QWidget *parent = nullptr);
+ virtual ~PaddedSpinBox();
+
+protected:
+ QString textFromValue(int value) const override;
+};
+} // namespace scopy::admt
+
+#endif // REGISTERBLOCKWIDGET_H
diff --git a/plugins/admt/res/chevron-down-s.svg b/plugins/admt/res/chevron-down-s.svg
new file mode 100644
index 0000000000..31fc00f903
--- /dev/null
+++ b/plugins/admt/res/chevron-down-s.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/admt/res/emu_xml/admt4000.xml b/plugins/admt/res/emu_xml/admt4000.xml
new file mode 100644
index 0000000000..96c29eda3b
--- /dev/null
+++ b/plugins/admt/res/emu_xml/admt4000.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/admt/res/minus.svg b/plugins/admt/res/minus.svg
new file mode 100644
index 0000000000..d750fa52a0
--- /dev/null
+++ b/plugins/admt/res/minus.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/admt/res/plus.svg b/plugins/admt/res/plus.svg
new file mode 100644
index 0000000000..90d50b4f38
--- /dev/null
+++ b/plugins/admt/res/plus.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/admt/res/resources.qrc b/plugins/admt/res/resources.qrc
new file mode 100644
index 0000000000..fa99638ef0
--- /dev/null
+++ b/plugins/admt/res/resources.qrc
@@ -0,0 +1,7 @@
+
+
+ minus.svg
+ plus.svg
+ chevron-down-s.svg
+
+
\ No newline at end of file
diff --git a/plugins/admt/src/admtcontroller.cpp b/plugins/admt/src/admtcontroller.cpp
new file mode 100644
index 0000000000..b4faf440fa
--- /dev/null
+++ b/plugins/admt/src/admtcontroller.cpp
@@ -0,0 +1,2054 @@
+/*
+ * 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 "admtcontroller.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static const size_t maxAttrSize = 512;
+
+static char *streamBuffer = new char[maxAttrSize];
+
+using namespace scopy::admt;
+using namespace std;
+
+ADMTController::ADMTController(QString uri, QObject *parent)
+ : QObject(parent)
+ , uri(uri)
+{
+ connect(this, &ADMTController::streamBufferedData, this, &ADMTController::handleStreamBufferedData);
+}
+
+ADMTController::~ADMTController() {}
+
+void ADMTController::connectADMT()
+{
+ m_conn = ConnectionProvider::open(uri);
+ m_iioCtx = m_conn->context();
+}
+
+void ADMTController::disconnectADMT()
+{
+ if(!m_conn || !m_iioCtx) {
+ return;
+ }
+
+ ConnectionProvider::close(uri);
+ m_conn = nullptr;
+ m_iioCtx = nullptr;
+}
+
+const char *ADMTController::getChannelId(Channel channel)
+{
+ if(channel >= 0 && channel < CHANNEL_COUNT) {
+ return ChannelIds[channel];
+ }
+ return "Unknown";
+}
+
+const char *ADMTController::getDeviceId(Device device)
+{
+ if(device >= 0 && device < DEVICE_COUNT) {
+ return DeviceIds[device];
+ }
+ return "Unknown";
+}
+
+const char *ADMTController::getDeviceAttribute(DeviceAttribute attribute)
+{
+ if(attribute >= 0 && attribute < DEVICE_ATTR_COUNT) {
+ return DeviceAttributes[attribute];
+ }
+ return "Unknown";
+}
+
+const char *ADMTController::getMotorAttribute(MotorAttribute attribute)
+{
+ if(attribute >= 0 && attribute < MOTOR_ATTR_COUNT) {
+ return MotorAttributes[attribute];
+ }
+ return "Unknown";
+}
+
+const uint32_t ADMTController::getHarmonicRegister(HarmonicRegister registerID)
+{
+ if(registerID >= 0 && registerID < HARMONIC_REGISTER_COUNT) {
+ return HarmonicRegisters[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint8_t ADMTController::getHarmonicPage(HarmonicRegister registerID)
+{
+ if(registerID >= 0 && registerID < HARMONIC_REGISTER_COUNT) {
+ return HarmonicPages[registerID];
+ }
+ return UINT8_MAX;
+}
+
+const uint32_t ADMTController::getConfigurationRegister(ConfigurationRegister registerID)
+{
+ if(registerID >= 0 && registerID < CONFIGURATION_REGISTER_COUNT) {
+ return ConfigurationRegisters[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint8_t ADMTController::getConfigurationPage(ConfigurationRegister registerID)
+{
+ if(registerID >= 0 && registerID < CONFIGURATION_REGISTER_COUNT) {
+ return ConfigurationPages[registerID];
+ }
+ return UINT8_MAX;
+}
+
+const uint32_t ADMTController::getSensorRegister(SensorRegister registerID)
+{
+ if(registerID >= 0 && registerID < SENSOR_REGISTER_COUNT) {
+ return SensorRegisters[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint8_t ADMTController::getSensorPage(SensorRegister registerID)
+{
+ if(registerID >= 0 && registerID < SENSOR_REGISTER_COUNT) {
+ return SensorPages[registerID];
+ }
+ return UINT8_MAX;
+}
+
+const uint32_t ADMTController::getUniqueIdRegister(UniqueIDRegister registerID)
+{
+ if(registerID >= 0 && registerID < UNIQID_REGISTER_COUNT) {
+ return UniqueIdRegisters[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint8_t ADMTController::getUniqueIdPage(UniqueIDRegister registerID)
+{
+ if(registerID >= 0 && registerID < UNIQID_REGISTER_COUNT) {
+ return UniqueIdPages[registerID];
+ }
+ return UINT8_MAX;
+}
+
+const uint32_t
+ADMTController::getRampGeneratorDriverFeatureControlRegister(RampGeneratorDriverFeatureControlRegister registerID)
+{
+ if(registerID >= 0 && registerID < RAMP_GENERATOR_DRIVER_FEATURE_CONTROL_REGISTER_COUNT) {
+ return RampGeneratorDriverFeatureControlRegisters[registerID];
+ }
+ return UINT32_MAX;
+}
+
+int ADMTController::getChannelIndex(const char *deviceName, const char *channelName)
+{
+ iio_device *admtDevice = iio_context_find_device(m_iioCtx, deviceName);
+ if(admtDevice == NULL) {
+ return -1;
+ }
+ int channelCount = iio_device_get_channels_count(admtDevice);
+ iio_channel *channel;
+ std::string message = "";
+ for(int i = 0; i < channelCount; i++) {
+ channel = iio_device_get_channel(admtDevice, i);
+ const char *deviceChannel = iio_channel_get_id(channel);
+
+ std::string strDeviceChannel = std::string(deviceChannel);
+ std::string strChannelName = std::string(channelName);
+
+ if(deviceChannel != nullptr && strDeviceChannel == strChannelName) {
+ message = message + "[" + std::to_string(i) + "]" + std::string(deviceChannel) + ", ";
+ return iio_channel_get_index(channel);
+ } else {
+ channel = NULL;
+ }
+ }
+ return -1;
+}
+
+double ADMTController::getChannelValue(const char *deviceName, const char *channelName, int bufferSize)
+{
+ double value = UINT32_MAX;
+ const char *scaleAttrName = "scale";
+ const char *offsetAttrName = "offset";
+ size_t samples = bufferSize;
+ vector values;
+ bool isOutput = false, isCyclic = false;
+
+ unsigned int i, j, major, minor;
+ char git_tag[8];
+ iio_library_get_version(&major, &minor, git_tag);
+ bool has_repeat = ((major * 10000) + minor) >= 8 ? true : false;
+
+ int offsetAttrValue = 0;
+
+ if(!m_iioCtx)
+ return UINT32_MAX;
+ if(iio_context_get_devices_count(m_iioCtx) < 1)
+ return UINT32_MAX;
+ struct iio_device *iioDevice = iio_context_find_device(m_iioCtx, deviceName);
+ if(iioDevice == NULL)
+ return UINT32_MAX;
+ struct iio_channel *channel = iio_device_find_channel(iioDevice, channelName, isOutput);
+ if(channel == NULL)
+ return UINT32_MAX;
+ iio_channel_enable(channel);
+ double *scaleAttrValue = new double(1);
+ int scaleRet = iio_channel_attr_read_double(channel, scaleAttrName, scaleAttrValue); // Read the scale attribute
+ if(scaleRet != 0) {
+ delete scaleAttrValue;
+ return scaleRet;
+ }
+
+ char *offsetDst = new char[maxAttrSize];
+ iio_channel_attr_read(channel, offsetAttrName, offsetDst,
+ maxAttrSize); // Read the offset attribute
+ offsetAttrValue = atoi(offsetDst);
+ delete[] offsetDst;
+
+ struct iio_buffer *buffer = iio_device_create_buffer(iioDevice, samples, isCyclic); // Create a buffer
+ ssize_t numBytesRead;
+ char *pointerData, *pointerEnd;
+ ptrdiff_t pointerIncrement;
+
+ numBytesRead = iio_buffer_refill(buffer);
+
+ if(numBytesRead >= 0) {
+ pointerIncrement = iio_buffer_step(buffer);
+ pointerEnd = static_cast(iio_buffer_end(buffer));
+
+ const struct iio_data_format *format = iio_channel_get_data_format(channel);
+ unsigned int repeat = has_repeat ? format->repeat : 1;
+
+ for(pointerData = static_cast(iio_buffer_first(buffer, channel)); pointerData < pointerEnd;
+ pointerData += pointerIncrement) {
+ for(int j = 0; j < repeat; j++) {
+ if(format->bits <= 8) {
+ int8_t rawValue = (reinterpret_cast(pointerData))[j];
+ values.push_back((rawValue - offsetAttrValue) * *scaleAttrValue);
+ } else if(format->is_fully_defined) {
+ values.push_back((reinterpret_cast(pointerData))[j]);
+ } else if(format->length / 8 == sizeof(int16_t)) {
+ int16_t rawValue = (reinterpret_cast(pointerData))[j];
+ values.push_back((rawValue - offsetAttrValue) * *scaleAttrValue);
+ }
+ }
+ }
+
+ value = values[bufferSize - 1];
+ }
+
+ delete scaleAttrValue;
+ iio_buffer_destroy(buffer);
+ iio_channel_disable(channel);
+ return value;
+}
+
+/** @brief Get the attribute value of a device
+ * @param deviceName A pointer to the device name
+ * @param attributeName A NULL-terminated string corresponding to the name of
+ * the attribute
+ * @param returnValue A pointer to a double variable where the value should be
+ * stored
+ * @return On success, 0 is returned.
+ * @return On error, -1 is returned. */
+int ADMTController::getDeviceAttributeValue(const char *deviceName, const char *attributeName, double *returnValue)
+{
+ if(!m_iioCtx) {
+ return -1;
+ }
+ int result = -1;
+ int deviceCount = iio_context_get_devices_count(m_iioCtx);
+ if(deviceCount == 0) {
+ return result;
+ }
+ iio_device *iioDevice = iio_context_find_device(m_iioCtx, deviceName);
+ if(iioDevice == NULL) {
+ return result;
+ }
+ const char *hasAttr = iio_device_find_attr(iioDevice, attributeName);
+ if(hasAttr == NULL) {
+ return result;
+ }
+ result = iio_device_attr_read_double(iioDevice, attributeName, returnValue);
+
+ return result;
+}
+
+int ADMTController::getDeviceAttributeValueString(const char *deviceName, const char *attributeName, char *returnValue,
+ size_t byteLength)
+{
+ if(!m_iioCtx) {
+ return -1;
+ }
+ int result = -1;
+ int deviceCount = iio_context_get_devices_count(m_iioCtx);
+ if(deviceCount == 0) {
+ return result;
+ }
+ iio_device *iioDevice = iio_context_find_device(m_iioCtx, deviceName);
+ if(iioDevice == NULL) {
+ return result;
+ }
+ const char *hasAttr = iio_device_find_attr(iioDevice, attributeName);
+ if(hasAttr == NULL) {
+ return result;
+ }
+ result = iio_device_attr_read(iioDevice, attributeName, returnValue, byteLength);
+
+ return result;
+}
+
+/** @brief Set the attribute value of a device
+ * @param deviceName A pointer to the device name
+ * @param attributeName A NULL-terminated string corresponding to the name of
+ * the attribute
+ * @param writeValue A double variable of the value to be set
+ * @return On success, 0 is returned.
+ * @return On error, -1 is returned. */
+int ADMTController::setDeviceAttributeValue(const char *deviceName, const char *attributeName, double writeValue)
+{
+ if(!m_iioCtx) {
+ return -1;
+ }
+ int result = -1;
+ int deviceCount = iio_context_get_devices_count(m_iioCtx);
+ if(deviceCount == 0) {
+ return result;
+ }
+ iio_device *iioDevice = iio_context_find_device(m_iioCtx, deviceName);
+ if(iioDevice == NULL) {
+ return result;
+ }
+ const char *hasAttr = iio_device_find_attr(iioDevice, attributeName);
+ if(hasAttr == NULL) {
+ return result;
+ }
+ result = iio_device_attr_write_double(iioDevice, attributeName, writeValue);
+
+ return result;
+}
+
+int ADMTController::writeDeviceRegistry(const char *deviceName, uint32_t address, uint32_t value)
+{
+ if(!m_iioCtx) {
+ return -1;
+ }
+ int result = -1;
+ int deviceCount = iio_context_get_devices_count(m_iioCtx);
+ if(deviceCount == 0) {
+ return result;
+ }
+ iio_device *iioDevice = iio_context_find_device(m_iioCtx, deviceName);
+ if(iioDevice == NULL) {
+ return result;
+ }
+ result = iio_device_reg_write(iioDevice, address, value);
+
+ return result;
+}
+
+int ADMTController::readDeviceRegistry(const char *deviceName, uint32_t address, uint32_t *returnValue)
+{
+ if(!m_iioCtx) {
+ return -1;
+ }
+ if(address == UINT32_MAX) {
+ return -1;
+ }
+ int result = -1;
+ int deviceCount = iio_context_get_devices_count(m_iioCtx);
+ if(deviceCount == 0) {
+ return result;
+ }
+ iio_device *iioDevice = iio_context_find_device(m_iioCtx, deviceName);
+ if(iioDevice == NULL) {
+ return result;
+ }
+ result = iio_device_reg_read(iioDevice, address, returnValue);
+
+ return result;
+}
+
+int ADMTController::readDeviceRegistry(const char *deviceName, uint32_t address, uint8_t page, uint32_t *returnValue)
+{
+ if(!m_iioCtx)
+ return -1;
+ if(address == UINT32_MAX)
+ return -1;
+
+ int result = -1;
+ int deviceCount = iio_context_get_devices_count(m_iioCtx);
+ if(deviceCount == 0) {
+ return result;
+ }
+ iio_device *iioDevice = iio_context_find_device(m_iioCtx, deviceName);
+ if(iioDevice == NULL) {
+ return result;
+ }
+
+ if(page != UINT8_MAX) {
+ uint32_t *readCNVValue = new uint32_t;
+
+ if(iio_device_reg_read(iioDevice, getConfigurationRegister(ConfigurationRegister::CNVPAGE),
+ readCNVValue) == 0) {
+ iio_device_reg_write(iioDevice, getConfigurationRegister(ConfigurationRegister::CNVPAGE),
+ changeCNVPage(static_cast(*readCNVValue), page));
+ } else {
+ delete readCNVValue;
+ return -1;
+ }
+
+ delete readCNVValue;
+ }
+
+ result = iio_device_reg_read(iioDevice, address, returnValue);
+
+ return result;
+}
+
+/* bit reversal from online example */
+unsigned int ADMTController::bitReverse(unsigned int x, int log2n)
+{
+ int n = 0;
+ int mask = 0x1;
+ for(int i = 0; i < log2n; i++) {
+ n <<= 1;
+ n |= (x & 1);
+ x >>= 1;
+ }
+ return n;
+}
+
+template
+void ADMTController::fft(Iter_T a, Iter_T b, int log2n)
+{
+ typedef typename iterator_traits::value_type complex;
+ const complex J(0, 1);
+ int n = 1 << log2n;
+ for(unsigned int i = 0; i < n; ++i) {
+ b[bitReverse(i, log2n)] = a[i];
+ }
+ for(int s = 1; s <= log2n; ++s) {
+ int m = 1 << s;
+ int m2 = m >> 1;
+ complex w(1, 0);
+ complex wm = exp(-J * (M_PI / m2));
+ for(int j = 0; j < m2; ++j) {
+ for(int k = j; k < n; k += m) {
+ complex t = w * b[k + m2];
+ complex u = b[k];
+ b[k] = u + t;
+ b[k + m2] = u - t;
+ }
+ w *= wm;
+ }
+ }
+}
+
+/* For linear fitting (hard-coded based on examples and formula for polynomial
+ * fitting) */
+int ADMTController::linear_fit(vector x, vector y, double *slope, double *intercept)
+{
+ /* x, y, x^2, y^2, xy, xy^2 */
+ double sum_x = 0, sum_y = 0, sum_x2 = 0, sum_y2 = 0, sum_xy = 0;
+ int i;
+
+ if(x.size() != y.size())
+ return -22;
+
+ for(i = 0; i < x.size(); i++) {
+ sum_x += x[i];
+ sum_y += y[i];
+ sum_x2 += (x[i] * x[i]);
+ sum_y2 += (y[i] * y[i]);
+ sum_xy += (x[i] * y[i]);
+ }
+
+ *slope = (x.size() * sum_xy - sum_x * sum_y) / (x.size() * sum_x2 - sum_x * sum_x);
+
+ *intercept = (sum_y * sum_x2 - sum_x * sum_xy) / (x.size() * sum_x2 - sum_x * sum_x);
+
+ return 0;
+}
+
+int ADMTController::calculate_angle_error(vector angle_meas, vector &angle_error_ret,
+ double *max_angle_err)
+{
+ vector angle_meas_rad(angle_meas.size()); // radian converted input
+ vector angle_meas_rad_unwrap(angle_meas.size()); // unwrapped radian input
+ vector angle_fit(angle_meas.size()); // array for polynomial fitted data
+ vector x_data(angle_meas.size());
+ double coeff_a, coeff_b; // coefficients generated by polynomial fitting
+
+ // convert to radian
+ for(int i = 0; i < angle_meas_rad.size(); i++)
+ angle_meas_rad[i] = angle_meas[i] * M_PI / 180.0;
+
+ // unwrap angle (extracted from decompiled Angle GSF Unit)
+ double num = 0.0;
+ angle_meas_rad_unwrap[0] = angle_meas_rad[0];
+ for(int i = 1; i < angle_meas_rad.size(); i++) {
+ double num2 = abs(angle_meas_rad[i] + num - angle_meas_rad_unwrap[i - 1]);
+ double num3 = abs(angle_meas_rad[i] + num - angle_meas_rad_unwrap[i - 1] + M_PI * 2.0);
+ double num4 = abs(angle_meas_rad[i] + num - angle_meas_rad_unwrap[i - 1] - M_PI * 2.0);
+ if(num3 < num2 && num3 < num4)
+ num += M_PI * 2.0;
+
+ else if(num4 < num2 && num4 < num3)
+ num -= M_PI * 2.0;
+
+ angle_meas_rad_unwrap[i] = angle_meas_rad[i] + num;
+ }
+
+ // set initial point to zero
+ double offset = angle_meas_rad_unwrap[0];
+ for(int i = 0; i < angle_meas_rad_unwrap.size(); ++i)
+ angle_meas_rad_unwrap[i] -= offset;
+
+ /* Generate xdata for polynomial fitting */
+ iota(x_data.begin(), x_data.end(), 1);
+
+ // linear angle fitting (generated coefficients not same with matlab and
+ // python) expecting 0.26 -0.26 getting ~0.27 ~-0.27 as of 4/2/2024
+ /* input args: x, y, *slope, *intercept */
+ linear_fit(x_data, angle_meas_rad_unwrap, &coeff_a, &coeff_b);
+
+ // generate data using coefficients from polynomial fitting
+ for(int i = 0; i < angle_fit.size(); i++) {
+ angle_fit[i] = coeff_a * x_data[i];
+ }
+
+ // get angle error using pass by ref angle_error_ret
+ for(int i = 0; i < angle_error_ret.size(); i++) {
+ angle_error_ret[i] = angle_meas_rad_unwrap[i] - angle_fit[i];
+ // cout << "angle_err_ret " << angle_error_ret[i] << "\n";
+ }
+
+ // Find the offset for error and subtract (using angle_error_ret)
+ auto minmax = minmax_element(angle_error_ret.begin(), angle_error_ret.end());
+ double angle_err_offset = (*minmax.first + *minmax.second) / 2;
+
+ for(int i = 0; i < angle_error_ret.size(); i++)
+ angle_error_ret[i] -= angle_err_offset;
+
+ // Convert back to degrees (angle_error_ret)
+ for(int i = 0; i < angle_meas.size(); i++)
+ angle_error_ret[i] *= (180 / M_PI);
+
+ // Find maximum absolute angle error
+ *max_angle_err = *minmax.second;
+
+ return 0;
+}
+
+// Function to unwrap angles that can span multiple cycles
+void ADMTController::unwrapAngles(vector &angles_rad)
+{
+ for(size_t i = 1; i < angles_rad.size(); ++i) {
+ // Calculate the difference between the current angle and the previous one
+ double diff = angles_rad[i] - angles_rad[i - 1];
+
+ // If the difference is greater than pi, subtract 2*pi (unwrap backward)
+ if(diff > M_PI) {
+ angles_rad[i] -= 2 * M_PI;
+ }
+ // If the difference is less than -pi, add 2*pi (unwrap forward)
+ else if(diff < -M_PI) {
+ angles_rad[i] += 2 * M_PI;
+ }
+ }
+}
+
+QString ADMTController::calibrate(vector PANG, int cycleCount, int samplesPerCycle, bool CCW)
+{
+ int circshiftData = 0;
+ QString result = "";
+
+ /* Check CCW flag to know if array is to be reversed */
+ if(CCW)
+ reverse(PANG.begin(), PANG.end());
+
+ /* Randomize starting point of array */
+ if(circshiftData) {
+ int shift = rand() % PANG.size();
+ rotate(PANG.begin(), PANG.begin() + shift, PANG.end());
+ }
+
+ // Declare vectors for pre-calibration FFT results
+ angle_errors_fft_pre = vector(PANG.size() / 2);
+ angle_errors_fft_phase_pre = vector(PANG.size() / 2);
+
+ // Call the new function for pre-calibration FFT
+ getPreCalibrationFFT(PANG, angle_errors_fft_pre, angle_errors_fft_phase_pre, cycleCount, samplesPerCycle);
+
+ // Extract HMag parameters
+ double H1Mag = angle_errors_fft_pre[cycleCount];
+ double H2Mag = angle_errors_fft_pre[2 * cycleCount];
+ double H3Mag = angle_errors_fft_pre[3 * cycleCount];
+ double H8Mag = angle_errors_fft_pre[8 * cycleCount];
+
+ /* Display HMAG values */
+ result.append("H1Mag = " + QString::number(H1Mag) + "\n");
+ result.append("H2Mag = " + QString::number(H2Mag) + "\n");
+ result.append("H3Mag = " + QString::number(H3Mag) + "\n");
+ result.append("H8Mag = " + QString::number(H8Mag) + "\n");
+
+ // Extract HPhase parameters
+ double H1Phase = (180 / M_PI) * (angle_errors_fft_phase_pre[cycleCount]);
+ double H2Phase = (180 / M_PI) * (angle_errors_fft_phase_pre[2 * cycleCount]);
+ double H3Phase = (180 / M_PI) * (angle_errors_fft_phase_pre[3 * cycleCount]);
+ double H8Phase = (180 / M_PI) * (angle_errors_fft_phase_pre[8 * cycleCount]);
+
+ /* Display HPHASE values */
+ result.append("H1Phase = " + QString::number(H1Phase) + "\n");
+ result.append("H2Phase = " + QString::number(H2Phase) + "\n");
+ result.append("H3Phase = " + QString::number(H3Phase) + "\n");
+ result.append("H8Phase = " + QString::number(H8Phase) + "\n");
+
+ double H1 = H1Mag * cos(M_PI / 180 * (H1Phase));
+ double H2 = H2Mag * cos(M_PI / 180 * (H2Phase));
+ double H3 = H3Mag * cos(M_PI / 180 * (H3Phase));
+ double H8 = H8Mag * cos(M_PI / 180 * (H8Phase));
+
+ double init_err = H1 + H2 + H3 + H8;
+ double init_angle = PANG[0] - init_err;
+
+ double H1PHcor, H2PHcor, H3PHcor, H8PHcor;
+
+ /* Counterclockwise, slope of error FIT is negative */
+ if(CCW) {
+ H1Phase *= -1;
+ H2Phase *= -1;
+ H3Phase *= -1;
+ H8Phase *= -1;
+ }
+
+ /* Clockwise */
+ H1PHcor = H1Phase - (1 * init_angle - 90);
+ H2PHcor = H2Phase - (2 * init_angle - 90);
+ H3PHcor = H3Phase - (3 * init_angle - 90);
+ H8PHcor = H8Phase - (8 * init_angle - 90);
+
+ /* Get modulo from 360 */
+ H1PHcor = (int)H1PHcor % 360;
+ H2PHcor = (int)H2PHcor % 360;
+ H3PHcor = (int)H3PHcor % 360;
+ H8PHcor = (int)H8PHcor % 360;
+
+ // HMag Scaling
+ H1Mag = H1Mag * 0.6072;
+ H2Mag = H2Mag * 0.6072;
+ H3Mag = H3Mag * 0.6072;
+ H8Mag = H8Mag * 0.6072;
+
+ // Derive register compatible HMAG values
+ double mag_scale_factor_11bit = 11.2455 / (1 << 11);
+ double mag_scale_factor_8bit = 1.40076 / (1 << 8);
+ HAR_MAG_1 = (int)(H1Mag / mag_scale_factor_11bit) & (0x7FF); // 11 bit
+ HAR_MAG_2 = (int)(H2Mag / mag_scale_factor_11bit) & (0x7FF); // 11 bit
+ HAR_MAG_3 = (int)(H3Mag / mag_scale_factor_8bit) & (0xFF); // 8 bit
+ HAR_MAG_8 = (int)(H8Mag / mag_scale_factor_8bit) & (0xFF); // 8 bit
+
+ // Derive register compatible HPHASE values
+ double pha_scale_factor_12bit = 360.0 / (1 << 12); // in Deg
+ HAR_PHASE_1 = (int)(H1PHcor / pha_scale_factor_12bit) & (0xFFF); // 12bit number
+ HAR_PHASE_2 = (int)(H2PHcor / pha_scale_factor_12bit) & (0xFFF); // 12bit number
+ HAR_PHASE_3 = (int)(H3PHcor / pha_scale_factor_12bit) & (0xFFF); // 12bit number
+ HAR_PHASE_8 = (int)(H8PHcor / pha_scale_factor_12bit) & (0xFFF); // 12bit number
+
+ result.append("HMAG1: " + QString::number(HAR_MAG_1) + "\n");
+ result.append("HMAG2: " + QString::number(HAR_MAG_2) + "\n");
+ result.append("HMAG3: " + QString::number(HAR_MAG_3) + "\n");
+ result.append("HMAG8: " + QString::number(HAR_MAG_8) + "\n");
+
+ result.append("HPHASE1: " + QString::number(HAR_PHASE_1) + "\n");
+ result.append("HPHASE2: " + QString::number(HAR_PHASE_2) + "\n");
+ result.append("HPHASE3: " + QString::number(HAR_PHASE_3) + "\n");
+ result.append("HPHASE8: " + QString::number(HAR_PHASE_8) + "\n");
+
+ return result;
+}
+
+void ADMTController::getPreCalibrationFFT(const vector &PANG, vector &angle_errors_fft_pre,
+ vector &angle_errors_fft_phase_pre, int cycleCount,
+ int samplesPerCycle)
+{
+ // Calculate the angle errors before calibration
+ double max_err_pre = 0;
+ vector angle_errors_pre(PANG.size());
+
+ // Calculate angle errors
+ calculate_angle_error(PANG, angle_errors_pre, &max_err_pre);
+ // Store the calculated angle errors (angle_errors_pre)
+ angleError = angle_errors_pre;
+
+ // Perform FFT on pre-calibration angle errors
+ performFFT(angle_errors_pre, angle_errors_fft_pre, angle_errors_fft_phase_pre, cycleCount);
+
+ // Store the FFT Angle Error Magnitude and Phase
+ FFTAngleErrorMagnitude = angle_errors_fft_pre;
+ FFTAngleErrorPhase = angle_errors_fft_phase_pre;
+}
+
+void ADMTController::postcalibrate(vector PANG, int cycleCount, int samplesPerCycle, bool CCW)
+{
+ int circshiftData = 0;
+
+ /* Check CCW flag to know if array is to be reversed */
+ if(CCW)
+ reverse(PANG.begin(), PANG.end());
+
+ /* Randomize starting point of array */
+ if(circshiftData) {
+ int shift = rand() % PANG.size();
+ rotate(PANG.begin(), PANG.begin() + shift, PANG.end());
+ }
+
+ // Declare vectors for pre-calibration FFT results
+ angle_errors_fft_post = vector(PANG.size() / 2);
+ angle_errors_fft_phase_post = vector(PANG.size() / 2);
+
+ // Call the new function for post-calibration FFT
+ getPostCalibrationFFT(PANG, angle_errors_fft_post, angle_errors_fft_phase_post, cycleCount, samplesPerCycle);
+}
+
+void ADMTController::getPostCalibrationFFT(const vector &updated_PANG, vector &angle_errors_fft_post,
+ vector &angle_errors_fft_phase_post, int cycleCount,
+ int samplesPerCycle)
+{
+ // Calculate the angle errors after calibration
+ double max_err_post = 0;
+ vector angle_errors_post(updated_PANG.size());
+
+ // Calculate angle errors
+ calculate_angle_error(updated_PANG, angle_errors_post, &max_err_post);
+ // Corrected Error (angle_errors_post)
+ correctedError = angle_errors_post;
+
+ // Perform FFT on post-calibration angle errors
+ performFFT(angle_errors_post, angle_errors_fft_post, angle_errors_fft_phase_post, cycleCount);
+ // FFT Corrected Error (angle_errors_fft_post)
+ FFTCorrectedErrorMagnitude = angle_errors_fft_post;
+ // FFT Corrected Error Phase (angle_errors_fft_phase_post)
+ FFTCorrectedErrorPhase = angle_errors_fft_phase_post;
+}
+
+void ADMTController::performFFT(const vector &angle_errors, vector &angle_errors_fft,
+ vector &angle_errors_fft_phase, int cycleCount)
+{
+ typedef complex cx;
+
+ int L = angle_errors.size(); // Original signal length (L)
+ int N = pow(2, ceil(log2(L))); // Ensure size is a power of 2 (padding if necessary)
+
+ vector fft_in(N, cx(0, 0)); // Input signal (zero-padded if necessary)
+ vector fft_out(N); // Output signal (complex)
+
+ // Format angle errors into the fft_in vector
+ for(int i = 0; i < L; i++) {
+ fft_in[i] = cx(angle_errors[i], 0);
+ }
+
+ // Perform FFT
+ fft(fft_in.data(), fft_out.data(), log2(N));
+
+ // Temporary vectors to store magnitude and phase
+ vector angle_errors_fft_temp(N);
+ vector angle_errors_fft_phase_temp(N);
+
+ // Calculate magnitude and phase for all values
+ for(int i = 0; i < N; i++) {
+ // Magnitude: Normalize by L (original signal length)
+ angle_errors_fft_temp[i] = abs(fft_out[i]) * 2.0 / L;
+ angle_errors_fft_phase_temp[i] = atan2(fft_out[i].imag(), fft_out[i].real());
+ }
+
+ // Prepare vectors for upper half of FFT (positive frequencies)
+ vector angle_errors_fft_upper_half(N / 2);
+ vector angle_errors_fft_phase_upper_half(N / 2);
+
+ // Get upper half only (due to symmetry in real-valued signal FFT)
+ for(int i = 0; i < N / 2; i++) {
+ angle_errors_fft_upper_half[i] = angle_errors_fft_temp[i];
+ angle_errors_fft_phase_upper_half[i] = angle_errors_fft_phase_temp[i];
+ }
+
+ // Resize final vectors based on cycle count (if needed)
+ angle_errors_fft = angle_errors_fft_upper_half;
+ angle_errors_fft_phase = angle_errors_fft_phase_upper_half;
+}
+
+void ADMTController::computeSineCosineOfAngles(const vector &angles)
+{
+ // Vectors to store sine and cosine values
+ calibration_samples_sine = vector(angles.size());
+ calibration_samples_cosine = vector(angles.size());
+ calibration_samples_sine_scaled = vector(angles.size());
+ calibration_samples_cosine_scaled = vector(angles.size());
+
+ const double scaleMin = 0.0;
+ const double scaleMax = 360.0;
+
+ // Convert angles to radians and compute sine, cosine, and their scaled
+ // versions
+ for(size_t i = 0; i < angles.size(); ++i) {
+ double radians = angles[i] * M_PI / 180.0; // Convert degrees to radians
+ calibration_samples_sine[i] = sin(radians);
+ calibration_samples_cosine[i] = cos(radians);
+
+ // Scale sine and cosine to the range 0 to 360
+ calibration_samples_sine_scaled[i] =
+ ((calibration_samples_sine[i] + 1) / 2) * (scaleMax - scaleMin) + scaleMin;
+ calibration_samples_cosine_scaled[i] =
+ ((calibration_samples_cosine[i] + 1) / 2) * (scaleMax - scaleMin) + scaleMin;
+ }
+}
+
+// Function to insert the harmonic coefficient magnitude directly into the
+// register
+uint16_t ADMTController::calculateHarmonicCoefficientMagnitude(uint16_t harmonicCoefficient, uint16_t originalValue,
+ const string &key)
+{
+ uint16_t result = 0;
+
+ // Switch case for different bitmapping based on the key
+ if(key == "h1" || key == "h2") {
+ // For h1 and h2: [15:11 reserved], [10:0 write]
+ result = harmonicCoefficient & 0x07FF;
+ originalValue = (originalValue & 0xF800) | result;
+ } else if(key == "h3" || key == "h8") {
+ // For h3 and h8: [15:8 reserved], [7:0 write]
+ result = harmonicCoefficient & 0x00FF;
+ originalValue = (originalValue & 0xFF00) | result;
+ } else {
+ // Handle invalid key, return the original value unchanged
+ return originalValue;
+ }
+
+ return originalValue; // Return the updated original value
+}
+
+// Function to insert the harmonic coefficient phase directly into the register
+uint16_t ADMTController::calculateHarmonicCoefficientPhase(uint16_t harmonicPhase, uint16_t originalValue)
+{
+ uint16_t result = 0;
+
+ // Mask to keep only bits 11:0 (since phase is represented in 12 bits)
+ result = harmonicPhase & 0x0FFF;
+
+ // Clear bits 11:0 of the original value, keeping bits 15:12 intact
+ uint16_t preservedValue = (originalValue & 0xF000) | result;
+
+ return preservedValue;
+}
+
+double ADMTController::getActualHarmonicRegisterValue(uint16_t registerValue, const string key)
+{
+ double result = 0.0;
+ const double cordicScaler = 0.6072;
+
+ // Switch case for different bitmapping based on the key
+ if(key == "h1mag" || key == "h2mag") {
+ // For h1h2mag: value is in bits [10:0], bits [15:12] are reserved
+ const double LSB = 0.005493;
+
+ // Extract the value from bits [10:0]
+ uint16_t extractedValue = registerValue & 0x07FF;
+
+ // Convert the extracted value by applying CORDIC scaler and LSB
+ result = extractedValue * LSB / cordicScaler;
+ } else if(key == "h3mag" || key == "h8mag") {
+ // For h3h8mag: value is in bits [7:0], bits [15:8] are reserved
+ const double LSB = 0.005493;
+
+ // Extract the value from bits [7:0]
+ uint16_t extractedValue = registerValue & 0x00FF;
+
+ // Convert the extracted value by applying CORDIC scaler and LSB
+ result = extractedValue * LSB / cordicScaler;
+ } else if(key == "h1phase" || key == "h2phase" || key == "h3phase" || key == "h8phase") {
+ // For Phase: value is in bits [11:0], bits [15:12] are reserved
+ const double LSB = 0.087891;
+
+ // Extract the value from bits [11:0]
+ uint16_t extractedValue = registerValue & 0x0FFF;
+
+ // Convert the extracted value by applying the LSB
+ result = extractedValue * LSB;
+ } else {
+ // Indicating an error or invalid key
+ result = -404.0;
+ }
+
+ return result;
+}
+
+map ADMTController::getFaultRegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Extract each bit and store the result in the map
+ // Rain: Current returns it as value.
+ result["Sequencer Watchdog"] = (registerValue >> 15) & 0x01;
+ result["AMR Radius Check"] = (registerValue >> 14) & 0x01;
+ result["Turn Counter Cross Check"] = (registerValue >> 13) & 0x01;
+ result["MT Diagnostic"] = (registerValue >> 12) & 0x01;
+ result["Turn Count Sensor Levels"] = (registerValue >> 11) & 0x01;
+ result["Angle Cross Check"] = (registerValue >> 10) & 0x01;
+ result["Count Sensor False State"] = (registerValue >> 9) & 0x01;
+ result["Oscillator Drift"] = (registerValue >> 8) & 0x01;
+ result["ECC Double Bit Error"] = (registerValue >> 7) & 0x01;
+ result["Reserved"] = (registerValue >> 6) & 0x01;
+ result["NVM CRC Fault"] = (registerValue >> 5) & 0x01;
+ result["AFE Diagnostic"] = (registerValue >> 4) & 0x01;
+ result["VDRIVE Over Voltage"] = (registerValue >> 3) & 0x01;
+ result["VDRIVE Under Voltage"] = (registerValue >> 2) & 0x01;
+ result["VDD Over Voltage"] = (registerValue >> 1) & 0x01;
+ result["VDD Under Voltage"] = (registerValue >> 0) & 0x01;
+
+ return result;
+}
+// // How to read each value sample
+// for (const auto& pair : result) {
+// std::cout << pair.first << ": " << (pair.second ? "Set" : "Not Set") <<
+// std::endl;
+// }
+
+map ADMTController::getGeneralRegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bit 15: STORAGE[7]
+ result["STORAGE[7]"] = ((registerValue >> 15) & 0x01) ? 1 : 0; // ? "Set" : "Not Set";
+
+ // Bits 14:13: Convert Synchronization
+ uint16_t convertSync = (registerValue >> 13) & 0x03;
+ switch(convertSync) {
+ case 0x00:
+ result["Convert Synchronization"] = 0; // "Disabled";
+ break;
+ case 0x03:
+ result["Convert Synchronization"] = 1; // "Enabled";
+ break;
+ default:
+ result["Convert Synchronization"] = -1; // "Reserved";
+ break;
+ }
+
+ // Bit 12: Angle Filter
+ result["Angle Filter"] = ((registerValue >> 12) & 0x01) ? 1 : 0; // ? "Enabled" : "Disabled";
+
+ // Bit 11: STORAGE[6]
+ result["STORAGE[6]"] = ((registerValue >> 11) & 0x01) ? 1 : 0; // ? "Set" : "Not Set";
+
+ // Bit 10: 8th Harmonic
+ result["8th Harmonic"] =
+ ((registerValue >> 10) & 0x01) ? 1 : 0; // ? "User-Supplied Values" : "ADI Factory Values";
+
+ // // Bit 9: Reserved (skipped)
+ // result["Reserved"] = "Reserved";
+
+ // Bits 8:6: STORAGE[5:3]
+ // uint16_t storage_5_3 = (registerValue >> 6) & 0x07;
+ // result["STORAGE[5:3]"] = std::to_string(storage_5_3);
+
+ // Bits 5:4: Sequence Type
+ uint16_t sequenceType = (registerValue >> 4) & 0x03;
+ switch(sequenceType) {
+ case 0x00:
+ result["Sequence Type"] = 1; // "Mode 2";
+ break;
+ case 0x03:
+ result["Sequence Type"] = 0; // "Mode 1";
+ break;
+ default:
+ result["Sequence Type"] = -1; // "Reserved";
+ break;
+ }
+
+ // Bits 3:1: STORAGE[2:0]
+ // uint16_t storage_2_0 = (registerValue >> 1) & 0x07;
+ // result["STORAGE[2:0]"] = std::to_string(storage_2_0);
+
+ // Bit 0: Conversion Type
+ result["Conversion Type"] =
+ (registerValue & 0x01) ? 1 : 0; // ? "One-shot conversion" : "Continuous conversions";
+
+ return result;
+}
+// // How to read each value sample
+// for (const auto& pair : result) {
+// std::cout << pair.first << ": " << pair.second << std::endl;
+// }
+
+map ADMTController::getDIGIOENRegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // // Bits 15:14: Reserved (skipped)
+ // result["Reserved (15:14)"] = "Reserved";
+
+ // Bit 13: DIGIO5EN
+ result["DIGIO5EN"] =
+ ((registerValue >> 13) & 0x01) ? true : false; // ? "GPIO5 output enable" : "GPIO5 output disable";
+
+ // Bit 12: DIGIO4EN
+ result["DIGIO4EN"] =
+ ((registerValue >> 12) & 0x01) ? true : false; // ? "GPIO4 output enable" : "GPIO4 output disable";
+
+ // Bit 11: DIGIO3EN
+ result["DIGIO3EN"] =
+ ((registerValue >> 11) & 0x01) ? true : false; // ? "GPIO3 output enable" : "GPIO3 output disable";
+
+ // Bit 10: DIGIO2EN
+ result["DIGIO2EN"] =
+ ((registerValue >> 10) & 0x01) ? true : false; // ? "GPIO2 output enable" : "GPIO2 output disable";
+
+ // Bit 9: DIGIO1EN
+ result["DIGIO1EN"] =
+ ((registerValue >> 9) & 0x01) ? true : false; // ? "GPIO1 output enable" : "GPIO1 output disable";
+
+ // Bit 8: DIGIO0EN
+ result["DIGIO0EN"] =
+ ((registerValue >> 8) & 0x01) ? true : false; // ? "GPIO0 output enable" : "GPIO0 output disable";
+
+ // // Bits 7:6: Reserved (skipped)
+ // result["Reserved (7:6)"] = "Reserved";
+
+ // Bit 5: Bootload
+ result["BOOTLOAD"] = ((registerValue >> 5) & 0x01) ? true : false; // ? "GPIO5" : "Bootload (Output only)";
+
+ // Bit 4: Fault
+ result["FAULT"] = ((registerValue >> 4) & 0x01) ? true : false; // ? "GPIO4" : "Fault (Output only)";
+
+ // Bit 3: Acalc
+ result["ACALC"] = ((registerValue >> 3) & 0x01) ? true : false; // ? "GPIO3" : "Acalc (Output only)";
+
+ // Bit 2: Sent
+ result["SENT"] = ((registerValue >> 2) & 0x01) ? true : false; // ? "GPIO2" : "Sent (Output only)";
+
+ // Bit 1: Cnv
+ result["CNV"] = ((registerValue >> 1) & 0x01) ? true : false; // ? "GPIO1" : "Cnv (Output only)";
+
+ // Bit 0: Busy
+ result["BUSY"] = (registerValue & 0x01) ? true : false; // ? "GPIO0" : "Busy (Output only)";
+
+ return result;
+}
+
+map ADMTController::getDIGIORegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bits 15:6: Reserved (skipped)
+
+ // Bit 5: GPIO5
+ result["GPIO5"] = ((registerValue >> 5) & 0x01) ? true : false;
+
+ // Bit 4: GPIO4
+ result["GPIO4"] = ((registerValue >> 4) & 0x01) ? true : false;
+
+ // Bit 3: GPIO3
+ result["GPIO3"] = ((registerValue >> 3) & 0x01) ? true : false;
+
+ // Bit 2: GPIO2
+ result["GPIO2"] = ((registerValue >> 2) & 0x01) ? true : false;
+
+ // Bit 1: GPIO1
+ result["GPIO1"] = ((registerValue >> 1) & 0x01) ? true : false;
+
+ // Bit 0: GPIO0
+ result["GPIO0"] = (registerValue & 0x01) ? true : false;
+
+ return result;
+}
+
+map ADMTController::getDiag1RegisterBitMapping_Register(uint16_t registerValue)
+{
+ map result;
+
+ // Bits 15 to 8: R7 to R0 (Enabled or Disabled)
+ result["R7"] = ((registerValue >> 15) & 0x01) ? true : false;
+ result["R6"] = ((registerValue >> 14) & 0x01) ? true : false;
+ result["R5"] = ((registerValue >> 13) & 0x01) ? true : false;
+ result["R4"] = ((registerValue >> 12) & 0x01) ? true : false;
+ result["R3"] = ((registerValue >> 11) & 0x01) ? true : false;
+ result["R2"] = ((registerValue >> 10) & 0x01) ? true : false;
+ result["R1"] = ((registerValue >> 9) & 0x01) ? true : false;
+ result["R0"] = ((registerValue >> 8) & 0x01) ? true : false;
+
+ return result;
+}
+
+map ADMTController::getDiag1RegisterBitMapping_Afe(uint16_t registerValue, bool is5V)
+{
+ map result;
+
+ // Bits 7:0: AFE Diagnostic 2 - Measurement of Fixed voltage (stored in 2's
+ // complement)
+ int8_t afeDiagnostic = static_cast(registerValue & 0x00FF); // Interpret as signed 8-bit
+
+ // Choose the correct resolution based on the voltage level (5V or 3.3V part)
+ double resolution = is5V ? 0.0048828 : 0.003222; // 0.0048828 for 5V, 0.003222 for 3.3V
+
+ // Convert the AFE Diagnostic value to a voltage
+ double diagnosticVoltage = static_cast(afeDiagnostic) * resolution;
+ result["AFE Diagnostic 2"] = diagnosticVoltage;
+
+ return result;
+}
+
+map ADMTController::getDiag2RegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bits 15:8: AFE Diagnostic 1 - Measurement of AFE +57% diagnostic resistor
+ uint16_t afeDiagnostic1 = (registerValue >> 8) & 0x00FF;
+
+ // Convert AFE Diagnostic 1 value to double
+ // Rain: adjust scaling factor as needed with actual method.
+ double diagnostic1Voltage = static_cast(afeDiagnostic1) * 0.01; // what I found (to be confirmed)
+
+ // Store the result with fixed precision
+ result["AFE Diagnostic 1 (+57%)"] = diagnostic1Voltage;
+
+ // Bits 7:0: AFE Diagnostic 0 - Measurement of AFE -57% diagnostic resistor
+ uint16_t afeDiagnostic0 = registerValue & 0x00FF;
+
+ // Convert AFE Diagnostic 0 value to double
+ // Rain: adjust scaling factor as needed with actual method.
+ double diagnostic0Voltage = static_cast(afeDiagnostic0) * 0.01; // what I found (to be confirmed)
+
+ // Store the result with fixed precision
+ result["AFE Diagnostic 0 (-57%)"] = diagnostic0Voltage;
+
+ return result;
+}
+
+uint16_t ADMTController::setGeneralRegisterBitMapping(uint16_t currentRegisterValue, map settings)
+{
+ uint16_t registerValue = currentRegisterValue; // Start with the current register value
+
+ // Bit 15: STORAGE[7] (preserve original value)
+ // Do nothing, as STORAGE[7] is preserved.
+
+ // Bits 14:13: Convert Synchronization
+ if(settings["Convert Synchronization"] == 1) { // Enabled
+ registerValue |= (0x03 << 13); // Set bits 14:13 to 0b11
+ } else if(settings["Convert Synchronization"] == 0) { // Disabled
+ registerValue &= ~(0x03 << 13); // Clear bits 14:13 (set to 0b00)
+ }
+
+ // Bit 12: Angle Filter
+ if(settings["Angle Filter"] == 1) { // Enabled
+ registerValue |= (1 << 12); // Set bit 12
+ } else if(settings["Angle Filter"] == 0) { // Disabled
+ registerValue &= ~(1 << 12); // Clear bit 12
+ }
+
+ // Bit 11: STORAGE[6] (preserve original value)
+ // Do nothing, as STORAGE[6] is preserved.
+
+ // Bit 10: 8th Harmonic
+ if(settings["8th Harmonic"] == 1) { // User-Supplied Values
+ registerValue |= (1 << 10); // Set bit 10
+ } else if(settings["8th Harmonic"] == 0) { // ADI Factory Values
+ registerValue &= ~(1 << 10); // Clear bit 10
+ }
+
+ // Bit 9: Reserved (no change)
+
+ // Bits 8:6: STORAGE[5:3] (preserve original value)
+ // Do nothing, as STORAGE[5:3] is preserved.
+
+ // Bits 5:4: Sequence Type
+ if(settings["Sequence Type"] == 0) { // Mode 1
+ registerValue |= (0x03 << 4); // Set bits 5:4 to 0b11
+ } else if(settings["Sequence Type"] == 1) { // Mode 2
+ registerValue &= ~(0x03 << 4); // Clear bits 5:4 (set to 0b00)
+ }
+
+ // Bits 3:1: STORAGE[2:0] (preserve original value)
+ // Do nothing, as STORAGE[2:0] is preserved.
+
+ // Bit 0: Conversion Type
+ if(settings["Conversion Type"] == 1) { // One-shot conversion
+ registerValue |= (1 << 0); // Set bit 0
+ } else if(settings["Conversion Type"] == 0) { // Continuous conversions
+ registerValue &= ~(1 << 0); // Clear bit 0
+ }
+
+ return registerValue;
+}
+
+int ADMTController::getAbsAngleTurnCount(uint16_t registerValue)
+{
+ // Bits 15:10: Number of whole turns
+ int8_t turnCount = registerValue >> 10;
+
+ if(turnCount <= 0x35) {
+ // Straight binary turn count
+ return turnCount; // Convert from quarter turns to whole turns
+ } else if(turnCount == 0x36) {
+ // Invalid turn count
+ return turnCount;
+ } else {
+ // 2's complement turn count
+ if(turnCount & (1 << 5)) {
+ turnCount -= 64;
+ }
+ return turnCount;
+ }
+}
+
+double ADMTController::getAbsAngle(uint16_t registerValue)
+{
+ double scale = 0.351562500;
+
+ int turnCount = getAbsAngleTurnCount(registerValue);
+
+ double angle = static_cast(registerValue & 0x03FF) * scale;
+ double absAngle = angle;
+
+ if(turnCount != 0)
+ absAngle += (turnCount * 360);
+
+ return absAngle;
+}
+
+double ADMTController::getAngle(uint16_t registerValue)
+{
+ // Angle resolution: 360 deg / 4096
+ double scale = 0.087890625;
+
+ // Bits 15:4: Magnetic Field Angle with 360 deg range.
+ double angle = static_cast(registerValue >> 4) * scale;
+
+ return angle;
+}
+
+double ADMTController::getTemperature(uint16_t registerValue)
+{
+ // Bits 15:4: Internal Temperature Sensor.
+ double temperature = (static_cast((registerValue >> 4)) - 1168) / 15.66;
+
+ return temperature;
+}
+
+uint16_t ADMTController::setDIGIOENRegisterBitMapping(uint16_t currentRegisterValue, map settings)
+{
+ uint16_t registerValue = currentRegisterValue; // Start with the current register value
+
+ // Bits 15:14: (preserve original value)
+
+ // Bit 13: DIGIO5EN
+ if(settings["DIGIO5EN"]) // "Enabled"
+ {
+ registerValue |= (1 << 13); // Set bit 13 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 13); // Clear bit 13 (Disabled)
+ }
+
+ // Bit 12: DIGIO4EN
+ if(settings["DIGIO4EN"]) // "Enabled"
+ {
+ registerValue |= (1 << 12); // Set bit 12 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 12); // Clear bit 12 (Disabled)
+ }
+
+ // Bit 11: DIGIO3EN
+ if(settings["DIGIO3EN"]) // "Enabled"
+ {
+ registerValue |= (1 << 11); // Set bit 11 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 11); // Clear bit 11 (Disabled)
+ }
+
+ // Bit 10: DIGIO2EN
+ if(settings["DIGIO2EN"]) // "Enabled"
+ {
+ registerValue |= (1 << 10); // Set bit 10 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 10); // Clear bit 10 (Disabled)
+ }
+
+ // Bit 9: DIGIO1EN
+ if(settings["DIGIO1EN"]) // "Enabled"
+ {
+ registerValue |= (1 << 9); // Set bit 9 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 9); // Clear bit 9 (Disabled)
+ }
+
+ // Bit 8: DIGIO0EN
+ if(settings["DIGIO0EN"]) // "Enabled"
+ {
+ registerValue |= (1 << 8); // Set bit 8 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 8); // Clear bit 8 (Disabled)
+ }
+
+ // Bits 7:6: (preserve original value)
+
+ // Bit 5: Bootload
+ if(settings["BOOTLOAD"]) // "Enabled"
+ {
+ registerValue |= (1 << 5); // Set bit 5 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 5); // Clear bit 5 (Disabled)
+ }
+
+ // Bit 4: Fault
+ if(settings["FAULT"]) // "Enabled"
+ {
+ registerValue |= (1 << 4); // Set bit 4 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 4); // Clear bit 4 (Disabled)
+ }
+
+ // Bit 3: Acalc
+ if(settings["ACALC"]) // "Enabled"
+ {
+ registerValue |= (1 << 3); // Set bit 3 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 3); // Clear bit 3 (Disabled)
+ }
+
+ // Bit 2: Sent
+ if(settings["SENT"]) // "Enabled"
+ {
+ registerValue |= (1 << 2); // Set bit 2 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 2); // Clear bit 2 (Disabled)
+ }
+
+ // Bit 1: Cnv
+ if(settings["CNV"]) // "Enabled"
+ {
+ registerValue |= (1 << 1); // Set bit 1 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 1); // Clear bit 1 (Disabled)
+ }
+
+ // Bit 0: Sent
+ if(settings["BUSY"]) // "Enabled"
+ {
+ registerValue |= (1 << 0); // Set bit 0 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 0); // Clear bit 0 (Disabled)
+ }
+
+ return registerValue;
+}
+
+uint16_t ADMTController::setDIGIORegisterBitMapping(uint16_t currentRegisterValue, map settings)
+{
+ uint16_t registerValue = currentRegisterValue; // Start with the current register value
+
+ // Bits 15:6: (preserve original value)
+
+ // Bit 5: GPIO5
+ if(settings["GPIO5"]) // "Enabled"
+ {
+ registerValue |= (1 << 5); // Set bit 5 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 5); // Clear bit 5 (Disabled)
+ }
+
+ // Bit 4: GPIO4
+ if(settings["GPIO4"]) // "Enabled"
+ {
+ registerValue |= (1 << 4); // Set bit 4 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 4); // Clear bit 4 (Disabled)
+ }
+
+ // Bit 3: GPIO3
+ if(settings["GPIO3"]) // "Enabled"
+ {
+ registerValue |= (1 << 3); // Set bit 3 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 3); // Clear bit 3 (Disabled)
+ }
+
+ // Bit 2: GPIO2
+ if(settings["GPIO2"]) // "Enabled"
+ {
+ registerValue |= (1 << 2); // Set bit 2 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 2); // Clear bit 2 (Disabled)
+ }
+
+ // Bit 1: GPIO1
+ if(settings["GPIO1"]) // "Enabled"
+ {
+ registerValue |= (1 << 1); // Set bit 1 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 1); // Clear bit 1 (Disabled)
+ }
+
+ // Bit 0: GPIO0
+ if(settings["GPIO0"]) // "Enabled"
+ {
+ registerValue |= (1 << 0); // Set bit 0 to 1 (Enabled)
+ } else // "Disabled"
+ {
+ registerValue &= ~(1 << 0); // Clear bit 0 (Disabled)
+ }
+
+ return registerValue;
+}
+
+map ADMTController::getUNIQID3RegisterMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bits 15:11 - Reserved (ignore)
+
+ // Bits 10:08 - Product ID (3 bits)
+ uint8_t productID = (registerValue >> 8) & 0x07;
+ switch(productID) {
+ case 0x00:
+ result["Product ID"] = "ADMT4000";
+ break;
+ case 0x01:
+ result["Product ID"] = "ADMT4001";
+ break;
+ default:
+ result["Product ID"] = "Unidentified";
+ break;
+ }
+
+ // Bits 7:06 - Supply ID (2 bits)
+ uint8_t supplyID = (registerValue >> 6) & 0x03;
+ switch(supplyID) {
+ case 0x00:
+ result["Supply ID"] = "3.3V";
+ break;
+ case 0x02:
+ result["Supply ID"] = "5V";
+ break;
+ default:
+ result["Supply ID"] = "Unknown";
+ break;
+ }
+
+ // Bits 5:03 - ASIL ID (3 bits)
+ uint8_t asilID = (registerValue >> 3) & 0x07; // Show both Seq 1 & 2 if unknown
+ switch(asilID) {
+ case 0x00:
+ result["ASIL ID"] = "ASIL QM";
+ break;
+ case 0x01:
+ result["ASIL ID"] = "ASIL A";
+ break;
+ case 0x02:
+ result["ASIL ID"] = "ASIL B";
+ break;
+ case 0x03:
+ result["ASIL ID"] = "ASIL C";
+ break;
+ case 0x04:
+ result["ASIL ID"] = "ASIL D";
+ break;
+ default:
+ result["ASIL ID"] = "Unidentified ASIL";
+ break;
+ }
+
+ // Bits 2:00 - Revision ID (3 bits)
+ uint8_t revisionID = registerValue & 0x07;
+ switch(revisionID) {
+ case 0x01:
+ result["Revision ID"] = "S1";
+ break;
+ case 0x02:
+ result["Revision ID"] = "S2";
+ break;
+ default:
+ result["Revision ID"] = "Unknown";
+ break;
+ }
+
+ return result;
+}
+
+map ADMTController::getSineRegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bit 0 - Extract the status
+ result["Status"] = (registerValue & 0x01) ? 1.0 : 0.0;
+
+ // Bit 1 - Reserved (ignore)
+
+ // Bits 15:2 - Extract the sine value
+ int16_t sineValueRaw = (registerValue >> 2); // Shift right by 2 to discard Bits [1:0]
+
+ // Check if the value is negative (2's complement format)
+ if(sineValueRaw & 0x2000) {
+ sineValueRaw |= 0xC000;
+ }
+
+ // Convert the raw uncorrected sine value to a double
+ result["SINE"] = static_cast(sineValueRaw);
+
+ return result;
+}
+
+map ADMTController::getCosineRegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bit 0 - Extract the status
+ result["Status"] = (registerValue & 0x01) ? 1.0 : 0.0;
+
+ // Bit 1 - Reserved (ignore)
+
+ // Bits 15:2 - Extract the cosine value
+ int16_t cosineValueRaw = (registerValue >> 2); // Shift right by 2 to discard Bits [1:0]
+
+ // Check if the value is negative (2's complement format)
+ if(cosineValueRaw & 0x2000) {
+ cosineValueRaw |= 0xC000;
+ }
+
+ // Convert the raw uncorrected cosine value to a double
+ result["COSINE"] = static_cast(cosineValueRaw);
+
+ return result;
+}
+
+map ADMTController::getRadiusRegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bit 0 - Extract the STATUS
+ result["Status"] = (registerValue & 0x01) ? 1.0 : 0.0;
+
+ // Bits 15:1 - Extract the RADIUS value
+ uint16_t radiusRaw = (registerValue >> 1); // Shift right by 1 to discard Bit 0
+
+ // Apply the resolution to convert the raw value
+ constexpr double resolution = 0.000924; // mV/V
+ result["RADIUS"] = static_cast(radiusRaw) * resolution;
+
+ return result;
+}
+
+map ADMTController::getAngleSecRegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bit 0 - Extract the STATUS
+ result["Status"] = (registerValue & 0x01) ? 1.0 : 0.0;
+
+ // Bits 15:4 - Extract the ANGLESEC value
+ uint16_t angleSecRaw = (registerValue >> 4); // Right-shift by 4 to discard Bits [3:0]
+
+ // Calculate the actual angle using the given resolution (360° / 4096)
+ constexpr double resolution = 360.0 / 4096.0; // 0.087890625 degrees per LSB
+ result["ANGLESEC"] = angleSecRaw * resolution;
+
+ return result;
+}
+
+map ADMTController::getSecAnglQRegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bit 0 - Extract the STATUS
+ result["Status"] = (registerValue & 0x01) ? 1.0 : 0.0;
+
+ // Bits 15:2 - Extract the SECANGLQ raw value
+ int16_t secAnglQRaw =
+ static_cast((registerValue & 0xFFFC) >> 2); // Mask Bits [1:0] and shift right by 2
+
+ // Convert the 2's complement raw value to the actual signed value
+ if(secAnglQRaw & 0x2000) { // Check the sign bit (Bit 13)
+ secAnglQRaw |= 0xC000; // Sign extend to preserve the 16-bit signed value
+ }
+
+ // Store the SECANGLQ raw uncorrected value
+ result["SECANGLQ"] = static_cast(secAnglQRaw);
+
+ return result;
+}
+
+map ADMTController::getSecAnglIRegisterBitMapping(uint16_t registerValue)
+{
+ map result;
+
+ // Bit 0 - Extract the STATUS bit
+ result["Status"] = (registerValue & 0x01) ? 1.0 : 0.0;
+
+ // Bits 15:2 - Extract the SECANGLI raw value
+ int16_t secAnglIRaw =
+ static_cast((registerValue & 0xFFFC) >> 2); // Mask Bits [1:0] and shift right by 2
+
+ // Convert the 2's complement raw value to the actual signed value
+ if(secAnglIRaw & 0x2000) { // Check the sign bit (Bit 13)
+ secAnglIRaw |= 0xC000; // Sign extend to preserve the 16-bit signed value
+ }
+
+ // Store the SECANGLI raw value (optional, for debugging or diagnostic
+ // purposes)
+ result["SECANGLI"] = static_cast(secAnglIRaw);
+
+ return result;
+}
+
+map ADMTController::getTmp1RegisterBitMapping(uint16_t registerValue, bool is5V)
+{
+ map result;
+
+ // Bits 15:4 - Extract the TMP1 raw value
+ uint16_t tmp1Raw = (registerValue & 0xFFF0) >> 4;
+
+ // Store the raw TMP1 value (for diagnostics)
+ result["TMP1Raw"] = static_cast(tmp1Raw);
+
+ // Calculate TMP1 temperature in degrees Celsius based on VDD
+ double tmp1DegC = 0.0;
+ if(is5V == true) {
+ tmp1DegC = (tmp1Raw - 1238.0) / 13.45;
+ } else {
+ tmp1DegC = (tmp1Raw - 1208.0) / 13.61;
+ }
+
+ // Store the calculated temperature in degrees Celsius
+ result["TMP1"] = tmp1DegC;
+
+ return result;
+}
+
+bool ADMTController::checkRegisterFault(uint16_t registerValue, bool isMode1)
+{
+ // Mode-specific checks
+ if(isMode1) {
+ return ((registerValue >> 14) & 0x01) || // AMR Radius Check
+ ((registerValue >> 13) & 0x01) || // Turn Counter Cross Check
+ ((registerValue >> 9) & 0x01) || // Count Sensor False State
+ ((registerValue >> 7) & 0x01) || // ECC Double Bit Error
+ ((registerValue >> 5) & 0x01) || // NVM CRC Fault
+ ((registerValue >> 3) & 0x01) || // VDRIVE Over Voltage
+ ((registerValue >> 2) & 0x01) || // VDRIVE Under Voltage
+ ((registerValue >> 1) & 0x01) || // VDD Over Voltage
+ ((registerValue >> 0) & 0x01); // VDD Under Voltage
+ } else {
+ // Check all bits if not in Mode 1
+ return ((registerValue >> 15) & 0x01) || // Sequencer Watchdog
+ ((registerValue >> 14) & 0x01) || // AMR Radius Check
+ ((registerValue >> 13) & 0x01) || // Turn Counter Cross Check
+ ((registerValue >> 12) & 0x01) || // MT Diagnostic
+ ((registerValue >> 11) & 0x01) || // Turn Count Sensor Levels
+ ((registerValue >> 10) & 0x01) || // Angle Cross Check
+ ((registerValue >> 9) & 0x01) || // Count Sensor False State
+ ((registerValue >> 8) & 0x01) || // Oscillator Drift
+ ((registerValue >> 7) & 0x01) || // ECC Double Bit Error
+ ((registerValue >> 6) & 0x01) || // Reserved
+ ((registerValue >> 5) & 0x01) || // NVM CRC Fault
+ ((registerValue >> 4) & 0x01) || // AFE Diagnostic
+ ((registerValue >> 3) & 0x01) || // VDRIVE Over Voltage
+ ((registerValue >> 2) & 0x01) || // VDRIVE Under Voltage
+ ((registerValue >> 1) & 0x01) || // VDD Over Voltage
+ ((registerValue >> 0) & 0x01); // VDD Under Voltage
+ }
+}
+
+int ADMTController::streamIO()
+{
+ int result = -1;
+ const char *deviceName = "admt4000";
+ const char *channelName = "rot";
+ const char *scaleAttrName = "scale";
+ const char *offsetAttrName = "offset";
+ size_t samples = 1;
+ bool isOutput = false;
+ bool isCyclic = false;
+
+ unsigned int i, j, major, minor;
+ char git_tag[8];
+ iio_library_get_version(&major, &minor, git_tag);
+ bool has_repeat = ((major * 10000) + minor) >= 8 ? true : false;
+
+ int offsetAttrValue = 0;
+
+ if(!m_iioCtx)
+ return result; // Check if the context is valid
+ if(iio_context_get_devices_count(m_iioCtx) < 1)
+ return result; // Check if there are devices in the context
+ struct iio_device *admtDevice = iio_context_find_device(m_iioCtx, deviceName); // Find the ADMT device
+ if(admtDevice == NULL)
+ return result;
+ struct iio_channel *channel =
+ iio_device_find_channel(admtDevice, channelName, isOutput); // Find the rotation channel
+ if(channel == NULL)
+ return result;
+ iio_channel_enable(channel);
+ double *scaleAttrValue = new double(1);
+ int scaleRet = iio_channel_attr_read_double(channel, scaleAttrName, scaleAttrValue); // Read the scale attribute
+ if(scaleRet != 0) {
+ delete scaleAttrValue;
+ return scaleRet;
+ }
+
+ char *offsetDst = new char[maxAttrSize];
+ iio_channel_attr_read(channel, offsetAttrName, offsetDst,
+ maxAttrSize); // Read the offset attribute
+ offsetAttrValue = atoi(offsetDst);
+ delete[] offsetDst;
+ struct iio_buffer *buffer = iio_device_create_buffer(admtDevice, samples, isCyclic); // Create a buffer
+
+ while(!stopStream.loadAcquire()) {
+ ssize_t numBytesRead;
+ char *pointerData, *pointerEnd;
+ ptrdiff_t pointerIncrement;
+
+ numBytesRead = iio_buffer_refill(buffer);
+ if(numBytesRead < 0)
+ break;
+
+ pointerIncrement = iio_buffer_step(buffer);
+ pointerEnd = static_cast(iio_buffer_end(buffer));
+
+ const struct iio_data_format *format = iio_channel_get_data_format(channel);
+ unsigned int repeat = has_repeat ? format->repeat : 1;
+
+ for(pointerData = static_cast(iio_buffer_first(buffer, channel)); pointerData < pointerEnd;
+ pointerData += pointerIncrement) {
+ for(int j = 0; j < repeat; j++) {
+ if(format->length / 8 == sizeof(int16_t)) {
+ int16_t rawValue = (reinterpret_cast(pointerData))[j];
+ double scaledValue = (rawValue - offsetAttrValue) * *scaleAttrValue;
+ Q_EMIT streamData(scaledValue);
+ }
+ }
+ }
+ }
+
+ delete scaleAttrValue;
+ iio_buffer_destroy(buffer);
+ return 0;
+}
+
+int ADMTController::streamChannel(const char *deviceName, const QVector channelNames, int bufferSize,
+ int sampleRate)
+{
+ int result = -1;
+ const char *scaleAttrName = "scale";
+ const char *offsetAttrName = "offset";
+ size_t samples = bufferSize;
+ vector values;
+ bool isOutput = false, isCyclic = false;
+
+ unsigned int i, j, major, minor;
+ char git_tag[8];
+ iio_library_get_version(&major, &minor, git_tag);
+ bool has_repeat = ((major * 10000) + minor) >= 8 ? true : false;
+
+ if(!m_iioCtx)
+ return result; // Check if the context is valid
+ if(iio_context_get_devices_count(m_iioCtx) < 1)
+ return result; // Check if there are devices in the context
+ struct iio_device *device = iio_context_find_device(m_iioCtx, deviceName); // Find the ADMT device
+
+ if(device == NULL)
+ return result;
+
+ QVector channels;
+ QVector scales;
+ QVector offsets;
+
+ for(int i = 0; i < channelNames.size(); i++) {
+ struct iio_channel *channel =
+ iio_device_find_channel(device, channelNames.at(i).toLocal8Bit().data(), isOutput);
+ if(channel == NULL)
+ break;
+ iio_channel_enable(channel);
+ channels.append(channel);
+ }
+
+ if(channels.size() == 0 && channels.size() != channelNames.size())
+ return result;
+
+ double *scaleAttrValue = new double();
+ for(int i = 0; i < channels.size(); i++) {
+ int scaleRet = iio_channel_attr_read_double(const_cast(channels[i]), scaleAttrName,
+ scaleAttrValue); // Read the scale attribute
+ if(scaleRet != 0)
+ break;
+ scales.insert(i, *scaleAttrValue);
+ }
+
+ delete scaleAttrValue;
+
+ for(int i = 0; i < channels.size(); i++) {
+ char *offsetDst = new char[maxAttrSize];
+ iio_channel_attr_read(const_cast(channels[i]), offsetAttrName, offsetDst,
+ maxAttrSize); // Read the offset attribute
+ int offsetAttrValue = atoi(offsetDst);
+ delete[] offsetDst;
+ offsets.insert(i, offsetAttrValue);
+ }
+
+ struct iio_buffer *buffer = iio_device_create_buffer(device, samples, isCyclic); // Create a buffer
+
+ QMap streamDataMap;
+ while(!stopStream.loadAcquire()) {
+ elapsedStreamTimer.start();
+ for(int i = 0; i < channels.size(); i++) {
+ values.clear();
+
+ ssize_t numBytesRead;
+ char *pointerData, *pointerEnd;
+ ptrdiff_t pointerIncrement;
+
+ numBytesRead = iio_buffer_refill(buffer);
+ if(numBytesRead < 0)
+ break;
+
+ pointerIncrement = iio_buffer_step(buffer);
+ pointerEnd = static_cast(iio_buffer_end(buffer));
+
+ const struct iio_data_format *format =
+ iio_channel_get_data_format(const_cast(channels[i]));
+ unsigned int repeat = has_repeat ? format->repeat : 1;
+
+ for(pointerData = static_cast(
+ iio_buffer_first(buffer, const_cast(channels[i])));
+ pointerData < pointerEnd; pointerData += pointerIncrement) {
+ for(int j = 0; j < repeat; j++) {
+ if(format->bits <= 8) {
+ int8_t rawValue = (reinterpret_cast