Last active
December 29, 2022 12:52
-
-
Save whatnick/3de9bf9690113e3c56f5676a901b685f to your computer and use it in GitHub Desktop.
Real Power Measurement over Serial port for classice through-hole ADS1115 + NodeMCU Energy Monitor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This sketch sends ads1115 current sensor data via out over serial port. | |
* It needs the following libraries to work (besides the esp8266 standard libraries supplied with the IDE): | |
* | |
* - https://github.com/adafruit/Adafruit_ADS1X15 | |
* - https://github.com/adafruit/Adafruit_SSD1306 | |
* - https://github.com/adafruit/Adafruit-GFX-Library | |
* | |
* The above libraries can be directly installed via the Arduino IDE | |
* | |
* designed to run directly on esp8266-01 module, to where it can be uploaded using this marvelous piece of software: | |
* | |
* https://github.com/esp8266/Arduino | |
* | |
* 2015 Tisham Dhar | |
* licensed under GNU GPL | |
*/ | |
#if PLATFORM_ID == 88 | |
#include "application.h" | |
#else | |
#include <Wire.h> | |
#include <SPI.h> | |
#endif | |
//#define Serial SerialUSB | |
//#define Serial Serial1 | |
/* | |
#if defined(ARDUINO) | |
SYSTEM_MODE(MANUAL);//do not connect to cloud | |
#else | |
SYSTEM_MODE(AUTOMATIC);//connect to cloud | |
#endif | |
*/ | |
#include <Adafruit_ADS1015.h> | |
#include <Adafruit_GFX.h> | |
#include <Adafruit_SSD1306.h> | |
#define OLED_RESET 16 | |
Adafruit_SSD1306 display(OLED_RESET); | |
#if (SSD1306_LCDHEIGHT != 32) | |
#error("Height incorrect, please fix Adafruit_SSD1306.h!"); | |
#endif | |
#ifdef ESP8266 | |
#include <pgmspace.h> | |
#else | |
#include <avr/pgmspace.h> | |
#endif | |
Adafruit_ADS1115 ads; /* Use this for the 16-bit version */ | |
//Maximum value of ADS | |
#define ADC_COUNTS 32768 | |
#define PHASECAL 1.7 | |
#define VCAL 0.92807 | |
#define ICAL 0.27666 | |
#define PCAL 0.85 | |
double filteredI; | |
double lastFilteredV,filteredV; //Filtered_ is the raw analog value minus the DC offset | |
int sampleV; //sample_ holds the raw analog read value | |
int sampleI; | |
double offsetV; //Low-pass filter output | |
double offsetI; //Low-pass filter output | |
double realPower, | |
apparentPower, | |
powerFactor, | |
Vrms, | |
Irms; | |
double phaseShiftedV; //Holds the calibrated phase shifted voltage. | |
int startV; //Instantaneous voltage at start of sample window. | |
double sqV,sumV,sqI,sumI,instP,sumP; //sq = squared, sum = Sum, inst = instantaneous | |
boolean lastVCross, checkVCross; //Used to measure number of times threshold is crossed. | |
double squareRoot(double fg) | |
{ | |
double n = fg / 2.0; | |
double lstX = 0.0; | |
while (n != lstX) | |
{ | |
lstX = n; | |
n = (n + fg / n) / 2.0; | |
} | |
return n; | |
} | |
void calcVI(unsigned int crossings, unsigned int timeout) | |
{ | |
unsigned int crossCount = 0; //Used to measure number of times threshold is crossed. | |
unsigned int numberOfSamples = 0; //This is now incremented | |
//------------------------------------------------------------------------------------------------------------------------- | |
// 1) Waits for the waveform to be close to 'zero' (mid-scale adc) part in sin curve. | |
//------------------------------------------------------------------------------------------------------------------------- | |
boolean st=false; //an indicator to exit the while loop | |
unsigned long start = millis(); //millis()-start makes sure it doesnt get stuck in the loop if there is an error. | |
while(st==false) //the while loop... | |
{ | |
startV = ads.readADC_Differential_2_3(); //using the voltage waveform | |
if ((abs(startV) < (ADC_COUNTS*0.55)) && (abs(startV) > (ADC_COUNTS*0.45))) st=true; //check its within range | |
if ((millis()-start)>timeout) st = true; | |
} | |
//------------------------------------------------------------------------------------------------------------------------- | |
// 2) Main measurement loop | |
//------------------------------------------------------------------------------------------------------------------------- | |
start = millis(); | |
while ((crossCount < crossings) && ((millis()-start)<timeout)) | |
{ | |
numberOfSamples++; //Count number of times looped. | |
lastFilteredV = filteredV; //Used for delay/phase compensation | |
//----------------------------------------------------------------------------- | |
// A) Read in raw voltage and current samples | |
//----------------------------------------------------------------------------- | |
sampleI = ads.readADC_Differential_0_1(); //Read in raw voltage signal | |
sampleV = ads.readADC_Differential_2_3(); //Read in raw current signal | |
//----------------------------------------------------------------------------- | |
// B) Apply digital low pass filters to extract the 2.5 V or 1.65 V dc offset, | |
// then subtract this - signal is now centred on 0 counts. | |
//----------------------------------------------------------------------------- | |
offsetV = offsetV + ((sampleV-offsetV)/1024); | |
filteredV = sampleV - offsetV; | |
offsetI = offsetI + ((sampleI-offsetI)/1024); | |
filteredI = sampleI - offsetI; | |
//----------------------------------------------------------------------------- | |
// C) Root-mean-square method voltage | |
//----------------------------------------------------------------------------- | |
sqV= filteredV * filteredV; //1) square voltage values | |
sumV += sqV; //2) sum | |
//----------------------------------------------------------------------------- | |
// D) Root-mean-square method current | |
//----------------------------------------------------------------------------- | |
sqI = filteredI * filteredI; //1) square current values | |
sumI += sqI; //2) sum | |
//----------------------------------------------------------------------------- | |
// E) Phase calibration | |
//----------------------------------------------------------------------------- | |
phaseShiftedV = lastFilteredV + PHASECAL * (filteredV - lastFilteredV); | |
//----------------------------------------------------------------------------- | |
// F) Instantaneous power calc | |
//----------------------------------------------------------------------------- | |
instP = phaseShiftedV * filteredI; //Instantaneous Power | |
sumP +=instP; //Sum | |
//----------------------------------------------------------------------------- | |
// G) Find the number of times the voltage has crossed the initial voltage | |
// - every 2 crosses we will have sampled 1 wavelength | |
// - so this method allows us to sample an integer number of half wavelengths which increases accuracy | |
//----------------------------------------------------------------------------- | |
lastVCross = checkVCross; | |
if (sampleV > startV) checkVCross = true; | |
else checkVCross = false; | |
if (numberOfSamples==1) lastVCross = checkVCross; | |
if (lastVCross != checkVCross) crossCount++; | |
} | |
//------------------------------------------------------------------------------------------------------------------------- | |
// 3) Post loop calculations | |
//------------------------------------------------------------------------------------------------------------------------- | |
//Calculation of the root of the mean of the voltage and current squared (rms) | |
//Calibration coefficients applied. | |
float multiplier = 0.125F; /* ADS1115 @ +/- 4.096V gain (16-bit results) */ | |
double V_RATIO = VCAL * multiplier; | |
Vrms = V_RATIO * squareRoot(sumV / numberOfSamples); | |
double I_RATIO = ICAL * multiplier; | |
Irms = I_RATIO * squareRoot(sumI / numberOfSamples); | |
//Calculation power values | |
realPower = V_RATIO * I_RATIO * sumP / numberOfSamples; | |
apparentPower = Vrms * Irms; | |
//If real power is too low power factor is unreliable (set to zero) | |
if(abs(realPower) > 5 ) | |
{ | |
powerFactor=realPower / (apparentPower*PCAL); | |
} | |
else | |
{ | |
powerFactor = 0.0; | |
} | |
//Reset accumulators | |
sumV = 0; | |
sumI = 0; | |
sumP = 0; | |
//-------------------------------------------------------------------------------------- | |
} | |
void setup() { | |
Serial.begin(115200); | |
delay(10); | |
ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 0.125mV | |
ads.begin(); | |
Wire.begin(); | |
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 128x32) | |
// init done | |
// Clear the buffer. | |
display.clearDisplay(); | |
display.display(); | |
delay(1000); | |
display.setTextSize(1); | |
display.setTextColor(WHITE); | |
} | |
void loop() { | |
calcVI(20,2000); | |
Serial.print(realPower); | |
Serial.print(","); | |
Serial.print(Irms); | |
Serial.print(","); | |
Serial.print(Vrms); | |
Serial.print(","); | |
Serial.println(powerFactor); | |
display.clearDisplay(); | |
display.setCursor(0,0); | |
display.print("Vrms: "); | |
display.println(Vrms); | |
display.print("Irms: "); | |
display.println(Irms); | |
display.print("Power: "); | |
display.println(realPower); | |
display.print("p.f.: "); | |
display.println(powerFactor); | |
display.display(); | |
delay(1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment