diff --git a/examples/Example1_ReadVoltageCurrentPower/Example1_ReadVoltageCurrentPower.ino b/examples/Example1_ReadVoltageCurrentPower/Example1_ReadVoltageCurrentPower.ino index 58a6bac..c8ffe7c 100644 --- a/examples/Example1_ReadVoltageCurrentPower/Example1_ReadVoltageCurrentPower.ino +++ b/examples/Example1_ReadVoltageCurrentPower/Example1_ReadVoltageCurrentPower.ino @@ -43,9 +43,9 @@ void setup() void loop() { - float volts; - float amps; - float watts; + float volts = 0.0; + float amps = 0.0; + float watts = 0.0; mySensor.readInstantaneous(&volts, &s, &watts); // Read the instantaneous voltage, current and power Serial.print(F("Volts: ")); diff --git a/examples/Example4_ReadVoltageCurrentRMS/Example4_ReadVoltageCurrentRMS.ino b/examples/Example4_ReadVoltageCurrentRMS/Example4_ReadVoltageCurrentRMS.ino index 70c3a64..547ad54 100644 --- a/examples/Example4_ReadVoltageCurrentRMS/Example4_ReadVoltageCurrentRMS.ino +++ b/examples/Example4_ReadVoltageCurrentRMS/Example4_ReadVoltageCurrentRMS.ino @@ -35,11 +35,11 @@ void setup() // CONFIGURING THE DEVICE FOR AC APPLICATIONS : DYNAMIC CALCULATION OF N // Set bypass_n_en = 0 (default). This setting enables the device to // dynamically calculate N based off the voltage zero crossings. - mySensor.setBypassNenable(false, false); // Disable bypass_n in shadow memory and eeprom + mySensor.setBypassNenable(false, true); // Disable bypass_n in shadow memory and eeprom // We need to connect the LO pin to the 'low' side of the AC source. // So we need to set the divider resistance to 4M Ohms (instead of 2M). - mySensor.setDividerRes(4000000); + mySensor.setDividerRes(4000000); // Comment this line if you are using GND to measure the 'low' side of the AC voltage } void loop() diff --git a/examples/Example5_ReadVoltageCurrentPowerRMS/Example5_ReadVoltageCurrentPowerRMS.ino b/examples/Example5_ReadVoltageCurrentPowerRMS/Example5_ReadVoltageCurrentPowerRMS.ino new file mode 100644 index 0000000..87635d2 --- /dev/null +++ b/examples/Example5_ReadVoltageCurrentPowerRMS/Example5_ReadVoltageCurrentPowerRMS.ino @@ -0,0 +1,85 @@ +/* + Library for the Allegro MicroSystems ACS37800 power monitor IC + By: Paul Clark + SparkFun Electronics + Date: December 4th, 2021 + License: please see LICENSE.md for details + + Feel like supporting our work? Buy a board from SparkFun! + https://www.sparkfun.com/products/17873 +*/ + +#include "SparkFun_ACS37800_Arduino_Library.h" // Click here to get the library: http://librarymanager/All#SparkFun_ACS37800 +#include + +ACS37800 mySensor; //Create an object of the ACS37800 class + +void setup() +{ + Serial.begin(115200); + Serial.println(F("ACS37800 Example")); + + Wire.begin(); + + //mySensor.enableDebugging(); // Uncomment this line to print useful debug messages to Serial + + //Initialize sensor using default I2C address + if (mySensor.begin() == false) + { + Serial.print(F("ACS37800 not detected. Check connections and I2C address. Freezing...")); + while (1) + ; // Do nothing more + } + + // From the ACS37800 datasheet: + // CONFIGURING THE DEVICE FOR AC APPLICATIONS : DYNAMIC CALCULATION OF N + // Set bypass_n_en = 0 (default). This setting enables the device to + // dynamically calculate N based off the voltage zero crossings. + mySensor.setBypassNenable(false, true); // Disable bypass_n in shadow memory and eeprom + + // We need to connect the LO pin to the 'low' side of the AC source. + // So we need to set the divider resistance to 4M Ohms (instead of 2M). + mySensor.setDividerRes(4000000); // Comment this line if you are using GND to measure the 'low' side of the AC voltage +} + +void loop() +{ + float volts = 0.0; + float amps = 0.0; + + mySensor.readRMS(&volts, &s); // Read the RMS voltage and current + Serial.print(F("Volts: ")); + Serial.print(volts, 2); + Serial.print(F(" Amps: ")); + Serial.println(amps, 2); + + float pactive = 0.0; + float preactive = 0.0; + + mySensor.readPowerActiveReactive(&pactive, &preactive); // Read the active and reactive power + Serial.print(F("Power: Active (W): ")); + Serial.print(pactive, 2); + Serial.print(F(" Reactive (VAR): ")); + Serial.println(preactive, 2); + + float papparent = 0.0; + float pfactor = 0.0; + bool posangle = 0; + bool pospf = 0; + + mySensor.readPowerFactor(&papparent, &pfactor, &posangle, &pospf); // Read the apparent power and the power factor + Serial.print(F("Power: Apparent (VA): ")); + Serial.print(papparent, 2); + Serial.print(F(" Power Factor: ")); + Serial.print(pfactor, 2); + if (posangle) + Serial.print(F(" Lagging")); + else + Serial.print(F(" Leading")); + if (pospf) + Serial.println(F(" Consumed")); + else + Serial.println(F(" Generated")); + + delay(250); +} diff --git a/keywords.txt b/keywords.txt index 5974e07..eb883a3 100644 --- a/keywords.txt +++ b/keywords.txt @@ -40,6 +40,8 @@ setBypassNenable KEYWORD2 getBypassNenable KEYWORD2 getCurrentCoarseGain KEYWORD2 readRMS KEYWORD2 +readPowerActiveReactive KEYWORD2 +readPowerFactor KEYWORD2 readInstantaneous KEYWORD2 readErrorFlags KEYWORD2 setSenseRes KEYWORD2 diff --git a/library.properties b/library.properties index 2aaff0f..322a517 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun ACS37800 Power Monitor Arduino Library -version=1.0.4 +version=1.0.5 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=Library for the Allegro MicroSystems ACS37800 power monitor IC diff --git a/src/SparkFun_ACS37800_Arduino_Library.cpp b/src/SparkFun_ACS37800_Arduino_Library.cpp index 3da46b4..8d4ea23 100644 --- a/src/SparkFun_ACS37800_Arduino_Library.cpp +++ b/src/SparkFun_ACS37800_Arduino_Library.cpp @@ -227,7 +227,7 @@ ACS37800ERR ACS37800::setI2Caddress(uint8_t newAddress) } //Set the number of samples for RMS calculations. Bypass_N_Enable must be set/true for this to have effect. -ACS37800ERR ACS37800::setNumberOfSamples(uint32_t numberOfSamples, boolean _eeprom) +ACS37800ERR ACS37800::setNumberOfSamples(uint32_t numberOfSamples, bool _eeprom) { ACS37800ERR error = writeRegister(ACS37800_CUSTOMER_ACCESS_CODE, ACS37800_REGISTER_VOLATILE_2F); // Set the customer access code @@ -353,7 +353,7 @@ ACS37800ERR ACS37800::getNumberOfSamples(uint32_t *numberOfSamples) } //Set/Clear the Bypass_N_Enable flag -ACS37800ERR ACS37800::setBypassNenable(boolean bypass, boolean _eeprom) +ACS37800ERR ACS37800::setBypassNenable(bool bypass, bool _eeprom) { ACS37800ERR error = writeRegister(ACS37800_CUSTOMER_ACCESS_CODE, ACS37800_REGISTER_VOLATILE_2F); // Set the customer access code @@ -466,7 +466,7 @@ ACS37800ERR ACS37800::setBypassNenable(boolean bypass, boolean _eeprom) } //// Read and return the bypass_n_en flag from shadow memory -ACS37800ERR ACS37800::getBypassNenable(boolean *bypass) +ACS37800ERR ACS37800::getBypassNenable(bool *bypass) { ACS37800_REGISTER_0F_t store; ACS37800ERR error = readRegister(&store.data.all, ACS37800_REGISTER_SHADOW_1F); // Read register 1F @@ -487,7 +487,7 @@ ACS37800ERR ACS37800::getBypassNenable(boolean *bypass) _debugPort->println(store.data.bits.bypass_n_en); } - *bypass = (boolean)store.data.bits.bypass_n_en; //Return bypass_n_en + *bypass = (bool)store.data.bits.bypass_n_en; //Return bypass_n_en return (error); } @@ -539,12 +539,13 @@ ACS37800ERR ACS37800::readRMS(float *vRMS, float *iRMS) //Extract vrms. Convert to voltage in Volts. // Note: datasheet says "RMS voltage output. This field is an unsigned 16-bit fixed point number with 16 fractional bits" + // Datasheet also says "Voltage Channel ADC Sensitivity: 110 LSB/mV" float volts = (float)store.data.bits.vrms; if (_printDebug == true) { _debugPort->print(F("readRMS: vrms: 0x")); _debugPort->println(store.data.bits.vrms, HEX); - _debugPort->print(F("readRMS: volts (mV, before correction) is ")); + _debugPort->print(F("readRMS: volts (LSB, before correction) is ")); _debugPort->println(volts); } volts /= 55000.0; //Convert from codes to the fraction of ADC Full Scale (16-bit) @@ -575,7 +576,7 @@ ACS37800ERR ACS37800::readRMS(float *vRMS, float *iRMS) { _debugPort->print(F("readRMS: irms: 0x")); _debugPort->println(store.data.bits.irms, HEX); - _debugPort->print(F("readRMS: amps (A, before correction) is ")); + _debugPort->print(F("readRMS: amps (LSB, before correction) is ")); _debugPort->println(amps); } amps /= 55000.0; //Convert from codes to the fraction of ADC Full Scale (16-bit) @@ -590,6 +591,178 @@ ACS37800ERR ACS37800::readRMS(float *vRMS, float *iRMS) return (error); } +// Read volatile register 0x21. Return the pactive and pimag. +ACS37800ERR ACS37800::readPowerActiveReactive(float *pActive, float *pReactive) +{ + ACS37800_REGISTER_21_t store; + ACS37800ERR error = readRegister(&store.data.all, ACS37800_REGISTER_VOLATILE_21); // Read register 21 + + if (error != ACS37800_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("readPowerActiveReactive: readRegister (21) returned: ")); + _debugPort->println(error); + } + return (error); // Bail + } + + // Extract pactive. Convert to Watts + // Note: datasheet says: + // "Active power output. This field is a signed 16-bit fixed point + // number with 15 fractional bits, where positive MaxPow = 0.704, + // and negative MaxPow = –0.704. To convert the value (input + // power) to line power, divide the input power by the RSENSE and + // RISO voltage divider ratio using actual resistor values." + // Datasheet also says: + // "3.08 LSB/mW for the 30A version and 1.03 LSB/mW for the 90A version" + + union + { + int16_t Signed; + uint16_t unSigned; + } signedUnsigned; // Avoid any ambiguity when casting to signed int + + signedUnsigned.unSigned = store.data.bits.pactive; + + float power = (float)signedUnsigned.Signed; + if (_printDebug == true) + { + _debugPort->print(F("readPowerActiveReactive: pactive: 0x")); + _debugPort->println(signedUnsigned.unSigned, HEX); + _debugPort->print(F("readPowerActiveReactive: pactive (LSB, before correction) is ")); + _debugPort->println(power); + } + float LSBpermW = 3.08; // LSB per mW + LSBpermW *= 30.0 / _currentSensingRange; // Correct for sensor version + power /= LSBpermW; //Convert from codes to mW + //Correct for the voltage divider: (RISO1 + RISO2 + RSENSE) / RSENSE + //Or: (RISO1 + RISO2 + RISO3 + RISO4 + RSENSE) / RSENSE + float resistorMultiplier = (_dividerResistance + _senseResistance) / _senseResistance; + power *= resistorMultiplier; + power /= 1000; // Convert from mW to W + if (_printDebug == true) + { + _debugPort->print(F("readPowerActiveReactive: pactive (W, after correction) is ")); + _debugPort->println(power); + } + *pActive = power; + + // Extract pimag. Convert to VAR + // Note: datasheet says: + // "Reactive power output. This field is an unsigned 16-bit fixed + // point number with 16 fractional bits, where MaxPow = 0.704. To + // convert the value (input power) to line power, divide the input + // power by the RSENSE and RISO voltage divider ratio using actual + // resistor values." + // Datasheet also says: + // "6.15 LSB/mVAR for the 30A version and 2.05 LSB/mVAR for the 90A version" + + power = (float)store.data.bits.pimag; + if (_printDebug == true) + { + _debugPort->print(F("readPowerActiveReactive: pimag: 0x")); + _debugPort->println(store.data.bits.pimag, HEX); + _debugPort->print(F("readPowerActiveReactive: pimag (LSB, before correction) is ")); + _debugPort->println(power); + } + float LSBpermVAR = 6.15; // LSB per mVAR + LSBpermVAR *= 30.0 / _currentSensingRange; // Correct for sensor version + power /= LSBpermVAR; //Convert from codes to mVAR + //Correct for the voltage divider: (RISO1 + RISO2 + RSENSE) / RSENSE + //Or: (RISO1 + RISO2 + RISO3 + RISO4 + RSENSE) / RSENSE + power *= resistorMultiplier; + power /= 1000; // Convert from mVAR to VAR + if (_printDebug == true) + { + _debugPort->print(F("readPowerActiveReactive: pimag (VAR, after correction) is ")); + _debugPort->println(power); + } + *pReactive = power; + + return (error); +} + +// Read volatile register 0x22. Return the apparent power, power factor, leading / lagging, generated / consumed +ACS37800ERR ACS37800::readPowerFactor(float *pApparent, float *pFactor, bool *posangle, bool *pospf) +{ + ACS37800_REGISTER_22_t store; + ACS37800ERR error = readRegister(&store.data.all, ACS37800_REGISTER_VOLATILE_22); // Read register 22 + + if (error != ACS37800_SUCCESS) + { + if (_printDebug == true) + { + _debugPort->print(F("readPowerFactor: readRegister (22) returned: ")); + _debugPort->println(error); + } + return (error); // Bail + } + + // Extract papparent. Convert to VA + // Note: datasheet says: + // "Apparent power output magnitude. This field is an unsigned + // 16-bit fixed point number with 16 fractional bits, where MaxPow + // = 0.704. To convert the value (input power) to line power, divide + // the input power by the RSENSE and RISO voltage divider ratio + // using actual resistor values." + // Datasheet also says: + // "6.15 LSB/mVA for the 30A version and 2.05 LSB/mVA for the 90A version" + + float power = (float)store.data.bits.papparent; + if (_printDebug == true) + { + _debugPort->print(F("readPowerFactor: papparent: 0x")); + _debugPort->println(store.data.bits.papparent, HEX); + _debugPort->print(F("readPowerFactor: papparent (LSB, before correction) is ")); + _debugPort->println(power); + } + float LSBpermVA = 6.15; // LSB per mVA + LSBpermVA *= 30.0 / _currentSensingRange; // Correct for sensor version + power /= LSBpermVA; //Convert from codes to mVA + //Correct for the voltage divider: (RISO1 + RISO2 + RSENSE) / RSENSE + //Or: (RISO1 + RISO2 + RISO3 + RISO4 + RSENSE) / RSENSE + float resistorMultiplier = (_dividerResistance + _senseResistance) / _senseResistance; + power *= resistorMultiplier; + power /= 1000; // Convert from mVAR to VAR + if (_printDebug == true) + { + _debugPort->print(F("readPowerFactor: papparent (VA, after correction) is ")); + _debugPort->println(power); + } + *pApparent = power; + + // Extract power factor + // Datasheet says: + // "Power factor output. This field is a signed 11-bit fixed point number + // with 10 fractional bits. It ranges from –1 to ~1 with a step + // size of 2^-10." + + union + { + int16_t Signed; + uint16_t unSigned; + } signedUnsigned; // Avoid any ambiguity when casting to signed int + + signedUnsigned.unSigned = store.data.bits.pfactor << 5; // Move 11-bit number into 16-bits (signed) + + float pfactor = (float)signedUnsigned.Signed / 32768.0; // Convert to +/- 1 + if (_printDebug == true) + { + _debugPort->print(F("readPowerFactor: pfactor: 0x")); + _debugPort->println(store.data.bits.pfactor, HEX); + _debugPort->print(F("readPowerFactor: pfactor is ")); + _debugPort->println(pfactor); + } + *pFactor = pfactor; + + // Extract posangle and pospf + *posangle = store.data.bits.posangle & 0x1; + *pospf = store.data.bits.pospf & 0x1; + + return (error); +} + // Read volatile registers 0x2A and 0x2C. Return the vInst (Volts), iInst (Amps) and pInst (VAR). ACS37800ERR ACS37800::readInstantaneous(float *vInst, float *iInst, float *pInst) { @@ -621,9 +794,10 @@ ACS37800ERR ACS37800::readInstantaneous(float *vInst, float *iInst, float *pInst { _debugPort->print(F("readInstantaneous: vcodes: 0x")); _debugPort->println(signedUnsigned.unSigned, HEX); - _debugPort->print(F("readInstantaneous: volts (mV, before correction) is ")); + _debugPort->print(F("readInstantaneous: volts (LSB, before correction) is ")); _debugPort->println(volts); } + // Datasheet says "Voltage Channel ADC Sensitivity: 110 LSB/mV" volts /= 27500.0; //Convert from codes to the fraction of ADC Full Scale volts *= 250; //Convert to mV (Differential Input Range is +/- 250mV) volts /= 1000; //Convert to Volts @@ -645,7 +819,7 @@ ACS37800ERR ACS37800::readInstantaneous(float *vInst, float *iInst, float *pInst { _debugPort->print(F("readInstantaneous: icodes: 0x")); _debugPort->println(signedUnsigned.unSigned, HEX); - _debugPort->print(F("readInstantaneous: amps (A, before correction) is ")); + _debugPort->print(F("readInstantaneous: amps (LSB, before correction) is ")); _debugPort->println(amps); } amps /= 27500.0; //Convert from codes to the fraction of ADC Full Scale @@ -670,7 +844,7 @@ ACS37800ERR ACS37800::readInstantaneous(float *vInst, float *iInst, float *pInst return (error); // Bail } - //Extract pinstant as signed int. Convert to VAR + //Extract pinstant as signed int. Convert to W //pinstant as actually int16_t but is stored in a uint32_t as a 16-bit bitfield signedUnsigned.unSigned = pstore.data.bits.pinstant; float power = (float)signedUnsigned.Signed; @@ -678,7 +852,7 @@ ACS37800ERR ACS37800::readInstantaneous(float *vInst, float *iInst, float *pInst { _debugPort->print(F("readInstantaneous: pinstant: 0x")); _debugPort->println(signedUnsigned.unSigned, HEX); - _debugPort->print(F("readInstantaneous: power (mW, before correction) is ")); + _debugPort->print(F("readInstantaneous: power (LSB, before correction) is ")); _debugPort->println(power); } //Datasheet says: 3.08 LSB/mW for the 30A version and 1.03 LSB/mW for the 90A version diff --git a/src/SparkFun_ACS37800_Arduino_Library.h b/src/SparkFun_ACS37800_Arduino_Library.h index e216b1d..f7cce4b 100644 --- a/src/SparkFun_ACS37800_Arduino_Library.h +++ b/src/SparkFun_ACS37800_Arduino_Library.h @@ -385,7 +385,7 @@ class ACS37800 //The user can also specify / override the ACS37800's current sensing range //ACS37800KMACTR-030B3-I2C is a 30.0 Amp part - Default - as used on the SparkFun Qwiic Power Meter //ACS37800KMACTR-090B3-I2C is a 90.0 Amp part - boolean begin(uint8_t address = ACS37800_DEFAULT_I2C_ADDRESS, TwoWire &wirePort = Wire); //If user doesn't specify then Wire will be used + bool begin(uint8_t address = ACS37800_DEFAULT_I2C_ADDRESS, TwoWire &wirePort = Wire); //If user doesn't specify then Wire will be used //Debugging void enableDebugging(Stream &debugPort = Serial); //Turn on debug printing. If user doesn't specify then Serial will be used. @@ -401,16 +401,18 @@ class ACS37800 //Configurable Settings //By default, settings are written to the shadow registers only. Set _eeprom to true to write to EEPROM too. //Set/Get the number of samples for RMS calculations. Bypass_N_Enable must be set/true for this to have effect. - ACS37800ERR setNumberOfSamples(uint32_t numberOfSamples, boolean _eeprom = false); + ACS37800ERR setNumberOfSamples(uint32_t numberOfSamples, bool _eeprom = false); ACS37800ERR getNumberOfSamples(uint32_t *numberOfSamples); // Read and return the number of samples (from _shadow_ memory) //Set/Clear the Bypass_N_Enable flag - ACS37800ERR setBypassNenable(boolean bypass, boolean _eeprom = false); - ACS37800ERR getBypassNenable(boolean *bypass); // Read and return the bypass_n_en flag (from _shadow_ memory) + ACS37800ERR setBypassNenable(bool bypass, bool _eeprom = false); + ACS37800ERR getBypassNenable(bool *bypass); // Read and return the bypass_n_en flag (from _shadow_ memory) // Read and return the gain (from _shadow_ memory) ACS37800ERR getCurrentCoarseGain(float *currentCoarseGain); //Basic methods for accessing the volatile registers ACS37800ERR readRMS(float *vRMS, float *iRMS); // Read volatile register 0x20. Return the vRMS and iRMS. + ACS37800ERR readPowerActiveReactive(float *pActive, float *pReactive); // Read volatile register 0x21. Return the pactive and pimag (reactive) + ACS37800ERR readPowerFactor(float *pApparent, float *pFactor, bool *posangle, bool *pospf); // Read volatile register 0x22. Return the apparent power, power factor, leading / lagging, generated / consumed ACS37800ERR readInstantaneous(float *vInst, float *iInst, float *pInst); // Read volatile registers 0x2A and 0x2C. Return the vInst, iInst and pInst. ACS37800ERR readErrorFlags(ACS37800_REGISTER_2D_t *errorFlags); // Read volatile register 0x2D. Return its contents in errorFlags. @@ -426,7 +428,7 @@ class ACS37800 //Debug Stream *_debugPort; //The stream to send debug messages to if enabled. Usually Serial. - boolean _printDebug = false; //Flag to print debugging variables + bool _printDebug = false; //Flag to print debugging variables //ACS37800's I2C address uint8_t _ACS37800Address = ACS37800_DEFAULT_I2C_ADDRESS;