From 9b64c571addfabb4088d1dbc0ead9b0472e3b312 Mon Sep 17 00:00:00 2001 From: Geoff McIntyre Date: Mon, 1 Jun 2026 20:16:24 +0000 Subject: [PATCH 1/8] Address PR #1 code review feedback Core driver (src/sfTk/sfDevADE7953.h/.cpp): - Trim implementation-detail block from the header comment. - Add typedef to the PGA gain and ZX edge enums for C compatibility. - Move all register addresses and magic values into the class as protected static members, dropping the sfADE7953 prefix. - Rename the I2C address constant (drop "default"; single fixed address). - Redesign the API to return sfTkError_t with values passed by reference, so communication failures are distinguishable from valid data. - Remove the unused DEBUG_SERIAL_PRINTS block and the redundant per-method null-pointer checks (the Toolkit bus already guards these). - Move isConnected() into the base class; begin() now verifies the device is present before writing any configuration. - Apply sensible defaults in begin() (4x PGA gain on both channels, HPF on). - Rename setZXISource/getZXISource -> setZXISourceChannel/getZXISourceChannel. Bug fixes: - reset() now re-applies the unlock/optimize sequence and defaults (the OPTIMIZE register reverts to POR after a software reset). - setGainV() rejects ADE7953_PGA_GAIN_22 (invalid for the voltage channel). - getCurrentA/B() read the active PGA gain per call, fixing the example bug where a hard-coded multiplier disagreed with the configured gain. New convenience methods: - getCurrentA()/getCurrentB() return RMS current in amps; configurable CT ratio and burden resistor with board defaults. - Float-based digital gain helpers (setDigitalGainIA/IB(float), getDigitalGainIA/IB(float&)). Wrapper + metadata: - SparkFun_ADE7953.h: drop @code usage blocks, ping the device before begin(), pass the I2C address by value, isConnected() now inherited from the base class. - library.properties: remove the unneeded includes line. Examples: - Example 1 simplified (retry instead of freeze, no memset/rolling average, uses getCurrentA()). - All examples updated to the error-code API and new method names. - Example 7 reworked to demonstrate error handling and a 64-bit calibration accumulator (fixes the overflow), with a note on the AIRMSOS squared domain. - Example 8 removed (it duplicated Examples 2 and 6). --- .../Example01_BasicCurrentReading.ino | 178 +--- .../Example02_DualChannelCurrent.ino | 46 +- .../Example03_GainConfiguration.ino | 78 +- .../Example04_PeakDetection.ino | 51 +- .../Example05_ZeroCrossing.ino | 53 +- .../Example06_InterruptPin.ino | 77 +- .../Example07_CalibrationOffset.ino | 133 ++- .../Example08_ChannelBWithInterrupt.ino | 154 ---- .../Example09_DiagnosticTool.ino | 287 ++---- src/SparkFun_ADE7953.h | 107 +-- src/sfTk/sfDevADE7953.cpp | 845 +++++++---------- src/sfTk/sfDevADE7953.h | 858 ++++++++++-------- 12 files changed, 1146 insertions(+), 1721 deletions(-) delete mode 100644 examples/Example08_ChannelBWithInterrupt/Example08_ChannelBWithInterrupt.ino diff --git a/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino b/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino index e4a300d..a473d7e 100644 --- a/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino +++ b/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino @@ -1,14 +1,9 @@ /* Example 01 - Basic Current Reading - Output is formatted for the Arduino Serial Plotter (labeled values): - Raw I:XXXXX;Filtered I:XXXXX;Averaged:XXXXX;Amps:X.XXXX - - The PGA gain is set to 4x for the 5.6 ohm shunt resistor on the - SparkFun Qwiic Current Sensor board. - - Zero calibration runs automatically at startup — make sure NO current - is flowing through the CT clamp when powering on. + The simplest way to read current from the SparkFun Qwiic Current Sensor (ADE7953). + begin() configures sensible defaults (PGA gain and high-pass filter) for you, so all + this sketch has to do is ask for the current in amps. SparkFun Electronics Date: 2025 @@ -19,7 +14,7 @@ IoT RedBoard --> ADE7953 QWIIC --> QWIIC - Serial.print it out at 115200 baud to serial monitor / serial plotter. + Open the Serial Monitor at 115200 baud. Feel like supporting our work? Buy a board from SparkFun! https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html @@ -29,167 +24,36 @@ SfeADE7953ArdI2C mySensor; -// ========================= Configuration ========================== -// Modify these for your CT / burden resistor setup. -const float CT_RATIO = 2000.0; // CT turns ratio (30A:15mA = 2000:1) -const float BURDEN_RESISTOR = 5.6; // Burden resistor in ohms -const float FULL_SCALE_CODE = 5928256.0; // ADE7953 full-scale IRMS code -const float FULL_SCALE_VRMS = 0.35355; // 0.5V peak / sqrt(2) - -const int AVERAGE_WINDOW = 20; // Rolling average sample count -const int SAMPLE_INTERVAL_MS = 100; // Milliseconds between reads -const int ZERO_CAL_SAMPLES = 50; // Samples to average for zero cal - -// ========================= State ================================== -uint32_t irmsBuffer[AVERAGE_WINDOW]; -int bufferIndex = 0; -bool bufferFull = false; -int32_t zeroOffset = 0; // Software zero offset (raw IRMS units) -float pgaMultiplier = 4.0; // Tracks current PGA for amps conversion - -// ========================= Helpers ================================ - -/// @brief Convert a corrected raw IRMS value to amps. -float rawToAmps(int32_t rawValue) -{ - if (rawValue <= 0) - return 0.0; - - // Pin voltage RMS = raw / fullScaleCode * (fullScaleVrms / pgaGain) - // Secondary current = pinVrms / burdenResistor - // Primary current = secondaryCurrent * ctRatio - float fullScaleAmps = (FULL_SCALE_VRMS / pgaMultiplier) / BURDEN_RESISTOR * CT_RATIO; - return (float)rawValue / FULL_SCALE_CODE * fullScaleAmps; -} - -/// @brief Add a sample to the rolling average buffer and return the average. -uint32_t updateAverage(uint32_t sample) -{ - irmsBuffer[bufferIndex] = sample; - bufferIndex++; - if (bufferIndex >= AVERAGE_WINDOW) - { - bufferIndex = 0; - bufferFull = true; - } - - int count = bufferFull ? AVERAGE_WINDOW : bufferIndex; - uint64_t sum = 0; - for (int i = 0; i < count; i++) - sum += irmsBuffer[i]; - - return (count > 0) ? (uint32_t)(sum / count) : 0; -} - -/// @brief Reset the rolling average buffer. -void resetBuffer() -{ - bufferIndex = 0; - bufferFull = false; - memset(irmsBuffer, 0, sizeof(irmsBuffer)); -} - -// ========================= Auto Zero Calibration ================== - -/// @brief Collect baseline samples with no current flowing and store as offset. -/// Called once during setup — keep CT clamped with no load attached. -void autoZeroCalibration() -{ - Serial.println("Auto zero calibration — ensure NO current is flowing."); - Serial.print("Collecting "); - Serial.print(ZERO_CAL_SAMPLES); - Serial.println(" samples..."); - - uint64_t sum = 0; - for (int i = 0; i < ZERO_CAL_SAMPLES; i++) - { - sum += mySensor.getIRmsA(); - delay(50); - } - - zeroOffset = (int32_t)(sum / ZERO_CAL_SAMPLES); - resetBuffer(); - - Serial.print("Zero offset set to: "); - Serial.println(zeroOffset); - Serial.println(); -} - -// ========================= Setup / Loop =========================== - void setup() { + // Start serial right away so we can report what is happening. Serial.begin(115200); - delay(1000); - Serial.println("ADE7953 Example 01 Enhanced - Basic Current Reading"); + Serial.println("SparkFun ADE7953 Example 1 - Basic Current Reading"); + // Start I2C communication. Wire.begin(); - if (!mySensor.begin()) - { - Serial.println("ADE7953 not found. Please check wiring. Freezing..."); - while (1) - delay(1000); - } - - Serial.print("ADE7953 connected!"); - - // Set PGA gain to 4x for the 5.6 ohm shunt resistor on the board. - if (!mySensor.setGainIA(ADE7953_PGA_GAIN_16)) + // Attempt to connect to the sensor. Keep trying so the message is not missed if the + // Serial Monitor is opened late. + while (mySensor.begin() == false) { - Serial.println("Failed to set gain. Freezing..."); - while (1) - delay(1000); + Serial.println("ADE7953 not connected, check your wiring!"); + delay(1000); } - // Enable HPF to remove DC offset from the current channel. - mySensor.enableHPF(true); - - Serial.println(" Gain: 16x | HPF: enabled"); - - // Let the DSP settle before sampling. - Serial.println("Waiting for DSP to settle..."); - delay(1000); - - memset(irmsBuffer, 0, sizeof(irmsBuffer)); - - // Run automatic zero calibration — no current should be flowing yet. - autoZeroCalibration(); - - Serial.println("Reading current..."); - Serial.println(); + Serial.println("ADE7953 connected!"); } void loop() { - // 1. Read raw IRMS from Channel A. - uint32_t rawIrms = mySensor.getIRmsA(); - - // 2. Subtract zero offset (clamped to 0). - int32_t filtered = (int32_t)rawIrms - zeroOffset; - if (filtered < 0) - filtered = 0; - - // 3. Rolling average of the filtered value. - uint32_t averaged = updateAverage((uint32_t)filtered); + // Read the current on Channel A in amps. + float amps = 0.0; + mySensor.getCurrentA(amps); - // 4. Convert averaged value to real amps. - float amps = rawToAmps(averaged); + // Print the current. + Serial.print("Current (A): "); + Serial.println(amps, 4); - // 5. Output — labeled format for Arduino Serial Plotter. - // Each label:value pair is separated by ; so the plotter - // treats them as separate named channels. - Serial.print("RawI:"); - Serial.print(rawIrms); - Serial.print(","); - Serial.print("FilteredI:"); - Serial.print(filtered); - Serial.print(","); - Serial.print("Averaged:"); - Serial.print(averaged); - Serial.print(","); - Serial.print("Amps:"); - Serial.println(amps); - - delay(SAMPLE_INTERVAL_MS); + // Slow things down so we are not spamming the output. + delay(1000); } diff --git a/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino b/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino index fb375f6..2a3c7e8 100644 --- a/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino +++ b/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino @@ -1,10 +1,12 @@ /* Example 02 - Dual Channel Current - This example reads the RMS current from both Channel A and Channel B - simultaneously and prints them side by side. Channel A is the primary - CT clamp input on the Qwiic connector. Channel B is available via the - optional pinout headers on the board. + Reads the RMS current from both Channel A and Channel B and prints them side by side. + Channel A is the primary CT clamp input on the Qwiic connector. Channel B is available + via the optional pinout headers on the board. + + begin() configures default gain on both channels for you, so this sketch just reads + the current in amps from each channel. SparkFun Electronics Date: 2025 @@ -16,7 +18,7 @@ QWIIC --> QWIIC (Optional) Current source --> Channel B header pins - Serial.print it out at 115200 baud to serial monitor. + Open the Serial Monitor at 115200 baud. Feel like supporting our work? Buy a board from SparkFun! https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html @@ -29,41 +31,33 @@ SfeADE7953ArdI2C mySensor; void setup() { Serial.begin(115200); - delay(1000); - Serial.println("ADE7953 Example 02 - Dual Channel Current"); + Serial.println("SparkFun ADE7953 Example 2 - Dual Channel Current"); Wire.begin(); - if (!mySensor.begin()) + while (mySensor.begin() == false) { - Serial.println("ADE7953 not found. Please check wiring. Freezing..."); - while (1) - delay(1000); + Serial.println("ADE7953 not connected, check your wiring!"); + delay(1000); } Serial.println("ADE7953 connected!"); - - // Set PGA gain to 4x on both channels for the 5.6 ohm shunt resistor. - mySensor.setGainIA(ADE7953_PGA_GAIN_4); - mySensor.setGainIB(ADE7953_PGA_GAIN_4); - - Serial.println("Both channels configured at 4x gain."); Serial.println(); - - // Print column headers. - Serial.println("Channel A (raw)\tChannel B (raw)"); - Serial.println("---------------\t---------------"); + Serial.println("Channel A (A)\tChannel B (A)"); + Serial.println("-------------\t-------------"); } void loop() { - // Read the RMS current from both channels. - uint32_t irmsA = mySensor.getIRmsA(); - uint32_t irmsB = mySensor.getIRmsB(); + // Read the RMS current from both channels in amps. + float ampsA = 0.0; + float ampsB = 0.0; + mySensor.getCurrentA(ampsA); + mySensor.getCurrentB(ampsB); - Serial.print(irmsA); + Serial.print(ampsA, 4); Serial.print("\t\t"); - Serial.println(irmsB); + Serial.println(ampsB, 4); delay(500); } diff --git a/examples/Example03_GainConfiguration/Example03_GainConfiguration.ino b/examples/Example03_GainConfiguration/Example03_GainConfiguration.ino index 6a63213..3cdd4c8 100644 --- a/examples/Example03_GainConfiguration/Example03_GainConfiguration.ino +++ b/examples/Example03_GainConfiguration/Example03_GainConfiguration.ino @@ -1,12 +1,11 @@ /* Example 03 - Gain Configuration - This example demonstrates how to configure the PGA (Programmable Gain - Amplifier) and digital gain on Channel A. The PGA gain should be set - based on the shunt resistor value on the board: - - 5.6 ohm shunt: use 4x gain (ADE7953_PGA_GAIN_4) + Demonstrates how to configure the PGA (Programmable Gain Amplifier) and the digital + (fine) gain on Channel A. - Available PGA gain settings: + The PGA gain is set based on the shunt resistor on the board (4x is the default for the + 5.6 ohm shunt). Available PGA gain settings: ADE7953_PGA_GAIN_1 (1x) ADE7953_PGA_GAIN_2 (2x) ADE7953_PGA_GAIN_4 (4x) <-- default for this board @@ -14,9 +13,8 @@ ADE7953_PGA_GAIN_16 (16x) ADE7953_PGA_GAIN_22 (22x, current channels only) - The digital gain register (AIGAIN) provides fine-tuning on top of the PGA. - Its default value is 0x400000 (unity). Values above this increase gain, - values below decrease it. + The digital gain provides fine-tuning on top of the PGA. This example sets it with a + simple floating-point multiplier (1.0 = unity) instead of a raw register value. SparkFun Electronics Date: 2025 @@ -27,7 +25,7 @@ IoT RedBoard --> ADE7953 QWIIC --> QWIIC - Serial.print it out at 115200 baud to serial monitor. + Open the Serial Monitor at 115200 baud. Feel like supporting our work? Buy a board from SparkFun! https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html @@ -69,53 +67,50 @@ void printGain(sfe_ade7953_pga_gain_t gain) void setup() { Serial.begin(115200); - delay(1000); - Serial.println("ADE7953 Example 03 - Gain Configuration"); + Serial.println("SparkFun ADE7953 Example 3 - Gain Configuration"); Wire.begin(); - if (!mySensor.begin()) + while (mySensor.begin() == false) { - Serial.println("ADE7953 not found. Please check wiring. Freezing..."); - while (1) - delay(1000); + Serial.println("ADE7953 not connected, check your wiring!"); + delay(1000); } Serial.println("ADE7953 connected!"); Serial.println(); - // Read and display the default PGA gain for Channel A. - sfe_ade7953_pga_gain_t currentGain = mySensor.getGainIA(); - Serial.print("Default PGA gain (Channel A): "); + // Read the current PGA gain on Channel A (set to 4x by begin()). + sfe_ade7953_pga_gain_t currentGain; + mySensor.getGainIA(currentGain); + Serial.print("PGA gain (Channel A): "); printGain(currentGain); Serial.println(); - // Set the PGA gain to 4x for the 5.6 ohm shunt on this board. - Serial.println("Setting PGA gain to 4x..."); - mySensor.setGainIA(ADE7953_PGA_GAIN_4); + // Change the PGA gain to 8x. + Serial.println("Setting PGA gain to 8x..."); + mySensor.setGainIA(ADE7953_PGA_GAIN_8); - // Read back to confirm. - currentGain = mySensor.getGainIA(); + mySensor.getGainIA(currentGain); Serial.print("PGA gain is now: "); printGain(currentGain); Serial.println(); Serial.println(); - // Read the default digital gain. - uint32_t digitalGain = mySensor.getDigitalGainIA(); - Serial.print("Default digital gain (AIGAIN): 0x"); - Serial.println(digitalGain, HEX); - - // Set a custom digital gain (10% increase above unity 0x400000). - uint32_t customDigitalGain = 0x466666; // ~1.1x multiplier - Serial.print("Setting digital gain to: 0x"); - Serial.println(customDigitalGain, HEX); - mySensor.setDigitalGainIA(customDigitalGain); - - // Read back to confirm. - digitalGain = mySensor.getDigitalGainIA(); - Serial.print("Digital gain is now: 0x"); - Serial.println(digitalGain, HEX); + // Read the digital gain as a multiplier (1.0 = unity). + float digitalGain = 0.0; + mySensor.getDigitalGainIA(digitalGain); + Serial.print("Digital gain multiplier: "); + Serial.println(digitalGain, 3); + + // Apply a 10% boost using a floating-point multiplier. The library converts it to the + // nearest valid register value for you. + Serial.println("Setting digital gain to 1.1x..."); + mySensor.setDigitalGainIA(1.1f); + + mySensor.getDigitalGainIA(digitalGain); + Serial.print("Digital gain multiplier is now: "); + Serial.println(digitalGain, 3); Serial.println(); Serial.println("Reading current with new gain settings..."); @@ -124,10 +119,11 @@ void setup() void loop() { - uint32_t irms = mySensor.getIRmsA(); + float amps = 0.0; + mySensor.getCurrentA(amps); - Serial.print("IRMS Channel A (raw): "); - Serial.println(irms); + Serial.print("Current (A): "); + Serial.println(amps, 4); delay(500); } diff --git a/examples/Example04_PeakDetection/Example04_PeakDetection.ino b/examples/Example04_PeakDetection/Example04_PeakDetection.ino index 3de729c..d0d97da 100644 --- a/examples/Example04_PeakDetection/Example04_PeakDetection.ino +++ b/examples/Example04_PeakDetection/Example04_PeakDetection.ino @@ -1,17 +1,15 @@ /* Example 04 - Peak Detection - This example demonstrates how to read peak current values from Channel A. - The ADE7953 continuously tracks the highest instantaneous current sample - since the last reset. This is useful for detecting transient events like - motor startup surges or inrush current. + Reads peak current values from Channel A. The ADE7953 continuously tracks the highest + instantaneous current sample since the last reset, which is useful for detecting transient + events like motor startup surges or inrush current. Two methods are available: - getPeakIA() - Reads the peak value (does NOT clear it) - readAndResetPeakIA() - Reads the peak value AND clears it atomically + getPeakIA() - Reads the running peak (does NOT clear it) + readAndResetPeakIA() - Reads the peak value AND clears it in a single operation - This example uses readAndResetPeakIA() to track the peak current over - fixed intervals. + This example reports the peak current over fixed two-second intervals. SparkFun Electronics Date: 2025 @@ -22,7 +20,7 @@ IoT RedBoard --> ADE7953 QWIIC --> QWIIC - Serial.print it out at 115200 baud to serial monitor. + Open the Serial Monitor at 115200 baud. Feel like supporting our work? Buy a board from SparkFun! https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html @@ -39,48 +37,45 @@ unsigned long lastResetTime = 0; void setup() { Serial.begin(115200); - delay(1000); - Serial.println("ADE7953 Example 04 - Peak Detection"); + Serial.println("SparkFun ADE7953 Example 4 - Peak Detection"); Wire.begin(); - if (!mySensor.begin()) + while (mySensor.begin() == false) { - Serial.println("ADE7953 not found. Please check wiring. Freezing..."); - while (1) - delay(1000); + Serial.println("ADE7953 not connected, check your wiring!"); + delay(1000); } Serial.println("ADE7953 connected!"); - - // Set PGA gain to 4x for the 5.6 ohm shunt resistor. - mySensor.setGainIA(ADE7953_PGA_GAIN_4); - Serial.println("Reading peak current every 2 seconds..."); Serial.println(); - // Clear any stale peak value by doing an initial read-and-reset. - mySensor.readAndResetPeakIA(); + // Clear any stale peak value with an initial read-and-reset. + uint32_t discard = 0; + mySensor.readAndResetPeakIA(discard); lastResetTime = millis(); } void loop() { - // Also print the current IRMS for context. - uint32_t irms = mySensor.getIRmsA(); - - Serial.print("IRMS: "); + // Print the current IRMS value (raw) for context. + uint32_t irms = 0; + mySensor.getIRmsA(irms); + Serial.print("IRMS (raw): "); Serial.print(irms); // Check the running peak (non-destructive read). - uint32_t runningPeak = mySensor.getPeakIA(); + uint32_t runningPeak = 0; + mySensor.getPeakIA(runningPeak); Serial.print(" | Running Peak: "); Serial.print(runningPeak); - // Every kPeakInterval milliseconds, atomically read and reset the peak. + // Every interval, read and clear the peak. if ((millis() - lastResetTime) >= kPeakInterval) { - uint32_t peak = mySensor.readAndResetPeakIA(); + uint32_t peak = 0; + mySensor.readAndResetPeakIA(peak); Serial.print(" | ** Peak (reset): "); Serial.print(peak); diff --git a/examples/Example05_ZeroCrossing/Example05_ZeroCrossing.ino b/examples/Example05_ZeroCrossing/Example05_ZeroCrossing.ino index b4c4073..1b3deaa 100644 --- a/examples/Example05_ZeroCrossing/Example05_ZeroCrossing.ino +++ b/examples/Example05_ZeroCrossing/Example05_ZeroCrossing.ino @@ -1,19 +1,15 @@ /* Example 05 - Zero Crossing - This example demonstrates how to configure the zero-crossing (ZX) detection - on the ADE7953. The ZX_I pin on the board outputs a pulse each time the - current waveform crosses zero, which is useful for determining line frequency - and synchronizing measurements to the AC cycle. + Configures the zero-crossing (ZX) detection on the ADE7953. The ZX_I pin on the board + outputs a pulse each time the current waveform crosses zero, which is useful for + determining line frequency and synchronizing measurements to the AC cycle. - Configuration options shown here: - setZXISource() - Select Channel A or B as the ZX_I source - setZXEdge() - Choose which edges trigger the ZX output - enableZXLPF() - Enable/disable the zero-crossing low-pass filter - getPeriod() - Read the measured line period from the ZX detector - - The ZX_I pin on the board header can be connected to a digital input - to count zero crossings externally. + Configuration shown here: + setZXISourceChannel() - Select Channel A or B as the ZX_I source + setZXEdge() - Choose which edges trigger the ZX output + enableZXLPF() - Enable/disable the zero-crossing low-pass filter + getPeriod() - Read the measured line period from the ZX detector SparkFun Electronics Date: 2025 @@ -25,7 +21,7 @@ QWIIC --> QWIIC ZX_I pin --> Digital pin 2 (for external counting, optional) - Serial.print it out at 115200 baud to serial monitor. + Open the Serial Monitor at 115200 baud. Feel like supporting our work? Buy a board from SparkFun! https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html @@ -42,7 +38,6 @@ const int kZXIPin = 2; // Zero-crossing counter for the interrupt. volatile unsigned long zxCount = 0; -// ISR for counting zero crossings on the ZX_I pin. void zxISR() { zxCount++; @@ -51,30 +46,25 @@ void zxISR() void setup() { Serial.begin(115200); - delay(1000); - Serial.println("ADE7953 Example 05 - Zero Crossing"); + Serial.println("SparkFun ADE7953 Example 5 - Zero Crossing"); Wire.begin(); - if (!mySensor.begin()) + while (mySensor.begin() == false) { - Serial.println("ADE7953 not found. Please check wiring. Freezing..."); - while (1) - delay(1000); + Serial.println("ADE7953 not connected, check your wiring!"); + delay(1000); } Serial.println("ADE7953 connected!"); - // Set PGA gain to 4x for the 5.6 ohm shunt resistor. - mySensor.setGainIA(ADE7953_PGA_GAIN_4); - // --- Zero-crossing configuration --- // Select Channel A as the ZX_I source (false = Channel A, true = Channel B). - mySensor.setZXISource(false); + mySensor.setZXISourceChannel(false); Serial.println("ZX_I source: Channel A"); - // Set edge detection to trigger on both positive and negative zero crossings. + // Trigger on both positive and negative zero crossings. mySensor.setZXEdge(ADE7953_ZX_EDGE_BOTH); Serial.println("ZX edge: Both (positive and negative)"); @@ -98,19 +88,18 @@ void setup() void loop() { - // Read the period register. This value represents the line period - // measured by the zero-crossing detector in units of the internal clock. - // Period (seconds) = value / 223,750 Hz (ADE7953 ZX clock) - uint16_t period = mySensor.getPeriod(); + // Read the period register. This value represents the line period measured by the + // zero-crossing detector in units of the internal clock. + uint16_t period = 0; + mySensor.getPeriod(period); Serial.print("Period register: "); Serial.print(period); if (period > 0) { - // Convert to approximate line frequency. - // The ADE7953 period register counts at 223,750 Hz (CLKIN/16), - // and measures a full cycle (two zero crossings). + // Convert to approximate line frequency. The ADE7953 period register counts at + // 223,750 Hz and measures a full cycle. float freqHz = 223750.0 / (float)period; Serial.print(" | Line freq: ~"); Serial.print(freqHz, 1); diff --git a/examples/Example06_InterruptPin/Example06_InterruptPin.ino b/examples/Example06_InterruptPin/Example06_InterruptPin.ino index b40b3a3..83a2c83 100644 --- a/examples/Example06_InterruptPin/Example06_InterruptPin.ino +++ b/examples/Example06_InterruptPin/Example06_InterruptPin.ino @@ -1,21 +1,17 @@ /* Example 06 - Interrupt Pin - This example demonstrates how to configure and use the ADE7953 interrupt - system. The IRQ pin on the board header is active low and can be connected - to a GPIO to detect events like overcurrent, zero-crossing, or cycle - completion without polling. - - This example sets up an overcurrent threshold on Channel A and enables - the overcurrent interrupt. When the threshold is exceeded, the IRQ pin - goes low and the interrupt status register reports which event fired. - - Interrupt bitmask constants (partial list): - ksfADE7953IrqOI - Overcurrent detected - ksfADE7953IrqZXI - Current zero-crossing detected - ksfADE7953IrqCycEnd - Line cycle accumulation complete - ksfADE7953IrqSag - Sag event occurred - ksfADE7953IrqReset - Reset occurred (always enabled) + Configures and uses the ADE7953 interrupt system. The IRQ pin on the board header is + active low and can be connected to a GPIO to detect events like overcurrent, + zero-crossing, or cycle completion without polling. + + This example sets an overcurrent threshold on Channel A and enables the overcurrent + interrupt. When the threshold is exceeded, the IRQ pin goes low and the interrupt status + register reports which event fired. + + Interrupts are configured with the sfe_ade7953_irq_reg_t bitfield. Set the field for the + interrupt(s) you want, for example: irqEnable.oI = 1 (overcurrent), irqEnable.zxI = 1 + (zero-crossing), irqEnable.cycEnd = 1 (line cycle complete). SparkFun Electronics Date: 2025 @@ -27,7 +23,7 @@ QWIIC --> QWIIC IRQ pin --> Digital pin 3 (active low) - Serial.print it out at 115200 baud to serial monitor. + Open the Serial Monitor at 115200 baud. Feel like supporting our work? Buy a board from SparkFun! https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html @@ -51,39 +47,34 @@ void irqISR() void setup() { Serial.begin(115200); - delay(1000); - Serial.println("ADE7953 Example 06 - Interrupt Pin"); + Serial.println("SparkFun ADE7953 Example 6 - Interrupt Pin"); Wire.begin(); - if (!mySensor.begin()) + while (mySensor.begin() == false) { - Serial.println("ADE7953 not found. Please check wiring. Freezing..."); - while (1) - delay(1000); + Serial.println("ADE7953 not connected, check your wiring!"); + delay(1000); } Serial.println("ADE7953 connected!"); - // Set PGA gain to 4x for the 5.6 ohm shunt resistor. - mySensor.setGainIA(ADE7953_PGA_GAIN_4); - // --- Interrupt configuration --- - // Set an overcurrent threshold. This value is in raw ADC counts. - // Adjust this based on your expected current range. + // Set an overcurrent threshold (raw ADC counts). Adjust for your expected current range. uint32_t overcurrentThreshold = 0x100000; mySensor.setOvercurrentLevel(overcurrentThreshold); Serial.print("Overcurrent threshold set to: 0x"); Serial.println(overcurrentThreshold, HEX); - // Clear any pending interrupt status by reading and resetting. - mySensor.readAndResetInterruptStatusA(); + // Clear any pending interrupt status. + sfe_ade7953_irq_reg_t irqStatus = {}; + mySensor.readAndResetInterruptStatusA(irqStatus); // Enable the overcurrent interrupt on Channel A. - // You can OR multiple bitmasks together to enable several interrupts: - // ksfADE7953IrqOI | ksfADE7953IrqZXI | ksfADE7953IrqCycEnd - mySensor.setInterruptEnableA(ksfADE7953IrqOI); + sfe_ade7953_irq_reg_t irqEnable = {}; + irqEnable.oI = 1; + mySensor.setInterruptEnableA(irqEnable); Serial.println("Overcurrent interrupt enabled on Channel A."); // Set up the external IRQ pin (active low, so trigger on FALLING). @@ -99,9 +90,10 @@ void setup() void loop() { - // Print the current IRMS value for context. - uint32_t irms = mySensor.getIRmsA(); - Serial.print("IRMS: "); + // Print the current IRMS value (raw) for context. + uint32_t irms = 0; + mySensor.getIRmsA(irms); + Serial.print("IRMS (raw): "); Serial.print(irms); // Check if the ISR flagged an interrupt. @@ -110,21 +102,22 @@ void loop() irqFired = false; // Read and clear the interrupt status register. - uint32_t status = mySensor.readAndResetInterruptStatusA(); + sfe_ade7953_irq_reg_t status = {}; + mySensor.readAndResetInterruptStatusA(status); Serial.print(" ** IRQ! Status: 0x"); - Serial.print(status, HEX); + Serial.print(status.word, HEX); // Decode which interrupt(s) fired. - if (status & ksfADE7953IrqOI) + if (status.oI) Serial.print(" [Overcurrent]"); - if (status & ksfADE7953IrqZXI) + if (status.zxI) Serial.print(" [ZeroCrossing]"); - if (status & ksfADE7953IrqCycEnd) + if (status.cycEnd) Serial.print(" [CycleEnd]"); - if (status & ksfADE7953IrqSag) + if (status.sag) Serial.print(" [Sag]"); - if (status & ksfADE7953IrqReset) + if (status.reset) Serial.print(" [Reset]"); Serial.print(" **"); diff --git a/examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino b/examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino index 39e3936..2f138c4 100644 --- a/examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino +++ b/examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino @@ -1,19 +1,24 @@ /* - Example 07 - Calibration Offset + Example 07 - Calibration Offset & Error Handling - This example demonstrates how to use the IRMS offset register to zero out - no-load readings on Channel A. Even with no current flowing, the ADC may - report a small nonzero IRMS value due to noise. The offset register lets - you subtract that baseline for more accurate low-current measurements. + Two things are shown here: - How it works: - 1. Read several IRMS samples with no load connected. - 2. Average them to find the baseline noise floor. - 3. Write the negative of that average into the IRMS offset register. - 4. Future IRMS readings will have the baseline subtracted automatically. + 1. Error handling. Every library read/write returns a SparkFun Toolkit error code + (ksfTkErrOk on success, a negative value on failure). Most sketches ignore it for + simplicity, but this example shows how to check it so you can detect a disconnected + or misbehaving device. - Note: Run this example with NO load connected to Channel A during the - calibration phase (the first few seconds after startup). + 2. Zeroing the no-load baseline. Even with no current flowing, the ADC reports a small + nonzero IRMS value due to noise. This example measures that baseline and removes it. + + A note on the hardware offset register (AIRMSOS): the ADE7953 applies it in the squared + domain (the offset is added to the square of the RMS result before the square root), so + the value needed to null a noise floor of N counts is NOT simply -N. The exact scaling is + datasheet dependent. To keep this example correct and easy to follow, the baseline is + removed in software; getIRmsOffsetA()/setIRmsOffsetA() are demonstrated so you can apply a + hardware offset once you have determined the right value for your setup. + + Run with NO load connected to Channel A during the calibration phase. SparkFun Electronics Date: 2025 @@ -25,7 +30,7 @@ QWIIC --> QWIIC ** Disconnect load from CT clamp during calibration phase ** - Serial.print it out at 115200 baud to serial monitor. + Open the Serial Monitor at 115200 baud. Feel like supporting our work? Buy a board from SparkFun! https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html @@ -36,92 +41,86 @@ SfeADE7953ArdI2C mySensor; // Number of samples to average during calibration. -const int kCalSamples = 20; +const int kCalSamples = 50; + +// Software baseline (raw IRMS counts measured with no load). +uint32_t baseline = 0; void setup() { Serial.begin(115200); - delay(1000); - Serial.println("ADE7953 Example 07 - Calibration Offset"); + Serial.println("SparkFun ADE7953 Example 7 - Calibration Offset & Error Handling"); Wire.begin(); - if (!mySensor.begin()) + while (mySensor.begin() == false) { - Serial.println("ADE7953 not found. Please check wiring. Freezing..."); - while (1) - delay(1000); + Serial.println("ADE7953 not connected, check your wiring!"); + delay(1000); } Serial.println("ADE7953 connected!"); - // Set PGA gain to 4x for the 5.6 ohm shunt resistor. - mySensor.setGainIA(ADE7953_PGA_GAIN_4); - - // Clear any existing offset before calibrating. + // Clear any existing hardware offset so it does not affect our baseline measurement. mySensor.setIRmsOffsetA(0); - // Read the current offset to confirm it is zero. - int32_t currentOffset = mySensor.getIRmsOffsetA(); - Serial.print("Current IRMS offset: "); - Serial.println(currentOffset); + int32_t hwOffset = 0; + mySensor.getIRmsOffsetA(hwOffset); + Serial.print("Hardware IRMS offset register: "); + Serial.println(hwOffset); - // --- Calibration phase --- - // Make sure NO load is connected to the CT clamp during this phase! + // --- Calibration phase: average the no-load reading --- Serial.println(); Serial.println("=== CALIBRATION PHASE ==="); - Serial.println("Ensure no load is connected to Channel A."); - Serial.print("Averaging "); - Serial.print(kCalSamples); - Serial.println(" samples..."); - Serial.println(); - - // Let the readings stabilize. + Serial.println("Ensure NO load is connected to Channel A."); delay(1000); - // Collect samples and compute the average baseline. - uint32_t sum = 0; + // Use a 64-bit accumulator so the sum cannot overflow regardless of sample count. + uint64_t sum = 0; for (int i = 0; i < kCalSamples; i++) { - uint32_t sample = mySensor.getIRmsA(); - Serial.print(" Sample "); - Serial.print(i + 1); - Serial.print(": "); - Serial.println(sample); + uint32_t sample = 0; + + // Demonstrate error checking: only use the sample if the read succeeded. + if (mySensor.getIRmsA(sample) != ksfTkErrOk) + { + Serial.println("Read failed during calibration!"); + i--; // retry this sample + delay(100); + continue; + } + sum += sample; delay(100); } - uint32_t average = sum / kCalSamples; - Serial.println(); - Serial.print("Average no-load reading: "); - Serial.println(average); - - // Write the negative of the average as the offset. - // The ADE7953 subtracts the offset from the squared signal before - // taking the square root, so this effectively removes the noise floor. - int32_t offset = -((int32_t)average); - Serial.print("Setting IRMS offset to: "); - Serial.println(offset); - mySensor.setIRmsOffsetA(offset); - - // Read back to confirm. - currentOffset = mySensor.getIRmsOffsetA(); - Serial.print("IRMS offset is now: "); - Serial.println(currentOffset); - - Serial.println(); + baseline = (uint32_t)(sum / kCalSamples); + Serial.print("No-load baseline (raw counts): "); + Serial.println(baseline); Serial.println("=== CALIBRATION COMPLETE ==="); - Serial.println("You may now connect a load. Reading calibrated current..."); + Serial.println("You may now connect a load. Readings have the baseline removed."); Serial.println(); } void loop() { - uint32_t irms = mySensor.getIRmsA(); + uint32_t raw = 0; + + // Check the return code so a communication error is not mistaken for a zero reading. + if (mySensor.getIRmsA(raw) != ksfTkErrOk) + { + Serial.println("IRMS read failed!"); + delay(500); + return; + } + + // Remove the no-load baseline in software (clamped at zero). + uint32_t corrected = (raw > baseline) ? (raw - baseline) : 0; - Serial.print("IRMS Channel A (calibrated): "); - Serial.println(irms); + Serial.print("Raw: "); + Serial.print(raw); + Serial.print(" | Baseline-corrected: "); + Serial.println(corrected); delay(500); } diff --git a/examples/Example08_ChannelBWithInterrupt/Example08_ChannelBWithInterrupt.ino b/examples/Example08_ChannelBWithInterrupt/Example08_ChannelBWithInterrupt.ino deleted file mode 100644 index 9cff5a8..0000000 --- a/examples/Example08_ChannelBWithInterrupt/Example08_ChannelBWithInterrupt.ino +++ /dev/null @@ -1,154 +0,0 @@ -/* - Example 08 - Channel B With Interrupt - - This example demonstrates the full Channel B path using the optional - pinout headers on the board. It configures gain, enables Channel B - interrupts, and reads IRMS from Channel B. This is useful when you - want to monitor a second current source independently from the primary - CT clamp on Channel A. - - Channel B has its own interrupt enable/status registers (IRQENB/IRQSTATB) - which support bits [13:0]. Channel A interrupts (IRQENA/IRQSTATA) include - additional voltage-related bits [21:14] that are not present on Channel B. - - SparkFun Electronics - Date: 2025 - SparkFun code, firmware, and software is released under the MIT License. - Please see LICENSE.md for further details. - - Hardware Connections: - IoT RedBoard --> ADE7953 - QWIIC --> QWIIC - Current source --> Channel B header pins - IRQ pin --> Digital pin 3 (active low, optional) - - Serial.print it out at 115200 baud to serial monitor. - - Feel like supporting our work? Buy a board from SparkFun! - https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html -*/ - -#include - -SfeADE7953ArdI2C mySensor; - -// Pin connected to the IRQ header on the board (active low). -// Set to -1 if not wired up — the example will poll the status register instead. -const int kIRQPin = 3; - -volatile bool irqFired = false; - -void irqISR() -{ - irqFired = true; -} - -void setup() -{ - Serial.begin(115200); - delay(1000); - Serial.println("ADE7953 Example 08 - Channel B With Interrupt"); - - Wire.begin(); - - if (!mySensor.begin()) - { - Serial.println("ADE7953 not found. Please check wiring. Freezing..."); - while (1) - delay(1000); - } - - Serial.println("ADE7953 connected!"); - - // --- Channel B gain configuration --- - // Set PGA gain to 4x on Channel B for the 5.6 ohm shunt. - mySensor.setGainIB(ADE7953_PGA_GAIN_4); - - sfe_ade7953_pga_gain_t gainB = mySensor.getGainIB(); - Serial.print("Channel B PGA gain set to: "); - Serial.print(gainB == ADE7953_PGA_GAIN_4 ? "4x" : "other"); - Serial.println(); - - // --- Channel B interrupt configuration --- - // Clear any pending Channel B interrupt status. - mySensor.readAndResetInterruptStatusB(); - - // Enable overcurrent and zero-crossing interrupts on Channel B. - uint32_t irqMask = ksfADE7953IrqOI | ksfADE7953IrqZXI; - mySensor.setInterruptEnableB(irqMask); - - // Read back to confirm. - uint32_t irqEnabled = mySensor.getInterruptEnableB(); - Serial.print("Channel B interrupts enabled: 0x"); - Serial.println(irqEnabled, HEX); - - // Set an overcurrent threshold for Channel B. - mySensor.setOvercurrentLevel(0x100000); - Serial.println("Overcurrent threshold set."); - - // Set up external IRQ pin if wired. - if (kIRQPin >= 0) - { - pinMode(kIRQPin, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(kIRQPin), irqISR, FALLING); - Serial.print("IRQ monitoring on pin "); - Serial.println(kIRQPin); - } - - // Also read Channel A alongside for comparison. - mySensor.setGainIA(ADE7953_PGA_GAIN_4); - - Serial.println(); - Serial.println("Reading both channels..."); - Serial.println("Channel A (raw)\tChannel B (raw)\tB Interrupt"); - Serial.println("---------------\t---------------\t-----------"); -} - -void loop() -{ - // Read IRMS from both channels. - uint32_t irmsA = mySensor.getIRmsA(); - uint32_t irmsB = mySensor.getIRmsB(); - - Serial.print(irmsA); - Serial.print("\t\t"); - Serial.print(irmsB); - Serial.print("\t\t"); - - // Check for Channel B interrupts via the ISR flag or by polling. - bool hasInterrupt = false; - - if (kIRQPin >= 0 && irqFired) - { - irqFired = false; - hasInterrupt = true; - } - else if (kIRQPin < 0) - { - // No IRQ pin wired — poll the status register directly. - uint32_t status = mySensor.getInterruptStatusB(); - if (status != 0) - hasInterrupt = true; - } - - if (hasInterrupt) - { - // Read and clear Channel B interrupt status. - uint32_t statusB = mySensor.readAndResetInterruptStatusB(); - - Serial.print("0x"); - Serial.print(statusB, HEX); - - if (statusB & ksfADE7953IrqOI) - Serial.print(" [OC]"); - if (statusB & ksfADE7953IrqZXI) - Serial.print(" [ZX]"); - } - else - { - Serial.print("--"); - } - - Serial.println(); - delay(500); -} diff --git a/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino b/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino index e6c930b..fdd1ca8 100644 --- a/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino +++ b/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino @@ -1,12 +1,12 @@ /* Example 09 - Diagnostic Tool - Interactive diagnostic sketch for the ADE7953 current sensor. Provides - serial commands to change PGA gain, toggle the high-pass filter, perform - zero calibration, and view averaged/raw current readings with amps conversion. + Interactive diagnostic sketch for the ADE7953 current sensor. Provides serial commands to + change PGA gain, toggle the high-pass filter, perform a software zero, and view raw, + averaged, and amps readings. - Prints both raw IRMS and instantaneous IA (signed) so you can see DC offset. - Includes a rolling average to smooth out noisy readings. + The amps conversion is handled by the library (getCurrentA), which reads the active PGA + gain on each call, so the displayed current stays correct no matter which gain you select. Serial Commands (send via Serial Monitor, line ending = Newline): g1 - Set PGA gain to 1x @@ -14,11 +14,10 @@ g3 - Set PGA gain to 4x (default) g4 - Set PGA gain to 8x g5 - Set PGA gain to 16x - z - Zero calibration (averages 50 samples as baseline offset) + z - Zero calibration (averages 50 samples as a software baseline) r - Reset zero offset to 0 h - Toggle HPF (high-pass filter) on/off c - Show chip config and status - p - Toggle plotter mode (CSV output for Serial Plotter) ? - Show help SparkFun Electronics @@ -30,7 +29,7 @@ IoT RedBoard --> ADE7953 QWIIC --> QWIIC - Serial.print it out at 115200 baud to serial monitor. + Open the Serial Monitor at 115200 baud. Feel like supporting our work? Buy a board from SparkFun! https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html @@ -40,41 +39,19 @@ SfeADE7953ArdI2C mySensor; -// ========================= Configuration ========================== -// Modify these for your CT / burden resistor setup. -const float CT_RATIO = 2000.0; // CT turns ratio (30A:15mA = 2000:1) -const float BURDEN_RESISTOR = 5.6; // Burden resistor in ohms -const float FULL_SCALE_CODE = 5928256.0; // ADE7953 full-scale IRMS code -const float FULL_SCALE_VRMS = 0.35355; // 0.5V peak / sqrt(2) - -const int AVERAGE_WINDOW = 20; // Rolling average sample count -const int SAMPLE_INTERVAL_MS = 100; // Milliseconds between reads - // ========================= State ================================= -uint32_t irmsBuffer[AVERAGE_WINDOW]; +const int AVERAGE_WINDOW = 20; // Rolling average sample count +const int SAMPLE_INTERVAL_MS = 100; // Milliseconds between reads + +uint32_t irmsBuffer[AVERAGE_WINDOW]; // Rolling buffer of raw IRMS values int bufferIndex = 0; bool bufferFull = false; -int32_t zeroOffset = 0; // Software zero offset (raw IRMS units) -float pgaMultiplier = 4.0; // Tracks current PGA setting -bool plotterMode = false; // CSV output for Arduino Serial Plotter +uint32_t zeroOffset = 0; // Software zero offset (raw IRMS counts) // ========================= Helpers =============================== -/// @brief Convert a corrected raw IRMS value to amps. -float rawToAmps(int32_t rawValue) -{ - if (rawValue <= 0) - return 0.0; - - // Pin voltage RMS = raw / fullScaleCode * (fullScaleVrms / pgaGain) - // Secondary current = pinVrms / burdenResistor - // Primary current = secondaryCurrent * ctRatio - float fullScaleAmps = (FULL_SCALE_VRMS / pgaMultiplier) / BURDEN_RESISTOR * CT_RATIO; - return (float)rawValue / FULL_SCALE_CODE * fullScaleAmps; -} - -/// @brief Add a sample to the rolling average buffer and return the average. +// Add a sample to the rolling average buffer and return the average. uint32_t updateAverage(uint32_t sample) { irmsBuffer[bufferIndex] = sample; @@ -93,12 +70,34 @@ uint32_t updateAverage(uint32_t sample) return (count > 0) ? (uint32_t)(sum / count) : 0; } -/// @brief Reset the rolling average buffer. void resetBuffer() { bufferIndex = 0; bufferFull = false; - memset(irmsBuffer, 0, sizeof(irmsBuffer)); + for (int i = 0; i < AVERAGE_WINDOW; i++) + irmsBuffer[i] = 0; +} + +// Convert a PGA gain enum to its numeric multiplier for display. +int pgaMultiplier(sfe_ade7953_pga_gain_t gain) +{ + switch (gain) + { + case ADE7953_PGA_GAIN_1: + return 1; + case ADE7953_PGA_GAIN_2: + return 2; + case ADE7953_PGA_GAIN_4: + return 4; + case ADE7953_PGA_GAIN_8: + return 8; + case ADE7953_PGA_GAIN_16: + return 16; + case ADE7953_PGA_GAIN_22: + return 22; + default: + return -1; + } } // ========================= Commands ============================== @@ -115,20 +114,18 @@ void printHelp() Serial.println(F(" r Reset zero offset")); Serial.println(F(" h Toggle HPF on/off")); Serial.println(F(" c Show config / status")); - Serial.println(F(" p Toggle plotter mode")); Serial.println(F(" ? This help")); Serial.println(F("=======================================\n")); } -void setGain(sfe_ade7953_pga_gain_t gain, float multiplier, const char *label) +void setGain(sfe_ade7953_pga_gain_t gain, const char *label) { - if (!mySensor.setGainIA(gain)) + if (mySensor.setGainIA(gain) != ksfTkErrOk) { Serial.println(F("Failed to set gain!")); return; } - pgaMultiplier = multiplier; zeroOffset = 0; resetBuffer(); @@ -141,26 +138,18 @@ void doZeroCalibration() { const int NUM_SAMPLES = 50; - Serial.println(F("\nZero calibration — keep CT clamped with NO current flowing.")); - Serial.print(F("Collecting ")); - Serial.print(NUM_SAMPLES); - Serial.println(F(" samples...")); + Serial.println(F("\nZero calibration - keep CT clamped with NO current flowing.")); uint64_t sum = 0; for (int i = 0; i < NUM_SAMPLES; i++) { - sum += mySensor.getIRmsA(); + uint32_t sample = 0; + mySensor.getIRmsA(sample); + sum += sample; delay(50); - if ((i + 1) % 10 == 0) - { - Serial.print(F(" ")); - Serial.print(i + 1); - Serial.print(F("/")); - Serial.println(NUM_SAMPLES); - } } - zeroOffset = (int32_t)(sum / NUM_SAMPLES); + zeroOffset = (uint32_t)(sum / NUM_SAMPLES); resetBuffer(); Serial.print(F("Zero offset set to ")); @@ -168,71 +157,38 @@ void doZeroCalibration() Serial.println(F(". Readings will subtract this baseline.\n")); } -/// @brief Convert PGA gain enum to its actual multiplier for display. -int pgaEnumToMultiplier(int enumVal) -{ - switch (enumVal) - { - case 0: - return 1; - case 1: - return 2; - case 2: - return 4; - case 3: - return 8; - case 4: - return 16; - case 5: - return 22; - default: - return -1; - } -} - void showConfig() { Serial.println(F("\n----- ADE7953 Status -----")); + uint8_t version = 0; + mySensor.getVersion(version); Serial.print(F(" Silicon version : 0x")); - Serial.println(mySensor.getVersion(), HEX); + Serial.println(version, HEX); + sfe_ade7953_pga_gain_t gain; + mySensor.getGainIA(gain); Serial.print(F(" PGA Gain IA : ")); - Serial.print(pgaEnumToMultiplier((int)mySensor.getGainIA())); - Serial.println(F("x")); - - Serial.print(F(" PGA Gain IB : ")); - Serial.print(pgaEnumToMultiplier((int)mySensor.getGainIB())); - Serial.println(F("x")); - - Serial.print(F(" PGA Gain V : ")); - Serial.print(pgaEnumToMultiplier((int)mySensor.getGainV())); + Serial.print(pgaMultiplier(gain)); Serial.println(F("x")); + bool hpf = false; + mySensor.isHPFEnabled(hpf); Serial.print(F(" HPF enabled : ")); - Serial.println(mySensor.isHPFEnabled() ? "YES" : "NO"); + Serial.println(hpf ? "YES" : "NO"); Serial.print(F(" Software offset : ")); Serial.println(zeroOffset); - Serial.print(F(" PGA multiplier : ")); - Serial.print(pgaMultiplier, 1); - Serial.println(F("x")); - - float fullScale = (FULL_SCALE_VRMS / pgaMultiplier) / BURDEN_RESISTOR * CT_RATIO; - Serial.print(F(" Full-scale amps : ")); - Serial.print(fullScale, 2); - Serial.println(F(" A")); - Serial.println(F("--------------------------\n")); } void toggleHPF() { - bool currently = mySensor.isHPFEnabled(); - bool newState = !currently; + bool currently = false; + mySensor.isHPFEnabled(currently); - if (!mySensor.enableHPF(newState)) + if (mySensor.enableHPF(!currently) != ksfTkErrOk) { Serial.println(F("Failed to toggle HPF!")); return; @@ -240,17 +196,7 @@ void toggleHPF() resetBuffer(); Serial.print(F("HPF ")); - Serial.println(newState ? F("ENABLED") : F("DISABLED")); -} - -void printTableHeader() -{ - if (!plotterMode) - { - Serial.println(); - Serial.println(F(" ms | Raw IRMS | Corrected | Averaged | Amps | Inst. IA (signed)")); - Serial.println(F("----------|----------|-----------|----------|----------|------------------")); - } + Serial.println(!currently ? F("ENABLED") : F("DISABLED")); } void processCommand() @@ -259,15 +205,15 @@ void processCommand() cmd.trim(); if (cmd == "g1") - setGain(ADE7953_PGA_GAIN_1, 1.0, "1"); + setGain(ADE7953_PGA_GAIN_1, "1"); else if (cmd == "g2") - setGain(ADE7953_PGA_GAIN_2, 2.0, "2"); + setGain(ADE7953_PGA_GAIN_2, "2"); else if (cmd == "g3") - setGain(ADE7953_PGA_GAIN_4, 4.0, "4"); + setGain(ADE7953_PGA_GAIN_4, "4"); else if (cmd == "g4") - setGain(ADE7953_PGA_GAIN_8, 8.0, "8"); + setGain(ADE7953_PGA_GAIN_8, "8"); else if (cmd == "g5") - setGain(ADE7953_PGA_GAIN_16, 16.0, "16"); + setGain(ADE7953_PGA_GAIN_16, "16"); else if (cmd == "z") doZeroCalibration(); else if (cmd == "r") @@ -280,12 +226,6 @@ void processCommand() toggleHPF(); else if (cmd == "c") showConfig(); - else if (cmd == "p") - { - plotterMode = !plotterMode; - Serial.print(F("Plotter mode ")); - Serial.println(plotterMode ? F("ON (CSV)") : F("OFF (table)")); - } else if (cmd == "?") printHelp(); else @@ -294,8 +234,6 @@ void processCommand() Serial.println(cmd); printHelp(); } - - printTableHeader(); } // ========================= Setup / Loop ========================== @@ -303,7 +241,6 @@ void processCommand() void setup() { Serial.begin(115200); - delay(1000); Serial.println(F("\n========================================")); Serial.println(F(" ADE7953 Diagnostic Tool")); @@ -311,31 +248,17 @@ void setup() Wire.begin(); - if (!mySensor.begin()) + while (mySensor.begin() == false) { - Serial.println(F("ADE7953 not found. Please check wiring. Freezing...")); - while (1) - delay(1000); + Serial.println(F("ADE7953 not connected, check your wiring!")); + delay(1000); } - Serial.print(F("ADE7953 connected! Silicon version: 0x")); - Serial.println(mySensor.getVersion(), HEX); - - // Default gain: 4x for 5.6 ohm burden - mySensor.setGainIA(ADE7953_PGA_GAIN_4); - - // Make sure HPF is on - mySensor.enableHPF(true); - - // Let DSP settle before we start reading - Serial.println(F("Waiting for DSP to settle...")); - delay(1000); - - memset(irmsBuffer, 0, sizeof(irmsBuffer)); + Serial.println(F("ADE7953 connected!")); + resetBuffer(); showConfig(); printHelp(); - printTableHeader(); } void loop() @@ -343,54 +266,34 @@ void loop() if (Serial.available()) processCommand(); - // Read raw IRMS from Channel A - uint32_t rawIrms = mySensor.getIRmsA(); - - // Apply software zero offset - int32_t corrected = (int32_t)rawIrms - zeroOffset; - if (corrected < 0) - corrected = 0; - - // Rolling average of corrected values - uint32_t averaged = updateAverage((uint32_t)corrected); - - // Instantaneous current — signed, shows DC offset directly - int32_t instantIA = mySensor.getInstantaneousIA(); - - // Convert to amps - float amps = rawToAmps(averaged); - - // Output - if (plotterMode) - { - // CSV format for Arduino Serial Plotter - // Columns: RawIRMS, Corrected, Averaged, Amps*1000000, InstantIA - Serial.print(rawIrms); - Serial.print(","); - Serial.print(corrected); - Serial.print(","); - Serial.print(averaged); - Serial.print(","); - Serial.print((int32_t)(amps * 1000000.0)); // microamps for plotter scale - Serial.print(","); - Serial.println(instantIA); - } - else - { - // Formatted table for Serial Monitor - // Avoid snprintf %f — not supported on AVR - char line[80]; - snprintf(line, sizeof(line), - "%9lu | %8lu | %9ld | %8lu | ", - (unsigned long)millis(), - (unsigned long)rawIrms, - (long)corrected, - (unsigned long)averaged); - Serial.print(line); - Serial.print(amps, 4); - Serial.print(F("A | ")); - Serial.println(instantIA); - } + // Read raw IRMS from Channel A. + uint32_t rawIrms = 0; + mySensor.getIRmsA(rawIrms); + + // Apply the software zero offset (clamped at zero). + uint32_t corrected = (rawIrms > zeroOffset) ? (rawIrms - zeroOffset) : 0; + + // Rolling average of the corrected values. + uint32_t averaged = updateAverage(corrected); + + // Instantaneous current - signed, shows DC offset directly. + int32_t instantIA = 0; + mySensor.getInstantaneousIA(instantIA); + + // Real-world current in amps (library handles the gain/CT/burden conversion). + float amps = 0.0; + mySensor.getCurrentA(amps); + + Serial.print(F("Raw: ")); + Serial.print(rawIrms); + Serial.print(F(" | Corrected: ")); + Serial.print(corrected); + Serial.print(F(" | Averaged: ")); + Serial.print(averaged); + Serial.print(F(" | Amps: ")); + Serial.print(amps, 4); + Serial.print(F(" | Inst IA: ")); + Serial.println(instantIA); delay(SAMPLE_INTERVAL_MS); } diff --git a/src/SparkFun_ADE7953.h b/src/SparkFun_ADE7953.h index 857bfe2..717032a 100644 --- a/src/SparkFun_ADE7953.h +++ b/src/SparkFun_ADE7953.h @@ -6,29 +6,7 @@ * This file provides the Arduino-specific wrapper for the ADE7953 driver class. * The SfeADE7953ArdI2C class inherits from the platform-independent sfDevADE7953 * driver and implements I2C communication using Arduino's Wire library via the - * SparkFun Toolkit. - * - * Typical usage: - * @code - * #include - * - * SfeADE7953ArdI2C ammeter; - * - * void setup() { - * Wire.begin(); - * if (!ammeter.begin()) { - * Serial.println("ADE7953 not found!"); - * while (1); - * } - * ammeter.setGainIA(ADE7953_PGA_GAIN_4); // 4x gain for 5.6 ohm shunt - * } - * - * void loop() { - * uint32_t irms = ammeter.getIRmsA(); - * Serial.println(irms); - * delay(500); - * } - * @endcode + * SparkFun Toolkit. See the examples folder for usage. * * @author SparkFun Electronics * @date 2025 @@ -54,9 +32,8 @@ * @details * This class provides Arduino-specific I2C communication for the ADE7953. * It inherits all register access methods from sfDevADE7953 and adds the - * begin() and isConnected() methods required for Arduino initialization. - * - * The class owns an sfTkArdI2C bus object which wraps the Arduino Wire library. + * begin() method required for Arduino initialization. The class owns an + * sfTkArdI2C bus object which wraps the Arduino Wire library. * * @see sfDevADE7953 * @see TwoWire @@ -72,82 +49,32 @@ class SfeADE7953ArdI2C : public sfDevADE7953 * @brief Initializes the ADE7953 with I2C communication. * * @details - * This method performs the following initialization steps: - * 1. Initializes the Toolkit I2C bus with the given address and Wire port - * 2. Calls the base class begin() to set the bus, configure byte order, - * and write the optimized performance register - * 3. Verifies the device is connected via ping and identity check - * - * @param address 7-bit I2C address of the device (default: 0x38) - * @param wirePort TwoWire instance to use for I2C communication (default: Wire) + * Initializes the Toolkit I2C bus, confirms the device is present on the bus, then calls the + * base class begin() to verify the device identity, configure byte order, apply the optimized + * performance register sequence, and set default gain / HPF configuration. * - * @return true If initialization is successful - * @return false If any initialization step fails + * @param address 7-bit I2C address of the device (the ADE7953 uses a single fixed address). + * @param wirePort TwoWire instance to use for I2C communication (default: Wire). * - * Example: - * @code - * SfeADE7953ArdI2C ammeter; - * Wire.begin(); - * if (!ammeter.begin()) { - * Serial.println("ADE7953 initialization failed!"); - * while (1); - * } - * @endcode + * @return true If initialization is successful. + * @return false If any initialization step fails. */ - bool begin(const uint8_t &address = kADE7953DefaultAddr, TwoWire &wirePort = Wire) + bool begin(uint8_t address = kI2CAddress, TwoWire &wirePort = Wire) { // Initialize the Toolkit I2C bus with the given Wire port and address. if (_theI2CBus.init(wirePort, address) != ksfTkErrOk) return false; - // Call the base class begin() which sets the bus pointer, - // configures byte order, and writes the optimized performance register. - if (!sfDevADE7953::begin(&_theI2CBus)) - return false; - - // Verify the device is present and responding correctly. - return isConnected(); - } - - /** - * @brief Checks if the ADE7953 is connected and responding. - * - * @details - * Pings the device at the configured I2C address and verifies identity - * by reading the silicon version register to confirm an ADE7953 is present. - * - * @return true If the device responds and identity is verified - * @return false If communication fails or identity check fails - * - * Example: - * @code - * if (!ammeter.isConnected()) { - * Serial.println("ADE7953 not found on bus!"); - * } - * @endcode - */ - bool isConnected(void) - { - // Ping the device at the configured I2C address. + // Confirm a device is actually responding at this address before we configure anything. if (_theI2CBus.ping() != ksfTkErrOk) return false; - // Verify we are talking to an ADE7953 by reading the version register. - uint8_t version = getVersion(); - return (version != 0); + // The base class begin() verifies the device identity, sets byte order, writes the + // optimized performance register, and applies default configuration. + return sfDevADE7953::begin(&_theI2CBus) == ksfTkErrOk; } private: - /** - * @brief Arduino I2C bus interface instance. - * - * @details - * This member handles low-level I2C communication between the Arduino and the ADE7953. - * It is configured during begin() and used by all communication methods inherited from - * sfDevADE7953. - * - * @see sfTkArdI2C - * @see begin() - */ + /// @brief Arduino I2C bus interface instance used for all communication with the ADE7953. sfTkArdI2C _theI2CBus; -}; \ No newline at end of file +}; diff --git a/src/sfTk/sfDevADE7953.cpp b/src/sfTk/sfDevADE7953.cpp index 333a34b..77196e7 100644 --- a/src/sfTk/sfDevADE7953.cpp +++ b/src/sfTk/sfDevADE7953.cpp @@ -19,767 +19,582 @@ #include "sfDevADE7953.h" -#define DEBUG_SERIAL_PRINTS (0) -#if DEBUG_SERIAL_PRINTS -#include "Arduino.h" -#endif - // ========================= Setup & Identity =============================== -bool sfDevADE7953::begin(sfTkIBus *theBus) +sfTkError_t sfDevADE7953::begin(sfTkIBus *theBus) { - // Nullptr check. - if (!_theBus && !theBus) - return false; - - // Set the internal bus pointer, overriding current bus if it exists. + // Adopt the supplied bus if one was provided; otherwise keep any bus set by a prior begin(). if (theBus != nullptr) - setCommunicationBus(theBus); + _theBus = theBus; - // ADE7953 is big-endian for both register addresses and data. - _theBus->setByteOrder(sfTkByteOrder::BigEndian); + // We need a bus to talk to. + if (_theBus == nullptr) + return ksfTkErrBusNotInit; - // Unlock the optimized performance register by writing 0xAD to register 0x0FE. - if (_theBus->writeRegister(ksfADE7953RegUnlock, ksfADE7953UnlockKey) != ksfTkErrOk) - return false; + // The ADE7953 is big-endian for both register addresses and data. + _theBus->setByteOrder(sfTkByteOrder::BigEndian); - // Write 0x0030 to register 0x120 for optimal performance per datasheet Table 1. - if (_theBus->writeRegister(ksfADE7953RegOptimize, ksfADE7953OptimizeValue) != ksfTkErrOk) - return false; + // Make sure the device is actually present before writing any configuration. This avoids + // clobbering registers on a different device that happens to share the bus address. + if (!isConnected()) + return ksfTkErrBusNoResponse; - return true; + // Apply the datasheet performance sequence and sensible defaults. + return applyDefaultConfig(); } -void sfDevADE7953::setCommunicationBus(sfTkIBus *theBus) +sfTkError_t sfDevADE7953::applyDefaultConfig(void) { - _theBus = theBus; -} + // Unlock the optimized performance register by writing the key to register 0x0FE. + sfTkError_t rc = _theBus->writeRegister(kRegUnlock, kUnlockKey); + if (rc != ksfTkErrOk) + return rc; -uint8_t sfDevADE7953::getVersion(void) -{ - uint8_t version = 0; + // Write 0x0030 to register 0x120 for optimal performance per datasheet Table 1. + rc = _theBus->writeRegister(kRegOptimize, kOptimizeValue); + if (rc != ksfTkErrOk) + return rc; - if (!_theBus) - return 0; + // Default the current channels to 4x gain, matching the 5.6 ohm shunt on the Qwiic board, so a + // basic sketch works without any extra configuration. + rc = setGainIA(ADE7953_PGA_GAIN_4); + if (rc != ksfTkErrOk) + return rc; - if (_theBus->readRegister(ksfADE7953RegVersion, version) != ksfTkErrOk) - return 0; + rc = setGainIB(ADE7953_PGA_GAIN_4); + if (rc != ksfTkErrOk) + return rc; - return version; + // Enable the high-pass filter so IRMS readings are not corrupted by DC offset. + return enableHPF(true); } -bool sfDevADE7953::reset(void) +bool sfDevADE7953::isConnected(void) { - if (!_theBus) - return false; - - // Read current CONFIG, set the SWRST bit, and write back. - uint16_t config = 0; - if (_theBus->readRegister(ksfADE7953RegConfig, config) != ksfTkErrOk) + uint8_t version = 0; + if (getVersion(version) != ksfTkErrOk) return false; - config |= ksfADE7953ConfigSWRst; - - return (_theBus->writeRegister(ksfADE7953RegConfig, config) == ksfTkErrOk); + // A valid ADE7953 reports a nonzero silicon version. + return version != 0; } -bool sfDevADE7953::setWriteProtect(uint8_t protect) +sfTkError_t sfDevADE7953::getVersion(uint8_t &version) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegWriteProtect, (uint8_t)(protect & 0x07)) == ksfTkErrOk); + return _theBus->readRegister(kRegVersion, version); } -uint8_t sfDevADE7953::getWriteProtect(void) +sfTkError_t sfDevADE7953::reset(void) { - uint8_t value = 0; + sfe_ade7953_config_reg_t config = {}; - if (!_theBus) - return 0; + sfTkError_t rc = _theBus->readRegister(kRegConfig, config.word); + if (rc != ksfTkErrOk) + return rc; - if (_theBus->readRegister(ksfADE7953RegWriteProtect, value) != ksfTkErrOk) - return 0; + config.swRst = 1; - return value & 0x07; -} + rc = _theBus->writeRegister(kRegConfig, config.word); + if (rc != ksfTkErrOk) + return rc; -// ======================== PGA Gain Configuration ========================== + // A software reset returns every register to its power-on default, including the OPTIMIZE + // register, so re-apply the performance sequence and default configuration. Callers that reset + // a running device may wish to allow a short settling delay before resuming reads. + return applyDefaultConfig(); +} -bool sfDevADE7953::setGainIA(sfe_ade7953_pga_gain_t gain) +sfTkError_t sfDevADE7953::setWriteProtect(uint8_t protect) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegPgaIA, (uint8_t)(gain & 0x07)) == ksfTkErrOk); + return _theBus->writeRegister(kRegWriteProtect, (uint8_t)(protect & 0x07)); } -sfe_ade7953_pga_gain_t sfDevADE7953::getGainIA(void) +sfTkError_t sfDevADE7953::getWriteProtect(uint8_t &protect) { - uint8_t value = 0; - - if (!_theBus) - return ADE7953_PGA_GAIN_1; + sfTkError_t rc = _theBus->readRegister(kRegWriteProtect, protect); + protect &= 0x07; + return rc; +} - if (_theBus->readRegister(ksfADE7953RegPgaIA, value) != ksfTkErrOk) - return ADE7953_PGA_GAIN_1; +// ======================== PGA Gain Configuration ========================== - return (sfe_ade7953_pga_gain_t)(value & 0x07); +float sfDevADE7953::pgaGainToMultiplier(sfe_ade7953_pga_gain_t gain) +{ + switch (gain) + { + case ADE7953_PGA_GAIN_1: + return 1.0f; + case ADE7953_PGA_GAIN_2: + return 2.0f; + case ADE7953_PGA_GAIN_4: + return 4.0f; + case ADE7953_PGA_GAIN_8: + return 8.0f; + case ADE7953_PGA_GAIN_16: + return 16.0f; + case ADE7953_PGA_GAIN_22: + return 22.0f; + default: + return 1.0f; + } } -bool sfDevADE7953::setGainIB(sfe_ade7953_pga_gain_t gain) +sfTkError_t sfDevADE7953::setGainIA(sfe_ade7953_pga_gain_t gain) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegPgaIB, (uint8_t)(gain & 0x07)) == ksfTkErrOk); + return _theBus->writeRegister(kRegPgaIA, (uint8_t)(gain & 0x07)); } -sfe_ade7953_pga_gain_t sfDevADE7953::getGainIB(void) +sfTkError_t sfDevADE7953::getGainIA(sfe_ade7953_pga_gain_t &gain) { uint8_t value = 0; - - if (!_theBus) - return ADE7953_PGA_GAIN_1; - - if (_theBus->readRegister(ksfADE7953RegPgaIB, value) != ksfTkErrOk) - return ADE7953_PGA_GAIN_1; - - return (sfe_ade7953_pga_gain_t)(value & 0x07); + sfTkError_t rc = _theBus->readRegister(kRegPgaIA, value); + gain = (sfe_ade7953_pga_gain_t)(value & 0x07); + return rc; } -bool sfDevADE7953::setGainV(sfe_ade7953_pga_gain_t gain) +sfTkError_t sfDevADE7953::setGainIB(sfe_ade7953_pga_gain_t gain) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegPgaV, (uint8_t)(gain & 0x07)) == ksfTkErrOk); + return _theBus->writeRegister(kRegPgaIB, (uint8_t)(gain & 0x07)); } -sfe_ade7953_pga_gain_t sfDevADE7953::getGainV(void) +sfTkError_t sfDevADE7953::getGainIB(sfe_ade7953_pga_gain_t &gain) { uint8_t value = 0; + sfTkError_t rc = _theBus->readRegister(kRegPgaIB, value); + gain = (sfe_ade7953_pga_gain_t)(value & 0x07); + return rc; +} - if (!_theBus) - return ADE7953_PGA_GAIN_1; - - if (_theBus->readRegister(ksfADE7953RegPgaV, value) != ksfTkErrOk) - return ADE7953_PGA_GAIN_1; +sfTkError_t sfDevADE7953::setGainV(sfe_ade7953_pga_gain_t gain) +{ + // Gain code 5 (22x) is only valid for the current channels, not the voltage channel. + if (gain == ADE7953_PGA_GAIN_22) + return ksfTkErrFail; - return (sfe_ade7953_pga_gain_t)(value & 0x07); + return _theBus->writeRegister(kRegPgaV, (uint8_t)(gain & 0x07)); } -bool sfDevADE7953::setDigitalGainIA(uint32_t gain) +sfTkError_t sfDevADE7953::getGainV(sfe_ade7953_pga_gain_t &gain) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegAIGain, gain) == ksfTkErrOk); + uint8_t value = 0; + sfTkError_t rc = _theBus->readRegister(kRegPgaV, value); + gain = (sfe_ade7953_pga_gain_t)(value & 0x07); + return rc; } -uint32_t sfDevADE7953::getDigitalGainIA(void) +sfTkError_t sfDevADE7953::setDigitalGainIA(uint32_t gain) { - uint32_t value = 0; + return _theBus->writeRegister(kRegAIGain, gain); +} - if (!_theBus) - return 0; +sfTkError_t sfDevADE7953::setDigitalGainIA(float multiplier) +{ + if (multiplier <= 0.0f) + return ksfTkErrFail; - if (_theBus->readRegister(ksfADE7953RegAIGain, value) != ksfTkErrOk) - return 0; + uint32_t code = (uint32_t)((float)kDigitalGainUnity * multiplier + 0.5f); + if (code > kDigitalGainMax) + return ksfTkErrFail; - return value; + return setDigitalGainIA(code); } -bool sfDevADE7953::setDigitalGainIB(uint32_t gain) +sfTkError_t sfDevADE7953::getDigitalGainIA(uint32_t &gain) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegBIGain, gain) == ksfTkErrOk); + return _theBus->readRegister(kRegAIGain, gain); } -uint32_t sfDevADE7953::getDigitalGainIB(void) +sfTkError_t sfDevADE7953::getDigitalGainIA(float &multiplier) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegBIGain, value) != ksfTkErrOk) - return 0; - - return value; + uint32_t code = 0; + sfTkError_t rc = getDigitalGainIA(code); + multiplier = (float)code / (float)kDigitalGainUnity; + return rc; } -// ======================= Current Measurement ============================== +sfTkError_t sfDevADE7953::setDigitalGainIB(uint32_t gain) +{ + return _theBus->writeRegister(kRegBIGain, gain); +} -uint32_t sfDevADE7953::getIRmsA(void) +sfTkError_t sfDevADE7953::setDigitalGainIB(float multiplier) { - uint32_t value = 0; + if (multiplier <= 0.0f) + return ksfTkErrFail; - if (!_theBus) - return 0; + uint32_t code = (uint32_t)((float)kDigitalGainUnity * multiplier + 0.5f); + if (code > kDigitalGainMax) + return ksfTkErrFail; - if (_theBus->readRegister(ksfADE7953RegIRmsA, value) != ksfTkErrOk) - return 0; + return setDigitalGainIB(code); +} - return value; +sfTkError_t sfDevADE7953::getDigitalGainIB(uint32_t &gain) +{ + return _theBus->readRegister(kRegBIGain, gain); } -uint32_t sfDevADE7953::getIRmsB(void) +sfTkError_t sfDevADE7953::getDigitalGainIB(float &multiplier) { - uint32_t value = 0; + uint32_t code = 0; + sfTkError_t rc = getDigitalGainIB(code); + multiplier = (float)code / (float)kDigitalGainUnity; + return rc; +} - if (!_theBus) - return 0; +// ======================= Current Measurement ============================== - if (_theBus->readRegister(ksfADE7953RegIRmsB, value) != ksfTkErrOk) - return 0; +sfTkError_t sfDevADE7953::getIRmsA(uint32_t &value) +{ + return _theBus->readRegister(kRegIRmsA, value); +} - return value; +sfTkError_t sfDevADE7953::getIRmsB(uint32_t &value) +{ + return _theBus->readRegister(kRegIRmsB, value); } -int32_t sfDevADE7953::getInstantaneousIA(void) +sfTkError_t sfDevADE7953::getCurrentA(float &s) { uint32_t raw = 0; + sfTkError_t rc = getIRmsA(raw); + if (rc != ksfTkErrOk) + return rc; - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegIA, raw) != ksfTkErrOk) - return 0; + sfe_ade7953_pga_gain_t gain = ADE7953_PGA_GAIN_1; + rc = getGainIA(gain); + if (rc != ksfTkErrOk) + return rc; - // Sign-extend from 24-bit to 32-bit. The upper 8 bits from the 0x3xx read are - // already sign-extended by the ADE7953, so we can cast directly. - return (int32_t)raw; + // Pin voltage RMS = raw / fullScaleCode * (fullScaleVrms / pgaGain) + // Secondary current = pinVrms / burdenResistor + // Primary current = secondaryCurrent * ctRatio + float fullScaleAmps = (_fullScaleVrms / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; + amps = (float)raw / _fullScaleCode * fullScaleAmps; + return ksfTkErrOk; } -int32_t sfDevADE7953::getInstantaneousIB(void) +sfTkError_t sfDevADE7953::getCurrentB(float &s) { uint32_t raw = 0; + sfTkError_t rc = getIRmsB(raw); + if (rc != ksfTkErrOk) + return rc; - if (!_theBus) - return 0; + sfe_ade7953_pga_gain_t gain = ADE7953_PGA_GAIN_1; + rc = getGainIB(gain); + if (rc != ksfTkErrOk) + return rc; - if (_theBus->readRegister(ksfADE7953RegIB, raw) != ksfTkErrOk) - return 0; - - return (int32_t)raw; + float fullScaleAmps = (_fullScaleVrms / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; + amps = (float)raw / _fullScaleCode * fullScaleAmps; + return ksfTkErrOk; } -// ======================== Peak Detection ================================== - -uint32_t sfDevADE7953::getPeakIA(void) +sfTkError_t sfDevADE7953::getInstantaneousIA(int32_t &value) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegIAPeak, value) != ksfTkErrOk) - return 0; + uint32_t raw = 0; + sfTkError_t rc = _theBus->readRegister(kRegIA, raw); - return value; + // The upper 8 bits from the 0x3xx read are already sign-extended by the ADE7953, so a plain + // conversion to a signed 32-bit value preserves the sign. + value = (int32_t)raw; + return rc; } -uint32_t sfDevADE7953::getPeakIB(void) +sfTkError_t sfDevADE7953::getInstantaneousIB(int32_t &value) { - uint32_t value = 0; - - if (!_theBus) - return 0; + uint32_t raw = 0; + sfTkError_t rc = _theBus->readRegister(kRegIB, raw); + value = (int32_t)raw; + return rc; +} - if (_theBus->readRegister(ksfADE7953RegIBPeak, value) != ksfTkErrOk) - return 0; +// =================== Current Conversion Calibration ======================= - return value; +void sfDevADE7953::setCurrentTransformerRatio(float ratio) +{ + _ctRatio = ratio; } -uint32_t sfDevADE7953::readAndResetPeakIA(void) +float sfDevADE7953::getCurrentTransformerRatio(void) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - // Reading RSTIAPEAK atomically reads the peak value and clears the register. - if (_theBus->readRegister(ksfADE7953RegRstIAPeak, value) != ksfTkErrOk) - return 0; - - return value; + return _ctRatio; } -uint32_t sfDevADE7953::readAndResetPeakIB(void) +void sfDevADE7953::setBurdenResistor(float ohms) { - uint32_t value = 0; + _burdenResistor = ohms; +} - if (!_theBus) - return 0; +float sfDevADE7953::getBurdenResistor(void) +{ + return _burdenResistor; +} - if (_theBus->readRegister(ksfADE7953RegRstIBPeak, value) != ksfTkErrOk) - return 0; +// ======================== Peak Detection ================================== - return value; +sfTkError_t sfDevADE7953::getPeakIA(uint32_t &value) +{ + return _theBus->readRegister(kRegIAPeak, value); } -bool sfDevADE7953::setOvercurrentLevel(uint32_t level) +sfTkError_t sfDevADE7953::getPeakIB(uint32_t &value) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegOiLvl, level) == ksfTkErrOk); + return _theBus->readRegister(kRegIBPeak, value); } -uint32_t sfDevADE7953::getOvercurrentLevel(void) +sfTkError_t sfDevADE7953::readAndResetPeakIA(uint32_t &value) { - uint32_t value = 0; + // Reading RSTIAPEAK returns the peak value and clears the register in a single operation. + return _theBus->readRegister(kRegRstIAPeak, value); +} - if (!_theBus) - return 0; +sfTkError_t sfDevADE7953::readAndResetPeakIB(uint32_t &value) +{ + return _theBus->readRegister(kRegRstIBPeak, value); +} - if (_theBus->readRegister(ksfADE7953RegOiLvl, value) != ksfTkErrOk) - return 0; +sfTkError_t sfDevADE7953::setOvercurrentLevel(uint32_t level) +{ + return _theBus->writeRegister(kRegOiLvl, level); +} - return value; +sfTkError_t sfDevADE7953::getOvercurrentLevel(uint32_t &level) +{ + return _theBus->readRegister(kRegOiLvl, level); } // ====================== Calibration & Offset ============================== -bool sfDevADE7953::setIRmsOffsetA(int32_t offset) +sfTkError_t sfDevADE7953::setIRmsOffsetA(int32_t offset) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegAIRmsOS, (uint32_t)offset) == ksfTkErrOk); + return _theBus->writeRegister(kRegAIRmsOS, (uint32_t)offset); } -int32_t sfDevADE7953::getIRmsOffsetA(void) +sfTkError_t sfDevADE7953::getIRmsOffsetA(int32_t &offset) { uint32_t raw = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegAIRmsOS, raw) != ksfTkErrOk) - return 0; - - return (int32_t)raw; + sfTkError_t rc = _theBus->readRegister(kRegAIRmsOS, raw); + offset = (int32_t)raw; + return rc; } -bool sfDevADE7953::setIRmsOffsetB(int32_t offset) +sfTkError_t sfDevADE7953::setIRmsOffsetB(int32_t offset) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegBIRmsOS, (uint32_t)offset) == ksfTkErrOk); + return _theBus->writeRegister(kRegBIRmsOS, (uint32_t)offset); } -int32_t sfDevADE7953::getIRmsOffsetB(void) +sfTkError_t sfDevADE7953::getIRmsOffsetB(int32_t &offset) { uint32_t raw = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegBIRmsOS, raw) != ksfTkErrOk) - return 0; - - return (int32_t)raw; + sfTkError_t rc = _theBus->readRegister(kRegBIRmsOS, raw); + offset = (int32_t)raw; + return rc; } // ======================= Zero-Crossing (ZX_I) ============================= -bool sfDevADE7953::setZXISource(bool useChannelB) +sfTkError_t sfDevADE7953::setZXISourceChannel(bool useChannelB) { - if (!_theBus) - return false; - - uint16_t config = 0; - if (_theBus->readRegister(ksfADE7953RegConfig, config) != ksfTkErrOk) - return false; + sfe_ade7953_config_reg_t config = {}; + sfTkError_t rc = _theBus->readRegister(kRegConfig, config.word); + if (rc != ksfTkErrOk) + return rc; - if (useChannelB) - config |= ksfADE7953ConfigZXI; - else - config &= ~ksfADE7953ConfigZXI; + config.zxI = useChannelB ? 1 : 0; - return (_theBus->writeRegister(ksfADE7953RegConfig, config) == ksfTkErrOk); + return _theBus->writeRegister(kRegConfig, config.word); } -bool sfDevADE7953::getZXISource(void) +sfTkError_t sfDevADE7953::getZXISourceChannel(bool &useChannelB) { - uint16_t config = 0; - - if (!_theBus) - return false; - - if (_theBus->readRegister(ksfADE7953RegConfig, config) != ksfTkErrOk) - return false; - - return (config & ksfADE7953ConfigZXI) != 0; + sfe_ade7953_config_reg_t config = {}; + sfTkError_t rc = _theBus->readRegister(kRegConfig, config.word); + useChannelB = config.zxI != 0; + return rc; } -bool sfDevADE7953::setZXEdge(sfe_ade7953_zx_edge_t edge) +sfTkError_t sfDevADE7953::setZXEdge(sfe_ade7953_zx_edge_t edge) { - if (!_theBus) - return false; + sfe_ade7953_config_reg_t config = {}; + sfTkError_t rc = _theBus->readRegister(kRegConfig, config.word); + if (rc != ksfTkErrOk) + return rc; - uint16_t config = 0; - if (_theBus->readRegister(ksfADE7953RegConfig, config) != ksfTkErrOk) - return false; - - // Clear the ZX_EDGE bits [13:12] and set the new value. - config &= ~ksfADE7953ConfigZXEdgeMask; - config |= ((uint16_t)edge << ksfADE7953ConfigZXEdgeShift) & ksfADE7953ConfigZXEdgeMask; + config.zxEdge = (uint16_t)edge; - return (_theBus->writeRegister(ksfADE7953RegConfig, config) == ksfTkErrOk); + return _theBus->writeRegister(kRegConfig, config.word); } -sfe_ade7953_zx_edge_t sfDevADE7953::getZXEdge(void) +sfTkError_t sfDevADE7953::getZXEdge(sfe_ade7953_zx_edge_t &edge) { - uint16_t config = 0; - - if (!_theBus) - return ADE7953_ZX_EDGE_BOTH; - - if (_theBus->readRegister(ksfADE7953RegConfig, config) != ksfTkErrOk) - return ADE7953_ZX_EDGE_BOTH; - - return (sfe_ade7953_zx_edge_t)((config & ksfADE7953ConfigZXEdgeMask) >> ksfADE7953ConfigZXEdgeShift); + sfe_ade7953_config_reg_t config = {}; + sfTkError_t rc = _theBus->readRegister(kRegConfig, config.word); + edge = (sfe_ade7953_zx_edge_t)config.zxEdge; + return rc; } -bool sfDevADE7953::enableZXLPF(bool enable) +sfTkError_t sfDevADE7953::enableZXLPF(bool enable) { - if (!_theBus) - return false; + sfe_ade7953_config_reg_t config = {}; + sfTkError_t rc = _theBus->readRegister(kRegConfig, config.word); + if (rc != ksfTkErrOk) + return rc; - uint16_t config = 0; - if (_theBus->readRegister(ksfADE7953RegConfig, config) != ksfTkErrOk) - return false; - - // Note: CONFIG bit 6 (ZXLPF) = 0 means LPF is ENABLED, 1 means DISABLED. - // So we invert the user's intent. - if (enable) - config &= ~ksfADE7953ConfigZXLPF; // Clear bit to enable LPF - else - config |= ksfADE7953ConfigZXLPF; // Set bit to disable LPF + // Note: the ZXLPF bit is inverted — 0 means the LPF is ENABLED, 1 means DISABLED. + config.zxLPF = enable ? 0 : 1; - return (_theBus->writeRegister(ksfADE7953RegConfig, config) == ksfTkErrOk); + return _theBus->writeRegister(kRegConfig, config.word); } -bool sfDevADE7953::enableHPF(bool enable) +sfTkError_t sfDevADE7953::enableHPF(bool enable) { - if (!_theBus) - return false; + sfe_ade7953_config_reg_t config = {}; + sfTkError_t rc = _theBus->readRegister(kRegConfig, config.word); + if (rc != ksfTkErrOk) + return rc; - uint16_t config = 0; - if (_theBus->readRegister(ksfADE7953RegConfig, config) != ksfTkErrOk) - return false; - - if (enable) - config |= ksfADE7953ConfigHPFEn; - else - config &= ~ksfADE7953ConfigHPFEn; + config.hpfEn = enable ? 1 : 0; - return (_theBus->writeRegister(ksfADE7953RegConfig, config) == ksfTkErrOk); + return _theBus->writeRegister(kRegConfig, config.word); } -bool sfDevADE7953::isHPFEnabled(void) +sfTkError_t sfDevADE7953::isHPFEnabled(bool &enabled) { - if (!_theBus) - return false; - - uint16_t config = 0; - if (_theBus->readRegister(ksfADE7953RegConfig, config) != ksfTkErrOk) - return false; - - return (config & ksfADE7953ConfigHPFEn) != 0; + sfe_ade7953_config_reg_t config = {}; + sfTkError_t rc = _theBus->readRegister(kRegConfig, config.word); + enabled = config.hpfEn != 0; + return rc; } -uint16_t sfDevADE7953::getPeriod(void) +sfTkError_t sfDevADE7953::getPeriod(uint16_t &period) { - uint16_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegPeriod, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegPeriod, period); } // =========================== Interrupts =================================== -bool sfDevADE7953::setInterruptEnableA(uint32_t mask) +sfTkError_t sfDevADE7953::setInterruptEnableA(sfe_ade7953_irq_reg_t mask) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegIrqEnA, mask) == ksfTkErrOk); + return _theBus->writeRegister(kRegIrqEnA, mask.word); } -uint32_t sfDevADE7953::getInterruptEnableA(void) +sfTkError_t sfDevADE7953::getInterruptEnableA(sfe_ade7953_irq_reg_t &mask) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegIrqEnA, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegIrqEnA, mask.word); } -uint32_t sfDevADE7953::getInterruptStatusA(void) +sfTkError_t sfDevADE7953::getInterruptStatusA(sfe_ade7953_irq_reg_t &status) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegIrqStatA, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegIrqStatA, status.word); } -uint32_t sfDevADE7953::readAndResetInterruptStatusA(void) +sfTkError_t sfDevADE7953::readAndResetInterruptStatusA(sfe_ade7953_irq_reg_t &status) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - // Reading RSTIRQSTATA atomically reads status and clears the flags. - if (_theBus->readRegister(ksfADE7953RegRstIrqStatA, value) != ksfTkErrOk) - return 0; - - return value; + // Reading RSTIRQSTATA returns the status and clears the flags in a single operation. + return _theBus->readRegister(kRegRstIrqStatA, status.word); } -bool sfDevADE7953::setInterruptEnableB(uint32_t mask) +sfTkError_t sfDevADE7953::setInterruptEnableB(sfe_ade7953_irq_reg_t mask) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegIrqEnB, mask) == ksfTkErrOk); + return _theBus->writeRegister(kRegIrqEnB, mask.word); } -uint32_t sfDevADE7953::getInterruptEnableB(void) +sfTkError_t sfDevADE7953::getInterruptEnableB(sfe_ade7953_irq_reg_t &mask) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegIrqEnB, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegIrqEnB, mask.word); } -uint32_t sfDevADE7953::getInterruptStatusB(void) +sfTkError_t sfDevADE7953::getInterruptStatusB(sfe_ade7953_irq_reg_t &status) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegIrqStatB, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegIrqStatB, status.word); } -uint32_t sfDevADE7953::readAndResetInterruptStatusB(void) +sfTkError_t sfDevADE7953::readAndResetInterruptStatusB(sfe_ade7953_irq_reg_t &status) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegRstIrqStatB, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegRstIrqStatB, status.word); } // ========================= No-Load Detection ============================== -bool sfDevADE7953::setNoLoadDisable(uint8_t mask) +sfTkError_t sfDevADE7953::setNoLoadDisable(sfe_ade7953_disnoload_reg_t mask) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegDisNoLoad, (uint8_t)(mask & 0x07)) == ksfTkErrOk); + return _theBus->writeRegister(kRegDisNoLoad, mask.byte); } -uint8_t sfDevADE7953::getNoLoadDisable(void) +sfTkError_t sfDevADE7953::getNoLoadDisable(sfe_ade7953_disnoload_reg_t &mask) { - uint8_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegDisNoLoad, value) != ksfTkErrOk) - return 0; - - return value & 0x07; + return _theBus->readRegister(kRegDisNoLoad, mask.byte); } // ====================== Line Cycle Accumulation =========================== -bool sfDevADE7953::setLineCycleMode(uint8_t mode) +sfTkError_t sfDevADE7953::setLineCycleMode(sfe_ade7953_lcycmode_reg_t mode) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegLCycMode, mode) == ksfTkErrOk); + return _theBus->writeRegister(kRegLCycMode, mode.byte); } -uint8_t sfDevADE7953::getLineCycleMode(void) +sfTkError_t sfDevADE7953::getLineCycleMode(sfe_ade7953_lcycmode_reg_t &mode) { - uint8_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegLCycMode, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegLCycMode, mode.byte); } -bool sfDevADE7953::setLineCycleCount(uint16_t halfCycles) +sfTkError_t sfDevADE7953::setLineCycleCount(uint16_t halfCycles) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegLineCyc, halfCycles) == ksfTkErrOk); + return _theBus->writeRegister(kRegLineCyc, halfCycles); } -uint16_t sfDevADE7953::getLineCycleCount(void) +sfTkError_t sfDevADE7953::getLineCycleCount(uint16_t &halfCycles) { - uint16_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegLineCyc, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegLineCyc, halfCycles); } // ========================= Sag Detection ================================== -bool sfDevADE7953::setSagCycles(uint8_t cycles) +sfTkError_t sfDevADE7953::setSagCycles(uint8_t cycles) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegSagCyc, cycles) == ksfTkErrOk); + return _theBus->writeRegister(kRegSagCyc, cycles); } -uint8_t sfDevADE7953::getSagCycles(void) +sfTkError_t sfDevADE7953::getSagCycles(uint8_t &cycles) { - uint8_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegSagCyc, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegSagCyc, cycles); } -bool sfDevADE7953::setSagLevel(uint32_t level) +sfTkError_t sfDevADE7953::setSagLevel(uint32_t level) { - if (!_theBus) - return false; - - return (_theBus->writeRegister(ksfADE7953RegSagLvl, level) == ksfTkErrOk); + return _theBus->writeRegister(kRegSagLvl, level); } -uint32_t sfDevADE7953::getSagLevel(void) +sfTkError_t sfDevADE7953::getSagLevel(uint32_t &level) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegSagLvl, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegSagLvl, level); } // ========================== Diagnostics =================================== -uint8_t sfDevADE7953::getLastOperation(void) +sfTkError_t sfDevADE7953::getLastOperation(uint8_t &op) { - uint8_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegLastOp, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegLastOp, op); } -uint16_t sfDevADE7953::getLastAddress(void) +sfTkError_t sfDevADE7953::getLastAddress(uint16_t &address) { - uint16_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegLastAdd, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegLastAdd, address); } -uint8_t sfDevADE7953::getLastData8(void) +sfTkError_t sfDevADE7953::getLastData8(uint8_t &data) { - uint8_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegLastRwData8, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegLastRwData8, data); } -uint16_t sfDevADE7953::getLastData16(void) +sfTkError_t sfDevADE7953::getLastData16(uint16_t &data) { - uint16_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegLastRwData16, value) != ksfTkErrOk) - return 0; - - return value; + return _theBus->readRegister(kRegLastRwData16, data); } -uint32_t sfDevADE7953::getLastData32(void) +sfTkError_t sfDevADE7953::getLastData32(uint32_t &data) { - uint32_t value = 0; - - if (!_theBus) - return 0; - - if (_theBus->readRegister(ksfADE7953RegLastRwData32, value) != ksfTkErrOk) - return 0; - - return value; -} \ No newline at end of file + return _theBus->readRegister(kRegLastRwData32, data); +} diff --git a/src/sfTk/sfDevADE7953.h b/src/sfTk/sfDevADE7953.h index 954431a..7f422d9 100644 --- a/src/sfTk/sfDevADE7953.h +++ b/src/sfTk/sfDevADE7953.h @@ -8,13 +8,6 @@ * CT clamp (Channel A primary, Channel B optional) with support for gain configuration, * zero-crossing detection, peak measurement, and interrupt handling. * - * The ADE7953 uses 16-bit register addresses. The address range determines data width: - * 0x0xx = 8-bit registers - * 0x1xx = 16-bit registers - * 0x2xx = 24-bit registers (lower 24 bits of 32-bit value) - * 0x3xx = 32-bit registers (upper 8 bits are sign-extended) - * This driver uses the 32-bit addresses (0x3xx) for all 24/32-bit registers. - * * @author SparkFun Electronics * @date 2025 * @copyright Copyright (c) 2025, SparkFun Electronics Inc. This project is released under the MIT License. @@ -32,234 +25,132 @@ #include #include -/////////////////////////////////////////////////////////////////////////////// -// I2C Addressing -/////////////////////////////////////////////////////////////////////////////// -const uint8_t kADE7953DefaultAddr = 0x38; ///< Default 7-bit I2C address for the ADE7953. - -/////////////////////////////////////////////////////////////////////////////// -// 8-Bit Register Addresses (0x0xx) -/////////////////////////////////////////////////////////////////////////////// -const uint16_t ksfADE7953RegSagCyc = 0x000; ///< Sag line cycles -const uint16_t ksfADE7953RegDisNoLoad = 0x001; ///< No-load detection disable -const uint16_t ksfADE7953RegLCycMode = 0x004; ///< Line cycle accumulation mode config -const uint16_t ksfADE7953RegPgaV = 0x007; ///< Voltage channel PGA gain (Bits[2:0]) -const uint16_t ksfADE7953RegPgaIA = 0x008; ///< Current Channel A PGA gain (Bits[2:0]) -const uint16_t ksfADE7953RegPgaIB = 0x009; ///< Current Channel B PGA gain (Bits[2:0]) -const uint16_t ksfADE7953RegWriteProtect = 0x040; ///< Write protection bits (Bits[2:0]) -const uint16_t ksfADE7953RegLastOp = 0x0FD; ///< Last operation type (0x35=read, 0xCA=write) -const uint16_t ksfADE7953RegLastRwData8 = 0x0FF; ///< Last successful 8-bit register data -const uint16_t ksfADE7953RegVersion = 0x702; ///< Silicon version number -const uint16_t ksfADE7953RegExRef = 0x800; ///< Reference input config (0=internal, 1=external) - -// Unlock register — write 0xAD here to unlock register 0x120 -const uint16_t ksfADE7953RegUnlock = 0x0FE; ///< Register unlock -const uint8_t ksfADE7953UnlockKey = 0xAD; ///< Unlock key value - -/////////////////////////////////////////////////////////////////////////////// -// 16-Bit Register Addresses (0x1xx) -/////////////////////////////////////////////////////////////////////////////// -const uint16_t ksfADE7953RegZXTout = 0x100; ///< Zero-crossing timeout -const uint16_t ksfADE7953RegLineCyc = 0x101; ///< Half line cycles for line cycle accumulation -const uint16_t ksfADE7953RegConfig = 0x102; ///< Configuration register -const uint16_t ksfADE7953RegCF1Den = 0x103; ///< CF1 frequency divider denominator -const uint16_t ksfADE7953RegCF2Den = 0x104; ///< CF2 frequency divider denominator -const uint16_t ksfADE7953RegCFMode = 0x107; ///< CF output selection -const uint16_t ksfADE7953RegPhCalA = 0x108; ///< Phase calibration (Channel A, sign-magnitude) -const uint16_t ksfADE7953RegPhCalB = 0x109; ///< Phase calibration (Channel B, sign-magnitude) -const uint16_t ksfADE7953RegPFA = 0x10A; ///< Power factor (Channel A) -const uint16_t ksfADE7953RegPFB = 0x10B; ///< Power factor (Channel B) -const uint16_t ksfADE7953RegAngleA = 0x10C; ///< Angle between voltage and Current Channel A -const uint16_t ksfADE7953RegAngleB = 0x10D; ///< Angle between voltage and Current Channel B -const uint16_t ksfADE7953RegPeriod = 0x10E; ///< Period register (line period from ZX) -const uint16_t ksfADE7953RegAltOutput = 0x110; ///< Alternative output functions -const uint16_t ksfADE7953RegOptimize = 0x120; ///< Performance optimization (set to 0x0030) -const uint16_t ksfADE7953RegLastAdd = 0x1FE; ///< Last successful communication address -const uint16_t ksfADE7953RegLastRwData16 = 0x1FF; ///< Last successful 16-bit register data - -// Optimization value required by datasheet for specified performance -const uint16_t ksfADE7953OptimizeValue = 0x0030; - -/////////////////////////////////////////////////////////////////////////////// -// 32-Bit Register Addresses (0x3xx) -// These registers contain valid data in the lower 24 bits; upper 8 bits are sign-extended. -/////////////////////////////////////////////////////////////////////////////// - -// --- Threshold and configuration --- -const uint16_t ksfADE7953RegSagLvl = 0x300; ///< Sag voltage level -const uint16_t ksfADE7953RegAccMode = 0x301; ///< Accumulation mode -const uint16_t ksfADE7953RegApNoLoad = 0x303; ///< Active power no-load level -const uint16_t ksfADE7953RegVarNoLoad = 0x304; ///< Reactive power no-load level -const uint16_t ksfADE7953RegVaNoLoad = 0x305; ///< Apparent power no-load level - -// --- Instantaneous values --- -const uint16_t ksfADE7953RegAVA = 0x310; ///< Instantaneous apparent power (Channel A) -const uint16_t ksfADE7953RegBVA = 0x311; ///< Instantaneous apparent power (Channel B) -const uint16_t ksfADE7953RegAWatt = 0x312; ///< Instantaneous active power (Channel A) -const uint16_t ksfADE7953RegBWatt = 0x313; ///< Instantaneous active power (Channel B) -const uint16_t ksfADE7953RegAVar = 0x314; ///< Instantaneous reactive power (Channel A) -const uint16_t ksfADE7953RegBVar = 0x315; ///< Instantaneous reactive power (Channel B) -const uint16_t ksfADE7953RegIA = 0x316; ///< Instantaneous current (Channel A) -const uint16_t ksfADE7953RegIB = 0x317; ///< Instantaneous current (Channel B) -const uint16_t ksfADE7953RegV = 0x318; ///< Instantaneous voltage - -// --- RMS values --- -const uint16_t ksfADE7953RegIRmsA = 0x31A; ///< IRMS (Channel A) -const uint16_t ksfADE7953RegIRmsB = 0x31B; ///< IRMS (Channel B) -const uint16_t ksfADE7953RegVRms = 0x31C; ///< VRMS - -// --- Energy accumulators --- -const uint16_t ksfADE7953RegAEnergyA = 0x31E; ///< Active energy (Channel A) -const uint16_t ksfADE7953RegAEnergyB = 0x31F; ///< Active energy (Channel B) -const uint16_t ksfADE7953RegREnergyA = 0x320; ///< Reactive energy (Channel A) -const uint16_t ksfADE7953RegREnergyB = 0x321; ///< Reactive energy (Channel B) -const uint16_t ksfADE7953RegApEnergyA = 0x322; ///< Apparent energy (Channel A) -const uint16_t ksfADE7953RegApEnergyB = 0x323; ///< Apparent energy (Channel B) - -// --- Peak and overcurrent --- -const uint16_t ksfADE7953RegOvLvl = 0x324; ///< Overvoltage level -const uint16_t ksfADE7953RegOiLvl = 0x325; ///< Overcurrent level -const uint16_t ksfADE7953RegVPeak = 0x326; ///< Voltage channel peak -const uint16_t ksfADE7953RegRstVPeak = 0x327; ///< Voltage peak read with reset -const uint16_t ksfADE7953RegIAPeak = 0x328; ///< Current Channel A peak -const uint16_t ksfADE7953RegRstIAPeak = 0x329; ///< Current Channel A peak read with reset -const uint16_t ksfADE7953RegIBPeak = 0x32A; ///< Current Channel B peak -const uint16_t ksfADE7953RegRstIBPeak = 0x32B; ///< Current Channel B peak read with reset - -// --- Interrupts --- -const uint16_t ksfADE7953RegIrqEnA = 0x32C; ///< Interrupt enable (Channel A + voltage) -const uint16_t ksfADE7953RegIrqStatA = 0x32D; ///< Interrupt status (Channel A + voltage) -const uint16_t ksfADE7953RegRstIrqStatA = 0x32E; ///< Reset interrupt status (Channel A + voltage) -const uint16_t ksfADE7953RegIrqEnB = 0x32F; ///< Interrupt enable (Channel B) -const uint16_t ksfADE7953RegIrqStatB = 0x330; ///< Interrupt status (Channel B) -const uint16_t ksfADE7953RegRstIrqStatB = 0x331; ///< Reset interrupt status (Channel B) - -// --- CRC --- -const uint16_t ksfADE7953RegCRC = 0x37F; ///< Checksum (32-bit only, no 24-bit access) - -// --- Gain registers (Channel A) --- -const uint16_t ksfADE7953RegAIGain = 0x380; ///< Current channel gain (Channel A) -const uint16_t ksfADE7953RegAVGain = 0x381; ///< Voltage channel gain -const uint16_t ksfADE7953RegAWGain = 0x382; ///< Active power gain (Channel A) -const uint16_t ksfADE7953RegAVarGain = 0x383; ///< Reactive power gain (Channel A) -const uint16_t ksfADE7953RegAVaGain = 0x384; ///< Apparent power gain (Channel A) -const uint16_t ksfADE7953RegAIRmsOS = 0x386; ///< IRMS offset (Channel A) -const uint16_t ksfADE7953RegVRmsOS = 0x388; ///< VRMS offset -const uint16_t ksfADE7953RegAWattOS = 0x389; ///< Active power offset (Channel A) -const uint16_t ksfADE7953RegAVarOS = 0x38A; ///< Reactive power offset (Channel A) -const uint16_t ksfADE7953RegAVaOS = 0x38B; ///< Apparent power offset (Channel A) - -// --- Gain registers (Channel B) --- -const uint16_t ksfADE7953RegBIGain = 0x38C; ///< Current channel gain (Channel B) -const uint16_t ksfADE7953RegBWGain = 0x38E; ///< Active power gain (Channel B) -const uint16_t ksfADE7953RegBVarGain = 0x38F; ///< Reactive power gain (Channel B) -const uint16_t ksfADE7953RegBVaGain = 0x390; ///< Apparent power gain (Channel B) -const uint16_t ksfADE7953RegBIRmsOS = 0x392; ///< IRMS offset (Channel B) -const uint16_t ksfADE7953RegBWattOS = 0x395; ///< Active power offset (Channel B) -const uint16_t ksfADE7953RegBVarOS = 0x396; ///< Reactive power offset (Channel B) -const uint16_t ksfADE7953RegBVaOS = 0x397; ///< Apparent power offset (Channel B) - -// --- Last data (32-bit) --- -const uint16_t ksfADE7953RegLastRwData32 = 0x3FF; ///< Last successful 24/32-bit register data - /////////////////////////////////////////////////////////////////////////////// // PGA Gain Values /////////////////////////////////////////////////////////////////////////////// -/// @brief PGA gain settings for PGA_V, PGA_IA, PGA_IB registers. -/// These map to Bits[2:0] of the respective register. -enum sfe_ade7953_pga_gain_t : uint8_t +/// @brief PGA gain settings for the PGA_V, PGA_IA, and PGA_IB registers. +/// @details These map to Bits[2:0] of the respective register. A typedef is provided so the +/// type can be used without the @c enum keyword in environments where that matters. +typedef enum sfe_ade7953_pga_gain_t : uint8_t { - ADE7953_PGA_GAIN_1 = 0, ///< 1x gain - ADE7953_PGA_GAIN_2 = 1, ///< 2x gain - ADE7953_PGA_GAIN_4 = 2, ///< 4x gain - ADE7953_PGA_GAIN_8 = 3, ///< 8x gain - ADE7953_PGA_GAIN_16 = 4, ///< 16x gain - ADE7953_PGA_GAIN_22 = 5 ///< 22x gain (current channels only) -}; + ADE7953_PGA_GAIN_1 = 0, ///< 1x gain + ADE7953_PGA_GAIN_2 = 1, ///< 2x gain + ADE7953_PGA_GAIN_4 = 2, ///< 4x gain + ADE7953_PGA_GAIN_8 = 3, ///< 8x gain + ADE7953_PGA_GAIN_16 = 4, ///< 16x gain + ADE7953_PGA_GAIN_22 = 5 ///< 22x gain (current channels only; not valid for the voltage channel) +} sfe_ade7953_pga_gain_t; /////////////////////////////////////////////////////////////////////////////// // Zero-Crossing Edge Selection /////////////////////////////////////////////////////////////////////////////// /// @brief Zero-crossing edge selection for CONFIG register bits [13:12]. -enum sfe_ade7953_zx_edge_t : uint8_t +typedef enum sfe_ade7953_zx_edge_t : uint8_t { - ADE7953_ZX_EDGE_BOTH = 0, ///< Interrupt on both positive and negative-going ZX - ADE7953_ZX_EDGE_NEGATIVE = 1, ///< Interrupt on negative-going ZX only - ADE7953_ZX_EDGE_POSITIVE = 2, ///< Interrupt on positive-going ZX only - ADE7953_ZX_EDGE_BOTH_ALT = 3 ///< Same as BOTH (alternate encoding) -}; + ADE7953_ZX_EDGE_BOTH = 0, ///< Interrupt on both positive and negative-going ZX + ADE7953_ZX_EDGE_NEGATIVE = 1, ///< Interrupt on negative-going ZX only + ADE7953_ZX_EDGE_POSITIVE = 2, ///< Interrupt on positive-going ZX only + ADE7953_ZX_EDGE_BOTH_ALT = 3 ///< Same as BOTH (alternate encoding) +} sfe_ade7953_zx_edge_t; /////////////////////////////////////////////////////////////////////////////// -// CONFIG Register Bit Definitions (Address 0x102) +// CONFIG Register Bitfield (Address 0x102) /////////////////////////////////////////////////////////////////////////////// -const uint16_t ksfADE7953ConfigIntEnA = (1 << 0); ///< Integrator enable (Channel A) -const uint16_t ksfADE7953ConfigIntEnB = (1 << 1); ///< Integrator enable (Channel B) -const uint16_t ksfADE7953ConfigHPFEn = (1 << 2); ///< HPF enable (all channels) -const uint16_t ksfADE7953ConfigPFMode = (1 << 3); ///< Power factor mode -const uint16_t ksfADE7953ConfigRevpCF = (1 << 4); ///< REVP updated on CF1(0) or CF2(1) -const uint16_t ksfADE7953ConfigRevpPulse = (1 << 5); ///< REVP output mode -const uint16_t ksfADE7953ConfigZXLPF = (1 << 6); ///< ZX LPF disable (1=disabled) -const uint16_t ksfADE7953ConfigSWRst = (1 << 7); ///< Software reset -const uint16_t ksfADE7953ConfigCRCEn = (1 << 8); ///< CRC enable -const uint16_t ksfADE7953ConfigZXI = (1 << 11); ///< ZX_I source: 0=Channel A, 1=Channel B -const uint16_t ksfADE7953ConfigCommLock = (1 << 15); ///< Communication locking (1=disabled) - -// CONFIG register ZX_EDGE field mask and shift -const uint16_t ksfADE7953ConfigZXEdgeMask = 0x3000; ///< Bits [13:12] -const uint8_t ksfADE7953ConfigZXEdgeShift = 12; +/// @brief Bitfield union for the 16-bit CONFIG register (0x102). +union sfe_ade7953_config_reg_t +{ + struct + { + uint16_t intEnA : 1; ///< Integrator enable (Channel A) [bit 0] + uint16_t intEnB : 1; ///< Integrator enable (Channel B) [bit 1] + uint16_t hpfEn : 1; ///< HPF enable (all channels) [bit 2] + uint16_t pfMode : 1; ///< Power factor mode [bit 3] + uint16_t revpCF : 1; ///< REVP updated on CF1(0) or CF2(1) [bit 4] + uint16_t revpPulse : 1; ///< REVP output mode [bit 5] + uint16_t zxLPF : 1; ///< ZX LPF disable (1=disabled) [bit 6] + uint16_t swRst : 1; ///< Software reset [bit 7] + uint16_t crcEn : 1; ///< CRC enable [bit 8] + uint16_t : 2; ///< Reserved [bits 9-10] + uint16_t zxI : 1; ///< ZX_I source: 0=Channel A, 1=Channel B [bit 11] + uint16_t zxEdge : 2; ///< ZX edge selection (see sfe_ade7953_zx_edge_t) [bits 13:12] + uint16_t : 1; ///< Reserved [bit 14] + uint16_t commLock : 1; ///< Communication locking (1=disabled) [bit 15] + }; + uint16_t word; ///< Raw register value for I2C read/write. +}; /////////////////////////////////////////////////////////////////////////////// -// LCYCMODE Register Bit Definitions (Address 0x004) +// LCYCMODE Register Bitfield (Address 0x004) /////////////////////////////////////////////////////////////////////////////// -const uint8_t ksfADE7953LCycALWatt = (1 << 0); ///< Active energy line cycle mode (Channel A) -const uint8_t ksfADE7953LCycBLWatt = (1 << 1); ///< Active energy line cycle mode (Channel B) -const uint8_t ksfADE7953LCycALVar = (1 << 2); ///< Reactive energy line cycle mode (Channel A) -const uint8_t ksfADE7953LCycBLVar = (1 << 3); ///< Reactive energy line cycle mode (Channel B) -const uint8_t ksfADE7953LCycALVa = (1 << 4); ///< Apparent energy line cycle mode (Channel A) -const uint8_t ksfADE7953LCycBLVa = (1 << 5); ///< Apparent energy line cycle mode (Channel B) -const uint8_t ksfADE7953LCycRstRead = (1 << 6); ///< Read-with-reset enable for all registers +/// @brief Bitfield union for the 8-bit LCYCMODE register (0x004). +union sfe_ade7953_lcycmode_reg_t +{ + struct + { + uint8_t aLWatt : 1; ///< Active energy line cycle mode (Channel A) [bit 0] + uint8_t bLWatt : 1; ///< Active energy line cycle mode (Channel B) [bit 1] + uint8_t aLVar : 1; ///< Reactive energy line cycle mode (Channel A) [bit 2] + uint8_t bLVar : 1; ///< Reactive energy line cycle mode (Channel B) [bit 3] + uint8_t aLVa : 1; ///< Apparent energy line cycle mode (Channel A) [bit 4] + uint8_t bLVa : 1; ///< Apparent energy line cycle mode (Channel B) [bit 5] + uint8_t rstRead : 1; ///< Read-with-reset enable for all registers [bit 6] + uint8_t : 1; ///< Reserved [bit 7] + }; + uint8_t byte; ///< Raw register value for I2C read/write. +}; /////////////////////////////////////////////////////////////////////////////// -// DISNOLOAD Register Bit Definitions (Address 0x001) +// DISNOLOAD Register Bitfield (Address 0x001) /////////////////////////////////////////////////////////////////////////////// -const uint8_t ksfADE7953NoLoadDisAP = (1 << 0); ///< Disable active power no-load detection -const uint8_t ksfADE7953NoLoadDisVAR = (1 << 1); ///< Disable reactive power no-load detection -const uint8_t ksfADE7953NoLoadDisVA = (1 << 2); ///< Disable apparent power no-load detection +/// @brief Bitfield union for the 8-bit DISNOLOAD register (0x001). +union sfe_ade7953_disnoload_reg_t +{ + struct + { + uint8_t disAP : 1; ///< Disable active power no-load detection [bit 0] + uint8_t disVAR : 1; ///< Disable reactive power no-load detection [bit 1] + uint8_t disVA : 1; ///< Disable apparent power no-load detection [bit 2] + uint8_t : 5; ///< Reserved [bits 3-7] + }; + uint8_t byte; ///< Raw register value for I2C read/write. +}; /////////////////////////////////////////////////////////////////////////////// -// Interrupt Bitmask Constants +// IRQ Register Bitfield // Used with IRQENA/IRQSTATA (Channel A) and IRQENB/IRQSTATB (Channel B). -// Channel B interrupts use bits [13:0] only. -/////////////////////////////////////////////////////////////////////////////// -const uint32_t ksfADE7953IrqAEHF = (1UL << 0); ///< Active energy half full -const uint32_t ksfADE7953IrqVAREHF = (1UL << 1); ///< Reactive energy half full -const uint32_t ksfADE7953IrqVAEHF = (1UL << 2); ///< Apparent energy half full -const uint32_t ksfADE7953IrqAEOF = (1UL << 3); ///< Active energy overflow/underflow -const uint32_t ksfADE7953IrqVAREOF = (1UL << 4); ///< Reactive energy overflow/underflow -const uint32_t ksfADE7953IrqVAEOF = (1UL << 5); ///< Apparent energy overflow/underflow -const uint32_t ksfADE7953IrqApNoLoad = (1UL << 6); ///< Active power no-load detected -const uint32_t ksfADE7953IrqVarNoLoad = (1UL << 7); ///< Reactive power no-load detected -const uint32_t ksfADE7953IrqVaNoLoad = (1UL << 8); ///< Apparent power no-load detected -const uint32_t ksfADE7953IrqApSign = (1UL << 9); ///< Active energy sign changed -const uint32_t ksfADE7953IrqVarSign = (1UL << 10); ///< Reactive energy sign changed -const uint32_t ksfADE7953IrqZXToI = (1UL << 11); ///< Current zero-crossing timeout -const uint32_t ksfADE7953IrqZXI = (1UL << 12); ///< Current zero-crossing detected -const uint32_t ksfADE7953IrqOI = (1UL << 13); ///< Overcurrent threshold exceeded -// Bits 14-21 are Channel A / voltage only (not present in IRQENB/IRQSTATB) -const uint32_t ksfADE7953IrqZXToV = (1UL << 14); ///< Voltage zero-crossing timeout -const uint32_t ksfADE7953IrqZXV = (1UL << 15); ///< Voltage zero-crossing detected -const uint32_t ksfADE7953IrqOV = (1UL << 16); ///< Overvoltage threshold exceeded -const uint32_t ksfADE7953IrqWSmp = (1UL << 17); ///< New waveform data acquired -const uint32_t ksfADE7953IrqCycEnd = (1UL << 18); ///< End of line cycle accumulation period -const uint32_t ksfADE7953IrqSag = (1UL << 19); ///< Sag event occurred -const uint32_t ksfADE7953IrqReset = (1UL << 20); ///< Reset occurred (always enabled) -const uint32_t ksfADE7953IrqCRC = (1UL << 21); ///< Checksum changed - -/////////////////////////////////////////////////////////////////////////////// -// LAST_OP Values +// Channel B interrupts use bits [13:0] only — bits [21:14] are Channel A / voltage only. /////////////////////////////////////////////////////////////////////////////// -const uint8_t ksfADE7953LastOpRead = 0x35; ///< Last operation was a read -const uint8_t ksfADE7953LastOpWrite = 0xCA; ///< Last operation was a write +/// @brief Bitfield union for the 32-bit IRQ enable and status registers. +union sfe_ade7953_irq_reg_t +{ + struct + { + uint32_t aehf : 1; ///< Active energy half full [bit 0] + uint32_t varehf : 1; ///< Reactive energy half full [bit 1] + uint32_t vaehf : 1; ///< Apparent energy half full [bit 2] + uint32_t aeof : 1; ///< Active energy overflow/underflow [bit 3] + uint32_t vareof : 1; ///< Reactive energy overflow/underflow [bit 4] + uint32_t vaeof : 1; ///< Apparent energy overflow/underflow [bit 5] + uint32_t apNoLoad : 1; ///< Active power no-load detected [bit 6] + uint32_t varNoLoad : 1; ///< Reactive power no-load detected [bit 7] + uint32_t vaNoLoad : 1; ///< Apparent power no-load detected [bit 8] + uint32_t apSign : 1; ///< Active energy sign changed [bit 9] + uint32_t varSign : 1; ///< Reactive energy sign changed [bit 10] + uint32_t zxToI : 1; ///< Current zero-crossing timeout [bit 11] + uint32_t zxI : 1; ///< Current zero-crossing detected [bit 12] + uint32_t oI : 1; ///< Overcurrent threshold exceeded [bit 13] + uint32_t zxToV : 1; ///< Voltage zero-crossing timeout (Channel A only) [bit 14] + uint32_t zxV : 1; ///< Voltage zero-crossing detected (Channel A only) [bit 15] + uint32_t oV : 1; ///< Overvoltage threshold exceeded (Channel A only) [bit 16] + uint32_t wSmp : 1; ///< New waveform data acquired (Channel A only) [bit 17] + uint32_t cycEnd : 1; ///< End of line cycle accumulation period (Channel A only) [bit 18] + uint32_t sag : 1; ///< Sag event occurred (Channel A only) [bit 19] + uint32_t reset : 1; ///< Reset occurred — always enabled (Channel A only) [bit 20] + uint32_t crc : 1; ///< Checksum changed (Channel A only) [bit 21] + uint32_t : 10; ///< Reserved [bits 22-31] + }; + uint32_t word; ///< Raw register value for I2C read/write. +}; /////////////////////////////////////////////////////////////////////////////// // Class Declaration @@ -268,12 +159,9 @@ const uint8_t ksfADE7953LastOpWrite = 0xCA; ///< Last operation was a write /// @brief Platform-independent driver for the ADE7953 energy metering IC. /// /// @details This class implements register-level access to the ADE7953 via the SparkFun Toolkit -/// bus interface. It is focused on current measurement (CT clamp on Channel A, optional Channel B) -/// with support for gain configuration, zero-crossing detection, peak measurement, calibration -/// offsets, and interrupt management. -/// -/// This class does not depend on Arduino — the Arduino-specific wrapper (SfeADE7953ArdI2C) provides -/// the begin() and isConnected() methods that initialize the Toolkit I2C bus. +/// bus interface. Most methods return a SparkFun Toolkit error code (::ksfTkErrOk on success, a +/// negative value on error); values read from the device are returned through reference (output) +/// parameters. Callers that do not care about error handling can simply ignore the returned code. class sfDevADE7953 { public: @@ -282,303 +170,519 @@ class sfDevADE7953 } /// @brief Initialize the device driver with the given bus. - /// @details Sets the communication bus, configures byte order for big-endian, - /// and writes the optimized performance register (0x120 = 0x0030) as required by the datasheet. - /// @param theBus Pointer to the initialized bus object. - /// @return ksfTkErrOk on success, or an error code on failure. - bool begin(sfTkIBus *theBus = nullptr); + /// @details Verifies the device is responding, configures big-endian byte order, applies the + /// optimized performance register sequence required by the datasheet, and sets sensible default + /// gain / HPF configuration so a basic sketch works without any further setup. + /// @param theBus Pointer to the initialized bus object. If null, a bus set by a prior call is used. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t begin(sfTkIBus *theBus = nullptr); - /// @brief Set the communication bus pointer. - /// @param theBus Bus to use for all register I/O. - void setCommunicationBus(sfTkIBus *theBus); + /// @brief Check whether the ADE7953 is connected and responding. + /// @return true if the device responds, false otherwise. + bool isConnected(void); // ========================= Identity & Setup =========================== /// @brief Read the silicon version number from the Version register (0x702). - /// @return Silicon version, or 0 on error. - uint8_t getVersion(void); + /// @param version Output reference that receives the silicon version. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getVersion(uint8_t &version); - /// @brief Perform a software reset via the CONFIG register SWRST bit. - /// @return True on success, false on error. - bool reset(void); + /// @brief Perform a software reset and re-apply the datasheet performance configuration. + /// @details Asserts the CONFIG SWRST bit. Because a reset returns all registers to their + /// power-on defaults, this method re-runs the unlock + optimize sequence (and default gain / HPF + /// configuration) afterward so the device is left in the same state as a fresh begin(). + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t reset(void); /// @brief Set write protection bits. /// @param protect Write protection value (Bits[2:0]). - /// @return True on success, false on error. - bool setWriteProtect(uint8_t protect); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setWriteProtect(uint8_t protect); /// @brief Get the current write protection setting. - /// @return Write protection value, or 0 on error. - uint8_t getWriteProtect(void); + /// @param protect Output reference that receives the write protection value (Bits[2:0]). + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getWriteProtect(uint8_t &protect); // ======================== PGA Gain Configuration ====================== /// @brief Set the PGA gain for Current Channel A. /// @param gain PGA gain setting (see sfe_ade7953_pga_gain_t). - /// @return True on success, false on error. - bool setGainIA(sfe_ade7953_pga_gain_t gain); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setGainIA(sfe_ade7953_pga_gain_t gain); /// @brief Get the PGA gain for Current Channel A. - /// @return PGA gain setting, or ADE7953_PGA_GAIN_1 on error. - sfe_ade7953_pga_gain_t getGainIA(void); + /// @param gain Output reference that receives the PGA gain setting. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getGainIA(sfe_ade7953_pga_gain_t &gain); /// @brief Set the PGA gain for Current Channel B. /// @param gain PGA gain setting. - /// @return True on success, false on error. - bool setGainIB(sfe_ade7953_pga_gain_t gain); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setGainIB(sfe_ade7953_pga_gain_t gain); /// @brief Get the PGA gain for Current Channel B. - /// @return PGA gain setting, or ADE7953_PGA_GAIN_1 on error. - sfe_ade7953_pga_gain_t getGainIB(void); + /// @param gain Output reference that receives the PGA gain setting. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getGainIB(sfe_ade7953_pga_gain_t &gain); /// @brief Set the PGA gain for the voltage channel. - /// @note Even in current-only configurations, this affects ZX detection behavior. - /// @param gain PGA gain setting (ADE7953_PGA_GAIN_22 not valid for voltage). - /// @return True on success, false on error. - bool setGainV(sfe_ade7953_pga_gain_t gain); + /// @note ADE7953_PGA_GAIN_22 is not valid for the voltage channel and is rejected. + /// @param gain PGA gain setting. + /// @return ::ksfTkErrOk on success, ::ksfTkErrFail for an invalid gain, or an error code on + /// communication failure. + sfTkError_t setGainV(sfe_ade7953_pga_gain_t gain); /// @brief Get the PGA gain for the voltage channel. - /// @return PGA gain setting, or ADE7953_PGA_GAIN_1 on error. - sfe_ade7953_pga_gain_t getGainV(void); + /// @param gain Output reference that receives the PGA gain setting. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getGainV(sfe_ade7953_pga_gain_t &gain); + + /// @brief Set the digital (fine) gain for Current Channel A using a raw register value. + /// @param gain Digital gain register value (0x400000 = unity). + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setDigitalGainIA(uint32_t gain); + + /// @brief Set the digital (fine) gain for Current Channel A using a floating-point multiplier. + /// @details Converts the multiplier to the nearest valid register value (0x400000 = 1.0x). + /// @param multiplier Desired gain multiplier (must be > 0 and within the representable range). + /// @return ::ksfTkErrOk on success, ::ksfTkErrFail if out of range, or an error code on failure. + sfTkError_t setDigitalGainIA(float multiplier); + + /// @brief Get the digital gain for Current Channel A as a raw register value. + /// @param gain Output reference that receives the digital gain register value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getDigitalGainIA(uint32_t &gain); + + /// @brief Get the digital gain for Current Channel A as a floating-point multiplier. + /// @param multiplier Output reference that receives the gain multiplier (1.0 = unity). + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getDigitalGainIA(float &multiplier); + + /// @brief Set the digital (fine) gain for Current Channel B using a raw register value. + /// @param gain Digital gain register value (0x400000 = unity). + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setDigitalGainIB(uint32_t gain); + + /// @brief Set the digital (fine) gain for Current Channel B using a floating-point multiplier. + /// @param multiplier Desired gain multiplier (must be > 0 and within the representable range). + /// @return ::ksfTkErrOk on success, ::ksfTkErrFail if out of range, or an error code on failure. + sfTkError_t setDigitalGainIB(float multiplier); + + /// @brief Get the digital gain for Current Channel B as a raw register value. + /// @param gain Output reference that receives the digital gain register value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getDigitalGainIB(uint32_t &gain); + + /// @brief Get the digital gain for Current Channel B as a floating-point multiplier. + /// @param multiplier Output reference that receives the gain multiplier (1.0 = unity). + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getDigitalGainIB(float &multiplier); - /// @brief Set the digital gain for Current Channel A. - /// @param gain Digital gain value (default 0x400000). - /// @return True on success, false on error. - bool setDigitalGainIA(uint32_t gain); + // ======================= Current Measurement ========================== - /// @brief Get the digital gain for Current Channel A. - /// @return Digital gain value, or 0 on error. - uint32_t getDigitalGainIA(void); + /// @brief Read the raw IRMS value for Current Channel A. + /// @param value Output reference that receives the raw unsigned IRMS value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getIRmsA(uint32_t &value); + + /// @brief Read the raw IRMS value for Current Channel B. + /// @param value Output reference that receives the raw unsigned IRMS value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getIRmsB(uint32_t &value); + + /// @brief Read the RMS current on Channel A converted to amps. + /// @details Combines the raw IRMS reading with the configured PGA gain, CT ratio, burden + /// resistor, and full-scale constants. The PGA gain is read from the device on each call, so the + /// conversion always matches the active gain. + /// @param amps Output reference that receives the current in amps. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getCurrentA(float &s); + + /// @brief Read the RMS current on Channel B converted to amps. + /// @param amps Output reference that receives the current in amps. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getCurrentB(float &s); - /// @brief Set the digital gain for Current Channel B. - /// @param gain Digital gain value (default 0x400000). - /// @return True on success, false on error. - bool setDigitalGainIB(uint32_t gain); + /// @brief Read the instantaneous current sample for Channel A. + /// @param value Output reference that receives the signed instantaneous current value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getInstantaneousIA(int32_t &value); - /// @brief Get the digital gain for Current Channel B. - /// @return Digital gain value, or 0 on error. - uint32_t getDigitalGainIB(void); + /// @brief Read the instantaneous current sample for Channel B. + /// @param value Output reference that receives the signed instantaneous current value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getInstantaneousIB(int32_t &value); - // ======================= Current Measurement ========================== + // =================== Current Conversion Calibration =================== - /// @brief Read the IRMS value for Current Channel A. - /// @return Raw unsigned IRMS value, or 0 on error. - uint32_t getIRmsA(void); + /// @brief Set the CT (current transformer) turns ratio used by getCurrentA() / getCurrentB(). + /// @param ratio Turns ratio of the current transformer (e.g. 2000.0 for a 30A:15mA clamp). + void setCurrentTransformerRatio(float ratio); - /// @brief Read the IRMS value for Current Channel B. - /// @return Raw unsigned IRMS value, or 0 on error. - uint32_t getIRmsB(void); + /// @brief Get the configured CT turns ratio. + /// @return The current transformer turns ratio. + float getCurrentTransformerRatio(void); - /// @brief Read the instantaneous current sample for Channel A. - /// @return Signed instantaneous current value, or 0 on error. - int32_t getInstantaneousIA(void); + /// @brief Set the burden resistor value (in ohms) used by getCurrentA() / getCurrentB(). + /// @param ohms Burden resistor value in ohms. + void setBurdenResistor(float ohms); - /// @brief Read the instantaneous current sample for Channel B. - /// @return Signed instantaneous current value, or 0 on error. - int32_t getInstantaneousIB(void); + /// @brief Get the configured burden resistor value. + /// @return The burden resistor value in ohms. + float getBurdenResistor(void); // ======================== Peak Detection ============================== /// @brief Read the peak current value for Channel A. - /// @return Peak value, or 0 on error. - uint32_t getPeakIA(void); + /// @param value Output reference that receives the peak value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getPeakIA(uint32_t &value); /// @brief Read the peak current value for Channel B. - /// @return Peak value, or 0 on error. - uint32_t getPeakIB(void); - - /// @brief Read and reset the peak current value for Channel A. - /// @details Atomically reads the peak and clears the register. - /// @return Peak value before reset, or 0 on error. - uint32_t readAndResetPeakIA(void); - - /// @brief Read and reset the peak current value for Channel B. - /// @details Atomically reads the peak and clears the register. - /// @return Peak value before reset, or 0 on error. - uint32_t readAndResetPeakIB(void); + /// @param value Output reference that receives the peak value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getPeakIB(uint32_t &value); + + /// @brief Read the peak current value for Channel A and clear it. + /// @details Reads the read-with-reset register, which returns the peak value and clears it in a + /// single operation. + /// @param value Output reference that receives the peak value before reset. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t readAndResetPeakIA(uint32_t &value); + + /// @brief Read the peak current value for Channel B and clear it. + /// @param value Output reference that receives the peak value before reset. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t readAndResetPeakIB(uint32_t &value); /// @brief Set the overcurrent detection threshold. /// @param level Threshold level (24-bit value). - /// @return True on success, false on error. - bool setOvercurrentLevel(uint32_t level); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setOvercurrentLevel(uint32_t level); /// @brief Get the overcurrent detection threshold. - /// @return Threshold level, or 0 on error. - uint32_t getOvercurrentLevel(void); + /// @param level Output reference that receives the threshold level. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getOvercurrentLevel(uint32_t &level); // ====================== Calibration & Offset ========================== /// @brief Set the IRMS offset for Current Channel A. + /// @note The ADE7953 applies this offset in the squared domain (the value is added to the + /// square of the RMS result before the square root). To null a no-load noise floor of @c N raw + /// counts the offset is approximately @c -(N*N) scaled per the datasheet, not @c -N. /// @param offset Signed offset value for IRMS correction. - /// @return True on success, false on error. - bool setIRmsOffsetA(int32_t offset); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setIRmsOffsetA(int32_t offset); /// @brief Get the IRMS offset for Current Channel A. - /// @return Signed offset value, or 0 on error. - int32_t getIRmsOffsetA(void); + /// @param offset Output reference that receives the signed offset value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getIRmsOffsetA(int32_t &offset); - /// @brief Set the IRMS offset for Current Channel B. + /// @brief Set the IRMS offset for Current Channel B. See setIRmsOffsetA() for the squared-domain note. /// @param offset Signed offset value for IRMS correction. - /// @return True on success, false on error. - bool setIRmsOffsetB(int32_t offset); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setIRmsOffsetB(int32_t offset); /// @brief Get the IRMS offset for Current Channel B. - /// @return Signed offset value, or 0 on error. - int32_t getIRmsOffsetB(void); + /// @param offset Output reference that receives the signed offset value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getIRmsOffsetB(int32_t &offset); // ======================= Zero-Crossing (ZX_I) ========================= /// @brief Select which current channel drives the ZX_I output. /// @param useChannelB If true, ZX_I is based on Channel B; if false, Channel A. - /// @return True on success, false on error. - bool setZXISource(bool useChannelB); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setZXISourceChannel(bool useChannelB); /// @brief Get which current channel drives the ZX_I output. - /// @return True if Channel B, false if Channel A (or on error). - bool getZXISource(void); + /// @param useChannelB Output reference set true if Channel B drives ZX_I, false for Channel A. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getZXISourceChannel(bool &useChannelB); /// @brief Set the zero-crossing edge selection for interrupt generation. /// @param edge Edge selection (see sfe_ade7953_zx_edge_t). - /// @return True on success, false on error. - bool setZXEdge(sfe_ade7953_zx_edge_t edge); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setZXEdge(sfe_ade7953_zx_edge_t edge); /// @brief Get the current zero-crossing edge selection. - /// @return Edge selection value. - sfe_ade7953_zx_edge_t getZXEdge(void); + /// @param edge Output reference that receives the edge selection value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getZXEdge(sfe_ade7953_zx_edge_t &edge); /// @brief Enable or disable the zero-crossing low-pass filter. /// @param enable True to enable the LPF, false to disable. - /// @return True on success, false on error. - bool enableZXLPF(bool enable); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t enableZXLPF(bool enable); /// @brief Enable or disable the high-pass filter on all channels. - /// @details The HPF removes DC offset from the current and voltage channels before - /// RMS computation. Should be enabled for accurate IRMS readings. Enabled by default - /// after power-on reset. + /// @details The HPF removes DC offset before RMS computation. It should be enabled for accurate + /// IRMS readings, and is enabled by default after power-on reset and by begin(). /// @param enable True to enable the HPF, false to disable. - /// @return True on success, false on error. - bool enableHPF(bool enable); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t enableHPF(bool enable); /// @brief Check whether the high-pass filter is currently enabled. - /// @return True if HPF is enabled, false if disabled or on error. - bool isHPFEnabled(void); + /// @param enabled Output reference set true if the HPF is enabled. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t isHPFEnabled(bool &enabled); /// @brief Read the line period derived from zero-crossing detection. - /// @return Period register value, or 0 on error. - uint16_t getPeriod(void); + /// @param period Output reference that receives the period register value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getPeriod(uint16_t &period); // =========================== Interrupts =============================== /// @brief Set the interrupt enable mask for Channel A (and voltage). - /// @details Use the ksfADE7953Irq* bitmask constants to build the mask. - /// @param mask Interrupt enable bitmask. - /// @return True on success, false on error. - bool setInterruptEnableA(uint32_t mask); + /// @param mask Interrupt enable bitfield struct. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setInterruptEnableA(sfe_ade7953_irq_reg_t mask); /// @brief Get the interrupt enable mask for Channel A. - /// @return Interrupt enable bitmask, or 0 on error. - uint32_t getInterruptEnableA(void); + /// @param mask Output reference that receives the interrupt enable bitfield. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getInterruptEnableA(sfe_ade7953_irq_reg_t &mask); /// @brief Read the interrupt status for Channel A (non-destructive). - /// @return Interrupt status bitmask, or 0 on error. - uint32_t getInterruptStatusA(void); + /// @param status Output reference that receives the interrupt status bitfield. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getInterruptStatusA(sfe_ade7953_irq_reg_t &status); - /// @brief Read and reset the interrupt status for Channel A. - /// @details This atomically reads the status and clears the flags. - /// @return Interrupt status bitmask before reset, or 0 on error. - uint32_t readAndResetInterruptStatusA(void); + /// @brief Read the interrupt status for Channel A and clear it. + /// @details Reads the read-with-reset register, returning the status and clearing the flags in a + /// single operation. + /// @param status Output reference that receives the interrupt status bitfield before reset. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t readAndResetInterruptStatusA(sfe_ade7953_irq_reg_t &status); /// @brief Set the interrupt enable mask for Channel B. /// @details Only bits [13:0] are valid for Channel B. - /// @param mask Interrupt enable bitmask. - /// @return True on success, false on error. - bool setInterruptEnableB(uint32_t mask); + /// @param mask Interrupt enable bitfield struct. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setInterruptEnableB(sfe_ade7953_irq_reg_t mask); /// @brief Get the interrupt enable mask for Channel B. - /// @return Interrupt enable bitmask, or 0 on error. - uint32_t getInterruptEnableB(void); + /// @param mask Output reference that receives the interrupt enable bitfield. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getInterruptEnableB(sfe_ade7953_irq_reg_t &mask); /// @brief Read the interrupt status for Channel B (non-destructive). - /// @return Interrupt status bitmask, or 0 on error. - uint32_t getInterruptStatusB(void); + /// @param status Output reference that receives the interrupt status bitfield. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getInterruptStatusB(sfe_ade7953_irq_reg_t &status); - /// @brief Read and reset the interrupt status for Channel B. - /// @return Interrupt status bitmask before reset, or 0 on error. - uint32_t readAndResetInterruptStatusB(void); + /// @brief Read the interrupt status for Channel B and clear it. + /// @param status Output reference that receives the interrupt status bitfield before reset. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t readAndResetInterruptStatusB(sfe_ade7953_irq_reg_t &status); // ========================= No-Load Detection ========================== /// @brief Set the no-load detection disable register. - /// @param mask Bitmask of no-load features to disable (see ksfADE7953NoLoadDis* constants). - /// @return True on success, false on error. - bool setNoLoadDisable(uint8_t mask); + /// @param mask No-load disable bitfield struct. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setNoLoadDisable(sfe_ade7953_disnoload_reg_t mask); /// @brief Get the no-load detection disable register. - /// @return Current disable bitmask, or 0 on error. - uint8_t getNoLoadDisable(void); + /// @param mask Output reference that receives the no-load disable bitfield. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getNoLoadDisable(sfe_ade7953_disnoload_reg_t &mask); // ====================== Line Cycle Accumulation ======================= /// @brief Set the line cycle accumulation mode register. - /// @param mode Bitmask of line cycle modes (see ksfADE7953LCyc* constants). - /// @return True on success, false on error. - bool setLineCycleMode(uint8_t mode); + /// @param mode Line cycle mode bitfield struct. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setLineCycleMode(sfe_ade7953_lcycmode_reg_t mode); /// @brief Get the line cycle accumulation mode register. - /// @return Mode bitmask, or 0 on error. - uint8_t getLineCycleMode(void); + /// @param mode Output reference that receives the line cycle mode bitfield. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getLineCycleMode(sfe_ade7953_lcycmode_reg_t &mode); /// @brief Set the number of half line cycles for line cycle accumulation. /// @param halfCycles Number of half cycles. - /// @return True on success, false on error. - bool setLineCycleCount(uint16_t halfCycles); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setLineCycleCount(uint16_t halfCycles); /// @brief Get the number of half line cycles for line cycle accumulation. - /// @return Number of half cycles, or 0 on error. - uint16_t getLineCycleCount(void); + /// @param halfCycles Output reference that receives the number of half cycles. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getLineCycleCount(uint16_t &halfCycles); // ========================= Sag Detection ============================== /// @brief Set the number of sag line cycles. /// @param cycles Number of half line cycles for sag detection. - /// @return True on success, false on error. - bool setSagCycles(uint8_t cycles); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setSagCycles(uint8_t cycles); /// @brief Get the number of sag line cycles. - /// @return Number of half line cycles, or 0 on error. - uint8_t getSagCycles(void); + /// @param cycles Output reference that receives the number of half line cycles. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getSagCycles(uint8_t &cycles); /// @brief Set the sag voltage level threshold. /// @param level Sag level threshold (24-bit value). - /// @return True on success, false on error. - bool setSagLevel(uint32_t level); + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setSagLevel(uint32_t level); /// @brief Get the sag voltage level threshold. - /// @return Sag level, or 0 on error. - uint32_t getSagLevel(void); + /// @param level Output reference that receives the sag level. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getSagLevel(uint32_t &level); // ========================== Diagnostics =============================== /// @brief Get the type of the last successful communication. - /// @return 0x35 for read, 0xCA for write, or 0 on error. - uint8_t getLastOperation(void); + /// @param op Output reference that receives 0x35 for a read or 0xCA for a write. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getLastOperation(uint8_t &op); /// @brief Get the address of the last successful communication. - /// @return Register address, or 0 on error. - uint16_t getLastAddress(void); + /// @param address Output reference that receives the register address. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getLastAddress(uint16_t &address); /// @brief Get the data from the last successful 8-bit register communication. - /// @return Data value, or 0 on error. - uint8_t getLastData8(void); + /// @param data Output reference that receives the data value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getLastData8(uint8_t &data); /// @brief Get the data from the last successful 16-bit register communication. - /// @return Data value, or 0 on error. - uint16_t getLastData16(void); + /// @param data Output reference that receives the data value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getLastData16(uint16_t &data); /// @brief Get the data from the last successful 24/32-bit register communication. - /// @return Data value, or 0 on error. - uint32_t getLastData32(void); + /// @param data Output reference that receives the data value. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t getLastData32(uint32_t &data); protected: + /// @brief Apply the datasheet unlock + optimize sequence and default configuration. + /// @details Shared by begin() and reset(). Assumes _theBus is valid and byte order is set. + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t applyDefaultConfig(void); + + /// @brief Convert a PGA gain enum value to its numeric multiplier (1, 2, 4, 8, 16, 22). + static float pgaGainToMultiplier(sfe_ade7953_pga_gain_t gain); + sfTkIBus *_theBus; ///< Pointer to the communication bus device. -}; \ No newline at end of file + + // --- Current-to-amps conversion parameters (sensible defaults for the Qwiic board) --- + float _ctRatio = 2000.0f; ///< CT turns ratio (30A:15mA = 2000:1). + float _burdenResistor = 5.6f; ///< Burden resistor in ohms. + float _fullScaleCode = 5928256.0f; ///< ADE7953 full-scale IRMS code. + float _fullScaleVrms = 0.35355f; ///< Full-scale input RMS voltage (0.5 V peak / sqrt(2)). + + /////////////////////////////////////////////////////////////////////////// + // I2C Addressing + /////////////////////////////////////////////////////////////////////////// + static const uint8_t kI2CAddress = 0x38; ///< 7-bit I2C address for the ADE7953 (only address). + + // 8-Bit Register Addresses (0x0xx) + static const uint16_t kRegSagCyc = 0x000; ///< Sag line cycles + static const uint16_t kRegDisNoLoad = 0x001; ///< No-load detection disable + static const uint16_t kRegLCycMode = 0x004; ///< Line cycle accumulation mode config + static const uint16_t kRegPgaV = 0x007; ///< Voltage channel PGA gain (Bits[2:0]) + static const uint16_t kRegPgaIA = 0x008; ///< Current Channel A PGA gain (Bits[2:0]) + static const uint16_t kRegPgaIB = 0x009; ///< Current Channel B PGA gain (Bits[2:0]) + static const uint16_t kRegWriteProtect = 0x040; ///< Write protection bits (Bits[2:0]) + static const uint16_t kRegLastOp = 0x0FD; ///< Last operation type (0x35=read, 0xCA=write) + static const uint16_t kRegLastRwData8 = 0x0FF; ///< Last successful 8-bit register data + static const uint16_t kRegVersion = 0x702; ///< Silicon version number + static const uint16_t kRegExRef = 0x800; ///< Reference input config (0=internal, 1=external) + static const uint16_t kRegUnlock = 0x0FE; ///< Register unlock (write key to unlock 0x120) + + // 16-Bit Register Addresses (0x1xx) + static const uint16_t kRegZXTout = 0x100; ///< Zero-crossing timeout + static const uint16_t kRegLineCyc = 0x101; ///< Half line cycles for line cycle accumulation + static const uint16_t kRegConfig = 0x102; ///< Configuration register + static const uint16_t kRegCF1Den = 0x103; ///< CF1 frequency divider denominator + static const uint16_t kRegCF2Den = 0x104; ///< CF2 frequency divider denominator + static const uint16_t kRegCFMode = 0x107; ///< CF output selection + static const uint16_t kRegPhCalA = 0x108; ///< Phase calibration (Channel A) + static const uint16_t kRegPhCalB = 0x109; ///< Phase calibration (Channel B) + static const uint16_t kRegPFA = 0x10A; ///< Power factor (Channel A) + static const uint16_t kRegPFB = 0x10B; ///< Power factor (Channel B) + static const uint16_t kRegAngleA = 0x10C; ///< Angle between voltage and Current Channel A + static const uint16_t kRegAngleB = 0x10D; ///< Angle between voltage and Current Channel B + static const uint16_t kRegPeriod = 0x10E; ///< Period register (line period from ZX) + static const uint16_t kRegAltOutput = 0x110; ///< Alternative output functions + static const uint16_t kRegOptimize = 0x120; ///< Performance optimization (set to 0x0030) + static const uint16_t kRegLastAdd = 0x1FE; ///< Last successful communication address + static const uint16_t kRegLastRwData16 = 0x1FF; ///< Last successful 16-bit register data + + // 32-Bit Register Addresses (0x3xx) -- valid data in lower 24 bits, upper 8 sign-extended + static const uint16_t kRegSagLvl = 0x300; ///< Sag voltage level + static const uint16_t kRegAccMode = 0x301; ///< Accumulation mode + static const uint16_t kRegApNoLoad = 0x303; ///< Active power no-load level + static const uint16_t kRegVarNoLoad = 0x304; ///< Reactive power no-load level + static const uint16_t kRegVaNoLoad = 0x305; ///< Apparent power no-load level + static const uint16_t kRegAVA = 0x310; ///< Instantaneous apparent power (Channel A) + static const uint16_t kRegBVA = 0x311; ///< Instantaneous apparent power (Channel B) + static const uint16_t kRegAWatt = 0x312; ///< Instantaneous active power (Channel A) + static const uint16_t kRegBWatt = 0x313; ///< Instantaneous active power (Channel B) + static const uint16_t kRegAVar = 0x314; ///< Instantaneous reactive power (Channel A) + static const uint16_t kRegBVar = 0x315; ///< Instantaneous reactive power (Channel B) + static const uint16_t kRegIA = 0x316; ///< Instantaneous current (Channel A) + static const uint16_t kRegIB = 0x317; ///< Instantaneous current (Channel B) + static const uint16_t kRegV = 0x318; ///< Instantaneous voltage + static const uint16_t kRegIRmsA = 0x31A; ///< IRMS (Channel A) + static const uint16_t kRegIRmsB = 0x31B; ///< IRMS (Channel B) + static const uint16_t kRegVRms = 0x31C; ///< VRMS + static const uint16_t kRegAEnergyA = 0x31E; ///< Active energy (Channel A) + static const uint16_t kRegAEnergyB = 0x31F; ///< Active energy (Channel B) + static const uint16_t kRegREnergyA = 0x320; ///< Reactive energy (Channel A) + static const uint16_t kRegREnergyB = 0x321; ///< Reactive energy (Channel B) + static const uint16_t kRegApEnergyA = 0x322; ///< Apparent energy (Channel A) + static const uint16_t kRegApEnergyB = 0x323; ///< Apparent energy (Channel B) + static const uint16_t kRegOvLvl = 0x324; ///< Overvoltage level + static const uint16_t kRegOiLvl = 0x325; ///< Overcurrent level + static const uint16_t kRegVPeak = 0x326; ///< Voltage channel peak + static const uint16_t kRegRstVPeak = 0x327; ///< Voltage peak read with reset + static const uint16_t kRegIAPeak = 0x328; ///< Current Channel A peak + static const uint16_t kRegRstIAPeak = 0x329; ///< Current Channel A peak read with reset + static const uint16_t kRegIBPeak = 0x32A; ///< Current Channel B peak + static const uint16_t kRegRstIBPeak = 0x32B; ///< Current Channel B peak read with reset + static const uint16_t kRegIrqEnA = 0x32C; ///< Interrupt enable (Channel A + voltage) + static const uint16_t kRegIrqStatA = 0x32D; ///< Interrupt status (Channel A + voltage) + static const uint16_t kRegRstIrqStatA = 0x32E; ///< Reset interrupt status (Channel A + voltage) + static const uint16_t kRegIrqEnB = 0x32F; ///< Interrupt enable (Channel B) + static const uint16_t kRegIrqStatB = 0x330; ///< Interrupt status (Channel B) + static const uint16_t kRegRstIrqStatB = 0x331; ///< Reset interrupt status (Channel B) + static const uint16_t kRegCRC = 0x37F; ///< Checksum (32-bit only) + static const uint16_t kRegAIGain = 0x380; ///< Current channel gain (Channel A) + static const uint16_t kRegAVGain = 0x381; ///< Voltage channel gain + static const uint16_t kRegAWGain = 0x382; ///< Active power gain (Channel A) + static const uint16_t kRegAVarGain = 0x383; ///< Reactive power gain (Channel A) + static const uint16_t kRegAVaGain = 0x384; ///< Apparent power gain (Channel A) + static const uint16_t kRegAIRmsOS = 0x386; ///< IRMS offset (Channel A) + static const uint16_t kRegVRmsOS = 0x388; ///< VRMS offset + static const uint16_t kRegAWattOS = 0x389; ///< Active power offset (Channel A) + static const uint16_t kRegAVarOS = 0x38A; ///< Reactive power offset (Channel A) + static const uint16_t kRegAVaOS = 0x38B; ///< Apparent power offset (Channel A) + static const uint16_t kRegBIGain = 0x38C; ///< Current channel gain (Channel B) + static const uint16_t kRegBWGain = 0x38E; ///< Active power gain (Channel B) + static const uint16_t kRegBVarGain = 0x38F; ///< Reactive power gain (Channel B) + static const uint16_t kRegBVaGain = 0x390; ///< Apparent power gain (Channel B) + static const uint16_t kRegBIRmsOS = 0x392; ///< IRMS offset (Channel B) + static const uint16_t kRegBWattOS = 0x395; ///< Active power offset (Channel B) + static const uint16_t kRegBVarOS = 0x396; ///< Reactive power offset (Channel B) + static const uint16_t kRegBVaOS = 0x397; ///< Apparent power offset (Channel B) + static const uint16_t kRegLastRwData32 = 0x3FF; ///< Last successful 24/32-bit register data + + // Magic Values + static const uint8_t kUnlockKey = 0xAD; ///< Key written to kRegUnlock to unlock 0x120. + static const uint16_t kOptimizeValue = 0x0030; ///< Value written to 0x120 for rated performance. + static const uint32_t kDigitalGainUnity = 0x400000; ///< Digital gain register value for 1.0x. + static const uint32_t kDigitalGainMax = 0x7FFFFF; ///< Maximum valid digital gain register value. + static const uint8_t kLastOpRead = 0x35; ///< Last operation was a read. + static const uint8_t kLastOpWrite = 0xCA; ///< Last operation was a write. +}; From 115464b6fdadfeb10a22b04fa6b5e4813fc3a0da Mon Sep 17 00:00:00 2001 From: Geoff McIntyre Date: Mon, 1 Jun 2026 21:52:17 +0000 Subject: [PATCH 2/8] Rename RMS, add current-clamp presets and autoCalibrate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename all *Rms* identifiers to *RMS* (acronym): getIRMSA/B, setIRMSOffsetA/B, getIRMSOffsetA/B, and the kReg*RMS* register constants. - Add sfe_ade7953_clamp_t plus setCurrentClamp(): pass ADE7953_CLAMP_ECS1030 or ADE7953_CLAMP_SCT013 to apply the turns ratio and a suitable PGA gain, or setCurrentClamp(float) to enter a custom turns ratio. - Add autoCalibrateA()/autoCalibrateB(numSamples) that average no-load samples into a software baseline; getCurrentA()/getCurrentB() remove it in the squared domain (sqrt(reading^2 - baseline^2)) — physically correct and needs no datasheet scaling. Add clearCalibration(). - Example 2 demonstrates setCurrentClamp(); Example 7 reworked to use autoCalibrateA(). --- .../Example02_DualChannelCurrent.ino | 6 + .../Example04_PeakDetection.ino | 2 +- .../Example06_InterruptPin.ino | 2 +- .../Example07_CalibrationOffset.ino | 91 ++++-------- .../Example09_DiagnosticTool.ino | 4 +- src/sfTk/sfDevADE7953.cpp | 132 +++++++++++++++--- src/sfTk/sfDevADE7953.h | 79 +++++++++-- 7 files changed, 219 insertions(+), 97 deletions(-) diff --git a/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino b/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino index 2a3c7e8..884de81 100644 --- a/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino +++ b/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino @@ -42,6 +42,12 @@ void setup() } Serial.println("ADE7953 connected!"); + + // Tell the library which clamp is attached so the amps conversion is correct. This sets the + // turns ratio and a suitable PGA gain for you. Use ADE7953_CLAMP_SCT013 for the SCT-013-000, + // or mySensor.setCurrentClamp(yourRatio) to enter a custom turns ratio. + mySensor.setCurrentClamp(ADE7953_CLAMP_ECS1030); + Serial.println(); Serial.println("Channel A (A)\tChannel B (A)"); Serial.println("-------------\t-------------"); diff --git a/examples/Example04_PeakDetection/Example04_PeakDetection.ino b/examples/Example04_PeakDetection/Example04_PeakDetection.ino index d0d97da..48d42c9 100644 --- a/examples/Example04_PeakDetection/Example04_PeakDetection.ino +++ b/examples/Example04_PeakDetection/Example04_PeakDetection.ino @@ -61,7 +61,7 @@ void loop() { // Print the current IRMS value (raw) for context. uint32_t irms = 0; - mySensor.getIRmsA(irms); + mySensor.getIRMSA(irms); Serial.print("IRMS (raw): "); Serial.print(irms); diff --git a/examples/Example06_InterruptPin/Example06_InterruptPin.ino b/examples/Example06_InterruptPin/Example06_InterruptPin.ino index 83a2c83..8c55f12 100644 --- a/examples/Example06_InterruptPin/Example06_InterruptPin.ino +++ b/examples/Example06_InterruptPin/Example06_InterruptPin.ino @@ -92,7 +92,7 @@ void loop() { // Print the current IRMS value (raw) for context. uint32_t irms = 0; - mySensor.getIRmsA(irms); + mySensor.getIRMSA(irms); Serial.print("IRMS (raw): "); Serial.print(irms); diff --git a/examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino b/examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino index 2f138c4..bc022b5 100644 --- a/examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino +++ b/examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino @@ -1,22 +1,19 @@ /* - Example 07 - Calibration Offset & Error Handling + Example 07 - Auto Calibration & Error Handling Two things are shown here: - 1. Error handling. Every library read/write returns a SparkFun Toolkit error code - (ksfTkErrOk on success, a negative value on failure). Most sketches ignore it for - simplicity, but this example shows how to check it so you can detect a disconnected - or misbehaving device. - - 2. Zeroing the no-load baseline. Even with no current flowing, the ADC reports a small - nonzero IRMS value due to noise. This example measures that baseline and removes it. + 1. Auto calibration. Even with no current flowing, the ADC reports a small nonzero IRMS + value due to noise. autoCalibrateA() averages a number of no-load samples and stores + that baseline; getCurrentA() then removes it in the squared domain + (corrected = sqrt(reading^2 - baseline^2)), which is the physically correct way to + subtract an RMS noise floor and needs no datasheet-specific scaling. The hardware + AIRMSOS register is left untouched, but setIRMSOffsetA()/getIRMSOffsetA() are still + available if you want to use it. - A note on the hardware offset register (AIRMSOS): the ADE7953 applies it in the squared - domain (the offset is added to the square of the RMS result before the square root), so - the value needed to null a noise floor of N counts is NOT simply -N. The exact scaling is - datasheet dependent. To keep this example correct and easy to follow, the baseline is - removed in software; getIRmsOffsetA()/setIRmsOffsetA() are demonstrated so you can apply a - hardware offset once you have determined the right value for your setup. + 2. Error handling. Every library read/write returns a SparkFun Toolkit error code + (ksfTkErrOk on success, a negative value on failure). Most sketches ignore it for + simplicity, but this example checks it so you can detect a misbehaving device. Run with NO load connected to Channel A during the calibration phase. @@ -41,15 +38,12 @@ SfeADE7953ArdI2C mySensor; // Number of samples to average during calibration. -const int kCalSamples = 50; - -// Software baseline (raw IRMS counts measured with no load). -uint32_t baseline = 0; +const uint16_t kCalSamples = 50; void setup() { Serial.begin(115200); - Serial.println("SparkFun ADE7953 Example 7 - Calibration Offset & Error Handling"); + Serial.println("SparkFun ADE7953 Example 7 - Auto Calibration & Error Handling"); Wire.begin(); @@ -61,66 +55,37 @@ void setup() Serial.println("ADE7953 connected!"); - // Clear any existing hardware offset so it does not affect our baseline measurement. - mySensor.setIRmsOffsetA(0); - - int32_t hwOffset = 0; - mySensor.getIRmsOffsetA(hwOffset); - Serial.print("Hardware IRMS offset register: "); - Serial.println(hwOffset); - - // --- Calibration phase: average the no-load reading --- + // --- Calibration phase --- Serial.println(); Serial.println("=== CALIBRATION PHASE ==="); Serial.println("Ensure NO load is connected to Channel A."); delay(1000); - // Use a 64-bit accumulator so the sum cannot overflow regardless of sample count. - uint64_t sum = 0; - for (int i = 0; i < kCalSamples; i++) - { - uint32_t sample = 0; - - // Demonstrate error checking: only use the sample if the read succeeded. - if (mySensor.getIRmsA(sample) != ksfTkErrOk) - { - Serial.println("Read failed during calibration!"); - i--; // retry this sample - delay(100); - continue; - } - - sum += sample; - delay(100); - } + // Average kCalSamples no-load readings into the channel's software baseline. Check the + // return code so a communication failure is not silently ignored. + if (mySensor.autoCalibrateA(kCalSamples) != ksfTkErrOk) + Serial.println("Calibration failed - check the connection!"); + else + Serial.println("Calibration complete. Baseline noise will be removed."); - baseline = (uint32_t)(sum / kCalSamples); - Serial.print("No-load baseline (raw counts): "); - Serial.println(baseline); - Serial.println("=== CALIBRATION COMPLETE ==="); - Serial.println("You may now connect a load. Readings have the baseline removed."); + Serial.println("You may now connect a load."); Serial.println(); } void loop() { - uint32_t raw = 0; - - // Check the return code so a communication error is not mistaken for a zero reading. - if (mySensor.getIRmsA(raw) != ksfTkErrOk) + // getCurrentA() applies the calibrated baseline automatically. Check the return code so a + // communication error is not mistaken for a zero reading. + float amps = 0.0; + if (mySensor.getCurrentA(amps) != ksfTkErrOk) { - Serial.println("IRMS read failed!"); + Serial.println("Current read failed!"); delay(500); return; } - // Remove the no-load baseline in software (clamped at zero). - uint32_t corrected = (raw > baseline) ? (raw - baseline) : 0; - - Serial.print("Raw: "); - Serial.print(raw); - Serial.print(" | Baseline-corrected: "); - Serial.println(corrected); + Serial.print("Calibrated current (A): "); + Serial.println(amps, 4); delay(500); } diff --git a/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino b/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino index fdd1ca8..234e90e 100644 --- a/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino +++ b/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino @@ -144,7 +144,7 @@ void doZeroCalibration() for (int i = 0; i < NUM_SAMPLES; i++) { uint32_t sample = 0; - mySensor.getIRmsA(sample); + mySensor.getIRMSA(sample); sum += sample; delay(50); } @@ -268,7 +268,7 @@ void loop() // Read raw IRMS from Channel A. uint32_t rawIrms = 0; - mySensor.getIRmsA(rawIrms); + mySensor.getIRMSA(rawIrms); // Apply the software zero offset (clamped at zero). uint32_t corrected = (rawIrms > zeroOffset) ? (rawIrms - zeroOffset) : 0; diff --git a/src/sfTk/sfDevADE7953.cpp b/src/sfTk/sfDevADE7953.cpp index 77196e7..437fe88 100644 --- a/src/sfTk/sfDevADE7953.cpp +++ b/src/sfTk/sfDevADE7953.cpp @@ -19,6 +19,8 @@ #include "sfDevADE7953.h" +#include + // ========================= Setup & Identity =============================== sfTkError_t sfDevADE7953::begin(sfTkIBus *theBus) @@ -244,20 +246,20 @@ sfTkError_t sfDevADE7953::getDigitalGainIB(float &multiplier) // ======================= Current Measurement ============================== -sfTkError_t sfDevADE7953::getIRmsA(uint32_t &value) +sfTkError_t sfDevADE7953::getIRMSA(uint32_t &value) { - return _theBus->readRegister(kRegIRmsA, value); + return _theBus->readRegister(kRegIRMSA, value); } -sfTkError_t sfDevADE7953::getIRmsB(uint32_t &value) +sfTkError_t sfDevADE7953::getIRMSB(uint32_t &value) { - return _theBus->readRegister(kRegIRmsB, value); + return _theBus->readRegister(kRegIRMSB, value); } sfTkError_t sfDevADE7953::getCurrentA(float &s) { uint32_t raw = 0; - sfTkError_t rc = getIRmsA(raw); + sfTkError_t rc = getIRMSA(raw); if (rc != ksfTkErrOk) return rc; @@ -266,18 +268,22 @@ sfTkError_t sfDevADE7953::getCurrentA(float &s) if (rc != ksfTkErrOk) return rc; - // Pin voltage RMS = raw / fullScaleCode * (fullScaleVrms / pgaGain) + // Remove the no-load baseline (if calibrated) in the squared domain. RMS values combine in + // quadrature, so the correct way to strip a noise floor is sqrt(reading^2 - baseline^2). + float corrected = removeBaseline(raw, _baselineA); + + // Pin voltage RMS = corrected / fullScaleCode * (fullScaleVrms / pgaGain) // Secondary current = pinVrms / burdenResistor // Primary current = secondaryCurrent * ctRatio float fullScaleAmps = (_fullScaleVrms / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; - amps = (float)raw / _fullScaleCode * fullScaleAmps; + amps = corrected / _fullScaleCode * fullScaleAmps; return ksfTkErrOk; } sfTkError_t sfDevADE7953::getCurrentB(float &s) { uint32_t raw = 0; - sfTkError_t rc = getIRmsB(raw); + sfTkError_t rc = getIRMSB(raw); if (rc != ksfTkErrOk) return rc; @@ -286,8 +292,10 @@ sfTkError_t sfDevADE7953::getCurrentB(float &s) if (rc != ksfTkErrOk) return rc; + float corrected = removeBaseline(raw, _baselineB); + float fullScaleAmps = (_fullScaleVrms / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; - amps = (float)raw / _fullScaleCode * fullScaleAmps; + amps = corrected / _fullScaleCode * fullScaleAmps; return ksfTkErrOk; } @@ -312,6 +320,96 @@ sfTkError_t sfDevADE7953::getInstantaneousIB(int32_t &value) // =================== Current Conversion Calibration ======================= +float sfDevADE7953::removeBaseline(uint32_t reading, uint32_t baseline) +{ + if (baseline == 0) + return (float)reading; + + if (reading <= baseline) + return 0.0f; + + float r = (float)reading; + float b = (float)baseline; + return sqrtf(r * r - b * b); +} + +sfTkError_t sfDevADE7953::setCurrentClamp(sfe_ade7953_clamp_t clamp) +{ + sfe_ade7953_pga_gain_t gain = ADE7953_PGA_GAIN_4; + + switch (clamp) + { + case ADE7953_CLAMP_SCT013: + // 100A:50mA = 2000:1. At 100 A the 50 mA secondary across the 5.6 ohm burden is near the + // ADC full scale, so use unity PGA gain. + _ctRatio = 2000.0f; + gain = ADE7953_PGA_GAIN_1; + break; + + case ADE7953_CLAMP_ECS1030: + default: + // 30A:15mA = 2000:1. The smaller secondary current benefits from 4x gain. + _ctRatio = 2000.0f; + gain = ADE7953_PGA_GAIN_4; + break; + } + + sfTkError_t rc = setGainIA(gain); + if (rc != ksfTkErrOk) + return rc; + + return setGainIB(gain); +} + +void sfDevADE7953::setCurrentClamp(float turnsRatio) +{ + _ctRatio = turnsRatio; +} + +sfTkError_t sfDevADE7953::autoCalibrateA(uint16_t numSamples) +{ + if (numSamples == 0) + return ksfTkErrFail; + + uint64_t sum = 0; + for (uint16_t i = 0; i < numSamples; i++) + { + uint32_t sample = 0; + sfTkError_t rc = getIRMSA(sample); + if (rc != ksfTkErrOk) + return rc; + sum += sample; + } + + _baselineA = (uint32_t)(sum / numSamples); + return ksfTkErrOk; +} + +sfTkError_t sfDevADE7953::autoCalibrateB(uint16_t numSamples) +{ + if (numSamples == 0) + return ksfTkErrFail; + + uint64_t sum = 0; + for (uint16_t i = 0; i < numSamples; i++) + { + uint32_t sample = 0; + sfTkError_t rc = getIRMSB(sample); + if (rc != ksfTkErrOk) + return rc; + sum += sample; + } + + _baselineB = (uint32_t)(sum / numSamples); + return ksfTkErrOk; +} + +void sfDevADE7953::clearCalibration(void) +{ + _baselineA = 0; + _baselineB = 0; +} + void sfDevADE7953::setCurrentTransformerRatio(float ratio) { _ctRatio = ratio; @@ -367,28 +465,28 @@ sfTkError_t sfDevADE7953::getOvercurrentLevel(uint32_t &level) // ====================== Calibration & Offset ============================== -sfTkError_t sfDevADE7953::setIRmsOffsetA(int32_t offset) +sfTkError_t sfDevADE7953::setIRMSOffsetA(int32_t offset) { - return _theBus->writeRegister(kRegAIRmsOS, (uint32_t)offset); + return _theBus->writeRegister(kRegAIRMSOS, (uint32_t)offset); } -sfTkError_t sfDevADE7953::getIRmsOffsetA(int32_t &offset) +sfTkError_t sfDevADE7953::getIRMSOffsetA(int32_t &offset) { uint32_t raw = 0; - sfTkError_t rc = _theBus->readRegister(kRegAIRmsOS, raw); + sfTkError_t rc = _theBus->readRegister(kRegAIRMSOS, raw); offset = (int32_t)raw; return rc; } -sfTkError_t sfDevADE7953::setIRmsOffsetB(int32_t offset) +sfTkError_t sfDevADE7953::setIRMSOffsetB(int32_t offset) { - return _theBus->writeRegister(kRegBIRmsOS, (uint32_t)offset); + return _theBus->writeRegister(kRegBIRMSOS, (uint32_t)offset); } -sfTkError_t sfDevADE7953::getIRmsOffsetB(int32_t &offset) +sfTkError_t sfDevADE7953::getIRMSOffsetB(int32_t &offset) { uint32_t raw = 0; - sfTkError_t rc = _theBus->readRegister(kRegBIRmsOS, raw); + sfTkError_t rc = _theBus->readRegister(kRegBIRMSOS, raw); offset = (int32_t)raw; return rc; } diff --git a/src/sfTk/sfDevADE7953.h b/src/sfTk/sfDevADE7953.h index 7f422d9..9bb0646 100644 --- a/src/sfTk/sfDevADE7953.h +++ b/src/sfTk/sfDevADE7953.h @@ -53,6 +53,19 @@ typedef enum sfe_ade7953_zx_edge_t : uint8_t ADE7953_ZX_EDGE_BOTH_ALT = 3 ///< Same as BOTH (alternate encoding) } sfe_ade7953_zx_edge_t; +/////////////////////////////////////////////////////////////////////////////// +// Current Clamp Presets +/////////////////////////////////////////////////////////////////////////////// +/// @brief Supported clamp-on current transformers. +/// @details Passing one of these to setCurrentClamp() applies the matching turns ratio and a +/// suitable PGA gain for the board's 5.6 ohm burden resistor. Users with a different clamp can call +/// setCurrentClamp(float) (or setCurrentTransformerRatio()) to enter a turns ratio directly. +typedef enum sfe_ade7953_clamp_t : uint8_t +{ + ADE7953_CLAMP_ECS1030 = 0, ///< SparkFun ECS1030-L72: 30A:15mA (2000:1 turns), 4x PGA gain + ADE7953_CLAMP_SCT013 = 1 ///< SCT-013-000: 100A:50mA (2000:1 turns), 1x PGA gain +} sfe_ade7953_clamp_t; + /////////////////////////////////////////////////////////////////////////////// // CONFIG Register Bitfield (Address 0x102) /////////////////////////////////////////////////////////////////////////////// @@ -285,12 +298,12 @@ class sfDevADE7953 /// @brief Read the raw IRMS value for Current Channel A. /// @param value Output reference that receives the raw unsigned IRMS value. /// @return ::ksfTkErrOk on success, or an error code on failure. - sfTkError_t getIRmsA(uint32_t &value); + sfTkError_t getIRMSA(uint32_t &value); /// @brief Read the raw IRMS value for Current Channel B. /// @param value Output reference that receives the raw unsigned IRMS value. /// @return ::ksfTkErrOk on success, or an error code on failure. - sfTkError_t getIRmsB(uint32_t &value); + sfTkError_t getIRMSB(uint32_t &value); /// @brief Read the RMS current on Channel A converted to amps. /// @details Combines the raw IRMS reading with the configured PGA gain, CT ratio, burden @@ -317,6 +330,19 @@ class sfDevADE7953 // =================== Current Conversion Calibration =================== + /// @brief Select a known current clamp and apply its turns ratio and PGA gain. + /// @details Sets the CT ratio used by getCurrentA()/getCurrentB() and writes a suitable PGA gain + /// to both current channels for the board's 5.6 ohm burden resistor. + /// @param clamp One of the supported clamps (see sfe_ade7953_clamp_t). + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setCurrentClamp(sfe_ade7953_clamp_t clamp); + + /// @brief Configure a custom current clamp by its turns ratio. + /// @details Use this for a clamp that is not one of the presets. Only the CT ratio is changed; + /// the PGA gain is left as-is (set it with setGainIA()/setGainIB() if needed). + /// @param turnsRatio Turns ratio of the current transformer (e.g. 2000.0 for 30A:15mA). + void setCurrentClamp(float turnsRatio); + /// @brief Set the CT (current transformer) turns ratio used by getCurrentA() / getCurrentB(). /// @param ratio Turns ratio of the current transformer (e.g. 2000.0 for a 30A:15mA clamp). void setCurrentTransformerRatio(float ratio); @@ -333,6 +359,25 @@ class sfDevADE7953 /// @return The burden resistor value in ohms. float getBurdenResistor(void); + /// @brief Auto-calibrate the Channel A no-load baseline by averaging samples. + /// @details Reads @p numSamples raw IRMS samples (which must be taken with NO current flowing) + /// and stores their average as a software noise floor. getCurrentA() then removes it in the + /// squared domain — corrected = sqrt(reading^2 - baseline^2) — which is the physically correct + /// way to subtract an RMS noise floor and needs no datasheet-specific scaling. This leaves the + /// hardware AIRMSOS register untouched. + /// @param numSamples Number of samples to average (must be > 0). + /// @return ::ksfTkErrOk on success, ::ksfTkErrFail if numSamples is 0, or an error code on + /// communication failure. + sfTkError_t autoCalibrateA(uint16_t numSamples = 50); + + /// @brief Auto-calibrate the Channel B no-load baseline by averaging samples. + /// @param numSamples Number of samples to average (must be > 0). + /// @return ::ksfTkErrOk on success, ::ksfTkErrFail if numSamples is 0, or an error code on failure. + sfTkError_t autoCalibrateB(uint16_t numSamples = 50); + + /// @brief Clear the software no-load baseline for both channels (set by autoCalibrate). + void clearCalibration(void); + // ======================== Peak Detection ============================== /// @brief Read the peak current value for Channel A. @@ -375,22 +420,22 @@ class sfDevADE7953 /// counts the offset is approximately @c -(N*N) scaled per the datasheet, not @c -N. /// @param offset Signed offset value for IRMS correction. /// @return ::ksfTkErrOk on success, or an error code on failure. - sfTkError_t setIRmsOffsetA(int32_t offset); + sfTkError_t setIRMSOffsetA(int32_t offset); /// @brief Get the IRMS offset for Current Channel A. /// @param offset Output reference that receives the signed offset value. /// @return ::ksfTkErrOk on success, or an error code on failure. - sfTkError_t getIRmsOffsetA(int32_t &offset); + sfTkError_t getIRMSOffsetA(int32_t &offset); - /// @brief Set the IRMS offset for Current Channel B. See setIRmsOffsetA() for the squared-domain note. + /// @brief Set the IRMS offset for Current Channel B. See setIRMSOffsetA() for the squared-domain note. /// @param offset Signed offset value for IRMS correction. /// @return ::ksfTkErrOk on success, or an error code on failure. - sfTkError_t setIRmsOffsetB(int32_t offset); + sfTkError_t setIRMSOffsetB(int32_t offset); /// @brief Get the IRMS offset for Current Channel B. /// @param offset Output reference that receives the signed offset value. /// @return ::ksfTkErrOk on success, or an error code on failure. - sfTkError_t getIRmsOffsetB(int32_t &offset); + sfTkError_t getIRMSOffsetB(int32_t &offset); // ======================= Zero-Crossing (ZX_I) ========================= @@ -573,6 +618,10 @@ class sfDevADE7953 /// @brief Convert a PGA gain enum value to its numeric multiplier (1, 2, 4, 8, 16, 22). static float pgaGainToMultiplier(sfe_ade7953_pga_gain_t gain); + /// @brief Remove a no-load baseline from a raw RMS reading in the squared domain. + /// @return sqrt(reading^2 - baseline^2), clamped at 0; or reading unchanged if baseline is 0. + static float removeBaseline(uint32_t reading, uint32_t baseline); + sfTkIBus *_theBus; ///< Pointer to the communication bus device. // --- Current-to-amps conversion parameters (sensible defaults for the Qwiic board) --- @@ -581,6 +630,10 @@ class sfDevADE7953 float _fullScaleCode = 5928256.0f; ///< ADE7953 full-scale IRMS code. float _fullScaleVrms = 0.35355f; ///< Full-scale input RMS voltage (0.5 V peak / sqrt(2)). + // --- Software no-load baselines set by autoCalibrate, removed in the squared domain --- + uint32_t _baselineA = 0; ///< Channel A no-load IRMS baseline (raw counts). + uint32_t _baselineB = 0; ///< Channel B no-load IRMS baseline (raw counts). + /////////////////////////////////////////////////////////////////////////// // I2C Addressing /////////////////////////////////////////////////////////////////////////// @@ -634,9 +687,9 @@ class sfDevADE7953 static const uint16_t kRegIA = 0x316; ///< Instantaneous current (Channel A) static const uint16_t kRegIB = 0x317; ///< Instantaneous current (Channel B) static const uint16_t kRegV = 0x318; ///< Instantaneous voltage - static const uint16_t kRegIRmsA = 0x31A; ///< IRMS (Channel A) - static const uint16_t kRegIRmsB = 0x31B; ///< IRMS (Channel B) - static const uint16_t kRegVRms = 0x31C; ///< VRMS + static const uint16_t kRegIRMSA = 0x31A; ///< IRMS (Channel A) + static const uint16_t kRegIRMSB = 0x31B; ///< IRMS (Channel B) + static const uint16_t kRegVRMS = 0x31C; ///< VRMS static const uint16_t kRegAEnergyA = 0x31E; ///< Active energy (Channel A) static const uint16_t kRegAEnergyB = 0x31F; ///< Active energy (Channel B) static const uint16_t kRegREnergyA = 0x320; ///< Reactive energy (Channel A) @@ -663,8 +716,8 @@ class sfDevADE7953 static const uint16_t kRegAWGain = 0x382; ///< Active power gain (Channel A) static const uint16_t kRegAVarGain = 0x383; ///< Reactive power gain (Channel A) static const uint16_t kRegAVaGain = 0x384; ///< Apparent power gain (Channel A) - static const uint16_t kRegAIRmsOS = 0x386; ///< IRMS offset (Channel A) - static const uint16_t kRegVRmsOS = 0x388; ///< VRMS offset + static const uint16_t kRegAIRMSOS = 0x386; ///< IRMS offset (Channel A) + static const uint16_t kRegVRMSOS = 0x388; ///< VRMS offset static const uint16_t kRegAWattOS = 0x389; ///< Active power offset (Channel A) static const uint16_t kRegAVarOS = 0x38A; ///< Reactive power offset (Channel A) static const uint16_t kRegAVaOS = 0x38B; ///< Apparent power offset (Channel A) @@ -672,7 +725,7 @@ class sfDevADE7953 static const uint16_t kRegBWGain = 0x38E; ///< Active power gain (Channel B) static const uint16_t kRegBVarGain = 0x38F; ///< Reactive power gain (Channel B) static const uint16_t kRegBVaGain = 0x390; ///< Apparent power gain (Channel B) - static const uint16_t kRegBIRmsOS = 0x392; ///< IRMS offset (Channel B) + static const uint16_t kRegBIRMSOS = 0x392; ///< IRMS offset (Channel B) static const uint16_t kRegBWattOS = 0x395; ///< Active power offset (Channel B) static const uint16_t kRegBVarOS = 0x396; ///< Reactive power offset (Channel B) static const uint16_t kRegBVaOS = 0x397; ///< Apparent power offset (Channel B) From 3ef2d467960c7959b1922958e877736c80503119 Mon Sep 17 00:00:00 2001 From: Geoff McIntyre Date: Mon, 1 Jun 2026 16:05:46 -0600 Subject: [PATCH 3/8] Update library.properties --- library.properties | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library.properties b/library.properties index cda595f..d71e228 100644 --- a/library.properties +++ b/library.properties @@ -7,5 +7,4 @@ paragraph=This library allows you to interface with the Qwiic Current Sensor ADE category=Sensors url=https://github.com/sparkfun/SparkFun_ADE7953_Arduino_Library architectures=* -depends=SparkFun Toolkit (>=1.2.0) -includes=SparkFun_ADE7953.h \ No newline at end of file +depends=SparkFun Toolkit (>=1.2.0) \ No newline at end of file From 9ed1eed09a5174abd485cbd6cf077dd5274f17c5 Mon Sep 17 00:00:00 2001 From: Geoff McIntyre Date: Tue, 2 Jun 2026 10:27:07 -0600 Subject: [PATCH 4/8] Fixed Case and gain values --- src/sfTk/sfDevADE7953.cpp | 15 ++++++--------- src/sfTk/sfDevADE7953.h | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/sfTk/sfDevADE7953.cpp b/src/sfTk/sfDevADE7953.cpp index 437fe88..2ca4a82 100644 --- a/src/sfTk/sfDevADE7953.cpp +++ b/src/sfTk/sfDevADE7953.cpp @@ -272,10 +272,10 @@ sfTkError_t sfDevADE7953::getCurrentA(float &s) // quadrature, so the correct way to strip a noise floor is sqrt(reading^2 - baseline^2). float corrected = removeBaseline(raw, _baselineA); - // Pin voltage RMS = corrected / fullScaleCode * (fullScaleVrms / pgaGain) - // Secondary current = pinVrms / burdenResistor + // Pin voltage RMS = corrected / fullScaleCode * (fullScaleVRMS / pgaGain) + // Secondary current = pinVRMS / burdenResistor // Primary current = secondaryCurrent * ctRatio - float fullScaleAmps = (_fullScaleVrms / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; + float fullScaleAmps = (_fullScaleVRMS / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; amps = corrected / _fullScaleCode * fullScaleAmps; return ksfTkErrOk; } @@ -294,7 +294,7 @@ sfTkError_t sfDevADE7953::getCurrentB(float &s) float corrected = removeBaseline(raw, _baselineB); - float fullScaleAmps = (_fullScaleVrms / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; + float fullScaleAmps = (_fullScaleVRMS / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; amps = corrected / _fullScaleCode * fullScaleAmps; return ksfTkErrOk; } @@ -340,17 +340,14 @@ sfTkError_t sfDevADE7953::setCurrentClamp(sfe_ade7953_clamp_t clamp) switch (clamp) { case ADE7953_CLAMP_SCT013: - // 100A:50mA = 2000:1. At 100 A the 50 mA secondary across the 5.6 ohm burden is near the - // ADC full scale, so use unity PGA gain. _ctRatio = 2000.0f; - gain = ADE7953_PGA_GAIN_1; + gain = ADE7953_PGA_GAIN_16; break; case ADE7953_CLAMP_ECS1030: default: - // 30A:15mA = 2000:1. The smaller secondary current benefits from 4x gain. _ctRatio = 2000.0f; - gain = ADE7953_PGA_GAIN_4; + gain = ADE7953_PGA_GAIN_16; break; } diff --git a/src/sfTk/sfDevADE7953.h b/src/sfTk/sfDevADE7953.h index 9bb0646..e4fa819 100644 --- a/src/sfTk/sfDevADE7953.h +++ b/src/sfTk/sfDevADE7953.h @@ -628,7 +628,7 @@ class sfDevADE7953 float _ctRatio = 2000.0f; ///< CT turns ratio (30A:15mA = 2000:1). float _burdenResistor = 5.6f; ///< Burden resistor in ohms. float _fullScaleCode = 5928256.0f; ///< ADE7953 full-scale IRMS code. - float _fullScaleVrms = 0.35355f; ///< Full-scale input RMS voltage (0.5 V peak / sqrt(2)). + float _fullScaleVRMS = 0.35355f; ///< Full-scale input RMS voltage (0.5 V peak / sqrt(2)). // --- Software no-load baselines set by autoCalibrate, removed in the squared domain --- uint32_t _baselineA = 0; ///< Channel A no-load IRMS baseline (raw counts). From 38921fc646adfca9f196ccb1852b2e17b3bfb15a Mon Sep 17 00:00:00 2001 From: Geoff McIntyre Date: Tue, 2 Jun 2026 18:19:32 +0000 Subject: [PATCH 5/8] Add README, library metadata, and CI workflows for publishing - README.md: long-form SparkFun-style readme with banner reference, badges, functionality overview, wiring, and a full usage guide (reading current, error handling, clamp selection, calibration, gain, peaks, interrupts, zero-crossing) plus example links. - keywords.txt: Arduino IDE syntax highlighting for all public methods, types, and constants. - library.json: PlatformIO manifest (with SparkFun Toolkit dependency). - docs/doxygen/doxygen-config: Doxygen config (README as main page, src input). - .github/workflows/build-deploy-ghpages.yml: build docs and deploy to Pages. - .github/workflows/test-compile-sketch.yml: cross-compile Example 01 across AVR/ESP32/ESP8266/SAMD/mbed/RP2040/RP2350/STM32, installing the Toolkit dep. --- .github/workflows/build-deploy-ghpages.yml | 64 ++++++ .github/workflows/test-compile-sketch.yml | 87 ++++++++ README.md | 224 +++++++++++++++++++++ docs/doxygen/doxygen-config | 49 +++++ keywords.txt | 101 ++++++++++ library.json | 16 ++ 6 files changed, 541 insertions(+) create mode 100644 .github/workflows/build-deploy-ghpages.yml create mode 100644 .github/workflows/test-compile-sketch.yml create mode 100644 README.md create mode 100644 docs/doxygen/doxygen-config create mode 100644 keywords.txt create mode 100644 library.json diff --git a/.github/workflows/build-deploy-ghpages.yml b/.github/workflows/build-deploy-ghpages.yml new file mode 100644 index 0000000..b69a484 --- /dev/null +++ b/.github/workflows/build-deploy-ghpages.yml @@ -0,0 +1,64 @@ +name: Build Documentation and Deploy + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + id-token: write + pages: write + +concurrency: + group: "pages" + cancel-in-progress: true + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + # Checkout the repository + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: "true" + + - name: Set Version + run: echo "PROJECT_NUMBER = `git describe --tags`" >> ./docs/doxygen/doxygen-config + + - name: Build Documentation + uses: mattnotmitt/doxygen-action@v1.9.5 + with: + doxyfile-path: "./docs/doxygen/doxygen-config" + + # Upload the documentation as an artifact + - name: Upload documentation + uses: actions/upload-pages-artifact@v3.0.1 + with: + path: ./docs/html + + # Deploy job + deploy: + # Add a dependency to the build job + needs: build + + # Grant GITHUB_TOKEN the permissions required to make a Pages deployment + permissions: + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + + # Deploy to the github-pages environment + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + # Specify runner + deployment step + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action diff --git a/.github/workflows/test-compile-sketch.yml b/.github/workflows/test-compile-sketch.yml new file mode 100644 index 0000000..2660939 --- /dev/null +++ b/.github/workflows/test-compile-sketch.yml @@ -0,0 +1,87 @@ +name: Cross-compilation Library Test + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + compile-sketch: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + board: + # Uno / AVR (Mega has the flash headroom for the examples) + - fqbn: arduino:avr:mega + name: arduino:avr + source-url: https://downloads.arduino.cc/packages/package_index.json + + # ESP32 + - fqbn: esp32:esp32:esp32 + name: esp32:esp32 + source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + + # ESP32-C3 + - fqbn: esp32:esp32:esp32c3 + name: esp32:esp32 + source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + + # ESP8266 + - fqbn: esp8266:esp8266:thingdev + name: esp8266:esp8266 + source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json + + # SAMD21 + - fqbn: arduino:samd:mkr1000 + name: arduino:samd + source-url: https://downloads.arduino.cc/packages/package_index.json + + # Nano BLE 33 / nRF52840 + - fqbn: arduino:mbed:nano33ble + name: arduino:mbed + source-url: https://downloads.arduino.cc/packages/package_index.json + + # RP2040 + - fqbn: rp2040:rp2040:sparkfun_promicrorp2040 + name: rp2040:rp2040 + source-url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json + + # RP2350 + - fqbn: rp2040:rp2040:sparkfun_promicrorp2350 + name: rp2040:rp2040 + source-url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json + + # STM32 + - fqbn: STMicroelectronics:stm32:GenF4 + name: STMicroelectronics:stm32 + source-url: https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Branch name + run: echo running on branch ${GITHUB_REF##*/} + + - name: Arduino - Install and setup the Arduino CLI + uses: arduino/setup-arduino-cli@v2 + + - name: Arduino - Start config file + run: arduino-cli config init --additional-urls ${{ matrix.board.source-url}} + + - name: Arduino - Update index + run: arduino-cli core update-index + + - name: Arduino - Install platform + run: arduino-cli core install ${{ matrix.board.name}} + + - name: Arduino - Install library dependencies + run: arduino-cli lib install "SparkFun Toolkit" + + - name: Compile Sketch + run: arduino-cli compile --fqbn ${{ matrix.board.fqbn }} examples/Example01_BasicCurrentReading --library . diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1d7599 --- /dev/null +++ b/README.md @@ -0,0 +1,224 @@ +![SparkFun Qwiic Current Sensor - ADE7953](docs/images/gh-banner-2025-arduino-ade7953.png "SparkFun Qwiic Current Sensor - ADE7953") + +# SparkFun Qwiic Current Sensor - ADE7953 + +Arduino Library for the SparkFun Qwiic Current Sensor (ADE7953) + +![GitHub License](https://img.shields.io/github/license/sparkfun/SparkFun_ADE7953_Arduino_Library) +![Release](https://img.shields.io/github/v/release/sparkfun/SparkFun_ADE7953_Arduino_Library) +![Release Date](https://img.shields.io/github/release-date/sparkfun/SparkFun_ADE7953_Arduino_Library) +![Documentation - build](https://img.shields.io/github/actions/workflow/status/sparkfun/SparkFun_ADE7953_Arduino_Library/build-deploy-ghpages.yml?label=doc%20build) +[![Compile Test](https://github.com/sparkfun/SparkFun_ADE7953_Arduino_Library/actions/workflows/test-compile-sketch.yml/badge.svg)](https://github.com/sparkfun/SparkFun_ADE7953_Arduino_Library/actions/workflows/test-compile-sketch.yml) +![GitHub issues](https://img.shields.io/github/issues/sparkfun/SparkFun_ADE7953_Arduino_Library) + +The [SparkFun Qwiic Current Sensor](https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html) puts the Analog Devices ADE7953 single-phase energy metering IC on a Qwiic-enabled breakout, making clamp-on AC current measurement as simple as plugging in a cable. Paired with a current transformer (CT) clamp, the board measures RMS current without any electrical contact with the conductor being measured. + +This library provides an easy-to-use interface to the ADE7953 over I2C, built on the [SparkFun Toolkit](https://github.com/sparkfun/SparkFun_Toolkit). It handles the device setup for you and returns real-world current readings in amps, while still exposing the lower-level registers for advanced use. + +## Functionality + +The library focuses on current measurement and the features most useful for it: + +- RMS current measurement on Channel A (primary CT input) and the optional Channel B +- Real-world readings in amps, with built-in presets for common current clamps +- Programmable gain (PGA) and fine digital-gain control +- Software no-load calibration to remove the sensor's noise floor +- Peak / inrush current detection +- Zero-crossing detection and line-period (frequency) measurement +- A full interrupt system (overcurrent, zero-crossing, sag, cycle-end, and more) +- Sag detection and line-cycle accumulation configuration +- Diagnostics for the device's last-operation registers + +> [!NOTE] +> This library targets current measurement via CT clamp. The ADE7953 is a full energy-metering IC (active/reactive/apparent power and energy), and the register map for those features is present in the driver, but the convenience API is centered on current. The lower-level register helpers can be used to access the additional features. + +## Hardware Connections + +The sensor connects over I2C using the Qwiic connector — no soldering required. The ADE7953 uses a single fixed 7-bit I2C address of `0x38`. + +| Pin / Header | Use | Notes | +| -- | -- | -- | +| Qwiic / I2C | Power + communication | Standard 3.3V Qwiic connection | +| IAP / IAN | Current Channel A input | Connect your CT clamp here (primary channel) | +| IBP / IBN | Current Channel B input | Optional second CT clamp via the header pins | +| IRQ | Interrupt request (active low) | Optional — used for the interrupt examples | +| ZX | Zero-crossing output | Optional — pulses on each current zero crossing | + +A burden resistor (5.6 Ω on the board) converts the CT's secondary current to a voltage the ADE7953 measures. The library's amps conversion accounts for this burden resistor and the CT turns ratio. + +## Using the Library + +### Installation + +Install through the Arduino Library Manager by searching for **SparkFun ADE7953**, or download this repository as a ZIP and add it via *Sketch > Include Library > Add .ZIP Library*. This library depends on the [SparkFun Toolkit](https://github.com/sparkfun/SparkFun_Toolkit), which the Library Manager will offer to install alongside it. + +### Getting Started + +The I2C interface to the sensor is provided by the `SfeADE7953ArdI2C` class. Declare a sensor object: + +```c++ +#include + +// Declare our sensor object +SfeADE7953ArdI2C mySensor; +``` + +In `setup()`, start I2C and call `begin()`. `begin()` confirms the device is present, applies the datasheet-recommended performance configuration, and sets sensible defaults (4x PGA gain and the high-pass filter enabled) so a basic sketch works with no further setup: + +```c++ +Wire.begin(); + +while (mySensor.begin() == false) +{ + Serial.println("ADE7953 not connected, check your wiring!"); + delay(1000); +} +``` + +At this point the sensor is ready for normal operation. + +### Reading Current + +The simplest way to read current is `getCurrentA()`, which returns the RMS current on Channel A in amps: + +```c++ +float amps = 0.0; +mySensor.getCurrentA(amps); +Serial.print("Current (A): "); +Serial.println(amps, 4); +``` + +Channel B (the optional second input) is read with `getCurrentB()`. If you only need the raw register value, `getIRMSA()` / `getIRMSB()` return the unscaled RMS reading. + +### A Note on Return Values and Error Handling + +Most library methods return a SparkFun Toolkit error code (`ksfTkErrOk` on success, a negative value on failure) and pass the value back through a reference parameter. This lets you tell the difference between a real reading of zero and a communication failure. For simple sketches you can ignore the return value: + +```c++ +float amps; +mySensor.getCurrentA(amps); // assume good data +``` + +For robust applications, check it: + +```c++ +float amps; +if (mySensor.getCurrentA(amps) != ksfTkErrOk) +{ + // Handle the communication error +} +else +{ + // amps holds a valid reading +} +``` + +### Selecting a Current Clamp + +To get accurate amps, the library needs the CT's turns ratio. Two common clamps are built in — pass one to `setCurrentClamp()` and it applies both the turns ratio and a suitable PGA gain for the board's 5.6 Ω burden resistor: + +```c++ +mySensor.setCurrentClamp(ADE7953_CLAMP_ECS1030); // SparkFun ECS1030-L72 (30A:15mA) +// or +mySensor.setCurrentClamp(ADE7953_CLAMP_SCT013); // SCT-013-000 (100A:50mA) +``` + +Using a different clamp? Enter the turns ratio directly: + +```c++ +mySensor.setCurrentClamp(1800.0f); // custom turns ratio +``` + +### Calibration + +Even with no current flowing, the ADC reports a small nonzero reading due to noise. `autoCalibrateA()` measures that no-load baseline by averaging a number of samples (taken with no current flowing) and removes it from future `getCurrentA()` readings: + +```c++ +// Run once at startup with NO load connected: +mySensor.autoCalibrateA(50); // average 50 no-load samples +``` + +The baseline is removed in the squared domain (`sqrt(reading² − baseline²)`), which is the correct way to subtract an RMS noise floor. Call `clearCalibration()` to reset it. + +> [!NOTE] +> The ADE7953 also has a hardware IRMS offset register (AIRMSOS), accessible via `setIRMSOffsetA()` / `getIRMSOffsetA()`. That register operates in the squared domain with a scaling factor that depends on the datasheet, so the library's `autoCalibrate` uses a software baseline instead, which is exact and portable. + +### Gain Configuration + +The PGA gain is set per channel and is normally chosen for you by `setCurrentClamp()` or `begin()`. You can set it explicitly: + +```c++ +mySensor.setGainIA(ADE7953_PGA_GAIN_4); // 1x, 2x, 4x, 8x, 16x, or 22x (current channels only) +``` + +A digital (fine) gain can be applied on top of the PGA. Use a floating-point multiplier (1.0 = unity) and the library converts it to the nearest valid register value: + +```c++ +mySensor.setDigitalGainIA(1.1f); // +10% +``` + +### Peak / Inrush Detection + +The ADE7953 continuously tracks the highest instantaneous current since the last reset — useful for catching motor startup surges and inrush: + +```c++ +uint32_t peak; +mySensor.getPeakIA(peak); // running peak (does not clear) +mySensor.readAndResetPeakIA(peak); // read and clear in one operation +``` + +### Interrupts + +Channel A and Channel B each have interrupt enable and status registers, exposed as a bitfield so you can set or test individual events by name: + +```c++ +sfe_ade7953_irq_reg_t irqEnable = {}; +irqEnable.oI = 1; // overcurrent +mySensor.setInterruptEnableA(irqEnable); + +sfe_ade7953_irq_reg_t status = {}; +mySensor.readAndResetInterruptStatusA(status); +if (status.oI) + Serial.println("Overcurrent!"); +``` + +### Zero-Crossing and Line Frequency + +```c++ +mySensor.setZXISourceChannel(false); // Channel A drives the ZX output +mySensor.setZXEdge(ADE7953_ZX_EDGE_BOTH); + +uint16_t period; +mySensor.getPeriod(period); // line period from the ZX detector +``` + +## Examples + +The library ships with a set of examples that build from the basics to more advanced features: + +- [Example 01 - Basic Current Reading](examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino) — the minimal sketch to read current in amps +- [Example 02 - Dual Channel Current](examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino) — read both channels and select a clamp +- [Example 03 - Gain Configuration](examples/Example03_GainConfiguration/Example03_GainConfiguration.ino) — PGA and digital gain +- [Example 04 - Peak Detection](examples/Example04_PeakDetection/Example04_PeakDetection.ino) — track peak / inrush current +- [Example 05 - Zero Crossing](examples/Example05_ZeroCrossing/Example05_ZeroCrossing.ino) — zero-crossing detection and line frequency +- [Example 06 - Interrupt Pin](examples/Example06_InterruptPin/Example06_InterruptPin.ino) — overcurrent interrupt on the IRQ pin +- [Example 07 - Auto Calibration & Error Handling](examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino) — no-load calibration and error checking +- [Example 09 - Diagnostic Tool](examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino) — an interactive serial diagnostic sketch + +## Documentation + +API documentation is generated with Doxygen and published to GitHub Pages from the `main` branch. + +## Products That Use This Library + +- [SparkFun Qwiic Current Sensor - ADE7953](https://www.sparkfun.com/sparkfun-qwiic-current-sensor.html) + +## Contributing + +If you would like to contribute to this library, please report issues and submit pull requests against the GitHub repository. + +## License + +This product is open source! Please see [LICENSE.md](LICENSE.md) for more information. + +- Your friends at SparkFun diff --git a/docs/doxygen/doxygen-config b/docs/doxygen/doxygen-config new file mode 100644 index 0000000..984dcbd --- /dev/null +++ b/docs/doxygen/doxygen-config @@ -0,0 +1,49 @@ +# Doxygen configuration for the SparkFun ADE7953 Arduino Library +# The CI workflow appends a PROJECT_NUMBER line (from git describe) before running. + +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = "SparkFun Qwiic Current Sensor - ADE7953" +PROJECT_BRIEF = "Arduino Library for the SparkFun Qwiic Current Sensor (ADE7953)" +OUTPUT_DIRECTORY = ./docs +CREATE_SUBDIRS = NO +ALLOW_UNICODE_NAMES = NO +OPTIMIZE_OUTPUT_FOR_C = NO +MARKDOWN_SUPPORT = YES +AUTOLINK_SUPPORT = YES + +# Extraction +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +HIDE_UNDOC_MEMBERS = NO +JAVADOC_AUTOBRIEF = YES + +# Input +INPUT = src \ + README.md \ + docs +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.h *.cpp *.md +RECURSIVE = YES +IMAGE_PATH = docs/images +USE_MDFILE_AS_MAINPAGE = README.md + +# HTML output +GENERATE_HTML = YES +HTML_OUTPUT = html +GENERATE_TREEVIEW = YES +DISABLE_INDEX = NO +FULL_SIDEBAR = NO + +# Disable other generators +GENERATE_LATEX = NO +GENERATE_XML = NO +GENERATE_RTF = NO +GENERATE_MAN = NO + +# Diagrams +HAVE_DOT = NO + +# Warnings +QUIET = YES +WARN_IF_UNDOCUMENTED = NO diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..6268a23 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,101 @@ +# Syntax Coloring Map for SparkFun ADE7953 Arduino Library + +# Datatypes / Classes (KEYWORD1) +SfeADE7953ArdI2C KEYWORD1 +sfDevADE7953 KEYWORD1 + +# Methods and Functions (KEYWORD2) +begin KEYWORD2 +isConnected KEYWORD2 +getVersion KEYWORD2 +reset KEYWORD2 +setWriteProtect KEYWORD2 +getWriteProtect KEYWORD2 +setGainIA KEYWORD2 +getGainIA KEYWORD2 +setGainIB KEYWORD2 +getGainIB KEYWORD2 +setGainV KEYWORD2 +getGainV KEYWORD2 +setDigitalGainIA KEYWORD2 +getDigitalGainIA KEYWORD2 +setDigitalGainIB KEYWORD2 +getDigitalGainIB KEYWORD2 +getIRMSA KEYWORD2 +getIRMSB KEYWORD2 +getCurrentA KEYWORD2 +getCurrentB KEYWORD2 +getInstantaneousIA KEYWORD2 +getInstantaneousIB KEYWORD2 +setCurrentClamp KEYWORD2 +setCurrentTransformerRatio KEYWORD2 +getCurrentTransformerRatio KEYWORD2 +setBurdenResistor KEYWORD2 +getBurdenResistor KEYWORD2 +autoCalibrateA KEYWORD2 +autoCalibrateB KEYWORD2 +clearCalibration KEYWORD2 +getPeakIA KEYWORD2 +getPeakIB KEYWORD2 +readAndResetPeakIA KEYWORD2 +readAndResetPeakIB KEYWORD2 +setOvercurrentLevel KEYWORD2 +getOvercurrentLevel KEYWORD2 +setIRMSOffsetA KEYWORD2 +getIRMSOffsetA KEYWORD2 +setIRMSOffsetB KEYWORD2 +getIRMSOffsetB KEYWORD2 +setZXISourceChannel KEYWORD2 +getZXISourceChannel KEYWORD2 +setZXEdge KEYWORD2 +getZXEdge KEYWORD2 +enableZXLPF KEYWORD2 +enableHPF KEYWORD2 +isHPFEnabled KEYWORD2 +getPeriod KEYWORD2 +setInterruptEnableA KEYWORD2 +getInterruptEnableA KEYWORD2 +getInterruptStatusA KEYWORD2 +readAndResetInterruptStatusA KEYWORD2 +setInterruptEnableB KEYWORD2 +getInterruptEnableB KEYWORD2 +getInterruptStatusB KEYWORD2 +readAndResetInterruptStatusB KEYWORD2 +setNoLoadDisable KEYWORD2 +getNoLoadDisable KEYWORD2 +setLineCycleMode KEYWORD2 +getLineCycleMode KEYWORD2 +setLineCycleCount KEYWORD2 +getLineCycleCount KEYWORD2 +setSagCycles KEYWORD2 +getSagCycles KEYWORD2 +setSagLevel KEYWORD2 +getSagLevel KEYWORD2 +getLastOperation KEYWORD2 +getLastAddress KEYWORD2 +getLastData8 KEYWORD2 +getLastData16 KEYWORD2 +getLastData32 KEYWORD2 + +# Structures / Types (KEYWORD3) +sfe_ade7953_pga_gain_t KEYWORD3 +sfe_ade7953_zx_edge_t KEYWORD3 +sfe_ade7953_clamp_t KEYWORD3 +sfe_ade7953_config_reg_t KEYWORD3 +sfe_ade7953_lcycmode_reg_t KEYWORD3 +sfe_ade7953_disnoload_reg_t KEYWORD3 +sfe_ade7953_irq_reg_t KEYWORD3 + +# Constants (LITERAL1) +ADE7953_PGA_GAIN_1 LITERAL1 +ADE7953_PGA_GAIN_2 LITERAL1 +ADE7953_PGA_GAIN_4 LITERAL1 +ADE7953_PGA_GAIN_8 LITERAL1 +ADE7953_PGA_GAIN_16 LITERAL1 +ADE7953_PGA_GAIN_22 LITERAL1 +ADE7953_ZX_EDGE_BOTH LITERAL1 +ADE7953_ZX_EDGE_NEGATIVE LITERAL1 +ADE7953_ZX_EDGE_POSITIVE LITERAL1 +ADE7953_ZX_EDGE_BOTH_ALT LITERAL1 +ADE7953_CLAMP_ECS1030 LITERAL1 +ADE7953_CLAMP_SCT013 LITERAL1 diff --git a/library.json b/library.json new file mode 100644 index 0000000..a64b3ca --- /dev/null +++ b/library.json @@ -0,0 +1,16 @@ +{ + "name": "SparkFun ADE7953 Arduino Library", + "version": "1.0.0", + "author": "SparkFun Electronics", + "keywords": "Sensor, current, energy, ADE7953, Qwiic", + "description": "An Arduino library for the SparkFun Qwiic Current Sensor (ADE7953), providing clamp-on current measurements.", + "repository": { + "type": "git", + "url": "https://github.com/sparkfun/SparkFun_ADE7953_Arduino_Library" + }, + "dependencies": { + "sparkfun/SparkFun Toolkit": ">=1.2.0" + }, + "frameworks": "arduino", + "platforms": "*" +} From 341957b2d31572fc858bc6af8864c46a9cd39b69 Mon Sep 17 00:00:00 2001 From: Geoff McIntyre Date: Tue, 2 Jun 2026 12:30:56 -0600 Subject: [PATCH 6/8] Removed context files. Added License --- LICENSE.md | 43 ++++ docs/ADE7953Registers.txt | 413 ------------------------------ docs/SparkFunCodingGuidelines.txt | 140 ---------- 3 files changed, 43 insertions(+), 553 deletions(-) create mode 100644 LICENSE.md delete mode 100644 docs/ADE7953Registers.txt delete mode 100644 docs/SparkFunCodingGuidelines.txt diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3762468 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,43 @@ +SparkFun License Information +============================ + +SparkFun uses two different licenses for our files — one for hardware and one for code. + +Hardware +--------- + +**SparkFun hardware is released under [Creative Commons Share-alike 4.0 International](http://creativecommons.org/licenses/by-sa/4.0/).** + +Note: This is a human-readable summary of (and not a substitute for) the [license](http://creativecommons.org/licenses/by-sa/4.0/legalcode). + +You are free to: + +Share — copy and redistribute the material in any medium or format +Adapt — remix, transform, and build upon the material for any purpose, even commercially. +The licensor cannot revoke these freedoms as long as you follow the license terms. +Under the following terms: + +Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. +ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. +No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. + +Notices: + +You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. +No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. + + +Code +-------- + +**SparkFun code, firmware, and software are released under the [MIT License](http://opensource.org/licenses/MIT).** + +The MIT License (MIT) + +Copyright (c) 2024 SparkFun Electronics + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/docs/ADE7953Registers.txt b/docs/ADE7953Registers.txt deleted file mode 100644 index b06f6c3..0000000 --- a/docs/ADE7953Registers.txt +++ /dev/null @@ -1,413 +0,0 @@ -ADE7953 REGISTERS -The ADE7953 contains registers that are 8, 16, 24, and 32 bits long. All signed registers are in the twos complement format with the -exception of the PHCALA and PHCALB registers, which are in sign magnitude format. The 24-bit and 32-bit registers contain the same -data but can be accessed in two different register lengths. The 24-bit register option increases communication speed; the 32-bit register -option provides simplicity when coding with the long format. When accessing the 32-bit registers, only the lower 24 bits contain valid -data (the upper 8 bits are sign extended). A write to a 24-bit register changesthe value in the corresponding 32-bit register, and vice versa. -Therefore, each 24-bit/32-bit register can be thought of as one memory location that can be accessed via two different paths. -Table 13. 8-BitRegisters -Address Register Name R/W Default Type Register Description -0x000 SAGCYC R/W 0x00 Unsigned Sag line cycles -0x001 DISNOLOAD R/W 0x00 Unsigned No-load detection disable (see Table 16) -0x004 LCYCMODE R/W 0x40 Unsigned Line cycle accumulation mode configuration (see Table 17) -0x007 PGA_V R/W 0x00 Unsigned Voltage channel gain configuration (Bits[2:0]) -0x008 PGA_IA R/W 0x00 Unsigned Current Channel A gain configuration (Bits[2:0]) -0x009 PGA_IB R/W 0x00 Unsigned Current Channel B gain configuration (Bits[2:0]) -0x040 WRITE_PROTECT R/W 0x00 Unsigned Write protection bits (Bits[2:0]) -0x0FD LAST_OP R 0x00 Unsigned Contains the type (read or write) of the last successful communication (0x35 = -read; 0xCA = write) -0x0FF LAST_RWDATA R 0x00 Unsigned Contains the data from the last successful 8-bit register communication -0x702 Version R N/A Unsigned Contains the silicon version number -0x800 EX_REF R/W 0x00 Unsigned Reference input configuration: set to 0 for internal;set to 1 for external -Table 14. 16-Bit Registers -Address Register Name R/W Default Type Register Description -0x100 ZXTOUT R/W 0xFFFF Unsigned Zero-crossing timeout -0x101 LINECYC R/W 0x0000 Unsigned Number of half line cycles for line cycle energy accumulation mode -0x102 CONFIG R/W 0x8004 Unsigned Configuration register (see Table 18) -0x103 CF1DEN R/W 0x003F Unsigned CF1 frequency divider denominator. When modifying this register,two -sequential write operations must be performed to ensure that the write is -successful. -0x104 CF2DEN R/W 0x003F Unsigned CF2 frequency divider denominator. When modifying this register,two -sequential write operations must be performed to ensure that the write is -successful. -0x107 CFMODE R/W 0x0300 Unsigned CF output selection (see Table 19) -0x108 PHCALA R/W 0x0000 Signed Phase calibration register (Current Channel A). This register is in sign -magnitude format. -0x109 PHCALB R/W 0x0000 Signed Phase calibration register (Current Channel B). This register is in sign -magnitude format. -0x10A PFA R 0x0000 Signed Power factor (Current Channel A) -0x10B PFB R 0x0000 Signed Power factor (Current Channel B) -0x10C ANGLE_A R 0x0000 Signed Angle between the voltage input and the Current Channel A input -0x10D ANGLE_B R 0x0000 Signed Angle between the voltage input and the Current Channel B input -0x10E Period R 0x0000 Unsigned Period register -0x110 ALT_OUTPUT R/W 0x0000 Unsigned Alternative output functions (see Table 20) -0x1FE LAST_ADD R 0x0000 Unsigned Contains the address of the last successful communication -0x1FF LAST_RWDATA R 0x0000 Unsigned Contains the data from the last successful 16-bit register communication -0x120 Reserved R/W 0x0000 Unsigned This register should be set to 30h to meet the performance specified in -Table 1. To modify this register, it must be unlocked by setting Register -Address 0xFE to 0xAD immediately prior. -Data Sheet ADE7953 -Rev. C | Page 61 of 72 -Table 15. 24-Bit/32-Bit Registers -Address -24-Bit 32-Bit Register Name R/W Default Type Register Description -0x200 0x300 SAGLVL R/W 0x000000 Unsigned Sag voltage level -0x201 0x301 ACCMODE R/W 0x000000 Unsigned Accumulation mode (see Table 21) -0x203 0x303 AP_NOLOAD R/W 0x00E419 Unsigned Active power no-load level -0x204 0x304 VAR_NOLOAD R/W 0x00E419 Unsigned Reactive power no-load level -0x205 0x305 VA_NOLOAD R/W 0x000000 Unsigned Apparent power no-load level -0x210 0x310 AVA R 0x000000 Signed Instantaneous apparent power (Current Channel A) -0x211 0x311 BVA R 0x000000 Signed Instantaneous apparent power (Current Channel B) -0x212 0x312 AWATT R 0x000000 Signed Instantaneous active power (Current Channel A) -0x213 0x313 BWATT R 0x000000 Signed Instantaneous active power (Current Channel B) -0x214 0x314 AVAR R 0x000000 Signed Instantaneous reactive power (Current Channel A) -0x215 0x315 BVAR R 0x000000 Signed Instantaneous reactive power (Current Channel B) -0x216 0x316 IA R 0x000000 Signed Instantaneous current (Current Channel A) -0x217 0x317 IB R 0x000000 Signed Instantaneous current (Current Channel B) -0x218 0x318 V R 0x000000 Signed Instantaneous voltage (voltage channel) -0x21A 0x31A IRMSA R 0x000000 Unsigned IRMS register (Current Channel A) -0x21B 0x31B IRMSB R 0x000000 Unsigned IRMS register (Current Channel B) -0x21C 0x31C VRMS R 0x000000 Unsigned VRMS register -0x21E 0x31E AENERGYA R 0x000000 Signed Active energy (Current Channel A) -0x21F 0x31F AENERGYB R 0x000000 Signed Active energy (Current Channel B) -0x220 0x320 RENERGYA R 0x000000 Signed Reactive energy (Current Channel A) -0x221 0x321 RENERGYB R 0x000000 Signed Reactive energy (Current Channel B) -0x222 0x322 APENERGYA R 0x000000 Signed Apparent energy (Current Channel A) -0x223 0x323 APENERGYB R 0x000000 Signed Apparent energy (Current Channel B) -0x224 0x324 OVLVL R/W 0xFFFFFF Unsigned Overvoltage level -0x225 0x325 OILVL R/W 0xFFFFFF Unsigned Overcurrent level -0x226 0x326 VPEAK R 0x000000 Unsigned Voltage channel peak -0x227 0x327 RSTVPEAK R 0x000000 Unsigned Read voltage peak with reset -0x228 0x328 IAPEAK R 0x000000 Unsigned Current Channel A peak -0x229 0x329 RSTIAPEAK R 0x000000 Unsigned Read Current Channel A peak with reset -0x22A 0x32A IBPEAK R 0x000000 Unsigned Current Channel B peak -0x22B 0x32B RSTIBPEAK R 0x000000 Unsigned Read Current Channel B peak with reset -0x22C 0x32C IRQENA R/W 0x100000 Unsigned Interrupt enable (Current Channel A, see Table 22) -0x22D 0x32D IRQSTATA R 0x000000 Unsigned Interrupt status (Current Channel A, see Table 23) -0x22E 0x32E RSTIRQSTATA R 0x000000 Unsigned Reset interrupt status (Current Channel A) -0x22F 0x32F IRQENB R/W 0x000000 Unsigned Interrupt enable (Current Channel B, see Table 24) -0x230 0x330 IRQSTATB R 0x000000 Unsigned Interrupt status (Current Channel B, see Table 25) -0x231 0x331 RSTIRQSTATB R 0x000000 Unsigned Reset interrupt status (Current Channel B) -N/A 0x37F CRC R 0xFFFFFFFF Unsigned Checksum -0x280 0x380 AIGAIN R/W 0x400000 Unsigned Current channel gain (Current Channel A) -0x281 0x381 AVGAIN R/W 0x400000 Unsigned Voltage channel gain -0x282 0x382 AWGAIN R/W 0x400000 Unsigned Active power gain (Current Channel A) -0x283 0x383 AVARGAIN R/W 0x400000 Unsigned Reactive power gain (Current Channel A) -0x284 0x384 AVAGAIN R/W 0x400000 Unsigned Apparent power gain (Current Channel A) -0x285 0x385 Reserved R/W 0x000000 Signed This register should not be modified. -0x286 0x386 AIRMSOS R/W 0x000000 Signed IRMS offset (Current Channel A) -0x287 0x387 Reserved R/W 0x000000 Signed This register should not be modified. -0x288 0x388 VRMSOS R/W 0x000000 Signed VRMS offset -0x289 0x389 AWATTOS R/W 0x000000 Signed Active power offset correction (Current Channel A) -0x28A 0x38A AVAROS R/W 0x000000 Signed Reactive power offset correction (Current Channel A) -0x28B 0x38B AVAOS R/W 0x000000 Signed Apparent power offset correction (Current Channel A) -ADE7953 Data Sheet -Rev. C | Page 62 of 72 -Address -24-Bit 32-Bit Register Name R/W Default Type Register Description -0x28C 0x38C BIGAIN R/W 0x400000 Unsigned Current channel gain (Current Channel B) -0x28D 0x38D BVGAIN R/W 0x400000 Unsigned This register should not be modified. -0x28E 0x38E BWGAIN R/W 0x400000 Unsigned Active power gain (Current Channel B) -0x28F 0x38F BVARGAIN R/W 0x400000 Unsigned Reactive power gain (Current Channel B) -0x290 0x390 BVAGAIN R/W 0x400000 Unsigned Apparent power gain (Current Channel B) -0x291 0x391 Reserved R/W 0x000000 Signed This register should not be modified. -0x292 0x392 BIRMSOS R/W 0x000000 Signed IRMS offset (Current Channel B) -0x293 0x393 Reserved R/W 0x000000 Unsigned This register should not be modified. -0x294 0x394 Reserved R/W 0x000000 Unsigned This register should not be modified. -0x295 0x395 BWATTOS R/W 0x000000 Signed Active power offset correction (Current Channel B) -0x296 0x396 BVAROS R/W 0x000000 Signed Reactive power offset correction (Current Channel B) -0x297 0x397 BVAOS R/W 0x000000 Signed Apparent power offset correction (Current Channel B) -0x2FF 0x3FF LAST_RWDATA R 0x000000 Unsigned Contains the data from the last successful 24-bit/32-bit -register communication -ADE7953 REGISTER DESCRIPTIONS -Table 16. DISNOLOAD Register (Address 0x001) -Bits Bit Name Default Description -0 DIS_APNLOAD 0 1 = disable the active power no-load feature on Current Channel A and Current Channel B -1 DIS_VARNLOAD 0 1 = disable the reactive power no-load feature on Current Channel A and Current Channel B -2 DIS_VANLOAD 0 1 = disable the apparent power no-load feature on Current Channel A and Current Channel B -Table 17. LCYCMODE Register (Address 0x004) -Bits Bit Name Default Description -0 ALWATT 0 0 = disable active energy line cycle accumulation mode on Current Channel A -1 = enable active energy line cycle accumulation mode on Current Channel A -1 BLWATT 0 0 = disable active energy line cycle accumulation mode on Current Channel B -1 = enable active energy line cycle accumulation mode on Current Channel B -2 ALVAR 0 0 = disable reactive energy line cycle accumulation mode on Current Channel A -1 = enable reactive energy line cycle accumulation mode on Current Channel A -3 BLVAR 0 0 = disable reactive energy line cycle accumulation mode on Current Channel B -1 = enable reactive energy line cycle accumulation mode on Current Channel B -4 ALVA 0 0 = disable apparent energy line cycle accumulation mode on Current Channel A -1 = enable apparent energy line cycle accumulation mode on Current Channel A -5 BLVA 0 0 = disable apparent energy line cycle accumulation mode on Current Channel B -1 = enable apparent energy line cycle accumulation mode on Current Channel B -6 RSTREAD 1 0 = disable read with reset for all registers -1 = enable read with reset for all registers -Table 18. CONFIG Register (Address 0x102) -Bits Bit Name Default Description -0 INTENA 0 1 = integrator enable (Current Channel A) -1 INTENB 0 1 = integrator enable (Current Channel B) -2 HPFEN 1 1 = HPF enable (all channels) -3 PFMODE 0 0 = power factor is based on instantaneous powers -1 = power factor is based on line cycle accumulation mode energies -4 REVP_CF 0 0 = REVP is updated on CF1 -1 = REVP is updated on CF2 -5 REVP_PULSE 0 0 = REVP is high when reverse polarity istrue, low when reverse polarity is false -1 = REVP outputs a 1 Hz pulse when reverse polarity is true and is low when reverse polarity is false -6 ZXLPF 0 0 = ZX LPF is enabled -1 = ZX LPF is disabled -7 SWRST 0 Setting this bit enables a software reset -Data Sheet ADE7953 -Rev. C | Page 63 of 72 -Bits Bit Name Default Description -8 CRC_ENABLE 0 0 = CRC is disabled -1 = CRC is enabled -[10:9] Reserved 00 Reserved -11 ZX_I 0 0 = ZX_I is based on Current Channel A -1 = ZX_I is based on Current Channel B -[13:12] ZX_EDGE 00 Zero-crossing interrupt edge selection -Setting Edge Selection -00 Interrupt isissued on both positive-going and negative-going zero crossing -01 Interrupt isissued on negative-going zero crossing -10 Interrupt isissued on positive-going zero crossing -11 Interrupt is issued on both positive-going and negative-going zero crossing -14 Reserved 0 Reserved -15 COMM_LOCK 1 0 = communication locking feature is enabled -1 = communication locking feature is disabled -Table 19. CFMODE Register (Address 0x107) -Bits Bit Name Default Description -[3:0] CF1SEL 0000 Configuration of output signal on CF1 pin -Setting CF1 Output Signal Configuration -0000 CF1 is proportional to active power (Current Channel A) -0001 CF1 is proportional to reactive power (Current Channel A) -0010 CF1 is proportional to apparent power (Current Channel A) -0011 CF1 is proportional to IRMS (Current Channel A) -0100 CF1 is proportional to active power (Current Channel B) -0101 CF1 is proportional to reactive power (Current Channel B) -0110 CF1 is proportional to apparent power (Current Channel B) -0111 CF1 is proportional to IRMS (Current Channel B) -1000 CF1 is proportional to IRMS (Current Channel A) + IRMS (Current Channel B) -1001 CF1 is proportional to active power (Current Channel A) + active power -(Current Channel B) -[7:4] CF2SEL 0000 Configuration of output signal on CF2 pin -Setting CF2 Output Signal Configuration -0000 CF2 is proportional to active power (Current Channel A) -0001 CF2 is proportional to reactive power (Current Channel A) -0010 CF2 is proportional to apparent power (Current Channel A) -0011 CF2 is proportional to IRMS (Current Channel A) -0100 CF2 is proportional to active power (Current Channel B) -0101 CF2 is proportional to reactive power (Current Channel B) -0110 CF2 is proportional to apparent power (Current Channel B) -0111 CF2 is proportional to IRMS (Current Channel B) -1000 CF2 is proportional to IRMS (Current Channel A) + IRMS (Current Channel B) -1001 CF2 is proportional to active power (Current Channel A) + active power -(Current Channel B) -8 CF1DIS 1 0 = CF1 output is enabled -1 = CF1 output is disabled -9 CF2DIS 1 0 = CF2 output is enabled -1 = CF2 output is disabled -ADE7953 Data Sheet -Rev. C | Page 64 of 72 -Table 20. ALT_OUTPUT Register (Address 0x110) -Bits Bit Name Default Description -[3:0] ZX_ALT 0000 Configuration of ZX pin (Pin 1) -Setting ZX Pin Configuration -0000 ZX detection is output on Pin 1 (default) -0001 Sag detection is output on Pin 1 -0010 Reserved -0011 Reserved -0100 Reserved -0101 Active power no-load detection (Current Channel A) is output on Pin 1 -0110 Active power no-load detection (Current Channel B) is output on Pin 1 -0111 Reactive power no-load detection (Current Channel A) is output on Pin 1 -1000 Reactive power no-load detection (Current Channel B) is output on Pin 1 -1001 Unlatched waveform sampling signal is output on Pin 1 -1010 IRQsignal is output on Pin 1 -1011 ZX_I detection is output on Pin 1 -1100 REVP detection is output on Pin 1 -1101 Reserved (set to default value) -111x Reserved (set to default value) -[7:4] ZXI_ALT 0000 Configuration of ZX_I pin (Pin 21) -Setting ZX_I Pin Configuration -0000 ZX_I detection is output on Pin 21 (default) -0001 Sag detection is output on Pin 21 -0010 Reserved -0011 Reserved -0100 Reserved -0101 Active power no-load detection (Current Channel A) is output on Pin 21 -0110 Active power no-load detection (Current Channel B) is output on Pin 21 -0111 Reactive power no-load detection (Current Channel A) is output on Pin 21 -1000 Reactive power no-load detection (Current Channel B) is output on Pin 21 -1001 Unlatched waveform sampling signal is output on Pin 21 -1010 IRQsignal is output on Pin 21 -1011 ZX detection is output on Pin 21 -1100 REVP detection is output on Pin 21 -1101 Reserved (set to default value) -111x Reserved (set to default value) -[11:8] REVP_ALT 0000 Configuration of REVP pin (Pin 20) -Setting REVP Pin Configuration -0000 REVP detection is output on Pin 20 (default) -0001 Sag detection is output on Pin 20 -0010 Reserved -0011 Reserved -0100 Reserved -0101 Active power no-load detection (Current Channel A) is output on Pin 20 -0110 Active power no-load detection (Current Channel B) is output on Pin 20 -0111 Reactive power no-load detection (Current Channel A) is output on Pin 20 -1000 Reactive power no-load detection (Current Channel B) is output on Pin 20 -1001 Unlatched waveform sampling signal is output on Pin 20 -1010 IRQsignal is output on Pin 20 -1011 ZX detection is output on Pin 20 -1100 ZX_I detection is output on Pin 20 -1101 Reserved (set to default value) -111x Reserved (set to default value) -Data Sheet ADE7953 -Rev. C | Page 65 of 72 -Table 21. ACCMODE Register (Address 0x201 and Address 0x301) -Bits Bit Name Default Description -[1:0] AWATTACC 00 Current Channel A active energy accumulation mode -Setting Active Energy Accumulation Mode (Current Channel A) -00 Normal mode -01 Positive-only accumulation mode -10 Absolute accumulation mode -11 Reserved -[3:2] BWATTACC 00 Current Channel B active energy accumulation mode -Setting Active Energy Accumulation Mode (Current Channel B) -00 Normal mode -01 Positive-only accumulation mode -10 Absolute accumulation mode -11 Reserved -[5:4] AVARACC 00 Current Channel A reactive energy accumulation mode -Setting Reactive Energy Accumulation Mode (Current Channel A) -00 Normal mode -01 Antitamper accumulation mode -10 Absolute accumulation mode -11 Reserved -[7:6] BVARACC 00 Current Channel B reactive energy accumulation mode -Setting Reactive Energy Accumulation Mode (Current Channel B) -00 Normal mode -01 Antitamper accumulation mode -10 Absolute accumulation mode -11 Reserved -8 AVAACC 0 0 = Current Channel A apparent energy accumulation is in normal mode -1 = Current Channel A apparent energy accumulation is based on IRMSA -9 BVAACC 0 0 = Current Channel B apparent energy accumulation is in normal mode -1 = Current Channel B apparent energy accumulation is based on IRMSB -10 APSIGN_A 0 0 = active power on Current Channel A is positive -1 = active power on Current Channel A is negative -11 APSIGN_B 0 0 = active power on Current Channel B is positive -1 = active power on Current Channel B is negative -12 VARSIGN_A 0 0 = reactive power on Current Channel A is positive -1 = reactive power on Current Channel A is negative -13 VARSIGN_B 0 0 = reactive power on Current Channel B is positive -1 = reactive power on Current Channel B is negative -[15:14] Reserved 00 Reserved -16 ACTNLOAD_A 0 0 = Current Channel A active energy is out of no-load condition -1 = Current Channel A active energy is in no-load condition -17 VANLOAD_A 0 0 = Current Channel A apparent energy is out of no-load condition -1 = Current Channel A apparent energy is in no-load condition -18 VARNLOAD_A 0 0 = Current Channel A reactive energy is out of no-load condition -1 = Current Channel A reactive energy is in no-load condition -19 ACTNLOAD_B 0 0 = Current Channel B active energy is out of no-load condition -1 = Current Channel B active energy is in no-load condition -20 VANLOAD_B 0 0 = Current Channel B apparent energy is out of no-load condition -1 = Current Channel B apparent energy is in no-load condition -21 VARNLOAD_B 0 0 = Current Channel B reactive energy is out of no-load condition -1 = Current Channel B reactive energy is in no-load condition -ADE7953 Data Sheet -Rev. C | Page 66 of 72 -Interrupt Enable and InterruptStatus Registers -Current Channel A and Voltage Channel Registers -Table 22. IRQENA Register (Address 0x22C and Address 0x32C) -Bits Bit Name Description -0 AEHFA Set to 1 to enable an interrupt when the active energy is half full (Current Channel A) -1 VAREHFA Set to 1 to enable an interrupt when the reactive energy is half full (Current Channel A) -2 VAEHFA Set to 1 to enable an interrupt when the apparent energy is half full (Current Channel A) -3 AEOFA Set to 1 to enable an interrupt when the active energy has overflowed or underflowed (Current Channel A) -4 VAREOFA Set to 1 to enable an interrupt when the reactive energy has overflowed or underflowed (Current Channel A) -5 VAEOFA Set to 1 to enable an interrupt when the apparent energy has overflowed or underflowed (Current Channel A) -6 AP_NOLOADA Set to 1 to enable an interrupt when the active power no-load condition is detected on Current Channel A -7 VAR_NOLOADA Set to 1 to enable an interrupt when the reactive power no-load condition is detected on Current Channel A -8 VA_NOLOADA Set to 1 to enable an interrupt when the apparent power no-load condition is detected on Current Channel A -9 APSIGN_A Set to 1 to enable an interrupt when the sign of active energy has changed (Current Channel A) -10 VARSIGN_A Set to 1 to enable an interrupt when the sign of reactive energy has changed (Current Channel A) -11 ZXTO_IA Set to 1 to enable an interrupt when the zero crossing has been missing on Current Channel A for the length of -time specified in the ZXTOUT register -12 ZXIA Set to 1 to enable an interrupt when the current Channel A zero crossing occurs -13 OIA Set to 1 to enable an interrupt when the current Channel A peak has exceeded the overcurrent threshold set in the -OILVL register -14 ZXTO Set to 1 to enable an interrupt when a zero crossing has been missing on the voltage channel for the length of -time specified in the ZXTOUT register -15 ZXV Set to 1 to enable an interrupt when the voltage channel zero crossing occurs -16 OV Set to 1 to enable an interrupt when the voltage peak has exceeded the overvoltage threshold set in the OVLVL -register -17 WSMP Set to 1 to enable an interrupt when new waveform data is acquired -18 CYCEND Set to 1 to enable an interrupt when it is the end of a line cycle accumulation period -19 Sag Set to 1 to enable an interrupt when a sag event has occurred -20 Reset This interrupt is always enabled and cannot be disabled -21 CRC Set to 1 to enable an interrupt when the checksum has changed -Table 23.IRQSTATA Register (Address 0x22D and Address 0x32D) and RSTIRQSTATA Register (Address 0x22E and -Address 0x32E) -Bits Bit Name Description -0 AEHFA Set to 1 when the active energy register is half full (Current Channel A) -1 VAREHFA Set to 1 when the reactive energy register is half full (Current Channel A) -2 VAEHFA Set to 1 when the apparent energy register is half full (Current Channel A) -3 AEOFA Set to 1 when the active energy register has overflowed or underflowed (Current Channel A) -4 VAREOFA Set to 1 when the reactive energy register has overflowed or underflowed (Current Channel A) -5 VAEOFA Set to 1 when the apparent energy register has overflowed or underflowed (Current Channel A) -6 AP_NOLOADA Set to 1 when the active power no-load condition is detected Current Channel A -7 VAR_NOLOADA Set to 1 when the reactive power no-load condition is detected Current Channel A -8 VA_NOLOADA Set to 1 when the apparent power no-load condition is detected Current Channel A -9 APSIGN_A Set to 1 when the sign of active energy has changed (Current Channel A) -10 VARSIGN_A Set to 1 when the sign of reactive energy has changed (Current Channel A) -11 ZXTO_IA Set to 1 when a zero crossing has been missing on Current Channel A for the length of time specified in the -ZXTOUT register -12 ZXIA Set to 1 when a current Channel A zero crossing is detected -13 OIA Set to 1 when the current Channel A peak has exceeded the overcurrent threshold set in the OILVL register -14 ZXTO Set to 1 when a zero crossing has been missing on the voltage channel for the length of time specified in the -ZXTOUT register -15 ZXV Set to 1 when the voltage channel zero crossing is detected -16 OV Set to 1 when the voltage peak has exceeded the overvoltage threshold set in the OVLVL register -Data Sheet ADE7953 -Rev. C | Page 67 of 72 -Bits Bit Name Description -17 WSMP Set to 1 when new waveform data is acquired -18 CYCEND Set to 1 at the end of a line cycle accumulation period -19 Sag Set to 1 when a sag event has occurred -20 Reset Set to 1 at the end of a software or hardware reset -21 CRC Set to 1 when the checksum has changed -Current Channel BRegisters -Table 24. IRQENB Register (Address 0x22F and Address 0x32F) -Bits Bit Name Description -0 AEHFB Set to 1 to enable an interrupt when the active energy is half full (Current Channel B) -1 VAREHFB Set to 1 to enable an interrupt when the reactive energy is half full (Current Channel B) -2 VAEHFB Set to 1 to enable an interrupt when the apparent energy is half full (Current Channel B) -3 AEOFB Set to 1 to enable an interrupt when the active energy has overflowed or underflowed (Current Channel B) -4 VAREOFB Set to 1 to enable an interrupt when the reactive energy has overflowed or underflowed (Current Channel B) -5 VAEOFB Set to 1 to enable an interrupt when the apparent energy has overflowed or underflowed (Current Channel B) -6 AP_NOLOADB Set to 1 to enable an interrupt when the active power no-load detection on Current Channel B occurs -7 VAR_NOLOADB Set to 1 to enable an interrupt when the reactive power no-load detection on Current Channel B occurs -8 VA_NOLOADB Set to 1 to enable an interrupt when the apparent power no-load detection on Current Channel B occurs -9 APSIGN_B Set to 1 to enable an interrupt when the sign of active energy has changed (Current Channel B) -10 VARSIGN_B Set to 1 to enable an interrupt when the sign of reactive energy has changed (Current Channel B) -11 ZXTO_IB Set to 1 to enable an interrupt when a zero crossing has been missing on Current Channel B for the length of time -specified in the ZXTOUT register -12 ZXIB Set to 1 to enable an interrupt when the current Channel B zero crossing occurs -13 OIB Set to 1 to enable an interrupt when the current Channel B peak has exceeded the overcurrent threshold set in the -OILVL register -Table 25. IRQSTATB Register (Address 0x230 and Address 0x330) and RSTIRQSTATB Register (Address 0x231 and -Address 0x331) -Bits Bit Name Description -0 AEHFB Set to 1 when the active energy register is half full (Current Channel B) -1 VAREHFB Set to 1 when the reactive energy register is half full (Current Channel B) -2 VAEHFB Set to 1 when the apparent energy register is half full (Current Channel B) -3 AEOFB Set to 1 when the active energy register has overflowed or underflowed (Current Channel B) -4 VAREOFB Set to 1 when the reactive energy register has overflowed or underflowed (Current Channel B) -5 VAEOFB Set to 1 when the apparent energy register has overflowed or underflowed (Current Channel B) -6 AP_NOLOADB Set to 1 when the active power no-load condition is detected on Current Channel B -7 VAR_NOLOADB Set to 1 when the reactive power no-load condition is detected on Current Channel B -8 VA_NOLOADB Set to 1 when the apparent power no-load condition is detected on Current Channel B -9 APSIGN_B Set to 1 when the sign of active energy has changed (Current Channel B) -10 VARSIGN_B Set to 1 when the sign of reactive energy has changed (Current Channel B) -11 ZXTO_IB Set to 1 when a zero crossing has been missing on Current Channel B for the length of time specified in the -ZXTOUT register -12 ZXIB Set to 1 when a current Channel B zero crossing is obtained -13 OIB Set to 1 when current Channel B peak has exceeded the overcurrent threshold set in the OILVL register \ No newline at end of file diff --git a/docs/SparkFunCodingGuidelines.txt b/docs/SparkFunCodingGuidelines.txt deleted file mode 100644 index 38a0501..0000000 --- a/docs/SparkFunCodingGuidelines.txt +++ /dev/null @@ -1,140 +0,0 @@ -# SparkFun Code Style Guidelines - -All code, examples and interfaces that are indented for a specific environment or platform (for example Arduino), will follow the guidelines for that specific platform. But for code implemented for use by SparkFun (internals of a library, a specific application, ...etc), the following coding style guidelines are used. - -## Format and Layout - -For all code layout and formatting, SparkFun uses the auto-formatter [clang-format](https://clang.llvm.org/docs/ClangFormat.html), with the format style set to **Microsoft**. Using clang-format with this style setting delivers code that is consistent, easy to follow and familiar. - -Additionally, clang-format is widely available as a command line command, as well as integrated in a majority of popular text editors. - -### Setting the Style in **Visual Studio Code** -In Visual Studio Code, clang-format is integrated in the standard **C/C++** extension. To change the code style, enter **Microsoft** as the value in of `Settings > Extensions > C/C++ >` **Clang_format_style**. - -Example of this setting: - -![Visual Studio Code setting](images/VSC_Settings.png) - -### Setting the Style in **Sublime Text** -Clang-format is available as a package via Package Control - specifically the ![Clang Format package](https://packagecontrol.io/packages/Clang%20Format). - -Normally the style used by the clang-format package is set using the command pallet, but the Microsoft option is fairly new and not available via this method. To set the style to **Microsoft**, the value is set in the the package settings file. - -The package settings is available from the menu `Package Settings > CLang Format > Settings - User`. In this settings file, add the `"style": "Microsoft"` line. - -```javascript -{ - "binary": "/usr/local/bin/clang-format", - "style": "Microsoft", -} -``` -Note: This example also sets the location of the clang-format binary. - -## SparkFun Style Specifics - -### Class Names, Type Names -For class names and type names, upper camel case is used. Underscores or dashes are not used. Types do not end it `_t`. - -This -```C++ -class MyClassName - -typedef int MyTypeName -``` -Not This -```C++ -class My_Class_Name -class MyClassName_isAwesome - -typedef int My_TypeName -typedef int MyTypeName_t -``` - -### Method and Variable Names -Use lower camel case for methods and variables. - -This -```C++ -void getName() -void startProcessingLoop() - -int deviceCount; -double convergenceRange; -``` - -Not This -```C++ -void GetName() -void get_Name() - -void start_processing_loop() - -int DeviceCount; -int device_count; -double CONVERGENCE_RANGE; -``` - -### Instance Variables -All instance variables begin with an underscore **"_"**. - -This -```C++ -int _currentValue; -MyLayout *_parentContainer; -``` -### Variable Names - Naming Pattern -Use full words, except in the rare case where an abbreviation would be more canonical and easier to understand. - -This -```C++ -size_t characterSize; -size_t length; -short tabIndex; // more canonical -``` - -Not This -```C++ -size_t charSize; -size_t len; -short tabulationIndex; // bizarre -``` -Note: For simple loop counters, the use the standard convention of `i, j, k, l, m, n ...` - -### Method Names - Naming Pattern -Method names should following the pattern `[verb][Noun]` - -Examples -```C++ -processEvents() -validateChecksum() -setOperatingMode() -``` - -### Property Getter and Setter Pattern -For a getter method, the name should follow the pattern `get[Noun]`. For a setter method, the name should follow the pattern `set[Noun]` - -Examples -```C++ -getColor() -setColor() - -getVoltage() -setVoltage() -``` - -### Constants -Follow the `kConstant` methodology - the letter **"k"** followed by an upper camel case name. - -This -```C++ -#define kErrorMessage (0x01 << 1) -#define kWarningMessage (0x01 << 2) -#define kInfoMessage (0x01 << 3) -``` - -Not This -```C++ -#define ERROR_MESSAGE (0x01 << 1) -#define WARNING_MESSAGE (0x01 << 2) -#define INFO_MESSAGE (0x01 << 3) -``` \ No newline at end of file From a707d7639f0f52b19af9942c7a015cbc16e463f4 Mon Sep 17 00:00:00 2001 From: Geoff McIntyre Date: Mon, 15 Jun 2026 10:47:32 -0600 Subject: [PATCH 7/8] Sets Gain to default 16x --- .../Example01_BasicCurrentReading.ino | 18 +++++++++++++----- .../Example03_GainConfiguration.ino | 10 +++++----- .../Example09_DiagnosticTool.ino | 8 ++++---- src/sfTk/sfDevADE7953.cpp | 9 +++++---- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino b/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino index a473d7e..130fd0c 100644 --- a/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino +++ b/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino @@ -2,10 +2,11 @@ Example 01 - Basic Current Reading The simplest way to read current from the SparkFun Qwiic Current Sensor (ADE7953). - begin() configures sensible defaults (PGA gain and high-pass filter) for you, so all - this sketch has to do is ask for the current in amps. + begin() configures 16x PGA gain and enables the high-pass filter. After connecting, + this sketch runs a quick auto-calibration (clamp open, no load) to zero the noise + floor before reporting current. - SparkFun Electronics + SparkFun Electronic Date: 2025 SparkFun code, firmware, and software is released under the MIT License. Please see LICENSE.md for further details. @@ -42,6 +43,13 @@ void setup() } Serial.println("ADE7953 connected!"); + + // Zero the noise floor. Make sure the CT clamp is open (no current flowing) before + // this runs. The library subtracts this baseline in the quadrature domain so the + // reading returns 0 A when there is no load. + Serial.println("Calibrating baseline — keep CT clamp open with no current flowing..."); + mySensor.autoCalibrateA(50); + Serial.println("Calibration done. Reading current..."); } void loop() @@ -51,9 +59,9 @@ void loop() mySensor.getCurrentA(amps); // Print the current. - Serial.print("Current (A): "); + //Serial.print("Current: "); Serial.println(amps, 4); // Slow things down so we are not spamming the output. - delay(1000); + delay(250); } diff --git a/examples/Example03_GainConfiguration/Example03_GainConfiguration.ino b/examples/Example03_GainConfiguration/Example03_GainConfiguration.ino index 3cdd4c8..c313cf3 100644 --- a/examples/Example03_GainConfiguration/Example03_GainConfiguration.ino +++ b/examples/Example03_GainConfiguration/Example03_GainConfiguration.ino @@ -4,13 +4,13 @@ Demonstrates how to configure the PGA (Programmable Gain Amplifier) and the digital (fine) gain on Channel A. - The PGA gain is set based on the shunt resistor on the board (4x is the default for the - 5.6 ohm shunt). Available PGA gain settings: + The PGA gain controls the amplifier sensitivity. 16x is the default for CT clamp use. + Available PGA gain settings: ADE7953_PGA_GAIN_1 (1x) ADE7953_PGA_GAIN_2 (2x) - ADE7953_PGA_GAIN_4 (4x) <-- default for this board + ADE7953_PGA_GAIN_4 (4x) ADE7953_PGA_GAIN_8 (8x) - ADE7953_PGA_GAIN_16 (16x) + ADE7953_PGA_GAIN_16 (16x) <-- default ADE7953_PGA_GAIN_22 (22x, current channels only) The digital gain provides fine-tuning on top of the PGA. This example sets it with a @@ -80,7 +80,7 @@ void setup() Serial.println("ADE7953 connected!"); Serial.println(); - // Read the current PGA gain on Channel A (set to 4x by begin()). + // Read the current PGA gain on Channel A (set to 16x by begin()). sfe_ade7953_pga_gain_t currentGain; mySensor.getGainIA(currentGain); Serial.print("PGA gain (Channel A): "); diff --git a/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino b/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino index 234e90e..eb0a72a 100644 --- a/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino +++ b/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino @@ -11,9 +11,9 @@ Serial Commands (send via Serial Monitor, line ending = Newline): g1 - Set PGA gain to 1x g2 - Set PGA gain to 2x - g3 - Set PGA gain to 4x (default) + g3 - Set PGA gain to 4x g4 - Set PGA gain to 8x - g5 - Set PGA gain to 16x + g5 - Set PGA gain to 16x (default) z - Zero calibration (averages 50 samples as a software baseline) r - Reset zero offset to 0 h - Toggle HPF (high-pass filter) on/off @@ -107,9 +107,9 @@ void printHelp() Serial.println(F("\n===== ADE7953 Diagnostic Commands =====")); Serial.println(F(" g1 PGA gain 1x")); Serial.println(F(" g2 PGA gain 2x")); - Serial.println(F(" g3 PGA gain 4x (default)")); + Serial.println(F(" g3 PGA gain 4x")); Serial.println(F(" g4 PGA gain 8x")); - Serial.println(F(" g5 PGA gain 16x")); + Serial.println(F(" g5 PGA gain 16x (default)")); Serial.println(F(" z Zero calibration")); Serial.println(F(" r Reset zero offset")); Serial.println(F(" h Toggle HPF on/off")); diff --git a/src/sfTk/sfDevADE7953.cpp b/src/sfTk/sfDevADE7953.cpp index 2ca4a82..e125f81 100644 --- a/src/sfTk/sfDevADE7953.cpp +++ b/src/sfTk/sfDevADE7953.cpp @@ -57,13 +57,14 @@ sfTkError_t sfDevADE7953::applyDefaultConfig(void) if (rc != ksfTkErrOk) return rc; - // Default the current channels to 4x gain, matching the 5.6 ohm shunt on the Qwiic board, so a - // basic sketch works without any extra configuration. - rc = setGainIA(ADE7953_PGA_GAIN_4); + // Default the current channels to 16x gain for maximum sensitivity with a CT clamp. A basic + // sketch should call autoCalibrateA()/autoCalibrateB() at startup (clamp open, no load) to + // zero the noise floor before reading current. + rc = setGainIA(ADE7953_PGA_GAIN_16); if (rc != ksfTkErrOk) return rc; - rc = setGainIB(ADE7953_PGA_GAIN_4); + rc = setGainIB(ADE7953_PGA_GAIN_16); if (rc != ksfTkErrOk) return rc; From 64d3f0d3f40943585fe966818a4068b9cd717285 Mon Sep 17 00:00:00 2001 From: Geoff McIntyre Date: Mon, 15 Jun 2026 11:28:17 -0600 Subject: [PATCH 8/8] Add per-channel CT ratios and align repo with SparkFun HowTo conventions Per-channel current clamp support: - Split the shared CT turns ratio into independent Channel A / Channel B ratios so each channel can use a different clamp (or measure directly). - Add setCurrentClampA/B, setCurrentTransformerRatioA/B, and getCurrentTransformerRatioA/B; existing single-arg methods now apply to both channels for backward compatibility. - Update keywords.txt, README, and Example02 to demonstrate per-channel use. Align with SparkFun_Arduino_Library_HowTo: - Wire up the doxygen-awesome theme: add doxygen-awesome-css submodule (v2.4.2), custom header.html/custom.css, and the matching doxygen-config HTML keys + PROJECT_LOGO. - Fix license-header wording in sfDevADE7953.cpp and SparkFun_ADE7953.h ("All rights reserved." -> MIT release wording). - Use the prescribed README License Information block. - Rename Example09 -> Example08 to close the numbering gap. - Fix "SparkFun Electronic" typo in Example01. - Add add_issue_to_project.yml issue-triage workflow. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/add_issue_to_project.yml | 17 +++ .gitmodules | 3 + README.md | 21 ++- docs/doxygen/doxygen-awesome-css | 1 + docs/doxygen/doxygen-config | 11 ++ docs/doxygen/doxygen-custom/custom.css | 21 +++ docs/doxygen/doxygen-custom/header.html | 123 ++++++++++++++++++ .../Example01_BasicCurrentReading.ino | 2 +- .../Example02_DualChannelCurrent.ino | 11 +- .../Example08_DiagnosticTool.ino} | 2 +- keywords.txt | 6 + src/SparkFun_ADE7953.h | 2 +- src/sfTk/sfDevADE7953.cpp | 82 ++++++++++-- src/sfTk/sfDevADE7953.h | 66 +++++++++- 14 files changed, 340 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/add_issue_to_project.yml create mode 100644 .gitmodules create mode 160000 docs/doxygen/doxygen-awesome-css create mode 100644 docs/doxygen/doxygen-custom/custom.css create mode 100644 docs/doxygen/doxygen-custom/header.html rename examples/{Example09_DiagnosticTool/Example09_DiagnosticTool.ino => Example08_DiagnosticTool/Example08_DiagnosticTool.ino} (99%) diff --git a/.github/workflows/add_issue_to_project.yml b/.github/workflows/add_issue_to_project.yml new file mode 100644 index 0000000..6ddb17c --- /dev/null +++ b/.github/workflows/add_issue_to_project.yml @@ -0,0 +1,17 @@ +# This action will add your issue to our Product Defect Project +name: Add new issue to main project + +on: + issues: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@main + with: + project-url: https://github.com/orgs/sparkfun/projects/19 + github-token: ${{ secrets.DEFECT_ADD_TO_PROJECT }} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a8d0052 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs/doxygen/doxygen-awesome-css"] + path = docs/doxygen/doxygen-awesome-css + url = https://github.com/jothepro/doxygen-awesome-css.git diff --git a/README.md b/README.md index b1d7599..f398183 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,17 @@ Using a different clamp? Enter the turns ratio directly: mySensor.setCurrentClamp(1800.0f); // custom turns ratio ``` +#### Per-Channel Clamps + +The methods above apply to both channels. To use a different clamp on each channel, use the `A` / `B` variants. For example, a 2000:1 clamp on Channel A and a direct (no transformer) measurement on Channel B: + +```c++ +mySensor.setCurrentClampA(ADE7953_CLAMP_ECS1030); // Channel A: 2000:1 clamp +mySensor.setCurrentTransformerRatioB(1.0f); // Channel B: measure directly (ratio 1:1) +``` + +`setCurrentClampA()` / `setCurrentClampB()` set the turns ratio and PGA gain for that channel only. `setCurrentTransformerRatioA()` / `setCurrentTransformerRatioB()` set just the ratio (leaving gain untouched) — pass `1.0` to measure that channel directly with no current transformer. + ### Calibration Even with no current flowing, the ADC reports a small nonzero reading due to noise. `autoCalibrateA()` measures that no-load baseline by averaging a number of samples (taken with no current flowing) and removes it from future `getCurrentA()` readings: @@ -203,7 +214,7 @@ The library ships with a set of examples that build from the basics to more adva - [Example 05 - Zero Crossing](examples/Example05_ZeroCrossing/Example05_ZeroCrossing.ino) — zero-crossing detection and line frequency - [Example 06 - Interrupt Pin](examples/Example06_InterruptPin/Example06_InterruptPin.ino) — overcurrent interrupt on the IRQ pin - [Example 07 - Auto Calibration & Error Handling](examples/Example07_CalibrationOffset/Example07_CalibrationOffset.ino) — no-load calibration and error checking -- [Example 09 - Diagnostic Tool](examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino) — an interactive serial diagnostic sketch +- [Example 08 - Diagnostic Tool](examples/Example08_DiagnosticTool/Example08_DiagnosticTool.ino) — an interactive serial diagnostic sketch ## Documentation @@ -217,8 +228,12 @@ API documentation is generated with Doxygen and published to GitHub Pages from t If you would like to contribute to this library, please report issues and submit pull requests against the GitHub repository. -## License +## License Information + +This product is ***open source***! + +This product is licensed using the [MIT Open Source License](https://opensource.org/license/mit) -This product is open source! Please see [LICENSE.md](LICENSE.md) for more information. +Please see [LICENSE.md](LICENSE.md) for more information. - Your friends at SparkFun diff --git a/docs/doxygen/doxygen-awesome-css b/docs/doxygen/doxygen-awesome-css new file mode 160000 index 0000000..d52eafe --- /dev/null +++ b/docs/doxygen/doxygen-awesome-css @@ -0,0 +1 @@ +Subproject commit d52eafe3e9303399fda15661f3d7bb8fe3d7eabc diff --git a/docs/doxygen/doxygen-config b/docs/doxygen/doxygen-config index 984dcbd..af88480 100644 --- a/docs/doxygen/doxygen-config +++ b/docs/doxygen/doxygen-config @@ -4,6 +4,7 @@ DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "SparkFun Qwiic Current Sensor - ADE7953" PROJECT_BRIEF = "Arduino Library for the SparkFun Qwiic Current Sensor (ADE7953)" +PROJECT_LOGO = ./docs/images/sfe_flame.png OUTPUT_DIRECTORY = ./docs CREATE_SUBDIRS = NO ALLOW_UNICODE_NAMES = NO @@ -35,6 +36,16 @@ GENERATE_TREEVIEW = YES DISABLE_INDEX = NO FULL_SIDEBAR = NO +# SparkFun doxygen-awesome theme (header, stylesheets, color scheme) +HTML_HEADER = ./docs/doxygen/doxygen-custom/header.html +HTML_EXTRA_STYLESHEET = docs/doxygen/doxygen-awesome-css/doxygen-awesome.css \ + docs/doxygen/doxygen-awesome-css/doxygen-awesome-sidebar-only.css \ + docs/doxygen/doxygen-custom/custom.css +HTML_COLORSTYLE = AUTO_LIGHT +HTML_COLORSTYLE_HUE = 209 +HTML_COLORSTYLE_SAT = 255 +HTML_COLORSTYLE_GAMMA = 113 + # Disable other generators GENERATE_LATEX = NO GENERATE_XML = NO diff --git a/docs/doxygen/doxygen-custom/custom.css b/docs/doxygen/doxygen-custom/custom.css new file mode 100644 index 0000000..92e001c --- /dev/null +++ b/docs/doxygen/doxygen-custom/custom.css @@ -0,0 +1,21 @@ +:root { + --side-nav-fixed-width: 300px; +} + +.github-corner svg { + fill: var(--primary-light-color); + color: var(--page-background-color); + width: 72px; + height: 72px; +} + +#projectnumber { + margin-right: 22px; +} + +@media screen and (max-width: 767px) { + .github-corner svg { + width: 55px; + height: 55px; + } +} diff --git a/docs/doxygen/doxygen-custom/header.html b/docs/doxygen/doxygen-custom/header.html new file mode 100644 index 0000000..0254b9b --- /dev/null +++ b/docs/doxygen/doxygen-custom/header.html @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + $projectname: $title + + $title + + + + + $treeview + $search + $mathjax + + $extrastylesheet + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+
$projectname +  $projectnumber +
+ +
$projectbrief
+
+
$projectbrief
+
$searchbox
+
+ + diff --git a/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino b/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino index 130fd0c..804cc5f 100644 --- a/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino +++ b/examples/Example01_BasicCurrentReading/Example01_BasicCurrentReading.ino @@ -6,7 +6,7 @@ this sketch runs a quick auto-calibration (clamp open, no load) to zero the noise floor before reporting current. - SparkFun Electronic + SparkFun Electronics Date: 2025 SparkFun code, firmware, and software is released under the MIT License. Please see LICENSE.md for further details. diff --git a/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino b/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino index 884de81..c78f699 100644 --- a/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino +++ b/examples/Example02_DualChannelCurrent/Example02_DualChannelCurrent.ino @@ -43,10 +43,13 @@ void setup() Serial.println("ADE7953 connected!"); - // Tell the library which clamp is attached so the amps conversion is correct. This sets the - // turns ratio and a suitable PGA gain for you. Use ADE7953_CLAMP_SCT013 for the SCT-013-000, - // or mySensor.setCurrentClamp(yourRatio) to enter a custom turns ratio. - mySensor.setCurrentClamp(ADE7953_CLAMP_ECS1030); + // Tell the library which clamp is attached so the amps conversion is correct. Each channel can + // be configured independently. Here Channel A uses a 2000:1 clamp, while Channel B measures + // directly (turns ratio 1:1, no current transformer). + // + // To use the same clamp on both channels, call mySensor.setCurrentClamp(ADE7953_CLAMP_ECS1030). + mySensor.setCurrentClampA(ADE7953_CLAMP_ECS1030); // Channel A: SparkFun ECS1030-L72 (2000:1) + mySensor.setCurrentTransformerRatioB(1.0f); // Channel B: direct measurement (1:1) Serial.println(); Serial.println("Channel A (A)\tChannel B (A)"); diff --git a/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino b/examples/Example08_DiagnosticTool/Example08_DiagnosticTool.ino similarity index 99% rename from examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino rename to examples/Example08_DiagnosticTool/Example08_DiagnosticTool.ino index eb0a72a..f0351a2 100644 --- a/examples/Example09_DiagnosticTool/Example09_DiagnosticTool.ino +++ b/examples/Example08_DiagnosticTool/Example08_DiagnosticTool.ino @@ -1,5 +1,5 @@ /* - Example 09 - Diagnostic Tool + Example 08 - Diagnostic Tool Interactive diagnostic sketch for the ADE7953 current sensor. Provides serial commands to change PGA gain, toggle the high-pass filter, perform a software zero, and view raw, diff --git a/keywords.txt b/keywords.txt index 6268a23..3e1c09b 100644 --- a/keywords.txt +++ b/keywords.txt @@ -28,8 +28,14 @@ getCurrentB KEYWORD2 getInstantaneousIA KEYWORD2 getInstantaneousIB KEYWORD2 setCurrentClamp KEYWORD2 +setCurrentClampA KEYWORD2 +setCurrentClampB KEYWORD2 setCurrentTransformerRatio KEYWORD2 +setCurrentTransformerRatioA KEYWORD2 +setCurrentTransformerRatioB KEYWORD2 getCurrentTransformerRatio KEYWORD2 +getCurrentTransformerRatioA KEYWORD2 +getCurrentTransformerRatioB KEYWORD2 setBurdenResistor KEYWORD2 getBurdenResistor KEYWORD2 autoCalibrateA KEYWORD2 diff --git a/src/SparkFun_ADE7953.h b/src/SparkFun_ADE7953.h index 717032a..ca8edfc 100644 --- a/src/SparkFun_ADE7953.h +++ b/src/SparkFun_ADE7953.h @@ -10,7 +10,7 @@ * * @author SparkFun Electronics * @date 2025 - * @copyright Copyright (c) 2025, SparkFun Electronics Inc. All rights reserved. + * @copyright Copyright (c) 2025, SparkFun Electronics Inc. This project is released under the MIT License. * * SPDX-License-Identifier: MIT * diff --git a/src/sfTk/sfDevADE7953.cpp b/src/sfTk/sfDevADE7953.cpp index e125f81..45c0d0a 100644 --- a/src/sfTk/sfDevADE7953.cpp +++ b/src/sfTk/sfDevADE7953.cpp @@ -10,7 +10,7 @@ * * @author SparkFun Electronics * @date 2025 - * @copyright Copyright (c) 2025, SparkFun Electronics Inc. All rights reserved. + * @copyright Copyright (c) 2025, SparkFun Electronics Inc. This project is released under the MIT License. * * SPDX-License-Identifier: MIT * @@ -276,7 +276,7 @@ sfTkError_t sfDevADE7953::getCurrentA(float &s) // Pin voltage RMS = corrected / fullScaleCode * (fullScaleVRMS / pgaGain) // Secondary current = pinVRMS / burdenResistor // Primary current = secondaryCurrent * ctRatio - float fullScaleAmps = (_fullScaleVRMS / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; + float fullScaleAmps = (_fullScaleVRMS / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatioA; amps = corrected / _fullScaleCode * fullScaleAmps; return ksfTkErrOk; } @@ -295,7 +295,7 @@ sfTkError_t sfDevADE7953::getCurrentB(float &s) float corrected = removeBaseline(raw, _baselineB); - float fullScaleAmps = (_fullScaleVRMS / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatio; + float fullScaleAmps = (_fullScaleVRMS / pgaGainToMultiplier(gain)) / _burdenResistor * _ctRatioB; amps = corrected / _fullScaleCode * fullScaleAmps; return ksfTkErrOk; } @@ -334,23 +334,31 @@ float sfDevADE7953::removeBaseline(uint32_t reading, uint32_t baseline) return sqrtf(r * r - b * b); } -sfTkError_t sfDevADE7953::setCurrentClamp(sfe_ade7953_clamp_t clamp) +void sfDevADE7953::clampPreset(sfe_ade7953_clamp_t clamp, float &ratio, sfe_ade7953_pga_gain_t &gain) { - sfe_ade7953_pga_gain_t gain = ADE7953_PGA_GAIN_4; - switch (clamp) { case ADE7953_CLAMP_SCT013: - _ctRatio = 2000.0f; + ratio = 2000.0f; gain = ADE7953_PGA_GAIN_16; break; case ADE7953_CLAMP_ECS1030: default: - _ctRatio = 2000.0f; + ratio = 2000.0f; gain = ADE7953_PGA_GAIN_16; break; } +} + +sfTkError_t sfDevADE7953::setCurrentClamp(sfe_ade7953_clamp_t clamp) +{ + float ratio = 2000.0f; + sfe_ade7953_pga_gain_t gain = ADE7953_PGA_GAIN_16; + clampPreset(clamp, ratio, gain); + + _ctRatioA = ratio; + _ctRatioB = ratio; sfTkError_t rc = setGainIA(gain); if (rc != ksfTkErrOk) @@ -359,9 +367,40 @@ sfTkError_t sfDevADE7953::setCurrentClamp(sfe_ade7953_clamp_t clamp) return setGainIB(gain); } +sfTkError_t sfDevADE7953::setCurrentClampA(sfe_ade7953_clamp_t clamp) +{ + float ratio = 2000.0f; + sfe_ade7953_pga_gain_t gain = ADE7953_PGA_GAIN_16; + clampPreset(clamp, ratio, gain); + + _ctRatioA = ratio; + return setGainIA(gain); +} + +sfTkError_t sfDevADE7953::setCurrentClampB(sfe_ade7953_clamp_t clamp) +{ + float ratio = 2000.0f; + sfe_ade7953_pga_gain_t gain = ADE7953_PGA_GAIN_16; + clampPreset(clamp, ratio, gain); + + _ctRatioB = ratio; + return setGainIB(gain); +} + void sfDevADE7953::setCurrentClamp(float turnsRatio) { - _ctRatio = turnsRatio; + _ctRatioA = turnsRatio; + _ctRatioB = turnsRatio; +} + +void sfDevADE7953::setCurrentClampA(float turnsRatio) +{ + _ctRatioA = turnsRatio; +} + +void sfDevADE7953::setCurrentClampB(float turnsRatio) +{ + _ctRatioB = turnsRatio; } sfTkError_t sfDevADE7953::autoCalibrateA(uint16_t numSamples) @@ -410,12 +449,33 @@ void sfDevADE7953::clearCalibration(void) void sfDevADE7953::setCurrentTransformerRatio(float ratio) { - _ctRatio = ratio; + _ctRatioA = ratio; + _ctRatioB = ratio; +} + +void sfDevADE7953::setCurrentTransformerRatioA(float ratio) +{ + _ctRatioA = ratio; +} + +void sfDevADE7953::setCurrentTransformerRatioB(float ratio) +{ + _ctRatioB = ratio; } float sfDevADE7953::getCurrentTransformerRatio(void) { - return _ctRatio; + return _ctRatioA; +} + +float sfDevADE7953::getCurrentTransformerRatioA(void) +{ + return _ctRatioA; +} + +float sfDevADE7953::getCurrentTransformerRatioB(void) +{ + return _ctRatioB; } void sfDevADE7953::setBurdenResistor(float ohms) diff --git a/src/sfTk/sfDevADE7953.h b/src/sfTk/sfDevADE7953.h index e4fa819..7d105f8 100644 --- a/src/sfTk/sfDevADE7953.h +++ b/src/sfTk/sfDevADE7953.h @@ -330,27 +330,72 @@ class sfDevADE7953 // =================== Current Conversion Calibration =================== - /// @brief Select a known current clamp and apply its turns ratio and PGA gain. + /// @brief Select a known current clamp and apply its turns ratio and PGA gain to both channels. /// @details Sets the CT ratio used by getCurrentA()/getCurrentB() and writes a suitable PGA gain - /// to both current channels for the board's 5.6 ohm burden resistor. + /// to both current channels for the board's 5.6 ohm burden resistor. To use a different clamp on + /// each channel, call setCurrentClampA()/setCurrentClampB() instead. /// @param clamp One of the supported clamps (see sfe_ade7953_clamp_t). /// @return ::ksfTkErrOk on success, or an error code on failure. sfTkError_t setCurrentClamp(sfe_ade7953_clamp_t clamp); - /// @brief Configure a custom current clamp by its turns ratio. + /// @brief Select a known current clamp for Channel A and apply its turns ratio and PGA gain. + /// @details Sets the Channel A CT ratio used by getCurrentA() and writes a suitable PGA gain to + /// Channel A only. Channel B is left unchanged. + /// @param clamp One of the supported clamps (see sfe_ade7953_clamp_t). + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setCurrentClampA(sfe_ade7953_clamp_t clamp); + + /// @brief Select a known current clamp for Channel B and apply its turns ratio and PGA gain. + /// @details Sets the Channel B CT ratio used by getCurrentB() and writes a suitable PGA gain to + /// Channel B only. Channel A is left unchanged. + /// @param clamp One of the supported clamps (see sfe_ade7953_clamp_t). + /// @return ::ksfTkErrOk on success, or an error code on failure. + sfTkError_t setCurrentClampB(sfe_ade7953_clamp_t clamp); + + /// @brief Configure a custom current clamp on both channels by its turns ratio. /// @details Use this for a clamp that is not one of the presets. Only the CT ratio is changed; /// the PGA gain is left as-is (set it with setGainIA()/setGainIB() if needed). /// @param turnsRatio Turns ratio of the current transformer (e.g. 2000.0 for 30A:15mA). void setCurrentClamp(float turnsRatio); - /// @brief Set the CT (current transformer) turns ratio used by getCurrentA() / getCurrentB(). + /// @brief Configure a custom current clamp on Channel A by its turns ratio. + /// @details Only the Channel A CT ratio is changed; the PGA gain is left as-is. Pass 1.0 to + /// measure Channel A directly (no current transformer). + /// @param turnsRatio Turns ratio of the current transformer (e.g. 2000.0 for 30A:15mA). + void setCurrentClampA(float turnsRatio); + + /// @brief Configure a custom current clamp on Channel B by its turns ratio. + /// @details Only the Channel B CT ratio is changed; the PGA gain is left as-is. Pass 1.0 to + /// measure Channel B directly (no current transformer). + /// @param turnsRatio Turns ratio of the current transformer (e.g. 2000.0 for 30A:15mA). + void setCurrentClampB(float turnsRatio); + + /// @brief Set the CT (current transformer) turns ratio used by getCurrentA() and getCurrentB(). + /// @details Sets both channels to the same ratio. Use setCurrentTransformerRatioA() / + /// setCurrentTransformerRatioB() to give each channel its own ratio. /// @param ratio Turns ratio of the current transformer (e.g. 2000.0 for a 30A:15mA clamp). void setCurrentTransformerRatio(float ratio); - /// @brief Get the configured CT turns ratio. - /// @return The current transformer turns ratio. + /// @brief Set the CT turns ratio used by getCurrentA() for Channel A. + /// @param ratio Turns ratio of the current transformer (e.g. 2000.0 for a 30A:15mA clamp). + void setCurrentTransformerRatioA(float ratio); + + /// @brief Set the CT turns ratio used by getCurrentB() for Channel B. + /// @param ratio Turns ratio of the current transformer (e.g. 2000.0 for a 30A:15mA clamp). + void setCurrentTransformerRatioB(float ratio); + + /// @brief Get the configured CT turns ratio for Channel A. + /// @return The Channel A current transformer turns ratio. float getCurrentTransformerRatio(void); + /// @brief Get the configured CT turns ratio for Channel A. + /// @return The Channel A current transformer turns ratio. + float getCurrentTransformerRatioA(void); + + /// @brief Get the configured CT turns ratio for Channel B. + /// @return The Channel B current transformer turns ratio. + float getCurrentTransformerRatioB(void); + /// @brief Set the burden resistor value (in ohms) used by getCurrentA() / getCurrentB(). /// @param ohms Burden resistor value in ohms. void setBurdenResistor(float ohms); @@ -618,6 +663,12 @@ class sfDevADE7953 /// @brief Convert a PGA gain enum value to its numeric multiplier (1, 2, 4, 8, 16, 22). static float pgaGainToMultiplier(sfe_ade7953_pga_gain_t gain); + /// @brief Look up the turns ratio and PGA gain for a known current clamp preset. + /// @param clamp One of the supported clamps (see sfe_ade7953_clamp_t). + /// @param ratio Output reference that receives the clamp's turns ratio. + /// @param gain Output reference that receives the clamp's recommended PGA gain. + static void clampPreset(sfe_ade7953_clamp_t clamp, float &ratio, sfe_ade7953_pga_gain_t &gain); + /// @brief Remove a no-load baseline from a raw RMS reading in the squared domain. /// @return sqrt(reading^2 - baseline^2), clamped at 0; or reading unchanged if baseline is 0. static float removeBaseline(uint32_t reading, uint32_t baseline); @@ -625,7 +676,8 @@ class sfDevADE7953 sfTkIBus *_theBus; ///< Pointer to the communication bus device. // --- Current-to-amps conversion parameters (sensible defaults for the Qwiic board) --- - float _ctRatio = 2000.0f; ///< CT turns ratio (30A:15mA = 2000:1). + float _ctRatioA = 2000.0f; ///< Channel A CT turns ratio (30A:15mA = 2000:1). + float _ctRatioB = 2000.0f; ///< Channel B CT turns ratio (30A:15mA = 2000:1). float _burdenResistor = 5.6f; ///< Burden resistor in ohms. float _fullScaleCode = 5928256.0f; ///< ADE7953 full-scale IRMS code. float _fullScaleVRMS = 0.35355f; ///< Full-scale input RMS voltage (0.5 V peak / sqrt(2)).