Skip to content

Instantly share code, notes, and snippets.

@whatnick
Last active December 29, 2022 12:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save whatnick/3de9bf9690113e3c56f5676a901b685f to your computer and use it in GitHub Desktop.
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 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