diff --git a/CMakeLists.txt b/CMakeLists.txt
index d8e6a51af5..0b6c1a545d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -302,4 +302,4 @@ set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
set(CPACK_SET_DESTDIR ON)
set(CPACK_COMPONENTS_ALL ${SCOPY_PDK})
-include(CPack)
+include(CPack)
\ No newline at end of file
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..b6fbb65d14
--- /dev/null
+++ b/plugins/admt/CMakeLists.txt
@@ -0,0 +1,109 @@
+#
+# 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 17)
+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
+)
+file(GLOB UI_LIST ui/*.ui)
+
+set(ENABLE_TESTING ON)
+if(ENABLE_TESTING)
+ add_subdirectory(test)
+endif()
+
+set(PROJECT_SOURCES ${SRC_LIST} ${HEADER_LIST} ${UI_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)
+
+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 ${CMAKE_CURRENT_SOURCE_DIR}/include/${SCOPY_MODULE})
+
+target_include_directories(${PROJECT_NAME} PUBLIC scopy-pluginbase scopy-gui)
+
+target_link_libraries(
+ ${PROJECT_NAME}
+ PUBLIC Qt::Widgets
+ Qt::Core
+ scopy-pluginbase
+ scopy-gui
+ scopy-iioutil
+ scopy-gr-util
+ scopy-iio-widgets
+)
+
+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)
\ No newline at end of file
diff --git a/plugins/admt/include/admt/admtcontroller.h b/plugins/admt/include/admt/admtcontroller.h
new file mode 100644
index 0000000000..bf0cd2c225
--- /dev/null
+++ b/plugins/admt/include/admt/admtcontroller.h
@@ -0,0 +1,256 @@
+/*
+ * 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
+#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;
+
+ 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,
+ ANGLE,
+ 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,
+ ECCDCDE,
+ ECCDIS,
+ CONFIGURATION_REGISTER_COUNT
+ };
+
+ enum SensorRegister
+ {
+ ABSANGLE,
+ ANGLEREG,
+ ANGLESEC,
+ SINE,
+ COSINE,
+ SECANGLI,
+ SECANGLQ,
+ RADIUS,
+ DIAG1,
+ DIAG2,
+ TMP0,
+ TMP1,
+ CNVCNT,
+ SENSOR_REGISTER_COUNT
+ };
+
+ enum UniqueIDRegister
+ {
+ UNIQID0,
+ UNIQID1,
+ UNIQID2,
+ UNIQID3,
+ UNIQID_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 uint32_t ConfigurationPages[CONFIGURATION_REGISTER_COUNT] = { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0x02, 0x02, 0x02, 0x02, 0x02 };
+ const uint32_t UniqueIdRegisters[UNIQID_REGISTER_COUNT] = { 0x1E, 0x1F, 0x20, 0x21 };
+ const uint32_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 uint32_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 uint32_t SensorPages[SENSOR_REGISTER_COUNT] = { UINT32_MAX, UINT32_MAX, UINT32_MAX, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 };
+
+ 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 uint32_t getConfigurationPage(ConfigurationRegister registerID);
+ const uint32_t getUniqueIdRegister(UniqueIDRegister registerID);
+ const uint32_t getHarmonicRegister(HarmonicRegister registerID);
+ const uint32_t getHarmonicPage(HarmonicRegister registerID);
+ const uint32_t getUniqueIdPage(UniqueIDRegister registerID);
+ const uint32_t getSensorRegister(SensorRegister registerID);
+ const uint32_t getSensorPage(SensorRegister 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 = 11, int samplesPerCycle = 256);
+ int writeDeviceRegistry(const char *deviceName, uint32_t address, uint32_t value);
+ int readDeviceRegistry(const char *deviceName, uint32_t address, 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);
+ int getAbsAngleTurnCount(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);
+
+private:
+ 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
\ No newline at end of file
diff --git a/plugins/admt/include/admt/admtplugin.h b/plugins/admt/include/admt/admtplugin.h
new file mode 100644
index 0000000000..9fa16a0668
--- /dev/null
+++ b/plugins/admt/include/admt/admtplugin.h
@@ -0,0 +1,70 @@
+/*
+ * 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 "scopy-admt_export.h"
+#include "admtcontroller.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;
+
+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/admtstylehelper.h b/plugins/admt/include/admt/admtstylehelper.h
new file mode 100644
index 0000000000..22b13f9f16
--- /dev/null
+++ b/plugins/admt/include/admt/admtstylehelper.h
@@ -0,0 +1,75 @@
+/*
+ * 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 ADMTSTYLEHELPER_H
+#define ADMTSTYLEHELPER_H
+
+#include "scopy-admt_export.h"
+#include "stylehelper.h"
+#include "style.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace scopy {
+namespace admt{
+class SCOPY_ADMT_EXPORT ADMTStyleHelper : public QObject
+{
+ Q_OBJECT
+protected:
+ ADMTStyleHelper(QObject *parent = nullptr);
+ ~ADMTStyleHelper();
+public:
+ // singleton
+ ADMTStyleHelper(ADMTStyleHelper &other) = delete;
+ void operator=(const ADMTStyleHelper &) = delete;
+ static ADMTStyleHelper *GetInstance();
+public:
+ static void initColorMap();
+ static QString getColor(QString id);
+ static void TopContainerButtonStyle(QPushButton *btn, QString objectName = "");
+ static void PlotWidgetStyle(PlotWidget *widget, QString objectName = "");
+ static void ComboBoxStyle(QComboBox *widget, QString objectName = "");
+ static void LineEditStyle(QLineEdit *widget, QString objectName = "");
+ static void ColoredSquareCheckbox(QCheckBox *chk, QColor color, QString objectName = "");
+ static void StartButtonStyle(QPushButton *btn, QString objectName = "");
+ static void TabWidgetStyle(QTabWidget *widget, const QString& styleHelperColor = "ScopyBlue", QString objectName = "");
+ static void TextStyle(QWidget *widget, const char *styleHelperColor = json::global::white, bool isBold = false, QString objectName = "");// void TextStyle(QWidget *widget, const QString& styleHelperColor, bool isBold = false, QString objectName = "");
+ static void MenuSmallLabel(QLabel *label, QString objectName = "");
+ static void LineStyle(QFrame *line, QString objectName = "");
+ static void UIBackgroundStyle(QWidget *widget, QString objectName = "");
+ static void GraphChannelStyle(QWidget *widget, QLayout *layout, QString objectName = "");
+ static void CalculatedCoeffWidgetRowStyle(QWidget *widget, QHBoxLayout *layout, QLabel *hLabel, QLabel *hMagLabel, QLabel *hPhaseLabel, QString objectName = "");
+private:
+ QMap colorMap;
+ static ADMTStyleHelper *pinstance_;
+};
+} // namespace admt
+} // namespace scopy
+
+#endif // ADMTSTYLEHELPER_H
\ No newline at end of file
diff --git a/plugins/admt/include/admt/harmoniccalibration.h b/plugins/admt/include/admt/harmoniccalibration.h
new file mode 100644
index 0000000000..4bc2a33027
--- /dev/null
+++ b/plugins/admt/include/admt/harmoniccalibration.h
@@ -0,0 +1,382 @@
+/*
+ * 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
+#include
+
+enum AcquisitionDataKey
+{
+ RADIUS,
+ ANGLE,
+ TURNCOUNT,
+ ABSANGLE,
+ SINE,
+ COSINE,
+ SECANGLI,
+ SECANGLQ,
+ ANGLESEC,
+ DIAG1,
+ DIAG2,
+ TMP0,
+ TMP1,
+ CNVCNT,
+ SCRADIUS,
+ SPIFAULT
+};
+
+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 commandLogWrite(QString message);
+ void updateFaultStatus(bool value);
+ void updateMotorPosition(double position);
+ void updateDIGIOUI(uint32_t *registerValue);
+ void updateFaultRegisterUI(uint32_t *registerValue);
+ void updateMTDiagnosticRegisterUI(uint32_t *registerValue);
+ void updateMTDiagnosticsUI(uint32_t *registerValue);
+Q_SIGNALS:
+ void runningChanged(bool);
+ void canCalibrateChanged(bool);
+ void updateUtilityUI();
+ void commandLogWriteSignal(QString message);
+ void updateFaultStatusSignal(bool value);
+ void motorPositionChanged(double position);
+ void DIGIORegisterChanged(uint32_t *registerValue);
+ void FaultRegisterChanged(uint32_t *registerValue);
+ void DIAG1RegisterChanged(uint32_t *registerValue);
+ void DIAG2RegisterChanged(uint32_t *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, angle, count, temp = 0.0, amax, rotate_vmax, dmax, disable, target_pos, current_pos, ramp_mode,
+ afeDiag0, afeDiag1, afeDiag2;
+
+ QPushButton *openLastMenuButton, *calibrationStartMotorButton,
+ *calibrateDataButton, *extractDataButton, *clearCalibrateDataButton,
+ *clearCommandLogButton, *applySequenceButton, *readAllRegistersButton;
+ QButtonGroup *rightMenuButtonGroup;
+
+ QLineEdit *motorTargetPositionLineEdit, *graphUpdateIntervalLineEdit, *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,
+ *sequenceTypeMenuCombo, *conversionTypeMenuCombo, *cnvSourceMenuCombo, *convertSynchronizationMenuCombo, *angleFilterMenuCombo, *eighthHarmonicMenuCombo;
+
+ QTabWidget *tabWidget, *calibrationDataGraphTabWidget, *resultDataTabWidget;
+
+ QListWidget *rawDataListWidget;
+
+ QPlainTextEdit *logsPlainTextEdit, *commandLogPlainTextEdit;
+
+ QCheckBox *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;
+
+ PlotWidget *acquisitionGraphPlotWidget, *angleErrorPlotWidget, *calibrationRawDataPlotWidget, *FFTAngleErrorPlotWidget,
+ *correctedErrorPlotWidget, *postCalibrationRawDataPlotWidget, *FFTCorrectedErrorPlotWidget;
+ PlotAxis *acquisitionXPlotAxis, *acquisitionYPlotAxis, *calibrationRawDataXPlotAxis, *calibrationRawDataYPlotAxis,
+ *angleErrorXPlotAxis, *angleErrorYPlotAxis, *FFTAngleErrorXPlotAxis, *FFTAngleErrorYPlotAxis,
+ *correctedErrorXPlotAxis, *correctedErrorYPlotAxis, *FFTCorrectedErrorXPlotAxis, *FFTCorrectedErrorYPlotAxis,
+ *postCalibrationRawDataXPlotAxis, *postCalibrationRawDataYPlotAxis;
+ PlotChannel *acquisitionAnglePlotChannel, *acquisitionABSAnglePlotChannel, *acquisitionTurnCountPlotChannel, *acquisitionTmp0PlotChannel,
+ *acquisitionTmp1PlotChannel, *acquisitionSinePlotChannel, *acquisitionCosinePlotChannel, *acquisitionRadiusPlotChannel,
+ *acquisitionSecAnglQPlotChannel, *acquisitionSecAnglIPlotChannel,
+ *angleErrorPlotChannel, *preCalibrationFFTPhasePlotChannel, *calibrationRawDataPlotChannel, *calibrationSineDataPlotChannel, *calibrationCosineDataPlotChannel,
+ *FFTAngleErrorMagnitudeChannel, *FFTAngleErrorPhaseChannel,
+ *correctedErrorPlotChannel,
+ *postCalibrationRawDataPlotChannel, *postCalibrationSineDataPlotChannel, *postCalibrationCosineDataPlotChannel,
+ *FFTCorrectedErrorMagnitudeChannel, *FFTCorrectedErrorPhaseChannel;
+
+ HorizontalSpinBox *motorMaxVelocitySpinBox, *motorAccelTimeSpinBox, *motorMaxDisplacementSpinBox, *motorTargetPositionSpinBox;
+
+ CustomSwitch *calibrationDisplayFormatSwitch,
+ *DIGIO0ENToggleSwitch, *DIGIO0FNCToggleSwitch,
+ *DIGIO1ENToggleSwitch, *DIGIO1FNCToggleSwitch,
+ *DIGIO2ENToggleSwitch, *DIGIO2FNCToggleSwitch,
+ *DIGIO3ENToggleSwitch, *DIGIO3FNCToggleSwitch,
+ *DIGIO4ENToggleSwitch, *DIGIO4FNCToggleSwitch,
+ *DIGIO5ENToggleSwitch, *DIGIO5FNCToggleSwitch,
+ *DIGIOALLToggleSwitch;
+
+ RegisterBlockWidget *cnvPageRegisterBlock, *digIORegisterBlock, *faultRegisterBlock, *generalRegisterBlock, *digIOEnRegisterBlock, *angleCkRegisterBlock, *eccDcdeRegisterBlock, *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_utilityUIThread, m_utilityThread;
+ QFutureWatcher m_deviceStatusWatcher, m_currentMotorPositionWatcher,
+ m_acquisitionUIWatcher, m_acquisitionDataWatcher, m_acquisitionGraphWatcher,
+ m_calibrationUIWatcher, m_utilityUIWatcher, m_utilityWatcher;
+
+ ToolTemplate* createAcquisitionWidget();
+ ToolTemplate* createCalibrationWidget();
+ ToolTemplate* createRegistersWidget();
+ ToolTemplate* createUtilityWidget();
+
+ void readDeviceProperties();
+ void initializeADMT();
+ bool readSequence();
+ void applySequence();
+ bool changeCNVPage(uint32_t page);
+ void initializeMotor();
+ void startDeviceStatusMonitor();
+ void stopDeviceStatusMonitor();
+ void getDeviceFaultStatus(int sampleRate);
+ void startCurrentMotorPositionMonitor();
+ void stopCurrentMotorPositionMonitor();
+ void currentMotorPositionTask(int sampleRate);
+ bool resetGENERAL();
+
+ #pragma region Acquisition Methods
+ bool updateChannelValues();
+ void updateCountValue();
+ void updateLineEditValues();
+ void startAcquisition();
+ void stopAcquisition();
+ void getAcquisitionSamples(int sampleRate);
+ double getAcquisitionParameterValue(const AcquisitionDataKey &key);
+ void plotAcquisition(QVector& list, PlotChannel* channel);
+ void prependAcquisitionData(const double& data, QVector& list);
+ void resetAcquisitionYAxisScale();
+ void acquisitionPlotTask(int sampleRate);
+ void acquisitionUITask(int sampleRate);
+ void startAcquisitionUITask();
+ void stopAcquisitionUITask();
+ void updateSequenceWidget();
+ void applySequenceAndUpdate();
+ void updateGeneralSettingEnabled(bool value);
+ void connectCheckBoxToAcquisitionGraph(QCheckBox* widget, PlotChannel* channel, AcquisitionDataKey key);
+ void GMRReset();
+ #pragma endregion
+
+ #pragma region Calibration Methods
+ void startCalibrationUITask();
+ void stopCalibrationUITask();
+ void calibrationUITask(int sampleRate);
+ void getCalibrationSamples();
+ void startCalibration();
+ void stopCalibration();
+ void startMotor();
+ void startMotorContinuous();
+ void postCalibrateData();
+ void resetAllCalibrationState();
+ void computeSineCosineOfAngles(QVector graphDataList);
+ void populateAngleErrorGraphs();
+ void populateCorrectedAngleErrorGraphs();
+ void flashHarmonicValues();
+ void calculateHarmonicValues();
+ void updateCalculatedCoeffAngle();
+ void updateCalculatedCoeffHex();
+ void resetCalculatedCoeffAngle();
+ void resetCalculatedCoeffHex();
+ void displayCalculatedCoeff();
+ void calibrationLogWrite(QString message = "");
+ void importCalibrationData();
+ void extractCalibrationData();
+ void toggleTabSwitching(bool value);
+ void canStartMotor(bool value);
+ void canCalibrate(bool);
+ void toggleMotorControls(bool value);
+ void clearCalibrationSamples();
+ void clearCalibrationSineCosine();
+ void clearPostCalibrationSamples();
+ void clearAngleErrorGraphs();
+ void clearCorrectedAngleErrorGraphs();
+ #pragma endregion
+
+ #pragma region Motor Methods
+ bool moveMotorToPosition(double& position, bool validate = true);
+ bool resetCurrentPositionToZero();
+ void stopMotor();
+ int readMotorAttributeValue(ADMTController::MotorAttribute attribute, double& value);
+ int writeMotorAttributeValue(ADMTController::MotorAttribute attribute, double value);
+ #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();
+ void toggleDIGIOEN(string DIGIOENName, bool value);
+ void toggleMTDiagnostics(int mode);
+ void toggleFaultRegisterMode(int mode);
+ bool resetDIGIO();
+ void clearCommandLog();
+ #pragma endregion
+
+ #pragma region Register Methods
+ void readAllRegisters();
+ void toggleRegisters(int mode);
+ #pragma endregion
+
+ #pragma region UI Helper Methods
+ void updateLabelValue(QLabel* label, int channelIndex);
+ void updateLabelValue(QLabel *label, ADMTController::MotorAttribute attribute);
+ bool updateChannelValue(int channelIndex);
+ void updateLineEditValue(QLineEdit* lineEdit, double value);
+ void toggleWidget(QPushButton *widget, bool value);
+ void changeCustomSwitchLabel(CustomSwitch *customSwitch, QString onLabel, QString offLabel);
+ void changeStatusLEDColor(MenuControlButton *menuControlButton, QColor color, bool checked = true);
+ void changeStatusLEDColor(QCheckBox *widget, const char *colorAttribute);
+ void updateFaultStatusLEDColor(MenuControlButton *widget, bool value);
+ void toggleStatusLEDColor(QCheckBox *widget, const char *trueAttribute, const char* falseAttribute, bool value);
+ MenuControlButton *createStatusLEDWidget(const QString title, QColor color, QWidget *parent = nullptr);
+ QCheckBox *createStatusLEDWidget(const QString &text, const char *colorAttribute, bool checked = false, QWidget *parent = nullptr);
+ MenuControlButton *createChannelToggleWidget(const QString title, QColor color, QWidget *parent = nullptr);
+ #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 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);
+ #pragma endregion
+
+ #pragma region Convert Methods
+ double convertRPStoVMAX(double rps);
+ double convertVMAXtoRPS(double vmax);
+ double convertAccelTimetoAMAX(double accelTime);
+ double convertAMAXtoAccelTime(double amax);
+ #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/horizontalspinbox.h b/plugins/admt/include/admt/widgets/horizontalspinbox.h
new file mode 100644
index 0000000000..ddeccadab2
--- /dev/null
+++ b/plugins/admt/include/admt/widgets/horizontalspinbox.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2025 Analog Devices Inc.
+ *
+ * This file is part of Scopy
+ * (see https://www.github.com/analogdevicesinc/scopy).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+#ifndef HORIZONTALSPINBOX_H
+#define HORIZONTALSPINBOX_H
+
+#include "scopy-admt_export.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace scopy::admt {
+ class SCOPY_ADMT_EXPORT HorizontalSpinBox : public QWidget
+ {
+ Q_OBJECT
+ public:
+ HorizontalSpinBox(QString header = "", double initialValue = 0.0, QString unit = "", QWidget *parent = nullptr);
+ QLineEdit *lineEdit();
+ void setEnabled(double value);
+ public Q_SLOTS:
+ void setValue(double);
+ protected Q_SLOTS:
+ void onMinusButtonPressed();
+ void onPlusButtonPressed();
+ void onLineEditTextEdited();
+ private:
+ double m_value = 0;
+ QString m_unit = "";
+ QLineEdit *m_lineEdit;
+ QPushButton *m_minusButton, *m_plusButton;
+ QLabel *m_unitLabel;
+ void applyLineEditStyle(QLineEdit *widget);
+ void applyPushButtonStyle(QPushButton *widget, int topLeftBorderRadius = 0, int topRightBorderRadius = 0, int bottomLeftBorderRadius = 0, int bottomRightBorderRadius = 0);
+ void applyUnitLabelStyle(QLabel *widget, bool isEnabled = true);
+ };
+} // namespace scopy::admt
+
+#endif // HORIZONTALSPINBOX_H
\ 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..6e326e9ff2
--- /dev/null
+++ b/plugins/admt/include/admt/widgets/registerblockwidget.h
@@ -0,0 +1,83 @@
+/*
+ * 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, uint32_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);
+ void applyLineEditStyle(QLineEdit *widget);
+ void applySpinBoxStyle(QSpinBox *widget);
+ };
+
+ 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
\ No newline at end of file
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/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..3dda2697c2
--- /dev/null
+++ b/plugins/admt/src/admtcontroller.cpp
@@ -0,0 +1,1586 @@
+/*
+ * 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
+#include
+#include
+#include
+
+static const size_t maxAttrSize = 512;
+
+using namespace scopy::admt;
+using namespace std;
+
+ADMTController::ADMTController(QString uri, QObject *parent)
+ :QObject(parent)
+ , uri(uri)
+{
+}
+
+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 uint32_t ADMTController::getHarmonicPage(HarmonicRegister registerID)
+{
+ if(registerID >= 0 && registerID < HARMONIC_REGISTER_COUNT){
+ return HarmonicPages[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint32_t ADMTController::getConfigurationRegister(ConfigurationRegister registerID)
+{
+ if(registerID >= 0 && registerID < CONFIGURATION_REGISTER_COUNT){
+ return ConfigurationRegisters[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint32_t ADMTController::getConfigurationPage(ConfigurationRegister registerID)
+{
+ if(registerID >= 0 && registerID < CONFIGURATION_REGISTER_COUNT){
+ return ConfigurationPages[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint32_t ADMTController::getSensorRegister(SensorRegister registerID)
+{
+ if(registerID >= 0 && registerID < SENSOR_REGISTER_COUNT){
+ return SensorRegisters[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint32_t ADMTController::getSensorPage(SensorRegister registerID)
+{
+ if(registerID >= 0 && registerID < SENSOR_REGISTER_COUNT){
+ return SensorPages[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint32_t ADMTController::getUniqueIdRegister(UniqueIDRegister registerID)
+{
+ if(registerID >= 0 && registerID < UNIQID_REGISTER_COUNT){
+ return UniqueIdRegisters[registerID];
+ }
+ return UINT32_MAX;
+}
+
+const uint32_t ADMTController::getUniqueIdPage(UniqueIDRegister registerID)
+{
+ if(registerID >= 0 && registerID < UNIQID_REGISTER_COUNT){
+ return UniqueIdPages[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)
+{
+ if(!m_iioCtx){ return static_cast(UINT64_MAX); } // return QString("No context available.");
+ double value;
+
+ int deviceCount = iio_context_get_devices_count(m_iioCtx);
+ if(deviceCount < 1) return static_cast(UINT64_MAX); // return QString("No devices found");
+
+ iio_device *admtDevice = iio_context_find_device(m_iioCtx, deviceName);
+ if(admtDevice == NULL) return static_cast(UINT64_MAX); // return QString("No ADMT4000 device");
+
+ int channelCount = iio_device_get_channels_count(admtDevice);
+ if(channelCount < 1) return static_cast(UINT64_MAX); // return QString("No channels found.");
+
+ 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);
+
+ if(deviceChannel != nullptr && std::string(deviceChannel) == std::string(channelName)){
+ message = message + "[" + std::to_string(i) + "]" + std::string(deviceChannel) + ", ";
+ break;
+ }
+ else {
+ channel = NULL;
+ }
+ }
+ if(channel == NULL) return static_cast(UINT64_MAX); // return QString("Channel not found.");
+ iio_channel_enable(channel);
+
+ double scale = 1.0;
+ int offsetAttrVal = 0;
+ const char *scaleAttrName = "scale";
+ const char *offsetAttrName = "offset";
+ const char *scaleAttr = iio_channel_find_attr(channel, scaleAttrName);
+ if(scaleAttr == NULL) return static_cast(UINT64_MAX); // return QString("No scale attribute");
+ const char *offsetAttr = iio_channel_find_attr(channel, offsetAttrName);
+ if(offsetAttr == NULL) return static_cast(UINT64_MAX); // return QString("No offset attribute");
+
+ double *scaleVal = new double(1);
+ int scaleRet = iio_channel_attr_read_double(channel, scaleAttr, scaleVal);
+ if(scaleRet != 0) return static_cast(UINT64_MAX); // return QString("Cannot read scale attribute");
+ scale = *scaleVal;
+
+ char *offsetDst = new char[maxAttrSize];
+ iio_channel_attr_read(channel, offsetAttr, offsetDst, maxAttrSize);
+ offsetAttrVal = std::atoi(offsetDst);
+
+ iio_buffer *iioBuffer = iio_device_create_buffer(admtDevice, bufferSize, false);
+ if(iioBuffer == NULL) return static_cast(UINT64_MAX); // return QString("Cannot create buffer.");
+
+ ssize_t numBytesRead;
+ int8_t *pointerData, *pointerEnd;
+ void *buffer;
+ ptrdiff_t pointerIncrement;
+
+ numBytesRead = iio_buffer_refill(iioBuffer);
+ if(numBytesRead < 0) return static_cast(UINT64_MAX); // return QString("Cannot refill buffer.");
+
+ pointerIncrement = reinterpret_cast(iio_buffer_step(iioBuffer));
+ pointerEnd = static_cast(iio_buffer_end(iioBuffer));
+
+ const struct iio_data_format *format = iio_channel_get_data_format(channel);
+ const struct iio_data_format channelFormat = *format;
+ unsigned int repeat = channelFormat.repeat;
+ uint8_t bitLength = static_cast(channelFormat.bits);
+ size_t offset = static_cast(channelFormat.shift);
+
+ QString result;
+ std::list rawSamples;
+ // std::list unsignedSamples;
+ std::list castSamples;
+
+ size_t sample, bytes;
+
+ size_t sampleSize = channelFormat.length / 8 * repeat;
+ //if(sampleSize == 0) return QString("Sample size is zero.");
+
+ buffer = malloc(sampleSize * bufferSize);
+ //if(!buffer) return QString("Cannot allocate memory for buffer.");
+
+ bytes = iio_channel_read(channel, iioBuffer, buffer, sampleSize * bufferSize);
+ for(sample = 0; sample < bytes / sampleSize; ++sample)
+ {
+ for(int j = 0; j < repeat; ++j)
+ {
+ if(channelFormat.length / 8 == sizeof(int16_t))
+ {
+ rawSamples.push_back(*((int8_t*)buffer));
+ int16_t rawValue = ((int16_t*)buffer)[sample+j];
+ castSamples.push_back(rawValue);
+ value = (rawValue - static_cast(offsetAttrVal)) * scale;
+ result = QString::number(value);
+ }
+ }
+ }
+
+ message = message + result.toStdString();
+ iio_buffer_destroy(iioBuffer);
+ return value; //QString::fromStdString(message);
+}
+
+/** @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;
+}
+
+/* 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) {
+ int CCW = 0, 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 + 1];
+ double H2Mag = angle_errors_fft_pre[2 * cycleCount + 1];
+ double H3Mag = angle_errors_fft_pre[3 * cycleCount + 1];
+ double H8Mag = angle_errors_fft_pre[8 * cycleCount + 1];
+
+ /* 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 + 1]);
+ double H2Phase = (180 / M_PI) * (angle_errors_fft_phase_pre[2 * cycleCount + 1]);
+ double H3Phase = (180 / M_PI) * (angle_errors_fft_phase_pre[3 * cycleCount + 1]);
+ double H8Phase = (180 / M_PI) * (angle_errors_fft_phase_pre[8 * cycleCount + 1]);
+
+ /* 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[1] - 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;
+
+ // Multiply the FFT magnitudes by 2
+ for (auto& magnitude : angle_errors_fft_pre) {
+ magnitude *= 2;
+ }
+}
+
+void ADMTController::postcalibrate(vector PANG, int cycleCount, int samplesPerCycle){
+ int CCW = 0, 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_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:8: Turn count in quarter turns
+ uint8_t turnCount = (registerValue & 0xFF00) >> 8;
+
+ if (turnCount <= 0xD5) {
+ // Straight binary turn count
+ return turnCount / 4; // Convert from quarter turns to whole turns
+ } else if (turnCount == 0xD6) {
+ // Invalid turn count
+ return -1;
+ } else {
+ // 2's complement turn count
+ int8_t signedTurnCount = static_cast(turnCount); // Handle as signed value
+ return signedTurnCount / 4; // Convert from quarter turns to whole turns
+ }
+}
+
+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