Index: AD5941_interface09302025/ad5941_library_extension.cpp =================================================================== diff -u -r00e9a3fe8b32dd4867784af1134f7cc2904d579f -rcddeaee04537fb518e201de8f8dbf957f168e71b --- AD5941_interface09302025/ad5941_library_extension.cpp (.../ad5941_library_extension.cpp) (revision 00e9a3fe8b32dd4867784af1134f7cc2904d579f) +++ AD5941_interface09302025/ad5941_library_extension.cpp (.../ad5941_library_extension.cpp) (revision cddeaee04537fb518e201de8f8dbf957f168e71b) @@ -2,20 +2,20 @@ * @file ad5941_library_extension.cpp * @brief For connecting teensy microcontroller to ad5941 via SPI with interrupt functionality * @author MK, Aly Development - * @date 09/30/2025, last modified + * @date 01/09/2026, last modified * * @details * Teensy 4.0 extension for the AD5941/AD5940 AFE (Analog Front End) library - * from Analog Devices Inc. + * from Analog Devices Inc. * * Provides hardware interface functions and * application-specific measurement routines for conductivity * and RTD temperature sensing. - * + * * **** Note: Any application of this software should be thoroughly tested and validated **** * * Adapted and expanded from: https://github.com/analogdevicesinc/ad5940-examples - * Note impedance.c modified for jitter + * Note impedance.c modified for jitter */ // Include guard @@ -24,43 +24,86 @@ // Include dependencies #include "ad5941_library_extension.h" +#include "DDTeensyDefs.h" -extern "C" { +extern "C" +{ #include "ad5940.h" #include "impedance.h" } #include +// Stores the instance of currently connected / detected Serial port +Stream *activeSerial = &Serial7; + +Stream *serialPorts[] = +{ + &Serial, // USB virtual serial + &Serial1, // pins 0, 1 + &Serial2, // pins 7, 8 + &Serial3, // pins 14, 15 + &Serial4, // pins 16, 17 + &Serial5, // pins 21, 20 + &Serial6, // pins 24, 25 + &Serial7 // pins 28, 29 +}; + +const char *serialNames[] = +{ + "Serial (USB)", + "Serial1 (pins 0,1)", + "Serial2 (pins 7,8)", + "Serial3 (pins 14,15)", + "Serial4 (pins 16,17)", + "Serial5 (pins 21,20)", + "Serial6 (pins 24,25)", + "Serial7 (pins 28,29)" + }; + +static const char *cfgSettingsParam[] = + { + "sinfreq", + "dacpp", + "bias", + "rtia", + "pga", + "dftnum", + "avgnum" + }; + uint32_t AppBuff[APPBUFF_SIZE]; // Arrays to store up to 5 measurements -float magnitudeArray[repeatNumber]; // Array to hold magnitude values (in Ohms) -float phaseArray[repeatNumber]; // Array to hold phase values (in degrees) -float storedFrequency; // Variable to store the measurement frequency +float magnitudeArray[repeatNumber]; // Array to hold magnitude values (in Ohms) +float phaseArray[repeatNumber]; // Array to hold phase values (in degrees) +float storedFrequency; // Variable to store the measurement frequency volatile static uint32_t ucInterrupted = 0; /* Flag to indicate interrupt occurred */ int currentTIA = DEFAULT_RTIA; int verboseMode = 0; -unsigned long previousMillis = 0; // Store last measurement time -const long jitterTimeout = 1500; // 1.5 seconds in milliseconds if it's been longer than this many mSec since last measurement, we assume discard first {numSettlingMeasurements} measurement as it may have startup jitter. +unsigned long previousMillis = 0; // Store last measurement time +const long jitterTimeout = 1500; // 1.5 seconds in milliseconds if it's been longer than this many mSec since last measurement, we assume discard first {numSettlingMeasurements} measurement as it may have startup jitter. - // SPI Settings for Teensy 4.0 static const SPISettings SPISettings(SPI_SPEED, MSBFIRST, SPI_MODE0); +static bool sendSensorData = false; + // Configuration storage structure -struct SavedConfig { +struct SavedConfig +{ float SinFreq; float DacVoltPP; float BiasVolt; uint32_t HstiaRtiaSel; uint32_t AdcPgaGain; uint32_t DftNum; uint32_t ADCAvgNum; - struct { + struct + { BoolFlag SweepEn; float SweepStart; float SweepStop; @@ -69,54 +112,86 @@ } SweepCfg; }; +// Enum to track unit status +enum UnitStatus +{ + STATUS_SKIPPED = 0, // Unit was never attempted to be initialized + STATUS_FAILED = 1, // Unit was attempted but failed initialization + STATUS_SUCCESS = 2 // Unit was successfully initialized +}; + +// Array to keep track of unit status (indexed 0-5 for units 1-6) +// Initialize all to skipped by default +UnitStatus unitStatus[6] = { + STATUS_SKIPPED, STATUS_SKIPPED, STATUS_SKIPPED, + STATUS_SKIPPED, STATUS_SKIPPED, STATUS_SKIPPED}; + +static bool isInitialized; // Set to true if the initialization function has been completely executed atleast once. +static Init_Status initStatus; // Stores the phase of initialization +static sensorDataPacketStruct sensorPacket[MAX_NUM_OF_SENSORS]; // Stores Impedance and RTD measurements for all the sensors. +static sensorDataPacketStruct singleSensorPacket; // Stores Impedance and RTD measurements for only singularly selected sensor. +static eepromDataPacketStruct eepromDataPacket; // Stores all the eeprom data +static measurementSettingsStruct measurementSettingsPacket; // Stores all the conductivity measurement settings +static measurementSettingsStruct tempSettings; // Stores received measurement settings to be updated. +static Update_cfg_Status updateCfgStatus[MAX_CONDUCTIVITY_MST_PARAM_IDX]; // Stores all configuration statuses for each measurement setting +static Update_EEPROM_Status updateEepromStatus = UPDATE_EEPROM_STATUS_INVALID_CMD; // Stores eeprom status + // Global variable to store the last known good configuration SavedConfig lastConfig; int propagateSettingsChanges = 0; -int TEENSY_RESET_PIN = 17; // Reset pin for AD5940 -int AD5940_INT_PIN = 18; // Interrupt pin from AD5940 +int TEENSY_RESET_PIN = 17; // Reset pin for AD5940 +int AD5940_INT_PIN = 18; // Interrupt pin from AD5940 float rtdVoltageValue = DEFAULT_RTD_VALUE; int outputNow = 0; /****************************************************************************** * @brief Method to deselect the chip select function of the AD5940 *****************************************************************************/ -void AD5940_CsSet(void) { +void AD5940_CsSet(void) +{ digitalWrite(TEENSY_SPI_CS_PIN, HIGH); - delayMicroseconds(10); // this is necessary to keep things from switching too fast -MK + delayMicroseconds(10); // this is necessary to keep things from switching too fast -MK } /****************************************************************************** * @brief Method to select the chip select function of the AD5940 *****************************************************************************/ -void AD5940_CsClr(void) { +void AD5940_CsClr(void) +{ digitalWrite(TEENSY_SPI_CS_PIN, LOW); - delayMicroseconds(10); // this is necessary to keep things from switching too fast -MK + delayMicroseconds(10); // this is necessary to keep things from switching too fast -MK } /****************************************************************************** * @brief Method to reset the AD5940 *****************************************************************************/ -void AD5940_RstSet(void) { +void AD5940_RstSet(void) +{ digitalWrite(TEENSY_RESET_PIN, HIGH); } /****************************************************************************** * @brief Method to clear the reset on the AD5940 *****************************************************************************/ -void AD5940_RstClr(void) { +void AD5940_RstClr(void) +{ digitalWrite(TEENSY_RESET_PIN, LOW); } /****************************************************************************** * @brief Method for delaying the AD5940 for a multiple time of 10 micro seconds - * + * * @param time: multiplier which defines the amount of times the AD5940 should * be delayed by 10 micro seconds *****************************************************************************/ -void AD5940_Delay10us(uint32_t iTime) { - if (iTime < 1638) { +void AD5940_Delay10us(uint32_t iTime) +{ + if (iTime < 1638) + { delayMicroseconds(iTime * 10); - } else { + } + else + { uint32_t iTimeDelayMicro = iTime % 1000; uint32_t iTimeDelay = iTime - iTimeDelayMicro; delay(iTimeDelay / 100); @@ -129,31 +204,33 @@ * @details: Modified for Teensy 4.0's SPI implementation * System clock frequency for the AD5940 remains at 16 MHz. * System clock frequency for Teensy 4.0 is 600 MHz. - * + * * @param pSendBuffer: Send buffer holding all data that should be sent via SPI * @param pRecvBuff: Receive buffer storing all data transmitted by the AD5940 * @param length: Length of transmitted data *****************************************************************************/ void AD5940_ReadWriteNBytes(unsigned char *pSendBuffer, unsigned char *pRecvBuff, - unsigned long length) { + unsigned long length) +{ SPI.beginTransaction(SPISettings); - for (unsigned long i = 0; i < length; i++) { + for (unsigned long i = 0; i < length; i++) + { *pRecvBuff++ = SPI.transfer(*pSendBuffer++); - delayMicroseconds(10); // Add small delay between bytes + delayMicroseconds(10); // Add small delay between bytes } SPI.endTransaction(); - delayMicroseconds(10); // Add delay after transaction + delayMicroseconds(10); // Add delay after transaction } - /****************************************************************************** * @brief Method to get the MCU interrupt flag status * @return uint32_t Returns the current state of the interrupt flag *****************************************************************************/ -uint32_t AD5940_GetMCUIntFlag(void) { +uint32_t AD5940_GetMCUIntFlag(void) +{ return ucInterrupted; } @@ -163,90 +240,94 @@ * interrupt flags on the Teensy 4.0 * @return uint32_t Returns 1 after successfully clearing the flag *****************************************************************************/ -uint32_t AD5940_ClrMCUIntFlag(void) { +uint32_t AD5940_ClrMCUIntFlag(void) +{ ucInterrupted = 0; #ifdef triggDiag - Serial.println("clear trigger"); + activeSerial->println("clear trigger"); #endif return 1; } - /****************************************************************************** * @brief error reporting function; only if ADI_DEBUG enabled in ad5940.h` * @details note must update in ad5940.h to set debug method output - * see: - * #define ADI_DEBUG + * see: + * #define ADI_DEBUG * #ifdef ADI_DEBUG * #define ADI_Print canary * #endif *****************************************************************************/ -void canary(const char *format, ...) { - if (verboseMode) { // only print in verbose mode - char buffer[256]; // Buffer for formatted string +void canary(const char *format, ...) +{ + if (verboseMode) + { // only print in verbose mode + char buffer[256]; // Buffer for formatted string va_list args; va_start(args, format); // Format the string vsnprintf(buffer, sizeof(buffer), format, args); // Print to serial with canary prefix - // Serial.print("🐤 "); // I commented this out, but it was funnier to have the canary function with this -MK - Serial.println(buffer); + // activeSerial->print("🐤 "); // I commented this out, but it was funnier to have the canary function with this -MK + activeSerial->println(buffer); va_end(args); // Force flush the serial buffer - Serial.flush(); + activeSerial->flush(); } } /****************************************************************************** * @brief simple interupt handler *****************************************************************************/ -void AD5940_InterruptHandler() { +void AD5940_InterruptHandler() +{ ucInterrupted = 1; #ifdef triggDiag - Serial.println("triggered"); + activeSerial->println("triggered"); #endif } - - /****************************************************************************** * APPLICATION SPECIFIC FUNCTIONS - * + * * The following functions extend the base AD5940 library with application- * specific implementations for impedance measurement, RTD sensing, and * system configuration management. *****************************************************************************/ /** * @brief convert mV to WgCode for setting on HSDAC - * - * Double check this function before any critical deployment. It was tested and matched emperically for 25mV, but should be verified for more complex usages as needed. - * + * + * Double check this function before any critical deployment. It was tested and matched emperically for 25mV, but should be verified for more complex usages as needed. + * * @return uint16_t - WgCode for HSDAC */ -uint16_t mV_to_WgCode(float mV) // +uint16_t mV_to_WgCode(float mV) // { - const float VDAC_MIN = 0.0f; // 0 V - const float VDAC_MAX = 1.82f; // full-scale HSDAC + const float VDAC_MIN = 0.0f; // 0 V + const float VDAC_MAX = 1.82f; // full-scale HSDAC const float VBIAS = 0; - float Vs = mV * 1e-3f; // V - float Vdac = (Vs + VBIAS) * 0.5f; // V + float Vs = mV * 1e-3f; // V + float Vdac = (Vs + VBIAS) * 0.5f; // V - if (Vdac < VDAC_MIN) Vdac = VDAC_MIN; - if (Vdac > VDAC_MAX) Vdac = VDAC_MAX; + if (Vdac < VDAC_MIN) + Vdac = VDAC_MIN; + if (Vdac > VDAC_MAX) + Vdac = VDAC_MAX; return (uint16_t)(Vdac * 4095.0f / 1.82f + 0.5f); } -uint16_t mV_to_WgCodeUpdate(float mV) { - const float VDAC_MIN = 0.0f; // 0 V - const float VDAC_MAX = 1.82f; // full-scale HSDAC (1.82V reference) +uint16_t mV_to_WgCodeUpdate(float mV) +{ + const float VDAC_MIN = 0.0f; // 0 V + const float VDAC_MAX = 1.82f; // full-scale HSDAC (1.82V reference) // Convert mV to volts float Vs = mV * 1e-3f; @@ -257,51 +338,57 @@ // Handle negative voltages by using the bipolar capability // The AD5940 can do bipolar output around a bias point - if (Vs < 0) { + if (Vs < 0) + { // For negative voltages, we might need to use a different approach // depending on your circuit configuration - Vdac = 0.91f + Vs; // 0.91V is mid-scale for 1.82V reference - } else { - Vdac = 0.91f + Vs; // Positive voltages above mid-scale + Vdac = 0.91f + Vs; // 0.91V is mid-scale for 1.82V reference } + else + { + Vdac = 0.91f + Vs; // Positive voltages above mid-scale + } // Clamp to valid DAC range - if (Vdac < VDAC_MIN) Vdac = VDAC_MIN; - if (Vdac > VDAC_MAX) Vdac = VDAC_MAX; + if (Vdac < VDAC_MIN) + Vdac = VDAC_MIN; + if (Vdac > VDAC_MAX) + Vdac = VDAC_MAX; // Convert to 12-bit DAC code (0-4095) uint16_t code = (uint16_t)(Vdac * 4095.0f / 1.82f + 0.5f); return code; } - /** - * @brief sample from ADC with delay - * - * Double check this function before any critical deployment. It was tested and matched emperically for 25mV, but should be verified for more complex usages as needed. - * + * @brief sample from ADC with delay + * + * Double check this function before any critical deployment. It was tested and matched emperically for 25mV, but should be verified for more complex usages as needed. + * * @return uint32_t code, result code from ADC */ -static inline uint32_t sampleADC(uint32_t t10us) { +static inline uint32_t sampleADC(uint32_t t10us) +{ AD5940_AFECtrlS(AFECTRL_ADCCNV, bTRUE); AD5940_Delay10us(t10us); uint32_t code = AD5940_ReadAfeResult(AFERESULT_SINC2); AD5940_AFECtrlS(AFECTRL_ADCCNV, bFALSE); - //Serial.println(code); + // activeSerial->println(code); return code; } /** * @brief Improved RTD measurement function with better error handling and stability - * + * * Key improvements: * - Better settling time management * - More robust ADC sampling * - Improved error handling * - Consistent switch matrix configuration */ -float AppRTDMeasure(float sensor_mV) { +float AppRTDMeasure(float sensor_mV) +{ AFERefCfg_Type aferef_cfg; HSLoopCfg_Type HsLoopCfg; DSPCfg_Type dsp_cfg; @@ -312,13 +399,14 @@ const float RtdRefRes = REF_RESISTOR_VALUE; // Wake up the AD5940 from low power mode - if (AD5940_WakeUp(10) > 10) { - Serial.println("ERROR: AD5940 wakeup timeout"); + if (AD5940_WakeUp(10) > 10) + { + // activeSerial->println("ERROR: AD5940 wakeup timeout"); return -1000.0f; } // Reset LPDAC configuration before RTD measurement - LPDACCfg_Type lpdac_reset = { 0 }; + LPDACCfg_Type lpdac_reset = {0}; lpdac_reset.PowerEn = bFALSE; AD5940_LPDACCfgS(&lpdac_reset); // no longer change ref subsystem between RTD and conductivity measurements -MK @@ -388,19 +476,20 @@ // Restart waveform generator with proper sequence AD5940_AFECtrlS(AFECTRL_WG, bFALSE); - AD5940_Delay10us(1000); // 10ms settling + AD5940_Delay10us(1000); // 10ms settling AD5940_AFECtrlS(AFECTRL_WG, bTRUE); // Extended settling time for RTD measurement - AD5940_Delay10us(500); // 5ms settling time + AD5940_Delay10us(500); // 5ms settling time // Sample RTD with improved averaging sampleADC(1); uint64_t accRTD = 0; - for (uint32_t i = 0; i < NUM_RTD_SAMPLES; ++i) { - accRTD += sampleADC(1); // Increased sampling delay - // Serial.println(accRTD); - AD5940_Delay10us(10); // Small delay between samples + for (uint32_t i = 0; i < NUM_RTD_SAMPLES; ++i) + { + accRTD += sampleADC(1); // Increased sampling delay + // activeSerial->println(accRTD); + AD5940_Delay10us(10); // Small delay between samples } adcCode_rtd = accRTD / NUM_RTD_SAMPLES; @@ -414,11 +503,12 @@ AD5940_SWMatrixCfgS(&HsLoopCfg.SWMatCfg); // Additional settling time after switch change - AD5940_Delay10us(500); // 5ms settling + AD5940_Delay10us(500); // 5ms settling sampleADC(1); // Sample reference resistor uint64_t accREF = 0; - for (uint32_t i = 0; i < NUM_RTD_SAMPLES; ++i) { + for (uint32_t i = 0; i < NUM_RTD_SAMPLES; ++i) + { accREF += sampleADC(1); AD5940_Delay10us(10); } @@ -438,82 +528,104 @@ // // Validate measurements // if (volt_ref <= 0.0f || volt_rtd <= 0.0f) { - // Serial.println("ERROR: Invalid voltage measurements"); + // activeSerial->println("ERROR: Invalid voltage measurements"); // return -1000.0f; // } // Calculate resistance rtd_resistance = RtdRefRes * (volt_ref / volt_rtd); // Verbose output - if (verboseMode) { //shrinking this output to avoid being too long - // Serial.println(""); - // Serial.println("verboseMode output"); - // Serial.print("sensor_mV"); - // Serial.println(sensor_mV); - Serial.println(" "); + if (verboseMode) + { // shrinking this output to avoid being too long + // activeSerial->println(""); + // activeSerial->println("verboseMode output"); + // activeSerial->print("sensor_mV"); + // activeSerial->println(sensor_mV); + activeSerial->println(" "); - Serial.println("RTD Measurement"); - Serial.print(" RCal Averaged Measurement: "); - Serial.println(adcCode_ref); - Serial.print(" RTD Averaged Measurement: "); - Serial.println(adcCode_rtd); - Serial.printf(" NUM_SAMPLES: %u", NUM_RTD_SAMPLES); - Serial.println(" "); - Serial.print(" RTD TIA Resistor: "); - switch (RTD_RTIA) { - case HSTIARTIA_200: Serial.println("200 Ohm"); break; - case HSTIARTIA_1K: Serial.println("1K Ohm"); break; - case HSTIARTIA_5K: Serial.println("5K Ohm"); break; - case HSTIARTIA_10K: Serial.println("10K Ohm"); break; - case HSTIARTIA_20K: Serial.println("20K Ohm"); break; - case HSTIARTIA_40K: Serial.println("40K Ohm"); break; - case HSTIARTIA_80K: Serial.println("80K Ohm"); break; - case HSTIARTIA_160K: Serial.println("160K Ohm"); break; - default: Serial.println("Unknown"); break; + activeSerial->println("RTD Measurement"); + activeSerial->print(" RCal Averaged Measurement: "); + activeSerial->println(adcCode_ref); + activeSerial->print(" RTD Averaged Measurement: "); + activeSerial->println(adcCode_rtd); + activeSerial->printf(" NUM_SAMPLES: %u", NUM_RTD_SAMPLES); + activeSerial->println(" "); + activeSerial->print(" RTD TIA Resistor: "); + switch (RTD_RTIA) + { + case HSTIARTIA_200: + activeSerial->println("200 Ohm"); + break; + case HSTIARTIA_1K: + activeSerial->println("1K Ohm"); + break; + case HSTIARTIA_5K: + activeSerial->println("5K Ohm"); + break; + case HSTIARTIA_10K: + activeSerial->println("10K Ohm"); + break; + case HSTIARTIA_20K: + activeSerial->println("20K Ohm"); + break; + case HSTIARTIA_40K: + activeSerial->println("40K Ohm"); + break; + case HSTIARTIA_80K: + activeSerial->println("80K Ohm"); + break; + case HSTIARTIA_160K: + activeSerial->println("160K Ohm"); + break; + default: + activeSerial->println("Unknown"); + break; } - // Serial.print("volt_ref: "); - // Serial.println(volt_ref, 6); // More precision - // Serial.print("volt_rtd: "); - // Serial.println(volt_rtd, 6); - // Serial.print("volt_ref / volt_rtd: "); - // Serial.println(volt_ref / volt_rtd, 6); - // Serial.print("Calculated RTD resistance: "); - // Serial.println(rtd_resistance, 6); + // activeSerial->print("volt_ref: "); + // activeSerial->println(volt_ref, 6); // More precision + // activeSerial->print("volt_rtd: "); + // activeSerial->println(volt_rtd, 6); + // activeSerial->print("volt_ref / volt_rtd: "); + // activeSerial->println(volt_ref / volt_rtd, 6); + // activeSerial->print("Calculated RTD resistance: "); + // activeSerial->println(rtd_resistance, 6); } // Display result - Serial.print("Freq:0.0 1 RzMag: "); - Serial.print(rtd_resistance, 5); - Serial.println(" Ohm, RzPhase: 0.0"); + activeSerial->print("Freq:0.0 1 RzMag: "); + activeSerial->print(rtd_resistance, 5); + activeSerial->println(" Ohm, RzPhase: 0.0"); return rtd_resistance; } /** * @brief Resets the AD5940 switch matrix to its default state - * + * * This function creates a clean configuration for the AD5940 switch matrix * by clearing all switch selections. It ensures that all internal connections * are opened before setting up a new configuration, preventing potential * conflicts or shorts between different signal paths. */ -void resetSwitchMatrix() { +void resetSwitchMatrix() +{ // Create a switch matrix configuration structure SWMatrixCfg_Type sw_cfg; // Clear all switch selections by setting them to 0 - sw_cfg.Dswitch = 0; // D switches (typically connected to excitation source) - sw_cfg.Pswitch = 0; // P switches (typically connected to positive input) - sw_cfg.Nswitch = 0; // N switches (typically connected to negative input) - sw_cfg.Tswitch = 0; // T switches (typically connected to TIA feedback network) + sw_cfg.Dswitch = 0; // D switches (typically connected to excitation source) + sw_cfg.Pswitch = 0; // P switches (typically connected to positive input) + sw_cfg.Nswitch = 0; // N switches (typically connected to negative input) + sw_cfg.Tswitch = 0; // T switches (typically connected to TIA feedback network) // Apply the configuration to the AD5940 hardware AD5940_SWMatrixCfgS(&sw_cfg); } // Function to save current configuration -void saveCurrentConfig() { +void saveCurrentConfig() +{ AppIMPCfg_Type *pImpedanceCfg; AppIMPGetCfg(&pImpedanceCfg); lastConfig.SinFreq = pImpedanceCfg->SinFreq; @@ -531,7 +643,8 @@ } // Function to recall saved configuration -void recallSavedConfig() { +void recallSavedConfig() +{ AppIMPCfg_Type *pImpedanceCfg; AppIMPGetCfg(&pImpedanceCfg); pImpedanceCfg->SinFreq = lastConfig.SinFreq; @@ -551,16 +664,17 @@ /** * @brief Handles configuration commands sent via serial interface - * + * * This function processes configuration commands in CSV format: * cfg,parameter,value - * - * It allows users to modify impedance measurement parameters, + * + * It allows users to modify impedance measurement parameters, * save configurations, and recall saved configurations. - * + * * @param cmd String containing the configuration command */ -void handleConfigCommand(String cmd) { +void handleConfigCommand(String cmd) +{ // Command format: cfg,parameter,value // Examples: // - cfg,sinfreq,1000.0 (Set sine wave frequency to 1000 Hz) @@ -571,19 +685,23 @@ int startIndex = 0; // Parse command string using comma delimiter - for (uint i = 0; i < cmd.length(); i++) { - if (cmd.charAt(i) == ',') { + for (uint i = 0; i < cmd.length(); i++) + { + if (cmd.charAt(i) == ',') + { params[paramIndex] = cmd.substring(startIndex, i); startIndex = i + 1; paramIndex++; // Safety check to prevent array overflow - if (paramIndex >= 3) break; + if (paramIndex >= 3) + break; } } // Get last parameter (after the last comma) - if (paramIndex < 3) { + if (paramIndex < 3) + { params[paramIndex] = cmd.substring(startIndex); } @@ -596,115 +714,165 @@ String value = params[2]; // Handle special commands for saving/recalling configurations - if (param == "save") { + if (param == "save") + { saveCurrentConfig(); - Serial.println("Configuration saved"); + activeSerial->println("Configuration saved"); return; - } else if (param == "recall") { + } + else if (param == "recall") + { recallSavedConfig(); - Serial.println("Configuration recalled"); + activeSerial->println("Configuration recalled"); return; } // Process different configuration parameters - if (param == "sinfreq") { + if (param == "sinfreq") + { // Set sine wave frequency (Hz) pImpedanceCfg->SinFreq = value.toFloat(); - Serial.printf("Set SinFreq to: %.2f Hz\n", pImpedanceCfg->SinFreq); - } else if (param == "dacpp") { + activeSerial->printf("Set SinFreq to: %.2f Hz\n", pImpedanceCfg->SinFreq); + } + else if (param == "dacpp") + { // Set DAC peak-to-peak voltage (mV) pImpedanceCfg->DacVoltPP = value.toFloat(); - Serial.printf("Set DacVoltPP to: %.2f mV\n", pImpedanceCfg->DacVoltPP); - } else if (param == "bias") { + activeSerial->printf("Set DacVoltPP to: %.2f mV\n", pImpedanceCfg->DacVoltPP); + } + else if (param == "bias") + { // Set DC bias voltage (mV) pImpedanceCfg->BiasVolt = value.toFloat(); - Serial.printf("Set BiasVolt to: %.2f mV\n", pImpedanceCfg->BiasVolt); - } else if (param == "rtia") { + activeSerial->printf("Set BiasVolt to: %.2f mV\n", pImpedanceCfg->BiasVolt); + } + else if (param == "rtia") + { // Set TIA (Transimpedance Amplifier) feedback resistor value - uint32_t rtia = DEFAULT_RTIA; // default value + uint32_t rtia = DEFAULT_RTIA; // default value int valueTest = value.toInt(); - Serial.print("Value:"); - Serial.println(value); + activeSerial->print("Value:"); + activeSerial->println(value); // Map string values to corresponding constants - if (valueTest == 0) rtia = HSTIARTIA_200; - else if (valueTest == 1) rtia = HSTIARTIA_1K; - else if (valueTest == 2) rtia = HSTIARTIA_5K; - else if (valueTest == 3) rtia = HSTIARTIA_10K; - else if (valueTest == 4) rtia = HSTIARTIA_20K; - else if (valueTest == 5) rtia = HSTIARTIA_40K; - else if (valueTest == 6) rtia = HSTIARTIA_80K; - else if (valueTest == 7) rtia = HSTIARTIA_160K; - else Serial.print("no change to tia"); + if (valueTest == 0) + rtia = HSTIARTIA_200; + else if (valueTest == 1) + rtia = HSTIARTIA_1K; + else if (valueTest == 2) + rtia = HSTIARTIA_5K; + else if (valueTest == 3) + rtia = HSTIARTIA_10K; + else if (valueTest == 4) + rtia = HSTIARTIA_20K; + else if (valueTest == 5) + rtia = HSTIARTIA_40K; + else if (valueTest == 6) + rtia = HSTIARTIA_80K; + else if (valueTest == 7) + rtia = HSTIARTIA_160K; + else + activeSerial->print("no change to tia"); pImpedanceCfg->HstiaRtiaSel = rtia; - Serial.printf("Set RTIA to: %s\n", value.c_str()); + activeSerial->printf("Set RTIA to: %s\n", value.c_str()); currentTIA = rtia; - Serial.print("currentTIA: "); - Serial.println(currentTIA); - - } else if (param == "pga") { + activeSerial->print("currentTIA: "); + activeSerial->println(currentTIA); + } + else if (param == "pga") + { // Set ADC Programmable Gain Amplifier gain - uint32_t pga = ADCPGA_1; // default value (1x gain) + uint32_t pga = ADCPGA_1; // default value (1x gain) // Map string values to corresponding constants - if (value == "1") pga = ADCPGA_1; - else if (value == "1.5") pga = ADCPGA_1P5; - else if (value == "2") pga = ADCPGA_2; - else if (value == "4") pga = ADCPGA_4; - else if (value == "9") pga = ADCPGA_9; + if (value == "1") + pga = ADCPGA_1; + else if (value == "1.5") + pga = ADCPGA_1P5; + else if (value == "2") + pga = ADCPGA_2; + else if (value == "4") + pga = ADCPGA_4; + else if (value == "9") + pga = ADCPGA_9; pImpedanceCfg->AdcPgaGain = pga; - Serial.printf("Set PGA gain to: %s\n", value.c_str()); - } else if (param == "dftnum") { + activeSerial->printf("Set PGA gain to: %s\n", value.c_str()); + } + else if (param == "dftnum") + { // Set DFT (Discrete Fourier Transform) number of points - uint32_t dft = DFTNUM_4096; // default value (4096 points) + uint32_t dft = DFTNUM_4096; // default value (4096 points) // Map string values to corresponding constants - if (value == "4096") dft = DFTNUM_4096; - else if (value == "2048") dft = DFTNUM_2048; - else if (value == "1024") dft = DFTNUM_1024; - else if (value == "512") dft = DFTNUM_512; - else if (value == "256") dft = DFTNUM_256; + if (value == "4096") + dft = DFTNUM_4096; + else if (value == "2048") + dft = DFTNUM_2048; + else if (value == "1024") + dft = DFTNUM_1024; + else if (value == "512") + dft = DFTNUM_512; + else if (value == "256") + dft = DFTNUM_256; pImpedanceCfg->DftNum = dft; - Serial.printf("Set DFT number to: %s\n", value.c_str()); - } else if (param == "avgnum") { + activeSerial->printf("Set DFT number to: %s\n", value.c_str()); + } + else if (param == "avgnum") + { // Set ADC averaging number (how many samples to average) - uint32_t avg = ADCAVGNUM_16; // default value (16 samples) + uint32_t avg = ADCAVGNUM_16; // default value (16 samples) // Map string values to corresponding constants - if (value == "2") avg = ADCAVGNUM_2; - else if (value == "4") avg = ADCAVGNUM_4; - else if (value == "8") avg = ADCAVGNUM_8; - else if (value == "16") avg = ADCAVGNUM_16; + if (value == "2") + avg = ADCAVGNUM_2; + else if (value == "4") + avg = ADCAVGNUM_4; + else if (value == "8") + avg = ADCAVGNUM_8; + else if (value == "16") + avg = ADCAVGNUM_16; pImpedanceCfg->ADCAvgNum = avg; - Serial.printf("Set ADC average number to: %s\n", value.c_str()); - } else if (param == "sweep") { + activeSerial->printf("Set ADC average number to: %s\n", value.c_str()); + } + else if (param == "sweep") + { // Enable/disable frequency sweep mode bool enableSweep = (value == "1" || value.equalsIgnoreCase("true")); pImpedanceCfg->SweepCfg.SweepEn = enableSweep ? bTRUE : bFALSE; - Serial.printf("Set sweep enable to: %d\n", pImpedanceCfg->SweepCfg.SweepEn); - } else if (param == "sweepstart") { + activeSerial->printf("Set sweep enable to: %d\n", pImpedanceCfg->SweepCfg.SweepEn); + } + else if (param == "sweepstart") + { // Set sweep start frequency (Hz) pImpedanceCfg->SweepCfg.SweepStart = value.toFloat(); - Serial.printf("Set sweep start frequency to: %.2f Hz\n", pImpedanceCfg->SweepCfg.SweepStart); - } else if (param == "sweepstop") { + activeSerial->printf("Set sweep start frequency to: %.2f Hz\n", pImpedanceCfg->SweepCfg.SweepStart); + } + else if (param == "sweepstop") + { // Set sweep stop frequency (Hz) pImpedanceCfg->SweepCfg.SweepStop = value.toFloat(); - Serial.printf("Set sweep stop frequency to: %.2f Hz\n", pImpedanceCfg->SweepCfg.SweepStop); - } else if (param == "sweeppoints") { + activeSerial->printf("Set sweep stop frequency to: %.2f Hz\n", pImpedanceCfg->SweepCfg.SweepStop); + } + else if (param == "sweeppoints") + { // Set number of frequency points in the sweep pImpedanceCfg->SweepCfg.SweepPoints = value.toInt(); - Serial.printf("Set sweep points to: %d\n", pImpedanceCfg->SweepCfg.SweepPoints); - } else if (param == "sweeplog") { + activeSerial->printf("Set sweep points to: %d\n", pImpedanceCfg->SweepCfg.SweepPoints); + } + else if (param == "sweeplog") + { // Set logarithmic (true) or linear (false) frequency spacing bool logSpacing = (value == "1" || value.equalsIgnoreCase("true")); pImpedanceCfg->SweepCfg.SweepLog = logSpacing ? bTRUE : bFALSE; - Serial.printf("Set sweep log mode to: %d\n", pImpedanceCfg->SweepCfg.SweepLog); - } else { + activeSerial->printf("Set sweep log mode to: %d\n", pImpedanceCfg->SweepCfg.SweepLog); + } + else + { // Handle unrecognized parameter - Serial.println("Malformed command: unrecognized parameter"); + activeSerial->println("Malformed command: unrecognized parameter"); return; } @@ -715,19 +883,19 @@ saveCurrentConfig(); } - /** * @brief Displays impedance measurement results - * + * * This function formats and outputs impedance measurement data to the serial port. * It retrieves the current measurement frequency and prints the magnitude and phase * of each impedance measurement in the data array. - * changed to enable just outputting every Nth result for debugging - * + * changed to enable just outputting every Nth result for debugging + * * @param pData Pointer to array of impedance data (as fImpPol_Type structures) * @param DataCount Number of impedance measurements in the array */ -void ImpedanceShowResult(uint32_t *pData, uint32_t DataCount, int readingCount) { +void ImpedanceShowResult(uint32_t *pData, uint32_t DataCount, int readingCount) +{ // Variable to store measurement frequency float freq; // Cast raw data pointer to impedance polar format type @@ -737,41 +905,41 @@ AppIMPCtrl(IMPCTRL_GETFREQ, &freq); // Print measurement frequency - //Serial.printf("Freq: %.2f Hz ", freq); + // activeSerial->printf("Freq: %.2f Hz ", freq); // Print the number of data points - //Serial.printf("DataPoints: %lu ", DataCount); + // activeSerial->printf("DataPoints: %lu ", DataCount); // // Process and print each impedance measurement // for (uint32_t i = 0; i < DataCount; i++) { // // Print magnitude in ohms and phase in degrees // // Note: Converting phase from radians to degrees (phase * 180 / π) - // Serial.printf("RzMag: %.2f Ohm, RzPhase: %.2f deg", + // activeSerial->printf("RzMag: %.2f Ohm, RzPhase: %.2f deg", // pImp[i].Magnitude, // pImp[i].Phase * 180 / MATH_PI); // // Add separator between multiple measurements if needed // if (i < DataCount - 1) { - // Serial.print(", "); + // activeSerial->print(", "); // } // } storedFrequency = freq; magnitudeArray[readingCount] = pImp[0].Magnitude; phaseArray[readingCount] = pImp[0].Phase * 180 / MATH_PI; // Add newline for better readability in serial monitor - //Serial.println(); + // activeSerial->println(); } - /** * @brief Initializes the AD5940 impedance measurement configuration structure - * + * * This function sets default values for the impedance measurement configuration * structure including sequence settings, frequency, switch matrix connections, * gain settings, filtering, and DFT parameters. It configures the AD5940 for * basic impedance measurements with appropriate default settings. */ -void AD5940ImpedanceStructInit(void) { +void AD5940ImpedanceStructInit(void) +{ // Pointer to the impedance configuration structure AppIMPCfg_Type *pImpedanceCfg; @@ -783,41 +951,41 @@ //---------- Sequence Configuration ---------- // Set sequence memory allocation - pImpedanceCfg->SeqStartAddr = 0; // Start address in SRAM - pImpedanceCfg->MaxSeqLen = 512; // Maximum sequence length + pImpedanceCfg->SeqStartAddr = 0; // Start address in SRAM + pImpedanceCfg->MaxSeqLen = 512; // Maximum sequence length //---------- Measurement Parameters ---------- - pImpedanceCfg->RcalVal = REF_RESISTOR_VALUE; // calibration resistor - pImpedanceCfg->SinFreq = DEFAULT_FREQ; // 10kHz sine wave frequency - pImpedanceCfg->FifoThresh = DEFAULT_FIFO_THRESH; // FIFO threshold for interrupt + pImpedanceCfg->RcalVal = REF_RESISTOR_VALUE; // calibration resistor + pImpedanceCfg->SinFreq = DEFAULT_FREQ; // 10kHz sine wave frequency + pImpedanceCfg->FifoThresh = DEFAULT_FIFO_THRESH; // FIFO threshold for interrupt //---------- Switch Matrix Configuration ---------- // Configure switch matrix connections for the measurement path // Connect AIN2 and AIN3 pins to the HSTIA (High-Speed TIA) inputs - pImpedanceCfg->DswitchSel = DEFAULT_DSWITCH_CON; // D switch to AIN2 - pImpedanceCfg->PswitchSel = DEFAULT_PSWITCH_CON; // P switch to AIN2 - pImpedanceCfg->NswitchSel = DEFAULT_NSWITCH_CON; // N switch to AIN3 - pImpedanceCfg->TswitchSel = DEFAULT_TSWITCH_CON; // T switch to AIN3 and RTIA + pImpedanceCfg->DswitchSel = DEFAULT_DSWITCH_CON; // D switch to AIN2 + pImpedanceCfg->PswitchSel = DEFAULT_PSWITCH_CON; // P switch to AIN2 + pImpedanceCfg->NswitchSel = DEFAULT_NSWITCH_CON; // N switch to AIN3 + pImpedanceCfg->TswitchSel = DEFAULT_TSWITCH_CON; // T switch to AIN3 and RTIA //---------- Gain and Filtering Configuration ---------- // Set gain and filtering parameters for accurate measurements // Note: Using 200Ω RTIA to handle low impedance sensors without saturation - pImpedanceCfg->HstiaRtiaSel = currentTIA; // 200Ω TIA feedback resistor - pImpedanceCfg->ADCAvgNum = ADCAVGNUM_16; // Average 16 ADC samples + pImpedanceCfg->HstiaRtiaSel = currentTIA; // 200Ω TIA feedback resistor + pImpedanceCfg->ADCAvgNum = ADCAVGNUM_16; // Average 16 ADC samples //---------- Sweep Configuration ---------- // Disable frequency sweep by default (single frequency measurement) pImpedanceCfg->SweepCfg.SweepEn = bFALSE; //---------- Power Mode Configuration ---------- // Set high-power mode for optimal performance at frequencies > 80kHz - pImpedanceCfg->PwrMod = AFEPWR_HP; // High power mode + pImpedanceCfg->PwrMod = AFEPWR_HP; // High power mode //---------- Signal Processing Configuration ---------- // Configure decimation filters and DFT parameters - pImpedanceCfg->ADCSinc3Osr = ADCSINC3OSR_2; // Sinc3 filter decimation ratio: 2 - // (ADC sampling rate: 800kSPS/2 = 400kSPS) - pImpedanceCfg->DftNum = DEFAULT_DFTNUM; // 4096-point DFT - pImpedanceCfg->DftSrc = DFTSRC_SINC3; // DFT input from Sinc3 filter + pImpedanceCfg->ADCSinc3Osr = ADCSINC3OSR_2; // Sinc3 filter decimation ratio: 2 + // (ADC sampling rate: 800kSPS/2 = 400kSPS) + pImpedanceCfg->DftNum = DEFAULT_DFTNUM; // 4096-point DFT + pImpedanceCfg->DftSrc = DFTSRC_SINC3; // DFT input from Sinc3 filter // Mark configuration as changed so it will be applied pImpedanceCfg->bParaChanged = bTRUE; @@ -828,18 +996,19 @@ } /** * @brief Configures the AD5940 platform settings including clock, FIFO, interrupts, and GPIO - * + * * This function performs the low-level platform configuration of the AD5940 AFE. * It sets up the system clocks, FIFO buffer, interrupt controller, and GPIO pins * required for proper operation of the measurement system. - * + * * @return int32_t Returns 0 on success */ -static int32_t AD5940PlatformCfg(void) { +static int32_t AD5940PlatformCfg(void) +{ // Configuration structures - CLKCfg_Type clk_cfg; // Clock configuration - FIFOCfg_Type fifo_cfg; // FIFO configuration - AGPIOCfg_Type gpio_cfg; // GPIO configuration + CLKCfg_Type clk_cfg; // Clock configuration + FIFOCfg_Type fifo_cfg; // FIFO configuration + AGPIOCfg_Type gpio_cfg; // GPIO configuration // Hardware reset is commented out as it's handled elsewhere // AD5940_HWReset(); @@ -849,42 +1018,43 @@ // Initialize SPI communication with AD5940 SPI.begin(); - //Serial.println("chipRead:"); + // activeSerial->println("chipRead:"); uint32_t testResult = AD5940_ReadReg(REG_AFECON_CHIPID); - //Serial.println(testResult); + // activeSerial->println(testResult); - if (testResult == 0 || testResult == 65535) { - Serial.println("System Cannot detect AD5940/1"); - Serial.println(testResult); + if (testResult == 0 || testResult == 65535) + { + // activeSerial->println("System Cannot detect AD5940/1"); + // activeSerial->println(testResult); SPI.end(); - return 1; //chip not present + return 1; // chip not present } - //Serial.println(AD5940_ReadReg(REG_AFECON_CHIPID)); - //Serial.println("ENDchipRead"); - // Initialize AD5940 core functions + // activeSerial->println(AD5940_ReadReg(REG_AFECON_CHIPID)); + // activeSerial->println("ENDchipRead"); + // Initialize AD5940 core functions AD5940_Initialize(); //---------- Clock Configuration ---------- // Set up system and ADC clocks - clk_cfg.ADCClkDiv = ADCCLKDIV_1; // No division for ADC clock - clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC; // ADC clock from high-frequency oscillator - clk_cfg.SysClkDiv = SYSCLKDIV_1; // No division for system clock - clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC; // System clock from high-frequency oscillator - clk_cfg.HfOSC32MHzMode = bFALSE; // Use 16MHz mode for HFOSC - clk_cfg.HFOSCEn = bTRUE; // Enable high-frequency oscillator - clk_cfg.HFXTALEn = bFALSE; // Disable high-frequency crystal - clk_cfg.LFOSCEn = bTRUE; // Enable low-frequency oscillator + clk_cfg.ADCClkDiv = ADCCLKDIV_1; // No division for ADC clock + clk_cfg.ADCCLkSrc = ADCCLKSRC_HFOSC; // ADC clock from high-frequency oscillator + clk_cfg.SysClkDiv = SYSCLKDIV_1; // No division for system clock + clk_cfg.SysClkSrc = SYSCLKSRC_HFOSC; // System clock from high-frequency oscillator + clk_cfg.HfOSC32MHzMode = bFALSE; // Use 16MHz mode for HFOSC + clk_cfg.HFOSCEn = bTRUE; // Enable high-frequency oscillator + clk_cfg.HFXTALEn = bFALSE; // Disable high-frequency crystal + clk_cfg.LFOSCEn = bTRUE; // Enable low-frequency oscillator // Apply clock configuration AD5940_CLKCfg(&clk_cfg); //---------- FIFO Configuration ---------- // Configure FIFO buffer for measurement data - fifo_cfg.FIFOEn = bFALSE; // Disable FIFO during configuration - fifo_cfg.FIFOMode = FIFOMODE_FIFO; // Use standard FIFO mode (not stream) - fifo_cfg.FIFOSize = FIFOSIZE_4KB; // Allocate 4KB for FIFO (2KB for sequencer) - fifo_cfg.FIFOSrc = FIFOSRC_DFT; // FIFO source is DFT results - fifo_cfg.FIFOThresh = 4; // Threshold for FIFO interrupt + fifo_cfg.FIFOEn = bFALSE; // Disable FIFO during configuration + fifo_cfg.FIFOMode = FIFOMODE_FIFO; // Use standard FIFO mode (not stream) + fifo_cfg.FIFOSize = FIFOSIZE_4KB; // Allocate 4KB for FIFO (2KB for sequencer) + fifo_cfg.FIFOSrc = FIFOSRC_DFT; // FIFO source is DFT results + fifo_cfg.FIFOThresh = 4; // Threshold for FIFO interrupt // Apply FIFO configuration AD5940_FIFOCfg(&fifo_cfg); @@ -897,7 +1067,7 @@ // Configure interrupt controller for data acquisition // Enable all interrupts in INTC1 for debugging/status checking - AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ADCMAXERR, bTRUE); // change to only adc max testing on intc1 + AD5940_INTCCfg(AFEINTC_1, AFEINTSRC_ADCMAXERR, bTRUE); // change to only adc max testing on intc1 AD5940_INTCClrFlag(AFEINTSRC_ALLINT); // Configure INTC0 for FIFO threshold interrupt only @@ -906,37 +1076,38 @@ //---------- GPIO Configuration ---------- // Configure AD5940 GPIO pins for specific functions - gpio_cfg.FuncSet = GP0_INT | GP1_SLEEP | GP2_SYNC; // GP0: Interrupt, GP1: Sleep, GP2: Sync - gpio_cfg.InputEnSet = 0; // No GPIO as inputs - gpio_cfg.OutputEnSet = AGPIO_Pin0 | AGPIO_Pin1 | AGPIO_Pin2; // Enable output on GP0-GP2 - gpio_cfg.OutVal = 0; // Initial output values low - gpio_cfg.PullEnSet = 0; // No pull-up/down resistors + gpio_cfg.FuncSet = GP0_INT | GP1_SLEEP | GP2_SYNC; // GP0: Interrupt, GP1: Sleep, GP2: Sync + gpio_cfg.InputEnSet = 0; // No GPIO as inputs + gpio_cfg.OutputEnSet = AGPIO_Pin0 | AGPIO_Pin1 | AGPIO_Pin2; // Enable output on GP0-GP2 + gpio_cfg.OutVal = 0; // Initial output values low + gpio_cfg.PullEnSet = 0; // No pull-up/down resistors // Apply GPIO configuration AD5940_AGPIOCfg(&gpio_cfg); // Unlock sleep mode to allow power management AD5940_SleepKeyCtrlS(SLPKEY_UNLOCK); - return 0; // Return success + return 0; // Return success } /** * @brief Initializes the hardware and software components of the measurement system - * + * * This function performs the complete startup sequence required to initialize - * the AD5940 AFE and the measurement system. It configures the GPIO pins, + * the AD5940 AFE and the measurement system. It configures the GPIO pins, * resets the AD5940, sets up the interrupt handling, and initializes the * impedance measurement application. */ -bool startupAD5941() { +bool startupAD5941() +{ // Initial delay to ensure power stabilization delay(100); //---------- GPIO Configuration ---------- // Configure SPI chip select pin pinMode(TEENSY_SPI_CS_PIN, OUTPUT); - digitalWrite(TEENSY_SPI_CS_PIN, HIGH); // Deselect AD5940 initially + digitalWrite(TEENSY_SPI_CS_PIN, HIGH); // Deselect AD5940 initially //---------- AD5940 Reset Sequence ---------- // Configure reset pin @@ -945,15 +1116,15 @@ // Hardware reset sequence for AD5940 digitalWriteFast(TEENSY_RESET_PIN, HIGH); - delay(1); // Wait for reset line to stabilize + delay(1); // Wait for reset line to stabilize digitalWriteFast(TEENSY_RESET_PIN, LOW); - delay(1); // Hold reset active + delay(1); // Hold reset active digitalWriteFast(TEENSY_RESET_PIN, HIGH); - delay(1); // Allow time for AD5940 to initialize + delay(1); // Allow time for AD5940 to initialize // Ensure SPI chip select is properly configured pinMode(TEENSY_SPI_CS_PIN, OUTPUT); - digitalWrite(TEENSY_SPI_CS_PIN, HIGH); // Deselect AD5940 + digitalWrite(TEENSY_SPI_CS_PIN, HIGH); // Deselect AD5940 //---------- Interrupt Configuration ---------- // Configure AD5940 interrupt pin with internal pull-up @@ -963,14 +1134,15 @@ // Attach interrupt handler to respond to AD5940 interrupts attachInterrupt(digitalPinToInterrupt(AD5940_INT_PIN), AD5940_InterruptHandler, - FALLING); // Trigger on falling edge + FALLING); // Trigger on falling edge //---------- AD5940 Initialization ---------- // Initialize AD5940 platform-specific configurations // (SPI communication, timing, etc.) - if (AD5940PlatformCfg()) { - return true; // startupfailed + if (AD5940PlatformCfg()) + { + return true; // startupfailed } // Initialize impedance measurement configuration structure @@ -982,30 +1154,136 @@ // Ensure measurement system is in a known stopped state AppIMPCtrl(IMPCTRL_STOPSYNC, 0); - resetSwitchMatrix(); // Disconnect all switches + resetSwitchMatrix(); // Disconnect all switches // Save the initial configuration saveCurrentConfig(); return false; // Confirm initialization complete and display user information - //Serial.println("Startup Complete"); - //printHelp(); // Show available commands - //printCurrentConfigShort(); // Display current measurement settings + // activeSerial->println("Startup Complete"); + // printHelp(); // Show available commands + // printCurrentConfigShort(); // Display current measurement settings // System is now ready for measurements - // Serial.println("System ready; enter b to perform reading"); + // activeSerial->println("System ready; enter b to perform reading"); } +// MK hotfix version 01/08/2026; checks fifo directly instead of using interrupts (interupts weren't working well on newest boards) +bool AppIMPMeasure(int readingCount) { + uint32_t bufferSize; // Variable to track valid data in buffer + int trigCount = 0; // Counter for completed measurements + unsigned long startTime; // For timeout tracking + const unsigned long TIMEOUT_MS = 5000; // 5 second timeout (adjust as needed) + bool timeoutOccurred = false; + + resetSwitchMatrix(); + AppIMPInit(AppBuff, APPBUFF_SIZE); + AD5940_EnableAdcMaxSaturationIRQ(/*max_code=*/maxValueThresholdADC, /*hysteresis=*/0x0080); + + // Get pointer to impedance configuration + AppIMPCfg_Type *pImpedanceCfg; + AppIMPGetCfg(&pImpedanceCfg); + + // Disable WUPT for on-demand operation + WUPTCfg_Type wupt_cfg; + wupt_cfg.WuptEn = bFALSE; + AD5940_WUPTCfg(&wupt_cfg); + + // Clear any stale FIFO data + AD5940_FIFOCtrlS(FIFOSRC_DFT, bFALSE); + AD5940_Delay10us(10); + AD5940_FIFOCtrlS(FIFOSRC_DFT, bTRUE); + + // Manually trigger the sequencer once + AD5940_SEQMmrTrig(pImpedanceCfg->MeasureSeqInfo.SeqId); + + // Record start time for timeout + startTime = millis(); + + // Wait initial 20ms for measurement to start + delay(20); + + // Poll FIFO count until data is ready or timeout + trigCount = 0; + while (trigCount < 1) { + // Check for timeout + if (millis() - startTime > TIMEOUT_MS) { + timeoutOccurred = true; + break; + } + + // Check FIFO count instead of interrupt + uint32_t fifo_count = AD5940_FIFOGetCnt(); + + if (fifo_count >= pImpedanceCfg->FifoThresh) { + // Data is ready in FIFO + + // Set up buffer for data collection and process the measurement + bufferSize = APPBUFF_SIZE; + AppIMPISR(AppBuff, &bufferSize); + + // Check if we actually got data + if (bufferSize == 0) { + if (verboseMode) { + Serial.println("WARNING: Empty FIFO - measurement did not complete properly"); + } + // Set default invalid values + magnitudeArray[readingCount] = INFINITY; + phaseArray[readingCount] = 0.0f; + storedFrequency = 0.0f; + } else { + // Display the impedance measurement results + // bufferSize now contains the actual number of valid data points + ImpedanceShowResult(AppBuff, bufferSize, readingCount); + + trigCount=1; // exit condition + + // Check if ADC saturated during measurement + if (AD5940_IsAdcMaxSaturatedAndClear()) { + if (verboseMode) { + Serial.println("WARNING: ADC saturation detected, measurement may be invalid"); + } + // Mark this measurement as saturated/invalid + magnitudeArray[readingCount] = INFINITY; + phaseArray[readingCount] = 0; + } + } + + // Increment measurement counter + } + + // Small delay between FIFO checks + delay(1); + } + + // Stop the impedance measurement cleanly + AppIMPCtrl(IMPCTRL_STOPSYNC, 0); + + // Explicitly stopping sequencer + SEQCfg_Type seq_cfg; + seq_cfg.SeqEnable = bFALSE; + AD5940_SEQCfg(&seq_cfg); + AD5940_Delay10us(100); + + // Handle timeout situation + if (timeoutOccurred) { + Serial.println("ERROR: Impedance measurement timeout occurred!"); + } + + return timeoutOccurred; +} + /** * @brief Performs a single impedance measurement and displays the result - * + * * This function conducts a complete impedance measurement cycle using the AD5940. * It initializes the measurement buffer, starts the measurement, waits for * completion, processes the data, and displays the results. */ -bool AppIMPMeasure(int readingCount) { - uint32_t bufferSize; // Variable to track valid data in buffer - int trigCount = 0; // Counter for completed measurements - unsigned long startTime; // For timeout tracking - const unsigned long TIMEOUT_MS = 5000; // 5 second timeout (adjust as needed) +bool AppIMPMeasure_OLD(int readingCount) +{ + uint32_t bufferSize; // Variable to track valid data in buffer + int trigCount = 0; // Counter for completed measurements + unsigned long startTime; // For timeout tracking + const unsigned long TIMEOUT_MS = 5000; // 5 second timeout (adjust as needed) bool timeoutOccurred = false; resetSwitchMatrix(); AppIMPInit(AppBuff, APPBUFF_SIZE); @@ -1018,7 +1296,7 @@ // // Stop any ongoing measurement before starting a new one // AppIMPCtrl(IMPCTRL_STOPSYNC, 0); - //AD5940_Delay10us(5000); // 50ms settling + // AD5940_Delay10us(5000); // 50ms settling // Start the impedance measurement AppIMPCtrl(IMPCTRL_START, 0); @@ -1027,22 +1305,25 @@ startTime = millis(); // Wait for one measurement cycle to complete or timeout trigCount = 0; - while (trigCount < 1) { + while (trigCount < 1) + { // Check for timeout - if (millis() - startTime > TIMEOUT_MS) { + if (millis() - startTime > TIMEOUT_MS) + { timeoutOccurred = true; break; } // Check if interrupt flag is set (measurement data ready) - if (AD5940_GetMCUIntFlag() == 1) { + if (AD5940_GetMCUIntFlag() == 1) + { // Clear the interrupt flag AD5940_ClrMCUIntFlag(); // Set up buffer for data collection and process the measurement bufferSize = APPBUFF_SIZE; AppIMPISR(AppBuff, &bufferSize); - //Serial.println("tset"); + // activeSerial->println("tset"); // Display the impedance measurement results // bufferSize now contains the actual number of valid data points @@ -1057,422 +1338,574 @@ AppIMPCtrl(IMPCTRL_STOPSYNC, 0); // Handle timeout situation - if (timeoutOccurred) { - Serial.println("ERROR: Impedance measurement timeout occurred!"); + if (timeoutOccurred) + { + // activeSerial->println("ERROR: Impedance measurement timeout occurred!"); } return timeoutOccurred; } /** * @brief Displays the current conductivity measurement configuration settings - * + * * This function retrieves the current configuration settings from the * impedance measurement module and prints a summary of the key parameters * to the serial monitor. It shows the essential parameters related to * excitation signal and bias voltage. */ -void printCurrentConfigShort() { +void printCurrentConfigShort() +{ // Pointer to store configuration structure AppIMPCfg_Type *cfg; // Get the current impedance measurement configuration AppIMPGetCfg(&cfg); // Print header with separator - Serial.println("------------------------"); - Serial.println("Current Conductivity Settings"); + activeSerial->println("------------------------"); + activeSerial->println("Current Conductivity Settings"); // Print essential measurement parameters with units - Serial.printf("SinFreq: %.2f Hz\n", cfg->SinFreq); // Sine wave frequency - Serial.printf("DacVoltPP: %.2f mV\n", cfg->DacVoltPP); // DAC peak-to-peak voltage - Serial.printf("BiasVolt: %.2f mV\n", cfg->BiasVolt); // DC bias voltage + activeSerial->printf("SinFreq: %.2f Hz\n", cfg->SinFreq); // Sine wave frequency + activeSerial->printf("DacVoltPP: %.2f mV\n", cfg->DacVoltPP); // DAC peak-to-peak voltage + activeSerial->printf("BiasVolt: %.2f mV\n", cfg->BiasVolt); // DC bias voltage // Additional parameters (commented out for simplified display) // Uncomment these lines to display more detailed configuration - Serial.printf("RTIA: %lu\n", cfg->HstiaRtiaSel); // Transimpedance amplifier setting - Serial.printf("PGA Gain: %lu\n", cfg->AdcPgaGain); // Programmable gain amplifier setting - Serial.printf("DFT Points: %lu\n", cfg->DftNum); // Number of DFT points - Serial.printf("ADC Avg: %u\n", cfg->ADCAvgNum); // ADC averaging setting + activeSerial->printf("RTIA: %lu\n", cfg->HstiaRtiaSel); // Transimpedance amplifier setting + activeSerial->printf("PGA Gain: %lu\n", cfg->AdcPgaGain); // Programmable gain amplifier setting + activeSerial->printf("DFT Points: %lu\n", cfg->DftNum); // Number of DFT points + activeSerial->printf("ADC Avg: %u\n", cfg->ADCAvgNum); // ADC averaging setting // Print footer with separator - Serial.println("------------------------"); + activeSerial->println("------------------------"); } /** * @brief Displays available commands and parameters to the serial monitor - * + * * This function prints a formatted help menu with all available commands, * their descriptions, and configuration parameters to guide users on how * to control the measurement system via the serial interface. */ -void printHelp() { +void printHelp() +{ - Serial.println("------------------"); - Serial.println("Version:"); - Serial.println(VERSION); - Serial.println(AUTHOR); + activeSerial->println("------------------"); + activeSerial->println("Version:"); + activeSerial->println(VERSION); + activeSerial->println(AUTHOR); - Serial.println("------------------"); - Serial.println("Available commands:"); + activeSerial->println("------------------"); + activeSerial->println("Available commands:"); // Measurement and system control commands - Serial.println("d - Perform measurement (Outputs: conductivity followed by temperature data,"); - Serial.println(" format: freq rzmag rzphase for each measurement separated by ;)"); - Serial.println("a - rerun initialize on all units"); + activeSerial->println("d - Perform measurement (Outputs: conductivity followed by temperature data,"); + activeSerial->println(" format: freq rzmag rzphase for each measurement separated by ;)"); + activeSerial->println("a - rerun initialize on all units"); - Serial.println("b - Perform measurement on all previously initialized units"); + activeSerial->println("b - Perform measurement on all previously initialized units"); - Serial.println(""); - Serial.println("s - Rerun system setup and initialization"); - Serial.println("y - Print current conductivity measurement settings"); - Serial.println("r - Restart microcontroller"); - Serial.println("i - Blink LED on teensy to id unit"); - Serial.println("j # - select unit 1 through 6; ie 'j 1' selected unit 1"); - Serial.println("v - toggle verbose mode for rtd measurement"); - Serial.println("load - read eeprom values"); - Serial.println("save - save values to eeprom (will prompt for confirmation) ie: 'save, 45.34,3634.344,63232.32,6232.2' will save those values to eeprom for currently selected unit upon confirmation"); - Serial.println(); - Serial.println(); + activeSerial->println(""); + activeSerial->println("s - Rerun system setup and initialization"); + activeSerial->println("y - Print current conductivity measurement settings"); + activeSerial->println("r - Restart microcontroller"); + activeSerial->println("i - Blink LED on teensy to id unit"); + activeSerial->println("j # - select unit 1 through 6; ie 'j 1' selected unit 1"); + activeSerial->println("v - toggle verbose mode for rtd measurement"); + activeSerial->println("load - read eeprom values"); + activeSerial->println("save - save values to eeprom (will prompt for confirmation) ie: 'save, 45.34,3634.344,63232.32,6232.2' will save those values to eeprom for currently selected unit upon confirmation"); + activeSerial->println(); + activeSerial->println(); // blink LED on teensy to id unit // Configuration command format and parameters - Serial.println("cfg - Change settings for conductivity measurement"); - Serial.println(" cfg,, - Configure parameters for conductivity measurement"); - Serial.println(" Parameters:"); - Serial.println(" sinfreq - Sine wave frequency (Hz)"); - Serial.println(" dacpp - DAC peak-to-peak voltage (mV, maximum: 800mVpp)"); - Serial.println(" bias - DC bias voltage (mV, maximum: 2200mV)"); + activeSerial->println("cfg - Change settings for conductivity measurement"); + activeSerial->println(" cfg,, - Configure parameters for conductivity measurement"); + activeSerial->println(" Parameters:"); + activeSerial->println(" sinfreq - Sine wave frequency (Hz)"); + activeSerial->println(" dacpp - DAC peak-to-peak voltage (mV, maximum: 800mVpp)"); + activeSerial->println(" bias - DC bias voltage (mV, maximum: 2200mV)"); - Serial.println("------------------"); + activeSerial->println("------------------"); } /** * @brief Reads and processes commands from the serial interface - * + * * This function reads incoming commands from the serial port and processes them based * on the first character or prefix. It handles configuration commands, measurement * requests, help display, and system control functions. */ -void readCommand() { +void readCommand() +{ // Read a line from the serial port until newline character - String command = Serial.readStringUntil('\n'); + String command = Serial7.readStringUntil('\0'); // Handle configuration commands (format: cfg,parameter,value) - if (command.startsWith("cfg,")) { + if (command.startsWith("cfg,")) + { handleConfigCommand(command); - Serial.println(command); - Serial.print("*"); + // activeSerial->println(command); + // activeSerial->print("*"); propagateSettingsChanges = 1; + return; + } + if (command.startsWith("mst,")) + { + handleMstCommand(command); + propagateSettingsChanges = 1; + activeSerial->write((uint8_t *)&updateCfgStatus, sizeof(updateCfgStatus)); return; } // Handle save command for doubles (format: save,value1,value2,value3,value4) - if (command.startsWith("save,")) { + if (command.startsWith("save,")) + { handleSaveCommand(command); return; } + // Handle save command for doubles (format: save,value1,value2,value3,value4) + if (command.startsWith("upe,")) + { + updateEEPROMdata(command); + activeSerial->write((uint8_t *)&updateEepromStatus, sizeof(updateEepromStatus)); + return; + } + // Handle load command for doubles (format: load) - if (command.startsWith("load")) { + if (command.startsWith("load")) + { + handleLoadCommand(); return; } - // Process commands based on the first character - switch (command.charAt(0)) { - case '?': // Help command - display available commands + // Get Single measurement for given sensor + if (command.startsWith("h,")) + { + int firstComma = command.indexOf(','); + if (firstComma > 0) + { + int unit = command.substring(firstComma + 1).toInt(); + + if ((isSensorValid (unit)) && (isSensorInitialized (unit))) { - printHelp(); + // Select the sensor + selectUnit(unit); + // Get single sensor data in binary format. + getSelectedSensorMeasurements(); } - break; - case 'a': + else { - initializeSelectedUnits(); - selectFirstWorkingUnit(); - Serial.print("*"); + initSinglePacketToDefault(); + activeSerial->write((uint8_t *)&singleSensorPacket, sizeof(singleSensorPacket)); } - break; - case '-': + } + return; + } + + // Get EEPROM data for given sensor + if (command.startsWith("u,")) + { + int firstComma = command.indexOf(','); + if (firstComma > 0) + { + int unit = command.substring(firstComma + 1).toInt(); + + if ((isSensorValid (unit)) && (isSensorInitialized (unit))) { - // if (AD5940_IsAdcMaxSaturatedAndClear()) { - // Serial.println("SaturationFlag"); - // } else { - // Serial.println("noSat"); - // } + // Select the sensor + selectUnit(unit); + activeSerial->write((uint8_t)1); } - break; - case '+': + else { - // AD5940_EnableAdcMaxSaturationIRQ(/*max_code=*/0xFFFF, /*hysteresis=*/0x0080); - Serial.println("rearm"); + activeSerial->write((uint8_t)0); } - break; - case 'b': - { - measureAllSuccessfulUnits(); - Serial.print("*"); - } - break; - case 'c': - { - // test case only - // AppRTDMeasure(rtdVoltageValue); - // AppIMPMeasure(0); - } - break; - case 'd': // Perform impedance measurement followed by RTD measurement - { + } + } - // quick patch for fixing spontaneous measurements - unsigned long currentMillis = millis(); - if (currentMillis - previousMillis >= jitterTimeout) { + // Process commands based on the first character + switch (command.charAt(0)) + { + case '?': // Help command - display available commands + { + printHelp(); + } + break; + case 'a': + { + isInitialized = false; + while (!initializeSelectedUnits()) + ; + if (true == isInitialized) + { + getInitStatus(); + } + selectFirstWorkingUnit(); + // activeSerial->print("*"); + } + break; + case '-': + { + // if (AD5940_IsAdcMaxSaturatedAndClear()) { + // activeSerial->println("SaturationFlag"); + // } else { + // activeSerial->println("noSat"); + // } + } + break; + case '+': + { + // AD5940_EnableAdcMaxSaturationIRQ(/*max_code=*/0xFFFF, /*hysteresis=*/0x0080); + activeSerial->println("rearm"); + } + break; + case 'b': + { + measureAllSuccessfulUnits(); + activeSerial->print("*"); + } + break; + case 'c': + { + // test case only + // AppRTDMeasure(rtdVoltageValue); + // AppIMPMeasure(0); + } + break; + case 'd': // Perform impedance measurement followed by RTD measurement + { - // Run multiple settling measurements in a loop - for (int settling = 0; settling < numSettlingMeasurements; settling++) { - if (settling == 0) { - Serial.println("Performing settling measurements..."); - } - if (verboseMode) { - Serial.printf(" Settling Measurement#: %u ", settling); - Serial.println(" "); - } - // Run one complete measurement cycle but discard results - AppIMPInit(AppBuff, APPBUFF_SIZE); - AppIMPCtrl(IMPCTRL_START, 0); + // quick patch for fixing spontaneous measurements + unsigned long currentMillis = millis(); + if (currentMillis - previousMillis >= jitterTimeout) + { - // Wait for completion (simplified polling) - int timeout_count = 0; - while (AD5940_GetMCUIntFlag() != 1 && timeout_count < 1000) { - delay(1); - timeout_count++; - } + // Run multiple settling measurements in a loop + for (int settling = 0; settling < numSettlingMeasurements; settling++) + { + if (settling == 0) + { + activeSerial->println("Performing settling measurements..."); + } + if (verboseMode) + { + activeSerial->printf(" Settling Measurement#: %u ", settling); + activeSerial->println(" "); + } + // Run one complete measurement cycle but discard results + AppIMPInit(AppBuff, APPBUFF_SIZE); + AppIMPCtrl(IMPCTRL_START, 0); - if (AD5940_GetMCUIntFlag() == 1) { - AD5940_ClrMCUIntFlag(); - uint32_t dummy_size = APPBUFF_SIZE; - AppIMPISR(AppBuff, &dummy_size); // Discard this data - } - - AppIMPCtrl(IMPCTRL_STOPSYNC, 0); - - // Small delay between settling measurements - if (settling < numSettlingMeasurements - 1) { - AD5940_Delay10us(1000); // 10ms between measurements - } - } - if (verboseMode) { - Serial.println(" End Settling Measurements"); - } + // Wait for completion (simplified polling) + int timeout_count = 0; + while (AD5940_GetMCUIntFlag() != 1 && timeout_count < 1000) + { + delay(1); + timeout_count++; } - if (verboseMode) { - Serial.println("Conductivity"); - Serial.print(" Conductivity TIA Resistor: "); - switch (DEFAULT_RTIA) { - case HSTIARTIA_200: Serial.println("200 Ohm"); break; - case HSTIARTIA_1K: Serial.println("1K Ohm"); break; - case HSTIARTIA_5K: Serial.println("5K Ohm"); break; - case HSTIARTIA_10K: Serial.println("10K Ohm"); break; - case HSTIARTIA_20K: Serial.println("20K Ohm"); break; - case HSTIARTIA_40K: Serial.println("40K Ohm"); break; - case HSTIARTIA_80K: Serial.println("80K Ohm"); break; - case HSTIARTIA_160K: Serial.println("160K Ohm"); break; - default: Serial.println("Unknown"); break; - } + if (AD5940_GetMCUIntFlag() == 1) + { + AD5940_ClrMCUIntFlag(); + uint32_t dummy_size = APPBUFF_SIZE; + AppIMPISR(AppBuff, &dummy_size); // Discard this data } - // Perform impedance measurement - for (int i = 0; i < repeatNumber; i++) { - AppIMPMeasure(i); - } - if (verboseMode) { - if (AD5940_IsAdcMaxSaturatedAndClear()) { - Serial.println("SaturationFlag tripped!"); - } else { - Serial.println("SaturationFlag NOT tripped!"); - } - } - float medianMag; - float medianPhase; - // take median and output results - if (repeatNumber == 1) { - medianMag = magnitudeArray[0]; - medianPhase = phaseArray[0]; - } else { - medianMag = calculateMedian(magnitudeArray, repeatNumber); - medianPhase = calculateMedian(phaseArray, repeatNumber); - } - Serial.printf("Freq: %.2f Hz ", storedFrequency); + AppIMPCtrl(IMPCTRL_STOPSYNC, 0); - // Print the number of data points - Serial.printf("DataPoints: %lu ", 1); - - // Print magnitude in ohms and phase in degrees - // Note: Converting phase from radians to degrees (phase * 180 / π) - Serial.printf("RzMag: %.2f Ohm, RzPhase: %.2f deg", - medianMag, - medianPhase); - - - // Add separator and perform RTD (temperature) measurement - Serial.print(";"); - AppRTDMeasure(rtdVoltageValue); - Serial.print("*"); - previousMillis = millis(); - } - break; - case 'e': - { - Serial.println("function removed"); - Serial.print("*"); - } - break; - case 'f': - { - // Anything after the initial 't' (e.g. ",123.4" or " -12.3")? - if (command.length() > 1) { - // Strip the leading 't' plus optional comma or space - String arg = command.substring(1); - arg.trim(); // remove whitespace - if (arg.startsWith(",")) arg.remove(0, 1); - arg.trim(); - - // Convert to float; String::toFloat() returns 0.0 if conversion fails - float parsed = arg.toFloat(); - if (arg.length() && !(isnan(parsed) || isinf(parsed))) { - rtdVoltageValue = parsed; // valid number received - } else { - Serial.println("bad value, not changing rtdVoltageValue"); - } + // Small delay between settling measurements + if (settling < numSettlingMeasurements - 1) + { + AD5940_Delay10us(1000); // 10ms between measurements } - Serial.println("rtdVoltageValue"); - Serial.println(rtdVoltageValue); } - break; - - case 't': // Read temperature at a user-defined RTD drive level (mV) + if (verboseMode) { - - AppRTDMeasure(rtdVoltageValue); - Serial.print("*"); // optional “command complete” asterisk + activeSerial->println(" End Settling Measurements"); } - case 's': // Perform system startup and configuration - { - // Run startup initialization - startupAD5941(); + } - // Save the configuration and stop any ongoing measurement - saveCurrentConfig(); - AppIMPCtrl(IMPCTRL_STOPSYNC, 0); - Serial.println("Startup Complete"); - } - break; - case 'p': + if (verboseMode) + { + activeSerial->println("Conductivity"); + activeSerial->print(" Conductivity TIA Resistor: "); + switch (DEFAULT_RTIA) { - //propagate settings change through all units - Serial.println("function removed"); - Serial.println("*"); + case HSTIARTIA_200: + activeSerial->println("200 Ohm"); + break; + case HSTIARTIA_1K: + activeSerial->println("1K Ohm"); + break; + case HSTIARTIA_5K: + activeSerial->println("5K Ohm"); + break; + case HSTIARTIA_10K: + activeSerial->println("10K Ohm"); + break; + case HSTIARTIA_20K: + activeSerial->println("20K Ohm"); + break; + case HSTIARTIA_40K: + activeSerial->println("40K Ohm"); + break; + case HSTIARTIA_80K: + activeSerial->println("80K Ohm"); + break; + case HSTIARTIA_160K: + activeSerial->println("160K Ohm"); + break; + default: + activeSerial->println("Unknown"); + break; } - case 'j': // Perform system startup and configuration + } + // Perform impedance measurement + for (int i = 0; i < repeatNumber; i++) + { + AppIMPMeasure(i); + } + if (verboseMode) + { + if (AD5940_IsAdcMaxSaturatedAndClear()) { - selectUnit(command.substring(2).toInt()); + activeSerial->println("SaturationFlag tripped!"); } - break; - - case 'r': // Restart the microcontroller + else { - _reboot_Teensyduino_(); + activeSerial->println("SaturationFlag NOT tripped!"); } - break; + } + float medianMag; + float medianPhase; + // take median and output results + if (repeatNumber == 1) + { + medianMag = magnitudeArray[0]; + medianPhase = phaseArray[0]; + } + else + { + medianMag = calculateMedian(magnitudeArray, repeatNumber); + medianPhase = calculateMedian(phaseArray, repeatNumber); + } - case 'x': // Echo test command - { - Serial.println("z"); - Serial.println("*"); - } - break; - case 'v': // Toggle verbose mode on/off - { - verboseMode = !verboseMode; - Serial.print("Verbose mode "); - Serial.println(verboseMode ? "ON" : "OFF"); - } - break; - case 'y': // Print current configuration in short format - { - printCurrentConfigShort(); - } - break; - case 'z': // Print current configuration in short format - { - Serial.print("AlyIDNumber: "); - // Read the 4 words (32-bits each) that make up the 128-bit serial number - uint32_t serNum[4]; + activeSerial->printf("Freq: %.2f Hz ", storedFrequency); - // OCOTP serial number registers - serNum[0] = HW_OCOTP_CFG0; // First 32 bits - serNum[1] = HW_OCOTP_CFG1; // Second 32 bits - serNum[2] = HW_OCOTP_CFG2; // Third 32 bits - serNum[3] = HW_OCOTP_CFG3; // Fourth 32 bits + // Print the number of data points + activeSerial->printf("DataPoints: %lu ", 1); - // Print the serial number in hexadecimal format - for (int i = 0; i < 4; i++) { - // Print each 32-bit word with leading zeros - char buf[9]; - snprintf(buf, sizeof(buf), "%08lX", serNum[i]); - Serial.print(buf); + // Print magnitude in ohms and phase in degrees + // Note: Converting phase from radians to degrees (phase * 180 / π) + activeSerial->printf("RzMag: %.2f Ohm, RzPhase: %.2f deg", + medianMag, + medianPhase); - // Add a separator between words (except after the last one) - if (i < 3) { - Serial.print("-"); - } - } - Serial.println(); - } - break; + // Add separator and perform RTD (temperature) measurement + activeSerial->print(";"); + AppRTDMeasure(rtdVoltageValue); + activeSerial->print("*"); + previousMillis = millis(); + } + break; + case 'f': + { + // Anything after the initial 't' (e.g. ",123.4" or " -12.3")? + if (command.length() > 1) + { + // Strip the leading 't' plus optional comma or space + String arg = command.substring(1); + arg.trim(); // remove whitespace + if (arg.startsWith(",")) + arg.remove(0, 1); + arg.trim(); - case 'i': // blink LED on teensy to id unit + // Convert to float; String::toFloat() returns 0.0 if conversion fails + float parsed = arg.toFloat(); + if (arg.length() && !(isnan(parsed) || isinf(parsed))) { - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, HIGH); - delay(500); - digitalWrite(LED_BUILTIN, LOW); - delay(500); - digitalWrite(LED_BUILTIN, HIGH); - delay(500); - digitalWrite(LED_BUILTIN, LOW); - delay(500); - SPI.begin(); // rerun SPI to initialize CLK pin (which is shared with LED_BUILTIN) + rtdVoltageValue = parsed; // valid number received } - break; - case 'h': + else { - Serial.println("function removed"); - Serial.println("*"); + activeSerial->println("bad value, not changing rtdVoltageValue"); } - break; + } + activeSerial->println("rtdVoltageValue"); + activeSerial->println(rtdVoltageValue); + } + break; - case 'k': + case 't': // Read temperature at a user-defined RTD drive level (mV) + { + + AppRTDMeasure(rtdVoltageValue); + activeSerial->print("*"); // optional “command complete” asterisk + } + case 's': // Perform system startup and configuration + { + // Run startup initialization + startupAD5941(); + + // Save the configuration and stop any ongoing measurement + saveCurrentConfig(); + AppIMPCtrl(IMPCTRL_STOPSYNC, 0); + activeSerial->println("Startup Complete"); + } + break; + case 'p': + { + // propagate settings change through all units + activeSerial->println("function removed"); + activeSerial->println("*"); + } + case 'j': // Perform system startup and configuration + { + selectUnit(command.substring(2).toInt()); + activeSerial->write((uint8_t*)&singleSensorPacket.sensorNum, sizeof(uint32_t)); + } + break; + + case 'r': // Restart the microcontroller + { + _reboot_Teensyduino_(); + } + break; + + case 'x': // Echo test command + { + activeSerial->println("z"); + activeSerial->println("*"); + } + break; + case 'v': // Toggle verbose mode on/off + { + verboseMode = !verboseMode; + activeSerial->print("Verbose mode "); + activeSerial->println(verboseMode ? "ON" : "OFF"); + } + break; + case 'y': // Print current configuration in short format + { + printCurrentConfigShort(); + } + break; + case 'z': // Print current configuration in short format + { + activeSerial->print("AlyIDNumber: "); + // Read the 4 words (32-bits each) that make up the 128-bit serial number + uint32_t serNum[4]; + + // OCOTP serial number registers + serNum[0] = HW_OCOTP_CFG0; // First 32 bits + serNum[1] = HW_OCOTP_CFG1; // Second 32 bits + serNum[2] = HW_OCOTP_CFG2; // Third 32 bits + serNum[3] = HW_OCOTP_CFG3; // Fourth 32 bits + + // Print the serial number in hexadecimal format + for (int i = 0; i < 4; i++) + { + // Print each 32-bit word with leading zeros + char buf[9]; + snprintf(buf, sizeof(buf), "%08lX", serNum[i]); + activeSerial->print(buf); + + // Add a separator between words (except after the last one) + if (i < 3) { - Serial.println("function removed"); - Serial.println("*"); + activeSerial->print("-"); } - break; - default: // Handle unknown commands - { - Serial.println("Unknown command. Type ? for help."); - } - break; + } + activeSerial->println(); } + break; + + case 'i': // blink LED on teensy to id unit + { + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + delay(500); + digitalWrite(LED_BUILTIN, LOW); + delay(500); + digitalWrite(LED_BUILTIN, HIGH); + delay(500); + digitalWrite(LED_BUILTIN, LOW); + delay(500); + SPI.begin(); // rerun SPI to initialize CLK pin (which is shared with LED_BUILTIN) + } + break; + + case 'e': + { + // Transmit all the data stored in EEPROM + getEEPROMdata(); + } + break; + + case 'g': + { + sendSensorData = false; + // Mesaure and transmit for all the initialized sensors + if (true == getAllMesaurements()) + { + // Send all the sensor data to DD + activeSerial->write((uint8_t *)&sensorPacket, sizeof(sensorPacket)); + } + else + { + activeSerial->write((uint8_t)0); + } + } + break; + + case 'k': + { + // Get conductivity mesaurement settings in binary format. + getMesaurementSettings(); + } + break; + + case 'l': + { + // Get Initialization Status + getInitStatus(); + } + break; + + case 'm': + { + sendSensorData = true; + sendAllSensorData(); + } + break; + case 'n': + { + sendSensorData = false; + activeSerial->write((uint8_t)1); + // sendAllSensorData(); + } + break; + default: // Handle unknown commands + { + activeSerial->println("Unknown command. Type ? for help."); + } + break; + } } // Function to select a unit and update pins -void selectUnit(int unitIn) { +void selectUnit(int unitIn) +{ // Validate unit input (1-6) - if (unitIn < 1 || unitIn > 6) { - Serial.println("Error: Unit must be between 1 and 6"); + if (unitIn < 1 || unitIn > 6) + { + // activeSerial->println("Error: Unit must be between 1 and 6"); return; } @@ -1485,135 +1918,149 @@ pinMode(SELECT_C, OUTPUT); // Default state - disable all units - digitalWrite(EN_A, HIGH); // Disabled (active LOW) - digitalWrite(EN_B, HIGH); // Disabled - digitalWrite(EN_C, HIGH); // Disabled + digitalWrite(EN_A, HIGH); // Disabled (active LOW) + digitalWrite(EN_B, HIGH); // Disabled + digitalWrite(EN_C, HIGH); // Disabled digitalWrite(SELECT_A, LOW); digitalWrite(SELECT_B, LOW); digitalWrite(SELECT_C, LOW); // Update reset and interrupt pins based on selected unit - switch (unitIn) { - case 1: - TEENSY_RESET_PIN = 17; - AD5940_INT_PIN = 18; - digitalWrite(EN_A, LOW); // Enable unit 1 (A) - break; - case 2: - TEENSY_RESET_PIN = 22; - AD5940_INT_PIN = 21; - digitalWrite(EN_A, LOW); // Enable unit 2 (A) - digitalWrite(SELECT_A, HIGH); // Select unit 2 on mux A - break; - case 3: - TEENSY_RESET_PIN = 14; - AD5940_INT_PIN = 15; - digitalWrite(EN_B, LOW); // Enable unit 3 (B) - break; - case 4: - TEENSY_RESET_PIN = 3; - AD5940_INT_PIN = 4; - digitalWrite(EN_B, LOW); // Enable unit 4 (B) - digitalWrite(SELECT_B, HIGH); // Select unit 4 on mux B - break; - case 5: - TEENSY_RESET_PIN = 9; - AD5940_INT_PIN = 8; - digitalWrite(EN_C, LOW); // Enable unit 5 (C) - break; - case 6: - TEENSY_RESET_PIN = 0; - AD5940_INT_PIN = 1; - digitalWrite(EN_C, LOW); // Enable unit 6 (C) - digitalWrite(SELECT_C, HIGH); // Select unit 6 on mux C - break; + switch (unitIn) + { + case 1: + TEENSY_RESET_PIN = 17; + AD5940_INT_PIN = 18; + digitalWrite(EN_A, LOW); // Enable unit 1 (A) + break; + case 2: + TEENSY_RESET_PIN = 22; + AD5940_INT_PIN = 21; + digitalWrite(EN_A, LOW); // Enable unit 2 (A) + digitalWrite(SELECT_A, HIGH); // Select unit 2 on mux A + break; + case 3: + TEENSY_RESET_PIN = 14; + AD5940_INT_PIN = 15; + digitalWrite(EN_B, LOW); // Enable unit 3 (B) + break; + case 4: + TEENSY_RESET_PIN = 3; + AD5940_INT_PIN = 4; + digitalWrite(EN_B, LOW); // Enable unit 4 (B) + digitalWrite(SELECT_B, HIGH); // Select unit 4 on mux B + break; + case 5: + TEENSY_RESET_PIN = 9; + AD5940_INT_PIN = 8; + digitalWrite(EN_C, LOW); // Enable unit 5 (C) + break; + case 6: + TEENSY_RESET_PIN = 0; + AD5940_INT_PIN = 1; + digitalWrite(EN_C, LOW); // Enable unit 6 (C) + digitalWrite(SELECT_C, HIGH); // Select unit 6 on mux C + break; } // Configure the new reset and interrupt pins pinMode(TEENSY_RESET_PIN, OUTPUT); pinMode(AD5940_INT_PIN, INPUT); - Serial.print("Selected Unit: "); - Serial.print(unitIn); - Serial.print(" | Reset Pin: "); - Serial.print(TEENSY_RESET_PIN); - Serial.print(" | Interrupt Pin: "); - Serial.println(AD5940_INT_PIN); + singleSensorPacket.sensorNum = unitIn; // Conductivity Sensor number i.e unit 1 to 6. + + // activeSerial->print("Selected Unit: "); + // activeSerial->print(unitIn); + // activeSerial->print(" | Reset Pin: "); + // activeSerial->print(TEENSY_RESET_PIN); + // activeSerial->print(" | Interrupt Pin: "); + // activeSerial->println(AD5940_INT_PIN); } -// Configuration: Number of double precision values (rest will be single precision) -#define DOUBLE_COUNT 8 // First 8 values as doubles, remaining as floats - // Write a mixed array (doubles first, then floats) to the EEPROM -void writeMixedValues(unsigned int baseAddress, double *doubleData, int doubleCount, float *floatData, int floatCount) { +void writeMixedValues(unsigned int baseAddress, double *doubleData, int doubleCount, float *floatData, int floatCount) +{ // Write doubles first - for (int i = 0; i < doubleCount; i++) { + for (int i = 0; i < doubleCount; i++) + { unsigned int address = baseAddress + (i * sizeof(double)); writeDouble(address, doubleData[i]); delay(10); } // Write floats after the doubles unsigned int floatBaseAddress = baseAddress + (doubleCount * sizeof(double)); - for (int i = 0; i < floatCount; i++) { + for (int i = 0; i < floatCount; i++) + { unsigned int address = floatBaseAddress + (i * sizeof(float)); writeFloat(address, floatData[i]); delay(10); } } // Read a mixed array (doubles first, then floats) from the EEPROM -void readMixedValues(unsigned int baseAddress, double *doubleData, int doubleCount, float *floatData, int floatCount) { +void readMixedValues(unsigned int baseAddress, double *doubleData, int doubleCount, float *floatData, int floatCount) +{ // Read doubles first - for (int i = 0; i < doubleCount; i++) { + for (int i = 0; i < doubleCount; i++) + { unsigned int address = baseAddress + (i * sizeof(double)); doubleData[i] = readDouble(address); } // Read floats after the doubles unsigned int floatBaseAddress = baseAddress + (doubleCount * sizeof(double)); - for (int i = 0; i < floatCount; i++) { + for (int i = 0; i < floatCount; i++) + { unsigned int address = floatBaseAddress + (i * sizeof(float)); floatData[i] = readFloat(address); } } // Write a single float to the EEPROM -void writeFloat(unsigned int address, float data) { +void writeFloat(unsigned int address, float data) +{ // Create a union to access the bytes of the float - union { + union + { float f; byte b[4]; } floatBytes; floatBytes.f = data; // Write each byte of the float - for (uint i = 0; i < sizeof(float); i++) { + for (uint i = 0; i < sizeof(float); i++) + { writeByte(address + i, floatBytes.b[i]); - delay(5); // Small delay between byte writes + delay(5); // Small delay between byte writes } } // Read a single float from the EEPROM -float readFloat(unsigned int address) { +float readFloat(unsigned int address) +{ // Create a union to build the float from bytes - union { + union + { float f; byte b[4]; } floatBytes; // Read each byte of the float - for (uint i = 0; i < sizeof(float); i++) { + for (uint i = 0; i < sizeof(float); i++) + { floatBytes.b[i] = readByte(address + i); } return floatBytes.f; } // Write an array of doubles to the EEPROM -void writeDoubles(unsigned int baseAddress, double *data, int count) { - for (int i = 0; i < count; i++) { +void writeDoubles(unsigned int baseAddress, double *data, int count) +{ + for (int i = 0; i < count; i++) + { // Calculate the address for this double // Each double is 8 bytes unsigned int address = baseAddress + (i * sizeof(double)); @@ -1627,8 +2074,10 @@ } // Read an array of doubles from the EEPROM -void readDoubles(unsigned int baseAddress, double *data, int count) { - for (int i = 0; i < count; i++) { +void readDoubles(unsigned int baseAddress, double *data, int count) +{ + for (int i = 0; i < count; i++) + { // Calculate the address for this double // Each double is 8 bytes unsigned int address = baseAddress + (i * sizeof(double)); @@ -1639,44 +2088,52 @@ } // Write a single double to the EEPROM -void writeDouble(unsigned int address, double data) { +void writeDouble(unsigned int address, double data) +{ // Create a union to access the bytes of the double - union { + union + { double d; byte b[8]; } doubleBytes; doubleBytes.d = data; // Write each byte of the double - for (uint i = 0; i < sizeof(double); i++) { + for (uint i = 0; i < sizeof(double); i++) + { writeByte(address + i, doubleBytes.b[i]); - delay(5); // Small delay between byte writes + delay(5); // Small delay between byte writes } } // Read a single double from the EEPROM -double readDouble(unsigned int address) { +double readDouble(unsigned int address) +{ // Create a union to build the double from bytes - union { + union + { double d; byte b[8]; } doubleBytes; // Read each byte of the double - for (uint i = 0; i < sizeof(double); i++) { + for (uint i = 0; i < sizeof(double); i++) + { doubleBytes.b[i] = readByte(address + i); } return doubleBytes.d; } // Write a single byte to the EEPROM -void writeByte(unsigned int address, byte data) { +void writeByte(unsigned int address, byte data) +{ // Check if address is valid - if (address >= EEPROM_SIZE) { - Serial.print("Error: Address out of range: "); - Serial.println(address); + if (address >= EEPROM_SIZE) + { + activeSerial->print("Error: Address out of range: "); + activeSerial->println(address); return; } @@ -1690,9 +2147,10 @@ SPI.transfer(EEPROM_WRITE); // Send address - if (EEPROM_SIZE > 256) { + if (EEPROM_SIZE > 256) + { // For AT25040B (4Kbit), use 9-bit address (A8-A0) - SPI.transfer((address >> 8) & 0x01); // Only 1 bit for A8 + SPI.transfer((address >> 8) & 0x01); // Only 1 bit for A8 } SPI.transfer(address & 0xFF); @@ -1703,15 +2161,17 @@ digitalWrite(TEENSY_SPI_CS_PIN, LOW); // Wait for the write to complete - delay(5); // 5ms max write time according to datasheet + delay(5); // 5ms max write time according to datasheet } // Read a single byte from the EEPROM -byte readByte(unsigned int address) { +byte readByte(unsigned int address) +{ // Check if address is valid - if (address >= EEPROM_SIZE) { - Serial.print("Error: Address out of range: "); - Serial.println(address); + if (address >= EEPROM_SIZE) + { + activeSerial->print("Error: Address out of range: "); + activeSerial->println(address); return 0; } @@ -1724,9 +2184,10 @@ SPI.transfer(EEPROM_READ); // Send address - if (EEPROM_SIZE > 256) { + if (EEPROM_SIZE > 256) + { // For AT25040B (4Kbit), use 9-bit address (A8-A0) - SPI.transfer((address >> 8) & 0x01); // Only 1 bit for A8 + SPI.transfer((address >> 8) & 0x01); // Only 1 bit for A8 } SPI.transfer(address & 0xFF); @@ -1739,19 +2200,22 @@ return data; } -void writeEnable() { +void writeEnable() +{ digitalWrite(TEENSY_SPI_CS_PIN, HIGH); SPI.transfer(EEPROM_WREN); digitalWrite(TEENSY_SPI_CS_PIN, LOW); - delay(1); // Small delay to ensure the command is processed + delay(1); // Small delay to ensure the command is processed } // Modified command handler for saving mixed precision values to EEPROM // Format: "save,value1,value2,value3,..." (first DOUBLE_COUNT are doubles, rest are floats) -void handleSaveCommand(String command) { +void handleSaveCommand(String command) +{ int firstComma = command.indexOf(','); - if (firstComma < 0) { - Serial.println("Error: Invalid save command format"); + if (firstComma < 0) + { + activeSerial->println("Error: Invalid save command format"); return; } String dataStr = command.substring(firstComma + 1); @@ -1766,24 +2230,32 @@ int valueIndex = 0; // Parse values - while (valueIndex < maxTotal && dataStr.length() > 0) { + while (valueIndex < maxTotal && dataStr.length() > 0) + { int commaPos = dataStr.indexOf(','); String valueStr; - if (commaPos >= 0) { + if (commaPos >= 0) + { valueStr = dataStr.substring(0, commaPos); dataStr = dataStr.substring(commaPos + 1); - } else { + } + else + { // Last value valueStr = dataStr; - dataStr = ""; // Clear dataStr to end the loop + dataStr = ""; // Clear dataStr to end the loop } // Convert string and store in appropriate array - if (valueStr.length() > 0) { - if (valueIndex < DOUBLE_COUNT) { + if (valueStr.length() > 0) + { + if (valueIndex < DOUBLE_COUNT) + { // Store as double doubleValues[valueIndex] = strtod(valueStr.c_str(), NULL); - } else { + } + else + { // Store as float floatValues[valueIndex - DOUBLE_COUNT] = strtof(valueStr.c_str(), NULL); } @@ -1792,128 +2264,149 @@ } // Check if we got at least 1 value - if (valueIndex == 0) { - Serial.println("Error: No valid values found"); + if (valueIndex == 0) + { + activeSerial->println("Error: No valid values found"); return; } - if (valueIndex > maxTotal) { - Serial.print("Error: Too many values, maximum is "); - Serial.print(maxTotal); - Serial.print(" ("); - Serial.print(DOUBLE_COUNT); - Serial.print(" doubles + "); - Serial.print(maxFloats); - Serial.println(" floats)"); + if (valueIndex > maxTotal) + { + activeSerial->print("Error: Too many values, maximum is "); + activeSerial->print(maxTotal); + activeSerial->print(" ("); + activeSerial->print(DOUBLE_COUNT); + activeSerial->print(" doubles + "); + activeSerial->print(maxFloats); + activeSerial->println(" floats)"); return; } int doubleCount = min(valueIndex, DOUBLE_COUNT); int floatCount = max(0, valueIndex - DOUBLE_COUNT); // Display the values and ask for confirmation - Serial.print("Preparing to save "); - Serial.print(valueIndex); - Serial.print(" values to EEPROM ("); - Serial.print(doubleCount); - Serial.print(" doubles, "); - Serial.print(floatCount); - Serial.println(" floats):"); + activeSerial->print("Preparing to save "); + activeSerial->print(valueIndex); + activeSerial->print(" values to EEPROM ("); + activeSerial->print(doubleCount); + activeSerial->print(" doubles, "); + activeSerial->print(floatCount); + activeSerial->println(" floats):"); // Display double values - for (int i = 0; i < doubleCount; i++) { - Serial.print(i); - Serial.print(" (double): "); - Serial.println(doubleValues[i], 10); + for (int i = 0; i < doubleCount; i++) + { + activeSerial->print(i); + activeSerial->print(" (double): "); + activeSerial->println(doubleValues[i], 10); } // Display float values - for (int i = 0; i < floatCount; i++) { - Serial.print(i + DOUBLE_COUNT); - Serial.print(" (float): "); - Serial.println(floatValues[i], 6); // Floats typically have 6-7 significant digits + for (int i = 0; i < floatCount; i++) + { + activeSerial->print(i + DOUBLE_COUNT); + activeSerial->print(" (float): "); + activeSerial->println(floatValues[i], 6); // Floats typically have 6-7 significant digits } - Serial.println("Confirm write to EEPROM? (y/n)"); + activeSerial->println("Confirm write to EEPROM? (y/n)"); // Wait for user confirmation unsigned long startTime = millis(); - const unsigned long timeout = 30000; // 30 seconds timeout - while (true) { + const unsigned long timeout = 30000; // 30 seconds timeout + while (true) + { // Check for timeout - if (millis() - startTime > timeout) { - Serial.println("Timed out waiting for confirmation. Operation aborted."); + if (millis() - startTime > timeout) + { + activeSerial->println("Timed out waiting for confirmation. Operation aborted."); return; } // Check if data is available to read - if (Serial.available() > 0) { - char response = Serial.read(); + if (activeSerial->available() > 0) + { + char response = activeSerial->read(); // Clear any remaining characters in buffer (like newline) - while (Serial.available() > 0) { - Serial.read(); + while (activeSerial->available() > 0) + { + activeSerial->read(); } // Process response - if (response == 'y' || response == 'Y') { + if (response == 'y' || response == 'Y') + { // User confirmed, write to EEPROM unsigned int baseAddress = 0; writeMixedValues(baseAddress, doubleValues, doubleCount, floatValues, floatCount); - Serial.println("save attempted"); + activeSerial->println("save attempted"); // Read back the values to confirm they were written correctly double readDoubles[DOUBLE_COUNT]; float readFloats[maxFloats]; readMixedValues(baseAddress, readDoubles, doubleCount, readFloats, floatCount); - Serial.println("Verification - Values read back from EEPROM:"); + activeSerial->println("Verification - Values read back from EEPROM:"); bool verificationSuccess = true; // Verify doubles - for (int i = 0; i < doubleCount; i++) { - Serial.print(i); - Serial.print(" (double): "); - Serial.println(readDoubles[i], 10); - if (abs(doubleValues[i] - readDoubles[i]) > 0.000000001) { + for (int i = 0; i < doubleCount; i++) + { + activeSerial->print(i); + activeSerial->print(" (double): "); + activeSerial->println(readDoubles[i], 10); + if (abs(doubleValues[i] - readDoubles[i]) > 0.000000001) + { verificationSuccess = false; - Serial.print("Warning: Double value "); - Serial.print(i); - Serial.println(" does not match exactly what was written"); + activeSerial->print("Warning: Double value "); + activeSerial->print(i); + activeSerial->println(" does not match exactly what was written"); } } // Verify floats - for (int i = 0; i < floatCount; i++) { - Serial.print(i + DOUBLE_COUNT); - Serial.print(" (float): "); - Serial.println(readFloats[i], 6); - if (abs(floatValues[i] - readFloats[i]) > 0.000001) { + for (int i = 0; i < floatCount; i++) + { + activeSerial->print(i + DOUBLE_COUNT); + activeSerial->print(" (float): "); + activeSerial->println(readFloats[i], 6); + if (abs(floatValues[i] - readFloats[i]) > 0.000001) + { verificationSuccess = false; - Serial.print("Warning: Float value "); - Serial.print(i + DOUBLE_COUNT); - Serial.println(" does not match exactly what was written"); + activeSerial->print("Warning: Float value "); + activeSerial->print(i + DOUBLE_COUNT); + activeSerial->println(" does not match exactly what was written"); } } - if (verificationSuccess) { - Serial.println("Verification successful: All values match!"); - } else { - Serial.println("Verification Failed, confirm device attached and powered."); + if (verificationSuccess) + { + activeSerial->println("Verification successful: All values match!"); } + else + { + activeSerial->println("Verification Failed, confirm device attached and powered."); + } return; - } else if (response == 'n' || response == 'N') { + } + else if (response == 'n' || response == 'N') + { // User aborted - Serial.println("Operation aborted by user."); + activeSerial->println("Operation aborted by user."); return; - } else { + } + else + { // Invalid response, ask again - Serial.println("Please enter 'y' to confirm or 'n' to abort:"); + activeSerial->println("Please enter 'y' to confirm or 'n' to abort:"); } } // Small delay to prevent CPU hogging delay(10); } } -void handleLoadCommand() { - unsigned int baseAddress = 0; // Same address used in save +void handleLoadCommand() +{ + unsigned int baseAddress = 0; // Same address used in save // Calculate maximum values we can store int maxFloats = (128 - (DOUBLE_COUNT * 8)) / 4; @@ -1923,30 +2416,34 @@ readMixedValues(baseAddress, doubleValues, DOUBLE_COUNT, floatValues, maxFloats); - Serial.println("Loaded values from EEPROM:"); + activeSerial->println("Loaded values from EEPROM:"); // Display doubles - for (int i = 0; i < DOUBLE_COUNT; i++) { - Serial.print(i); - Serial.print(" (double): "); - Serial.println(doubleValues[i], 10); + for (int i = 0; i < DOUBLE_COUNT; i++) + { + activeSerial->print(i); + activeSerial->print(" (double): "); + activeSerial->println(doubleValues[i], 10); } // Display floats - for (int i = 0; i < maxFloats; i++) { - Serial.print(i + DOUBLE_COUNT); - Serial.print(" (float): "); - Serial.println(floatValues[i], 6); + for (int i = 0; i < maxFloats; i++) + { + activeSerial->print(i + DOUBLE_COUNT); + activeSerial->print(" (float): "); + activeSerial->println(floatValues[i], 6); } } // Function to initialize all units sequentially -void initializeAllUnits() { - Serial.println("Initializing all units (1-6)..."); +void initializeAllUnits() +{ + activeSerial->println("Initializing all units (1-6)..."); - for (int unit = 1; unit <= 6; unit++) { - Serial.print("Initializing Unit "); - Serial.println(unit); + for (int unit = 1; unit <= 6; unit++) + { + activeSerial->print("Initializing Unit "); + activeSerial->println(unit); // Select the current unit selectUnit(unit); @@ -1960,124 +2457,62 @@ // Give some time for the initialization to complete delay(200); - Serial.print("Unit "); - Serial.print(unit); - Serial.println(" initialized successfully"); + activeSerial->print("Unit "); + activeSerial->print(unit); + activeSerial->println(" initialized successfully"); } - Serial.println("All units initialized successfully"); + activeSerial->println("All units initialized successfully"); } -// Enum to track unit status -enum UnitStatus { - STATUS_SKIPPED = 0, // Unit was never attempted to be initialized - STATUS_FAILED = 1, // Unit was attempted but failed initialization - STATUS_SUCCESS = 2 // Unit was successfully initialized -}; - -// Array to keep track of unit status (indexed 0-5 for units 1-6) -// Initialize all to skipped by default -UnitStatus unitStatus[6] = { - STATUS_SKIPPED, STATUS_SKIPPED, STATUS_SKIPPED, - STATUS_SKIPPED, STATUS_SKIPPED, STATUS_SKIPPED -}; - // Function to ask the user which units to initialize and then initialize them -void initializeSelectedUnits() { - Serial.println("\nUnit Initialization"); - // Serial.println("Enter 'all' to initialize all units (1-6)"); - // Serial.println("Or enter specific units separated by commas (e.g., '1,3,5')"); - // Serial.println("Then press Enter:"); +bool initializeSelectedUnits() +{ + initStatus = INIT_STATUS_IN_PROGRESS; + activeSerial->write((uint8_t *)&initStatus, sizeof(initStatus)); - // // Wait for user input - // while (!Serial.available()) { - // // Wait for input - // delay(100); - // } - // Read the input - String input = Serial.readStringUntil('\n'); - input.trim(); // Remove any whitespace + String input = activeSerial->readStringUntil('\0'); + input.trim(); // Remove any whitespace - // Initialize all units - Serial.println("Initializing all units (1-6)..."); + // activeSerial->println("Initializing all units (1-6)..."); - for (int unit = 1; unit <= 6; unit++) { + for (int unit = 1; unit <= 6; unit++) + { initializeSingleUnit(unit); } - - // Print summary of unit status - printUnitStatusSummary(); - - - // else { - // // Parse the comma-separated list - // Serial.print("Initializing selected units: "); - - // // Start at the beginning of the string - // int startPos = 0; - // int endPos; - - // // Parse each number in the comma-separated list - // while (startPos < input.length()) { - // // Find the next comma or the end of the string - // endPos = input.indexOf(',', startPos); - // if (endPos == -1) { - // endPos = input.length(); - // } - - // // Extract the unit number - // String unitStr = input.substring(startPos, endPos); - // unitStr.trim(); - - // // Convert to integer - // int unit = unitStr.toInt(); - - // // Check if it's a valid unit number - // if (unit >= 1 && unit <= 6) { - // Serial.print(unit); - // Serial.print(" "); - - // // Initialize this unit - // initializeSingleUnit(unit); - // } else { - // Serial.print("(Skipping invalid unit "); - // Serial.print(unitStr); - // Serial.print(") "); - // } - - // // Move to the position after the comma - // startPos = endPos + 1; - // } - - // Serial.println("\nSelected units initialization complete"); - - // Print summary of unit status + isInitialized = true; + selectFirstWorkingUnit(); + return isInitialized; } // Function to select the first working unit -void selectFirstWorkingUnit() { - for (int i = 0; i < 6; i++) { - if (unitStatus[i] == STATUS_SUCCESS) { +void selectFirstWorkingUnit() +{ + for (int i = 0; i < 6; i++) + { + if (unitStatus[i] == STATUS_SUCCESS) + { // Found a working unit, select it (adding 1 because units are 1-indexed) int workingUnit = i + 1; selectUnit(workingUnit); - Serial.print("\nAutomatically selected first working unit: Unit "); - Serial.println(workingUnit); + // activeSerial->print("\nAutomatically selected first working unit: Unit "); + // activeSerial->println(workingUnit); startupAD5941(); - return; // Exit after selecting the first working unit + return; // Exit after selecting the first working unit } } // If we get here, no working units were found - Serial.println("\nNo working units available to select!"); + // activeSerial->println("\nNo working units available to select!"); } // Helper function to initialize a single unit -void initializeSingleUnit(int unit) { - Serial.print("\nInitializing Unit "); - Serial.println(unit); +void initializeSingleUnit(int unit) +{ + // activeSerial->print("\nInitializing Unit "); + // activeSerial->println(unit); // Select the unit selectUnit(unit); @@ -2086,236 +2521,280 @@ delay(50); // Initialize the AD5940 analog front-end for this unit - if (startupAD5941()) { + if (startupAD5941()) + { // Startup failed - Serial.print("Unit "); - Serial.print(unit); - Serial.println(" startup FAILED"); + // activeSerial->print("Unit "); + // activeSerial->print(unit); + // activeSerial->println(" startup FAILED"); // Record the failure in our status array (0-indexed) unitStatus[unit - 1] = STATUS_FAILED; - } else { + } + else + { // Startup worked - Serial.print("Unit "); - Serial.print(unit); - Serial.println(" startup SUCCESS"); + // activeSerial->print("Unit "); + // activeSerial->print(unit); + // activeSerial->println(" startup SUCCESS"); // Record the success in our status array unitStatus[unit - 1] = STATUS_SUCCESS; } } // Function to print a summary of the status of all units -void printUnitStatusSummary() { +void printUnitStatusSummary() +{ // Count units by status int skippedCount = 0; int failedCount = 0; int successCount = 0; - for (int i = 0; i < 6; i++) { - if (unitStatus[i] == STATUS_SKIPPED) { + for (int i = 0; i < 6; i++) + { + if (unitStatus[i] == STATUS_SKIPPED) + { skippedCount++; - } else if (unitStatus[i] == STATUS_FAILED) { + } + else if (unitStatus[i] == STATUS_FAILED) + { failedCount++; - } else { // STATUS_SUCCESS + } + else + { // STATUS_SUCCESS successCount++; } } // Print summary - Serial.println("\n----- Initialization Results -----"); + activeSerial->println("\n----- Initialization Results -----"); // Report units by status - Serial.print("Successful: "); - Serial.print(successCount); - Serial.print(", Failed: "); - Serial.print(failedCount); - Serial.print(", Skipped: "); - Serial.println(skippedCount); + activeSerial->print("Successful: "); + activeSerial->print(successCount); + activeSerial->print(", Failed: "); + activeSerial->print(failedCount); + activeSerial->print(", Skipped: "); + activeSerial->println(skippedCount); // List failed units - if (failedCount > 0) { - Serial.print("Failed units: "); + if (failedCount > 0) + { + activeSerial->print("Failed units: "); bool firstPrinted = false; - for (int i = 0; i < 6; i++) { - if (unitStatus[i] == STATUS_FAILED) { - if (firstPrinted) { - Serial.print(", "); + for (int i = 0; i < 6; i++) + { + if (unitStatus[i] == STATUS_FAILED) + { + if (firstPrinted) + { + activeSerial->print(", "); } - Serial.print(i + 1); + activeSerial->print(i + 1); firstPrinted = true; } } - Serial.println(); + activeSerial->println(); } // List skipped units - if (skippedCount > 0) { - Serial.print("Skipped units: "); + if (skippedCount > 0) + { + activeSerial->print("Skipped units: "); bool firstPrinted = false; - for (int i = 0; i < 6; i++) { - if (unitStatus[i] == STATUS_SKIPPED) { - if (firstPrinted) { - Serial.print(", "); + for (int i = 0; i < 6; i++) + { + if (unitStatus[i] == STATUS_SKIPPED) + { + if (firstPrinted) + { + activeSerial->print(", "); } - Serial.print(i + 1); + activeSerial->print(i + 1); firstPrinted = true; } } - Serial.println(); + activeSerial->println(); } // Report available units - Serial.print("\nAvailable units: "); - if (successCount == 0) { - Serial.println("NONE - No successfully initialized units!"); - } else { + activeSerial->print("\nAvailable units: "); + if (successCount == 0) + { + activeSerial->println("NONE - No successfully initialized units!"); + } + else + { // List available units bool firstPrinted = false; - for (int i = 0; i < 6; i++) { - if (unitStatus[i] == STATUS_SUCCESS) { + for (int i = 0; i < 6; i++) + { + if (unitStatus[i] == STATUS_SUCCESS) + { // Add comma if not the first available unit - if (firstPrinted) { - Serial.print(", "); + if (firstPrinted) + { + activeSerial->print(", "); } - Serial.print(i + 1); + activeSerial->print(i + 1); firstPrinted = true; } } - Serial.println(); + activeSerial->println(); } - Serial.println("---------------------------------"); + activeSerial->println("---------------------------------"); } // Function to measure all successfully initialized units -void measureAllSuccessfulUnits() { +void measureAllSuccessfulUnits() +{ int successCount = 0; // Count successful units first - for (int i = 0; i < 6; i++) { - if (unitStatus[i] == STATUS_SUCCESS) { + for (int i = 0; i < 6; i++) + { + if (unitStatus[i] == STATUS_SUCCESS) + { successCount++; } } // If no successful units, report and return - if (successCount == 0) { - Serial.println("No successfully initialized units to measure!"); + if (successCount == 0) + { + activeSerial->println("No successfully initialized units to measure!"); return; } - Serial.print("Measuring all "); - Serial.print(successCount); - Serial.println(" successful units..."); + activeSerial->print("Measuring all "); + activeSerial->print(successCount); + activeSerial->println(" successful units..."); AppIMPCfg_Type *pImpedanceCfg; // Loop through and measure each successful unit - for (int unit = 1; unit <= 6; unit++) { + for (int unit = 1; unit <= 6; unit++) + { // Skip units that aren't successfully initialized - if (unitStatus[unit - 1] != STATUS_SUCCESS) { + if (unitStatus[unit - 1] != STATUS_SUCCESS) + { continue; } // Select the unit selectUnit(unit); - if (propagateSettingsChanges) { + if (propagateSettingsChanges) + { AppIMPGetCfg(&pImpedanceCfg); pImpedanceCfg->bParaChanged = bTRUE; } // Allow some settling time after switching units delay(50); // Print unit identifier before measurement - Serial.print("Unit "); - Serial.print(unit); - Serial.print(": "); + activeSerial->print("Unit "); + activeSerial->print(unit); + activeSerial->print(": "); // quick patch for fixing spontaneous measurements unsigned long currentMillis = millis(); - if (currentMillis - previousMillis >= jitterTimeout) { - Serial.println("Performing settling measurements..."); + if (currentMillis - previousMillis >= jitterTimeout) + { + activeSerial->println("Performing settling measurements..."); // Run multiple settling measurements in a loop - for (int settling = 0; settling < numSettlingMeasurements; settling++) { + for (int settling = 0; settling < numSettlingMeasurements; settling++) + { // Run one complete measurement cycle but discard results AppIMPInit(AppBuff, APPBUFF_SIZE); AppIMPCtrl(IMPCTRL_START, 0); // Wait for completion (simplified polling) int timeout_count = 0; - while (AD5940_GetMCUIntFlag() != 1 && timeout_count < 1000) { + while (AD5940_GetMCUIntFlag() != 1 && timeout_count < 1000) + { delay(1); timeout_count++; } - if (AD5940_GetMCUIntFlag() == 1) { + if (AD5940_GetMCUIntFlag() == 1) + { AD5940_ClrMCUIntFlag(); uint32_t dummy_size = APPBUFF_SIZE; - AppIMPISR(AppBuff, &dummy_size); // Discard this data + AppIMPISR(AppBuff, &dummy_size); // Discard this data } AppIMPCtrl(IMPCTRL_STOPSYNC, 0); // Small delay between settling measurements - if (settling < numSettlingMeasurements - 1) { - AD5940_Delay10us(1000); // 10ms between measurements + if (settling < numSettlingMeasurements - 1) + { + AD5940_Delay10us(1000); // 10ms between measurements } } } // Perform impedance measurement - for (int i = 0; i < repeatNumber; i++) { + for (int i = 0; i < repeatNumber; i++) + { AppIMPMeasure(i); } float medianMag; float medianPhase; // take median and output results - if (repeatNumber == 1) { + if (repeatNumber == 1) + { medianMag = magnitudeArray[0]; medianPhase = phaseArray[0]; - } else { + } + else + { medianMag = calculateMedian(magnitudeArray, repeatNumber); medianPhase = calculateMedian(phaseArray, repeatNumber); } - Serial.printf("Freq: %.2f Hz ", storedFrequency); + activeSerial->printf("Freq: %.2f Hz ", storedFrequency); // Print the number of data points - Serial.printf("DataPoints: %lu ", 1); + activeSerial->printf("DataPoints: %lu ", 1); // Print magnitude in ohms and phase in degrees // Note: Converting phase from radians to degrees (phase * 180 / π) - Serial.printf("RzMag: %.2f Ohm, RzPhase: %.2f deg", - medianMag, - medianPhase); + activeSerial->printf("RzMag: %.2f Ohm, RzPhase: %.2f deg", + medianMag, + medianPhase); // Add separator - Serial.print(";"); + activeSerial->print(";"); // Perform RTD (temperature) measurement AppRTDMeasure(rtdVoltageValue); // Line break between units - Serial.println(); + activeSerial->println(); } previousMillis = millis(); - Serial.println("All measurements complete."); + activeSerial->println("All measurements complete."); propagateSettingsChanges = 0; } - -float calculateMedian(float arr[], int size) { +float calculateMedian(float arr[], int size) +{ // Create a temporary copy of the array float tempArr[size]; - for (int i = 0; i < size; i++) { + for (int i = 0; i < size; i++) + { tempArr[i] = arr[i]; } // Sort the temporary array - for (int i = 0; i < size - 1; i++) { - for (int j = 0; j < size - i - 1; j++) { - if (tempArr[j] > tempArr[j + 1]) { + for (int i = 0; i < size - 1; i++) + { + for (int j = 0; j < size - i - 1; j++) + { + if (tempArr[j] > tempArr[j + 1]) + { float temp = tempArr[j]; tempArr[j] = tempArr[j + 1]; tempArr[j + 1] = temp; @@ -2324,41 +2803,1122 @@ } // Return median value - if (size % 2 == 0) { + if (size % 2 == 0) + { return (tempArr[size / 2 - 1] + tempArr[size / 2]) / 2.0; - } else { + } + else + { return tempArr[size / 2]; } } -static void AD5940_EnableAdcMaxSaturationIRQ(uint32_t max_code, uint16_t hysteresis) { +static void AD5940_EnableAdcMaxSaturationIRQ(uint32_t max_code, uint16_t hysteresis) +{ // 1) Program the max comparator + hysteresis - AD5940_WriteReg(0x000020B0, max_code); // ADCMAX - AD5940_WriteReg(0x000020B4, hysteresis); // ADCMAXSMEN + AD5940_WriteReg(0x000020B0, max_code); // ADCMAX + AD5940_WriteReg(0x000020B4, hysteresis); // ADCMAXSMEN // 2) Enable FLAG5 in INTC1 (had to move INTC1 off debug, but we weren't really using it -MK) - uint32_t sel1 = AD5940_ReadReg(0x0000300C); // INTCSEL1 - sel1 |= (1u << 5); // INTSEL5: enable ADCMAX fail IRQ on this controller + uint32_t sel1 = AD5940_ReadReg(0x0000300C); // INTCSEL1 + sel1 |= (1u << 5); // INTSEL5: enable ADCMAX fail IRQ on this controller AD5940_WriteReg(0x0000300C, sel1); // 3) Clear any stale flags (write-1-to-clear) - AD5940_WriteReg(0x00003004, (1u << 5)); // INTCCLR: clear ADCMAX fail - if (verboseMode) { - Serial.println("saturationFlagSet"); + AD5940_WriteReg(0x00003004, (1u << 5)); // INTCCLR: clear ADCMAX fail + if (verboseMode) + { + activeSerial->println("saturationFlagSet"); } } -static inline bool AD5940_IsAdcMaxSaturatedAndClear() { +static inline bool AD5940_IsAdcMaxSaturatedAndClear() +{ // Read flags (either INTCFLAG0 or INTCFLAG1 is fine since we enabled in INTC1) - uint32_t f1 = AD5940_ReadReg(0x00003014); // INTCFLAG1 - bool saturated = (f1 & (1u << 5)) != 0; // FLAG5: ADC max fail + uint32_t f1 = AD5940_ReadReg(0x00003014); // INTCFLAG1 + bool saturated = (f1 & (1u << 5)) != 0; // FLAG5: ADC max fail - if (saturated) { + if (saturated) + { // Clear it (write-1-to-clear) - AD5940_WriteReg(0x00003004, (1u << 5)); // INTCCLR: clear ADCMAX fail + AD5940_WriteReg(0x00003004, (1u << 5)); // INTCCLR: clear ADCMAX fail } return saturated; } +// ************************************ Diality ********************************************** -#endif /* ad5940_library_extension_C */ \ No newline at end of file + // Function to measure all successfully initialized units +bool getAllMesaurements(void) +{ + int successCount = 0; + bool retVal = true; // Success + + // Count successful units first + for (int i = 0; i < 6; i++) + { + if (unitStatus[i] == STATUS_SUCCESS) + { + successCount++; + } + } + + // If no successful units, report and return + if (successCount == 0) + { + retVal = false; // Error + // activeSerial->println("No successfully initialized units to measure!"); + return retVal; + } + + AppIMPCfg_Type *pImpedanceCfg; + + // Loop through and measure each successful unit + for (int unit = 1; unit <= 6; unit++) + { + // Skip units that aren't successfully initialized + if (unitStatus[unit - 1] != STATUS_SUCCESS) + { + continue; + } + + // Select the unit + selectUnit(unit); + if (propagateSettingsChanges) + { + AppIMPGetCfg(&pImpedanceCfg); + pImpedanceCfg->bParaChanged = bTRUE; + } + // Allow some settling time after switching units + delay(50); + + // quick patch for fixing spontaneous measurements + unsigned long currentMillis = millis(); + if (currentMillis - previousMillis >= jitterTimeout) + { + // Run multiple settling measurements in a loop + for (int settling = 0; settling < numSettlingMeasurements; settling++) + { + // Run one complete measurement cycle but discard results + AppIMPInit(AppBuff, APPBUFF_SIZE); + AppIMPCtrl(IMPCTRL_START, 0); + + // Wait for completion (simplified polling) + int timeout_count = 0; + while (AD5940_GetMCUIntFlag() != 1 && timeout_count < 1000) + { + delay(1); + timeout_count++; + } + + if (AD5940_GetMCUIntFlag() == 1) + { + AD5940_ClrMCUIntFlag(); + uint32_t dummy_size = APPBUFF_SIZE; + AppIMPISR(AppBuff, &dummy_size); // Discard this data + } + + AppIMPCtrl(IMPCTRL_STOPSYNC, 0); + + // Small delay between settling measurements + if (settling < numSettlingMeasurements - 1) + { + AD5940_Delay10us(1000); // 10ms between measurements + } + } + } + + // Perform impedance measurement + for (int i = 0; i < repeatNumber; i++) + { + AppIMPMeasure(i); + } + float medianMag; + float medianPhase; + + // take median and output results + if (repeatNumber == 1) + { + medianMag = magnitudeArray[0]; + medianPhase = phaseArray[0]; + } + else + { + medianMag = calculateMedian(magnitudeArray, repeatNumber); + medianPhase = calculateMedian(phaseArray, repeatNumber); + } + + // Perform RTD (temperature) measurement + float rtd_res = getRTDMeasurements(rtdVoltageValue); + + previousMillis = millis(); + + // Check for "ERROR: AD5940 wakeup timeout" + if (-1000.0f == rtd_res) + { + // In case of an error, send all 0's + initPacketToDefault(unit); + } + if (false == isSensorInitialized(unit)) + { + // In case the unit is not initialized, send all 0's + initPacketToDefault(unit); + } + // Pack data for case 'g' + else + { + // Pack the sensor data + // The ALY code loops from 1 to 6. Our packet index from 0 to 5. So, index at i-1. + int sensorIdx = unit - 1; + sensorPacket[sensorIdx].sensorNum = unit; // Conductivity Sensor number i.e unit 1 to 6. + sensorPacket[sensorIdx].impFreq = storedFrequency; // Impedance Frequency + sensorPacket[sensorIdx].impDataPoints = 1; // Impedance Data Points. Hard coded to 1 + sensorPacket[sensorIdx].impRzMag = medianMag; // Value of medianMag + sensorPacket[sensorIdx].impRzPhase = medianPhase; // Value of medianPhase + sensorPacket[sensorIdx].rtdFreq = 0; // RTD Frequency. Hard coded to 0.0 + sensorPacket[sensorIdx].rtdDataPoints = 1; // Impedance Data Points. Hard coded to 1 + sensorPacket[sensorIdx].rtdRzMag = rtd_res; // Value of rtd_resistance + sensorPacket[sensorIdx].rtdRzPhase = 0.0; // RTD Rz Phase. Hard coded to 0.0 + + // Reusing this function for case 'm' + if (sendSensorData) + { + singleSensorPacket.sensorNum = sensorPacket[sensorIdx].sensorNum; + singleSensorPacket.impFreq = sensorPacket[sensorIdx].impFreq; // Impedance Frequency + singleSensorPacket.impDataPoints = sensorPacket[sensorIdx].impDataPoints; // Impedance Data Points. Hard coded to 1 + singleSensorPacket.impRzMag = sensorPacket[sensorIdx].impRzMag; // Value of medianMag + singleSensorPacket.impRzPhase = sensorPacket[sensorIdx].impRzPhase; // Value of medianPhase + singleSensorPacket.rtdFreq = sensorPacket[sensorIdx].rtdFreq; // RTD Frequency. Hard coded to 0.0 + singleSensorPacket.rtdDataPoints = sensorPacket[sensorIdx].rtdDataPoints; // Impedance Data Points. Hard coded to 1 + singleSensorPacket.rtdRzMag = sensorPacket[sensorIdx].rtdRzMag; // Value of rtd_resistance + singleSensorPacket.rtdRzPhase = sensorPacket[sensorIdx].rtdRzPhase; // RTD Rz Phase. Hard coded to 0.0 + + activeSerial->write((uint8_t *)&singleSensorPacket, sizeof(singleSensorPacket)); + } + } + } + + propagateSettingsChanges = 0; + return retVal; +} + +void getSelectedSensorMeasurements(void) +{ + // quick patch for fixing spontaneous measurements + unsigned long currentMillis = millis(); + if (currentMillis - previousMillis >= jitterTimeout) + { + + // Run multiple settling measurements in a loop + for (int settling = 0; settling < numSettlingMeasurements; settling++) + { + if (settling == 0) + { + // activeSerial->println("Performing settling measurements..."); + } + if (verboseMode) + { + // activeSerial->printf(" Settling Measurement#: %u ", settling); + // activeSerial->println(" "); + } + // Run one complete measurement cycle but discard results + AppIMPInit(AppBuff, APPBUFF_SIZE); + AppIMPCtrl(IMPCTRL_START, 0); + + // Wait for completion (simplified polling) + int timeout_count = 0; + while (AD5940_GetMCUIntFlag() != 1 && timeout_count < 1000) + { + delay(1); + timeout_count++; + } + + if (AD5940_GetMCUIntFlag() == 1) + { + AD5940_ClrMCUIntFlag(); + uint32_t dummy_size = APPBUFF_SIZE; + AppIMPISR(AppBuff, &dummy_size); // Discard this data + } + + AppIMPCtrl(IMPCTRL_STOPSYNC, 0); + + // Small delay between settling measurements + if (settling < numSettlingMeasurements - 1) + { + AD5940_Delay10us(1000); // 10ms between measurements + } + } + if (verboseMode) + { + // activeSerial->println(" End Settling Measurements"); + } + } + + if (verboseMode) + { + // activeSerial->println("Conductivity"); + // activeSerial->print(" Conductivity TIA Resistor: "); + switch (DEFAULT_RTIA) + { + case HSTIARTIA_200: + // activeSerial->println("200 Ohm"); + break; + case HSTIARTIA_1K: + // activeSerial->println("1K Ohm"); + break; + case HSTIARTIA_5K: + // activeSerial->println("5K Ohm"); + break; + case HSTIARTIA_10K: + // activeSerial->println("10K Ohm"); + break; + case HSTIARTIA_20K: + // activeSerial->println("20K Ohm"); + break; + case HSTIARTIA_40K: + // activeSerial->println("40K Ohm"); + break; + case HSTIARTIA_80K: + // activeSerial->println("80K Ohm"); + break; + case HSTIARTIA_160K: + // activeSerial->println("160K Ohm"); + break; + default: + // activeSerial->println("Unknown"); + break; + } + } + // Perform impedance measurement + for (int i = 0; i < repeatNumber; i++) + { + AppIMPMeasure(i); + } + if (verboseMode) + { + if (AD5940_IsAdcMaxSaturatedAndClear()) + { + // activeSerial->println("SaturationFlag tripped!"); + } + else + { + // activeSerial->println("SaturationFlag NOT tripped!"); + } + } + float medianMag; + float medianPhase; + // take median and output results + if (repeatNumber == 1) + { + medianMag = magnitudeArray[0]; + medianPhase = phaseArray[0]; + } + else + { + medianMag = calculateMedian(magnitudeArray, repeatNumber); + medianPhase = calculateMedian(phaseArray, repeatNumber); + } + + // activeSerial->printf("Freq: %.2f Hz ", storedFrequency); + + // Print the number of data points + // activeSerial->printf("DataPoints: %lu ", 1); + + // Print magnitude in ohms and phase in degrees + // Note: Converting phase from radians to degrees (phase * 180 / π) + // activeSerial->printf("RzMag: %.2f Ohm, RzPhase: %.2f deg", + // medianMag, + // medianPhase); + + // Add separator and perform RTD (temperature) measurement + // activeSerial->print(";"); + float rtd_res = getRTDMeasurements(rtdVoltageValue); + // activeSerial->print("*"); + previousMillis = millis(); + + // Check for "ERROR: AD5940 wakeup timeout" + if (-1000.0f == rtd_res) + { + // In case of an error, send all 0's + initSinglePacketToDefault(); + } + else + { + singleSensorPacket.impFreq = storedFrequency; // Impedance Frequency + singleSensorPacket.impDataPoints = 1; // Impedance Data Points. Hard coded to 1 + singleSensorPacket.impRzMag = medianMag; // Value of medianMag + singleSensorPacket.impRzPhase = medianPhase; // Value of medianPhase + singleSensorPacket.rtdFreq = 0; // RTD Frequency. Hard coded to 0.0 + singleSensorPacket.rtdDataPoints = 1; // Impedance Data Points. Hard coded to 1 + singleSensorPacket.rtdRzMag = rtd_res; // Value of rtd_resistance + singleSensorPacket.rtdRzPhase = 0.0; // RTD Rz Phase. Hard coded to 0.0 + } + activeSerial->write((uint8_t *)&singleSensorPacket, sizeof(singleSensorPacket)); +} + +float getRTDMeasurements(float sensor_mV) +{ + AFERefCfg_Type aferef_cfg; + HSLoopCfg_Type HsLoopCfg; + DSPCfg_Type dsp_cfg; + + uint32_t adcCode_rtd, adcCode_ref; + float volt_rtd, volt_ref; + float rtd_resistance; + const float RtdRefRes = REF_RESISTOR_VALUE; + + // Wake up the AD5940 from low power mode + if (AD5940_WakeUp(10) > 10) + { + // activeSerial->println("ERROR: AD5940 wakeup timeout"); + return -1000.0f; + } + + // Reset LPDAC configuration before RTD measurement + LPDACCfg_Type lpdac_reset = {0}; + lpdac_reset.PowerEn = bFALSE; + AD5940_LPDACCfgS(&lpdac_reset); + + // Configure High-Speed Loop + HsLoopCfg.HsDacCfg.ExcitBufGain = EXCITBUFGAIN_2; + HsLoopCfg.HsDacCfg.HsDacGain = HSDACGAIN_1; + HsLoopCfg.HsDacCfg.HsDacUpdateRate = 7; + + // TIA settings + HsLoopCfg.HsTiaCfg.DiodeClose = bFALSE; + HsLoopCfg.HsTiaCfg.HstiaBias = HSTIABIAS_1P1; + HsLoopCfg.HsTiaCfg.HstiaCtia = 32; + HsLoopCfg.HsTiaCfg.HstiaDeRload = HSTIADERLOAD_OPEN; + HsLoopCfg.HsTiaCfg.HstiaDeRtia = HSTIADERTIA_OPEN; + HsLoopCfg.HsTiaCfg.HstiaRtiaSel = RTD_RTIA; + + // Switch matrix for RTD measurement + HsLoopCfg.SWMatCfg.Dswitch = RTD_DSWITCH; + HsLoopCfg.SWMatCfg.Pswitch = RTD_PSWITCH; + HsLoopCfg.SWMatCfg.Nswitch = RTD_NSWITCH; + HsLoopCfg.SWMatCfg.Tswitch = RTD_TSWITCH; + + // Configure waveform generator + HsLoopCfg.WgCfg.WgType = WGTYPE_MMR; + HsLoopCfg.WgCfg.GainCalEn = bFALSE; + HsLoopCfg.WgCfg.OffsetCalEn = bFALSE; + uint32_t code = mV_to_WgCode(sensor_mV); + HsLoopCfg.WgCfg.WgCode = code; + + // Apply configuration twice (didn't seem to stick unless we did) + AD5940_HSLoopCfgS(&HsLoopCfg); + AD5940_HSLoopCfgS(&HsLoopCfg); + + // Configure ADC + dsp_cfg.ADCBaseCfg.ADCMuxN = ADCMUXN_HSTIA_N; + dsp_cfg.ADCBaseCfg.ADCMuxP = ADCMUXP_HSTIA_P; + dsp_cfg.ADCBaseCfg.ADCPga = ADCPGA_1; + + dsp_cfg.ADCFilterCfg.ADCAvgNum = ADCAVGNUM_16; + dsp_cfg.ADCFilterCfg.ADCRate = ADCRATE_800KHZ; + dsp_cfg.ADCFilterCfg.ADCSinc2Osr = ADCSINC2OSR_44; + dsp_cfg.ADCFilterCfg.ADCSinc3Osr = ADCSINC3OSR_4; + dsp_cfg.ADCFilterCfg.BpNotch = bTRUE; + dsp_cfg.ADCFilterCfg.BpSinc3 = bTRUE; + dsp_cfg.ADCFilterCfg.Sinc2NotchEnable = bTRUE; + + AD5940_DSPCfgS(&dsp_cfg); + + // Enable AFE blocks + AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR | AFECTRL_WG | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR | AFECTRL_SINC2NOTCH | AFECTRL_ADCPWR, + bTRUE); + + // Restart waveform generator with proper sequence + AD5940_AFECtrlS(AFECTRL_WG, bFALSE); + AD5940_Delay10us(1000); // 10ms settling + AD5940_AFECtrlS(AFECTRL_WG, bTRUE); + + // Extended settling time for RTD measurement + AD5940_Delay10us(500); // 5ms settling time + + // Sample RTD with improved averaging + sampleADC(1); + uint64_t accRTD = 0; + for (uint32_t i = 0; i < NUM_RTD_SAMPLES; ++i) + { + accRTD += sampleADC(1); // Increased sampling delay + + AD5940_Delay10us(10); // Small delay between samples + } + adcCode_rtd = accRTD / NUM_RTD_SAMPLES; + + // Reconfigure switch matrix for reference resistor + HsLoopCfg.SWMatCfg.Dswitch = SWD_RCAL0; + HsLoopCfg.SWMatCfg.Pswitch = SWP_RCAL0; + HsLoopCfg.SWMatCfg.Nswitch = SWN_RCAL1; + HsLoopCfg.SWMatCfg.Tswitch = SWT_RCAL1 | SWT_TRTIA; + + // Apply new switch configuration + AD5940_SWMatrixCfgS(&HsLoopCfg.SWMatCfg); + + // Additional settling time after switch change + AD5940_Delay10us(500); // 5ms settling + sampleADC(1); + // Sample reference resistor + uint64_t accREF = 0; + for (uint32_t i = 0; i < NUM_RTD_SAMPLES; ++i) + { + accREF += sampleADC(1); + AD5940_Delay10us(10); + } + adcCode_ref = accREF / NUM_RTD_SAMPLES; + + // reset switch matrix + resetSwitchMatrix(); + + // Disable all AFE blocks that were enabled + AD5940_AFECtrlS(AFECTRL_HSTIAPWR | AFECTRL_INAMPPWR | AFECTRL_EXTBUFPWR | AFECTRL_WG | AFECTRL_DACREFPWR | AFECTRL_HSDACPWR | AFECTRL_SINC2NOTCH | AFECTRL_ADCPWR, bFALSE); + + // Convert to voltages + volt_ref = AD5940_ADCCode2Volt(adcCode_ref, ADCPGA_1, 1.82); + volt_rtd = AD5940_ADCCode2Volt(adcCode_rtd, ADCPGA_1, 1.82); + + // Calculate resistance + rtd_resistance = RtdRefRes * (volt_ref / volt_rtd); + + // Verbose output + if (verboseMode) + { + activeSerial->println(" "); + + // activeSerial->println("RTD Measurement"); + // activeSerial->print(" RCal Averaged Measurement: "); + activeSerial->println(adcCode_ref); + // activeSerial->print(" RTD Averaged Measurement: "); + activeSerial->println(adcCode_rtd); + // activeSerial->printf(" NUM_SAMPLES: %u", NUM_RTD_SAMPLES); + activeSerial->println(" "); + // activeSerial->print(" RTD TIA Resistor: "); + switch (RTD_RTIA) + { + case HSTIARTIA_200: + // activeSerial->println("200 Ohm"); + break; + case HSTIARTIA_1K: + // activeSerial->println("1K Ohm"); + break; + case HSTIARTIA_5K: + // activeSerial->println("5K Ohm"); + break; + case HSTIARTIA_10K: + // activeSerial->println("10K Ohm"); + break; + case HSTIARTIA_20K: + // activeSerial->println("20K Ohm"); + break; + case HSTIARTIA_40K: + // activeSerial->println("40K Ohm"); + break; + case HSTIARTIA_80K: + // activeSerial->println("80K Ohm"); + break; + case HSTIARTIA_160K: + // activeSerial->println("160K Ohm"); + break; + default: + // activeSerial->println("Unknown"); + break; + } + } + return rtd_resistance; +} + +void updateEEPROMdata(String command) +{ + updateEepromStatus = UPDATE_EEPROM_STATUS_SUCCESS; + int firstComma = command.indexOf(','); + if (firstComma < 0) + { + updateEepromStatus = UPDATE_EEPROM_STATUS_INVALID_CMD; + // activeSerial->println("Error: Invalid save command format"); + return; + } + String dataStr = command.substring(firstComma + 1); + + // Calculate maximum values we can store in AT25010B (128 bytes) + // DOUBLE_COUNT * 8 + float_count * 4 <= 128 + int maxFloats = (128 - (DOUBLE_COUNT * 8)) / 4; + int maxTotal = DOUBLE_COUNT + maxFloats; + + double doubleValues[DOUBLE_COUNT]; + float floatValues[maxFloats]; + int valueIndex = 0; + + // Parse values + while (valueIndex < maxTotal && dataStr.length() > 0) + { + int commaPos = dataStr.indexOf(','); + String valueStr; + if (commaPos >= 0) + { + valueStr = dataStr.substring(0, commaPos); + dataStr = dataStr.substring(commaPos + 1); + } + else + { + // Last value + valueStr = dataStr; + dataStr = ""; // Clear dataStr to end the loop + } + + // Convert string and store in appropriate array + if (valueStr.length() > 0) + { + if (valueIndex < DOUBLE_COUNT) + { + // Store as double + doubleValues[valueIndex] = strtod(valueStr.c_str(), NULL); + } + else + { + // Store as float + floatValues[valueIndex - DOUBLE_COUNT] = strtof(valueStr.c_str(), NULL); + } + valueIndex++; + } + } + + // Check if we got at least 1 value + if (valueIndex == 0) + { + // activeSerial->println("Error: No valid values found"); + updateEepromStatus = UPDATE_EEPROM_STATUS_NO_VALID_VALUES; + return; + } + if (valueIndex > maxTotal) + { + updateEepromStatus = UPDATE_EEPROM_STATUS_TOO_MANY_VALUES; + return; + } + + int doubleCount = min(valueIndex, DOUBLE_COUNT); + int floatCount = max(0, valueIndex - DOUBLE_COUNT); + + unsigned int baseAddress = 0; + writeMixedValues(baseAddress, doubleValues, doubleCount, floatValues, floatCount); + // activeSerial->println("save attempted"); + + // Read back the values to confirm they were written correctly + double readDoubles[DOUBLE_COUNT]; + float readFloats[maxFloats]; + readMixedValues(baseAddress, readDoubles, doubleCount, readFloats, floatCount); + + // activeSerial->println("Verification - Values read back from EEPROM:"); + bool DoubleVerificationSuccess = true; + bool FloatVerificationSuccess = true; + + // Verify doubles + for (int i = 0; i < doubleCount; i++) + { + // activeSerial->print(i); + // activeSerial->print(" (double): "); + // activeSerial->println(readDoubles[i], 10); + if (abs(doubleValues[i] - readDoubles[i]) > 0.000000001) + { + DoubleVerificationSuccess = false; + break; + // activeSerial->print("Warning: Double value "); + // activeSerial->print(i); + // activeSerial->println(" does not match exactly what was written"); + } + } + + // Verify floats + for (int i = 0; i < floatCount; i++) + { + // activeSerial->print(i + DOUBLE_COUNT); + // activeSerial->print(" (float): "); + // activeSerial->println(readFloats[i], 6); + if (abs(floatValues[i] - readFloats[i]) > 0.000001) + { + FloatVerificationSuccess = false; + break; + // activeSerial->print("Warning: Float value "); + // activeSerial->print(i + DOUBLE_COUNT); + // activeSerial->println(" does not match exactly what was written"); + } + } + + if (DoubleVerificationSuccess && FloatVerificationSuccess) + { + updateEepromStatus = UPDATE_EEPROM_STATUS_SUCCESS; + // activeSerial->println("Verification successful: All values match!"); + } + else + { + updateEepromStatus = UPDATE_EEPROM_STATUS_VALUES_VERIFICATION_FAILED; + } +} + +void getEEPROMdata(void) +{ + unsigned int baseAddress = 0; // Same address used in save + + // Calculate maximum values we can store + int maxFloats = (128 - (DOUBLE_COUNT * 8)) / 4; + + double doubleValues[DOUBLE_COUNT]; + float floatValues[maxFloats]; + + readMixedValues(baseAddress, doubleValues, DOUBLE_COUNT, floatValues, maxFloats); + + // Display doubles + for (int i = 0; i < DOUBLE_COUNT; i++) + { + eepromDataPacket.doubleValue[i] = doubleValues[i]; + } + + // Display floats + for (int i = 0; i < maxFloats; i++) + { + eepromDataPacket.floatValue[i] = floatValues[i]; + } + + activeSerial->write((uint8_t *)&eepromDataPacket, sizeof(eepromDataPacket)); +} + +void handleMstCommand(String cmd) +{ + // Check if the first 4 characters are "mst," + if (cmd.startsWith("mst,")) + { + // Remove "mst," prefix + String dataPart = cmd.substring(4); + + // Split by comma + int idx = 0; + int start = 0; + int end = 0; + + while (idx < MAX_CONDUCTIVITY_MST_PARAM_IDX && start < dataPart.length()) + { + end = dataPart.indexOf(',', start); + if (end == -1) + end = dataPart.length(); // Last value + + String update_param = "mst,"; + String token = dataPart.substring(start, end); + + // Assign to tempSettings + switch (idx) + { + case CONDUCTIVITY_MST_PARAM_IDX_SINFREQ: + tempSettings.SinFreq = token.toFloat(); + update_param += String(cfgSettingsParam[idx]) + "," + String(tempSettings.SinFreq, 4); + break; + + case CONDUCTIVITY_MST_PARAM_IDX_DACPP: + tempSettings.DacVoltPP = token.toFloat(); + update_param += String(cfgSettingsParam[idx]) + "," + String(tempSettings.DacVoltPP, 4); + break; + + case CONDUCTIVITY_MST_PARAM_IDX_BIAS: + tempSettings.BiasVolt = token.toFloat(); + update_param += String(cfgSettingsParam[idx]) + "," + String(tempSettings.BiasVolt, 4); + break; + + case CONDUCTIVITY_MST_PARAM_IDX_RTIA: + tempSettings.HstiaRtiaSel = token.toInt(); + update_param += String(cfgSettingsParam[idx]) + "," + String(tempSettings.HstiaRtiaSel); + break; + + case CONDUCTIVITY_MST_PARAM_IDX_PGA: + tempSettings.AdcPgaGain = token.toInt(); + update_param += String(cfgSettingsParam[idx]) + "," + String(tempSettings.AdcPgaGain); + break; + + case CONDUCTIVITY_MST_PARAM_IDX_DFTNUM: + tempSettings.DftNum = token.toInt(); + update_param += String(cfgSettingsParam[idx]) + "," + String(tempSettings.DftNum); + break; + + case CONDUCTIVITY_MST_PARAM_IDX_AVGNUM: + tempSettings.ADCAvgNum = token.toInt(); + update_param += String(cfgSettingsParam[idx]) + "," + String(tempSettings.ADCAvgNum); + break; + } + + // Call handleMstCommand for this parameter + updateMeasurementSettings(update_param, idx); + + start = end + 1; + idx++; + } + } +} + +void updateMeasurementSettings(String cmd, uint8_t idx) +{ + if ((idx >= CONDUCTIVITY_MST_PARAM_IDX_SINFREQ) && (idx < MAX_CONDUCTIVITY_MST_PARAM_IDX)) + { + updateCfgStatus[idx] = UPDATE_CFG_STATUS_SUCCESS; + } + else + { + return; + } + + // Command format: cfg,parameter,value + // Examples: + // - cfg,sinfreq,1000.0 (Set sine wave frequency to 1000 Hz) + + // Array to hold parsed command parts + String params[3]; + int paramIndex = 0; + int startIndex = 0; + + // Parse command string using comma delimiter + for (uint i = 0; i < cmd.length(); i++) + { + if (cmd.charAt(i) == ',') + { + params[paramIndex] = cmd.substring(startIndex, i); + startIndex = i + 1; + paramIndex++; + + // Safety check to prevent array overflow + if (paramIndex >= 3) + break; + } + } + + // Get last parameter (after the last comma) + if (paramIndex < 3) + { + params[paramIndex] = cmd.substring(startIndex); + } + + // Get configuration structure pointer + AppIMPCfg_Type *pImpedanceCfg; + AppIMPGetCfg(&pImpedanceCfg); + + // Extract parameter name and value + String param = params[1]; + String value = params[2]; + + // Handle special commands for saving/recalling configurations + if (param == "save") + { + saveCurrentConfig(); + // activeSerial->println("Configuration saved"); + return; + } + else if (param == "recall") + { + recallSavedConfig(); + // activeSerial->println("Configuration recalled"); + return; + } + + // Process different configuration parameters + if (param == "sinfreq") + { + // Set sine wave frequency (Hz) + pImpedanceCfg->SinFreq = value.toFloat(); + // activeSerial->printf("Set SinFreq to: %.2f Hz\n", pImpedanceCfg->SinFreq); + } + else if (param == "dacpp") + { + // Set DAC peak-to-peak voltage (mV) + pImpedanceCfg->DacVoltPP = value.toFloat(); + // activeSerial->printf("Set DacVoltPP to: %.2f mV\n", pImpedanceCfg->DacVoltPP); + } + else if (param == "bias") + { + // Set DC bias voltage (mV) + pImpedanceCfg->BiasVolt = value.toFloat(); + // activeSerial->printf("Set BiasVolt to: %.2f mV\n", pImpedanceCfg->BiasVolt); + } + else if (param == "rtia") + { + // Set TIA (Transimpedance Amplifier) feedback resistor value + uint32_t rtia = DEFAULT_RTIA; // default value + int valueTest = value.toInt(); + // activeSerial->print("Value:"); + // activeSerial->println(value); + // Map string values to corresponding constants + if (valueTest == 0) + rtia = HSTIARTIA_200; + else if (valueTest == 1) + rtia = HSTIARTIA_1K; + else if (valueTest == 2) + rtia = HSTIARTIA_5K; + else if (valueTest == 3) + rtia = HSTIARTIA_10K; + else if (valueTest == 4) + rtia = HSTIARTIA_20K; + else if (valueTest == 5) + rtia = HSTIARTIA_40K; + else if (valueTest == 6) + rtia = HSTIARTIA_80K; + else if (valueTest == 7) + rtia = HSTIARTIA_160K; + else + updateCfgStatus[idx] = UPDATE_CFG_STATUS_NO_CHANGE_TO_TIA; + // activeSerial->print("no change to tia"); + + pImpedanceCfg->HstiaRtiaSel = rtia; + // activeSerial->printf("Set RTIA to: %s\n", value.c_str()); + currentTIA = rtia; + // activeSerial->print("currentTIA: "); + // activeSerial->println(currentTIA); + } + else if (param == "pga") + { + // Set ADC Programmable Gain Amplifier gain + uint32_t pga = ADCPGA_1; // default value (1x gain) + + // Map string values to corresponding constants + if (value == "1") + pga = ADCPGA_1; + else if (value == "1.5") + pga = ADCPGA_1P5; + else if (value == "2") + pga = ADCPGA_2; + else if (value == "4") + pga = ADCPGA_4; + else if (value == "9") + pga = ADCPGA_9; + else + updateCfgStatus[idx] = UPDATE_CFG_STATUS_NO_CHANGE_TO_PGA; + + pImpedanceCfg->AdcPgaGain = pga; + // activeSerial->printf("Set PGA gain to: %s\n", value.c_str()); + } + else if (param == "dftnum") + { + // Set DFT (Discrete Fourier Transform) number of points + uint32_t dft = DFTNUM_4096; // default value (4096 points) + + // Map string values to corresponding constants + if (value == "4096") + dft = DFTNUM_4096; + else if (value == "2048") + dft = DFTNUM_2048; + else if (value == "1024") + dft = DFTNUM_1024; + else if (value == "512") + dft = DFTNUM_512; + else if (value == "256") + dft = DFTNUM_256; + else + updateCfgStatus[idx] = UPDATE_CFG_STATUS_NO_CHANGE_TO_DFT_NUM; + + pImpedanceCfg->DftNum = dft; + // activeSerial->printf("Set DFT number to: %s\n", value.c_str()); + } + else if (param == "avgnum") + { + // Set ADC averaging number (how many samples to average) + uint32_t avg = ADCAVGNUM_16; // default value (16 samples) + + // Map string values to corresponding constants + if (value == "2") + avg = ADCAVGNUM_2; + else if (value == "4") + avg = ADCAVGNUM_4; + else if (value == "8") + avg = ADCAVGNUM_8; + else if (value == "16") + avg = ADCAVGNUM_16; + else + updateCfgStatus[idx] = UPDATE_CFG_STATUS_NO_CHANGE_TO_AVG_NUM; + + pImpedanceCfg->ADCAvgNum = avg; + // activeSerial->printf("Set ADC average number to: %s\n", value.c_str()); + } + else if (param == "sweep") + { + // Enable/disable frequency sweep mode + bool enableSweep = (value == "1" || value.equalsIgnoreCase("true")); + pImpedanceCfg->SweepCfg.SweepEn = enableSweep ? bTRUE : bFALSE; + // activeSerial->printf("Set sweep enable to: %d\n", pImpedanceCfg->SweepCfg.SweepEn); + } + else if (param == "sweepstart") + { + // Set sweep start frequency (Hz) + pImpedanceCfg->SweepCfg.SweepStart = value.toFloat(); + // activeSerial->printf("Set sweep start frequency to: %.2f Hz\n", pImpedanceCfg->SweepCfg.SweepStart); + } + else if (param == "sweepstop") + { + // Set sweep stop frequency (Hz) + pImpedanceCfg->SweepCfg.SweepStop = value.toFloat(); + // activeSerial->printf("Set sweep stop frequency to: %.2f Hz\n", pImpedanceCfg->SweepCfg.SweepStop); + } + else if (param == "sweeppoints") + { + // Set number of frequency points in the sweep + pImpedanceCfg->SweepCfg.SweepPoints = value.toInt(); + // activeSerial->printf("Set sweep points to: %d\n", pImpedanceCfg->SweepCfg.SweepPoints); + } + else if (param == "sweeplog") + { + // Set logarithmic (true) or linear (false) frequency spacing + bool logSpacing = (value == "1" || value.equalsIgnoreCase("true")); + pImpedanceCfg->SweepCfg.SweepLog = logSpacing ? bTRUE : bFALSE; + // activeSerial->printf("Set sweep log mode to: %d\n", pImpedanceCfg->SweepCfg.SweepLog); + } + else + { + // Handle unrecognized parameter + // activeSerial->println("Malformed command: unrecognized parameter"); + updateCfgStatus[idx] = UPDATE_CFG_STATUS_ERR_UNRECOGNIZED_PARAM; + return; + } + + // Mark that parameters have been changed and need to be applied + pImpedanceCfg->bParaChanged = bTRUE; + + // Automatically save the configuration after any successful change + saveCurrentConfig(); +} + +void getMesaurementSettings(void) +{ + // Pointer to store configuration structure + AppIMPCfg_Type *cfg; + + // Get the current impedance measurement configuration + AppIMPGetCfg(&cfg); + + measurementSettingsPacket.SinFreq = cfg->SinFreq; + measurementSettingsPacket.DacVoltPP = cfg->DacVoltPP; + measurementSettingsPacket.BiasVolt = cfg->BiasVolt; + measurementSettingsPacket.HstiaRtiaSel = cfg->HstiaRtiaSel; + measurementSettingsPacket.AdcPgaGain = cfg->AdcPgaGain; + measurementSettingsPacket.DftNum = cfg->DftNum; + measurementSettingsPacket.ADCAvgNum = cfg->ADCAvgNum; + activeSerial->write((uint8_t *)&measurementSettingsPacket, sizeof(measurementSettingsPacket)); +} + +void getInitStatus(void) +{ + // Count units by status + int skippedCount = 0; + int failedCount = 0; + int successCount = 0; + + // If Initialization function has been completely executed + if (true == isInitialized) + { + // Count number of skipped, failed and successful sensor initialization + for (int i = 0; i < 6; i++) + { + if (unitStatus[i] == STATUS_SKIPPED) + { + skippedCount++; + } + else if (unitStatus[i] == STATUS_FAILED) + { + failedCount++; + } + else + { // STATUS_SUCCESS + successCount++; + } + } + + // If any of the sensor was skipped or failed, then + if ((skippedCount > 0) || (failedCount > 0)) + { + // Set init status to failed + initStatus = INIT_STATUS_FAILED; + } + // If all the sensors were initialized successfully, then + else if (successCount == MAX_NUM_OF_SENSORS) + { + // Set the init status to initialized. + initStatus = INIT_STATUS_INITIALIZED; + } + else + { + // Still need to decide, what to do here. + } + } + + activeSerial->write((uint8_t *)&initStatus, sizeof(initStatus)); +} + +void sendAllSensorData(void) +{ + while (sendSensorData) + { + if (activeSerial->available()) + { + String command = activeSerial->readStringUntil('\0'); + if (command.length() > 0) + { + activeSerial->write((uint8_t)1); + break; // Exit if command received + } + } + + else + { + getAllMesaurements(); + } + } +} + +void initSinglePacketToDefault(void) +{ + singleSensorPacket.sensorNum = 0; // Conductivity Sensor number i.e unit 1 to 6. + singleSensorPacket.impFreq = 0.0; // Impedance Frequency + singleSensorPacket.impDataPoints = 0; // Impedance Data Points. Hard coded to 1 + singleSensorPacket.impRzMag = 0.0; // Value of medianMag + singleSensorPacket.impRzPhase = 0.0; // Value of medianPhase + singleSensorPacket.rtdFreq = 0.0; // RTD Frequency. Hard coded to 0.0 + singleSensorPacket.rtdDataPoints = 0; // Impedance Data Points. Hard coded to 1 + singleSensorPacket.rtdRzMag = 0.0; // Value of rtd_resistance + singleSensorPacket.rtdRzPhase = 0.0; // RTD Rz Phase. Hard coded to 0.0 +} + +void initPacketToDefault(int unit) +{ + if (true == isSensorValid(unit)) + { + // Pack the sensor data + // The ALY code loops from 1 to 6. Our packet index from 0 to 5. So, index at i-1. + int sensorIdx = unit - 1; + sensorPacket[sensorIdx].sensorNum = 0; // Conductivity Sensor number i.e unit 1 to 6. + sensorPacket[sensorIdx].impFreq = 0.0; // Impedance Frequency + sensorPacket[sensorIdx].impDataPoints = 0; // Impedance Data Points. Hard coded to 1 + sensorPacket[sensorIdx].impRzMag = 0.0; // Value of medianMag + sensorPacket[sensorIdx].impRzPhase = 0.0; // Value of medianPhase + sensorPacket[sensorIdx].rtdFreq = 0.0; // RTD Frequency. Hard coded to 0.0 + sensorPacket[sensorIdx].rtdDataPoints = 0; // Impedance Data Points. Hard coded to 1 + sensorPacket[sensorIdx].rtdRzMag = 0.0; // Value of rtd_resistance + sensorPacket[sensorIdx].rtdRzPhase = 0.0; // RTD Rz Phase. Hard coded to 0.0 + } +} + +bool isSensorValid(int unit) +{ + bool retVal = false; + if ((unit >= 1) && (unit <= 6)) + { + retVal = true; + } + return retVal; +} + +bool isSensorInitialized(int unit) +{ + bool retVal = false; + if (true == isSensorValid(unit)) + { + int sensorIdx = unit - 1; + if (unitStatus[sensorIdx] == STATUS_SUCCESS) + { + retVal = true; + } + } + return retVal; +} +#endif /* ad5940_library_extension_C */