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 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 + } +} \ No newline at end of file diff --git a/plugins/admt/src/admtplugin.cpp b/plugins/admt/src/admtplugin.cpp new file mode 100644 index 0000000000..c030a8e027 --- /dev/null +++ b/plugins/admt/src/admtplugin.cpp @@ -0,0 +1,160 @@ +/* + * 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 "admtplugin.h" +#include "admtcontroller.h" +#include "harmoniccalibration.h" + +#include +#include + +#include + +Q_LOGGING_CATEGORY(CAT_ADMTPLUGIN, "ADMTPlugin") +using namespace scopy; +using namespace scopy::admt; + +const bool isDebug = false; + +bool ADMTPlugin::compatible(QString m_param, QString category) +{ + m_name = "ADMT4000"; + bool ret = false; + Connection *conn = ConnectionProvider::GetInstance()->open(m_param); + + if(!conn) { + qWarning(CAT_ADMTPLUGIN) << "No context available for ADMT"; + return false; + } + + iio_device *admtDevice = iio_context_find_device(conn->context(), "admt4000"); + if(admtDevice) { + ret = true; + } + + ConnectionProvider::close(m_param); + if(isDebug) return true; + return ret; +} + +bool ADMTPlugin::loadPage() +{ + // Here you must write the code for the plugin info page + // Below is an example for an iio device + /*m_page = new QWidget(); + m_page->setLayout(new QVBoxLayout(m_page)); + m_page->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + m_infoPage = new InfoPage(m_page); + m_infoPage->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + m_page->layout()->addWidget(m_infoPage); + m_page->layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Preferred, QSizePolicy::Expanding)); + + auto cp = ContextProvider::GetInstance(); + struct iio_context *context = cp->open(m_param); + ssize_t attributeCount = iio_context_get_attrs_count(context); + for(int i = 0; i < attributeCount; ++i) { + const char *name; + const char *value; + int ret = iio_context_get_attr(context, i, &name, &value); + if(ret < 0) { + qWarning(CAT_ADMTPLUGIN) << "Could not read attribute with index:" << i; + continue; + } + + m_infoPage->update(name, value); + } + cp->close(m_param); + m_page->ensurePolished();*/ + return true; +} + +bool ADMTPlugin::loadIcon() +{ + SCOPY_PLUGIN_ICON(":/gui/icons/adalm.svg"); + return true; +} + +void ADMTPlugin::loadToolList() +{ + m_toolList.append( + SCOPY_NEW_TOOLMENUENTRY("harmoniccalibration", "Harmonic Calibration", ":/gui/icons/scopy-default/icons/tool_oscilloscope.svg")); +} + +void ADMTPlugin::unload() { /*delete m_infoPage;*/ } + +QString ADMTPlugin::description() { return "Plugin for ADMT Harmonic Calibration"; } + +bool ADMTPlugin::onConnect() +{ + // This method is called when you try to connect to a device and the plugin is + // compatible to that device + // In case of success the function must return true and false otherwise + + Connection *conn = ConnectionProvider::GetInstance()->open(m_param); + if(conn == nullptr) + return false; + m_ctx = conn->context(); + m_toolList[0]->setEnabled(true); + m_toolList[0]->setRunBtnVisible(false); + + m_admtController = new ADMTController(m_param, this); + m_admtController->connectADMT(); + harmonicCalibration = new HarmonicCalibration(m_admtController, isDebug); + m_toolList[0]->setTool(harmonicCalibration); + + return true; +} + +bool ADMTPlugin::onDisconnect() +{ + // This method is called when the disconnect button is pressed + // It must remove all connections that were established on the connection + + dynamic_cast(harmonicCalibration)->requestDisconnect(); + + for(auto &tool : m_toolList) { + tool->setEnabled(false); + tool->setRunning(false); + tool->setRunBtnVisible(false); + QWidget *w = tool->tool(); + if(w) { + tool->setTool(nullptr); + delete(w); + } + } + + m_admtController->disconnectADMT(); + return true; +} + +void ADMTPlugin::initMetadata() +{ + loadMetadata( + R"plugin( + { + "priority":102, + "category":[ + "iio" + ], + "exclude":["*", "!debugger"] + } +)plugin"); +} diff --git a/plugins/admt/src/admtstylehelper.cpp b/plugins/admt/src/admtstylehelper.cpp new file mode 100644 index 0000000000..4bfc578873 --- /dev/null +++ b/plugins/admt/src/admtstylehelper.cpp @@ -0,0 +1,395 @@ +/* + * 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 "admtstylehelper.h" +#include "stylehelper.h" +#include + +using namespace scopy::admt; + +ADMTStyleHelper *ADMTStyleHelper::pinstance_{nullptr}; + +ADMTStyleHelper:: ADMTStyleHelper(QObject *parent) {} + +ADMTStyleHelper *ADMTStyleHelper::GetInstance() +{ + if(pinstance_ == nullptr) { + pinstance_ = new ADMTStyleHelper(QApplication::instance()); // singleton has the app as parent + } + return pinstance_; +} + +ADMTStyleHelper::~ADMTStyleHelper() {} + +void ADMTStyleHelper::initColorMap() +{ + auto sh = ADMTStyleHelper::GetInstance(); + sh->colorMap.insert("CH0", "#FF7200"); + sh->colorMap.insert("CH1", "#9013FE"); + sh->colorMap.insert("CH2", "#27B34F"); + sh->colorMap.insert("CH3", "#F8E71C"); + sh->colorMap.insert("CH4", "#4A64FF"); + sh->colorMap.insert("CH5", "#02BCD4"); + sh->colorMap.insert("CH6", "#F44336"); + sh->colorMap.insert("CH7", "#F5A623"); + sh->colorMap.insert("CH8", "#1981AE"); + sh->colorMap.insert("CH9", "#6FCEA6"); + sh->colorMap.insert("CH10", "#F7A1DA"); + sh->colorMap.insert("CH11", "#E3F5FC"); +} + +QString ADMTStyleHelper::getColor(QString id) +{ + auto sh = ADMTStyleHelper::GetInstance(); + return sh->colorMap[id]; +} + +void ADMTStyleHelper::TopContainerButtonStyle(QPushButton *btn, QString objectName) +{ + if(!objectName.isEmpty()) + btn->setObjectName(objectName); + QString style = QString(R"css( + QPushButton { + width: 88px; + height: 48px; + border-radius: 2px; + padding-left: 20px; + padding-right: 20px; + color: white; + font-weight: 700; + font-size: 14px; + background-color: &&ScopyBlue&&; + } + + QPushButton:disabled { + background-color:#727273; /* design token - uiElement*/ + } + + QPushButton:checked { + background-color:#272730; /* design token - scopy blue*/ + } + QPushButton:pressed { + background-color:#272730; + } + })css"); + style.replace("&&ScopyBlue&&", Style::getAttribute(json::theme::interactive_primary_idle)); + btn->setStyleSheet(style); +} + +void ADMTStyleHelper::PlotWidgetStyle(PlotWidget *widget, QString objectName) +{ + if(!objectName.isEmpty()) + widget->setObjectName(objectName); + widget->setContentsMargins(10, 10, 10, 6); + widget->plot()->canvas()->setStyleSheet("background-color: black;"); +} + +void ADMTStyleHelper::ComboBoxStyle(QComboBox *widget, QString objectName) +{ + if(!objectName.isEmpty()) + widget->setObjectName(objectName); + QString style = QString(R"css( + QWidget { + } + QComboBox { + text-align: right; + color: &&colorname&&; + border-radius: 4px; + height: 30px; + border-bottom: 0px solid none; + padding-left: 12px; + padding-right: 12px; + font-weight: normal; + font-size: 16px; + background-color: black; + } + QComboBox:disabled, QLineEdit:disabled { + background-color: #18181d; + color: #9c4600; + } + QComboBox QAbstractItemView { + border: none; + color: transparent; + outline: none; + background-color: black; + border-bottom: 0px solid transparent; + border-top: 0px solid transparent; + } + QComboBox QAbstractItemView::item { + text-align: right; + } + QComboBox::item:selected { + font-weight: bold; + font-size: 18px; + background-color: transparent; + } + QComboBox::drop-down { + border-image: none; + border: 0px; + width: 16px; + height: 16px; + margin-right: 12px; + } + QComboBox::down-arrow { + image: url(:/admt/chevron-down-s.svg); + } + QComboBox::indicator { + background-color: transparent; + selection-background-color: transparent; + color: transparent; + selection-color: transparent; + } + )css"); + style = style.replace(QString("&&colorname&&"), Style::getAttribute(json::global::ch0)); + widget->setStyleSheet(style); + widget->setFixedHeight(30); +} + +void ADMTStyleHelper::LineEditStyle(QLineEdit *widget, QString objectName) +{ + QString style = QString(R"css( + QLineEdit { + font-family: Open Sans; + font-size: 16px; + font-weight: normal; + text-align: right; + color: &&colorname&&; + + background-color: black; + border-radius: 4px; + border: none; + } + + QLineEdit:disabled { + background-color: #18181d; + color: #9c4600; + } + )css"); + style = style.replace(QString("&&colorname&&"), Style::getAttribute(json::global::ch0)); + widget->setStyleSheet(style); + widget->setFixedHeight(30); + widget->setContentsMargins(0, 0, 0, 0); + widget->setTextMargins(12, 4, 12, 4); + widget->setAlignment(Qt::AlignRight); +} + +void ADMTStyleHelper::ColoredSquareCheckbox(QCheckBox *chk, QColor color, QString objectName) +{ + if(!objectName.isEmpty()) + chk->setObjectName(objectName); + QString style = QString(R"css( + QCheckBox { + width:16px; + height:16px; + background-color: rgba(128,128,128,0); + color: rgba(255, 255, 255, 153); + } + QCheckBox::indicator { + width: 12px; + height: 12px; + border: 2px solid #FFFFFF; + border-radius: 4px; + } + QCheckBox::indicator:unchecked { background-color: &&UIElementBackground&&; } + QCheckBox::indicator:checked { background-color: &&colorname&&; } + )css"); + style.replace("&&colorname&&", color.name()); + style.replace("&&UIElementBackground&&", Style::getAttribute(json::theme::background_primary)); + chk->setStyleSheet(style); +} + +void ADMTStyleHelper::StartButtonStyle(QPushButton *btn, QString objectName) +{ + if(!objectName.isEmpty()) + btn->setObjectName(objectName); + QString style = QString(R"css( + QPushButton { + border-radius: 2px; + padding-left: 20px; + padding-right: 20px; + color: white; + font-weight: 700; + font-size: 14px; + } + + QPushButton:!checked { + background-color: #27b34f; + } + + QPushButton:checked { + background-color: #F45000; + } + + QPushButton:disabled { + background-color: grey; + })css"); + btn->setCheckable(true); + btn->setChecked(false); + btn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + btn->setFixedHeight(36); + btn->setStyleSheet(style); + QIcon playIcon; + playIcon.addPixmap(Util::ChangeSVGColor(":/gui/icons/play.svg", "white", 1), QIcon::Normal, QIcon::Off); + playIcon.addPixmap(Util::ChangeSVGColor(":/gui/icons/scopy-default/icons/play_stop.svg", "white", 1), + QIcon::Normal, QIcon::On); + btn->setIcon(playIcon); + btn->setIconSize(QSize(64, 64)); +} + +void ADMTStyleHelper::TabWidgetStyle(QTabWidget *widget, const QString& styleHelperColor, QString objectName) +{ + if(!objectName.isEmpty()) + widget->setObjectName(objectName); + QString style = QString(R"css( + QTabWidget::tab-bar { + left: 5px; /* move to the right by 5px */ + } + QTabBar::tab { + min-width: 100px; + min-height: 32px; + padding-bottom: 5px; + padding-left: 16px; + padding-right: 16px; + background-color: &&UIElementBackground&&; + font: normal; + } + QTabBar::tab:selected { + color: white; + border-bottom: 2px solid &&ScopyBlue&&; + margin-top: 0px; + } + QTabBar::tab:disabled{ + color: grey; + } + )css"); + style.replace("&&ScopyBlue&&", Style::getAttribute(json::theme::interactive_primary_idle)); + style.replace("&&UIElementBackground&&", Style::getAttribute(json::theme::background_primary)); + widget->tabBar()->setStyleSheet(style); +} + +void ADMTStyleHelper::TextStyle(QWidget *widget, const char *styleHelperColor, bool isBold, QString objectName) +{ + if(!objectName.isEmpty()) + widget->setObjectName(objectName); + QString existingStyle = widget->styleSheet(); + QString style = QString(R"css( + font-size: 16px; + font-weight: &&fontweight&&; + text-align: right; + color: &&colorname&&; + )css"); + style = style.replace(QString("&&colorname&&"), Style::getAttribute(styleHelperColor)); + QString fontWeight = QString("normal"); + if(isBold){ + fontWeight = QString("bold"); + } + style = style.replace(QString("&&fontweight&&"), fontWeight); + widget->setStyleSheet(existingStyle + style); +} + +void ADMTStyleHelper::MenuSmallLabel(QLabel *label, QString objectName) +{ + if(!objectName.isEmpty()) + label->setObjectName(objectName); + label->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + + QString style = QString(R"css( + QLabel { + color: white; + background-color: rgba(255,255,255,0); + font-weight: 500; + font-family: Open Sans; + font-size: 12px; + font-style: normal; + } + QLabel:disabled { + color: grey; + } + )css"); + label->setStyleSheet(style); +} + +void ADMTStyleHelper::LineStyle(QFrame *line, QString objectName) +{ + if(!objectName.isEmpty()) + line->setObjectName(objectName); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Plain); + line->setFixedHeight(1); + QString lineStyle = QString(R"css( + QFrame { + border: 1px solid #808085; + } + )css"); + line->setStyleSheet(lineStyle); +} + +void ADMTStyleHelper::UIBackgroundStyle(QWidget *widget, QString objectName) +{ + if(!objectName.isEmpty()) + widget->setObjectName(objectName); + QString style = QString(R"css( + background-color: &&colorname&&; + )css"); + style.replace(QString("&&colorname&&"), Style::getAttribute(json::theme::background_primary)); + widget->setStyleSheet(style); +} + +void ADMTStyleHelper::GraphChannelStyle(QWidget *widget, QLayout *layout, QString objectName) +{ + if(!objectName.isEmpty()) + widget->setObjectName(objectName); + widget->setLayout(layout); + ADMTStyleHelper::UIBackgroundStyle(widget); + layout->setContentsMargins(20, 13, 20, 5); + layout->setSpacing(20); +} + +void ADMTStyleHelper::CalculatedCoeffWidgetRowStyle(QWidget *widget, QHBoxLayout *layout, QLabel *hLabel, QLabel *hMagLabel, QLabel *hPhaseLabel, QString objectName) +{ + if(!objectName.isEmpty()) + widget->setObjectName(objectName); + + widget->setLayout(layout); + QString style = QString(R"css( + background-color: &&colorname&&; + border-radius: 4px; + )css"); + style.replace(QString("&&colorname&&"), Style::getAttribute(json::theme::background_subtle)); + widget->setStyleSheet(style); + widget->setFixedHeight(30); + widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + layout->setContentsMargins(12, 4, 12, 4); + + ADMTStyleHelper::TextStyle(hLabel, json::global::white, true); + ADMTStyleHelper::TextStyle(hMagLabel, json::global::ch0); + ADMTStyleHelper::TextStyle(hPhaseLabel, json::global::ch1); + + hLabel->setFixedWidth(24); + hMagLabel->setContentsMargins(0, 0, 32, 0); + hPhaseLabel->setFixedWidth(72); + + layout->addWidget(hLabel); + layout->addWidget(hMagLabel, 0, Qt::AlignRight); + layout->addWidget(hPhaseLabel); +} + +#include "moc_admtstylehelper.cpp" \ No newline at end of file diff --git a/plugins/admt/src/harmoniccalibration.cpp b/plugins/admt/src/harmoniccalibration.cpp new file mode 100644 index 0000000000..6501d5daac --- /dev/null +++ b/plugins/admt/src/harmoniccalibration.cpp @@ -0,0 +1,3838 @@ +/* + * 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 "harmoniccalibration.h" +#include "qtconcurrentrun.h" +#include "style.h" +#include "style_properties.h" + +#include +#include +#include + +static int acquisitionUITimerRate = 500; // In ms +static int acquisitionSampleRate = 20; +static int acquisitionGraphSampleRate = 100; + +static int calibrationUITimerRate = 500; +static int utilityUITimerRate = 1000; + +static int deviceStatusMonitorRate = 500; +static int motorPositionMonitorRate = 500; + +static int bufferSize = 1; +static int dataGraphSamples = 100; +static int tempGraphSamples = 100; +static bool running = false; +static double *dataGraphValue; +static double *tempGraphValue; + +static int cycleCount = 11; +static int samplesPerCycle = 256; +static int totalSamplesCount = cycleCount * samplesPerCycle; +static bool isStartMotor = false; +static bool isPostCalibration = false; +static bool isCalculatedCoeff = false; +static bool isAngleDisplayFormat = false; +static bool resetToZero = true; +static bool hasMTDiagnostics = false; + +static double motorTimeUnit = 1.048576; // t = 2^24/16Mhz +static int motorMicrostepPerRevolution = 51200; +static int motorfCLK = 16000000; // 16Mhz + +static uint32_t h1MagDeviceRegister = 0x15; +static uint32_t h2MagDeviceRegister = 0x17; +static uint32_t h3MagDeviceRegister = 0x19; +static uint32_t h8MagDeviceRegister = 0x1B; +static uint32_t h1PhaseDeviceRegister = 0x16; +static uint32_t h2PhaseDeviceRegister = 0x18; +static uint32_t h3PhaseDeviceRegister = 0x1A; +static uint32_t h8PhaseDeviceRegister = 0x1C; + +static int acquisitionDisplayLength = 200; +static QVector acquisitionAngleList, acquisitionABSAngleList, acquisitionTurnCountList, acquisitionTmp0List, + acquisitionTmp1List, acquisitionSineList, acquisitionCosineList, acquisitionRadiusList, + graphDataList, graphPostDataList; + +static const QColor scopyBlueColor = scopy::Style::getColor(json::theme::interactive_primary_idle); +static const QColor sineColor = QColor("#85e94c"); +static const QColor cosineColor = QColor("#91e6cf"); +static const QColor faultLEDColor = QColor("#c81a28"); +static const QColor gpioLEDColor = scopy::Style::getColor(json::theme::interactive_secondary_idle); +static const QColor statusLEDColor = QColor("#2e9e6f"); + +static const QPen scopyBluePen(scopyBlueColor); +static const QPen channel0Pen(scopy::Style::getAttribute(json::global::ch0)); +static const QPen channel1Pen(scopy::Style::getAttribute(json::global::ch1)); +static const QPen channel2Pen(scopy::Style::getAttribute(json::global::ch2)); +static const QPen channel3Pen(scopy::Style::getAttribute(json::global::ch3)); +static const QPen channel4Pen(scopy::Style::getAttribute(json::global::ch4)); +static const QPen channel5Pen(scopy::Style::getAttribute(json::global::ch5)); +static const QPen channel6Pen(scopy::Style::getAttribute(json::global::ch6)); +static const QPen channel7Pen(scopy::Style::getAttribute(json::global::ch7)); +static const QPen sinePen(sineColor); +static const QPen cosinePen(cosineColor); + +static map deviceRegisterMap; +static map generalRegisterMap; +static map DIGIOENRegisterMap; +static map FAULTRegisterMap; +static map DIAG1RegisterMap; +static map DIAG2RegisterMap; +static map DIAG1AFERegisterMap; + +static QString deviceName = ""; +static QString deviceType = ""; +static bool is5V = false; + +static double H1_MAG_ANGLE, H2_MAG_ANGLE, H3_MAG_ANGLE, H8_MAG_ANGLE, H1_PHASE_ANGLE, H2_PHASE_ANGLE, H3_PHASE_ANGLE, H8_PHASE_ANGLE; +static uint32_t H1_MAG_HEX, H2_MAG_HEX, H3_MAG_HEX, H8_MAG_HEX, H1_PHASE_HEX, H2_PHASE_HEX, H3_PHASE_HEX, H8_PHASE_HEX; + +static int acquisitionGraphYMin = 0; +static int acquisitionGraphYMax = 360; + +static bool deviceStatusFault = false; + +static bool isAcquisitionTab = false; +static bool isCalibrationTab = false; +static bool isUtilityTab = false; + +static bool isStartAcquisition = false; + +static bool isDeviceStatusMonitor = false; +static bool isMotorPositionMonitor = false; + +static int readMotorDebounce = 50; // In ms + +static map acquisitionDataMap = { + {RADIUS, false}, + {ANGLE, false}, + {TURNCOUNT, false}, + {ABSANGLE, false}, + {SINE, false}, + {COSINE, false}, + {SECANGLI, false}, + {SECANGLQ, false}, + {ANGLESEC, false}, + {DIAG1, false}, + {DIAG2, false}, + {TMP0, false}, + {TMP1, false}, + {CNVCNT, false}, + {SCRADIUS, false}, + {SPIFAULT, false} +}; + +using namespace scopy; +using namespace scopy::admt; + +HarmonicCalibration::HarmonicCalibration(ADMTController *m_admtController, bool isDebug, QWidget *parent) + : QWidget(parent) + , isDebug(isDebug) + , m_admtController(m_admtController) +{ + ADMTStyleHelper::GetInstance()->initColorMap(); + readDeviceProperties(); + readSequence(); + initializeADMT(); + initializeMotor(); + + rotationChannelName = m_admtController->getChannelId(ADMTController::Channel::ROTATION); + angleChannelName = m_admtController->getChannelId(ADMTController::Channel::ANGLE); + countChannelName = m_admtController->getChannelId(ADMTController::Channel::COUNT); + temperatureChannelName = m_admtController->getChannelId(ADMTController::Channel::TEMPERATURE); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + QHBoxLayout *lay = new QHBoxLayout(this); + tabWidget = new QTabWidget(this); + + setLayout(lay); + lay->setMargin(0); + lay->insertWidget(1, tabWidget); + tabWidget->addTab(createAcquisitionWidget(), "Acquisition"); + tabWidget->addTab(createCalibrationWidget(), "Calibration"); + tabWidget->addTab(createUtilityWidget(), "Utility"); + tabWidget->addTab(createRegistersWidget(), "Registers"); + + connect(tabWidget, &QTabWidget::currentChanged, [=](int index){ + tabWidget->setCurrentIndex(index); + + if(index == 0 || index == 1) + { + if(isDeviceStatusMonitor) isDeviceStatusMonitor = false; + if(isMotorPositionMonitor) isMotorPositionMonitor = false; + + startDeviceStatusMonitor(); + if(index == 1) startCurrentMotorPositionMonitor(); + } + else{ + isDeviceStatusMonitor = false; + isMotorPositionMonitor = false; + } + + if(index == 0) // Acquisition Tab + { + startAcquisitionUITask(); + readSequence(); + updateSequenceWidget(); + } + else + { + stopAcquisitionUITask(); + stop(); + } + + if(index == 1) // Calibration Tab + { + startCalibrationUITask(); + } + else + { + stopCalibrationUITask(); + } + + if(index == 2) // Utility Tab + { + readSequence(); + toggleFaultRegisterMode(generalRegisterMap.at("Sequence Type")); + toggleMTDiagnostics(generalRegisterMap.at("Sequence Type")); + toggleUtilityTask(true); + } + else + { + toggleUtilityTask(false); + } + + if(index == 3) // Registers Tab + { + readSequence(); + toggleRegisters(generalRegisterMap.at("Sequence Type")); + } + }); + + connect(this, &HarmonicCalibration::updateFaultStatusSignal, this, &HarmonicCalibration::updateFaultStatus); + connect(this, &HarmonicCalibration::motorPositionChanged, this, &HarmonicCalibration::updateMotorPosition); + + connect(this, &HarmonicCalibration::commandLogWriteSignal, this, &HarmonicCalibration::commandLogWrite); + connect(this, &HarmonicCalibration::DIGIORegisterChanged, this, &HarmonicCalibration::updateDIGIOUI); + connect(this, &HarmonicCalibration::FaultRegisterChanged, this, &HarmonicCalibration::updateFaultRegisterUI); + connect(this, &HarmonicCalibration::DIAG1RegisterChanged, this, &HarmonicCalibration::updateMTDiagnosticRegisterUI); + connect(this, &HarmonicCalibration::DIAG2RegisterChanged, this, &HarmonicCalibration::updateMTDiagnosticsUI); + + startAcquisitionUITask(); + startDeviceStatusMonitor(); + startCurrentMotorPositionMonitor(); +} + +HarmonicCalibration::~HarmonicCalibration() +{ + requestDisconnect(); +} + +ToolTemplate* HarmonicCalibration::createAcquisitionWidget() +{ + tool = new ToolTemplate(this); + openLastMenuButton = new OpenLastMenuBtn(dynamic_cast(tool->rightContainer()), true, this); + rightMenuButtonGroup = dynamic_cast(openLastMenuButton)->getButtonGroup(); + + settingsButton = new GearBtn(this); + runButton = new RunBtn(this); + + QPushButton *resetGMRButton = new QPushButton(this); + resetGMRButton->setText("GMR Reset"); + Style::setStyle(resetGMRButton, style::properties::button::singleButton); + connect(resetGMRButton, &QPushButton::clicked, this, &HarmonicCalibration::GMRReset); + + rightMenuButtonGroup->addButton(settingsButton); + + #pragma region Raw Data Widget + QScrollArea *rawDataScroll = new QScrollArea(this); + rawDataScroll->setWidgetResizable(true); + QWidget *rawDataWidget = new QWidget(rawDataScroll); + rawDataScroll->setWidget(rawDataWidget); + QVBoxLayout *rawDataLayout = new QVBoxLayout(rawDataWidget); + rawDataLayout->setMargin(0); + rawDataWidget->setLayout(rawDataLayout); + + MenuSectionWidget *rotationWidget = new MenuSectionWidget(rawDataWidget); + MenuSectionWidget *angleWidget = new MenuSectionWidget(rawDataWidget); + MenuSectionWidget *countWidget = new MenuSectionWidget(rawDataWidget); + MenuSectionWidget *tempWidget = new MenuSectionWidget(rawDataWidget); + rotationWidget->contentLayout()->setSpacing(8); + angleWidget->contentLayout()->setSpacing(8); + countWidget->contentLayout()->setSpacing(8); + tempWidget->contentLayout()->setSpacing(8); + MenuCollapseSection *rotationSection = new MenuCollapseSection("ABS Angle", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, rotationWidget); + MenuCollapseSection *angleSection = new MenuCollapseSection("Angle", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, angleWidget); + MenuCollapseSection *countSection = new MenuCollapseSection("Count", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, countWidget); + MenuCollapseSection *tempSection = new MenuCollapseSection("Temp 0", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, tempWidget); + rotationSection->contentLayout()->setSpacing(8); + angleSection->contentLayout()->setSpacing(8); + countSection->contentLayout()->setSpacing(8); + tempSection->contentLayout()->setSpacing(8); + + rotationWidget->contentLayout()->addWidget(rotationSection); + angleWidget->contentLayout()->addWidget(angleSection); + countWidget->contentLayout()->addWidget(countSection); + tempWidget->contentLayout()->addWidget(tempSection); + + rotationValueLabel = new QLabel("--.--°", rotationSection); + angleValueLabel = new QLabel("--.--°", angleSection); + countValueLabel = new QLabel("--", countSection); + tempValueLabel = new QLabel("--.-- °C", tempSection); + + rotationSection->contentLayout()->addWidget(rotationValueLabel); + angleSection->contentLayout()->addWidget(angleValueLabel); + countSection->contentLayout()->addWidget(countValueLabel); + tempSection->contentLayout()->addWidget(tempValueLabel); + + #pragma region Acquisition Motor Configuration Section Widget + MenuSectionWidget *motorConfigurationSectionWidget = new MenuSectionWidget(rawDataWidget); + MenuCollapseSection *motorConfigurationCollapseSection = new MenuCollapseSection("Motor Configuration", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, motorConfigurationSectionWidget); + motorConfigurationCollapseSection->header()->toggle(); + motorConfigurationSectionWidget->contentLayout()->addWidget(motorConfigurationCollapseSection); + + motorMaxVelocitySpinBox = new HorizontalSpinBox("Max Velocity", convertVMAXtoRPS(rotate_vmax), "rps", motorConfigurationSectionWidget); + motorMaxVelocitySpinBox->setValue(1); + motorAccelTimeSpinBox = new HorizontalSpinBox("Acceleration Time", convertAMAXtoAccelTime(amax), "sec", motorConfigurationSectionWidget); + motorAccelTimeSpinBox->setValue(1); + motorMaxDisplacementSpinBox = new HorizontalSpinBox("Max Displacement", dmax, "", motorConfigurationSectionWidget); + + m_calibrationMotorRampModeMenuCombo = new MenuCombo("Ramp Mode", motorConfigurationSectionWidget); + auto calibrationMotorRampModeCombo = m_calibrationMotorRampModeMenuCombo->combo(); + calibrationMotorRampModeCombo->addItem("Position", QVariant(ADMTController::MotorRampMode::POSITION)); + calibrationMotorRampModeCombo->addItem("Ramp Mode 1", QVariant(ADMTController::MotorRampMode::RAMP_MODE_1)); + ADMTStyleHelper::ComboBoxStyle(calibrationMotorRampModeCombo); + + motorConfigurationCollapseSection->contentLayout()->setSpacing(8); + motorConfigurationCollapseSection->contentLayout()->addWidget(motorMaxVelocitySpinBox); + motorConfigurationCollapseSection->contentLayout()->addWidget(motorAccelTimeSpinBox); + motorConfigurationCollapseSection->contentLayout()->addWidget(motorMaxDisplacementSpinBox); + motorConfigurationCollapseSection->contentLayout()->addWidget(m_calibrationMotorRampModeMenuCombo); + #pragma endregion + + #pragma region Acquisition Motor Control Section Widget + MenuSectionWidget *motorControlSectionWidget = new MenuSectionWidget(rawDataWidget); + MenuCollapseSection *motorControlCollapseSection = new MenuCollapseSection("Motor Control", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, motorControlSectionWidget); + motorControlSectionWidget->contentLayout()->setSpacing(8); + motorControlSectionWidget->contentLayout()->addWidget(motorControlCollapseSection); + QLabel *currentPositionLabel = new QLabel("Current Position", motorControlSectionWidget); + acquisitionMotorCurrentPositionLineEdit = new QLineEdit("--.--", motorControlSectionWidget); + acquisitionMotorCurrentPositionLineEdit->setReadOnly(true); + connectLineEditToDouble(acquisitionMotorCurrentPositionLineEdit, current_pos); + + QLabel *targetPositionLabel = new QLabel("Target Position", motorControlSectionWidget); + motorTargetPositionLineEdit = new QLineEdit(QString::number(target_pos), motorControlSectionWidget); + + motorControlCollapseSection->contentLayout()->setSpacing(8); + motorControlCollapseSection->contentLayout()->addWidget(currentPositionLabel); + motorControlCollapseSection->contentLayout()->addWidget(acquisitionMotorCurrentPositionLineEdit); + motorControlCollapseSection->contentLayout()->addWidget(targetPositionLabel); + motorControlCollapseSection->contentLayout()->addWidget(motorTargetPositionLineEdit); + #pragma endregion + + rawDataLayout->addWidget(angleWidget); + rawDataLayout->addWidget(rotationWidget); + rawDataLayout->addWidget(countWidget); + rawDataLayout->addWidget(tempWidget); + rawDataLayout->addWidget(motorConfigurationSectionWidget); + rawDataLayout->addWidget(motorControlSectionWidget); + rawDataLayout->addStretch(); + #pragma endregion + + #pragma region Acquisition Graph Section Widget + MenuSectionWidget *acquisitionGraphSectionWidget = new MenuSectionWidget(this); + MenuCollapseSection *acquisitionGraphCollapseSection = new MenuCollapseSection("Captured Data", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, acquisitionGraphSectionWidget); + acquisitionGraphSectionWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + acquisitionGraphSectionWidget->contentLayout()->addWidget(acquisitionGraphCollapseSection); + acquisitionGraphCollapseSection->contentLayout()->setSpacing(8); + + acquisitionGraphPlotWidget = new PlotWidget(); + ADMTStyleHelper::PlotWidgetStyle(acquisitionGraphPlotWidget); + acquisitionGraphPlotWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + acquisitionGraphPlotWidget->setShowXAxisLabels(true); + acquisitionGraphPlotWidget->setShowYAxisLabels(true); + acquisitionGraphPlotWidget->showAxisLabels(); + + acquisitionXPlotAxis = new PlotAxis(QwtAxis::XBottom, acquisitionGraphPlotWidget, channel0Pen); + acquisitionXPlotAxis->setInterval(0, acquisitionDisplayLength); + acquisitionYPlotAxis = new PlotAxis(QwtAxis::YLeft, acquisitionGraphPlotWidget, channel0Pen); + acquisitionYPlotAxis->setInterval(0, 360); + + acquisitionAnglePlotChannel = new PlotChannel("Angle", channel0Pen, acquisitionXPlotAxis, acquisitionYPlotAxis); + acquisitionABSAnglePlotChannel = new PlotChannel("ABS Angle", channel1Pen, acquisitionXPlotAxis, acquisitionYPlotAxis); + acquisitionTurnCountPlotChannel = new PlotChannel("Turn Count", channel2Pen, acquisitionXPlotAxis, acquisitionYPlotAxis); + acquisitionTmp0PlotChannel = new PlotChannel("TMP 0", channel3Pen, acquisitionXPlotAxis, acquisitionYPlotAxis); + acquisitionTmp1PlotChannel = new PlotChannel("TMP 1", channel4Pen, acquisitionXPlotAxis, acquisitionYPlotAxis); + acquisitionSinePlotChannel = new PlotChannel("Sine", sinePen, acquisitionXPlotAxis, acquisitionYPlotAxis); + acquisitionCosinePlotChannel = new PlotChannel("Cosine", cosinePen, acquisitionXPlotAxis, acquisitionYPlotAxis); + acquisitionRadiusPlotChannel = new PlotChannel("Radius", channel5Pen, acquisitionXPlotAxis, acquisitionYPlotAxis); + acquisitionSecAnglQPlotChannel = new PlotChannel("SECANGLQ", channel6Pen, acquisitionXPlotAxis, acquisitionYPlotAxis); + acquisitionSecAnglIPlotChannel = new PlotChannel("SECANGLI", channel7Pen, acquisitionXPlotAxis, acquisitionYPlotAxis); + + acquisitionGraphPlotWidget->addPlotChannel(acquisitionAnglePlotChannel); + acquisitionGraphPlotWidget->addPlotChannel(acquisitionABSAnglePlotChannel); + acquisitionGraphPlotWidget->addPlotChannel(acquisitionTurnCountPlotChannel); + acquisitionGraphPlotWidget->addPlotChannel(acquisitionTmp0PlotChannel); + acquisitionGraphPlotWidget->addPlotChannel(acquisitionTmp1PlotChannel); + acquisitionGraphPlotWidget->addPlotChannel(acquisitionSinePlotChannel); + acquisitionGraphPlotWidget->addPlotChannel(acquisitionCosinePlotChannel); + acquisitionGraphPlotWidget->addPlotChannel(acquisitionRadiusPlotChannel); + acquisitionGraphPlotWidget->addPlotChannel(acquisitionSecAnglQPlotChannel); + acquisitionGraphPlotWidget->addPlotChannel(acquisitionSecAnglIPlotChannel); + acquisitionAnglePlotChannel->setEnabled(true); + acquisitionABSAnglePlotChannel->setEnabled(true); + acquisitionTurnCountPlotChannel->setEnabled(true); + acquisitionTmp0PlotChannel->setEnabled(true); + acquisitionTmp1PlotChannel->setEnabled(true); + acquisitionSinePlotChannel->setEnabled(true); + acquisitionCosinePlotChannel->setEnabled(true); + acquisitionRadiusPlotChannel->setEnabled(true); + acquisitionSecAnglQPlotChannel->setEnabled(true); + acquisitionSecAnglIPlotChannel->setEnabled(true); + acquisitionGraphPlotWidget->selectChannel(acquisitionAnglePlotChannel); + + acquisitionGraphPlotWidget->replot(); + + #pragma region Channel Selection + QWidget *acquisitionGraphChannelWidget = new QWidget(acquisitionGraphSectionWidget); + QGridLayout *acquisitionGraphChannelGridLayout = new QGridLayout(acquisitionGraphChannelWidget); + acquisitionGraphChannelGridLayout->setContentsMargins(16, 8, 8, 16); + acquisitionGraphChannelGridLayout->setSpacing(8); + + QCheckBox *angleCheckBox = new QCheckBox("Angle", acquisitionGraphChannelWidget); + ADMTStyleHelper::ColoredSquareCheckbox(angleCheckBox, channel0Pen.color()); + connectCheckBoxToAcquisitionGraph(angleCheckBox, acquisitionAnglePlotChannel, ANGLE); + angleCheckBox->setChecked(true); + + QCheckBox *absAngleCheckBox = new QCheckBox("ABS Angle", acquisitionGraphChannelWidget); + ADMTStyleHelper::ColoredSquareCheckbox(absAngleCheckBox, channel1Pen.color()); + connectCheckBoxToAcquisitionGraph(absAngleCheckBox, acquisitionABSAnglePlotChannel, ABSANGLE); + + QCheckBox *temp0CheckBox = new QCheckBox("Temp 0", acquisitionGraphChannelWidget); + ADMTStyleHelper::ColoredSquareCheckbox(temp0CheckBox, channel3Pen.color()); + connectCheckBoxToAcquisitionGraph(temp0CheckBox, acquisitionTmp0PlotChannel, TMP0); + + QCheckBox *sineCheckBox = new QCheckBox("Sine", acquisitionGraphChannelWidget); + ADMTStyleHelper::ColoredSquareCheckbox(sineCheckBox, sineColor); + connectCheckBoxToAcquisitionGraph(sineCheckBox, acquisitionSinePlotChannel, SINE); + + QCheckBox *cosineCheckBox = new QCheckBox("Cosine", acquisitionGraphChannelWidget); + ADMTStyleHelper::ColoredSquareCheckbox(cosineCheckBox, cosineColor); + connectCheckBoxToAcquisitionGraph(cosineCheckBox, acquisitionCosinePlotChannel, COSINE); + + QCheckBox *radiusCheckBox = new QCheckBox("Radius", acquisitionGraphChannelWidget); + ADMTStyleHelper::ColoredSquareCheckbox(radiusCheckBox, channel5Pen.color()); + connectCheckBoxToAcquisitionGraph(radiusCheckBox, acquisitionRadiusPlotChannel, RADIUS); + + if(generalRegisterMap.at("Sequence Type") == 0) // Sequence Mode 1 + { + acquisitionGraphChannelGridLayout->addWidget(angleCheckBox, 0, 0); + acquisitionGraphChannelGridLayout->addWidget(sineCheckBox, 0, 1); + acquisitionGraphChannelGridLayout->addWidget(cosineCheckBox, 0, 2); + acquisitionGraphChannelGridLayout->addWidget(radiusCheckBox, 0, 3); + acquisitionGraphChannelGridLayout->addWidget(absAngleCheckBox, 1, 0); + acquisitionGraphChannelGridLayout->addWidget(temp0CheckBox, 1, 1); + } + else if(generalRegisterMap.at("Sequence Type") == 1) // Sequence Mode 2 + { + acquisitionGraphChannelGridLayout->addWidget(angleCheckBox, 0, 0); + acquisitionGraphChannelGridLayout->addWidget(sineCheckBox, 0, 1); + acquisitionGraphChannelGridLayout->addWidget(cosineCheckBox, 0, 2); + acquisitionGraphChannelGridLayout->addWidget(radiusCheckBox, 0, 3); + acquisitionGraphChannelGridLayout->addWidget(absAngleCheckBox, 1, 0); + acquisitionGraphChannelGridLayout->addWidget(temp0CheckBox, 1, 1); + } + #pragma endregion + + acquisitionGraphCollapseSection->contentLayout()->addWidget(acquisitionGraphPlotWidget); + acquisitionGraphCollapseSection->contentLayout()->addWidget(acquisitionGraphChannelWidget); + #pragma endregion + + #pragma region General Setting + QScrollArea *generalSettingScroll = new QScrollArea(this); + generalSettingScroll->setWidgetResizable(true); + QWidget *generalSettingWidget = new QWidget(generalSettingScroll); + generalSettingScroll->setWidget(generalSettingWidget); + QVBoxLayout *generalSettingLayout = new QVBoxLayout(generalSettingWidget); + generalSettingLayout->setMargin(0); + generalSettingWidget->setLayout(generalSettingLayout); + + header = new MenuHeaderWidget(deviceName + " " + deviceType, scopyBluePen, this); + + // General Setting Widget + MenuSectionWidget *generalWidget = new MenuSectionWidget(generalSettingWidget); + generalWidget->contentLayout()->setSpacing(8); + MenuCollapseSection *generalSection = new MenuCollapseSection("Data Acquisition", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, generalWidget); + generalSection->header()->toggle(); + generalSection->contentLayout()->setSpacing(8); + generalWidget->contentLayout()->addWidget(generalSection); + + // Graph Update Interval + QLabel *graphUpdateIntervalLabel = new QLabel(generalSection); + graphUpdateIntervalLabel->setText("Graph Update Interval (ms)"); + graphUpdateIntervalLineEdit = new QLineEdit(generalSection); + graphUpdateIntervalLineEdit->setText(QString::number(acquisitionGraphSampleRate)); + + connectLineEditToNumber(graphUpdateIntervalLineEdit, acquisitionGraphSampleRate, 1, 5000); + + // Data Sample Size + QLabel *displayLengthLabel = new QLabel("Display Length", generalSection); + displayLengthLineEdit = new QLineEdit(generalSection); + displayLengthLineEdit->setText(QString::number(acquisitionDisplayLength)); + + connectLineEditToNumber(displayLengthLineEdit, acquisitionDisplayLength, 1, 2048); + + QPushButton *resetYAxisButton = new QPushButton("Reset Y-Axis Scale", generalSection); + StyleHelper::BasicButton(resetYAxisButton); + connect(resetYAxisButton, &QPushButton::clicked, this, &HarmonicCalibration::resetAcquisitionYAxisScale); + + generalSection->contentLayout()->addWidget(graphUpdateIntervalLabel); + generalSection->contentLayout()->addWidget(graphUpdateIntervalLineEdit); + generalSection->contentLayout()->addWidget(displayLengthLabel); + generalSection->contentLayout()->addWidget(displayLengthLineEdit); + generalSection->contentLayout()->addWidget(resetYAxisButton); + + MenuSectionWidget *sequenceWidget = new MenuSectionWidget(generalSettingWidget); + MenuCollapseSection *sequenceSection = new MenuCollapseSection("Sequence", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, sequenceWidget); + sequenceWidget->contentLayout()->addWidget(sequenceSection); + sequenceSection->contentLayout()->setSpacing(8); + + sequenceTypeMenuCombo = new MenuCombo("Sequence Type", sequenceSection); + QComboBox *sequenceTypeComboBox = sequenceTypeMenuCombo->combo(); + sequenceTypeComboBox->addItem("Mode 1", QVariant(0)); + if(deviceType.toStdString() == "Automotive") + { + sequenceTypeComboBox->addItem("Mode 2", QVariant(1)); + } + + conversionTypeMenuCombo = new MenuCombo("Conversion Type", sequenceSection); + QComboBox *conversionTypeComboBox = conversionTypeMenuCombo->combo(); + conversionTypeComboBox->addItem("Continuous conversions", QVariant(0)); + conversionTypeComboBox->addItem("One-shot conversion", QVariant(1)); + + convertSynchronizationMenuCombo = new MenuCombo("Convert Synchronization", sequenceSection); + QComboBox *convertSynchronizationComboBox = convertSynchronizationMenuCombo->combo(); + convertSynchronizationComboBox->addItem("Enabled", QVariant(1)); + convertSynchronizationComboBox->addItem("Disabled", QVariant(0)); + + angleFilterMenuCombo = new MenuCombo("Angle Filter", sequenceSection); + QComboBox *angleFilterComboBox = angleFilterMenuCombo->combo(); + angleFilterComboBox->addItem("Enabled", QVariant(1)); + angleFilterComboBox->addItem("Disabled", QVariant(0)); + + eighthHarmonicMenuCombo = new MenuCombo("8th Harmonic", sequenceSection); + QComboBox *eighthHarmonicComboBox = eighthHarmonicMenuCombo->combo(); + eighthHarmonicComboBox->addItem("User-Supplied Values", QVariant(1)); + eighthHarmonicComboBox->addItem("ADI Factory Values", QVariant(0)); + + updateSequenceWidget(); + + applySequenceButton = new QPushButton("Apply", sequenceSection); + StyleHelper::BasicButton(applySequenceButton); + connect(applySequenceButton, &QPushButton::clicked, this, &HarmonicCalibration::applySequenceAndUpdate); + + sequenceSection->contentLayout()->addWidget(sequenceTypeMenuCombo); + sequenceSection->contentLayout()->addWidget(conversionTypeMenuCombo); + sequenceSection->contentLayout()->addWidget(convertSynchronizationMenuCombo); + sequenceSection->contentLayout()->addWidget(angleFilterMenuCombo); + sequenceSection->contentLayout()->addWidget(eighthHarmonicMenuCombo); + sequenceSection->contentLayout()->addWidget(applySequenceButton); + + #pragma region Device Status Widget + MenuSectionWidget *acquisitionDeviceStatusWidget = new MenuSectionWidget(generalSettingWidget); + acquisitionDeviceStatusWidget->contentLayout()->setSpacing(8); + MenuCollapseSection *acquisitionDeviceStatusSection = new MenuCollapseSection("Device Status", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, generalWidget); + acquisitionDeviceStatusSection->contentLayout()->setSpacing(8); + acquisitionDeviceStatusWidget->contentLayout()->addWidget(acquisitionDeviceStatusSection); + + acquisitionFaultRegisterLEDWidget = createStatusLEDWidget("Fault Register", json::theme::content_success, true, acquisitionDeviceStatusSection); + acquisitionDeviceStatusSection->contentLayout()->addWidget(acquisitionFaultRegisterLEDWidget); + + if(deviceType == "Automotive" && generalRegisterMap.at("Sequence Type") == 1) // Automotive & Sequence Mode 2 + { + QCheckBox *acquisitionSPICRCLEDWidget = createStatusLEDWidget("SPI CRC", json::theme::content_success, true, acquisitionDeviceStatusSection); + QCheckBox *acquisitionSPIFlagLEDWidget = createStatusLEDWidget("SPI Flag", json::theme::content_success, true, acquisitionDeviceStatusSection); + acquisitionDeviceStatusSection->contentLayout()->addWidget(acquisitionSPICRCLEDWidget); + acquisitionDeviceStatusSection->contentLayout()->addWidget(acquisitionSPIFlagLEDWidget); + } + #pragma endregion + + generalSettingLayout->addWidget(acquisitionDeviceStatusWidget); + generalSettingLayout->addWidget(header); + generalSettingLayout->addSpacerItem(new QSpacerItem(0, 3, QSizePolicy::Fixed, QSizePolicy::Fixed)); + generalSettingLayout->addWidget(sequenceWidget); + generalSettingLayout->addWidget(generalWidget); + generalSettingLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + #pragma endregion + + tool->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tool->topContainer()->setVisible(true); + tool->leftContainer()->setVisible(true); + tool->rightContainer()->setVisible(true); + tool->bottomContainer()->setVisible(false); + tool->setLeftContainerWidth(210); + tool->setRightContainerWidth(300); + tool->setTopContainerHeight(100); + tool->openBottomContainerHelper(false); + tool->openTopContainerHelper(false); + tool->addWidgetToTopContainerMenuControlHelper(openLastMenuButton, TTA_RIGHT); + tool->addWidgetToTopContainerMenuControlHelper(settingsButton, TTA_LEFT); + tool->addWidgetToTopContainerHelper(resetGMRButton, TTA_RIGHT); + tool->addWidgetToTopContainerHelper(runButton, TTA_RIGHT); + tool->leftStack()->add("rawDataScroll", rawDataScroll); + tool->rightStack()->add("generalSettingScroll", generalSettingScroll); + tool->addWidgetToCentralContainerHelper(acquisitionGraphSectionWidget); + + connect(runButton, &QPushButton::toggled, this, &HarmonicCalibration::setRunning); + connect(this, &HarmonicCalibration::runningChanged, this, &HarmonicCalibration::run); + connect(this, &HarmonicCalibration::runningChanged, runButton, &QAbstractButton::setChecked); + connectLineEditToRPSConversion(motorMaxVelocitySpinBox->lineEdit(), rotate_vmax); + connectLineEditToAMAXConversion(motorAccelTimeSpinBox->lineEdit(), amax); + connectLineEditToNumberWrite(motorMaxDisplacementSpinBox->lineEdit(), dmax, ADMTController::MotorAttribute::DMAX); + connectLineEditToNumberWrite(motorTargetPositionLineEdit, target_pos, ADMTController::MotorAttribute::TARGET_POS); + connectMenuComboToNumber(m_calibrationMotorRampModeMenuCombo, ramp_mode); + + return tool; +} + +ToolTemplate* HarmonicCalibration::createCalibrationWidget() +{ + ToolTemplate *tool = new ToolTemplate(this); + + #pragma region Calibration Data Graph Widget + QWidget *calibrationDataGraphWidget = new QWidget(); + QGridLayout *calibrationDataGraphLayout = new QGridLayout(calibrationDataGraphWidget); + calibrationDataGraphWidget->setLayout(calibrationDataGraphLayout); + calibrationDataGraphLayout->setMargin(0); + calibrationDataGraphLayout->setSpacing(5); + + MenuSectionWidget *calibrationDataGraphSectionWidget = new MenuSectionWidget(calibrationDataGraphWidget); + calibrationDataGraphTabWidget = new QTabWidget(calibrationDataGraphSectionWidget); + calibrationDataGraphTabWidget->tabBar()->setStyleSheet("QTabBar::tab { width: 176px; }"); + calibrationDataGraphSectionWidget->contentLayout()->setSpacing(8); + calibrationDataGraphSectionWidget->contentLayout()->addWidget(calibrationDataGraphTabWidget); + + #pragma region Calibration Samples + QWidget *calibrationSamplesWidget = new QWidget(calibrationDataGraphTabWidget); + QVBoxLayout *calibrationSamplesLayout = new QVBoxLayout(calibrationSamplesWidget); + calibrationSamplesWidget->setLayout(calibrationSamplesLayout); + calibrationSamplesLayout->setMargin(0); + calibrationSamplesLayout->setSpacing(0); + + calibrationRawDataPlotWidget = new PlotWidget(); + ADMTStyleHelper::PlotWidgetStyle(calibrationRawDataPlotWidget); + + calibrationRawDataXPlotAxis = new PlotAxis(QwtAxis::XBottom, calibrationRawDataPlotWidget, scopyBluePen); + calibrationRawDataXPlotAxis->setMin(0); + calibrationRawDataYPlotAxis = new PlotAxis(QwtAxis::YLeft, calibrationRawDataPlotWidget, scopyBluePen); + calibrationRawDataYPlotAxis->setInterval(0, 360); + + calibrationRawDataPlotChannel = new PlotChannel("Samples", scopyBluePen, calibrationRawDataXPlotAxis, calibrationRawDataYPlotAxis); + calibrationSineDataPlotChannel = new PlotChannel("Sine", sinePen, calibrationRawDataXPlotAxis, calibrationRawDataYPlotAxis); + calibrationCosineDataPlotChannel = new PlotChannel("Cosine", cosinePen, calibrationRawDataXPlotAxis, calibrationRawDataYPlotAxis); + + calibrationRawDataPlotWidget->addPlotChannel(calibrationRawDataPlotChannel); + calibrationRawDataPlotWidget->addPlotChannel(calibrationSineDataPlotChannel); + calibrationRawDataPlotWidget->addPlotChannel(calibrationCosineDataPlotChannel); + calibrationSineDataPlotChannel->setEnabled(true); + calibrationCosineDataPlotChannel->setEnabled(true); + calibrationRawDataPlotChannel->setEnabled(true); + calibrationRawDataPlotWidget->selectChannel(calibrationRawDataPlotChannel); + + calibrationRawDataPlotWidget->setShowXAxisLabels(true); + calibrationRawDataPlotWidget->setShowYAxisLabels(true); + calibrationRawDataPlotWidget->showAxisLabels(); + + calibrationRawDataPlotWidget->replot(); + + QWidget *calibrationDataGraphChannelsWidget = new QWidget(calibrationDataGraphTabWidget); + ADMTStyleHelper::UIBackgroundStyle(calibrationDataGraphChannelsWidget); + QHBoxLayout *calibrationDataGraphChannelsLayout = new QHBoxLayout(calibrationDataGraphChannelsWidget); + calibrationDataGraphChannelsWidget->setLayout(calibrationDataGraphChannelsLayout); + calibrationDataGraphChannelsLayout->setContentsMargins(20, 13, 20, 5); + calibrationDataGraphChannelsLayout->setSpacing(20); + + MenuControlButton *toggleAngleButton = createChannelToggleWidget("Angle", scopyBlueColor, calibrationDataGraphChannelsWidget); + MenuControlButton *toggleSineButton = createChannelToggleWidget("Sine", sineColor, calibrationDataGraphChannelsWidget); + MenuControlButton *toggleCosineButton = createChannelToggleWidget("Cosine", cosineColor, calibrationDataGraphChannelsWidget); + + calibrationDataGraphChannelsLayout->addWidget(toggleAngleButton); + calibrationDataGraphChannelsLayout->addWidget(toggleSineButton); + calibrationDataGraphChannelsLayout->addWidget(toggleCosineButton); + calibrationDataGraphChannelsLayout->addStretch(); + + calibrationSamplesLayout->addWidget(calibrationRawDataPlotWidget); + calibrationSamplesLayout->addWidget(calibrationDataGraphChannelsWidget); + #pragma endregion + + #pragma region Post Calibration Samples + QWidget *postCalibrationSamplesWidget = new QWidget(calibrationDataGraphTabWidget); + QVBoxLayout *postCalibrationSamplesLayout = new QVBoxLayout(postCalibrationSamplesWidget); + postCalibrationSamplesWidget->setLayout(postCalibrationSamplesLayout); + postCalibrationSamplesLayout->setMargin(0); + postCalibrationSamplesLayout->setSpacing(0); + + postCalibrationRawDataPlotWidget = new PlotWidget(); + ADMTStyleHelper::PlotWidgetStyle(postCalibrationRawDataPlotWidget); + postCalibrationRawDataPlotWidget->xAxis()->setVisible(false); + postCalibrationRawDataPlotWidget->yAxis()->setVisible(false); + + postCalibrationRawDataXPlotAxis = new PlotAxis(QwtAxis::XBottom, postCalibrationRawDataPlotWidget, scopyBluePen); + postCalibrationRawDataXPlotAxis->setMin(0); + postCalibrationRawDataYPlotAxis = new PlotAxis(QwtAxis::YLeft, postCalibrationRawDataPlotWidget, scopyBluePen); + postCalibrationRawDataYPlotAxis->setInterval(0, 360); + + postCalibrationRawDataPlotChannel = new PlotChannel("Samples", scopyBluePen, postCalibrationRawDataXPlotAxis, postCalibrationRawDataYPlotAxis); + postCalibrationSineDataPlotChannel = new PlotChannel("Sine", sinePen, postCalibrationRawDataXPlotAxis, postCalibrationRawDataYPlotAxis); + postCalibrationCosineDataPlotChannel = new PlotChannel("Cosine", cosinePen, postCalibrationRawDataXPlotAxis, postCalibrationRawDataYPlotAxis); + + postCalibrationRawDataPlotWidget->addPlotChannel(postCalibrationRawDataPlotChannel); + postCalibrationRawDataPlotWidget->addPlotChannel(postCalibrationSineDataPlotChannel); + postCalibrationRawDataPlotWidget->addPlotChannel(postCalibrationCosineDataPlotChannel); + + postCalibrationSineDataPlotChannel->setEnabled(true); + postCalibrationCosineDataPlotChannel->setEnabled(true); + postCalibrationRawDataPlotChannel->setEnabled(true); + postCalibrationRawDataPlotWidget->selectChannel(postCalibrationRawDataPlotChannel); + postCalibrationRawDataPlotWidget->replot(); + + postCalibrationRawDataPlotWidget->setShowXAxisLabels(true); + postCalibrationRawDataPlotWidget->setShowYAxisLabels(true); + postCalibrationRawDataPlotWidget->showAxisLabels(); + + QWidget *postCalibrationDataGraphChannelsWidget = new QWidget(calibrationDataGraphTabWidget); + QHBoxLayout *postCalibrationDataGraphChannelsLayout = new QHBoxLayout(postCalibrationDataGraphChannelsWidget); + ADMTStyleHelper::GraphChannelStyle(postCalibrationDataGraphChannelsWidget, postCalibrationDataGraphChannelsLayout); + + MenuControlButton *togglePostAngleButton = createChannelToggleWidget("Angle", scopyBlueColor, postCalibrationDataGraphChannelsWidget); + MenuControlButton *togglePostSineButton = createChannelToggleWidget("Sine", sineColor, postCalibrationDataGraphChannelsWidget); + MenuControlButton *togglePostCosineButton = createChannelToggleWidget("Cosine", cosineColor, postCalibrationDataGraphChannelsWidget); + + postCalibrationDataGraphChannelsLayout->addWidget(togglePostAngleButton); + postCalibrationDataGraphChannelsLayout->addWidget(togglePostSineButton); + postCalibrationDataGraphChannelsLayout->addWidget(togglePostCosineButton); + postCalibrationDataGraphChannelsLayout->addStretch(); + + postCalibrationSamplesLayout->addWidget(postCalibrationRawDataPlotWidget); + postCalibrationSamplesLayout->addWidget(postCalibrationDataGraphChannelsWidget); + #pragma endregion + + calibrationDataGraphTabWidget->addTab(calibrationSamplesWidget, "Calibration Samples"); + calibrationDataGraphTabWidget->addTab(postCalibrationSamplesWidget, "Post Calibration Samples"); + + MenuSectionWidget *resultDataSectionWidget = new MenuSectionWidget(calibrationDataGraphWidget); + resultDataTabWidget = new QTabWidget(resultDataSectionWidget); + resultDataTabWidget->tabBar()->setStyleSheet("QTabBar::tab { width: 160px; }"); + resultDataSectionWidget->contentLayout()->setSpacing(8); + resultDataSectionWidget->contentLayout()->addWidget(resultDataTabWidget); + + QColor magnitudeColor = Style::getColor(json::global::ch0); + QColor phaseColor = Style::getColor(json::global::ch1); + QPen magnitudePen = QPen(magnitudeColor); + QPen phasePen = QPen(phaseColor); + + #pragma region Angle Error Widget + QWidget *angleErrorWidget = new QWidget(); + QVBoxLayout *angleErrorLayout = new QVBoxLayout(angleErrorWidget); + angleErrorWidget->setLayout(angleErrorLayout); + angleErrorLayout->setMargin(0); + angleErrorLayout->setSpacing(0); + + angleErrorPlotWidget = new PlotWidget(); + ADMTStyleHelper::PlotWidgetStyle(angleErrorPlotWidget); + angleErrorPlotWidget->xAxis()->setVisible(false); + angleErrorPlotWidget->yAxis()->setVisible(false); + + angleErrorXPlotAxis = new PlotAxis(QwtAxis::XBottom, angleErrorPlotWidget, scopyBluePen); + angleErrorXPlotAxis->setMin(0); + angleErrorYPlotAxis = new PlotAxis(QwtAxis::YLeft, angleErrorPlotWidget, scopyBluePen); + angleErrorYPlotAxis->setInterval(-4, 4); + + angleErrorPlotChannel = new PlotChannel("Angle Error", scopyBluePen, angleErrorXPlotAxis, angleErrorYPlotAxis); + angleErrorPlotWidget->addPlotChannel(angleErrorPlotChannel); + + angleErrorPlotChannel->setEnabled(true); + angleErrorPlotWidget->selectChannel(angleErrorPlotChannel); + angleErrorPlotWidget->replot(); + + angleErrorPlotWidget->setShowXAxisLabels(true); + angleErrorPlotWidget->setShowYAxisLabels(true); + angleErrorPlotWidget->showAxisLabels(); + + angleErrorLayout->addWidget(angleErrorPlotWidget); + #pragma endregion + + #pragma region FFT Angle Error Widget + QWidget *FFTAngleErrorWidget = new QWidget(); + QVBoxLayout *FFTAngleErrorLayout = new QVBoxLayout(FFTAngleErrorWidget); + FFTAngleErrorWidget->setLayout(FFTAngleErrorLayout); + FFTAngleErrorLayout->setMargin(0); + FFTAngleErrorLayout->setSpacing(0); + + FFTAngleErrorPlotWidget = new PlotWidget(); + ADMTStyleHelper::PlotWidgetStyle(FFTAngleErrorPlotWidget); + FFTAngleErrorPlotWidget->xAxis()->setVisible(false); + FFTAngleErrorPlotWidget->yAxis()->setVisible(false); + + FFTAngleErrorXPlotAxis = new PlotAxis(QwtAxis::XBottom, FFTAngleErrorPlotWidget, scopyBluePen); + FFTAngleErrorXPlotAxis->setMin(0); + FFTAngleErrorYPlotAxis = new PlotAxis(QwtAxis::YLeft, FFTAngleErrorPlotWidget, scopyBluePen); + FFTAngleErrorYPlotAxis->setInterval(-4, 4); + + FFTAngleErrorMagnitudeChannel = new PlotChannel("FFT Angle Error Magnitude", magnitudePen, FFTAngleErrorXPlotAxis, FFTAngleErrorYPlotAxis); + FFTAngleErrorPhaseChannel = new PlotChannel("FFT Angle Error Phase", phasePen, FFTAngleErrorXPlotAxis, FFTAngleErrorYPlotAxis); + FFTAngleErrorPlotWidget->addPlotChannel(FFTAngleErrorMagnitudeChannel); + FFTAngleErrorPlotWidget->addPlotChannel(FFTAngleErrorPhaseChannel); + + FFTAngleErrorPhaseChannel->setEnabled(true); + FFTAngleErrorMagnitudeChannel->setEnabled(true); + FFTAngleErrorPlotWidget->selectChannel(FFTAngleErrorMagnitudeChannel); + FFTAngleErrorPlotWidget->replot(); + + FFTAngleErrorPlotWidget->setShowXAxisLabels(true); + FFTAngleErrorPlotWidget->setShowYAxisLabels(true); + FFTAngleErrorPlotWidget->showAxisLabels(); + + QWidget *FFTAngleErrorChannelsWidget = new QWidget(); + QHBoxLayout *FFTAngleErrorChannelsLayout = new QHBoxLayout(FFTAngleErrorChannelsWidget); + ADMTStyleHelper::GraphChannelStyle(FFTAngleErrorChannelsWidget, FFTAngleErrorChannelsLayout); + + MenuControlButton *toggleFFTAngleErrorMagnitudeButton = createChannelToggleWidget("Magnitude", magnitudeColor, FFTAngleErrorChannelsWidget); + MenuControlButton *toggleFFTAngleErrorPhaseButton = createChannelToggleWidget("Phase", phaseColor, FFTAngleErrorChannelsWidget); + + FFTAngleErrorChannelsLayout->addWidget(toggleFFTAngleErrorMagnitudeButton); + FFTAngleErrorChannelsLayout->addWidget(toggleFFTAngleErrorPhaseButton); + FFTAngleErrorChannelsLayout->addStretch(); + + FFTAngleErrorLayout->addWidget(FFTAngleErrorPlotWidget); + FFTAngleErrorLayout->addWidget(FFTAngleErrorChannelsWidget); + #pragma endregion + + #pragma region Corrected Error Widget + QWidget *correctedErrorWidget = new QWidget(); + QVBoxLayout *correctedErrorLayout = new QVBoxLayout(correctedErrorWidget); + correctedErrorWidget->setLayout(correctedErrorLayout); + correctedErrorLayout->setMargin(0); + correctedErrorLayout->setSpacing(0); + + correctedErrorPlotWidget = new PlotWidget(); + ADMTStyleHelper::PlotWidgetStyle(correctedErrorPlotWidget); + correctedErrorPlotWidget->xAxis()->setVisible(false); + correctedErrorPlotWidget->yAxis()->setVisible(false); + + correctedErrorXPlotAxis = new PlotAxis(QwtAxis::XBottom, correctedErrorPlotWidget, scopyBluePen); + correctedErrorXPlotAxis->setMin(0); + correctedErrorYPlotAxis = new PlotAxis(QwtAxis::YLeft, correctedErrorPlotWidget, scopyBluePen); + correctedErrorYPlotAxis->setInterval(-4, 4); + + correctedErrorPlotChannel = new PlotChannel("Corrected Error", scopyBluePen, correctedErrorXPlotAxis, correctedErrorYPlotAxis); + correctedErrorPlotWidget->addPlotChannel(correctedErrorPlotChannel); + + correctedErrorPlotChannel->setEnabled(true); + correctedErrorPlotWidget->selectChannel(correctedErrorPlotChannel); + correctedErrorPlotWidget->replot(); + + correctedErrorPlotWidget->setShowXAxisLabels(true); + correctedErrorPlotWidget->setShowYAxisLabels(true); + correctedErrorPlotWidget->showAxisLabels(); + + correctedErrorLayout->addWidget(correctedErrorPlotWidget); + #pragma endregion + + #pragma region FFT Corrected Error Widget + QWidget *FFTCorrectedErrorWidget = new QWidget(); + QVBoxLayout *FFTCorrectedErrorLayout = new QVBoxLayout(FFTCorrectedErrorWidget); + FFTCorrectedErrorWidget->setLayout(FFTCorrectedErrorLayout); + FFTCorrectedErrorLayout->setMargin(0); + FFTCorrectedErrorLayout->setSpacing(0); + + FFTCorrectedErrorPlotWidget = new PlotWidget(); + ADMTStyleHelper::PlotWidgetStyle(FFTCorrectedErrorPlotWidget); + FFTCorrectedErrorPlotWidget->xAxis()->setVisible(false); + FFTCorrectedErrorPlotWidget->yAxis()->setVisible(false); + + FFTCorrectedErrorXPlotAxis = new PlotAxis(QwtAxis::XBottom, FFTCorrectedErrorPlotWidget, scopyBluePen); + FFTCorrectedErrorXPlotAxis->setMin(0); + FFTCorrectedErrorYPlotAxis = new PlotAxis(QwtAxis::YLeft, FFTCorrectedErrorPlotWidget, scopyBluePen); + FFTCorrectedErrorYPlotAxis->setInterval(-4, 4); + + FFTCorrectedErrorMagnitudeChannel = new PlotChannel("FFT Corrected Error Magnitude", magnitudePen, FFTCorrectedErrorXPlotAxis, FFTCorrectedErrorYPlotAxis); + FFTCorrectedErrorPhaseChannel = new PlotChannel("FFT Corrected Error Phase", phasePen, FFTCorrectedErrorXPlotAxis, FFTCorrectedErrorYPlotAxis); + FFTCorrectedErrorPlotWidget->addPlotChannel(FFTCorrectedErrorMagnitudeChannel); + FFTCorrectedErrorPlotWidget->addPlotChannel(FFTCorrectedErrorPhaseChannel); + + FFTCorrectedErrorPhaseChannel->setEnabled(true); + FFTCorrectedErrorMagnitudeChannel->setEnabled(true); + FFTCorrectedErrorPlotWidget->selectChannel(FFTCorrectedErrorMagnitudeChannel); + FFTCorrectedErrorPlotWidget->replot(); + + FFTCorrectedErrorPlotWidget->setShowXAxisLabels(true); + FFTCorrectedErrorPlotWidget->setShowYAxisLabels(true); + FFTCorrectedErrorPlotWidget->showAxisLabels(); + + QWidget *FFTCorrectedErrorChannelsWidget = new QWidget(); + QHBoxLayout *FFTCorrectedErrorChannelsLayout = new QHBoxLayout(FFTCorrectedErrorChannelsWidget); + ADMTStyleHelper::GraphChannelStyle(FFTCorrectedErrorChannelsWidget, FFTCorrectedErrorChannelsLayout); + + MenuControlButton *toggleFFTCorrectedErrorMagnitudeButton = createChannelToggleWidget("Magnitude", magnitudeColor, FFTCorrectedErrorChannelsWidget); + MenuControlButton *toggleFFTCorrectedErrorPhaseButton = createChannelToggleWidget("Phase", phaseColor, FFTCorrectedErrorChannelsWidget); + + FFTCorrectedErrorChannelsLayout->addWidget(toggleFFTCorrectedErrorMagnitudeButton); + FFTCorrectedErrorChannelsLayout->addWidget(toggleFFTCorrectedErrorPhaseButton); + FFTCorrectedErrorChannelsLayout->addStretch(); + + FFTCorrectedErrorLayout->addWidget(FFTCorrectedErrorPlotWidget); + FFTCorrectedErrorLayout->addWidget(FFTCorrectedErrorChannelsWidget); + #pragma endregion + + resultDataTabWidget->addTab(angleErrorWidget, "Angle Error"); + resultDataTabWidget->addTab(FFTAngleErrorWidget, "FFT Angle Error"); + resultDataTabWidget->addTab(correctedErrorWidget, "Corrected Error"); + resultDataTabWidget->addTab(FFTCorrectedErrorWidget, "FFT Corrected Error"); + + calibrationDataGraphLayout->addWidget(calibrationDataGraphSectionWidget, 0, 0); + calibrationDataGraphLayout->addWidget(resultDataSectionWidget, 1, 0); + + calibrationDataGraphLayout->setColumnStretch(0, 1); + calibrationDataGraphLayout->setRowStretch(0, 1); + calibrationDataGraphLayout->setRowStretch(1, 1); + #pragma endregion + + #pragma region Calibration Settings Widget + QWidget *calibrationSettingsGroupWidget = new QWidget(); + QVBoxLayout *calibrationSettingsGroupLayout = new QVBoxLayout(calibrationSettingsGroupWidget); + calibrationSettingsGroupWidget->setLayout(calibrationSettingsGroupLayout); + calibrationSettingsGroupLayout->setMargin(0); + calibrationSettingsGroupLayout->setSpacing(8); + + #pragma region Device Status Widget + MenuSectionWidget *calibrationDeviceStatusWidget = new MenuSectionWidget(calibrationSettingsGroupWidget); + calibrationDeviceStatusWidget->contentLayout()->setSpacing(8); + MenuCollapseSection *calibrationDeviceStatusSection = new MenuCollapseSection("Device Status", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, calibrationSettingsGroupWidget); + calibrationDeviceStatusSection->contentLayout()->setSpacing(8); + calibrationDeviceStatusWidget->contentLayout()->addWidget(calibrationDeviceStatusSection); + + calibrationFaultRegisterLEDWidget = createStatusLEDWidget("Fault Register", json::theme::content_success, true, calibrationDeviceStatusSection); + calibrationDeviceStatusSection->contentLayout()->addWidget(calibrationFaultRegisterLEDWidget); + + if(deviceType == "Automotive" && generalRegisterMap.at("Sequence Type") == 1) // Automotive & Sequence Mode 2 + { + QCheckBox *calibrationSPICRCLEDWidget = createStatusLEDWidget("SPI CRC", json::theme::content_success, true, calibrationDeviceStatusSection); + QCheckBox *calibrationSPIFlagLEDWidget = createStatusLEDWidget("SPI Flag", json::theme::content_success, true, calibrationDeviceStatusSection); + calibrationDeviceStatusSection->contentLayout()->addWidget(calibrationSPICRCLEDWidget); + calibrationDeviceStatusSection->contentLayout()->addWidget(calibrationSPIFlagLEDWidget); + } + #pragma endregion + + #pragma region Acquire Calibration Samples Button + calibrationStartMotorButton = new QPushButton(" Acquire Samples", calibrationSettingsGroupWidget); + ADMTStyleHelper::StartButtonStyle(calibrationStartMotorButton); + + connect(calibrationStartMotorButton, &QPushButton::toggled, this, [=](bool toggled) { + calibrationStartMotorButton->setText(toggled ? " Stop Acquisition" : " Acquire Samples"); + isStartMotor = toggled; + if(toggled){ + isPostCalibration = false; + startCalibration(); + } + else{ + stopCalibration(); + } + }); + #pragma endregion + + #pragma region Start Calibration Button + calibrateDataButton = new QPushButton(calibrationSettingsGroupWidget); + ADMTStyleHelper::StartButtonStyle(calibrateDataButton); + calibrateDataButton->setText(" Calibrate"); + calibrateDataButton->setEnabled(false); + + connect(calibrateDataButton, &QPushButton::toggled, this, [=](bool toggled) { + calibrateDataButton->setText(toggled ? " Stop Calibration" : " Calibrate"); + if(toggled) postCalibrateData(); + else stopCalibration(); + }); + #pragma endregion + + #pragma region Reset Calibration Button + clearCalibrateDataButton = new QPushButton("Reset Calibration", calibrationSettingsGroupWidget); + StyleHelper::BasicButton(clearCalibrateDataButton); + QIcon resetIcon; + resetIcon.addPixmap(Util::ChangeSVGColor(":/gui/icons/refresh.svg", "white", 1), QIcon::Normal, QIcon::Off); + clearCalibrateDataButton->setIcon(resetIcon); + clearCalibrateDataButton->setIconSize(QSize(26, 26)); + #pragma endregion + + QScrollArea *calibrationSettingsScrollArea = new QScrollArea(); + QWidget *calibrationSettingsWidget = new QWidget(calibrationSettingsScrollArea); + QVBoxLayout *calibrationSettingsLayout = new QVBoxLayout(calibrationSettingsWidget); + calibrationSettingsScrollArea->setWidgetResizable(true); + calibrationSettingsScrollArea->setWidget(calibrationSettingsWidget); + calibrationSettingsWidget->setFixedWidth(260); + calibrationSettingsWidget->setLayout(calibrationSettingsLayout); + + calibrationSettingsGroupLayout->addWidget(calibrationDeviceStatusWidget); + calibrationSettingsGroupLayout->addWidget(calibrationStartMotorButton); + calibrationSettingsGroupLayout->addWidget(calibrateDataButton); + calibrationSettingsGroupLayout->addWidget(clearCalibrateDataButton); + calibrationSettingsGroupLayout->addWidget(calibrationSettingsScrollArea); + + #pragma region Calibration Coefficient Section Widget + MenuSectionWidget *calibrationCoeffSectionWidget = new MenuSectionWidget(calibrationSettingsWidget); + QLabel *calibrationDisplayFormatLabel = new QLabel(calibrationCoeffSectionWidget); + calibrationDisplayFormatLabel->setText("Display Format"); + Style::setStyle(calibrationDisplayFormatLabel, style::properties::label::menuMedium); + QLabel *calibrationCalculatedCoeffLabel = new QLabel(calibrationCoeffSectionWidget); + calibrationCalculatedCoeffLabel->setText("Calculated Coefficients"); + Style::setStyle(calibrationCalculatedCoeffLabel, style::properties::label::menuMedium); + + calibrationDisplayFormatSwitch = new CustomSwitch(); + calibrationDisplayFormatSwitch->setOffText("Hex"); + calibrationDisplayFormatSwitch->setOnText("Angle"); + calibrationDisplayFormatSwitch->setProperty("bigBtn", true); + + QWidget *calibrationCalculatedCoeffWidget = new QWidget(calibrationCoeffSectionWidget); + QGridLayout *calibrationCalculatedCoeffLayout = new QGridLayout(calibrationCalculatedCoeffWidget); + + calibrationCalculatedCoeffWidget->setLayout(calibrationCalculatedCoeffLayout); + calibrationCalculatedCoeffLayout->setMargin(0); + calibrationCalculatedCoeffLayout->setVerticalSpacing(4); + ADMTStyleHelper::UIBackgroundStyle(calibrationCalculatedCoeffWidget); + + QWidget *h1RowContainer = new QWidget(calibrationCalculatedCoeffWidget); + QHBoxLayout *h1RowLayout = new QHBoxLayout(h1RowContainer); + QLabel *calibrationH1Label = new QLabel("H1", calibrationCalculatedCoeffWidget); + calibrationH1MagLabel = new QLabel("0x----", calibrationCalculatedCoeffWidget); + calibrationH1PhaseLabel = new QLabel("0x----", calibrationCalculatedCoeffWidget); + ADMTStyleHelper::CalculatedCoeffWidgetRowStyle(h1RowContainer, h1RowLayout, calibrationH1Label, calibrationH1MagLabel, calibrationH1PhaseLabel); + + QWidget *h2RowContainer = new QWidget(calibrationCalculatedCoeffWidget); + QHBoxLayout *h2RowLayout = new QHBoxLayout(h2RowContainer); + QLabel *calibrationH2Label = new QLabel("H2", calibrationCalculatedCoeffWidget); + calibrationH2MagLabel = new QLabel("0x----", calibrationCalculatedCoeffWidget); + calibrationH2PhaseLabel = new QLabel("0x----", calibrationCalculatedCoeffWidget); + ADMTStyleHelper::CalculatedCoeffWidgetRowStyle(h2RowContainer, h2RowLayout, calibrationH2Label, calibrationH2MagLabel, calibrationH2PhaseLabel); + + QWidget *h3RowContainer = new QWidget(calibrationCalculatedCoeffWidget); + QHBoxLayout *h3RowLayout = new QHBoxLayout(h3RowContainer); + QLabel *calibrationH3Label = new QLabel("H3", calibrationCalculatedCoeffWidget); + calibrationH3MagLabel = new QLabel("0x----", calibrationCalculatedCoeffWidget); + calibrationH3PhaseLabel = new QLabel("0x----", calibrationCalculatedCoeffWidget); + ADMTStyleHelper::CalculatedCoeffWidgetRowStyle(h3RowContainer, h3RowLayout, calibrationH3Label, calibrationH3MagLabel, calibrationH3PhaseLabel); + + QWidget *h8RowContainer = new QWidget(calibrationCalculatedCoeffWidget); + QHBoxLayout *h8RowLayout = new QHBoxLayout(h8RowContainer); + QLabel *calibrationH8Label = new QLabel("H8", calibrationCalculatedCoeffWidget); + calibrationH8MagLabel = new QLabel("0x----", calibrationCalculatedCoeffWidget); + calibrationH8PhaseLabel = new QLabel("0x----", calibrationCalculatedCoeffWidget); + ADMTStyleHelper::CalculatedCoeffWidgetRowStyle(h8RowContainer, h8RowLayout, calibrationH8Label, calibrationH8MagLabel, calibrationH8PhaseLabel); + + calibrationCalculatedCoeffLayout->addWidget(h1RowContainer, 0, 0); + calibrationCalculatedCoeffLayout->addWidget(h2RowContainer, 1, 0); + calibrationCalculatedCoeffLayout->addWidget(h3RowContainer, 2, 0); + calibrationCalculatedCoeffLayout->addWidget(h8RowContainer, 3, 0); + + calibrationCoeffSectionWidget->contentLayout()->setSpacing(8); + calibrationCoeffSectionWidget->contentLayout()->addWidget(calibrationDisplayFormatLabel); + calibrationCoeffSectionWidget->contentLayout()->addWidget(calibrationDisplayFormatSwitch); + calibrationCoeffSectionWidget->contentLayout()->addWidget(calibrationCalculatedCoeffLabel); + calibrationCoeffSectionWidget->contentLayout()->addWidget(calibrationCalculatedCoeffWidget); + #pragma endregion + + #pragma region Calibration Dataset Configuration + MenuSectionWidget *calibrationDatasetConfigSectionWidget = new MenuSectionWidget(calibrationSettingsWidget); + MenuCollapseSection *calibrationDatasetConfigCollapseSection = new MenuCollapseSection("Dataset Configuration", MenuCollapseSection::MenuHeaderCollapseStyle::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, calibrationDatasetConfigSectionWidget); + calibrationDatasetConfigSectionWidget->contentLayout()->setSpacing(8); + calibrationDatasetConfigSectionWidget->contentLayout()->addWidget(calibrationDatasetConfigCollapseSection); + + QLabel *calibrationCycleCountLabel = new QLabel("Cycle Count", calibrationDatasetConfigCollapseSection); + QLineEdit *calibrationCycleCountLineEdit = new QLineEdit(calibrationDatasetConfigCollapseSection); + calibrationCycleCountLineEdit->setText(QString::number(cycleCount)); + connectLineEditToNumber(calibrationCycleCountLineEdit, cycleCount, 1, 1000); + + QLabel *calibrationSamplesPerCycleLabel = new QLabel("Samples Per Cycle", calibrationDatasetConfigCollapseSection); + QLineEdit *calibrationSamplesPerCycleLineEdit = new QLineEdit(calibrationDatasetConfigCollapseSection); + calibrationSamplesPerCycleLineEdit->setText(QString::number(samplesPerCycle)); + calibrationSamplesPerCycleLineEdit->setReadOnly(true); + connectLineEditToNumber(calibrationSamplesPerCycleLineEdit, samplesPerCycle, 1, 5000); + + calibrationDatasetConfigCollapseSection->contentLayout()->setSpacing(8); + calibrationDatasetConfigCollapseSection->contentLayout()->addWidget(calibrationCycleCountLabel); + calibrationDatasetConfigCollapseSection->contentLayout()->addWidget(calibrationCycleCountLineEdit); + calibrationDatasetConfigCollapseSection->contentLayout()->addWidget(calibrationSamplesPerCycleLabel); + calibrationDatasetConfigCollapseSection->contentLayout()->addWidget(calibrationSamplesPerCycleLineEdit); + + #pragma endregion + + #pragma region Calibration Data Section Widget + MenuSectionWidget *calibrationDataSectionWidget = new MenuSectionWidget(calibrationSettingsWidget); + MenuCollapseSection *calibrationDataCollapseSection = new MenuCollapseSection("Calibration Data", MenuCollapseSection::MenuHeaderCollapseStyle::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, calibrationDataSectionWidget); + calibrationDataSectionWidget->contentLayout()->setSpacing(8); + calibrationDataSectionWidget->contentLayout()->addWidget(calibrationDataCollapseSection); + + QPushButton *importSamplesButton = new QPushButton("Import Samples", calibrationDataCollapseSection); + QPushButton *extractDataButton = new QPushButton("Save to CSV", calibrationDataCollapseSection); + StyleHelper::BasicButton(importSamplesButton); + StyleHelper::BasicButton(extractDataButton); + + calibrationDataCollapseSection->contentLayout()->setSpacing(8); + calibrationDataCollapseSection->contentLayout()->addWidget(importSamplesButton); + calibrationDataCollapseSection->contentLayout()->addWidget(extractDataButton); + #pragma endregion + + #pragma region Motor Configuration Section Widget + MenuSectionWidget *motorConfigurationSectionWidget = new MenuSectionWidget(calibrationSettingsWidget); + MenuCollapseSection *motorConfigurationCollapseSection = new MenuCollapseSection("Motor Configuration", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, motorConfigurationSectionWidget); + motorConfigurationCollapseSection->header()->toggle(); + motorConfigurationSectionWidget->contentLayout()->addWidget(motorConfigurationCollapseSection); + + motorMaxVelocitySpinBox = new HorizontalSpinBox("Max Velocity", convertVMAXtoRPS(rotate_vmax), "rps", motorConfigurationSectionWidget); + motorMaxVelocitySpinBox->setValue(1); + motorAccelTimeSpinBox = new HorizontalSpinBox("Acceleration Time", convertAMAXtoAccelTime(amax), "sec", motorConfigurationSectionWidget); + motorAccelTimeSpinBox->setValue(1); + motorMaxDisplacementSpinBox = new HorizontalSpinBox("Max Displacement", dmax, "", motorConfigurationSectionWidget); + + m_calibrationMotorRampModeMenuCombo = new MenuCombo("Ramp Mode", motorConfigurationSectionWidget); + auto calibrationMotorRampModeCombo = m_calibrationMotorRampModeMenuCombo->combo(); + calibrationMotorRampModeCombo->addItem("Position", QVariant(ADMTController::MotorRampMode::POSITION)); + calibrationMotorRampModeCombo->addItem("Ramp Mode 1", QVariant(ADMTController::MotorRampMode::RAMP_MODE_1)); + ADMTStyleHelper::ComboBoxStyle(calibrationMotorRampModeCombo); + + motorConfigurationCollapseSection->contentLayout()->setSpacing(8); + motorConfigurationCollapseSection->contentLayout()->addWidget(motorMaxVelocitySpinBox); + motorConfigurationCollapseSection->contentLayout()->addWidget(motorAccelTimeSpinBox); + motorConfigurationCollapseSection->contentLayout()->addWidget(motorMaxDisplacementSpinBox); + motorConfigurationCollapseSection->contentLayout()->addWidget(m_calibrationMotorRampModeMenuCombo); + #pragma endregion + + #pragma region Motor Control Section Widget + MenuSectionWidget *motorControlSectionWidget = new MenuSectionWidget(calibrationSettingsWidget); + MenuCollapseSection *motorControlCollapseSection = new MenuCollapseSection("Motor Control", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, motorControlSectionWidget); + motorControlSectionWidget->contentLayout()->setSpacing(8); + motorControlSectionWidget->contentLayout()->addWidget(motorControlCollapseSection); + QLabel *currentPositionLabel = new QLabel("Current Position", motorControlSectionWidget); + calibrationMotorCurrentPositionLineEdit = new QLineEdit("--.--", motorControlSectionWidget); + calibrationMotorCurrentPositionLineEdit->setReadOnly(true); + connectLineEditToDouble(calibrationMotorCurrentPositionLineEdit, current_pos); + + QLabel *targetPositionLabel = new QLabel("Target Position", motorControlSectionWidget); + motorTargetPositionLineEdit = new QLineEdit(QString::number(target_pos), motorControlSectionWidget); + + motorControlCollapseSection->contentLayout()->setSpacing(8); + motorControlCollapseSection->contentLayout()->addWidget(currentPositionLabel); + motorControlCollapseSection->contentLayout()->addWidget(calibrationMotorCurrentPositionLineEdit); + motorControlCollapseSection->contentLayout()->addWidget(targetPositionLabel); + motorControlCollapseSection->contentLayout()->addWidget(motorTargetPositionLineEdit); + #pragma endregion + + #pragma region Logs Section Widget + MenuSectionWidget *logsSectionWidget = new MenuSectionWidget(calibrationSettingsWidget); + MenuCollapseSection *logsCollapseSection = new MenuCollapseSection("Logs", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, logsSectionWidget); + logsCollapseSection->header()->toggle(); + logsSectionWidget->contentLayout()->setSpacing(8); + logsSectionWidget->contentLayout()->addWidget(logsCollapseSection); + + logsPlainTextEdit = new QPlainTextEdit(logsSectionWidget); + logsPlainTextEdit->setReadOnly(true); + logsPlainTextEdit->setFixedHeight(320); + logsPlainTextEdit->setStyleSheet("QPlainTextEdit { border: none; }"); + + logsCollapseSection->contentLayout()->setSpacing(8); + logsCollapseSection->contentLayout()->addWidget(logsPlainTextEdit); + #pragma endregion + + calibrationSettingsLayout->setMargin(0); + calibrationSettingsLayout->addWidget(calibrationDatasetConfigSectionWidget); + calibrationSettingsLayout->addWidget(calibrationCoeffSectionWidget); + calibrationSettingsLayout->addWidget(motorControlSectionWidget); + calibrationSettingsLayout->addWidget(motorConfigurationSectionWidget); + calibrationSettingsLayout->addWidget(calibrationDataSectionWidget); + calibrationSettingsLayout->addWidget(logsSectionWidget); + calibrationSettingsLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + #pragma endregion + + tool->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tool->topContainer()->setVisible(false); + tool->topContainerMenuControl()->setVisible(false); + tool->leftContainer()->setVisible(false); + tool->rightContainer()->setVisible(true); + tool->bottomContainer()->setVisible(false); + tool->setLeftContainerWidth(270); + tool->setRightContainerWidth(270); + tool->openBottomContainerHelper(false); + tool->openTopContainerHelper(false); + + tool->addWidgetToCentralContainerHelper(calibrationDataGraphWidget); + tool->rightStack()->add("calibrationSettingsGroupWidget", calibrationSettingsGroupWidget); + + connect(extractDataButton, &QPushButton::clicked, this, &HarmonicCalibration::extractCalibrationData); + connect(importSamplesButton, &QPushButton::clicked, this, &HarmonicCalibration::importCalibrationData); + connect(clearCalibrateDataButton, &QPushButton::clicked, this, &HarmonicCalibration::resetAllCalibrationState); + connectLineEditToRPSConversion(motorMaxVelocitySpinBox->lineEdit(), rotate_vmax); + connectLineEditToAMAXConversion(motorAccelTimeSpinBox->lineEdit(), amax); + connectLineEditToNumberWrite(motorMaxDisplacementSpinBox->lineEdit(), dmax, ADMTController::MotorAttribute::DMAX); + connectLineEditToNumberWrite(motorTargetPositionLineEdit, target_pos, ADMTController::MotorAttribute::TARGET_POS); + connectMenuComboToNumber(m_calibrationMotorRampModeMenuCombo, ramp_mode); + connect(toggleAngleButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + calibrationRawDataPlotWidget->selectChannel(calibrationRawDataPlotChannel); + calibrationRawDataPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(toggleSineButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + calibrationRawDataPlotWidget->selectChannel(calibrationSineDataPlotChannel); + calibrationRawDataPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(toggleCosineButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + calibrationRawDataPlotWidget->selectChannel(calibrationCosineDataPlotChannel); + calibrationRawDataPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(togglePostAngleButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + postCalibrationRawDataPlotWidget->selectChannel(postCalibrationRawDataPlotChannel); + postCalibrationRawDataPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(togglePostSineButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + postCalibrationRawDataPlotWidget->selectChannel(postCalibrationSineDataPlotChannel); + postCalibrationRawDataPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(togglePostCosineButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + postCalibrationRawDataPlotWidget->selectChannel(postCalibrationCosineDataPlotChannel); + postCalibrationRawDataPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(toggleFFTAngleErrorMagnitudeButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + FFTAngleErrorPlotWidget->selectChannel(FFTAngleErrorMagnitudeChannel); + FFTAngleErrorPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(toggleFFTAngleErrorPhaseButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + FFTAngleErrorPlotWidget->selectChannel(FFTAngleErrorPhaseChannel); + FFTAngleErrorPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(toggleFFTCorrectedErrorMagnitudeButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + FFTCorrectedErrorPlotWidget->selectChannel(FFTCorrectedErrorMagnitudeChannel); + FFTCorrectedErrorPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(toggleFFTCorrectedErrorPhaseButton->checkBox(), &QCheckBox::toggled, this, [=](bool b){ + FFTCorrectedErrorPlotWidget->selectChannel(FFTCorrectedErrorPhaseChannel); + FFTCorrectedErrorPlotWidget->selectedChannel()->setEnabled(b); + }); + connect(calibrationDisplayFormatSwitch, &CustomSwitch::toggled, this, [=](bool b){ + isAngleDisplayFormat = b; + displayCalculatedCoeff(); + }); + + return tool; +} + +ToolTemplate* HarmonicCalibration::createRegistersWidget() +{ + ToolTemplate *tool = new ToolTemplate(this); + + QScrollArea *registerScrollArea = new QScrollArea(); + QWidget *registerWidget = new QWidget(registerScrollArea); + QVBoxLayout *registerLayout = new QVBoxLayout(registerWidget); + registerScrollArea->setWidgetResizable(true); + registerScrollArea->setWidget(registerWidget); + registerWidget->setLayout(registerLayout); + registerLayout->setMargin(0); + registerLayout->setSpacing(8); + + QLabel *registerConfigurationLabel = new QLabel("Configuration", registerWidget); + Style::setStyle(registerConfigurationLabel, style::properties::label::menuBig); + QWidget *registerConfigurationGridWidget = new QWidget(registerWidget); + QGridLayout *registerConfigurationGridLayout = new QGridLayout(registerConfigurationGridWidget); + registerConfigurationGridWidget->setLayout(registerConfigurationGridLayout); + registerConfigurationGridLayout->setMargin(0); + registerConfigurationGridLayout->setSpacing(8); + + QLabel *registerDeviceIDLabel = new QLabel("Device ID", registerWidget); + Style::setStyle(registerDeviceIDLabel, style::properties::label::menuBig); + QWidget *registerDeviceIDGridWidget = new QWidget(registerWidget); + QGridLayout *registerDeviceIDGridLayout = new QGridLayout(registerDeviceIDGridWidget); + registerDeviceIDGridWidget->setLayout(registerDeviceIDGridLayout); + registerDeviceIDGridLayout->setMargin(0); + registerDeviceIDGridLayout->setSpacing(8); + + QLabel *registerHarmonicsLabel = new QLabel("Harmonics", registerWidget); + Style::setStyle(registerHarmonicsLabel, style::properties::label::menuBig); + QWidget *registerHarmonicsGridWidget = new QWidget(registerWidget); + QGridLayout *registerHarmonicsGridLayout = new QGridLayout(registerHarmonicsGridWidget); + registerHarmonicsGridWidget->setLayout(registerHarmonicsGridLayout); + registerHarmonicsGridLayout->setMargin(0); + registerHarmonicsGridLayout->setSpacing(8); + + QLabel *registerSensorDataLabel = new QLabel("Sensor Data", registerWidget); + Style::setStyle(registerSensorDataLabel, style::properties::label::menuBig); + QWidget *registerSensorDataGridWidget = new QWidget(registerWidget); + QGridLayout *registerSensorDataGridLayout = new QGridLayout(registerSensorDataGridWidget); + registerSensorDataGridWidget->setLayout(registerSensorDataGridLayout); + registerSensorDataGridLayout->setMargin(0); + registerSensorDataGridLayout->setSpacing(8); + + cnvPageRegisterBlock = new RegisterBlockWidget("CNVPAGE", "Convert Start and Page Select", m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::CNVPAGE), m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::CNVPAGE), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + digIORegisterBlock = new RegisterBlockWidget("DIGIO", "Digital Input Output", m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::DIGIO), m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::DIGIO), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + faultRegisterBlock = new RegisterBlockWidget("FAULT", "Fault Register", m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::FAULT), m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::FAULT), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + generalRegisterBlock = new RegisterBlockWidget("GENERAL", "General Device Configuration", m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::GENERAL), m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::GENERAL), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + digIOEnRegisterBlock = new RegisterBlockWidget("DIGIOEN", "Enable Digital Input/Outputs", m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::DIGIOEN), m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::DIGIOEN), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + angleCkRegisterBlock = new RegisterBlockWidget("ANGLECK", "Primary vs Secondary Angle Check", m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::ANGLECK), m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::ANGLECK), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + eccDcdeRegisterBlock = new RegisterBlockWidget("ECCDCDE", "Error Correction Codes", m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::ECCDCDE), m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::ECCDCDE), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + eccDisRegisterBlock = new RegisterBlockWidget("ECCDIS", "Error Correction Code disable", m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::ECCDIS), m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::ECCDIS), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + + absAngleRegisterBlock = new RegisterBlockWidget("ABSANGLE", "Absolute Angle", m_admtController->getSensorRegister(ADMTController::SensorRegister::ABSANGLE), m_admtController->getSensorPage(ADMTController::SensorRegister::ABSANGLE), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + angleRegisterBlock = new RegisterBlockWidget("ANGLE", "Angle Register", m_admtController->getSensorRegister(ADMTController::SensorRegister::ANGLEREG), m_admtController->getSensorPage(ADMTController::SensorRegister::ANGLEREG), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + angleSecRegisterBlock = new RegisterBlockWidget("ANGLESEC", "Secondary Angle", m_admtController->getSensorRegister(ADMTController::SensorRegister::ANGLESEC), m_admtController->getSensorPage(ADMTController::SensorRegister::ANGLESEC), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + sineRegisterBlock = new RegisterBlockWidget("SINE", "Sine Measurement", m_admtController->getSensorRegister(ADMTController::SensorRegister::SINE), m_admtController->getSensorPage(ADMTController::SensorRegister::SINE), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + cosineRegisterBlock = new RegisterBlockWidget("COSINE", "Cosine Measurement", m_admtController->getSensorRegister(ADMTController::SensorRegister::COSINE), m_admtController->getSensorPage(ADMTController::SensorRegister::COSINE), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + secAnglIRegisterBlock = new RegisterBlockWidget("SECANGLI", "In-phase secondary angle measurement", m_admtController->getSensorRegister(ADMTController::SensorRegister::SECANGLI), m_admtController->getSensorPage(ADMTController::SensorRegister::SECANGLI), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + secAnglQRegisterBlock = new RegisterBlockWidget("SECANGLQ", "Quadrature phase secondary angle measurement", m_admtController->getSensorRegister(ADMTController::SensorRegister::SECANGLQ), m_admtController->getSensorPage(ADMTController::SensorRegister::SECANGLQ), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + radiusRegisterBlock = new RegisterBlockWidget("RADIUS", "Angle measurement radius", m_admtController->getSensorRegister(ADMTController::SensorRegister::RADIUS), m_admtController->getSensorPage(ADMTController::SensorRegister::RADIUS), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + diag1RegisterBlock = new RegisterBlockWidget("DIAG1", "State of the MT reference resistors and AFE fixed input voltage", m_admtController->getSensorRegister(ADMTController::SensorRegister::DIAG1), m_admtController->getSensorPage(ADMTController::SensorRegister::DIAG1), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + diag2RegisterBlock = new RegisterBlockWidget("DIAG2", "Measurements of two diagnostics resistors of fixed value", m_admtController->getSensorRegister(ADMTController::SensorRegister::DIAG2), m_admtController->getSensorPage(ADMTController::SensorRegister::DIAG2), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + tmp0RegisterBlock = new RegisterBlockWidget("TMP0", "Primary Temperature Sensor", m_admtController->getSensorRegister(ADMTController::SensorRegister::TMP0), m_admtController->getSensorPage(ADMTController::SensorRegister::TMP0), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + tmp1RegisterBlock = new RegisterBlockWidget("TMP1", "Secondary Temperature Sensor", m_admtController->getSensorRegister(ADMTController::SensorRegister::TMP1), m_admtController->getSensorPage(ADMTController::SensorRegister::TMP1), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + cnvCntRegisterBlock = new RegisterBlockWidget("CNVCNT", "Conversion Count", m_admtController->getSensorRegister(ADMTController::SensorRegister::CNVCNT), m_admtController->getSensorPage(ADMTController::SensorRegister::CNVCNT), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + + uniqID0RegisterBlock = new RegisterBlockWidget("UNIQID0", "Unique ID Register 0", m_admtController->getUniqueIdRegister(ADMTController::UniqueIDRegister::UNIQID0), m_admtController->getUniqueIdPage(ADMTController::UniqueIDRegister::UNIQID0), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + uniqID1RegisterBlock = new RegisterBlockWidget("UNIQID1", "Unique ID Register 1", m_admtController->getUniqueIdRegister(ADMTController::UniqueIDRegister::UNIQID1), m_admtController->getUniqueIdPage(ADMTController::UniqueIDRegister::UNIQID1), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + uniqID2RegisterBlock = new RegisterBlockWidget("UNIQID2", "Unique ID Register 2", m_admtController->getUniqueIdRegister(ADMTController::UniqueIDRegister::UNIQID2), m_admtController->getUniqueIdPage(ADMTController::UniqueIDRegister::UNIQID2), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + uniqID3RegisterBlock = new RegisterBlockWidget("UNIQID3", "Product, voltage supply. ASIL and ASIC revision identifiers", m_admtController->getUniqueIdRegister(ADMTController::UniqueIDRegister::UNIQID3), m_admtController->getUniqueIdPage(ADMTController::UniqueIDRegister::UNIQID3), RegisterBlockWidget::ACCESS_PERMISSION::READ, registerWidget); + + h1MagRegisterBlock = new RegisterBlockWidget("H1MAG", "1st Harmonic error magnitude", m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H1MAG), m_admtController->getHarmonicPage(ADMTController::HarmonicRegister::H1MAG), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + h1PhRegisterBlock = new RegisterBlockWidget("H1PH", "1st Harmonic error phase", m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H1PH), m_admtController->getHarmonicPage(ADMTController::HarmonicRegister::H1PH), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + h2MagRegisterBlock = new RegisterBlockWidget("H2MAG", "2nd Harmonic error magnitude", m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H2MAG), m_admtController->getHarmonicPage(ADMTController::HarmonicRegister::H2MAG), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + h2PhRegisterBlock = new RegisterBlockWidget("H2PH", "2nd Harmonic error phase", m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H2PH), m_admtController->getHarmonicPage(ADMTController::HarmonicRegister::H2PH), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + h3MagRegisterBlock = new RegisterBlockWidget("H3MAG", "3rd Harmonic error magnitude", m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H3MAG), m_admtController->getHarmonicPage(ADMTController::HarmonicRegister::H3MAG), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + h3PhRegisterBlock = new RegisterBlockWidget("H3PH", "3rd Harmonic error phase", m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H3PH), m_admtController->getHarmonicPage(ADMTController::HarmonicRegister::H3PH), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + h8MagRegisterBlock = new RegisterBlockWidget("H8MAG", "8th Harmonic error magnitude", m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H8MAG), m_admtController->getHarmonicPage(ADMTController::HarmonicRegister::H8MAG), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + h8PhRegisterBlock = new RegisterBlockWidget("H8PH", "8th Harmonic error phase", m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H8PH), m_admtController->getHarmonicPage(ADMTController::HarmonicRegister::H8PH), RegisterBlockWidget::ACCESS_PERMISSION::READWRITE, registerWidget); + + registerConfigurationGridLayout->addWidget(cnvPageRegisterBlock, 0, 0); + registerConfigurationGridLayout->addWidget(digIORegisterBlock, 0, 1); + registerConfigurationGridLayout->addWidget(faultRegisterBlock, 0, 2); + registerConfigurationGridLayout->addWidget(generalRegisterBlock, 0, 3); + registerConfigurationGridLayout->addWidget(digIOEnRegisterBlock, 0, 4); + registerConfigurationGridLayout->addWidget(eccDcdeRegisterBlock, 1, 0); + registerConfigurationGridLayout->addWidget(eccDisRegisterBlock, 1, 1); + registerConfigurationGridLayout->addWidget(angleCkRegisterBlock, 1, 2); + + registerSensorDataGridLayout->addWidget(absAngleRegisterBlock, 0, 0); + registerSensorDataGridLayout->addWidget(angleRegisterBlock, 0, 1); + registerSensorDataGridLayout->addWidget(sineRegisterBlock, 0, 2); + registerSensorDataGridLayout->addWidget(cosineRegisterBlock, 0, 3); + registerSensorDataGridLayout->addWidget(cnvCntRegisterBlock, 0, 4); + registerSensorDataGridLayout->addWidget(tmp0RegisterBlock, 1, 0); + registerSensorDataGridLayout->addWidget(tmp1RegisterBlock, 1, 1); + registerSensorDataGridLayout->addWidget(diag1RegisterBlock, 1, 2); + registerSensorDataGridLayout->addWidget(diag2RegisterBlock, 1, 3); + registerSensorDataGridLayout->addWidget(radiusRegisterBlock, 1, 4); + registerSensorDataGridLayout->addWidget(angleSecRegisterBlock, 2, 0); + registerSensorDataGridLayout->addWidget(secAnglIRegisterBlock, 2, 1); + registerSensorDataGridLayout->addWidget(secAnglQRegisterBlock, 2, 2); + + registerDeviceIDGridLayout->addWidget(uniqID0RegisterBlock, 0, 0); + registerDeviceIDGridLayout->addWidget(uniqID1RegisterBlock, 0, 1); + registerDeviceIDGridLayout->addWidget(uniqID2RegisterBlock, 0, 2); + registerDeviceIDGridLayout->addWidget(uniqID3RegisterBlock, 0, 3); + QSpacerItem *registerDeviceSpacer = new QSpacerItem(0, 0, QSizePolicy::Preferred, QSizePolicy::Preferred); + registerDeviceIDGridLayout->addItem(registerDeviceSpacer, 0, 4); + + registerHarmonicsGridLayout->addWidget(h1MagRegisterBlock, 0, 0); + registerHarmonicsGridLayout->addWidget(h1PhRegisterBlock, 0, 1); + registerHarmonicsGridLayout->addWidget(h2MagRegisterBlock, 0, 2); + registerHarmonicsGridLayout->addWidget(h2PhRegisterBlock, 0, 3); + registerHarmonicsGridLayout->addWidget(h3MagRegisterBlock, 0, 4); + registerHarmonicsGridLayout->addWidget(h3PhRegisterBlock, 1, 0); + registerHarmonicsGridLayout->addWidget(h8MagRegisterBlock, 1, 1); + registerHarmonicsGridLayout->addWidget(h8PhRegisterBlock, 1, 2); + + for(int c=0; c < registerConfigurationGridLayout->columnCount(); ++c) registerConfigurationGridLayout->setColumnStretch(c,1); + for(int c=0; c < registerDeviceIDGridLayout->columnCount(); ++c) registerDeviceIDGridLayout->setColumnStretch(c,1); + for(int c=0; c < registerHarmonicsGridLayout->columnCount(); ++c) registerHarmonicsGridLayout->setColumnStretch(c,1); + for(int c=0; c < registerSensorDataGridLayout->columnCount(); ++c) registerSensorDataGridLayout->setColumnStretch(c,1); + + readAllRegistersButton = new QPushButton("Read All Registers", registerWidget); + StyleHelper::BasicButton(readAllRegistersButton); + readAllRegistersButton->setFixedWidth(270); + connect(readAllRegistersButton, &QPushButton::clicked, this, &HarmonicCalibration::readAllRegisters); + + registerLayout->addWidget(readAllRegistersButton); + registerLayout->addWidget(registerConfigurationLabel); + registerLayout->addWidget(registerConfigurationGridWidget); + registerLayout->addWidget(registerSensorDataLabel); + registerLayout->addWidget(registerSensorDataGridWidget); + registerLayout->addWidget(registerDeviceIDLabel); + registerLayout->addWidget(registerDeviceIDGridWidget); + registerLayout->addWidget(registerHarmonicsLabel); + registerLayout->addWidget(registerHarmonicsGridWidget); + registerLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + + tool->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tool->topContainer()->setVisible(false); + tool->topContainerMenuControl()->setVisible(false); + tool->leftContainer()->setVisible(false); + tool->rightContainer()->setVisible(false); + tool->bottomContainer()->setVisible(false); + tool->setLeftContainerWidth(270); + tool->setRightContainerWidth(270); + tool->openBottomContainerHelper(false); + tool->openTopContainerHelper(false); + + tool->addWidgetToCentralContainerHelper(registerScrollArea); + + connectRegisterBlockToRegistry(cnvPageRegisterBlock); + connectRegisterBlockToRegistry(digIORegisterBlock); + connectRegisterBlockToRegistry(faultRegisterBlock); + connectRegisterBlockToRegistry(generalRegisterBlock); + connectRegisterBlockToRegistry(digIOEnRegisterBlock); + connectRegisterBlockToRegistry(angleCkRegisterBlock); + connectRegisterBlockToRegistry(eccDcdeRegisterBlock); + connectRegisterBlockToRegistry(eccDisRegisterBlock); + + connectRegisterBlockToRegistry(absAngleRegisterBlock); + connectRegisterBlockToRegistry(angleRegisterBlock); + connectRegisterBlockToRegistry(angleSecRegisterBlock); + connectRegisterBlockToRegistry(sineRegisterBlock); + connectRegisterBlockToRegistry(cosineRegisterBlock); + connectRegisterBlockToRegistry(secAnglIRegisterBlock); + connectRegisterBlockToRegistry(secAnglQRegisterBlock); + connectRegisterBlockToRegistry(radiusRegisterBlock); + connectRegisterBlockToRegistry(diag1RegisterBlock); + connectRegisterBlockToRegistry(diag2RegisterBlock); + connectRegisterBlockToRegistry(tmp0RegisterBlock); + connectRegisterBlockToRegistry(tmp1RegisterBlock); + connectRegisterBlockToRegistry(cnvCntRegisterBlock); + + connectRegisterBlockToRegistry(uniqID0RegisterBlock); + connectRegisterBlockToRegistry(uniqID1RegisterBlock); + connectRegisterBlockToRegistry(uniqID2RegisterBlock); + connectRegisterBlockToRegistry(uniqID3RegisterBlock); + + connectRegisterBlockToRegistry(h1MagRegisterBlock); + connectRegisterBlockToRegistry(h1PhRegisterBlock); + connectRegisterBlockToRegistry(h2MagRegisterBlock); + connectRegisterBlockToRegistry(h2PhRegisterBlock); + connectRegisterBlockToRegistry(h3MagRegisterBlock); + connectRegisterBlockToRegistry(h3PhRegisterBlock); + connectRegisterBlockToRegistry(h8MagRegisterBlock); + connectRegisterBlockToRegistry(h8PhRegisterBlock); + + return tool; +} + +ToolTemplate* HarmonicCalibration::createUtilityWidget() +{ + ToolTemplate *tool = new ToolTemplate(this); + + #pragma region Left Utility Widget + QWidget *leftUtilityWidget = new QWidget(this); + QVBoxLayout *leftUtilityLayout = new QVBoxLayout(leftUtilityWidget); + leftUtilityWidget->setLayout(leftUtilityLayout); + leftUtilityLayout->setMargin(0); + leftUtilityLayout->setSpacing(8); + #pragma region Command Log Widget + MenuSectionWidget *commandLogSectionWidget = new MenuSectionWidget(leftUtilityWidget); + MenuCollapseSection *commandLogCollapseSection = new MenuCollapseSection("Command Log", MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, commandLogSectionWidget); + commandLogSectionWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + commandLogSectionWidget->contentLayout()->addWidget(commandLogCollapseSection); + commandLogCollapseSection->contentLayout()->setSpacing(8); + + commandLogPlainTextEdit = new QPlainTextEdit(commandLogSectionWidget); + commandLogPlainTextEdit->setReadOnly(true); + commandLogPlainTextEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + commandLogPlainTextEdit->setStyleSheet("QPlainTextEdit { border: none; }"); + + clearCommandLogButton = new QPushButton("Clear Command Logs", commandLogSectionWidget); + StyleHelper::BasicButton(clearCommandLogButton); + connect(clearCommandLogButton, &QPushButton::clicked, this, &HarmonicCalibration::clearCommandLog); + + commandLogCollapseSection->contentLayout()->addWidget(commandLogPlainTextEdit); + commandLogCollapseSection->contentLayout()->addWidget(clearCommandLogButton); + + leftUtilityLayout->addWidget(commandLogSectionWidget, 1); + leftUtilityLayout->addStretch(); + #pragma endregion + #pragma endregion + + #pragma region Center Utility Widget + QWidget *centerUtilityWidget = new QWidget(this); + QHBoxLayout *centerUtilityLayout = new QHBoxLayout(centerUtilityWidget); + centerUtilityWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + centerUtilityWidget->setContentsMargins(2, 0, 2, 0); + centerUtilityWidget->setLayout(centerUtilityLayout); + centerUtilityLayout->setMargin(0); + centerUtilityLayout->setSpacing(8); + + QScrollArea *DIGIOScrollArea = new QScrollArea(centerUtilityWidget); + QWidget *DIGIOWidget = new QWidget(DIGIOScrollArea); + DIGIOScrollArea->setWidget(DIGIOWidget); + DIGIOScrollArea->setWidgetResizable(true); + QVBoxLayout *DIGIOLayout = new QVBoxLayout(DIGIOWidget); + DIGIOWidget->setLayout(DIGIOLayout); + DIGIOLayout->setMargin(0); + DIGIOLayout->setSpacing(8); + + #pragma region DIGIO Monitor + MenuSectionWidget *DIGIOMonitorSectionWidget = new MenuSectionWidget(DIGIOWidget); + MenuCollapseSection *DIGIOMonitorCollapseSection = new MenuCollapseSection("DIGIO Monitor", MenuCollapseSection::MenuHeaderCollapseStyle::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, DIGIOMonitorSectionWidget); + DIGIOMonitorSectionWidget->contentLayout()->addWidget(DIGIOMonitorCollapseSection); + DIGIOMonitorCollapseSection->contentLayout()->setSpacing(8); + + DIGIOBusyStatusLED = createStatusLEDWidget("BUSY (Output)", json::theme::background_primary, true, DIGIOMonitorCollapseSection); + DIGIOCNVStatusLED = createStatusLEDWidget("CNV (Input)", json::theme::background_primary, true, DIGIOMonitorCollapseSection); + DIGIOSENTStatusLED = createStatusLEDWidget("SENT (Output)", json::theme::background_primary, true, DIGIOMonitorCollapseSection); + DIGIOACALCStatusLED = createStatusLEDWidget("ACALC (Output)", json::theme::background_primary, true, DIGIOMonitorCollapseSection); + DIGIOFaultStatusLED = createStatusLEDWidget("FAULT (Output)", json::theme::background_primary, true, DIGIOMonitorCollapseSection); + DIGIOBootloaderStatusLED = createStatusLEDWidget("BOOTLOADER (Output)", json::theme::background_primary, true, DIGIOMonitorCollapseSection); + + DIGIOMonitorCollapseSection->contentLayout()->addWidget(DIGIOBusyStatusLED); + DIGIOMonitorCollapseSection->contentLayout()->addWidget(DIGIOCNVStatusLED); + DIGIOMonitorCollapseSection->contentLayout()->addWidget(DIGIOSENTStatusLED); + DIGIOMonitorCollapseSection->contentLayout()->addWidget(DIGIOACALCStatusLED); + DIGIOMonitorCollapseSection->contentLayout()->addWidget(DIGIOFaultStatusLED); + DIGIOMonitorCollapseSection->contentLayout()->addWidget(DIGIOBootloaderStatusLED); + #pragma endregion + + #pragma region DIGIO Control + MenuSectionWidget *DIGIOControlSectionWidget = new MenuSectionWidget(DIGIOWidget); + MenuCollapseSection *DIGIOControlCollapseSection = new MenuCollapseSection("DIGIO Control", MenuCollapseSection::MenuHeaderCollapseStyle::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, DIGIOControlSectionWidget); + DIGIOControlCollapseSection->contentLayout()->setSpacing(8); + DIGIOControlSectionWidget->contentLayout()->addWidget(DIGIOControlCollapseSection); + + QWidget *DIGIOControlGridWidget = new QWidget(DIGIOControlCollapseSection); + QGridLayout *DIGIOControlGridLayout = new QGridLayout(DIGIOControlGridWidget); + DIGIOControlGridWidget->setLayout(DIGIOControlGridLayout); + DIGIOControlGridLayout->setMargin(0); + DIGIOControlGridLayout->setSpacing(8); + + QLabel *DIGIO0Label = new QLabel("DIGIO0", DIGIOControlGridWidget); + QLabel *DIGIO1Label = new QLabel("DIGIO1", DIGIOControlGridWidget); + QLabel *DIGIO2Label = new QLabel("DIGIO2", DIGIOControlGridWidget); + QLabel *DIGIO3Label = new QLabel("DIGIO3", DIGIOControlGridWidget); + QLabel *DIGIO4Label = new QLabel("DIGIO4", DIGIOControlGridWidget); + QLabel *DIGIO5Label = new QLabel("DIGIO5", DIGIOControlGridWidget); + QLabel *DIGIOFunctionLabel = new QLabel("DIGIO Function", DIGIOControlGridWidget); + QLabel *GPIOModeLabel = new QLabel("GPIO Mode", DIGIOControlGridWidget); + + DIGIO0ENToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO0ENToggleSwitch, "Output", "Input"); + connect(DIGIO0ENToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("DIGIO0EN", value); + }); + + DIGIO0FNCToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO0FNCToggleSwitch, "GPIO0", "BUSY"); + connect(DIGIO0FNCToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("BUSY", value); + }); + + DIGIO1ENToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO1ENToggleSwitch, "Output", "Input"); + connect(DIGIO1ENToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("DIGIO1EN", value); + }); + + DIGIO1FNCToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO1FNCToggleSwitch, "GPIO1", "CNV"); + connect(DIGIO1FNCToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("CNV", value); + }); + + DIGIO2ENToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO2ENToggleSwitch, "Output", "Input"); + connect(DIGIO2ENToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("DIGIO2EN", value); + }); + + DIGIO2FNCToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO2FNCToggleSwitch, "GPIO2", "SENT"); + connect(DIGIO2FNCToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("SENT", value); + }); + + DIGIO3ENToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO3ENToggleSwitch, "Output", "Input"); + connect(DIGIO3ENToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("DIGIO3EN", value); + }); + + DIGIO3FNCToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO3FNCToggleSwitch, "GPIO3", "ACALC"); + connect(DIGIO3FNCToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("ACALC", value); + }); + + DIGIO4ENToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO4ENToggleSwitch, "Output", "Input"); + connect(DIGIO4ENToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("DIGIO4EN", value); + }); + + DIGIO4FNCToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO4FNCToggleSwitch, "GPIO4", "FAULT"); + connect(DIGIO4FNCToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("FAULT", value); + }); + + DIGIO5ENToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO5ENToggleSwitch, "Output", "Input"); + connect(DIGIO5ENToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("DIGIO5EN", value); + }); + + DIGIO5FNCToggleSwitch = new CustomSwitch(); + changeCustomSwitchLabel(DIGIO5FNCToggleSwitch, "GPIO5", "BOOT"); + connect(DIGIO5FNCToggleSwitch, &CustomSwitch::clicked, [=](bool value){ + toggleDIGIOEN("BOOTLOAD", value); + }); + + QPushButton *DIGIOResetButton = new QPushButton("Reset DIGIO", DIGIOControlGridWidget); + DIGIOResetButton->setFixedWidth(100); + StyleHelper::BasicButton(DIGIOResetButton); + connect(DIGIOResetButton, &QPushButton::clicked, [=]{ + toggleUtilityTask(false); + resetDIGIO(); + toggleUtilityTask(true); + }); + + DIGIOControlGridLayout->addWidget(DIGIOFunctionLabel, 0, 1); + DIGIOControlGridLayout->addWidget(GPIOModeLabel, 0, 2); + + DIGIOControlGridLayout->addWidget(DIGIO0Label, 1, 0, Qt::AlignLeft); + DIGIOControlGridLayout->addWidget(DIGIO0FNCToggleSwitch, 1, 1); + DIGIOControlGridLayout->addWidget(DIGIO0ENToggleSwitch, 1, 2); + + DIGIOControlGridLayout->addWidget(DIGIO1Label, 2, 0, Qt::AlignLeft); + DIGIOControlGridLayout->addWidget(DIGIO1FNCToggleSwitch, 2, 1); + DIGIOControlGridLayout->addWidget(DIGIO1ENToggleSwitch, 2, 2); + + DIGIOControlGridLayout->addWidget(DIGIO2Label, 3, 0, Qt::AlignLeft); + DIGIOControlGridLayout->addWidget(DIGIO2FNCToggleSwitch, 3, 1); + DIGIOControlGridLayout->addWidget(DIGIO2ENToggleSwitch, 3, 2); + + DIGIOControlGridLayout->addWidget(DIGIO3Label, 4, 0, Qt::AlignLeft); + DIGIOControlGridLayout->addWidget(DIGIO3FNCToggleSwitch, 4, 1); + DIGIOControlGridLayout->addWidget(DIGIO3ENToggleSwitch, 4, 2); + + DIGIOControlGridLayout->addWidget(DIGIO4Label, 5, 0, Qt::AlignLeft); + DIGIOControlGridLayout->addWidget(DIGIO4FNCToggleSwitch, 5, 1); + DIGIOControlGridLayout->addWidget(DIGIO4ENToggleSwitch, 5, 2); + + DIGIOControlGridLayout->addWidget(DIGIO5Label, 6, 0, Qt::AlignLeft); + DIGIOControlGridLayout->addWidget(DIGIO5FNCToggleSwitch, 6, 1); + DIGIOControlGridLayout->addWidget(DIGIO5ENToggleSwitch, 6, 2); + + DIGIOControlGridLayout->setColumnStretch(0, 1); + + DIGIOControlCollapseSection->contentLayout()->addWidget(DIGIOControlGridWidget); + DIGIOControlCollapseSection->contentLayout()->addWidget(DIGIOResetButton); + + if(generalRegisterMap.at("Sequence Type") == 0) + { + DIGIO2FNCToggleSwitch->setVisible(false); + DIGIO4FNCToggleSwitch->setVisible(false); + } + + #pragma endregion + + DIGIOLayout->addWidget(DIGIOMonitorSectionWidget); + DIGIOLayout->addWidget(DIGIOControlSectionWidget); + DIGIOLayout->addStretch(); + + #pragma region MTDIAG1 Widget + MenuSectionWidget *MTDIAG1SectionWidget = new MenuSectionWidget(centerUtilityWidget); + MenuCollapseSection *MTDIAG1CollapseSection = new MenuCollapseSection("MT Diagnostic Register", MenuCollapseSection::MenuHeaderCollapseStyle::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, MTDIAG1SectionWidget); + MTDIAG1SectionWidget->contentLayout()->addWidget(MTDIAG1CollapseSection); + MTDIAG1CollapseSection->contentLayout()->setSpacing(8); + + R0StatusLED = createStatusLEDWidget("R0", json::theme::background_primary, true, MTDIAG1SectionWidget); + R1StatusLED = createStatusLEDWidget("R1", json::theme::background_primary, true, MTDIAG1SectionWidget); + R2StatusLED = createStatusLEDWidget("R2", json::theme::background_primary, true, MTDIAG1SectionWidget); + R3StatusLED = createStatusLEDWidget("R3", json::theme::background_primary, true, MTDIAG1SectionWidget); + R4StatusLED = createStatusLEDWidget("R4", json::theme::background_primary, true, MTDIAG1SectionWidget); + R5StatusLED = createStatusLEDWidget("R5", json::theme::background_primary, true, MTDIAG1SectionWidget); + R6StatusLED = createStatusLEDWidget("R6", json::theme::background_primary, true, MTDIAG1SectionWidget); + R7StatusLED = createStatusLEDWidget("R7", json::theme::background_primary, true, MTDIAG1SectionWidget); + + MTDIAG1CollapseSection->contentLayout()->addWidget(R0StatusLED); + MTDIAG1CollapseSection->contentLayout()->addWidget(R1StatusLED); + MTDIAG1CollapseSection->contentLayout()->addWidget(R2StatusLED); + MTDIAG1CollapseSection->contentLayout()->addWidget(R3StatusLED); + MTDIAG1CollapseSection->contentLayout()->addWidget(R4StatusLED); + MTDIAG1CollapseSection->contentLayout()->addWidget(R5StatusLED); + MTDIAG1CollapseSection->contentLayout()->addWidget(R6StatusLED); + MTDIAG1CollapseSection->contentLayout()->addWidget(R7StatusLED); + #pragma endregion + + #pragma region MT Diagnostics + MTDiagnosticsScrollArea = new QScrollArea(centerUtilityWidget); + QWidget *MTDiagnosticsWidget = new QWidget(MTDiagnosticsScrollArea); + MTDiagnosticsScrollArea->setWidget(MTDiagnosticsWidget); + MTDiagnosticsScrollArea->setWidgetResizable(true); + QVBoxLayout *MTDiagnosticsLayout = new QVBoxLayout(MTDiagnosticsWidget); + MTDiagnosticsWidget->setLayout(MTDiagnosticsLayout); + MTDiagnosticsLayout->setMargin(0); + MTDiagnosticsLayout->setSpacing(8); + + MenuSectionWidget *MTDiagnosticsSectionWidget = new MenuSectionWidget(centerUtilityWidget); + MenuCollapseSection *MTDiagnosticsCollapseSection = new MenuCollapseSection("MT Diagnostics", MenuCollapseSection::MenuHeaderCollapseStyle::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, MTDiagnosticsSectionWidget); + MTDiagnosticsSectionWidget->contentLayout()->addWidget(MTDiagnosticsCollapseSection); + MTDiagnosticsCollapseSection->contentLayout()->setSpacing(8); + + QLabel *AFEDIAG0Label = new QLabel("AFEDIAG0 (%)"); + Style::setStyle(AFEDIAG0Label, style::properties::label::menuSmall); + QLabel *AFEDIAG1Label = new QLabel("AFEDIAG1 (%)"); + Style::setStyle(AFEDIAG1Label, style::properties::label::menuSmall); + QLabel *AFEDIAG2Label = new QLabel("AFEDIAG2 (V)"); + Style::setStyle(AFEDIAG2Label, style::properties::label::menuSmall); + + AFEDIAG0LineEdit = new QLineEdit(MTDiagnosticsSectionWidget); + AFEDIAG1LineEdit = new QLineEdit(MTDiagnosticsSectionWidget); + AFEDIAG2LineEdit = new QLineEdit(MTDiagnosticsSectionWidget); + AFEDIAG0LineEdit->setReadOnly(true); + AFEDIAG1LineEdit->setReadOnly(true); + AFEDIAG2LineEdit->setReadOnly(true); + connectLineEditToNumber(AFEDIAG0LineEdit, afeDiag0, "V"); + connectLineEditToNumber(AFEDIAG1LineEdit, afeDiag1, "V"); + connectLineEditToNumber(AFEDIAG2LineEdit, afeDiag2, "V"); + + MTDiagnosticsCollapseSection->contentLayout()->addWidget(AFEDIAG0Label); + MTDiagnosticsCollapseSection->contentLayout()->addWidget(AFEDIAG0LineEdit); + MTDiagnosticsCollapseSection->contentLayout()->addWidget(AFEDIAG1Label); + MTDiagnosticsCollapseSection->contentLayout()->addWidget(AFEDIAG1LineEdit); + MTDiagnosticsCollapseSection->contentLayout()->addWidget(AFEDIAG2Label); + MTDiagnosticsCollapseSection->contentLayout()->addWidget(AFEDIAG2LineEdit); + + MTDiagnosticsLayout->addWidget(MTDiagnosticsSectionWidget); + MTDiagnosticsLayout->addWidget(MTDIAG1SectionWidget); + MTDiagnosticsLayout->addStretch(); + #pragma endregion + + centerUtilityLayout->addWidget(DIGIOScrollArea); + centerUtilityLayout->addWidget(MTDiagnosticsScrollArea); + + #pragma endregion + + #pragma region Right Utility Widget + QScrollArea *rightUtilityScrollArea = new QScrollArea(this); + QWidget *rightUtilityWidget = new QWidget(rightUtilityScrollArea); + rightUtilityScrollArea->setWidget(rightUtilityWidget); + rightUtilityScrollArea->setWidgetResizable(true); + QVBoxLayout *rightUtilityLayout = new QVBoxLayout(rightUtilityWidget); + rightUtilityWidget->setLayout(rightUtilityLayout); + rightUtilityLayout->setMargin(0); + rightUtilityLayout->setSpacing(8); + + MenuSectionWidget *faultRegisterSectionWidget = new MenuSectionWidget(rightUtilityWidget); + MenuCollapseSection *faultRegisterCollapseSection = new MenuCollapseSection("Fault Register", MenuCollapseSection::MenuHeaderCollapseStyle::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, faultRegisterSectionWidget); + faultRegisterSectionWidget->contentLayout()->addWidget(faultRegisterCollapseSection); + faultRegisterCollapseSection->contentLayout()->setSpacing(8); + + VDDUnderVoltageStatusLED = createStatusLEDWidget("VDD Under Voltage", json::theme::content_error, true, faultRegisterCollapseSection); + VDDOverVoltageStatusLED = createStatusLEDWidget("VDD Over Voltage", json::theme::content_error, true, faultRegisterCollapseSection); + VDRIVEUnderVoltageStatusLED = createStatusLEDWidget("VDRIVE Under Voltage", json::theme::content_error, true, faultRegisterCollapseSection); + VDRIVEOverVoltageStatusLED = createStatusLEDWidget("VDRIVE Over Voltage", json::theme::content_error, true, faultRegisterCollapseSection); + AFEDIAGStatusLED = createStatusLEDWidget("AFEDIAG", json::theme::content_error, true, faultRegisterCollapseSection); + NVMCRCFaultStatusLED = createStatusLEDWidget("NVM CRC Fault", json::theme::content_error, true, faultRegisterCollapseSection); + ECCDoubleBitErrorStatusLED = createStatusLEDWidget("ECC Double Bit Error", json::theme::content_error, true, faultRegisterCollapseSection); + OscillatorDriftStatusLED = createStatusLEDWidget("Oscillator Drift", json::theme::content_error, true, faultRegisterCollapseSection); + CountSensorFalseStateStatusLED = createStatusLEDWidget("Count Sensor False State", json::theme::content_error, true, faultRegisterCollapseSection); + AngleCrossCheckStatusLED = createStatusLEDWidget("Angle Cross Check", json::theme::content_error, true, faultRegisterCollapseSection); + TurnCountSensorLevelsStatusLED = createStatusLEDWidget("Turn Count Sensor Levels", json::theme::content_error, true, faultRegisterCollapseSection); + MTDIAGStatusLED = createStatusLEDWidget("MTDIAG", json::theme::content_error, true, faultRegisterCollapseSection); + TurnCounterCrossCheckStatusLED = createStatusLEDWidget("Turn Counter Cross Check", json::theme::content_error, true, faultRegisterCollapseSection); + RadiusCheckStatusLED = createStatusLEDWidget("Radius Check", json::theme::content_error, true, faultRegisterCollapseSection); + SequencerWatchdogStatusLED = createStatusLEDWidget("Sequencer Watchdog", json::theme::content_error, true, faultRegisterCollapseSection); + + faultRegisterCollapseSection->contentLayout()->addWidget(VDDUnderVoltageStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(VDDOverVoltageStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(VDRIVEUnderVoltageStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(VDRIVEOverVoltageStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(AFEDIAGStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(NVMCRCFaultStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(ECCDoubleBitErrorStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(OscillatorDriftStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(CountSensorFalseStateStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(AngleCrossCheckStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(TurnCountSensorLevelsStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(MTDIAGStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(TurnCounterCrossCheckStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(RadiusCheckStatusLED); + faultRegisterCollapseSection->contentLayout()->addWidget(SequencerWatchdogStatusLED); + + rightUtilityLayout->addWidget(faultRegisterSectionWidget); + rightUtilityLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + #pragma endregion + + tool->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + tool->topContainer()->setVisible(false); + tool->topContainerMenuControl()->setVisible(false); + tool->leftContainer()->setVisible(true); + tool->rightContainer()->setVisible(true); + tool->bottomContainer()->setVisible(false); + tool->setLeftContainerWidth(240); + tool->setRightContainerWidth(224); + tool->openBottomContainerHelper(false); + tool->openTopContainerHelper(false); + + tool->leftStack()->addWidget(leftUtilityWidget); + tool->addWidgetToCentralContainerHelper(centerUtilityWidget); + tool->rightStack()->addWidget(rightUtilityScrollArea); + + return tool; +} + +void HarmonicCalibration::readDeviceProperties() +{ + uint32_t *uniqId3RegisterValue = new uint32_t; + uint32_t *cnvPageRegValue = new uint32_t; + uint32_t page = m_admtController->getUniqueIdPage(ADMTController::UniqueIDRegister::UNIQID3); + uint32_t cnvPageAddress = m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::CNVPAGE); + + bool success = false; + + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), cnvPageAddress, page) != -1){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), cnvPageAddress, cnvPageRegValue) != -1){ + if(*cnvPageRegValue == page){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getUniqueIdRegister(ADMTController::UniqueIDRegister::UNIQID3), uniqId3RegisterValue) != -1){ + deviceRegisterMap = m_admtController->getUNIQID3RegisterMapping(static_cast(*uniqId3RegisterValue)); + + if(deviceRegisterMap.at("Supply ID") == "5V") { is5V = true; } + else if(deviceRegisterMap.at("Supply ID") == "3.3V") { is5V = false; } + else { is5V = false; } + + deviceName = QString::fromStdString(deviceRegisterMap.at("Product ID")); + + if(deviceRegisterMap.at("ASIL ID") == "ASIL QM") { deviceType = QString::fromStdString("Industrial"); } + else if(deviceRegisterMap.at("ASIL ID") == "ASIL B") { deviceType = QString::fromStdString("Automotive"); } + else { deviceType = QString::fromStdString(deviceRegisterMap.at("ASIL ID")); } + + success = true; + } + } + } + } + + if(!success) { StatusBarManager::pushMessage("Failed to read device properties"); } +} + +void HarmonicCalibration::initializeADMT() +{ + bool success = resetDIGIO(); + success = resetGENERAL(); + + if(!success){ StatusBarManager::pushMessage("Failed initialize ADMT"); } +} + +bool HarmonicCalibration::readSequence(){ + uint32_t *generalRegValue = new uint32_t; + uint32_t generalRegisterAddress = m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::GENERAL); + uint32_t generalRegisterPage = m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::GENERAL); + + bool success = false; + + if(changeCNVPage(generalRegisterPage)){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), generalRegisterAddress, generalRegValue) != -1){ + if(*generalRegValue != UINT32_MAX){ + generalRegisterMap = m_admtController->getGeneralRegisterBitMapping(static_cast(*generalRegValue)); + success = true; + } + } + } + + return success; +} + +void HarmonicCalibration::applySequence(){ + toggleWidget(applySequenceButton, false); + applySequenceButton->setText("Writing..."); + QTimer::singleShot(1000, this, [this](){ + this->toggleWidget(applySequenceButton, true); + applySequenceButton->setText("Apply"); + }); + uint32_t *generalRegValue = new uint32_t; + uint32_t generalRegisterAddress = m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::GENERAL); + map settings; + + settings["Convert Synchronization"] = qvariant_cast(convertSynchronizationMenuCombo->combo()->currentData()); // convertSync; + settings["Angle Filter"] = qvariant_cast(angleFilterMenuCombo->combo()->currentData()); // angleFilter; + settings["8th Harmonic"] = qvariant_cast(eighthHarmonicMenuCombo->combo()->currentData()); // eighthHarmonic; + settings["Sequence Type"] = qvariant_cast(sequenceTypeMenuCombo->combo()->currentData()); // sequenceType; + settings["Conversion Type"] = qvariant_cast(conversionTypeMenuCombo->combo()->currentData()); // conversionType; + + bool success = false; + + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), generalRegisterAddress, generalRegValue) != -1){ + + uint32_t newGeneralRegValue = m_admtController->setGeneralRegisterBitMapping(*generalRegValue, settings); + uint32_t generalRegisterPage = m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::GENERAL); + + if(changeCNVPage(generalRegisterPage)){ + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), generalRegisterAddress, newGeneralRegValue) != -1){ + if(readSequence()){ + if(settings.at("Convert Synchronization") == generalRegisterMap.at("Convert Synchronization") && + settings.at("Angle Filter") == generalRegisterMap.at("Angle Filter") && + settings.at("8th Harmonic") == generalRegisterMap.at("8th Harmonic") && + settings.at("Sequence Type") == generalRegisterMap.at("Sequence Type") && + settings.at("Conversion Type") == generalRegisterMap.at("Conversion Type")) + { + StatusBarManager::pushMessage("Sequence settings applied successfully"); + success = true; + } + } + } + } + } + + + if(!success){ StatusBarManager::pushMessage("Failed to apply sequence settings"); } +} + +bool HarmonicCalibration::changeCNVPage(uint32_t page){ + uint32_t *cnvPageRegValue = new uint32_t; + uint32_t cnvPageAddress = m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::CNVPAGE); + + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), cnvPageAddress, page) != -1){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), cnvPageAddress, cnvPageRegValue) != -1){ + if(*cnvPageRegValue == page){ + return true; + } + } + } + + return false; +} + +void HarmonicCalibration::initializeMotor() +{ + rotate_vmax = 53687.0912; + writeMotorAttributeValue(ADMTController::MotorAttribute::ROTATE_VMAX, rotate_vmax); + readMotorAttributeValue(ADMTController::MotorAttribute::ROTATE_VMAX, rotate_vmax); + writeMotorAttributeValue(ADMTController::MotorAttribute::DISABLE, 1); + + amax = 439.8046511104; + writeMotorAttributeValue(ADMTController::MotorAttribute::AMAX, amax); + readMotorAttributeValue(ADMTController::MotorAttribute::AMAX, amax); + + dmax = 3000; + writeMotorAttributeValue(ADMTController::MotorAttribute::DMAX, dmax); + readMotorAttributeValue(ADMTController::MotorAttribute::DMAX, dmax); + + ramp_mode = 0; + writeMotorAttributeValue(ADMTController::MotorAttribute::RAMP_MODE, ramp_mode); + readMotorAttributeValue(ADMTController::MotorAttribute::RAMP_MODE, ramp_mode); + + target_pos = 0; + writeMotorAttributeValue(ADMTController::MotorAttribute::TARGET_POS, target_pos); + + current_pos = 0; + readMotorAttributeValue(ADMTController::MotorAttribute::CURRENT_POS, current_pos); +} + +void HarmonicCalibration::startDeviceStatusMonitor() +{ + isDeviceStatusMonitor = true; + m_deviceStatusThread = QtConcurrent::run(this, &HarmonicCalibration::getDeviceFaultStatus, deviceStatusMonitorRate); + m_deviceStatusWatcher.setFuture(m_deviceStatusThread); +} + +void HarmonicCalibration::stopDeviceStatusMonitor() +{ + isDeviceStatusMonitor = false; + if(m_deviceStatusThread.isRunning()) + { + m_deviceStatusThread.cancel(); + m_deviceStatusWatcher.waitForFinished(); + } +} + +void HarmonicCalibration::getDeviceFaultStatus(int sampleRate) +{ + uint32_t *readValue = new uint32_t; + bool registerFault = false; + while(isDeviceStatusMonitor) + { + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::FAULT), 0) == 0) + { + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::FAULT), readValue) == 0) + { + registerFault = m_admtController->checkRegisterFault(static_cast(*readValue), generalRegisterMap.at("Sequence Type") == 0 ? true : false); + Q_EMIT updateFaultStatusSignal(registerFault); + } + else + { + Q_EMIT updateFaultStatusSignal(true); + } + } + else + { + Q_EMIT updateFaultStatusSignal(true); + } + + QThread::msleep(sampleRate); + } +} + +void HarmonicCalibration::requestDisconnect() +{ + stopAcquisition(); + + stopAcquisitionUITask(); + stopCalibrationUITask(); + stopUtilityTask(); + + stopDeviceStatusMonitor(); + stopCurrentMotorPositionMonitor(); +} + +void HarmonicCalibration::startCurrentMotorPositionMonitor() +{ + isMotorPositionMonitor = true; + m_currentMotorPositionThread = QtConcurrent::run(this, &HarmonicCalibration::currentMotorPositionTask, motorPositionMonitorRate); + m_currentMotorPositionWatcher.setFuture(m_currentMotorPositionThread); +} + +void HarmonicCalibration::stopCurrentMotorPositionMonitor() +{ + isMotorPositionMonitor = false; + if(m_currentMotorPositionThread.isRunning()) + { + m_currentMotorPositionThread.cancel(); + m_currentMotorPositionWatcher.waitForFinished(); + } +} + +void HarmonicCalibration::currentMotorPositionTask(int sampleRate) +{ + double motorPosition = 0; + while(isMotorPositionMonitor) + { + readMotorAttributeValue(ADMTController::MotorAttribute::CURRENT_POS, motorPosition); + + Q_EMIT motorPositionChanged(motorPosition); + + QThread::msleep(sampleRate); + } +} + +void HarmonicCalibration::updateMotorPosition(double position) +{ + current_pos = position; + if(isAcquisitionTab) updateLineEditValue(acquisitionMotorCurrentPositionLineEdit, current_pos); + else if(isCalibrationTab) updateLineEditValue(calibrationMotorCurrentPositionLineEdit, current_pos); +} + +bool HarmonicCalibration::resetGENERAL() +{ + bool success = false; + + uint32_t resetValue = 0x0000; + if(deviceRegisterMap.at("ASIL ID") == "ASIL QM") { resetValue = 0x1230; } // Industrial or ASIL QM + else if(deviceRegisterMap.at("ASIL ID") == "ASIL B") { resetValue = 0x1200; } // Automotive or ASIL B + + if(resetValue != 0x0000){ + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::GENERAL), resetValue) == 0) { success = true; } + } + + return success; +} + +#pragma region Acquisition Methods +bool HarmonicCalibration::updateChannelValues(){ + bool success = false; + rotation = m_admtController->getChannelValue(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), rotationChannelName, bufferSize); + if(rotation == static_cast(UINT64_MAX)) { return false; } + angle = m_admtController->getChannelValue(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), angleChannelName, bufferSize); + if(angle == static_cast(UINT64_MAX)) { return false; } + updateCountValue(); + if(count == static_cast(UINT64_MAX)) { return false; } + temp = m_admtController->getChannelValue(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), temperatureChannelName, bufferSize); + if(temp == static_cast(UINT64_MAX)) { return false; } + return success = true; +} + +void HarmonicCalibration::updateCountValue(){ + uint32_t *absAngleRegValue = new uint32_t; + bool success = false; + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::CNVPAGE), 0x0000) == 0){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getSensorRegister(ADMTController::SensorRegister::ABSANGLE), absAngleRegValue) == 0){ + count = m_admtController->getAbsAngleTurnCount(static_cast(*absAngleRegValue)); + success = true; + } + } + if(!success){ count = static_cast(UINT64_MAX); } +} + +void HarmonicCalibration::updateLineEditValues(){ + if(rotation == static_cast(UINT64_MAX)) { rotationValueLabel->setText("N/A"); } + else { rotationValueLabel->setText(QString::number(rotation) + "°"); } + if(angle == static_cast(UINT64_MAX)) { angleValueLabel->setText("N/A"); } + else { angleValueLabel->setText(QString::number(angle) + "°"); } + if(count == static_cast(UINT64_MAX)) { countValueLabel->setText("N/A"); } + else { countValueLabel->setText(QString::number(count)); } + if(temp == static_cast(UINT64_MAX)) { tempValueLabel->setText("N/A"); } + else { tempValueLabel->setText(QString::number(temp) + " °C"); } +} + +void HarmonicCalibration::startAcquisition() +{ + isStartAcquisition = true; + acquisitionXPlotAxis->setInterval(0, acquisitionDisplayLength); + + m_acquisitionDataThread = QtConcurrent::run(this, &HarmonicCalibration::getAcquisitionSamples, acquisitionSampleRate); + m_acquisitionDataWatcher.setFuture(m_acquisitionDataThread); + m_acquisitionGraphThread = QtConcurrent::run(this, &HarmonicCalibration::acquisitionPlotTask, acquisitionGraphSampleRate); + m_acquisitionGraphWatcher.setFuture(m_acquisitionGraphThread); + + startCurrentMotorPositionMonitor(); +} + +void HarmonicCalibration::stopAcquisition() +{ + isStartAcquisition = false; + if(m_acquisitionDataThread.isRunning()) + { + m_acquisitionDataThread.cancel(); + m_acquisitionDataWatcher.waitForFinished(); + } + if(m_acquisitionGraphThread.isRunning()) + { + m_acquisitionGraphThread.cancel(); + m_acquisitionGraphWatcher.waitForFinished(); + } + + stopCurrentMotorPositionMonitor(); +} + +void HarmonicCalibration::updateFaultStatus(bool status) +{ + bool isChanged = (deviceStatusFault != status); + if(isChanged) + { + deviceStatusFault = status; + toggleStatusLEDColor(acquisitionFaultRegisterLEDWidget, json::theme::content_error, json::theme::content_success, deviceStatusFault); + toggleStatusLEDColor(calibrationFaultRegisterLEDWidget, json::theme::content_error, json::theme::content_success, deviceStatusFault); + } +} + +void HarmonicCalibration::getAcquisitionSamples(int sampleRate) +{ + while(isStartAcquisition) + { + if(!updateChannelValues()) { break; } + + if(acquisitionDataMap.at(ANGLE) == false && acquisitionAngleList.size() > 0) acquisitionAngleList.clear(); + if(acquisitionDataMap.at(ABSANGLE) == false && acquisitionABSAngleList.size() > 0) acquisitionABSAngleList.clear(); + if(acquisitionDataMap.at(TURNCOUNT) == false && acquisitionTurnCountList.size() > 0) acquisitionTurnCountList.clear(); + if(acquisitionDataMap.at(TMP0) == false && acquisitionTmp0List.size() > 0) acquisitionTmp0List.clear(); + if(acquisitionDataMap.at(SINE) == false && acquisitionSineList.size() > 0) acquisitionSineList.clear(); + if(acquisitionDataMap.at(COSINE) == false && acquisitionCosineList.size() > 0) acquisitionCosineList.clear(); + if(acquisitionDataMap.at(RADIUS) == false && acquisitionRadiusList.size() > 0) acquisitionRadiusList.clear(); + + if(acquisitionDataMap.at(ANGLE)) prependAcquisitionData(angle, acquisitionAngleList); + if(acquisitionDataMap.at(ABSANGLE)) prependAcquisitionData(rotation, acquisitionABSAngleList); + if(acquisitionDataMap.at(TURNCOUNT)) prependAcquisitionData(count, acquisitionTurnCountList); + if(acquisitionDataMap.at(TMP0)) prependAcquisitionData(temp, acquisitionTmp0List); + if(acquisitionDataMap.at(SINE)) prependAcquisitionData(getAcquisitionParameterValue(SINE), acquisitionSineList); + if(acquisitionDataMap.at(COSINE)) prependAcquisitionData(getAcquisitionParameterValue(COSINE), acquisitionCosineList); + if(acquisitionDataMap.at(RADIUS)) prependAcquisitionData(getAcquisitionParameterValue(RADIUS), acquisitionRadiusList); + + QThread::msleep(sampleRate); + } +} + +double HarmonicCalibration::getAcquisitionParameterValue(const AcquisitionDataKey &key) +{ + uint32_t *readValue = new uint32_t; + switch(key) + { + case SINE: + { + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getSensorRegister(ADMTController::SensorRegister::SINE), + readValue) == -1) return qQNaN(); + map sineRegisterMap = m_admtController->getSineRegisterBitMapping(static_cast(*readValue)); + return sineRegisterMap.at("SINE"); + break; + } + case COSINE: + { + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getSensorRegister(ADMTController::SensorRegister::COSINE), + readValue) == -1) return qQNaN(); + map cosineRegisterMap = m_admtController->getCosineRegisterBitMapping(static_cast(*readValue)); + return cosineRegisterMap.at("COSINE"); + break; + } + case RADIUS: + { + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getSensorRegister(ADMTController::SensorRegister::RADIUS), + readValue) == -1) return qQNaN(); + map radiusRegisterMap = m_admtController->getRadiusRegisterBitMapping(static_cast(*readValue)); + return radiusRegisterMap.at("RADIUS"); + break; + } + default: + return qQNaN(); + break; + } +} + +void HarmonicCalibration::plotAcquisition(QVector& list, PlotChannel* channel) +{ + channel->curve()->setSamples(list); + auto result = minmax_element(list.begin(), list.end()); + if(*result.first < acquisitionGraphYMin) acquisitionGraphYMin = *result.first; + if(*result.second > acquisitionGraphYMax) acquisitionGraphYMax = *result.second; +} + +void HarmonicCalibration::prependAcquisitionData(const double& data, QVector& list) +{ + list.prepend(data); + if(list.size() >= acquisitionDisplayLength){ + list.resize(acquisitionDisplayLength); + list.squeeze(); + } +} + +void HarmonicCalibration::resetAcquisitionYAxisScale() +{ + acquisitionGraphYMin = 0; + acquisitionGraphYMax = 360; + acquisitionYPlotAxis->setInterval(acquisitionGraphYMin, acquisitionGraphYMax); + acquisitionGraphPlotWidget->replot(); +} + +void HarmonicCalibration::acquisitionPlotTask(int sampleRate) +{ + while(isStartAcquisition){ + if(acquisitionDataMap.at(ANGLE)) + plotAcquisition(acquisitionAngleList, acquisitionAnglePlotChannel); + if(acquisitionDataMap.at(ABSANGLE)) + plotAcquisition(acquisitionABSAngleList, acquisitionABSAnglePlotChannel); + if(acquisitionDataMap.at(TURNCOUNT)) + plotAcquisition(acquisitionTurnCountList, acquisitionTurnCountPlotChannel); + if(acquisitionDataMap.at(TMP0)) + plotAcquisition(acquisitionTmp0List, acquisitionTmp0PlotChannel); + if(acquisitionDataMap.at(SINE)) + plotAcquisition(acquisitionSineList, acquisitionSinePlotChannel); + if(acquisitionDataMap.at(COSINE)) + plotAcquisition(acquisitionCosineList, acquisitionCosinePlotChannel); + if(acquisitionDataMap.at(RADIUS)) + plotAcquisition(acquisitionRadiusList, acquisitionRadiusPlotChannel); + + acquisitionYPlotAxis->setInterval(acquisitionGraphYMin, acquisitionGraphYMax); + acquisitionGraphPlotWidget->replot(); + + QThread::msleep(sampleRate); + } +} + +void HarmonicCalibration::acquisitionUITask(int sampleRate) +{ + while(isAcquisitionTab) + { + if(isStartAcquisition) updateLineEditValues(); + + QThread::msleep(sampleRate); + } +} + +void HarmonicCalibration::startAcquisitionUITask() +{ + isAcquisitionTab = true; + m_acquisitionUIThread = QtConcurrent::run(this, &HarmonicCalibration::acquisitionUITask, acquisitionUITimerRate); + m_acquisitionUIWatcher.setFuture(m_acquisitionUIThread); +} + +void HarmonicCalibration::stopAcquisitionUITask() +{ + isAcquisitionTab = false; + if(m_acquisitionUIThread.isRunning()) + { + m_acquisitionUIThread.cancel(); + m_acquisitionUIWatcher.waitForFinished(); + } +} + +void HarmonicCalibration::updateSequenceWidget(){ + if(generalRegisterMap.at("Sequence Type") == -1){ sequenceTypeMenuCombo->combo()->setCurrentText("Reserved"); } + else{ sequenceTypeMenuCombo->combo()->setCurrentIndex(sequenceTypeMenuCombo->combo()->findData(generalRegisterMap.at("Sequence Type"))); } + conversionTypeMenuCombo->combo()->setCurrentIndex(conversionTypeMenuCombo->combo()->findData(generalRegisterMap.at("Conversion Type"))); + if(generalRegisterMap.at("Convert Synchronization") == -1){ convertSynchronizationMenuCombo->combo()->setCurrentText("Reserved"); } + else{ convertSynchronizationMenuCombo->combo()->setCurrentIndex(convertSynchronizationMenuCombo->combo()->findData(generalRegisterMap.at("Convert Synchronization"))); } + angleFilterMenuCombo->combo()->setCurrentIndex(angleFilterMenuCombo->combo()->findData(generalRegisterMap.at("Angle Filter"))); + eighthHarmonicMenuCombo->combo()->setCurrentIndex(eighthHarmonicMenuCombo->combo()->findData(generalRegisterMap.at("8th Harmonic"))); +} + +void HarmonicCalibration::applySequenceAndUpdate() +{ + applySequence(); + updateSequenceWidget(); +} + +void HarmonicCalibration::updateGeneralSettingEnabled(bool value) +{ + graphUpdateIntervalLineEdit->setEnabled(value); + displayLengthLineEdit->setEnabled(value); +} + +void HarmonicCalibration::connectCheckBoxToAcquisitionGraph(QCheckBox* widget, PlotChannel* channel, AcquisitionDataKey key) +{ + connect(widget, &QCheckBox::stateChanged, [=](int state){ + if(state == Qt::Checked){ + channel->setEnabled(true); + acquisitionDataMap[key] = true; + } + else{ + channel->setEnabled(false); + acquisitionDataMap[key] = false; + } + }); +} + +void HarmonicCalibration::GMRReset() +{ + // Set Motor Angle to 315 degrees + target_pos = 0; + writeMotorAttributeValue(ADMTController::MotorAttribute::TARGET_POS, target_pos); + + // Write 1 to ADMT IIO Attribute coil_rs + m_admtController->setDeviceAttributeValue(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getDeviceAttribute(ADMTController::DeviceAttribute::SDP_COIL_RS), 1); + + // Write 0xc000 to CNVPAGE + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::CNVPAGE), 0xc000) != -1) + { + // Write 0x0000 to CNVPAGE + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::CNVPAGE), 0x0000) != -1) + { + // Read ABSANGLE + + StatusBarManager::pushMessage("GMR Reset Done"); + } + else { StatusBarManager::pushMessage("Failed to write CNVPAGE Register"); } + } + else { StatusBarManager::pushMessage("Failed to write CNVPAGE Register"); } +} + +void HarmonicCalibration::restart() +{ + if(m_running) { + run(false); + run(true); + } +} + +bool HarmonicCalibration::running() const { return m_running; } + +void HarmonicCalibration::setRunning(bool newRunning) +{ + if(m_running == newRunning) + return; + m_running = newRunning; + Q_EMIT runningChanged(newRunning); +} + +void HarmonicCalibration::start() { run(true); } + +void HarmonicCalibration::stop() { run(false); } + +void HarmonicCalibration::run(bool b) +{ + qInfo() << b; + QElapsedTimer tim; + tim.start(); + + if(!b) { + isStartAcquisition = false; + runButton->setChecked(false); + } + else{ + startAcquisition(); + } + + updateGeneralSettingEnabled(!b); +} +#pragma endregion + +#pragma region Calibration Methods +void HarmonicCalibration::startCalibrationUITask() +{ + isCalibrationTab = true; + m_calibrationUIThread = QtConcurrent::run(this, &HarmonicCalibration::calibrationUITask, calibrationUITimerRate); + m_calibrationUIWatcher.setFuture(m_calibrationUIThread); +} + +void HarmonicCalibration::stopCalibrationUITask() +{ + isCalibrationTab = false; + if(m_calibrationUIThread.isRunning()) + { + m_calibrationUIThread.cancel(); + m_calibrationUIWatcher.waitForFinished(); + } +} + +void HarmonicCalibration::calibrationUITask(int sampleRate) +{ + while (isCalibrationTab) + { + if(isStartMotor) + { + if(isPostCalibration){ + postCalibrationRawDataPlotChannel->curve()->setSamples(graphPostDataList); + postCalibrationRawDataPlotWidget->replot(); + } + else{ + calibrationRawDataPlotChannel->curve()->setSamples(graphDataList); + calibrationRawDataPlotWidget->replot(); + } + } + + QThread::msleep(sampleRate); + } +} + +void HarmonicCalibration::getCalibrationSamples() +{ + if(resetCurrentPositionToZero()){ + if(isPostCalibration){ + int currentSamplesCount = graphPostDataList.size(); + while(isStartMotor && currentSamplesCount < totalSamplesCount){ + target_pos = current_pos + -408; + moveMotorToPosition(target_pos, true); + updateChannelValue(ADMTController::Channel::ANGLE); + graphPostDataList.append(angle); + currentSamplesCount++; + } + } + else{ + int currentSamplesCount = graphDataList.size(); + while(isStartMotor && currentSamplesCount < totalSamplesCount){ + target_pos = current_pos + -408; + if(moveMotorToPosition(target_pos, true) == false) { m_admtController->disconnectADMT(); } + if(updateChannelValue(ADMTController::Channel::ANGLE)) { break; } + graphDataList.append(angle); + currentSamplesCount++; + } + } + } + + stopMotor(); +} + +void HarmonicCalibration::startCalibration() +{ + totalSamplesCount = cycleCount * samplesPerCycle; + + graphPostDataList.reserve(totalSamplesCount); + graphPostDataList.squeeze(); + graphDataList.reserve(totalSamplesCount); + graphDataList.squeeze(); + + toggleTabSwitching(false); + toggleMotorControls(false); + + startMotor(); +} + +void HarmonicCalibration::stopCalibration() +{ + isStartMotor = false; + + toggleTabSwitching(true); + toggleMotorControls(true); +} + +void HarmonicCalibration::startMotor() +{ + if(resetToZero && !isPostCalibration){ + clearCalibrationSamples(); + clearPostCalibrationSamples(); + clearAngleErrorGraphs(); + clearCorrectedAngleErrorGraphs(); + } + else if(resetToZero){ + clearPostCalibrationSamples(); + } + + if(isPostCalibration) + postCalibrationRawDataXPlotAxis->setInterval(0, totalSamplesCount); + else + calibrationRawDataXPlotAxis->setInterval(0, totalSamplesCount); + + if(isPostCalibration) + calibrationDataGraphTabWidget->setCurrentIndex(1); // Set tab to Post Calibration Samples + else + calibrationDataGraphTabWidget->setCurrentIndex(0); // Set tab to Calibration Samples + + clearCalibrateDataButton->setEnabled(false); + QFuture future = QtConcurrent::run(this, &HarmonicCalibration::getCalibrationSamples); + QFutureWatcher *watcher = new QFutureWatcher(this); + + connect(watcher, &QFutureWatcher::finished, this, [=]() { + toggleTabSwitching(true); + toggleMotorControls(true); + + calibrationRawDataPlotChannel->curve()->setSamples(graphDataList); + calibrationRawDataPlotChannel->xAxis()->setMax(graphDataList.size()); + calibrationRawDataPlotWidget->replot(); + isStartMotor = false; + calibrationStartMotorButton->setChecked(false); + clearCalibrateDataButton->setEnabled(true); + + if(isPostCalibration) + { + if(static_cast(graphPostDataList.size()) == totalSamplesCount) + { + computeSineCosineOfAngles(graphPostDataList); + m_admtController->postcalibrate(vector(graphPostDataList.begin(), graphPostDataList.end()), cycleCount, samplesPerCycle); + populateCorrectedAngleErrorGraphs(); + isPostCalibration = false; + isStartMotor = false; + resetToZero = true; + canCalibrate(false); + } + } + else{ + if(static_cast(graphDataList.size()) == totalSamplesCount) + { + computeSineCosineOfAngles(graphDataList); + calibrationLogWrite(m_admtController->calibrate(vector(graphDataList.begin(), graphDataList.end()), cycleCount, samplesPerCycle)); + populateAngleErrorGraphs(); + calculateHarmonicValues(); + canStartMotor(false); + canCalibrate(true); + } + else{ + resetToZero = true; + } + } + }); + connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); + watcher->setFuture(future); +} + +void HarmonicCalibration::startMotorContinuous() +{ + writeMotorAttributeValue(ADMTController::MotorAttribute::ROTATE_VMAX, rotate_vmax); +} + +void HarmonicCalibration::postCalibrateData() +{ + calibrationLogWrite("==== Post Calibration Start ====\n"); + flashHarmonicValues(); + calibrationDataGraphTabWidget->setCurrentIndex(1); + isPostCalibration = true; + isStartMotor = true; + resetToZero = true; + + toggleTabSwitching(false); + toggleMotorControls(false); + + startMotor(); +} + +void HarmonicCalibration::resetAllCalibrationState() +{ + clearCalibrationSamples(); + clearPostCalibrationSamples(); + calibrationDataGraphTabWidget->setCurrentIndex(0); + + clearAngleErrorGraphs(); + clearCorrectedAngleErrorGraphs(); + resultDataTabWidget->setCurrentIndex(0); + + canStartMotor(true); + canCalibrate(false); + calibrateDataButton->setChecked(false); + isPostCalibration = false; + isCalculatedCoeff = false; + resetToZero = true; + displayCalculatedCoeff(); +} + +void HarmonicCalibration::computeSineCosineOfAngles(QVector graphDataList) +{ + m_admtController->computeSineCosineOfAngles(vector(graphDataList.begin(), graphDataList.end())); + if(isPostCalibration){ + postCalibrationSineDataPlotChannel->curve()->setSamples(m_admtController->calibration_samples_sine_scaled.data(), m_admtController->calibration_samples_sine_scaled.size()); + postCalibrationCosineDataPlotChannel->curve()->setSamples(m_admtController->calibration_samples_cosine_scaled.data(), m_admtController->calibration_samples_cosine_scaled.size()); + postCalibrationRawDataPlotWidget->replot(); + } + else{ + calibrationSineDataPlotChannel->curve()->setSamples(m_admtController->calibration_samples_sine_scaled.data(), m_admtController->calibration_samples_sine_scaled.size()); + calibrationCosineDataPlotChannel->curve()->setSamples(m_admtController->calibration_samples_cosine_scaled.data(), m_admtController->calibration_samples_cosine_scaled.size()); + calibrationRawDataPlotWidget->replot(); + } +} + +void HarmonicCalibration::populateAngleErrorGraphs() +{ + QVector angleError = QVector(m_admtController->angleError.begin(), m_admtController->angleError.end()); + QVector FFTAngleErrorMagnitude = QVector(m_admtController->FFTAngleErrorMagnitude.begin(), m_admtController->FFTAngleErrorMagnitude.end()); + QVector FFTAngleErrorPhase = QVector(m_admtController->FFTAngleErrorPhase.begin(), m_admtController->FFTAngleErrorPhase.end()); + + angleErrorPlotChannel->curve()->setSamples(angleError); + auto angleErrorMinMax = minmax_element(angleError.begin(), angleError.end()); + angleErrorYPlotAxis->setInterval(*angleErrorMinMax.first, *angleErrorMinMax.second); + angleErrorXPlotAxis->setInterval(0, angleError.size()); + angleErrorPlotWidget->replot(); + + FFTAngleErrorPhaseChannel->curve()->setSamples(FFTAngleErrorPhase); + FFTAngleErrorMagnitudeChannel->curve()->setSamples(FFTAngleErrorMagnitude); + auto angleErrorMagnitudeMinMax = minmax_element(FFTAngleErrorMagnitude.begin(), FFTAngleErrorMagnitude.end()); + auto angleErrorPhaseMinMax = minmax_element(FFTAngleErrorPhase.begin(), FFTAngleErrorPhase.end()); + double FFTAngleErrorPlotMin = *angleErrorMagnitudeMinMax.first < *angleErrorPhaseMinMax.first ? *angleErrorMagnitudeMinMax.first : *angleErrorPhaseMinMax.first; + double FFTAngleErrorPlotMax = *angleErrorMagnitudeMinMax.second > *angleErrorPhaseMinMax.second ? *angleErrorMagnitudeMinMax.second : *angleErrorPhaseMinMax.second; + FFTAngleErrorYPlotAxis->setInterval(FFTAngleErrorPlotMin, FFTAngleErrorPlotMax); + FFTAngleErrorXPlotAxis->setInterval(0, FFTAngleErrorMagnitude.size()); + FFTAngleErrorPlotWidget->replot(); + + resultDataTabWidget->setCurrentIndex(0); // Set tab to Angle Error +} + +void HarmonicCalibration::populateCorrectedAngleErrorGraphs() +{ + QVector correctedError(m_admtController->correctedError.begin(), m_admtController->correctedError.end()); + QVector FFTCorrectedErrorMagnitude(m_admtController->FFTCorrectedErrorMagnitude.begin(), m_admtController->FFTCorrectedErrorMagnitude.end()); + QVector FFTCorrectedErrorPhase(m_admtController->FFTCorrectedErrorPhase.begin(), m_admtController->FFTCorrectedErrorPhase.end()); + + correctedErrorPlotChannel->curve()->setSamples(correctedError); + auto correctedErrorMagnitudeMinMax = minmax_element(correctedError.begin(), correctedError.end()); + correctedErrorYPlotAxis->setInterval(*correctedErrorMagnitudeMinMax.first, *correctedErrorMagnitudeMinMax.second); + correctedErrorXPlotAxis->setMax(correctedError.size()); + correctedErrorPlotWidget->replot(); + + FFTCorrectedErrorPhaseChannel->curve()->setSamples(FFTCorrectedErrorPhase); + FFTCorrectedErrorMagnitudeChannel->curve()->setSamples(FFTCorrectedErrorMagnitude); + auto FFTCorrectedErrorMagnitudeMinMax = minmax_element(FFTCorrectedErrorMagnitude.begin(), FFTCorrectedErrorMagnitude.end()); + auto FFTCorrectedErrorPhaseMinMax = minmax_element(FFTCorrectedErrorPhase.begin(), FFTCorrectedErrorPhase.end()); + double FFTCorrectedErrorPlotMin = *FFTCorrectedErrorMagnitudeMinMax.first < *FFTCorrectedErrorPhaseMinMax.first ? *FFTCorrectedErrorMagnitudeMinMax.first : *FFTCorrectedErrorPhaseMinMax.first; + double FFTCorrectedErrorPlotMax = *FFTCorrectedErrorMagnitudeMinMax.second > *FFTCorrectedErrorPhaseMinMax.second ? *FFTCorrectedErrorMagnitudeMinMax.second : *FFTCorrectedErrorPhaseMinMax.second; + FFTCorrectedErrorYPlotAxis->setInterval(FFTCorrectedErrorPlotMin, FFTCorrectedErrorPlotMax); + FFTCorrectedErrorXPlotAxis->setMax(FFTCorrectedErrorMagnitude.size()); + FFTCorrectedErrorPlotWidget->replot(); + + resultDataTabWidget->setCurrentIndex(2); // Set tab to Angle Error +} + +void HarmonicCalibration::flashHarmonicValues() +{ + if(changeCNVPage(0x02)){ + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), 0x01, 0x02); + + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H1MAG), + H1_MAG_HEX); + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H1PH), + H1_PHASE_HEX); + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H2MAG), + H2_MAG_HEX); + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H2PH), + H2_PHASE_HEX); + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H3MAG), + H3_MAG_HEX); + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H3PH), + H3_PHASE_HEX); + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H8MAG), + H8_MAG_HEX); + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H8PH), + H8_PHASE_HEX); + + isCalculatedCoeff = true; + displayCalculatedCoeff(); + } + else{ + calibrationLogWrite("Unabled to flash Harmonic Registers!"); + } +} + +void HarmonicCalibration::calculateHarmonicValues() +{ + uint32_t *h1MagCurrent = new uint32_t, + *h1PhaseCurrent = new uint32_t, + *h2MagCurrent = new uint32_t, + *h2PhaseCurrent = new uint32_t, + *h3MagCurrent = new uint32_t, + *h3PhaseCurrent = new uint32_t, + *h8MagCurrent = new uint32_t, + *h8PhaseCurrent = new uint32_t; + + if(changeCNVPage(0x02)) + { + // Read and store current harmonic values + m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H1MAG), h1MagCurrent); + m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H2MAG), h2MagCurrent); + m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H3MAG), h3MagCurrent); + m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H8MAG), h8MagCurrent); + m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H1PH), h1PhaseCurrent); + m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H2PH), h2PhaseCurrent); + m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H3PH), h3PhaseCurrent); + m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), m_admtController->getHarmonicRegister(ADMTController::HarmonicRegister::H8PH), h8PhaseCurrent); + + // Calculate harmonic coefficients (Hex) + H1_MAG_HEX = static_cast(m_admtController->calculateHarmonicCoefficientMagnitude(static_cast(m_admtController->HAR_MAG_1), static_cast(*h1MagCurrent), "h1")); + H2_MAG_HEX = static_cast(m_admtController->calculateHarmonicCoefficientMagnitude(static_cast(m_admtController->HAR_MAG_2), static_cast(*h2MagCurrent), "h2")); + H3_MAG_HEX = static_cast(m_admtController->calculateHarmonicCoefficientMagnitude(static_cast(m_admtController->HAR_MAG_3), static_cast(*h3MagCurrent), "h3")); + H8_MAG_HEX = static_cast(m_admtController->calculateHarmonicCoefficientMagnitude(static_cast(m_admtController->HAR_MAG_8), static_cast(*h8MagCurrent), "h8")); + H1_PHASE_HEX = static_cast(m_admtController->calculateHarmonicCoefficientPhase(static_cast(m_admtController->HAR_PHASE_1), static_cast(*h1PhaseCurrent))); + H2_PHASE_HEX = static_cast(m_admtController->calculateHarmonicCoefficientPhase(static_cast(m_admtController->HAR_PHASE_2), static_cast(*h2PhaseCurrent))); + H3_PHASE_HEX = static_cast(m_admtController->calculateHarmonicCoefficientPhase(static_cast(m_admtController->HAR_PHASE_3), static_cast(*h3PhaseCurrent))); + H8_PHASE_HEX = static_cast(m_admtController->calculateHarmonicCoefficientPhase(static_cast(m_admtController->HAR_PHASE_8), static_cast(*h8PhaseCurrent))); + + calibrationLogWrite(); + calibrationLogWrite(QString("Calculated H1 Mag (Hex): 0x%1").arg(QString::number(H1_MAG_HEX, 16).rightJustified(4, '0'))); + calibrationLogWrite(QString("Calculated H1 Phase (Hex): 0x%1").arg(QString::number(H1_PHASE_HEX, 16).rightJustified(4, '0'))); + calibrationLogWrite(QString("Calculated H2 Mag (Hex): 0x%1").arg(QString::number(H2_MAG_HEX, 16).rightJustified(4, '0'))); + calibrationLogWrite(QString("Calculated H2 Phase (Hex): 0x%1").arg(QString::number(H2_PHASE_HEX, 16).rightJustified(4, '0'))); + calibrationLogWrite(QString("Calculated H3 Mag (Hex): 0x%1").arg(QString::number(H3_MAG_HEX, 16).rightJustified(4, '0'))); + calibrationLogWrite(QString("Calculated H3 Phase (Hex): 0x%1").arg(QString::number(H3_PHASE_HEX, 16).rightJustified(4, '0'))); + calibrationLogWrite(QString("Calculated H8 Mag (Hex): 0x%1").arg(QString::number(H8_MAG_HEX, 16).rightJustified(4, '0'))); + calibrationLogWrite(QString("Calculated H8 Phase (Hex): 0x%1").arg(QString::number(H8_PHASE_HEX, 16).rightJustified(4, '0'))); + + // Get actual harmonic values from hex + H1_MAG_ANGLE = m_admtController->getActualHarmonicRegisterValue(static_cast(H1_MAG_HEX), "h1mag"); + H1_PHASE_ANGLE = m_admtController->getActualHarmonicRegisterValue(static_cast(H1_PHASE_HEX), "h1phase"); + H2_MAG_ANGLE = m_admtController->getActualHarmonicRegisterValue(static_cast(H2_MAG_HEX), "h2mag"); + H2_PHASE_ANGLE = m_admtController->getActualHarmonicRegisterValue(static_cast(H2_PHASE_HEX), "h2phase"); + H3_MAG_ANGLE = m_admtController->getActualHarmonicRegisterValue(static_cast(H3_MAG_HEX), "h3mag"); + H3_PHASE_ANGLE = m_admtController->getActualHarmonicRegisterValue(static_cast(H3_PHASE_HEX), "h3phase"); + H8_MAG_ANGLE = m_admtController->getActualHarmonicRegisterValue(static_cast(H8_MAG_HEX), "h8mag"); + H8_PHASE_ANGLE = m_admtController->getActualHarmonicRegisterValue(static_cast(H8_PHASE_HEX), "h8phase"); + + calibrationLogWrite(); + calibrationLogWrite(QString("Calculated H1 Mag (Angle): 0x%1").arg(QString::number(H1_MAG_ANGLE))); + calibrationLogWrite(QString("Calculated H1 Phase (Angle): 0x%1").arg(QString::number(H1_PHASE_ANGLE))); + calibrationLogWrite(QString("Calculated H2 Mag (Angle): 0x%1").arg(QString::number(H2_MAG_ANGLE))); + calibrationLogWrite(QString("Calculated H2 Phase (Angle): 0x%1").arg(QString::number(H2_PHASE_ANGLE))); + calibrationLogWrite(QString("Calculated H3 Mag (Angle): 0x%1").arg(QString::number(H3_MAG_ANGLE))); + calibrationLogWrite(QString("Calculated H3 Phase (Angle): 0x%1").arg(QString::number(H3_PHASE_ANGLE))); + calibrationLogWrite(QString("Calculated H8 Mag (Angle): 0x%1").arg(QString::number(H8_MAG_ANGLE))); + calibrationLogWrite(QString("Calculated H8 Phase (Angle): 0x%1").arg(QString::number(H8_PHASE_ANGLE))); + + if(isAngleDisplayFormat) updateCalculatedCoeffAngle(); + else updateCalculatedCoeffHex(); + isCalculatedCoeff = true; + } +} + +void HarmonicCalibration::updateCalculatedCoeffAngle() +{ + calibrationH1MagLabel->setText(QString::number(H1_MAG_ANGLE, 'f', 2) + "°"); + calibrationH2MagLabel->setText(QString::number(H2_MAG_ANGLE, 'f', 2) + "°"); + calibrationH3MagLabel->setText(QString::number(H3_MAG_ANGLE, 'f', 2) + "°"); + calibrationH8MagLabel->setText(QString::number(H8_MAG_ANGLE, 'f', 2) + "°"); + calibrationH1PhaseLabel->setText("Φ " + QString::number(H1_PHASE_ANGLE, 'f', 2)); + calibrationH2PhaseLabel->setText("Φ " + QString::number(H2_PHASE_ANGLE, 'f', 2)); + calibrationH3PhaseLabel->setText("Φ " + QString::number(H3_PHASE_ANGLE, 'f', 2)); + calibrationH8PhaseLabel->setText("Φ " + QString::number(H8_PHASE_ANGLE, 'f', 2)); +} + +void HarmonicCalibration::updateCalculatedCoeffHex() +{ + calibrationH1MagLabel->setText(QString("0x%1").arg(H1_MAG_HEX, 4, 16, QChar('0'))); + calibrationH2MagLabel->setText(QString("0x%1").arg(H2_MAG_HEX, 4, 16, QChar('0'))); + calibrationH3MagLabel->setText(QString("0x%1").arg(H3_MAG_HEX, 4, 16, QChar('0'))); + calibrationH8MagLabel->setText(QString("0x%1").arg(H8_MAG_HEX, 4, 16, QChar('0'))); + calibrationH1PhaseLabel->setText(QString("0x%1").arg(H1_PHASE_HEX, 4, 16, QChar('0'))); + calibrationH2PhaseLabel->setText(QString("0x%1").arg(H2_PHASE_HEX, 4, 16, QChar('0'))); + calibrationH3PhaseLabel->setText(QString("0x%1").arg(H3_PHASE_HEX, 4, 16, QChar('0'))); + calibrationH8PhaseLabel->setText(QString("0x%1").arg(H8_PHASE_HEX, 4, 16, QChar('0'))); +} + +void HarmonicCalibration::resetCalculatedCoeffAngle() +{ + calibrationH1MagLabel->setText("--.--°"); + calibrationH2MagLabel->setText("--.--°"); + calibrationH3MagLabel->setText("--.--°"); + calibrationH8MagLabel->setText("--.--°"); + calibrationH1PhaseLabel->setText("Φ --.--"); + calibrationH2PhaseLabel->setText("Φ --.--"); + calibrationH3PhaseLabel->setText("Φ --.--"); + calibrationH8PhaseLabel->setText("Φ --.--"); +} + +void HarmonicCalibration::resetCalculatedCoeffHex() +{ + calibrationH1MagLabel->setText("0x----"); + calibrationH2MagLabel->setText("0x----"); + calibrationH3MagLabel->setText("0x----"); + calibrationH8MagLabel->setText("0x----"); + calibrationH1PhaseLabel->setText("0x----"); + calibrationH2PhaseLabel->setText("0x----"); + calibrationH3PhaseLabel->setText("0x----"); + calibrationH8PhaseLabel->setText("0x----"); +} + +void HarmonicCalibration::displayCalculatedCoeff() +{ + if(isAngleDisplayFormat){ + if(isCalculatedCoeff){ + updateCalculatedCoeffAngle(); + } + else{ + resetCalculatedCoeffAngle(); + } + } + else{ + if(isCalculatedCoeff){ + updateCalculatedCoeffHex(); + } + else{ + resetCalculatedCoeffHex(); + } + } +} + +void HarmonicCalibration::calibrationLogWrite(QString message) +{ + logsPlainTextEdit->appendPlainText(message); +} + +void HarmonicCalibration::importCalibrationData() +{ + QString fileName = QFileDialog::getOpenFileName( + this, tr("Import"), "", + tr("Comma-separated values files (*.csv);;" + "Tab-delimited values files (*.txt)"), + nullptr, QFileDialog::Options()); + + FileManager fm("HarmonicCalibration"); + + try { + fm.open(fileName, FileManager::IMPORT); + + graphDataList = fm.read(0); + if(graphDataList.size() > 0) + { + calibrationRawDataPlotChannel->curve()->setSamples(graphDataList); + calibrationRawDataXPlotAxis->setInterval(0, graphDataList.size()); + calibrationRawDataPlotWidget->replot(); + + computeSineCosineOfAngles(graphDataList); + calibrationLogWrite(m_admtController->calibrate(vector(graphDataList.begin(), graphDataList.end()), cycleCount, samplesPerCycle)); + populateAngleErrorGraphs(); + canStartMotor(false); + canCalibrate(true); + } + } catch(FileManagerException &ex) { + calibrationLogWrite(QString(ex.what())); + } +} + +void HarmonicCalibration::extractCalibrationData() +{ + QStringList filter; + filter += QString(tr("Comma-separated values files (*.csv)")); + filter += QString(tr("Tab-delimited values files (*.txt)")); + filter += QString(tr("All Files(*)")); + + QString selectedFilter = filter[0]; + + QString fileName = QFileDialog::getSaveFileName(this, tr("Export"), "", filter.join(";;"), &selectedFilter, QFileDialog::Options()); + + if(fileName.split(".").size() <= 1) { + QString ext = selectedFilter.split(".")[1].split(")")[0]; + fileName += "." + ext; + } + + if(!fileName.isEmpty()) { + bool withScopyHeader = false; + FileManager fm("HarmonicCalibration"); + fm.open(fileName, FileManager::EXPORT); + + QVector preCalibrationAngleErrorsFFTMagnitude(m_admtController->angle_errors_fft_pre.begin(), m_admtController->angle_errors_fft_pre.end()); + QVector preCalibrationAngleErrorsFFTPhase(m_admtController->angle_errors_fft_phase_pre.begin(), m_admtController->angle_errors_fft_phase_pre.end()); + + QVector h1Mag = { H1_MAG_ANGLE }; + QVector h2Mag = { H2_MAG_ANGLE }; + QVector h3Mag = { H3_MAG_ANGLE }; + QVector h8Mag = { H8_MAG_ANGLE }; + QVector h1Phase = { H1_PHASE_ANGLE }; + QVector h2Phase = { H2_PHASE_ANGLE }; + QVector h3Phase = { H3_PHASE_ANGLE }; + QVector h8Phase = { H8_PHASE_ANGLE }; + + fm.save(graphDataList, "Raw Data"); + fm.save(preCalibrationAngleErrorsFFTMagnitude, "Pre-Calibration Angle Errors FFT Magnitude"); + fm.save(preCalibrationAngleErrorsFFTPhase, "Pre-Calibration Angle Errors FFT Phase"); + fm.save(h1Mag, "H1 Mag"); + fm.save(h2Mag, "H2 Mag"); + fm.save(h3Mag, "H3 Mag"); + fm.save(h8Mag, "H8 Mag"); + fm.save(h1Phase, "H1 Phase"); + fm.save(h2Phase, "H2 Phase"); + fm.save(h3Phase, "H3 Phase"); + fm.save(h8Phase, "H8 Phase"); + + fm.performWrite(withScopyHeader); + } +} + +void HarmonicCalibration::toggleTabSwitching(bool value) +{ + tabWidget->setTabEnabled(0, value); + tabWidget->setTabEnabled(2, value); + tabWidget->setTabEnabled(3, value); +} + +void HarmonicCalibration::canStartMotor(bool value) +{ + calibrationStartMotorButton->setEnabled(value); +} + +void HarmonicCalibration::canCalibrate(bool value) +{ + calibrateDataButton->setEnabled(value); +} + +void HarmonicCalibration::toggleMotorControls(bool value) +{ + motorMaxVelocitySpinBox->setEnabled(value); + motorAccelTimeSpinBox->setEnabled(value); + motorMaxDisplacementSpinBox->setEnabled(value); + m_calibrationMotorRampModeMenuCombo->setEnabled(value); + motorTargetPositionLineEdit->setEnabled(value); +} + +void HarmonicCalibration::clearCalibrationSamples() +{ + graphDataList.clear(); + calibrationRawDataPlotChannel->curve()->setData(nullptr); + calibrationSineDataPlotChannel->curve()->setData(nullptr); + calibrationCosineDataPlotChannel->curve()->setData(nullptr); + calibrationRawDataPlotWidget->replot(); +} + +void HarmonicCalibration::clearCalibrationSineCosine() +{ + calibrationSineDataPlotChannel->curve()->setData(nullptr); + calibrationCosineDataPlotChannel->curve()->setData(nullptr); + calibrationRawDataPlotWidget->replot(); +} + +void HarmonicCalibration::clearPostCalibrationSamples() +{ + graphPostDataList.clear(); + postCalibrationRawDataPlotChannel->curve()->setData(nullptr); + postCalibrationSineDataPlotChannel->curve()->setData(nullptr); + postCalibrationCosineDataPlotChannel->curve()->setData(nullptr); + postCalibrationRawDataPlotWidget->replot(); +} + +void HarmonicCalibration::clearAngleErrorGraphs() +{ + angleErrorPlotChannel->curve()->setData(nullptr); + angleErrorPlotWidget->replot(); + FFTAngleErrorMagnitudeChannel->curve()->setData(nullptr); + FFTAngleErrorPhaseChannel->curve()->setData(nullptr); + FFTAngleErrorPlotWidget->replot(); +} + +void HarmonicCalibration::clearCorrectedAngleErrorGraphs() +{ + correctedErrorPlotChannel->curve()->setData(nullptr); + correctedErrorPlotWidget->replot(); + FFTCorrectedErrorMagnitudeChannel->curve()->setData(nullptr); + FFTCorrectedErrorPhaseChannel->curve()->setData(nullptr); + FFTCorrectedErrorPlotWidget->replot(); +} +#pragma endregion + +#pragma region Motor Methods +bool HarmonicCalibration::moveMotorToPosition(double& position, bool validate) +{ + bool success = false; + bool canRead = true; + if(writeMotorAttributeValue(ADMTController::MotorAttribute::TARGET_POS, position) == 0){ + if(validate){ + if(readMotorAttributeValue(ADMTController::MotorAttribute::CURRENT_POS, current_pos) == 0) + { + while(target_pos != current_pos && canRead) { + canRead = readMotorAttributeValue(ADMTController::MotorAttribute::CURRENT_POS, current_pos) == 0 ? true : false; + } + if(canRead) success = true; + } + } + } + + return success; +} + +bool HarmonicCalibration::resetCurrentPositionToZero() +{ + bool success = false; + if(readMotorAttributeValue(ADMTController::MotorAttribute::CURRENT_POS, current_pos) == 0) + { + if(current_pos != 0 && + writeMotorAttributeValue(ADMTController::MotorAttribute::TARGET_POS, 0) == 0 && + readMotorAttributeValue(ADMTController::MotorAttribute::CURRENT_POS, current_pos) == 0) + { + while(current_pos != 0){ + if(readMotorAttributeValue(ADMTController::MotorAttribute::CURRENT_POS, current_pos) != 0) break; + QThread::msleep(readMotorDebounce); + } + if(current_pos == 0) + { + resetToZero = false; + success = true; + } + } + else{ + success = true; + } + } + + return success; +} + +void HarmonicCalibration::stopMotor() +{ + writeMotorAttributeValue(ADMTController::MotorAttribute::DISABLE, 1); +} + +int HarmonicCalibration::readMotorAttributeValue(ADMTController::MotorAttribute attribute, double& value) +{ + int result = -1; + if(!isDebug){ + result = m_admtController->getDeviceAttributeValue(m_admtController->getDeviceId(ADMTController::Device::TMC5240), + m_admtController->getMotorAttribute(attribute), + &value); + } + return result; +} + +int HarmonicCalibration::writeMotorAttributeValue(ADMTController::MotorAttribute attribute, double value) +{ + int result = -1; + if(!isDebug){ + result = m_admtController->setDeviceAttributeValue(m_admtController->getDeviceId(ADMTController::Device::TMC5240), + m_admtController->getMotorAttribute(attribute), + value); + } + return result; +} +#pragma endregion + +#pragma region Utility Methods +void HarmonicCalibration::startUtilityTask() +{ + isUtilityTab = true; + m_utilityThread = QtConcurrent::run(this, &HarmonicCalibration::utilityTask, utilityUITimerRate); + m_utilityWatcher.setFuture(m_utilityThread); +} + +void HarmonicCalibration::stopUtilityTask() +{ + isUtilityTab = false; + if(m_utilityThread.isRunning()) + { + m_utilityThread.cancel(); + m_utilityWatcher.waitForFinished(); + } +} + +void HarmonicCalibration::utilityTask(int sampleRate){ + while(isUtilityTab) + { + getDIGIOENRegister(); + getFAULTRegister(); + if(hasMTDiagnostics) + { + getDIAG1Register(); + getDIAG2Register(); + } + + Q_EMIT commandLogWriteSignal(""); + + QThread::msleep(sampleRate); + } +} + +void HarmonicCalibration::toggleUtilityTask(bool run) +{ + if(run) + { + startUtilityTask(); + } + else + { + stopUtilityTask(); + } +} + +void HarmonicCalibration::getDIGIOENRegister(){ + uint32_t *digioRegValue = new uint32_t; + uint32_t digioEnPage = m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::DIGIOEN); + if(changeCNVPage(digioEnPage)) + { + uint32_t digioRegisterAddress = m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::DIGIOEN); + + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), digioRegisterAddress, digioRegValue) != -1){ + + Q_EMIT DIGIORegisterChanged(digioRegValue); + + Q_EMIT commandLogWriteSignal("DIGIOEN: 0b" + QString::number(static_cast(*digioRegValue), 2).rightJustified(16, '0')); + } + else{ Q_EMIT commandLogWriteSignal("Failed to read DIGIOEN Register"); } + } + + delete digioRegValue; +} + +void HarmonicCalibration::updateDIGIOUI(uint32_t *registerValue) +{ + DIGIOENRegisterMap = m_admtController->getDIGIOENRegisterBitMapping(static_cast(*registerValue)); + updateDIGIOMonitorUI(); + updateDIGIOControlUI(); +} + +void HarmonicCalibration::updateDIGIOMonitorUI() +{ + map registerMap = DIGIOENRegisterMap; + if(!registerMap.at("BUSY")){ + changeStatusLEDColor(DIGIOBusyStatusLED, json::theme::content_success); + DIGIOBusyStatusLED->setText("BUSY (Output)"); + } + else{ + changeStatusLEDColor(DIGIOBusyStatusLED, json::theme::interactive_primary_idle); + if(registerMap.at("DIGIO0EN")) DIGIOBusyStatusLED->setText("GPIO0 (Output)"); + else DIGIOBusyStatusLED->setText("GPIO0 (Input)"); + } + + if(!registerMap.at("CNV")){ + changeStatusLEDColor(DIGIOCNVStatusLED, json::theme::content_success); + DIGIOCNVStatusLED->setText("CNV (Input)"); + } + else{ + changeStatusLEDColor(DIGIOCNVStatusLED, json::theme::interactive_primary_idle); + if(registerMap.at("DIGIO1EN")) DIGIOCNVStatusLED->setText("GPIO1 (Output)"); + else DIGIOCNVStatusLED->setText("GPIO1 (Input)"); + } + + if(!registerMap.at("SENT")){ + changeStatusLEDColor(DIGIOSENTStatusLED, json::theme::content_success); + DIGIOSENTStatusLED->setText("SENT (Output)"); + } + else{ + changeStatusLEDColor(DIGIOSENTStatusLED, json::theme::interactive_primary_idle); + if(registerMap.at("DIGIO2EN")) DIGIOSENTStatusLED->setText("GPIO2 (Output)"); + else DIGIOSENTStatusLED->setText("GPIO2 (Input)"); + } + + if(!registerMap.at("ACALC")){ + changeStatusLEDColor(DIGIOACALCStatusLED, json::theme::content_success); + DIGIOACALCStatusLED->setText("ACALC (Output)"); + } + else{ + changeStatusLEDColor(DIGIOACALCStatusLED, json::theme::interactive_primary_idle); + if(registerMap.at("DIGIO3EN")) DIGIOACALCStatusLED->setText("GPIO3 (Output)"); + else DIGIOACALCStatusLED->setText("GPIO3 (Input)"); + } + + if(!registerMap.at("FAULT")){ + changeStatusLEDColor(DIGIOFaultStatusLED, json::theme::content_success); + DIGIOFaultStatusLED->setText("FAULT (Output)"); + } + else{ + changeStatusLEDColor(DIGIOFaultStatusLED, json::theme::interactive_primary_idle); + if(registerMap.at("DIGIO4EN")) DIGIOFaultStatusLED->setText("GPIO4 (Output)"); + else DIGIOFaultStatusLED->setText("GPIO4 (Input)"); + } + + if(!registerMap.at("BOOTLOAD")){ + changeStatusLEDColor(DIGIOBootloaderStatusLED, json::theme::content_success); + DIGIOBootloaderStatusLED->setText("BOOTLOAD (Output)"); + } + else{ + changeStatusLEDColor(DIGIOBootloaderStatusLED, json::theme::interactive_primary_idle); + if(registerMap.at("DIGIO5EN")) DIGIOBootloaderStatusLED->setText("GPIO5 (Output)"); + else DIGIOBootloaderStatusLED->setText("GPIO5 (Input)"); + } +} + +void HarmonicCalibration::updateDIGIOControlUI() +{ + map registerMap = DIGIOENRegisterMap; + + DIGIO0ENToggleSwitch->setChecked(registerMap.at("DIGIO0EN")); + DIGIO1ENToggleSwitch->setChecked(registerMap.at("DIGIO1EN")); + DIGIO2ENToggleSwitch->setChecked(registerMap.at("DIGIO2EN")); + DIGIO3ENToggleSwitch->setChecked(registerMap.at("DIGIO3EN")); + DIGIO4ENToggleSwitch->setChecked(registerMap.at("DIGIO4EN")); + DIGIO5ENToggleSwitch->setChecked(registerMap.at("DIGIO5EN")); + DIGIO0FNCToggleSwitch->setChecked(registerMap.at("BUSY")); + DIGIO1FNCToggleSwitch->setChecked(registerMap.at("CNV")); + DIGIO2FNCToggleSwitch->setChecked(registerMap.at("SENT")); + DIGIO3FNCToggleSwitch->setChecked(registerMap.at("ACALC")); + DIGIO4FNCToggleSwitch->setChecked(registerMap.at("FAULT")); + DIGIO5FNCToggleSwitch->setChecked(registerMap.at("BOOTLOAD")); +} + +void HarmonicCalibration::getDIAG2Register(){ + uint32_t *mtDiag2RegValue = new uint32_t; + uint32_t *cnvPageRegValue = new uint32_t; + + uint32_t mtDiag2RegisterAddress = m_admtController->getSensorRegister(ADMTController::SensorRegister::DIAG2); + + uint32_t mtDiag2PageValue = m_admtController->getSensorPage(ADMTController::SensorRegister::DIAG2); + + uint32_t cnvPageAddress = m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::CNVPAGE); + + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), cnvPageAddress, mtDiag2PageValue) != -1){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), cnvPageAddress, cnvPageRegValue) != -1){ + if(*cnvPageRegValue == mtDiag2PageValue){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), mtDiag2RegisterAddress, mtDiag2RegValue) != -1){ + + Q_EMIT DIAG2RegisterChanged(mtDiag2RegValue); + + Q_EMIT commandLogWriteSignal("DIAG2: 0b" + QString::number(static_cast(*mtDiag2RegValue), 2).rightJustified(16, '0')); + } + else{ Q_EMIT commandLogWriteSignal("Failed to read MT Diagnostic 2 Register"); } + } + else{ Q_EMIT commandLogWriteSignal("CNVPAGE for MT Diagnostic 2 is a different value, abort reading"); } + } + else{ Q_EMIT commandLogWriteSignal("Failed to read CNVPAGE for MT Diagnostic 2"); } + } + else{ Q_EMIT commandLogWriteSignal("Failed to write CNVPAGE for MT Diagnostic 2"); } + + delete mtDiag2RegValue; + delete cnvPageRegValue; +} + +void HarmonicCalibration::updateMTDiagnosticsUI(uint32_t *registerValue) +{ + DIAG2RegisterMap = m_admtController->getDiag2RegisterBitMapping(static_cast(*registerValue)); + + map regmap = DIAG2RegisterMap; + afeDiag0 = regmap.at("AFE Diagnostic 0 (-57%)"); + afeDiag1 = regmap.at("AFE Diagnostic 1 (+57%)"); + AFEDIAG0LineEdit->setText(QString::number(afeDiag0) + " V"); + AFEDIAG1LineEdit->setText(QString::number(afeDiag1) + " V"); +} + +void HarmonicCalibration::getDIAG1Register(){ + uint32_t *mtDiag1RegValue = new uint32_t; + uint32_t *cnvPageRegValue = new uint32_t; + uint32_t mtDiag1RegisterAddress = m_admtController->getSensorRegister(ADMTController::SensorRegister::DIAG1); + uint32_t mtDiag1PageValue = m_admtController->getSensorPage(ADMTController::SensorRegister::DIAG1); + uint32_t cnvPageAddress = m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::CNVPAGE); + + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), cnvPageAddress, mtDiag1PageValue) != -1){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), cnvPageAddress, cnvPageRegValue) != -1){ + if(*cnvPageRegValue == mtDiag1PageValue){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), mtDiag1RegisterAddress, mtDiag1RegValue) != -1){ + + Q_EMIT DIAG1RegisterChanged(mtDiag1RegValue); + + Q_EMIT commandLogWriteSignal("DIAG1: 0b" + QString::number(static_cast(*mtDiag1RegValue), 2).rightJustified(16, '0')); + } + else{ Q_EMIT commandLogWriteSignal("Failed to read MT Diagnostic 1 Register"); } + } + else{ Q_EMIT commandLogWriteSignal("CNVPAGE for MT Diagnostic 1 is a different value, abort reading"); } + } + else{ Q_EMIT commandLogWriteSignal("Failed to read CNVPAGE for MT Diagnostic 1"); } + } + else{ Q_EMIT commandLogWriteSignal("Failed to write CNVPAGE for MT Diagnostic 1"); } + + delete mtDiag1RegValue; + delete cnvPageRegValue; +} + +void HarmonicCalibration::updateMTDiagnosticRegisterUI(uint32_t *registerValue) +{ + DIAG1RegisterMap = m_admtController->getDiag1RegisterBitMapping_Register(static_cast(*registerValue)); + DIAG1AFERegisterMap = m_admtController->getDiag1RegisterBitMapping_Afe(static_cast(*registerValue), is5V); + + map regmap = DIAG1RegisterMap; + map afeRegmap = DIAG1AFERegisterMap; + toggleStatusLEDColor(R0StatusLED, json::theme::content_success, json::theme::background_primary, DIAG1RegisterMap.at("R0")); + toggleStatusLEDColor(R1StatusLED, json::theme::content_success, json::theme::background_primary, DIAG1RegisterMap.at("R1")); + toggleStatusLEDColor(R2StatusLED, json::theme::content_success, json::theme::background_primary, DIAG1RegisterMap.at("R2")); + toggleStatusLEDColor(R3StatusLED, json::theme::content_success, json::theme::background_primary, DIAG1RegisterMap.at("R3")); + toggleStatusLEDColor(R4StatusLED, json::theme::content_success, json::theme::background_primary, DIAG1RegisterMap.at("R4")); + toggleStatusLEDColor(R5StatusLED, json::theme::content_success, json::theme::background_primary, DIAG1RegisterMap.at("R5")); + toggleStatusLEDColor(R6StatusLED, json::theme::content_success, json::theme::background_primary, DIAG1RegisterMap.at("R6")); + toggleStatusLEDColor(R7StatusLED, json::theme::content_success, json::theme::background_primary, DIAG1RegisterMap.at("R7")); + afeDiag2 = afeRegmap.at("AFE Diagnostic 2"); + AFEDIAG2LineEdit->setText(QString::number(afeDiag2) + " V"); +} + +void HarmonicCalibration::getFAULTRegister(){ + uint32_t *faultRegValue = new uint32_t; + uint32_t faultRegisterAddress = m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::FAULT); + m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), faultRegisterAddress, 0); // Write all zeros to fault before read + m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), faultRegisterAddress, faultRegValue); + + if(*faultRegValue != -1){ + Q_EMIT FaultRegisterChanged(faultRegValue); + + Q_EMIT commandLogWriteSignal("FAULT: 0b" + QString::number(static_cast(*faultRegValue), 2).rightJustified(16, '0')); + } + else{ Q_EMIT commandLogWriteSignal("Failed to read FAULT Register"); } + + delete faultRegValue; +} + +void HarmonicCalibration::updateFaultRegisterUI(uint32_t *faultRegValue) +{ + FAULTRegisterMap = m_admtController->getFaultRegisterBitMapping(static_cast(*faultRegValue)); + + map regmap = FAULTRegisterMap; + toggleStatusLEDColor(VDDUnderVoltageStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("VDD Under Voltage")); + toggleStatusLEDColor(VDDOverVoltageStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("VDD Over Voltage")); + toggleStatusLEDColor(VDRIVEUnderVoltageStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("VDRIVE Under Voltage")); + toggleStatusLEDColor(VDRIVEOverVoltageStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("VDRIVE Over Voltage")); + toggleStatusLEDColor(AFEDIAGStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("AFE Diagnostic")); + toggleStatusLEDColor(NVMCRCFaultStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("NVM CRC Fault")); + toggleStatusLEDColor(ECCDoubleBitErrorStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("ECC Double Bit Error")); + toggleStatusLEDColor(OscillatorDriftStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("Oscillator Drift")); + toggleStatusLEDColor(CountSensorFalseStateStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("Count Sensor False State")); + toggleStatusLEDColor(AngleCrossCheckStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("Angle Cross Check")); + toggleStatusLEDColor(TurnCountSensorLevelsStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("Turn Count Sensor Levels")); + toggleStatusLEDColor(MTDIAGStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("MT Diagnostic")); + toggleStatusLEDColor(TurnCounterCrossCheckStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("Turn Counter Cross Check")); + toggleStatusLEDColor(RadiusCheckStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("AMR Radius Check")); + toggleStatusLEDColor(SequencerWatchdogStatusLED, json::theme::content_error, json::theme::background_primary, regmap.at("Sequencer Watchdog")); +} + +void HarmonicCalibration::toggleDIGIOEN(string DIGIOENName, bool value) +{ + toggleUtilityTask(false); + + uint32_t *DIGIOENRegisterValue = new uint32_t; + uint32_t DIGIOENPage = m_admtController->getConfigurationPage(ADMTController::ConfigurationRegister::DIGIOEN); + + if(changeCNVPage(DIGIOENPage)) + { + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::DIGIOEN), + DIGIOENRegisterValue) != -1) + { + map DIGIOSettings = m_admtController->getDIGIOENRegisterBitMapping(static_cast(*DIGIOENRegisterValue)); + + DIGIOSettings.at(DIGIOENName) = value; + + uint16_t newRegisterValue = m_admtController->setDIGIOENRegisterBitMapping(static_cast(*DIGIOENRegisterValue), DIGIOSettings); + + if(changeCNVPage(DIGIOENPage)){ + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::DIGIOEN), + static_cast(newRegisterValue)) != -1) + { + updateDIGIOControlUI(); + } + } + + } + } + + toggleUtilityTask(true); +} + +void HarmonicCalibration::toggleMTDiagnostics(int mode) +{ + switch(mode){ + case 0: + MTDiagnosticsScrollArea->hide(); + hasMTDiagnostics = false; + break; + case 1: + MTDiagnosticsScrollArea->show(); + hasMTDiagnostics = true; + break; + } +} + +void HarmonicCalibration::toggleFaultRegisterMode(int mode) +{ + switch(mode){ + case 0: + AFEDIAGStatusLED->hide(); + OscillatorDriftStatusLED->hide(); + AngleCrossCheckStatusLED->hide(); + TurnCountSensorLevelsStatusLED->hide(); + MTDIAGStatusLED->hide(); + SequencerWatchdogStatusLED->hide(); + break; + case 1: + AFEDIAGStatusLED->show(); + OscillatorDriftStatusLED->show(); + AngleCrossCheckStatusLED->show(); + TurnCountSensorLevelsStatusLED->show(); + MTDIAGStatusLED->show(); + SequencerWatchdogStatusLED->show(); + break; + } +} + +bool HarmonicCalibration::resetDIGIO() +{ + return (m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getConfigurationRegister(ADMTController::ConfigurationRegister::DIGIOEN), + 0x241b) == 0 ? true : false); +} + +void HarmonicCalibration::commandLogWrite(QString message) +{ + commandLogPlainTextEdit->appendPlainText(message); +} + +void HarmonicCalibration::clearCommandLog(){ + commandLogPlainTextEdit->clear(); +} +#pragma endregion + +#pragma region Register Methods +void HarmonicCalibration::readAllRegisters() +{ + readAllRegistersButton->setEnabled(false); + readAllRegistersButton->setText(QString("Reading Registers...")); + QTimer::singleShot(1000, this, [this](){ + readAllRegistersButton->setEnabled(true); + readAllRegistersButton->setText(QString("Read All Registers")); + }); + + cnvPageRegisterBlock->readButton()->click(); + digIORegisterBlock->readButton()->click(); + faultRegisterBlock->readButton()->click(); + generalRegisterBlock->readButton()->click(); + digIOEnRegisterBlock->readButton()->click(); + eccDcdeRegisterBlock->readButton()->click(); + eccDisRegisterBlock->readButton()->click(); + absAngleRegisterBlock->readButton()->click(); + angleRegisterBlock->readButton()->click(); + sineRegisterBlock->readButton()->click(); + cosineRegisterBlock->readButton()->click(); + tmp0RegisterBlock->readButton()->click(); + cnvCntRegisterBlock->readButton()->click(); + uniqID0RegisterBlock->readButton()->click(); + uniqID1RegisterBlock->readButton()->click(); + uniqID2RegisterBlock->readButton()->click(); + uniqID3RegisterBlock->readButton()->click(); + h1MagRegisterBlock->readButton()->click(); + h1PhRegisterBlock->readButton()->click(); + h2MagRegisterBlock->readButton()->click(); + h2PhRegisterBlock->readButton()->click(); + h3MagRegisterBlock->readButton()->click(); + h3PhRegisterBlock->readButton()->click(); + h8MagRegisterBlock->readButton()->click(); + h8PhRegisterBlock->readButton()->click(); + + if(generalRegisterMap.at("Sequence Type") == 1){ + angleSecRegisterBlock->readButton()->click(); + secAnglIRegisterBlock->readButton()->click(); + secAnglQRegisterBlock->readButton()->click(); + tmp1RegisterBlock->readButton()->click(); + angleCkRegisterBlock->readButton()->click(); + radiusRegisterBlock->readButton()->click(); + diag1RegisterBlock->readButton()->click(); + diag2RegisterBlock->readButton()->click(); + } +} + +void HarmonicCalibration::toggleRegisters(int mode) +{ + switch(mode){ + case 0: + angleSecRegisterBlock->hide(); + secAnglIRegisterBlock->hide(); + secAnglQRegisterBlock->hide(); + tmp1RegisterBlock->hide(); + angleCkRegisterBlock->hide(); + radiusRegisterBlock->hide(); + diag1RegisterBlock->hide(); + diag2RegisterBlock->hide(); + break; + case 1: + angleSecRegisterBlock->show(); + secAnglIRegisterBlock->show(); + secAnglQRegisterBlock->show(); + tmp1RegisterBlock->show(); + angleCkRegisterBlock->show(); + radiusRegisterBlock->show(); + diag1RegisterBlock->show(); + diag2RegisterBlock->show(); + break; + } +} +#pragma endregion + +#pragma region UI Helper Methods +void HarmonicCalibration::updateLabelValue(QLabel* label, int channelIndex) +{ + switch(channelIndex) + { + case ADMTController::Channel::ROTATION: + label->setText(QString("%1").arg(rotation, 0, 'f', 2) + "°"); + break; + case ADMTController::Channel::ANGLE: + label->setText(QString("%1").arg(angle, 0, 'f', 2) + "°"); + break; + case ADMTController::Channel::COUNT: + label->setText(QString::number(count)); + break; + case ADMTController::Channel::TEMPERATURE: + label->setText(QString("%1").arg(temp, 0, 'f', 2) + "°C"); + break; + } +} + +void HarmonicCalibration::updateLabelValue(QLabel *label, ADMTController::MotorAttribute attribute) +{ + switch(attribute) + { + case ADMTController::MotorAttribute::AMAX: + label->setText(QString::number(amax)); + break; + case ADMTController::MotorAttribute::ROTATE_VMAX: + label->setText(QString::number(rotate_vmax)); + break; + case ADMTController::MotorAttribute::DMAX: + label->setText(QString::number(dmax)); + break; + case ADMTController::MotorAttribute::DISABLE: + label->setText(QString::number(disable)); + break; + case ADMTController::MotorAttribute::TARGET_POS: + label->setText(QString::number(target_pos)); + break; + case ADMTController::MotorAttribute::CURRENT_POS: + label->setText(QString::number(current_pos)); + break; + case ADMTController::MotorAttribute::RAMP_MODE: + label->setText(QString::number(ramp_mode)); + break; + } +} + +bool HarmonicCalibration::updateChannelValue(int channelIndex) +{ + bool success = false; + switch(channelIndex) + { + case ADMTController::Channel::ROTATION: + rotation = m_admtController->getChannelValue(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), rotationChannelName, 1); + if(rotation == static_cast(UINT64_MAX)) { success = false; } + break; + case ADMTController::Channel::ANGLE: + angle = m_admtController->getChannelValue(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), angleChannelName, 1); + if(angle == static_cast(UINT64_MAX)) { success = false; } + break; + case ADMTController::Channel::COUNT: + count = m_admtController->getChannelValue(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), countChannelName, 1); + if(count == static_cast(UINT64_MAX)) { success = false; } + break; + case ADMTController::Channel::TEMPERATURE: + temp = m_admtController->getChannelValue(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), temperatureChannelName, 1); + if(temp == static_cast(UINT64_MAX)) { success = false; } + break; + } + return success; +} + +void HarmonicCalibration::updateLineEditValue(QLineEdit* lineEdit, double value){ + if(value == static_cast(UINT64_MAX)) { lineEdit->setText("N/A"); } + else { lineEdit->setText(QString::number(value)); } +} + +void HarmonicCalibration::toggleWidget(QPushButton *widget, bool value){ + widget->setEnabled(value); +} + +void HarmonicCalibration::changeCustomSwitchLabel(CustomSwitch *customSwitch, QString onLabel, QString offLabel) +{ + customSwitch->setOnText(onLabel); + customSwitch->setOffText(offLabel); +} + +void HarmonicCalibration::changeStatusLEDColor(MenuControlButton *menuControlButton, QColor color, bool checked) +{ + menuControlButton->setColor(color); + menuControlButton->checkBox()->setChecked(checked); +} + +void HarmonicCalibration::changeStatusLEDColor(QCheckBox *widget, const char *colorAttribute) +{ + Style::setStyle(widget, style::properties::admt::checkBoxLED, true, true); + QString style = QString(R"css( + QCheckBox::indicator:checked { + background-color: &&LEDColor&&; + } + )css"); + style.replace("&&LEDColor&&", Style::getAttribute(colorAttribute)); + widget->setStyleSheet(widget->styleSheet() + "\n" + style); + widget->update(); +} + +void HarmonicCalibration::updateFaultStatusLEDColor(MenuControlButton *widget, bool value) +{ + if(value) changeStatusLEDColor(widget, faultLEDColor); + else changeStatusLEDColor(widget, statusLEDColor); +} + +void HarmonicCalibration::toggleStatusLEDColor(QCheckBox *widget, const char *trueAttribute, const char* falseAttribute, bool value) +{ + if(value) changeStatusLEDColor(widget, trueAttribute); + else changeStatusLEDColor(widget, falseAttribute); +} + +MenuControlButton *HarmonicCalibration::createStatusLEDWidget(const QString title, QColor color, QWidget *parent) +{ + MenuControlButton *menuControlButton = new MenuControlButton(parent); + menuControlButton->setName(title); + menuControlButton->setCheckBoxStyle(MenuControlButton::CheckboxStyle::CS_CIRCLE); + menuControlButton->setOpenMenuChecksThis(true); + menuControlButton->setDoubleClickToOpenMenu(true); + menuControlButton->setColor(color); + menuControlButton->button()->setVisible(false); + menuControlButton->setCheckable(true); + menuControlButton->checkBox()->setChecked(false); + menuControlButton->setEnabled(false); + menuControlButton->layout()->setMargin(8); + return menuControlButton; +} + +QCheckBox *HarmonicCalibration::createStatusLEDWidget(const QString &text, const char *colorAttribute, bool checked, QWidget *parent) +{ + QCheckBox *checkBox = new QCheckBox(text, parent); + Style::setStyle(checkBox, style::properties::admt::checkBoxLED, true, true); + checkBox->setChecked(checked); + checkBox->setEnabled(false); + return checkBox; +} + +MenuControlButton *HarmonicCalibration::createChannelToggleWidget(const QString title, QColor color, QWidget *parent) +{ + MenuControlButton *menuControlButton = new MenuControlButton(parent); + menuControlButton->setName(title); + menuControlButton->setCheckBoxStyle(MenuControlButton::CheckboxStyle::CS_CIRCLE); + menuControlButton->setOpenMenuChecksThis(true); + menuControlButton->setDoubleClickToOpenMenu(true); + menuControlButton->setColor(color); + menuControlButton->button()->setVisible(false); + menuControlButton->setCheckable(false); + menuControlButton->checkBox()->setChecked(true); + menuControlButton->layout()->setMargin(0); + return menuControlButton; +} +#pragma endregion + +#pragma region Connect Methods +void HarmonicCalibration::connectLineEditToNumber(QLineEdit* lineEdit, int& variable, int min, int max) +{ + QIntValidator *validator = new QIntValidator(min, max, this); + lineEdit->setValidator(validator); + connect(lineEdit, &QLineEdit::editingFinished, this, [&variable, lineEdit, min, max]() { + bool ok; + int value = lineEdit->text().toInt(&ok); + if (ok && value >= min && value <= max) { + variable = value; + } else { + lineEdit->setText(QString::number(variable)); + } + }); +} + +void HarmonicCalibration::connectLineEditToNumber(QLineEdit* lineEdit, double& variable, QString unit) +{ + connect(lineEdit, &QLineEdit::editingFinished, this, [&variable, lineEdit, unit]() { + bool ok; + double value = lineEdit->text().replace(unit, "").trimmed().toDouble(&ok); + if (ok) { + variable = value; + + } else { + lineEdit->setText(QString::number(variable) + " " + unit); + } + }); +} + +void HarmonicCalibration::connectLineEditToDouble(QLineEdit* lineEdit, double& variable) +{ + connect(lineEdit, &QLineEdit::editingFinished, this, [&variable, lineEdit]() { + bool ok; + double value = lineEdit->text().toDouble(&ok); + if (ok){ + variable = value; + } else { + lineEdit->setText(QString::number(variable, 'f', 2)); + } + }); +} + +void HarmonicCalibration::connectLineEditToNumberWrite(QLineEdit* lineEdit, double& variable, ADMTController::MotorAttribute attribute) +{ + QDoubleValidator *validator = new QDoubleValidator(this); + validator->setNotation(QDoubleValidator::StandardNotation); + lineEdit->setValidator(validator); + connect(lineEdit, &QLineEdit::editingFinished, [=, &variable]() { + bool ok; + double value = lineEdit->text().toDouble(&ok); + if (ok) { + variable = value; + writeMotorAttributeValue(attribute, variable); + + } else { + lineEdit->setText(QString::number(variable)); + } + }); +} + +void HarmonicCalibration::connectMenuComboToNumber(MenuCombo* menuCombo, double& variable) +{ + QComboBox *combo = menuCombo->combo(); + connect(combo, QOverload::of(&QComboBox::currentIndexChanged), [=, &variable]() { + variable = qvariant_cast(combo->currentData()); + }); +} + +void HarmonicCalibration::connectMenuComboToNumber(MenuCombo* menuCombo, int& variable) +{ + QComboBox *combo = menuCombo->combo(); + connect(combo, QOverload::of(&QComboBox::currentIndexChanged), [=, &variable]() { + variable = qvariant_cast(combo->currentData()); + }); +} + +void HarmonicCalibration::connectLineEditToRPSConversion(QLineEdit* lineEdit, double& vmax) +{ + connect(lineEdit, &QLineEdit::editingFinished, [=, &vmax]() { + bool ok; + double rps = lineEdit->text().toDouble(&ok); + if (ok) { + vmax = convertRPStoVMAX(rps); + writeMotorAttributeValue(ADMTController::MotorAttribute::ROTATE_VMAX, vmax); + writeMotorAttributeValue(ADMTController::MotorAttribute::DISABLE, 1); + amax = convertAccelTimetoAMAX(motorAccelTimeSpinBox->lineEdit()->text().toDouble()); + writeMotorAttributeValue(ADMTController::MotorAttribute::AMAX, amax); + } else { + lineEdit->setText(QString::number(convertVMAXtoRPS(vmax))); + } + }); +} + +void HarmonicCalibration::connectLineEditToAMAXConversion(QLineEdit* lineEdit, double& amax) +{ + connect(lineEdit, &QLineEdit::editingFinished, [=, &amax]() { + bool ok; + double accelTime = lineEdit->text().toDouble(&ok); + if (ok) { + amax = convertAccelTimetoAMAX(accelTime); + } else { + lineEdit->setText(QString::number(convertAMAXtoAccelTime(amax))); + } + }); +} + +void HarmonicCalibration::connectRegisterBlockToRegistry(RegisterBlockWidget* widget) +{ + uint32_t *readValue = new uint32_t; + connect(widget->readButton(), &QPushButton::clicked, this, [=]{ + bool ok = false, success = false; + + if(widget->getCnvPage() != UINT32_MAX) + { + ok = this->changeCNVPage(widget->getCnvPage()); + } + else { ok = true; } + + if(ok){ + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), widget->getAddress(), readValue) == 0) + { widget->setValue(*readValue); } + } + else{ StatusBarManager::pushMessage("Failed to read registry"); } + }); + if(widget->getAccessPermission() == RegisterBlockWidget::ACCESS_PERMISSION::READWRITE || + widget->getAccessPermission() == RegisterBlockWidget::ACCESS_PERMISSION::WRITE){ + connect(widget->writeButton(), &QPushButton::clicked, this, [=]{ + bool ok = false, success = false; + + if(widget->getCnvPage() != UINT32_MAX) + { + ok = this->changeCNVPage(widget->getCnvPage()); + } + else { ok = true; } + + if(ok){ + if(m_admtController->writeDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), widget->getAddress(), widget->getValue()) == 0) + { + if(m_admtController->readDeviceRegistry(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), widget->getAddress(), readValue) == 0) + { + widget->setValue(*readValue); + success = true; + } + } + } + + if(!success) { StatusBarManager::pushMessage("Failed to write to registry"); } + }); + } +} +#pragma endregion + +#pragma region Convert Methods +double HarmonicCalibration::convertRPStoVMAX(double rps) +{ + return (rps * motorMicrostepPerRevolution * motorTimeUnit); +} + +double HarmonicCalibration::convertVMAXtoRPS(double vmax) +{ + return (vmax / motorMicrostepPerRevolution / motorTimeUnit); +} + +double HarmonicCalibration::convertAccelTimetoAMAX(double accelTime) +{ + return (rotate_vmax * 131072 / accelTime / motorfCLK); +} + +double HarmonicCalibration::convertAMAXtoAccelTime(double amax) +{ + return ((rotate_vmax * 131072) / (amax * motorfCLK)); +} +#pragma endregion + +#pragma region Debug Methods +QString HarmonicCalibration::readRegmapDumpAttributeValue() +{ + QString output = ""; + char value[1024]; + int result = -1; + result = m_admtController->getDeviceAttributeValueString(m_admtController->getDeviceId(ADMTController::Device::ADMT4000), + m_admtController->getDeviceAttribute(ADMTController::DeviceAttribute::REGMAP_DUMP), + value, 1024); + output = QString(value); + return output; +} +#pragma endregion \ No newline at end of file diff --git a/plugins/admt/src/widgets/horizontalspinbox.cpp b/plugins/admt/src/widgets/horizontalspinbox.cpp new file mode 100644 index 0000000000..968821432d --- /dev/null +++ b/plugins/admt/src/widgets/horizontalspinbox.cpp @@ -0,0 +1,219 @@ +/* + * 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 +#include +#include "widgets/horizontalspinbox.h" + +using namespace scopy::admt; + +HorizontalSpinBox::HorizontalSpinBox(QString header, double initialValue, QString unit, QWidget *parent) + : QWidget(parent) + , m_value(initialValue) + , m_unit(unit) +{ + QVBoxLayout *container = new QVBoxLayout(this); + setLayout(container); + container->setMargin(0); + container->setSpacing(4); + + if(header != ""){ + QLabel *headerLabel = new QLabel(header, this); + Style::setStyle(headerLabel, style::properties::label::menuSmall); + container->addWidget(headerLabel); + } + + QWidget *controlWidget = new QWidget(this); + QHBoxLayout *controlLayout = new QHBoxLayout(controlWidget); + controlWidget->setLayout(controlLayout); + controlLayout->setMargin(0); + controlLayout->setSpacing(2); + + m_lineEdit = new QLineEdit(controlWidget); + QDoubleValidator *validator = new QDoubleValidator(this); + validator->setNotation(QDoubleValidator::StandardNotation); + m_lineEdit->setValidator(validator); + applyLineEditStyle(m_lineEdit); + + if(QString::compare(m_unit, "") != 0) { + QWidget *lineEditContainer = new QWidget(controlWidget); + QHBoxLayout *lineEditLayout = new QHBoxLayout(lineEditContainer); + lineEditContainer->setLayout(lineEditLayout); + lineEditLayout->setMargin(0); + lineEditLayout->setSpacing(0); + + m_unitLabel = new QLabel(m_unit, controlWidget); + applyUnitLabelStyle(m_unitLabel); + + m_lineEdit->setTextMargins(12, 4, 0, 4); + + lineEditLayout->addWidget(m_lineEdit); + lineEditLayout->addWidget(m_unitLabel); + controlLayout->addWidget(lineEditContainer); + } + else{ + controlLayout->addWidget(m_lineEdit); + } + + m_minusButton = new QPushButton(controlWidget); + m_minusButton->setIcon(QIcon(":/admt/minus.svg")); + applyPushButtonStyle(m_minusButton); + + m_plusButton = new QPushButton(controlWidget); + m_plusButton->setIcon(QIcon(":/admt/plus.svg")); + applyPushButtonStyle(m_plusButton, 0, 4, 0, 4); + + controlLayout->addWidget(m_minusButton); + controlLayout->addWidget(m_plusButton); + + container->addWidget(controlWidget); + + setValue(m_value); + connect(m_lineEdit, SIGNAL(editingFinished()), SLOT(onLineEditTextEdited())); + connect(m_minusButton, SIGNAL(clicked()), SLOT(onMinusButtonPressed())); + connect(m_plusButton, SIGNAL(clicked()), SLOT(onPlusButtonPressed())); +} + +void HorizontalSpinBox::onMinusButtonPressed() +{ + m_value--; + setValue(m_value); + Q_EMIT m_lineEdit->editingFinished(); +} + +void HorizontalSpinBox::onPlusButtonPressed() +{ + m_value++; + setValue(m_value); + Q_EMIT m_lineEdit->editingFinished(); +} + +void HorizontalSpinBox::onLineEditTextEdited() +{ + QLineEdit *lineEdit = static_cast(QObject::sender()); + bool ok; + double value = lineEdit->text().toDouble(&ok); + if (ok) { + m_value = value; + } + setValue(m_value); +} + +void HorizontalSpinBox::setValue(double value) +{ + m_lineEdit->setText(QString::number(value)); +} + +void HorizontalSpinBox::setEnabled(double value) +{ + m_lineEdit->setEnabled(value); + m_minusButton->setEnabled(value); + m_plusButton->setEnabled(value); + if(QString::compare(m_unit, "") != 0){ + applyUnitLabelStyle(m_unitLabel, value); + } +} + +void HorizontalSpinBox::applyLineEditStyle(QLineEdit *widget) +{ + QString style = QString(R"css( + QLineEdit { + font-family: Open Sans; + font-size: 16px; + font-weight: normal; + text-align: right; + color: &&colorname&&; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + + background-color: black; + border: none; + qproperty-frame: false; + } + + QLineEdit:disabled { + background-color: #18181d; + color: #9c4600; + } + )css"); + style = style.replace(QString("&&colorname&&"), Style::getAttribute(json::global::ch0)); + widget->setStyleSheet(style); + widget->setFixedHeight(30); + widget->setAlignment(Qt::AlignRight); + widget->setContentsMargins(0, 0, 0, 0); + widget->setTextMargins(6, 4, 6, 4); +} + +void HorizontalSpinBox::applyPushButtonStyle(QPushButton *widget, int topLeftBorderRadius, int topRightBorderRadius, int bottomLeftBorderRadius, int bottomRightBorderRadius) +{ + QString style = QString(R"css( + QPushButton{ + background-color: black; + font-family: Open Sans; + font-size: 32px; + font-weight: bold; + text-align: center center; + color: &&colorname&&; + border-top-left-radius: &&topLeftBorderRadius&&px; + border-top-right-radius: &&topRightBorderRadius&&px; + border-bottom-left-radius: &&bottomLeftBorderRadius&&px; + border-bottom-right-radius: &&bottomRightBorderRadius&&px; + } + QPushButton:disabled{ + background-color: #18181d; + color: #2d3d9c; + } + )css"); + style = style.replace(QString("&&colorname&&"), Style::getAttribute(json::theme::interactive_primary_idle)); + style = style.replace(QString("&&topLeftBorderRadius&&"), QString::number(topLeftBorderRadius)); + style = style.replace(QString("&&topRightBorderRadius&&"), QString::number(topRightBorderRadius)); + style = style.replace(QString("&&bottomLeftBorderRadius&&"), QString::number(bottomLeftBorderRadius)); + style = style.replace(QString("&&bottomRightBorderRadius&&"), QString::number(bottomRightBorderRadius)); + widget->setStyleSheet(style); + widget->setFixedHeight(30); + widget->setFixedWidth(38); +} + +void HorizontalSpinBox::applyUnitLabelStyle(QLabel *widget, bool isEnabled) +{ + QString style = QString(R"css( + background-color: &&backgroundcolor&&; + font-family: Open Sans; + font-size: 16px; + text-align: right; + color: &&colorname&&; + border: none; + )css"); + if(isEnabled){ + style = style.replace(QString("&&backgroundcolor&&"), "black"); + style = style.replace(QString("&&colorname&&"), Style::getAttribute(json::global::ch0)); + } + else{ + style = style.replace(QString("&&backgroundcolor&&"), "#18181d"); + style = style.replace(QString("&&colorname&&"), "#9c4600"); + } + widget->setStyleSheet(style); + widget->setFixedHeight(30); + widget->setAlignment(Qt::AlignRight); + widget->setContentsMargins(0, 4, 12, 4); +} + +QLineEdit *HorizontalSpinBox::lineEdit() { return m_lineEdit; } \ No newline at end of file diff --git a/plugins/admt/src/widgets/registerblockwidget.cpp b/plugins/admt/src/widgets/registerblockwidget.cpp new file mode 100644 index 0000000000..e3b3d55d2f --- /dev/null +++ b/plugins/admt/src/widgets/registerblockwidget.cpp @@ -0,0 +1,179 @@ +/* + * 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 +#include +#include "widgets/registerblockwidget.h" +#include "style_properties.h" + +using namespace scopy; +using namespace scopy::admt; + +RegisterBlockWidget::RegisterBlockWidget(QString header, QString description, uint32_t address, uint32_t cnvPage, RegisterBlockWidget::ACCESS_PERMISSION accessPermission, QWidget *parent) + : QWidget(parent) + , m_address(address) + , m_cnvPage(cnvPage) + , m_accessPermission(accessPermission) +{ + QVBoxLayout *container = new QVBoxLayout(this); + setLayout(container); + container->setMargin(0); + container->setSpacing(0); + MenuSectionWidget *menuSectionWidget = new MenuSectionWidget(this); + MenuCollapseSection *menuCollapseSection = new MenuCollapseSection(header, MenuCollapseSection::MHCW_NONE, MenuCollapseSection::MenuHeaderWidgetType::MHW_BASEWIDGET, menuSectionWidget); + menuCollapseSection->contentLayout()->setSpacing(10); + menuSectionWidget->setFixedHeight(180); + menuSectionWidget->contentLayout()->setSpacing(10); + menuSectionWidget->contentLayout()->addWidget(menuCollapseSection); + + QLabel *descriptionLabel = new QLabel(description, menuSectionWidget); + descriptionLabel->setWordWrap(true); + descriptionLabel->setMinimumHeight(24); + descriptionLabel->setAlignment(Qt::AlignTop); + descriptionLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + + m_spinBox = new PaddedSpinBox(menuSectionWidget); + Style::setStyle(m_spinBox, style::properties::admt::spinBox); + m_spinBox->setButtonSymbols(m_spinBox->ButtonSymbols::NoButtons); + + m_value = 0x00; + m_spinBox->setValue(m_value); + + QWidget *buttonsWidget = new QWidget(menuSectionWidget); + QHBoxLayout *buttonsContainer = new QHBoxLayout(buttonsWidget); + buttonsWidget->setLayout(buttonsContainer); + + buttonsContainer->setMargin(0); + buttonsContainer->setSpacing(10); + switch(m_accessPermission) + { + case ACCESS_PERMISSION::READWRITE: + addReadButton(buttonsWidget); + addWriteButton(buttonsWidget); + break; + case ACCESS_PERMISSION::WRITE: + addWriteButton(buttonsWidget); + break; + case ACCESS_PERMISSION::READ: + addReadButton(buttonsWidget); + m_spinBox->setReadOnly(true); + break; + } + + menuCollapseSection->contentLayout()->setSpacing(10); + menuCollapseSection->contentLayout()->addWidget(descriptionLabel); + menuCollapseSection->contentLayout()->addWidget(m_spinBox); + menuCollapseSection->contentLayout()->addWidget(buttonsWidget); + + container->addWidget(menuSectionWidget); + container->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Preferred)); + + connect(m_spinBox, QOverload::of(&QSpinBox::valueChanged), this, &RegisterBlockWidget::onValueChanged); +} + +RegisterBlockWidget::~RegisterBlockWidget() {} + +void RegisterBlockWidget::onValueChanged(int newValue){ m_value = static_cast(newValue); } + +uint32_t RegisterBlockWidget::getValue() { return m_value; } + +void RegisterBlockWidget::setValue(uint32_t value) +{ + m_value = value; + m_spinBox->setValue(m_value); +} + +uint32_t RegisterBlockWidget::getAddress() { return m_address; } + +uint32_t RegisterBlockWidget::getCnvPage() { return m_cnvPage; } + +RegisterBlockWidget::ACCESS_PERMISSION RegisterBlockWidget::getAccessPermission() { return m_accessPermission; } + +void RegisterBlockWidget::addReadButton(QWidget *parent) +{ + m_readButton = new QPushButton("Read", parent); + StyleHelper::BasicButton(m_readButton); + parent->layout()->addWidget(m_readButton); +} + +QPushButton *RegisterBlockWidget::readButton() { return m_readButton; } + +void RegisterBlockWidget::addWriteButton(QWidget *parent) +{ + m_writeButton = new QPushButton("Write", parent); + StyleHelper::BasicButton(m_writeButton); + parent->layout()->addWidget(m_writeButton); +} + +QPushButton *RegisterBlockWidget::writeButton() { return m_writeButton; } + +void RegisterBlockWidget::applyLineEditStyle(QLineEdit *widget) +{ + QString style = QString(R"css( + background-color: black; + font-family: Open Sans; + font-size: 16px; + color: &&colorname&&; + border: none; + border-radius: 4px; + qproperty-frame: false; + )css"); + style = style.replace(QString("&&colorname&&"), Style::getAttribute(json::global::ch0)); + widget->setStyleSheet(style); + widget->setFixedHeight(30); + widget->setAlignment(Qt::AlignRight); + widget->setContentsMargins(0, 0, 0, 0); + widget->setTextMargins(6, 4, 6, 4); +} + +void RegisterBlockWidget::applySpinBoxStyle(QSpinBox *widget) +{ + QString style = QString(R"css( + background-color: black; + font-family: Open Sans; + font-size: 16px; + color: &&colorname&&; + border: none; + border-radius: 4px; + font-weight: normal; + )css"); + style = style.replace(QString("&&colorname&&"), Style::getAttribute(json::global::ch0)); + widget->setStyleSheet(style); + widget->setFixedHeight(30); + widget->setAlignment(Qt::AlignRight); + widget->setContentsMargins(12, 4, 12, 4); + widget->setButtonSymbols(widget->ButtonSymbols::NoButtons); +} + +PaddedSpinBox::PaddedSpinBox(QWidget *parent) + : QSpinBox(parent) +{ + setDisplayIntegerBase(16); + setMinimum(0); + setMaximum(INT_MAX); +} + +PaddedSpinBox::~PaddedSpinBox() {} + +QString PaddedSpinBox::textFromValue(int value) const +{ + return QString("0x%1").arg(value, 4, 16, QChar('0')); +} \ No newline at end of file diff --git a/plugins/admt/style/qss/properties/admt/checkBoxLED.qss b/plugins/admt/style/qss/properties/admt/checkBoxLED.qss new file mode 100644 index 0000000000..c79e2d6418 --- /dev/null +++ b/plugins/admt/style/qss/properties/admt/checkBoxLED.qss @@ -0,0 +1,20 @@ +QCheckBox[&&property&&=true] { + width: &unit_1&; + height: &unit_1&; + background-color: transparent; + color: &content_default&; +} +QCheckBox::indicator[&&property&&=true] { + width: 12px; + height: 12px; + border: 1px solid &content_default&; + border-radius: 7px; + image: none; + background-color: &background_primary&; +} +QCheckBox::indicator:checked { + background-color: &background_primary&; +} +QCheckBox::indicator[&&property&&=true]::unchecked { + background-color: &background_primary&; +} \ No newline at end of file diff --git a/plugins/admt/style/qss/properties/admt/spinBox.qss b/plugins/admt/style/qss/properties/admt/spinBox.qss new file mode 100644 index 0000000000..b71c89ad12 --- /dev/null +++ b/plugins/admt/style/qss/properties/admt/spinBox.qss @@ -0,0 +1,8 @@ +QSpinBox[&&property&&=true]{ + height: &unit_2&; + border: &border_width& solid &interactive_subtle_idle&; + border-bottom: &border_width_interactive& solid &interactive_subtle_idle&; + border-radius: &radius_interactive&; + padding: 0 &padding_interactive& 0 &padding_interactive&; + selection-background-color: &interactive_subtle_pressed&; +} \ No newline at end of file diff --git a/plugins/admt/test/CMakeLists.txt b/plugins/admt/test/CMakeLists.txt new file mode 100644 index 0000000000..2b852586f3 --- /dev/null +++ b/plugins/admt/test/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# 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 . +# + +cmake_minimum_required(VERSION 3.5) + +include(ScopyTest) + +setup_scopy_tests(pluginloader) \ No newline at end of file diff --git a/plugins/admt/test/tst_pluginloader.cpp b/plugins/admt/test/tst_pluginloader.cpp new file mode 100644 index 0000000000..ee227af7db --- /dev/null +++ b/plugins/admt/test/tst_pluginloader.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2023 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 "qpluginloader.h" + +#include +#include + +#include + +using namespace scopy; + +class TST_ADMTPlugin : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void fileExists(); + void isLibrary(); + void loaded(); + void className(); + void instanceNotNull(); + void multipleInstances(); + void qobjectcast_to_plugin(); + void clone(); + void name(); + void metadata(); +}; + +#define PLUGIN_LOCATION "../../plugins" +#define FILENAME PLUGIN_LOCATION "/libscopy-admtplugin.so" + +void TST_ADMTPlugin::fileExists() +{ + QFile f(FILENAME); + bool ret; + ret = f.open(QIODevice::ReadOnly); + if(ret) + f.close(); + QVERIFY(ret); +} + +void TST_ADMTPlugin::isLibrary() { QVERIFY(QLibrary::isLibrary(FILENAME)); } + +void TST_ADMTPlugin::className() +{ + QPluginLoader qp(FILENAME, this); + QVERIFY(qp.metaData().value("className") == "ADMTPlugin"); +} + +void TST_ADMTPlugin::loaded() +{ + QPluginLoader qp(FILENAME, this); + qp.load(); + QVERIFY(qp.isLoaded()); +} + +void TST_ADMTPlugin::instanceNotNull() +{ + QPluginLoader qp(FILENAME, this); + QVERIFY(qp.instance() != nullptr); +} + +void TST_ADMTPlugin::multipleInstances() +{ + QPluginLoader qp1(FILENAME, this); + QPluginLoader qp2(FILENAME, this); + + QVERIFY(qp1.instance() == qp2.instance()); +} + +void TST_ADMTPlugin::qobjectcast_to_plugin() +{ + QPluginLoader qp(FILENAME, this); + auto instance = qobject_cast(qp.instance()); + QVERIFY(instance != nullptr); +} + +void TST_ADMTPlugin::clone() +{ + QPluginLoader qp(FILENAME, this); + + Plugin *p1 = nullptr, *p2 = nullptr; + auto original = qobject_cast(qp.instance()); + p1 = original->clone(); + QVERIFY(p1 != nullptr); + p2 = original->clone(); + QVERIFY(p2 != nullptr); + QVERIFY(p1 != p2); +} + +void TST_ADMTPlugin::name() +{ + QPluginLoader qp(FILENAME, this); + + Plugin *p1 = nullptr, *p2 = nullptr; + auto original = qobject_cast(qp.instance()); + p1 = original->clone(); + qDebug() << p1->name(); +} + +void TST_ADMTPlugin::metadata() +{ + QPluginLoader qp(FILENAME, this); + + Plugin *p1 = nullptr, *p2 = nullptr; + auto original = qobject_cast(qp.instance()); + original->initMetadata(); + p1 = original->clone(); + qDebug() << p1->metadata(); + QVERIFY(!p1->metadata().isEmpty()); +} + +QTEST_MAIN(TST_ADMTPlugin) + +#include "tst_pluginloader.moc" \ No newline at end of file