From ce6f8e6a46792df6e92641693ac080ffe319ec7a Mon Sep 17 00:00:00 2001 From: pszsh Date: Mon, 2 Feb 2026 06:18:38 -0800 Subject: [PATCH] UPGRADES UPGRADES UPGRADES --- .sdkmanrc | 4 + RampTest.c | 11 +- host/CMakeLists.txt | 117 ++++++++------------ host/Makefile | 4 +- host/android/AndroidManifest.xml | 8 +- host/src/MainWindow_Actions.cpp | 179 ++++++++++++++----------------- 6 files changed, 141 insertions(+), 182 deletions(-) create mode 100644 .sdkmanrc diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000..ff24131 --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,4 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=17-homebrew +gradle=9.2.1 \ No newline at end of file diff --git a/RampTest.c b/RampTest.c index 9196339..8298bcd 100644 --- a/RampTest.c +++ b/RampTest.c @@ -18,7 +18,7 @@ AppRAMPCfg_Type AppRAMPCfg = .LFOSCClkFreq = 32000.0, .SysClkFreq = 16000000.0, .AdcClkFreq = 16000000.0, - .RcalVal = 10000.0, + .RcalVal = 100.0, .ADCRefVolt = 1820.0f, .bTestFinished = bFALSE, @@ -64,7 +64,14 @@ AD5940Err AppRAMPCtrl(uint32_t Command, void *pPara) if(AD5940_WakeUp(10) > 10) return AD5940ERR_WAKEUP; if(AppRAMPCfg.RAMPInited == bFALSE) return AD5940ERR_APPERROR; - if(AppRAMPCfg.RampState == RAMP_STOP) return AD5940ERR_APPERROR; + + // --- CRITICAL FIX: Reset State on Start --- + AppRAMPCfg.RampState = RAMP_STATE0; + AppRAMPCfg.CurrStepPos = 0; + AppRAMPCfg.bFirstDACSeq = bTRUE; + AppRAMPCfg.StopRequired = bFALSE; + AppRAMPCfg.FifoThresh = 4; + // ------------------------------------------ wupt_cfg.WuptEn = bTRUE; wupt_cfg.WuptEndSeq = WUPTENDSEQ_D; diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt index 0a5cc8e..dbf6f3c 100644 --- a/host/CMakeLists.txt +++ b/host/CMakeLists.txt @@ -1,4 +1,5 @@ -# File: host/CMakeLists.txt +# host/CMakeLists.txt + cmake_minimum_required(VERSION 3.18) project(EISConfigurator VERSION 1.0 LANGUAGES CXX C) @@ -20,22 +21,17 @@ include(FetchContent) option(BUILD_ANDROID "Build for Android" OFF) option(BUILD_IOS "Build for iOS" OFF) -# --- Dependencies --- -# Added SerialPort and PrintSupport for EISConfigurator -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets SerialPort PrintSupport OpenGLWidgets) +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets SerialPort PrintSupport) -# --- FFTW3 Configuration (Double Precision) --- +# ========================================== +# --- FFTW3 CONFIGURATION --- +# ========================================== if(WIN32) - # Windows: Expects FFTW3 to be installed/found via Config or PkgConfig - # If not found, you might need to adjust paths or use the source build block below for Windows too - find_package(FFTW3 CONFIG QUIET) + # Windows: Expects FFTW3 to be installed/found via Config + find_package(FFTW3 CONFIG REQUIRED) if(TARGET FFTW3::fftw3) add_library(fftw3 ALIAS FFTW3::fftw3) - else() - # Fallback to building from source on Windows if not found - message(STATUS "FFTW3 not found via Config. Building from source...") - set(BUILD_FFTW_FROM_SOURCE ON) endif() elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" AND NOT BUILD_IOS) message(STATUS "Detected Apple Silicon Desktop. Using Homebrew FFTW3 (Double).") @@ -52,10 +48,6 @@ elseif(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64" AND NOT BUILD_IOS) INTERFACE_INCLUDE_DIRECTORIES "${FFTW3_INCLUDE_DIR}" ) else() - set(BUILD_FFTW_FROM_SOURCE ON) -endif() - -if(BUILD_FFTW_FROM_SOURCE) message(STATUS "Building FFTW3 from source (Double Precision)...") set(ENABLE_FLOAT OFF CACHE BOOL "Build double precision" FORCE) @@ -69,17 +61,15 @@ if(BUILD_FFTW_FROM_SOURCE) set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build Static Libs" FORCE) set(BUILD_TESTS OFF CACHE BOOL "Disable Tests" FORCE) - # Enhanced NEON detection + # Enable NEON for Android ARM64 / iOS if(ANDROID_ABI STREQUAL "arm64-v8a") message(STATUS "Enabling NEON for Android ARM64") set(ENABLE_NEON ON CACHE BOOL "Enable NEON" FORCE) elseif(BUILD_IOS) set(ENABLE_NEON ON CACHE BOOL "Enable NEON" FORCE) - elseif(MSVC AND CMAKE_SYSTEM_PROCESSOR MATCHES "(ARM64|arm64|aarch64)") - set(ENABLE_NEON ON CACHE BOOL "Enable NEON" FORCE) endif() - # Only apply sed patch on UNIX-like systems to fix CMake version requirement in FFTW source + # Patch for older CMake versions inside the tarball if needed if(UNIX) set(PATCH_CMD sed -i.bak "s/cmake_minimum_required.*/cmake_minimum_required(VERSION 3.16)/" /CMakeLists.txt) else() @@ -96,26 +86,18 @@ if(BUILD_FFTW_FROM_SOURCE) FetchContent_MakeAvailable(fftw3_source) endif() -# --- QCustomPlot Integration --- - +# ========================================== +# --- QCUSTOMPLOT --- +# ========================================== FetchContent_Declare( QCustomPlot URL https://www.qcustomplot.com/release/2.1.1/QCustomPlot.tar.gz - DOWNLOAD_EXTRACT_TIMESTAMP TRUE ) +FetchContent_MakeAvailable(QCustomPlot) -FetchContent_GetProperties(QCustomPlot) -if(NOT qcustomplot_POPULATED) - FetchContent_Populate(QCustomPlot) - add_library(QCustomPlot - ${qcustomplot_SOURCE_DIR}/qcustomplot.cpp - ${qcustomplot_SOURCE_DIR}/qcustomplot.h - ) - target_link_libraries(QCustomPlot PUBLIC Qt6::Core Qt6::Gui Qt6::Widgets Qt6::PrintSupport) - target_include_directories(QCustomPlot PUBLIC ${qcustomplot_SOURCE_DIR}) -endif() - -# --- Icon Generation --- +# ========================================== +# --- ICON GENERATION --- +# ========================================== set(ICON_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/assets/icon_source.png") @@ -125,56 +107,36 @@ if(NOT MAGICK_EXECUTABLE) endif() if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE) - if(WIN32) - # --- WINDOWS SPECIFIC --- set(WINDOWS_ICON "${CMAKE_CURRENT_BINARY_DIR}/app_icon.ico") set(WINDOWS_RC "${CMAKE_CURRENT_BINARY_DIR}/app_icon.rc") - # Assuming you have a batch script or use the shell script via git bash - set(ICON_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_icons.sh") - - # Create .rc file + set(ICON_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_icons.bat") file(WRITE "${WINDOWS_RC}" "IDI_ICON1 ICON \"app_icon.ico\"\n") - - # Generate ICO (Requires bash on Windows or a separate .bat script) - # For simplicity, we assume a unix-like environment or WSL for generation add_custom_command( OUTPUT "${WINDOWS_ICON}" - COMMAND "${ICON_SCRIPT}" "${MAGICK_EXECUTABLE}" - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMAND "${ICON_SCRIPT}" "${MAGICK_EXECUTABLE}" "${ICON_SOURCE}" "${WINDOWS_ICON}" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" DEPENDS "${ICON_SOURCE}" "${ICON_SCRIPT}" - COMMENT "Generating Icons..." + COMMENT "Generating Windows Icon..." VERBATIM ) - - # Copy generated ico to binary dir for RC compiler - add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/app_icon.ico" - COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/app_icon.ico" "${WINDOWS_ICON}" - DEPENDS "${WINDOWS_ICON}" - ) - add_custom_target(GenerateIcons DEPENDS "${WINDOWS_ICON}") - else() - # --- MAC/LINUX/ANDROID/IOS SPECIFIC --- set(MACOS_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/app_icon.icns") set(IOS_ASSETS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/ios/Assets.xcassets") + set(IOS_CONTENTS_JSON "${IOS_ASSETS_PATH}/Contents.json") set(ANDROID_RES_PATH "${CMAKE_CURRENT_SOURCE_DIR}/android/res/mipmap-mdpi/ic_launcher.png") - set(ICON_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/scripts/generate_icons.sh") execute_process(COMMAND chmod +x "${ICON_SCRIPT}") - add_custom_command( - OUTPUT "${MACOS_ICON}" "${ANDROID_RES_PATH}" + OUTPUT "${MACOS_ICON}" "${IOS_CONTENTS_JSON}" "${ANDROID_RES_PATH}" COMMAND "${ICON_SCRIPT}" "${MAGICK_EXECUTABLE}" WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DEPENDS "${ICON_SOURCE}" "${ICON_SCRIPT}" COMMENT "Generating Cross-Platform Icons..." VERBATIM ) - - add_custom_target(GenerateIcons DEPENDS "${MACOS_ICON}" "${ANDROID_RES_PATH}") + add_custom_target(GenerateIcons DEPENDS "${MACOS_ICON}" "${IOS_CONTENTS_JSON}" "${ANDROID_RES_PATH}") endif() endif() @@ -184,9 +146,10 @@ set(PROJECT_SOURCES src/main.cpp src/MainWindow.cpp src/MainWindow_UI.cpp - src/MainWindow_Serial.cpp src/MainWindow_Actions.cpp + src/MainWindow_Serial.cpp src/GraphWidget.cpp + ${qcustomplot_SOURCE_DIR}/qcustomplot.cpp ) if(EXISTS "${ICON_SOURCE}") @@ -200,22 +163,27 @@ endif() set(PROJECT_HEADERS src/MainWindow.h src/GraphWidget.h + ${qcustomplot_SOURCE_DIR}/qcustomplot.h ) -if(ANDROID) - add_library(EISConfigurator SHARED ${PROJECT_SOURCES} ${PROJECT_HEADERS}) -else() - add_executable(EISConfigurator ${PROJECT_SOURCES} ${PROJECT_HEADERS}) -endif() +# Use qt_add_executable for proper Android/iOS handling +qt_add_executable(EISConfigurator MANUAL_FINALIZATION ${PROJECT_SOURCES} ${PROJECT_HEADERS}) if(EXISTS "${ICON_SOURCE}" AND MAGICK_EXECUTABLE) add_dependencies(EISConfigurator GenerateIcons) endif() +# --- Mobile Definitions --- +if(BUILD_ANDROID OR BUILD_IOS) + target_compile_definitions(EISConfigurator PRIVATE IS_MOBILE) +endif() + # --- Linking --- +# Handle FFTW3 Linking and Include Paths if(TARGET fftw3) set(FFTW_TARGET fftw3) + # If built from source via FetchContent, we need to manually add include dirs if(DEFINED fftw3_source_SOURCE_DIR) target_include_directories(EISConfigurator PRIVATE "${fftw3_source_SOURCE_DIR}/api" @@ -223,25 +191,26 @@ if(TARGET fftw3) ) endif() else() + # Fallback if target isn't defined (shouldn't happen with above logic) set(FFTW_TARGET fftw3) endif() +target_include_directories(EISConfigurator PRIVATE ${qcustomplot_SOURCE_DIR}) + target_link_libraries(EISConfigurator PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets Qt6::SerialPort Qt6::PrintSupport - QCustomPlot ${FFTW_TARGET} ) if(BUILD_ANDROID) target_link_libraries(EISConfigurator PRIVATE log m) - set_target_properties(EISConfigurator PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android) + set_property(TARGET EISConfigurator PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") endif() if(BUILD_IOS) if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/Info.plist") message(FATAL_ERROR "Missing ios/Info.plist. Please create it before building.") endif() - set_target_properties(EISConfigurator PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_BUNDLE_NAME "EIS Configurator" @@ -250,7 +219,6 @@ if(BUILD_IOS) XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" RESOURCE "${IOS_ASSETS_PATH}" ) - if(EXISTS "${ICON_SOURCE}") file(MAKE_DIRECTORY "${IOS_ASSETS_PATH}") target_sources(EISConfigurator PRIVATE "${IOS_ASSETS_PATH}") @@ -271,6 +239,5 @@ elseif(WIN32) ) endif() -if(NOT ANDROID) - qt_finalize_executable(EISConfigurator) -endif() \ No newline at end of file +# CRITICAL: Triggers androiddeployqt to build the APK +qt_finalize_executable(EISConfigurator) \ No newline at end of file diff --git a/host/Makefile b/host/Makefile index 3b69d08..30e8ed6 100644 --- a/host/Makefile +++ b/host/Makefile @@ -1,4 +1,4 @@ -# Makefile +# host/Makefile QT_ANDROID_KIT ?= $(HOME)/Qt/6.8.3/android_arm64_v8a QT_IOS_KIT ?= $(HOME)/Qt/6.8.3/ios @@ -13,7 +13,7 @@ TARGET = EISConfigurator # Android Specifics PKG_NAME = org.qtproject.example.EISConfigurator -# CRITICAL FIX: Qt6 generates 'android-build-debug.apk' by default +# Qt6 generates 'android-build-debug.apk' by default APK_PATH = $(BUILD_DIR_ANDROID)/android-build/build/outputs/apk/debug/android-build-debug.apk all: macos diff --git a/host/android/AndroidManifest.xml b/host/android/AndroidManifest.xml index 26199f8..3f4eb96 100644 --- a/host/android/AndroidManifest.xml +++ b/host/android/AndroidManifest.xml @@ -1,6 +1,6 @@ @@ -10,12 +10,12 @@ - - + \ No newline at end of file diff --git a/host/src/MainWindow_Actions.cpp b/host/src/MainWindow_Actions.cpp index 57e4f7e..9fe8eed 100644 --- a/host/src/MainWindow_Actions.cpp +++ b/host/src/MainWindow_Actions.cpp @@ -119,9 +119,16 @@ void MainWindow::toggleAmperometry() { void MainWindow::startLSVBlank() { if (!serial->isOpen()) return; + + // Toggle Logic: If running, stop it. + if (lsvState == LSV_RUNNING_BLANK) { + stopLSV(); + return; + } + if (isMeasuringImp) toggleMeasurement(); if (isMeasuringAmp) toggleAmperometry(); - if (lsvState != LSV_IDLE) stopLSV(); + if (lsvState != LSV_IDLE) stopLSV(); // Stop sample if running if (isSweeping) startSweep(); double start = spinLsvStart->value(); @@ -140,8 +147,9 @@ void MainWindow::startLSVBlank() { setButtonBlinking(lsvBlankBtn, true); lsvState = LSV_RUNNING_BLANK; + // Only clear if we are actually starting a new run lsvGraph->clearLSV(GraphWidget::LSV_BLANK); - lsvGraph->clearLSV(GraphWidget::LSV_DIFF); // Clear old diff + lsvGraph->clearLSV(GraphWidget::LSV_DIFF); lsvBlankData.clear(); tabWidget->setCurrentIndex(3); @@ -149,9 +157,16 @@ void MainWindow::startLSVBlank() { void MainWindow::startLSVSample() { if (!serial->isOpen()) return; + + // Toggle Logic + if (lsvState == LSV_RUNNING_SAMPLE) { + stopLSV(); + return; + } + if (isMeasuringImp) toggleMeasurement(); if (isMeasuringAmp) toggleAmperometry(); - if (lsvState != LSV_IDLE) stopLSV(); + if (lsvState != LSV_IDLE) stopLSV(); // Stop blank if running if (isSweeping) startSweep(); double start = spinLsvStart->value(); @@ -170,8 +185,9 @@ void MainWindow::startLSVSample() { setButtonBlinking(lsvSampleBtn, true); lsvState = LSV_RUNNING_SAMPLE; + // Only clear if we are actually starting a new run lsvGraph->clearLSV(GraphWidget::LSV_SAMPLE); - lsvGraph->clearLSV(GraphWidget::LSV_DIFF); // Clear old diff + lsvGraph->clearLSV(GraphWidget::LSV_DIFF); lsvSampleData.clear(); tabWidget->setCurrentIndex(3); @@ -282,108 +298,73 @@ void MainWindow::performCircleFit() { int n = sweepReals.size(); if (n < 5) return; - double measuredRs = -1.0; - bool found = false; - - for (int i = 0; i < n - 1; i++) { - double img1 = sweepImags[i]; - double img2 = sweepImags[i+1]; - - if ((img1 >= 0 && img2 < 0) || (img1 < 0 && img2 >= 0)) { - double r1 = sweepReals[i]; - double r2 = sweepReals[i+1]; - - if (std::abs(img2 - img1) < 1e-9) continue; + // 1. Centering (Crucial for stability) + double meanX = 0, meanY = 0; + for(int i=0; i 0) { - lblResultRs->setText(QString(" Rs: %1 Ω (Meas)").arg(measuredRs, 0, 'f', 2)); + // Solve 3x3 Linear System (Normal Equations) for Centered Kasa + // [ 4*sum_x2 4*sum_xy 0 ] [ A ] [ 2*sum_zx ] + // [ 4*sum_xy 4*sum_y2 0 ] [ B ] = [ 2*sum_zy ] + // [ 0 0 n ] [ C ] [ sum_z ] + + double C = sum_z / n; + + // Solve 2x2 for A, B + double D = 16 * (sum_x2 * sum_y2 - sum_xy * sum_xy); + + if (std::abs(D) < 1e-9) return; // Collinear or insufficient data + + double A = (2 * sum_zx * 4 * sum_y2 - 2 * sum_zy * 4 * sum_xy) / D; + double B = (4 * sum_x2 * 2 * sum_zy - 4 * sum_xy * 2 * sum_zx) / D; + + double xc = A + meanX; + double yc = B + meanY; + double r_sq = A*A + B*B + C; + + if (r_sq <= 0) return; + double r = std::sqrt(r_sq); + + // Calculate Intercepts with Real Axis (y=0) + // (x - xc)^2 + (0 - yc)^2 = r^2 + // (x - xc)^2 = r^2 - yc^2 + + double term = r*r - yc*yc; + if (term < 0) return; // Circle doesn't intersect real axis + + double x1 = xc - std::sqrt(term); + double x2 = xc + std::sqrt(term); + + double Rs = std::min(x1, x2); + if (Rs < 0) Rs = std::max(x1, x2); // If one is negative, take the positive one + + if (Rs > 0) { + lblResultRs->setText(QString(" Rs: %1 Ω").arg(Rs, 0, 'f', 2)); - double cond = (cellConstant / measuredRs) * 1000000.0; + double cond = (cellConstant / Rs) * 1000000.0; lblResultCond->setText(QString(" Cond: %1 µS/cm").arg(cond, 0, 'f', 2)); - nyquistGraph->setExtrapolatedPoint(measuredRs, 0); - return; + nyquistGraph->setExtrapolatedPoint(Rs, 0); } - - int startIdx = n - (n / 3); - if (startIdx < 0) startIdx = 0; - int count = n - startIdx; - if (count < 3) return; - - double sum_x = 0, sum_y = 0, sum_x2 = 0, sum_y2 = 0; - double sum_xy = 0, sum_x3 = 0, sum_y3 = 0, sum_xy2 = 0, sum_x2y = 0; - - for (int i = startIdx; i < n; i++) { - double x = sweepReals[i]; - double y = -sweepImags[i]; - - double x2 = x * x; - double y2 = y * y; - - sum_x += x; - sum_y += y; - sum_x2 += x2; - sum_y2 += y2; - sum_xy += x * y; - sum_x3 += x2 * x; - sum_y3 += y2 * y; - sum_xy2 += x * y2; - sum_x2y += x2 * y; - } - - double M11 = sum_x2, M12 = sum_xy, M13 = sum_x; - double M21 = sum_xy, M22 = sum_y2, M23 = sum_y; - double M31 = sum_x, M32 = sum_y, M33 = (double)count; - - double R1 = sum_x3 + sum_xy2; - double R2 = sum_x2y + sum_y3; - double R3 = sum_x2 + sum_y2; - - double det = M11*(M22*M33 - M23*M32) - M12*(M21*M33 - M23*M31) + M13*(M21*M32 - M22*M31); - - if (std::abs(det) < 1e-9) return; - - double detA = R1*(M22*M33 - M23*M32) - M12*(R2*M33 - M23*R3) + M13*(R2*M32 - M22*R3); - double detB = M11*(R2*M33 - M23*R3) - R1*(M21*M33 - M23*M31) + M13*(M21*R3 - R2*M31); - double detC = M11*(M22*R3 - R2*M32) - M12*(M21*R3 - R2*M31) + R1*(M21*M32 - M22*M31); - - double A = detA / det; - double B = detB / det; - double C = detC / det; - - double xc = A / 2.0; - double r_sq = C + (A*A)/4.0 + (B*B)/4.0; - - if (r_sq < 0) return; - - double disc = A*A + 4*C; - if (disc < 0) return; - - double x1 = (A - std::sqrt(disc)) / 2.0; - double x2 = (A + std::sqrt(disc)) / 2.0; - - double Rs = 0; - if (x1 > 0 && x2 > 0) Rs = std::min(x1, x2); - else if (x1 > 0) Rs = x1; - else if (x2 > 0) Rs = x2; - else Rs = std::max(x1, x2); - - lblResultRs->setText(QString(" Rs: %1 Ω").arg(Rs, 0, 'f', 2)); - - double cond = (cellConstant / Rs) * 1000000.0; - lblResultCond->setText(QString(" Cond: %1 µS/cm").arg(cond, 0, 'f', 2)); - - nyquistGraph->setExtrapolatedPoint(Rs, 0); } void MainWindow::calibrateCellConstant() {