Created
December 30, 2015 16:13
-
-
Save phec/7d069d6ec21cbd11e17d to your computer and use it in GitHub Desktop.
Updated particle.io version of emonLib (photon compatible and with waveform saved)
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
/* | |
Emon.cpp - Library for openenergymonitor | |
Created by Trystan Lea, April 27 2010 | |
GNU GPL | |
modified to use up to 12 bits ADC resolution (ex. Arduino Due) | |
by [email protected] 26.12.2013 | |
Low Pass filter for offset removal replaces HP filter 1/1/2015 - RW | |
Save waveform and adapt for Particle Core/Photon 6/9/15 PHEC | |
*/ | |
//#include "WProgram.h" un-comment for use on older versions of Arduino IDE | |
#include "EmonLib.h" | |
//-------------------------------------------------------------------------------------- | |
// Sets the pins to be used for voltage and current sensors | |
//-------------------------------------------------------------------------------------- | |
void EnergyMonitor::voltage(unsigned int _inPinV, double _VCAL, double _PHASECAL) | |
{ | |
inPinV = _inPinV; | |
VCAL = _VCAL; | |
PHASECAL = _PHASECAL; | |
offsetV = ADC_COUNTS>>1; | |
} | |
void EnergyMonitor::current(unsigned int _inPinI, double _ICAL) | |
{ | |
inPinI = _inPinI; | |
ICAL = _ICAL; | |
offsetI = ADC_COUNTS>>1; | |
} | |
//-------------------------------------------------------------------------------------- | |
// Sets the pins to be used for voltage and current sensors | |
//-------------------------------------------------------------------------------------- | |
void EnergyMonitor::voltage(unsigned int _inPinV, float _VCAL, float _PHASECAL) | |
{ | |
inPinV = _inPinV; | |
VCAL = _VCAL; | |
PHASECAL = _PHASECAL; | |
offsetV = ADC_COUNTS>>1; | |
} | |
void EnergyMonitor::current(unsigned int _inPinI, float _ICAL) | |
{ | |
inPinI = _inPinI; | |
ICAL = _ICAL; | |
offsetI = ADC_COUNTS>>1; | |
} | |
//-------------------------------------------------------------------------------------- | |
// emon_calc procedure | |
// Calculates realPower,apparentPower,powerFactor,Vrms,Irms,kWh increment | |
// From a sample window of the mains AC voltage and current. | |
// The Sample window length is defined by the number of half wavelengths or crossings we choose to measure. | |
//-------------------------------------------------------------------------------------- | |
void EnergyMonitor::calcVI(unsigned int crossings, unsigned int timeout) | |
{ | |
int SupplyVoltage=3300; | |
//*** Photon the following variables are defined as globals for diagnostics *** | |
// unsigned int crossCount = 0; //Used to measure number of times threshold is crossed. | |
// unsigned int numberOfSamples = 0; //This is now incremented | |
crossCount = 0; //Used to measure number of times threshold is crossed. | |
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 = analogRead(inPinV); //using the voltage waveform | |
if ((startV < (ADC_COUNTS*0.55)) && (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 = analogRead(inPinV); //Read in raw voltage signal | |
sampleI = analogRead(inPinI); //Read in raw current signal | |
delayMicroseconds(500); // *** Photon add delay to aid Phase correction | |
//----------------------------------------------------------------------------- | |
// 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); | |
// *** Photon Save waveforms (use phase shifted values so phase correction can be checked) | |
Vwaveform[numberOfSamples%256]=uint8_t((phaseShiftedV+2048)/16);//Particle save waveform | |
Iwaveform[numberOfSamples%256]=uint8_t((filteredI+1024)/8); //Particle save waveform | |
//----------------------------------------------------------------------------- | |
// 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++; | |
*/ | |
// *** Photon simplify - just count the number of positive going crossings of the filtered voltage *** | |
if((filteredV>0)&&(lastFilteredV<0)) crossCount++;//*** Photon This always ends on upward transition which makes for a consistent V graph | |
} | |
//------------------------------------------------------------------------------------------------------------------------- | |
// 3) Post loop calculations | |
//------------------------------------------------------------------------------------------------------------------------- | |
//Calculation of the root of the mean of the voltage and current squared (rms) | |
//Calibration coefficients applied. | |
double V_RATIO = VCAL *((SupplyVoltage/1000.0) / (ADC_COUNTS)); | |
Vrms = V_RATIO * sqrt(sumV / numberOfSamples); | |
double I_RATIO = ICAL *((SupplyVoltage/1000.0) / (ADC_COUNTS)); | |
Irms = I_RATIO * sqrt(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; | |
//-------------------------------------------------------------------------------------- | |
} | |
//-------------------------------------------------------------------------------------- | |
double EnergyMonitor::calcIrms(unsigned int Number_of_Samples) | |
{ | |
#if defined emonTxV3 | |
int SupplyVoltage=3300; | |
#else | |
int SupplyVoltage = readVcc(); | |
#endif | |
for (unsigned int n = 0; n < Number_of_Samples; n++) | |
{ | |
sampleI = analogRead(inPinI); | |
// Digital low pass filter extracts the 2.5 V or 1.65 V dc offset, | |
// then subtract this - signal is now centered on 0 counts. | |
offsetI = (offsetI + (sampleI-offsetI)/1024); | |
filteredI = sampleI - offsetI; | |
// Root-mean-square method current | |
// 1) square current values | |
sqI = filteredI * filteredI; | |
// 2) sum | |
sumI += sqI; | |
} | |
double I_RATIO = ICAL *((SupplyVoltage/1000.0) / (ADC_COUNTS)); | |
Irms = I_RATIO * sqrt(sumI / Number_of_Samples); | |
//Reset accumulators | |
sumI = 0; | |
//-------------------------------------------------------------------------------------- | |
return Irms; | |
} | |
void EnergyMonitor::serialprint() | |
{ | |
Serial.print(realPower); | |
Serial.print(' '); | |
Serial.print(apparentPower); | |
Serial.print(' '); | |
Serial.print(Vrms); | |
Serial.print(' '); | |
Serial.print(Irms); | |
Serial.print(' '); | |
Serial.print(powerFactor); | |
Serial.println(' '); | |
delay(100); | |
} | |
//thanks to http://hacking.majenko.co.uk/making-accurate-adc-readings-on-arduino | |
//and Jérôme who alerted us to http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/ | |
long EnergyMonitor::readVcc() { | |
long result; | |
//not used on emonTx V3 - as Vcc is always 3.3V - eliminates bandgap error and need for calibration http://harizanov.com/2013/09/thoughts-on-avr-adc-accuracy/ | |
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__) || defined (__AVR_ATmega328P__) | |
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); | |
#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_AT90USB1286__) | |
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); | |
ADCSRB &= ~_BV(MUX5); // Without this the function always returns -1 on the ATmega2560 http://openenergymonitor.org/emon/node/2253#comment-11432 | |
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) | |
ADMUX = _BV(MUX5) | _BV(MUX0); | |
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) | |
ADMUX = _BV(MUX3) | _BV(MUX2); | |
#endif | |
#if defined(__AVR__) | |
delay(2); // Wait for Vref to settle | |
ADCSRA |= _BV(ADSC); // Convert | |
while (bit_is_set(ADCSRA,ADSC)); | |
result = ADCL; | |
result |= ADCH<<8; | |
result = READVCC_CALIBRATION_CONST / result; //1100mV*1024 ADC steps http://openenergymonitor.org/emon/node/1186 | |
return result; | |
#elif defined(__arm__) | |
return (3300); //Arduino Due | |
#else | |
return (3300); //Guess that other un-supported architectures will be running a 3.3V! | |
#endif | |
} | |
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
/* | |
Emon.h - Library for openenergymonitor | |
Created by Trystan Lea, April 27 2010 | |
GNU GPL | |
modified to use up to 12 bits ADC resolution (ex. Arduino Due) | |
by [email protected] 26.12.2013 | |
Low Pass filter for offset removal replaces HP filter 1/1/2015 - RW | |
Access to V and I waveforms added 6/9/15 - PHEC | |
Adapted for Particle Core and Particle Photon 6/9/15 - PHEC | |
doubles changed to float to save RAM (not an issue with Photon) | |
*/ | |
#ifndef EmonLib_h | |
#define EmonLib_h | |
#if defined(ARDUINO) && ARDUINO >= 100 | |
#include "Arduino.h" | |
#else | |
// Particle headers | |
#include "application.h" | |
#include "math.h" | |
#endif | |
//***Photon defaults to 12 bits*** | |
#define ADC_BITS 12 | |
#define ADC_COUNTS (1<<ADC_BITS) | |
class EnergyMonitor | |
{ | |
public: | |
void voltage(unsigned int _inPinV, float _VCAL, float _PHASECAL); | |
void current(unsigned int _inPinI, float _ICAL); | |
void voltageTX(float _VCAL, float _PHASECAL); | |
void currentTX(unsigned int _channel, float _ICAL); | |
void calcVI(unsigned int crossings, unsigned int timeout); | |
float calcIrms(unsigned int NUMBER_OF_SAMPLES); | |
void serialprint(); | |
long readVcc(); | |
//Useful value variables | |
float realPower, | |
apparentPower, | |
powerFactor, | |
Vrms, | |
Irms; | |
//*** Photon Expose some more useful variables *** | |
unsigned int numberOfSamples; | |
unsigned int crossCount; | |
uint8_t Vwaveform[256]; | |
uint8_t Iwaveform[256]; | |
private: | |
//Set Voltage and current input pins | |
unsigned int inPinV; | |
unsigned int inPinI; | |
//Calibration coefficients | |
//These need to be set in order to obtain accurate results | |
float VCAL; | |
float ICAL; | |
float PHASECAL; | |
//-------------------------------------------------------------------------------------- | |
// Variable declaration for emon_calc procedure | |
//-------------------------------------------------------------------------------------- | |
int sampleV; //sample_ holds the raw analog read value | |
int sampleI; | |
float lastFilteredV,filteredV; //Filtered_ is the raw analog value minus the DC offset | |
float filteredI; | |
float offsetV; //Low-pass filter output | |
float offsetI; //Low-pass filter output | |
float phaseShiftedV; //Holds the calibrated phase shifted voltage. | |
float sqV,sumV,sqI,sumI,instP,sumP; //sq = squared, sum = Sum, inst = instantaneous | |
int startV; //Instantaneous voltage at start of sample window. | |
boolean lastVCross, checkVCross; //Used to measure number of times threshold is crossed. | |
}; | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment