Last active
March 17, 2016 14:13
-
-
Save whatnick/cb84902719187f77d2e7 to your computer and use it in GitHub Desktop.
Real Power meter with OLED display, no wifi
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 HTTP POST request to thingspeak server. | |
* 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-GFX-Library | |
* - https://github.com/adafruit/Adafruit_SSD1306 | |
* | |
* designed to run directly on esp8266-12 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 | |
*/ | |
#include <Wire.h> | |
#include <Adafruit_ADS1015.h> | |
#include <SPI.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.65 | |
#define ICAL 0.02 | |
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 | |
//----------------------------------------------------------------------------- | |
sampleV = ads.readADC_Differential_2_3(); //Read in raw voltage signal | |
sampleI = ads.readADC_Differential_0_1(); //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; | |
powerFactor=realPower / apparentPower; | |
//Reset accumulators | |
sumV = 0; | |
sumI = 0; | |
sumP = 0; | |
//-------------------------------------------------------------------------------------- | |
} | |
void setup() { | |
Serial.begin(115200); | |
delay(10); | |
// We start by connecting to a WiFi network | |
//Serial.println(); | |
//Serial.println(); | |
//Serial.print("Connecting to "); | |
//Serial.println(ssid); | |
//Serial.println(""); | |
//Serial.println("WiFi connected"); | |
//Serial.println("IP address: "); | |
//Serial.println(WiFi.localIP()); | |
ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 0.125mV | |
ads.begin(); | |
Wire.begin(); | |
// by default, we'll generate the high voltage from the 3.3v line internally! (neat!) | |
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); | |
//ESP.wdtEnable(WDTO_8S); // Enabling Watchdog | |
} | |
void loop() { | |
calcVI(20,2000); | |
Serial.print("Real Power:"); | |
Serial.println(realPower); | |
Serial.print("Irms:"); | |
Serial.println(Irms); | |
Serial.print("Vrms:"); | |
Serial.println(Vrms); | |
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(100); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment