0% found this document useful (0 votes)
105 views

Arduino Wattmeter v1

This document contains code for an Arduino-based wattmeter. It defines variables and constants for calibrating the sensors, averaging sensor readings, measuring voltage, current and power over time, and displaying the measurements on an LCD screen. Primary and secondary averaging is used to filter sensor noise from the voltage, current and power readings. Calculated values like RMS, maximum and minimum are regularly updated and shown on the LCD.
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
105 views

Arduino Wattmeter v1

This document contains code for an Arduino-based wattmeter. It defines variables and constants for calibrating the sensors, averaging sensor readings, measuring voltage, current and power over time, and displaying the measurements on an LCD screen. Primary and secondary averaging is used to filter sensor noise from the voltage, current and power readings. Calculated values like RMS, maximum and minimum are regularly updated and shown on the LCD.
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 34

/************************************/

/*** Arduino Wattmeter v1.0 ***/


/*** Board: Arduino Nano 3.0 ***/
/*** By: Freddy Alferink ***/
/*** http://meettechniek.info ***/
/*** May 2014 ***/
/************************************/

/*********** Calibration & Hardware Data ***********/


float Vdiv = 101.0; // Voltage conversion factor
float Cdiv = 10.0; // Current conversion factor
const byte LCDlines = 2; // LCD: Number of lines
const byte LCDwidth = 16; // LCD: Number of character per line
/***************************************************/

const float adcSense = 1.074219e-3; // ADC conversion factor volt/bit (1.1V


/ 1024)
float Vscale = 1.0; // Voltage scale
float Cscale = 1.0; // Current scale
byte muxCnt = 4; // Analog multiplexer start
int nullVal = 512; // Measured null value
boolean overFlowVolt = false; // Voltage overflow flag
boolean overFlowCurr = false; // Current overflow flag
unsigned int vCnt = 0; // Values counter
/*** Voltage frequency measurement ***/
int vfmUpperTh = 10; // Upper threshold voltage
int vfmLowerTh = -10; // Lower threshold voltage
boolean vfmDir = false; // Direction voltage
unsigned int vfmPeriods = 7; // Periods counter
unsigned int vfmPeriodsTime = 1000; // Synchronized with periods time
unsigned int vfmTime = 1000; // Time counter
/*** Current frequency measurement ***/
int cfmUpperTh = 10; // Upper threshold current
int cfmLowerTh = -10; // Lower threshold current
boolean cfmDir = false; // Direction current
unsigned int cfmPeriods = 5; // Periods counter
unsigned int cfmPeriodsTime = 1000; // Synchronized with periods time
unsigned int cfmTime = 1000; // Time counter
/*** Primary averaging ***/
int primCnt = 0; // Primary averaging counter
boolean primReady = false; // Primary averaging complete flag
int adcVolt; // Copy ADC voltage
const unsigned int primAvLength = 64; // Number of samples for primary
averaging
long primMeanVolt = 0; // Cumulative ADC mean voltage
long primMeanVoltCopy = 0; // Averaged primary mean voltage
long primSquaredVolt = 0; // Cumulative ADC squared voltage
long primSquaredVoltCopy = 0; // Averaged primary squared voltage
long primMaxVolt = 0; // Highest measured ADC voltage
long primMinVolt = 0; // Lowest measured ADC voltage
int adcCurr; // Copy ADC current
long primMeanCurr = 0; // Cumulative ADC mean current
long primMeanCurrCopy = 0; // Averaged primary current
long primSquaredCurr = 0; // Cumulative ADC squared current
long primSquaredCurrCopy = 0; // Averaged primary squared current
long primMaxCurr = 0; // Highest measured ADC current
long primMinCurr = 0; // Lowest measured ADC current
long primMeanPow = 0; // Cumulative ADC mean power
long primMeanPowCopy = 0; // Averaged primary mean power
long primMaxPow = 0; // Highest measured ADC power
long primMinPow = 0; // Lowest measured ADC power
/*** Secondary averaging ***/
unsigned int secArrCnt = 0;
const unsigned int secAvLength = 50;
long secMeanVoltArr[secAvLength]; // Mean Voltage secondary averageing
array
long secSquaredVoltArr[secAvLength]; // Squared Voltage secondary averageing
array
long secMeanCurrArr[secAvLength]; // Mean Current secondary averageing
array
long secSquaredCurrArr[secAvLength]; // Squared Current secondary averageing
array
long secMeanPowArr[secAvLength]; // Real Power secondary averageing array
long secMeanVolt = 0; // Result secondary averaging mean
voltage
long secSquaredVolt = 0; // Result secondary averaging squared
voltage
long secMeanCurr = 0; // Result secondary averaging mean
current
long secSquaredCurr = 0; // Result secondary averaging squared
current
long secMeanPow = 0; // Result secondary averaging mean power
/*** Cumulative values ***/
byte tFlux[8]; // 64 bits integrated ADC voltage
byte tCharge[8]; // 64 bits integrated ADC current
byte tEnergy[8]; // 64 bits integrated ADC power
unsigned int preTimeCnt = 0; // Time prescaler
unsigned long timeCnt = 0; // Time seconds counter
/*** ***/
float totAverage = 10.0; // Total averaging length (primary *
secondary)
float fSample = 10.0; // ADC channel sample frequency
const byte LCDlinePos[4] = {0x80,0xC0,0x94,0xD4}; // First char position for
each line (as DDRAM instruction)
byte lineSelect = 0; // The selected dsplay line
byte paramPointers[4] = {0,7,13,18}; // Parameter pointer for each display
line
char* paramLabels[] = {"Vmean ","Vrms ","Vsdef ","Vmax ","Vmin ","Flux
","f (V)","Imean ","Irms ","Isdef ","Imax ","Imin ","Charge","f (I)
","Preal ","S * ","Q * ","Pmax ","Pmin ","Energy","\x01 * ","time
"};
char* paramUnits[] =
{"V","V","V","V","V","Vs","Hz","A","A","A","A","A","C","Hz","W","VA","var","W
","W","J","\x02","s"};
float paramValues[] =
{0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
0.0,0.0,0.0};
int paramRange[] = {0,0,0,0,0,99,99,0,0,0,0,0,99,99,99,0,0,0,0,99,99,100};
// 99=autorange, 100=int-number
int paramDigits[] = {4,4,4,3,3,5,4,4,4,4,3,3,5,4,4,4,4,3,3,5,3,7}; //
number of digits
byte antiBounceCnt = 0; // Buttons anti bounce counter
boolean buttonState = false; // Button is pressed
unsigned int buttonPushed = 0; // Pressed buttons
void setup() {
MCUCR &= 0xEF; // Enable pull-up resistors
DDRB &= 0xE1; // Buttons on D9...12 are inputs
DDRB |= 0x20; // Overflow LED on D13 is output
PORTB |= 0x1E; // Pull-up resistor for the buttons
bitClear(PORTB, 5); // Overflow LED off
initiateADC(); // initiate AD converter
lcdInitiate(); // initiate LCD
lcdCustomChars(); // Make custom characters
lcdWrite(true, LCDlinePos[0]); // LCD cursor first line position 0
lcdPrintString("Universal power");
lcdWrite(true, LCDlinePos[1]); // LCD cursor second line position 0
lcdPrintString("meter v1.0");
for(int md=0; md<100; md++) { // Wait 2 seconds
muDelay(20000);
}
lcdWrite(true, LCDlinePos[0]); // LCD cursor first line position 0
lcdPrintString("meettechniek ");
lcdWrite(true, LCDlinePos[1]); // LCD cursor second line position 0
lcdPrintString(" .info");
for(int md=0; md<100; md++) { // Wait 2 seconds
muDelay(20000);
}
setProperties(); // Set properties and Clear measured
values
updateParamLabels(); // Write Parameter labels to LCD
updateParamValues(); // Write parameter values to LCD
sei(); // set interrupt flag
}

void loop() {
if (primReady == true) {
/*** Secondary avearaging Mean Volt ***/
secMeanVolt -= secMeanVoltArr[secArrCnt]; // Subtract oldest
value strored in array from average
secMeanVolt += primMeanVoltCopy; // Add newest value
to average
secMeanVoltArr[secArrCnt] = primMeanVoltCopy; // Store newest
value in array
/*** Secondary avearaging Mean Squared Volt ***/
secSquaredVolt -= secSquaredVoltArr[secArrCnt]; // the same for
squared voltage
secSquaredVolt += primSquaredVoltCopy;
secSquaredVoltArr[secArrCnt] = primSquaredVoltCopy;
/*** Secondary avearaging Mean Current ***/
secMeanCurr -= secMeanCurrArr[secArrCnt]; // and mean current
secMeanCurr += primMeanCurrCopy;
secMeanCurrArr[secArrCnt] = primMeanCurrCopy;
/*** Secondary avearaging Mean Squared Current ***/
secSquaredCurr -= secSquaredCurrArr[secArrCnt]; // squared current
secSquaredCurr += primSquaredCurrCopy;
secSquaredCurrArr[secArrCnt] = primSquaredCurrCopy;
/*** Secondary avearaging Mean Power ***/
secMeanPow -= secMeanPowArr[secArrCnt]; // and power
secMeanPow += primMeanPowCopy;
secMeanPowArr[secArrCnt] = primMeanPowCopy;
/*** Array Pointer ***/
secArrCnt++; // Increase secondary
averaging array pointer
if(secArrCnt >= secAvLength) {
secArrCnt = 0;
}
/*** Cumulative values ***/
extraLongAddLong(tFlux, primMeanVoltCopy); // Add primary averaged
voltage to total Flux
extraLongAddLong(tCharge, primMeanCurrCopy); // Add primary averaged
current to total Charge
extraLongAddLong(tEnergy, primMeanPowCopy); // Add primary averaged
power to total Energy
primReady = false;
}
/*** Update display ***/
if (vCnt > 1000) { // After 1000 channel samples refresh values
on display
vCnt = 0;
/*** Calculate values Voltage ***/
paramValues[0] = float(secMeanVolt) * Vscale / totAverage;
// calc. mean Voltage
paramValues[1] = sqrt(float(secSquaredVolt) / totAverage) * Vscale;
// calc. RMS Voltage
paramValues[2] = sqrt((paramValues[1]*paramValues[1])-
(paramValues[0]*paramValues[0])); // calc. standard deviation Voltage:
Vsdev=sqrt(Vrms^2 - Vmean^2)
paramValues[3] = float(primMaxVolt) * Vscale;
// Max voltage
paramValues[4] = float(primMinVolt) * Vscale;
// Min voltage
/*** Calculate values Current ***/
paramValues[7] = float(secMeanCurr) * Cscale / totAverage;
// calc. mean Current
paramValues[8] = sqrt(float(secSquaredCurr) / totAverage) * Cscale;
// calc. RMS Current
paramValues[9] = sqrt((paramValues[8]*paramValues[8])-
(paramValues[7]*paramValues[7])); // calc. standard deviation Current:
Csdev=sqrt(Crms^2 - Cmean^2)
paramValues[10] = float(primMaxCurr) * Cscale;
// Max current
paramValues[11] = float(primMinCurr) * Cscale;
// Min current
/*** Calculate values Power ***/
paramValues[14] = float(secMeanPow) * Vscale * Cscale / totAverage;
// Real Power
paramValues[17] = float(primMaxPow) * Vscale * Cscale;
// Max Power
paramValues[18] = float(primMinPow) * Vscale * Cscale;
// Min Power
paramValues[15] = paramValues[1] * paramValues[8];
// Apparent Power = Urms * Irms
paramValues[16] = sqrt((paramValues[15]*paramValues[15])-
(paramValues[14]*paramValues[14])); // Reactive power
/*** Flux, Charge, Energy ***/
paramValues[5] = extraLongToFloat(tFlux) * Vscale / fSample;
// Flux
paramValues[12] = extraLongToFloat(tCharge) * Cscale / fSample;
// Charge
paramValues[19] = extraLongToFloat(tEnergy) * Vscale * Cscale / fSample;
// Energy
/*** Phase & Time ***/
paramValues[20] = acos(paramValues[14]/paramValues[15]);
// Phase = acos(P/S)
paramValues[21] = (float) round(timeCnt);
// Time
/*** Voltage Frequency ***/
if(vfmPeriodsTime > 10000 && vfmPeriods > 3) {
vfmUpperTh = 2000;
// Prevent asynchronious reading
paramValues[6] = float(vfmPeriods) * fSample / float(vfmPeriodsTime-2);
// calc. voltage frequency
if(pow(10, paramRange[2]) / paramValues[2] > 100) {
paramValues[6] = 0.0; }
// Make zero if signal is too small
vfmTime = 0;
// Start new voltage freqency measurement
}
vfmUpperTh = int((paramValues[0] + (paramValues[2] / 2)) / Vscale);
// Upper voltage threshold
vfmLowerTh = int((paramValues[0] - (paramValues[2] / 2)) / Vscale);
// lower voltage threshold
/*** Current Frequency ***/
if(cfmPeriodsTime > 10000 && cfmPeriods > 3) {
cfmUpperTh = 2000;
// Prevent asynchronious reading
paramValues[13] = float(cfmPeriods) * fSample / float(cfmPeriodsTime-
2); // calc. current frequency
if(pow(10, paramRange[9]) / paramValues[9] > 100) {
paramValues[13] = 0.0; }
// Make zero if signal is too small
cfmTime = 0;
// Start new voltage freqency measurement
}
cfmUpperTh = int((paramValues[7] + (paramValues[9] / 2)) / Cscale);
// Upper current threshold
cfmLowerTh = int((paramValues[7] - (paramValues[9] / 2)) / Cscale);
// Lower current threshold

/*** Update LCD values ***/


updateParamValues(); // Write values to LCD

/*** OverFlow ***/


if (overFlowVolt == true || overFlowCurr == true) {
// If there was an overflow ...
overFlowVolt = false;
// ... clear overflow flags ...
overFlowCurr = false;
bitSet(PORTB, 5);
// ... turn overflow LED on.
}
else {
bitClear(PORTB, 5);
// Else overflow LED off.
}
}
/*** Button Nulling values ***/
if (buttonPushed == 0x10) { // If "clear values" button is pressed
...
if (~PINB & 0x02) { // ... and "line select" button ...
buttonPushed = 0;
setProperties(); // ... set parametes to zero.
}
}
/*** Button Parameter Select Down ***/
else if (buttonPushed == 0x04) { // If "parameter select down" button is
pressed ...
buttonPushed = 0;
changeParamPointer(false); // ... select previous parameter ...
updateParamLabels(); // ... refresh parameter labels ...
updateParamValues(); // ... and parameter values.
}
/*** Button Parameter Select Up ***/
else if (buttonPushed == 0x08) { // If "parameter select up" button is
pressed ...
buttonPushed = 0;
changeParamPointer(true); // ... select next parameter ...
updateParamLabels(); // ... refresh parameter labels ...
updateParamValues(); // ... and parameter values.
}
/*** Button Line select ***/
else if (buttonPushed == 0x02) { // If "line select" buton is pressed ...
buttonPushed = 0;
updateParamLabels(); // ... refresh parameter labels ...
lineSelect++; // ... select next line.
if(lineSelect >= LCDlines) {
lineSelect = 0; }
lcdWrite(true, LCDlinePos[lineSelect]); // First position on selected
line
lcdPrintString("....."); // Indicate the line with ".....".
vCnt = 0; // Zero the values refresh counter.
}
}

/*** Clear extra long ***/


void extraLongClear(byte* elVal) {
for(int ci=0; ci<8; ci++) {
elVal[ci] = 0x00; // Clear all 8 bytes of the eztra long
}
}

/*** Clear array ***/


void avArrClear(unsigned int arrLen, long* avArr) {
for(int ci=0; ci<arrLen; ci++) {
avArr[ci] = 0; // Clear all values in the array
}
}

/*** Set elementary properties & clear all parameters***/


void setProperties() {
/*** Set division factors ***/
totAverage = float(primAvLength * secAvLength);
fSample = 19230.7 / 4.0; // sample frequency for
each channel
Vscale = adcSense * Vdiv; // Voltage scale
Cscale = adcSense * Cdiv; // Voltage scale
/*** Set parameter ranges ***/
int pp;
int expo = ceil(log10(Vscale*560.0)); // Calculate exponent of
highest expected voltage
for(pp=0; pp<=6; pp++) {
if(paramRange[pp] < 98) { // If not auto range or
int ...
paramRange[pp] = expo; } // ... set range for
voltage related parameters.
}
expo = ceil(log10(Cscale*560.0)); // Calculate exponent of
highest expected current
for(pp=7; pp<=13; pp++) {
if(paramRange[pp] < 98) { // If not auto range or
int ...
paramRange[pp] = expo; } // ... set range for
current related parameters.
}
expo = ceil(log10(Vscale*Cscale*313600.0)); // Calculate exponent of
highest expected power
for(pp=14; pp<=19; pp++) {
if(paramRange[pp] < 98) { // If not auto range or
int ...
paramRange[pp] = expo; } // ... set range for power
related parameters.
}
/*** Clear values ***/
avArrClear(secAvLength, secMeanVoltArr); // Clear the secondary
averaging arrays ...
avArrClear(secAvLength, secSquaredVoltArr);
avArrClear(secAvLength, secMeanCurrArr);
avArrClear(secAvLength, secSquaredCurrArr);
avArrClear(secAvLength, secMeanPowArr);
secArrCnt = 0; // Clear secondary array
counter
secMeanVolt = 0; // Clear secondary
averaging values ...
secSquaredVolt = 0;
secMeanCurr = 0;
secSquaredCurr = 0;
secMeanPow = 0;
primMaxVolt = -1024; // Set maximum vales at
lowest possible value ...
primMinVolt = 1024; // ... and minimum values
at highest possible value.
primMaxCurr = -1024;
primMinCurr = 1024;
primMaxPow = -1048576;
primMinPow = 1048576;
extraLongClear(tFlux); // Clear thecCumulative
values
extraLongClear(tCharge);
extraLongClear(tEnergy);
preTimeCnt = 0; // Clear time prescaler
...
timeCnt = 0; // ... and time counter
}

/*** Select the next or previous parameter ***/


void changeParamPointer(boolean dir) {
int paramMax = 21; // Number of parameters
if(dir == true) {
paramPointers[lineSelect]++; // Increase parameter pointer for
the selected line
if(paramPointers[lineSelect] > paramMax) {
paramPointers[lineSelect] = 0; }
}
else {
if(paramPointers[lineSelect] == 0) { // Decrease parameter pointer for
the selected line
paramPointers[lineSelect] = paramMax + 1; }
paramPointers[lineSelect]--;
}
}

/*** Write parameter label on each line ***/


void updateParamLabels() {
for (int ci=0; ci<LCDlines; ci++) {
lcdWrite(true, LCDlinePos[ci]);
lcdPrintString(paramLabels[paramPointers[ci]]);
}
}

/*** Write parameter value on each line ***/


void updateParamValues() {
for (int ci=0; ci<LCDlines; ci++) {
lcdParamValues(paramPointers[ci], LCDlinePos[ci] + LCDwidth);
}
}

void lcdParamValues(byte paramPo, byte pos) {


boolean ofm = false;
if(paramPo < 7) { // Voltage related parameters
ofm = overFlowVolt; }
else if(paramPo < 14) { // Current related parameters
ofm = overFlowCurr; }
else if(paramPo < 20) { // Power related parameters
ofm = overFlowVolt | overFlowCurr; }
if(paramRange[paramPo] == 100) { // int number
lcdTechNot(0, timeCnt, paramRange[paramPo], paramDigits[paramPo],
paramUnits[paramPo], ofm, pos, true);
}
else { // float number
lcdTechNot(paramValues[paramPo], 0, paramRange[paramPo],
paramDigits[paramPo], paramUnits[paramPo], ofm, pos, true);
}
}

/*** Convert an extra long value to float ***/


float extraLongToFloat(byte* elVal) {
boolean sign bitRead(elVal[7], 7); // true = negative, false = positive
value
int fi;
for(fi=7; fi>3; fi--) { // Find the first MS byte without
leading zero's in the higherst 4
if(sign == false && (elVal[fi] != 0x00 || bitRead(elVal[fi-1], 7) ==
true)) {
break;
} // Find the first MS byte without
leading 0xFF in the higherst 4
if(sign == true && (elVal[fi] != 0xFF || bitRead(elVal[fi-1], 7) ==
false)) {
break;
}
}
long tVal;
long loVal = 0;
for(int shi=24; shi>=0; shi-=8) { // Shift values for each byte
tVal = long(elVal[fi--]); // Take first relevant byte
loVal += tVal << shi; // and shift it into te long
}
float flVal = float(loVal); // Make it a float
flVal *= pow(2.0, (fi + 1.0) * 8.0); // and multiply it by the last byte
exponent
return flVal;
}

/*** Add a long to an extra long ***/


void extraLongAddLong(byte* elVal, long addVal) {
byte add0 = byte(addVal); // put the long in seperate bytes.
addVal = addVal >> 8;
byte add1 = byte(addVal);
addVal = addVal >> 8;
byte add2 = byte(addVal);
addVal = addVal >> 8;
byte add3 = byte(addVal);
byte ext = 0x00; // extend the long with 0x00 ...
if(bitRead(add3, 7) == true) {
ext = 0xFF; // ... or 0xFF if the long is negative.
}
asm volatile(
"add %[v0], %[a0]\n" // add the bytes of the long to the extralong
"adc %[v1], %[a1]\n"
"adc %[v2], %[a2]\n"
"adc %[v3], %[a3]\n"
"adc %[v4], %[bext]\n"
"adc %[v5], %[bext]\n"
"adc %[v6], %[bext]\n"
"adc %[v7], %[bext]\n"
: [v0] "+r" (elVal[0]), // ouput operand list
[v1] "+r" (elVal[1]),
[v2] "+r" (elVal[2]),
[v3] "+r" (elVal[3]),
[v4] "+r" (elVal[4]),
[v5] "+r" (elVal[5]),
[v6] "+r" (elVal[6]),
[v7] "+r" (elVal[7])
: [a0] "r" (add0), // input operand list
[a1] "r" (add1),
[a2] "r" (add2),
[a3] "r" (add3),
[bext] "r" (ext)
);
}

void initiateADC() {
ADCSRB = 0x00;
DIDR0 = 0x30; // digital inputs disabled for A4 & A5
ADMUX = 0xC4; // measuring on ADC4, left adjust, internal 1.1V
ref
ADCSRA = 0xAE; // AD-converter on, interrupt enabled, prescaler =
64
ADCSRB = 0x40; // AD channels MUX on, free running mode
bitWrite(ADCSRA, 6, 1); // Start the conversion by setting bit 6 (=ADSC)
in ADCSRA
}

/*** Interrupt routine ADC ready ***/


ISR(ADC_vect) {
/*** Read ADC result ***/
int adcInt = ADCL; // store lower byte ADC
adcInt += ADCH << 8; // store higher bytes ADC
long adcVal = long(adcInt) - nullVal; // relative ADC value
/*** ADC multiplexer counter ***/
muxCnt++; // Multiplexer counter +1
muxCnt &= 0x03;
ADMUX = muxCnt | 0xC4;
/*** Voltage measurement ***/
if(muxCnt == 2) { // Result Analog voltage input
ready
if(adcInt < 12 || adcInt > 1010) { // Voltage overflow detection
overFlowVolt = true;
}
adcVolt = adcVal;
primMeanVolt += adcVal; // Primary add add Mean Voltage
primSquaredVolt += adcVal * adcVal; // calc. and primary add squared
value
if(adcVal > primMaxVolt) { // Store new maximum voltage if
greater
primMaxVolt = adcVal; }
if(adcVal < primMinVolt) { // Store new minimum voltage if
smaller
primMinVolt = adcVal; }
/*** Voltage frequency ***/
if(vfmDir == true && adcVal > vfmUpperTh) { // If the upper threshold is
reached ...
vfmDir = false; // the next threshold is the
negative one
vfmPeriodsTime = vfmTime; // copy the current time into
the period time
if(vfmTime > 0) {
vfmPeriods++; // If the measurement is
running: increase the number of periods.
}
else {
vfmTime = 1; // Else start the measurement
...
vfmPeriods = 0; // ... and clear the number
of periods
}
}
else if(vfmDir == false && adcVal < vfmLowerTh) { // If the negative
threshold is reached ...
vfmDir = true; // ... the next
threshold is the positive one.
}
if(vfmTime > 0) { // If the frequency
measurement is running ...
vfmTime++; } // increase time.
}
/*** Current measurement ***/
if(muxCnt == 3) { // Result Analog current input
ready
if(adcInt < 12 || adcInt > 1010) { // Current overflow detection
overFlowCurr = true;
}
adcVal = -adcVal; // Inverting preamp
adcCurr = adcVal;
primMeanCurr += adcVal; // Primary add Mean Voltage
primSquaredCurr += adcVal * adcVal; // calc. and primaty add squared
value
if(adcVal > primMaxCurr) { // Store new maximum current if
greater
primMaxCurr = adcVal; }
if(adcVal < primMinCurr) { // Store new minimum current if
smaller
primMinCurr = adcVal; }
/*** Current frequency ***/
if(cfmDir == true && adcVal > cfmUpperTh) { // If the upper threshold is
reached ...
cfmDir = false; // the next threshold is the
negative one
cfmPeriodsTime = cfmTime; // copy the current time into
the period time
if(cfmTime > 0) {
cfmPeriods++; // If the measurement is
running: increase the number of periods.
}
else {
cfmTime = 1; // Else start the measurement
...
cfmPeriods = 0; // ... and clear the number
of periods
}
}
else if(cfmDir == false && adcVal < cfmLowerTh) { // If the negative
threshold is reached ...
cfmDir = true; // ... the next
threshold is the positive one.
}
if(cfmTime > 0) { // If the frequency
measurement is running ...
cfmTime++; } // increase time.
}
/*** Power calculation ***/
if(muxCnt == 0) { // Result Analog null reference
input ready
nullVal = long(adcInt); // Store null reference
long TadcVal = long(adcVolt) * long(adcCurr); // Calc. the instantanious
power ...
primMeanPow += TadcVal; // ... and primary add it.
if(TadcVal > primMaxPow) { // Store new maximum power
if greater
primMaxPow = TadcVal; }
if(TadcVal < primMinPow) { // Store new minimum power
if smaller
primMinPow = TadcVal; }
}
/*** Transfer primary averaged values ***/
if(muxCnt == 1) { // At a quiet moment ...
(Analog channel 7 isn't used)
primCnt++; // increase primary
averaging counter
if(primCnt >= primAvLength) { // If the required averaging
number is reached ...
primCnt = 0;
primMeanVoltCopy = primMeanVolt; // Make a copy of all the
primary averaging values ...
primMeanVolt = 0; // ... and clear the primary
averaging value.
primSquaredVoltCopy = primSquaredVolt;
primSquaredVolt = 0;
primMeanCurrCopy = primMeanCurr;
primMeanCurr = 0;
primSquaredCurrCopy = primSquaredCurr;
primSquaredCurr = 0;
primMeanPowCopy = primMeanPow;
primMeanPow = 0;
primReady = true; // The primary averaging
values are availeble for the secondary averaging.
}
vCnt++; // value count +1 // Increase value counter
/*** Button debouncing ***/
byte buttons = ~PINB & 0x1E; // Read the four buttons
if(buttons == 0) { // If no button is pressed
...
if(antiBounceCnt == 0) { // ... and anti bounce is
already zero ...
buttonState = false;
buttonPushed = 0; // clear the button pressed
register
}
else {
antiBounceCnt--; } // decrease anti bounce
counter if it is not zero
}
else {
if(antiBounceCnt < 40) {
antiBounceCnt++; } // Increase anti bounce
counter if not maximum
else if(buttonState == false) { // If anti bounce reaches
maximum the first time ...
buttonState = true;
buttonPushed = buttons; // ... copy button status
for further processing
}
}
}
/*** Time ***/
preTimeCnt++;
if(preTimeCnt >= 19231) { // Seconds counter prescaler
preTimeCnt = 0;
timeCnt++; // Seconds counter
}
//bitClear(PORTB, 5); // overflow LED off
}

void lcdInitiate() {
/*** Initialize 2*16 char LCD ***/
/*** LCD DB4...7 => PD4...7 (D4...7), RS => PD2 (D2), R/W => PD3 (D3), E =>
PB0 (D8) ***/
bitClear(PORTB, 0); // LCD enable = low
bitSet(DDRB, 0); // LCD enable = output
PORTD = PORTD & 0x03; // LCD D4...7 & R/W & RS = low
DDRD = DDRD | 0xFC; // LCD D4...7 & R/W & RS = output
muDelay(50000);
byte PD = PORTD & 0x03; // LCD in 4 bit mode
PORTD = PD | 0x20;
__asm__("nop\n\t");
bitSet(PORTB, 0); // E high
__asm__("nop\n\tnop\n\tnop\n\tnop\n\t");
bitClear(PORTB, 0); // E low
__asm__("nop\n\tnop\n\t");
// Write function set three times, else the second line isn't functional.
lcdWrite(true, 0x28); // 2 line mode, 5*7 dots
muDelay(40);
lcdWrite(true, 0x28); // 2 line mode, 5*7 dots
muDelay(40);
lcdWrite(true, 0x28); // 2 line mode, 5*7 dots
muDelay(40);
lcdWrite(true, 0x0C); // display on, cursor off, blink off
lcdWrite(true, 0x01); // display clear
lcdWrite(true, 0x06); // increment mode, entire shift off
}
void lcdPrintString(const char* strArr) {
/*** Write a text string to the LCD ***/
for(byte ci=0; ci<strlen(strArr); ci++) {
lcdWrite(false, strArr[ci]);
}
}

void lcdWrite(boolean reg, byte val) {


/*** write byte to LCD in nibble mode ***/
/*** reg: false = data, true = command ***/
lcdCheckBusyFlag();
byte loni = val << 4; // prepare LS nibble
byte hini = val & 0xF0; // prepare MS nibble
byte PD = PORTD & 0x03; // R/W = 0, RS = 0, DB4...7 = 0
if (reg == false) {
PD = PD | 0x04; } // RS = 1
PORTD = PD | hini; // MS nibble to LCD
DDRD = DDRD | 0xF0; // PD4...7 = output
//__asm__("nop\n\tnop\n\t");
bitSet(PORTB, 0); // E = 1
__asm__("nop\n\tnop\n\tnop\n\t");
bitClear(PORTB, 0); // E = 0
__asm__("nop\n\tnop\n\t");
PORTD = PD | loni; // LS nibble to LCD
//__asm__("nop\n\t");
bitSet(PORTB, 0); // E = 1
__asm__("nop\n\tnop\n\tnop\n\t");
bitClear(PORTB, 0); // E = 0
__asm__("nop\n\tnop\n\t");
DDRD = DDRD & 0x0F; // PD4...7 = input
}

void lcdCheckBusyFlag() {
/*** check until LCD is ready ***/
byte dVal;
DDRD = DDRD & 0x0F; // PD4...7 = input
bitSet(PORTD, 3); // R/W = 1
bitClear(PORTD, 2); // RS=0
do {
bitSet(PORTB, 0); // enable = 1
__asm__("nop\n\tnop\n\t");
dVal = PIND & 0xF0; // Read MS nibble
bitClear(PORTB, 0); // enable = 0
__asm__("nop\n\tnop\n\tnop\n\t");
bitSet(PORTB, 0); // enable = 1
__asm__("nop\n\tnop\n\t");
dVal = dVal | (PIND >> 4); // Read LS nibble
bitClear(PORTB, 0); // enable = 0
__asm__("nop\n\tnop\n\tnop\n\t");
} while(bitRead(dVal, 7) == HIGH);
bitClear(PORTD, 3); // R/W = 0
}

void lcdCustomChars() {
/*** Make some custom characters ***/
lcdWrite(true, 0x48); // Char 0x01 = phi
lcdWrite(false, 0x02);
lcdWrite(false, 0x15);
lcdWrite(false, 0x15);
lcdWrite(false, 0x0E);
lcdWrite(false, 0x04);
lcdWrite(false, 0x04);
lcdWrite(false, 0x04);
lcdWrite(false, 0x00);
lcdWrite(true, 0x50); // Char 0x02 = deg
lcdWrite(false, 0x0C);
lcdWrite(false, 0x12);
lcdWrite(false, 0x12);
lcdWrite(false, 0x0C);
lcdWrite(false, 0x00);
lcdWrite(false, 0x00);
lcdWrite(false, 0x00);
lcdWrite(false, 0x00);
lcdWrite(true, 0x80); // First char address
}

void lcdTechNot(float fval, long lval, int rangeExp, int digits, const char*
unit, boolean overFlow, byte pos, boolean alignR) {
/*** Write a value in Technical Notation on the LCD ***/
/*** fval = float value, ore use lval = long int value ***/
/*** rangeExp = exponent of the limit value (e.g. rangeExp=2 => 10^2=100 as
limit), 99=autorange, 100 = use the long int value ***/
/*** digits = number of digits ***/
/*** unit = the unit: "V", "A",... ***/
/*** overFlow = external overflow indicator, e.g. from ADC. Puts the "^"
char between number and unit ***/
/*** pos & alignR = LCD position & right align: last position if
alignR=true, first position if alignR=false) ***/
byte totChar = 11; // Total number of chars to fill
on the LCD (to ensure the old valu is erased)
char valStr[totChar]; // Text string
int strPnt = 0; // Text string pointer
digits -= 1;
int decpoint = digits + 3; // No decimal point if val is
long
char vvArr[] = {'f','p','n',0xE4,'m',' ','k','M','G','T','P'}; // prefix
array
int expo; // Exponent
if (rangeExp == 99) { // If auto range ...
expo = floor(log10(fabs(fval))); // ... use the value exponent ...
}
else {
expo = rangeExp - 1; // ... else use a fixed exponent
as range
}
if(rangeExp != 100) { // If the float value is used ...
lval = long(round(fval / pow(10, expo - digits))); // ... calculate the
value in N digits.
if(lval >= round(pow(10, digits + 1))) { // Fix floating
point errors
lval /= 10;
expo++;
}
decpoint = digits - ((expo+15) % 3); // Calc. decimal point position
}
else { // If the long int value is used
...
expo = 0; // ... prevent using a prefix.
}
if(lval < 0) { // If the value is negative ...
lval = abs(lval); // ... make the value absolute
...
valStr[strPnt++] = 0x2D; // ... and put a negative sign =>
string
}
long dec;
byte num;
boolean prtStart = false; // Don't push any number into the
string yet (no leading zero's)
for(int nd=digits; nd>=0; nd--) { // For the number of digits the
value is long ...
dec = long(round(pow(10, nd))); // calc. the 10th power number
for the current digit,
num = 0; // start value is 0.
while(lval >= dec) { // While the 10th power number is
smaller than the value ...
lval -= dec; // decrease the value,
num++; // increase the current digit.
if(num > 9) { // If digit > 9 ...
num = 0x3C; // digit is "<" if value doesn't
fit
lval += dec; // for all digit places
break;
}
}
if(prtStart == true || num != 0 || nd == 0) { // If printing is active
or digit is not zero or LS digit
prtStart = true; // printing is active,
valStr[strPnt++] = num | 0x30; // digit => string.
}
else if(prtStart == false && decpoint == nd) { // If no number is printed
yet and decimal point must be printed
prtStart = true; // printing is active,
valStr[strPnt++] = 0x30; // 0 => string.
}
if(decpoint == nd && nd != 0) { // If decimal point must
be printed and it is not the LS digit
valStr[strPnt++] = 0x2E; // decimal point =>
string.
}
}
if(overFlow == true) {
valStr[strPnt++] = 0x5E; // "^" => string if there the
external overflow flag is set
}
else {
valStr[strPnt++] = 0x20; // or a white space => string
}
int ediv = floor(expo / 3.0); // Calc. prefix pointer
if (ediv != 0) {
valStr[strPnt++] = vvArr[ediv+5]; // Prefix => string
}
byte ci;
for(ci=0; ci<strlen(unit); ci++) { // For every unit character ...
valStr[strPnt++] = unit[ci]; // ... unit char = string.
}
if(alignR == true) { // If the alignment is right ...
pos -= totChar; // ... calc. new start position.
}
lcdWrite(true, pos); // Position cursor LCD
for(byte iw=0; iw<(totChar-strPnt); iw++) { // For every unoccupied
preceding character ...
lcdWrite(false, 0x20); // ... send white spaces to LCD to
erase old text
}
for(ci=0; ci<strPnt; ci++) { // For every character ...
lcdWrite(false, valStr[ci]); // ... send character to LCD
}
}

void muDelay(unsigned int mutime) {


/*** micro seconds delay ***/
for(unsigned int ci=0; ci<mutime; ci++) {

__asm__("nop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\t");
}
}
Wattmeter code

/************************************/
/*** Arduino Wattmeter v1.0 ***/
/*** Board: Arduino Nano 3.0 ***/
/*** By: Freddy Alferink ***/
/*** http://meettechniek.info ***/
/*** May 2014 ***/
/************************************/

/*********** Calibration & Hardware Data ***********/


float Vdiv = 101.0; // Voltage conversion factor
float Cdiv = 10.0; // Current conversion factor
const byte LCDlines = 2; // LCD: Number of lines
const byte LCDwidth = 16; // LCD: Number of character per line
/***************************************************/

const float adcSense = 1.074219e-3; // ADC conversion factor volt/bit (1.1V


/ 1024)
float Vscale = 1.0; // Voltage scale
float Cscale = 1.0; // Current scale
byte muxCnt = 4; // Analog multiplexer start
int nullVal = 512; // Measured null value
boolean overFlowVolt = false; // Voltage overflow flag
boolean overFlowCurr = false; // Current overflow flag
unsigned int vCnt = 0; // Values counter
/*** Voltage frequency measurement ***/
int vfmUpperTh = 10; // Upper threshold voltage
int vfmLowerTh = -10; // Lower threshold voltage
boolean vfmDir = false; // Direction voltage
unsigned int vfmPeriods = 7; // Periods counter
unsigned int vfmPeriodsTime = 1000; // Synchronized with periods time
unsigned int vfmTime = 1000; // Time counter
/*** Current frequency measurement ***/
int cfmUpperTh = 10; // Upper threshold current
int cfmLowerTh = -10; // Lower threshold current
boolean cfmDir = false; // Direction current
unsigned int cfmPeriods = 5; // Periods counter
unsigned int cfmPeriodsTime = 1000; // Synchronized with periods time
unsigned int cfmTime = 1000; // Time counter
/*** Primary averaging ***/
int primCnt = 0; // Primary averaging counter
boolean primReady = false; // Primary averaging complete flag
int adcVolt; // Copy ADC voltage
const unsigned int primAvLength = 64; // Number of samples for primary
averaging
long primMeanVolt = 0; // Cumulative ADC mean voltage
long primMeanVoltCopy = 0; // Averaged primary mean voltage
long primSquaredVolt = 0; // Cumulative ADC squared voltage
long primSquaredVoltCopy = 0; // Averaged primary squared voltage
long primMaxVolt = 0; // Highest measured ADC voltage
long primMinVolt = 0; // Lowest measured ADC voltage
int adcCurr; // Copy ADC current
long primMeanCurr = 0; // Cumulative ADC mean current
long primMeanCurrCopy = 0; // Averaged primary current
long primSquaredCurr = 0; // Cumulative ADC squared current
long primSquaredCurrCopy = 0; // Averaged primary squared current
long primMaxCurr = 0; // Highest measured ADC current
long primMinCurr = 0; // Lowest measured ADC current
long primMeanPow = 0; // Cumulative ADC mean power
long primMeanPowCopy = 0; // Averaged primary mean power
long primMaxPow = 0; // Highest measured ADC power
long primMinPow = 0; // Lowest measured ADC power
/*** Secondary averaging ***/
unsigned int secArrCnt = 0;
const unsigned int secAvLength = 50;
long secMeanVoltArr[secAvLength]; // Mean Voltage secondary averageing
array
long secSquaredVoltArr[secAvLength]; // Squared Voltage secondary averageing
array
long secMeanCurrArr[secAvLength]; // Mean Current secondary averageing
array
long secSquaredCurrArr[secAvLength]; // Squared Current secondary averageing
array
long secMeanPowArr[secAvLength]; // Real Power secondary averageing array
long secMeanVolt = 0; // Result secondary averaging mean
voltage
long secSquaredVolt = 0; // Result secondary averaging squared
voltage
long secMeanCurr = 0; // Result secondary averaging mean
current
long secSquaredCurr = 0; // Result secondary averaging squared
current
long secMeanPow = 0; // Result secondary averaging mean power
/*** Cumulative values ***/
byte tFlux[8]; // 64 bits integrated ADC voltage
byte tCharge[8]; // 64 bits integrated ADC current
byte tEnergy[8]; // 64 bits integrated ADC power
unsigned int preTimeCnt = 0; // Time prescaler
unsigned long timeCnt = 0; // Time seconds counter
/*** ***/
float totAverage = 10.0; // Total averaging length (primary *
secondary)
float fSample = 10.0; // ADC channel sample frequency
const byte LCDlinePos[4] = {0x80,0xC0,0x94,0xD4}; // First char position for
each line (as DDRAM instruction)
byte lineSelect = 0; // The selected dsplay line
byte paramPointers[4] = {0,7,13,18}; // Parameter pointer for each display
line
char* paramLabels[] = {"Vmean ","Vrms ","Vsdef ","Vmax ","Vmin ","Flux
","f (V)","Imean ","Irms ","Isdef ","Imax ","Imin ","Charge","f (I)
","Preal ","S * ","Q * ","Pmax ","Pmin ","Energy","\x01 * ","time
"};
char* paramUnits[] =
{"V","V","V","V","V","Vs","Hz","A","A","A","A","A","C","Hz","W","VA","var","W
","W","J","\x02","s"};
float paramValues[] =
{0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
0.0,0.0,0.0};
int paramRange[] = {0,0,0,0,0,99,99,0,0,0,0,0,99,99,99,0,0,0,0,99,99,100};
// 99=autorange, 100=int-number
int paramDigits[] = {4,4,4,3,3,5,4,4,4,4,3,3,5,4,4,4,4,3,3,5,3,7}; //
number of digits
byte antiBounceCnt = 0; // Buttons anti bounce counter
boolean buttonState = false; // Button is pressed
unsigned int buttonPushed = 0; // Pressed buttons

void setup() {
MCUCR &= 0xEF; // Enable pull-up resistors
DDRB &= 0xE1; // Buttons on D9...12 are inputs
DDRB |= 0x20; // Overflow LED on D13 is output
PORTB |= 0x1E; // Pull-up resistor for the buttons
bitClear(PORTB, 5); // Overflow LED off
initiateADC(); // initiate AD converter
lcdInitiate(); // initiate LCD
lcdCustomChars(); // Make custom characters
lcdWrite(true, LCDlinePos[0]); // LCD cursor first line position 0
lcdPrintString("Universal power");
lcdWrite(true, LCDlinePos[1]); // LCD cursor second line position 0
lcdPrintString("meter v1.0");
for(int md=0; md<100; md++) { // Wait 2 seconds
muDelay(20000);
}
lcdWrite(true, LCDlinePos[0]); // LCD cursor first line position 0
lcdPrintString("meettechniek ");
lcdWrite(true, LCDlinePos[1]); // LCD cursor second line position 0
lcdPrintString(" .info");
for(int md=0; md<100; md++) { // Wait 2 seconds
muDelay(20000);
}
setProperties(); // Set properties and Clear measured
values
updateParamLabels(); // Write Parameter labels to LCD
updateParamValues(); // Write parameter values to LCD
sei(); // set interrupt flag
}

void loop() {
if (primReady == true) {
/*** Secondary avearaging Mean Volt ***/
secMeanVolt -= secMeanVoltArr[secArrCnt]; // Subtract oldest
value strored in array from average
secMeanVolt += primMeanVoltCopy; // Add newest value
to average
secMeanVoltArr[secArrCnt] = primMeanVoltCopy; // Store newest
value in array
/*** Secondary avearaging Mean Squared Volt ***/
secSquaredVolt -= secSquaredVoltArr[secArrCnt]; // the same for
squared voltage
secSquaredVolt += primSquaredVoltCopy;
secSquaredVoltArr[secArrCnt] = primSquaredVoltCopy;
/*** Secondary avearaging Mean Current ***/
secMeanCurr -= secMeanCurrArr[secArrCnt]; // and mean current
secMeanCurr += primMeanCurrCopy;
secMeanCurrArr[secArrCnt] = primMeanCurrCopy;
/*** Secondary avearaging Mean Squared Current ***/
secSquaredCurr -= secSquaredCurrArr[secArrCnt]; // squared current
secSquaredCurr += primSquaredCurrCopy;
secSquaredCurrArr[secArrCnt] = primSquaredCurrCopy;
/*** Secondary avearaging Mean Power ***/
secMeanPow -= secMeanPowArr[secArrCnt]; // and power
secMeanPow += primMeanPowCopy;
secMeanPowArr[secArrCnt] = primMeanPowCopy;
/*** Array Pointer ***/
secArrCnt++; // Increase secondary
averaging array pointer
if(secArrCnt >= secAvLength) {
secArrCnt = 0;
}
/*** Cumulative values ***/
extraLongAddLong(tFlux, primMeanVoltCopy); // Add primary averaged
voltage to total Flux
extraLongAddLong(tCharge, primMeanCurrCopy); // Add primary averaged
current to total Charge
extraLongAddLong(tEnergy, primMeanPowCopy); // Add primary averaged
power to total Energy
primReady = false;
}
/*** Update display ***/
if (vCnt > 1000) { // After 1000 channel samples refresh values
on display
vCnt = 0;
/*** Calculate values Voltage ***/
paramValues[0] = float(secMeanVolt) * Vscale / totAverage;
// calc. mean Voltage
paramValues[1] = sqrt(float(secSquaredVolt) / totAverage) * Vscale;
// calc. RMS Voltage
paramValues[2] = sqrt((paramValues[1]*paramValues[1])-
(paramValues[0]*paramValues[0])); // calc. standard deviation Voltage:
Vsdev=sqrt(Vrms^2 - Vmean^2)
paramValues[3] = float(primMaxVolt) * Vscale;
// Max voltage
paramValues[4] = float(primMinVolt) * Vscale;
// Min voltage
/*** Calculate values Current ***/
paramValues[7] = float(secMeanCurr) * Cscale / totAverage;
// calc. mean Current
paramValues[8] = sqrt(float(secSquaredCurr) / totAverage) * Cscale;
// calc. RMS Current
paramValues[9] = sqrt((paramValues[8]*paramValues[8])-
(paramValues[7]*paramValues[7])); // calc. standard deviation Current:
Csdev=sqrt(Crms^2 - Cmean^2)
paramValues[10] = float(primMaxCurr) * Cscale;
// Max current
paramValues[11] = float(primMinCurr) * Cscale;
// Min current
/*** Calculate values Power ***/
paramValues[14] = float(secMeanPow) * Vscale * Cscale / totAverage;
// Real Power
paramValues[17] = float(primMaxPow) * Vscale * Cscale;
// Max Power
paramValues[18] = float(primMinPow) * Vscale * Cscale;
// Min Power
paramValues[15] = paramValues[1] * paramValues[8];
// Apparent Power = Urms * Irms
paramValues[16] = sqrt((paramValues[15]*paramValues[15])-
(paramValues[14]*paramValues[14])); // Reactive power
/*** Flux, Charge, Energy ***/
paramValues[5] = extraLongToFloat(tFlux) * Vscale / fSample;
// Flux
paramValues[12] = extraLongToFloat(tCharge) * Cscale / fSample;
// Charge
paramValues[19] = extraLongToFloat(tEnergy) * Vscale * Cscale / fSample;
// Energy
/*** Phase & Time ***/
paramValues[20] = acos(paramValues[14]/paramValues[15]);
// Phase = acos(P/S)
paramValues[21] = (float) round(timeCnt);
// Time
/*** Voltage Frequency ***/
if(vfmPeriodsTime > 10000 && vfmPeriods > 3) {
vfmUpperTh = 2000;
// Prevent asynchronious reading
paramValues[6] = float(vfmPeriods) * fSample / float(vfmPeriodsTime-2);
// calc. voltage frequency
if(pow(10, paramRange[2]) / paramValues[2] > 100) {
paramValues[6] = 0.0; }
// Make zero if signal is too small
vfmTime = 0;
// Start new voltage freqency measurement
}
vfmUpperTh = int((paramValues[0] + (paramValues[2] / 2)) / Vscale);
// Upper voltage threshold
vfmLowerTh = int((paramValues[0] - (paramValues[2] / 2)) / Vscale);
// lower voltage threshold
/*** Current Frequency ***/
if(cfmPeriodsTime > 10000 && cfmPeriods > 3) {
cfmUpperTh = 2000;
// Prevent asynchronious reading
paramValues[13] = float(cfmPeriods) * fSample / float(cfmPeriodsTime-
2); // calc. current frequency
if(pow(10, paramRange[9]) / paramValues[9] > 100) {
paramValues[13] = 0.0; }
// Make zero if signal is too small
cfmTime = 0;
// Start new voltage freqency measurement
}
cfmUpperTh = int((paramValues[7] + (paramValues[9] / 2)) / Cscale);
// Upper current threshold
cfmLowerTh = int((paramValues[7] - (paramValues[9] / 2)) / Cscale);
// Lower current threshold

/*** Update LCD values ***/


updateParamValues(); // Write values to LCD

/*** OverFlow ***/


if (overFlowVolt == true || overFlowCurr == true) {
// If there was an overflow ...
overFlowVolt = false;
// ... clear overflow flags ...
overFlowCurr = false;
bitSet(PORTB, 5);
// ... turn overflow LED on.
}
else {
bitClear(PORTB, 5);
// Else overflow LED off.
}
}
/*** Button Nulling values ***/
if (buttonPushed == 0x10) { // If "clear values" button is pressed
...
if (~PINB & 0x02) { // ... and "line select" button ...
buttonPushed = 0;
setProperties(); // ... set parametes to zero.
}
}
/*** Button Parameter Select Down ***/
else if (buttonPushed == 0x04) { // If "parameter select down" button is
pressed ...
buttonPushed = 0;
changeParamPointer(false); // ... select previous parameter ...
updateParamLabels(); // ... refresh parameter labels ...
updateParamValues(); // ... and parameter values.
}
/*** Button Parameter Select Up ***/
else if (buttonPushed == 0x08) { // If "parameter select up" button is
pressed ...
buttonPushed = 0;
changeParamPointer(true); // ... select next parameter ...
updateParamLabels(); // ... refresh parameter labels ...
updateParamValues(); // ... and parameter values.
}
/*** Button Line select ***/
else if (buttonPushed == 0x02) { // If "line select" buton is pressed ...
buttonPushed = 0;
updateParamLabels(); // ... refresh parameter labels ...
lineSelect++; // ... select next line.
if(lineSelect >= LCDlines) {
lineSelect = 0; }
lcdWrite(true, LCDlinePos[lineSelect]); // First position on selected
line
lcdPrintString("....."); // Indicate the line with ".....".
vCnt = 0; // Zero the values refresh counter.
}
}

/*** Clear extra long ***/


void extraLongClear(byte* elVal) {
for(int ci=0; ci<8; ci++) {
elVal[ci] = 0x00; // Clear all 8 bytes of the eztra long
}
}

/*** Clear array ***/


void avArrClear(unsigned int arrLen, long* avArr) {
for(int ci=0; ci<arrLen; ci++) {
avArr[ci] = 0; // Clear all values in the array
}
}

/*** Set elementary properties & clear all parameters***/


void setProperties() {
/*** Set division factors ***/
totAverage = float(primAvLength * secAvLength);
fSample = 19230.7 / 4.0; // sample frequency for
each channel
Vscale = adcSense * Vdiv; // Voltage scale
Cscale = adcSense * Cdiv; // Voltage scale
/*** Set parameter ranges ***/
int pp;
int expo = ceil(log10(Vscale*560.0)); // Calculate exponent of
highest expected voltage
for(pp=0; pp<=6; pp++) {
if(paramRange[pp] < 98) { // If not auto range or
int ...
paramRange[pp] = expo; } // ... set range for
voltage related parameters.
}
expo = ceil(log10(Cscale*560.0)); // Calculate exponent of
highest expected current
for(pp=7; pp<=13; pp++) {
if(paramRange[pp] < 98) { // If not auto range or
int ...
paramRange[pp] = expo; } // ... set range for
current related parameters.
}
expo = ceil(log10(Vscale*Cscale*313600.0)); // Calculate exponent of
highest expected power
for(pp=14; pp<=19; pp++) {
if(paramRange[pp] < 98) { // If not auto range or
int ...
paramRange[pp] = expo; } // ... set range for power
related parameters.
}
/*** Clear values ***/
avArrClear(secAvLength, secMeanVoltArr); // Clear the secondary
averaging arrays ...
avArrClear(secAvLength, secSquaredVoltArr);
avArrClear(secAvLength, secMeanCurrArr);
avArrClear(secAvLength, secSquaredCurrArr);
avArrClear(secAvLength, secMeanPowArr);
secArrCnt = 0; // Clear secondary array
counter
secMeanVolt = 0; // Clear secondary
averaging values ...
secSquaredVolt = 0;
secMeanCurr = 0;
secSquaredCurr = 0;
secMeanPow = 0;
primMaxVolt = -1024; // Set maximum vales at
lowest possible value ...
primMinVolt = 1024; // ... and minimum values
at highest possible value.
primMaxCurr = -1024;
primMinCurr = 1024;
primMaxPow = -1048576;
primMinPow = 1048576;
extraLongClear(tFlux); // Clear thecCumulative
values
extraLongClear(tCharge);
extraLongClear(tEnergy);
preTimeCnt = 0; // Clear time prescaler
...
timeCnt = 0; // ... and time counter
}

/*** Select the next or previous parameter ***/


void changeParamPointer(boolean dir) {
int paramMax = 21; // Number of parameters
if(dir == true) {
paramPointers[lineSelect]++; // Increase parameter pointer for
the selected line
if(paramPointers[lineSelect] > paramMax) {
paramPointers[lineSelect] = 0; }
}
else {
if(paramPointers[lineSelect] == 0) { // Decrease parameter pointer for
the selected line
paramPointers[lineSelect] = paramMax + 1; }
paramPointers[lineSelect]--;
}
}

/*** Write parameter label on each line ***/


void updateParamLabels() {
for (int ci=0; ci<LCDlines; ci++) {
lcdWrite(true, LCDlinePos[ci]);
lcdPrintString(paramLabels[paramPointers[ci]]);
}
}

/*** Write parameter value on each line ***/


void updateParamValues() {
for (int ci=0; ci<LCDlines; ci++) {
lcdParamValues(paramPointers[ci], LCDlinePos[ci] + LCDwidth);
}
}

void lcdParamValues(byte paramPo, byte pos) {


boolean ofm = false;
if(paramPo < 7) { // Voltage related parameters
ofm = overFlowVolt; }
else if(paramPo < 14) { // Current related parameters
ofm = overFlowCurr; }
else if(paramPo < 20) { // Power related parameters
ofm = overFlowVolt | overFlowCurr; }
if(paramRange[paramPo] == 100) { // int number
lcdTechNot(0, timeCnt, paramRange[paramPo], paramDigits[paramPo],
paramUnits[paramPo], ofm, pos, true);
}
else { // float number
lcdTechNot(paramValues[paramPo], 0, paramRange[paramPo],
paramDigits[paramPo], paramUnits[paramPo], ofm, pos, true);
}
}

/*** Convert an extra long value to float ***/


float extraLongToFloat(byte* elVal) {
boolean sign bitRead(elVal[7], 7); // true = negative, false = positive
value
int fi;
for(fi=7; fi>3; fi--) { // Find the first MS byte without
leading zero's in the higherst 4
if(sign == false && (elVal[fi] != 0x00 || bitRead(elVal[fi-1], 7) ==
true)) {
break;
} // Find the first MS byte without
leading 0xFF in the higherst 4
if(sign == true && (elVal[fi] != 0xFF || bitRead(elVal[fi-1], 7) ==
false)) {
break;
}
}
long tVal;
long loVal = 0;
for(int shi=24; shi>=0; shi-=8) { // Shift values for each byte
tVal = long(elVal[fi--]); // Take first relevant byte
loVal += tVal << shi; // and shift it into te long
}
float flVal = float(loVal); // Make it a float
flVal *= pow(2.0, (fi + 1.0) * 8.0); // and multiply it by the last byte
exponent
return flVal;
}

/*** Add a long to an extra long ***/


void extraLongAddLong(byte* elVal, long addVal) {
byte add0 = byte(addVal); // put the long in seperate bytes.
addVal = addVal >> 8;
byte add1 = byte(addVal);
addVal = addVal >> 8;
byte add2 = byte(addVal);
addVal = addVal >> 8;
byte add3 = byte(addVal);
byte ext = 0x00; // extend the long with 0x00 ...
if(bitRead(add3, 7) == true) {
ext = 0xFF; // ... or 0xFF if the long is negative.
}
asm volatile(
"add %[v0], %[a0]\n" // add the bytes of the long to the extralong
"adc %[v1], %[a1]\n"
"adc %[v2], %[a2]\n"
"adc %[v3], %[a3]\n"
"adc %[v4], %[bext]\n"
"adc %[v5], %[bext]\n"
"adc %[v6], %[bext]\n"
"adc %[v7], %[bext]\n"
: [v0] "+r" (elVal[0]), // ouput operand list
[v1] "+r" (elVal[1]),
[v2] "+r" (elVal[2]),
[v3] "+r" (elVal[3]),
[v4] "+r" (elVal[4]),
[v5] "+r" (elVal[5]),
[v6] "+r" (elVal[6]),
[v7] "+r" (elVal[7])
: [a0] "r" (add0), // input operand list
[a1] "r" (add1),
[a2] "r" (add2),
[a3] "r" (add3),
[bext] "r" (ext)
);
}

void initiateADC() {
ADCSRB = 0x00;
DIDR0 = 0x30; // digital inputs disabled for A4 & A5
ADMUX = 0xC4; // measuring on ADC4, left adjust, internal 1.1V
ref
ADCSRA = 0xAE; // AD-converter on, interrupt enabled, prescaler =
64
ADCSRB = 0x40; // AD channels MUX on, free running mode
bitWrite(ADCSRA, 6, 1); // Start the conversion by setting bit 6 (=ADSC)
in ADCSRA
}

/*** Interrupt routine ADC ready ***/


ISR(ADC_vect) {
/*** Read ADC result ***/
int adcInt = ADCL; // store lower byte ADC
adcInt += ADCH << 8; // store higher bytes ADC
long adcVal = long(adcInt) - nullVal; // relative ADC value
/*** ADC multiplexer counter ***/
muxCnt++; // Multiplexer counter +1
muxCnt &= 0x03;
ADMUX = muxCnt | 0xC4;
/*** Voltage measurement ***/
if(muxCnt == 2) { // Result Analog voltage input
ready
if(adcInt < 12 || adcInt > 1010) { // Voltage overflow detection
overFlowVolt = true;
}
adcVolt = adcVal;
primMeanVolt += adcVal; // Primary add add Mean Voltage
primSquaredVolt += adcVal * adcVal; // calc. and primary add squared
value
if(adcVal > primMaxVolt) { // Store new maximum voltage if
greater
primMaxVolt = adcVal; }
if(adcVal < primMinVolt) { // Store new minimum voltage if
smaller
primMinVolt = adcVal; }
/*** Voltage frequency ***/
if(vfmDir == true && adcVal > vfmUpperTh) { // If the upper threshold is
reached ...
vfmDir = false; // the next threshold is the
negative one
vfmPeriodsTime = vfmTime; // copy the current time into
the period time
if(vfmTime > 0) {
vfmPeriods++; // If the measurement is
running: increase the number of periods.
}
else {
vfmTime = 1; // Else start the measurement
...
vfmPeriods = 0; // ... and clear the number
of periods
}
}
else if(vfmDir == false && adcVal < vfmLowerTh) { // If the negative
threshold is reached ...
vfmDir = true; // ... the next
threshold is the positive one.
}
if(vfmTime > 0) { // If the frequency
measurement is running ...
vfmTime++; } // increase time.
}
/*** Current measurement ***/
if(muxCnt == 3) { // Result Analog current input
ready
if(adcInt < 12 || adcInt > 1010) { // Current overflow detection
overFlowCurr = true;
}
adcVal = -adcVal; // Inverting preamp
adcCurr = adcVal;
primMeanCurr += adcVal; // Primary add Mean Voltage
primSquaredCurr += adcVal * adcVal; // calc. and primaty add squared
value
if(adcVal > primMaxCurr) { // Store new maximum current if
greater
primMaxCurr = adcVal; }
if(adcVal < primMinCurr) { // Store new minimum current if
smaller
primMinCurr = adcVal; }
/*** Current frequency ***/
if(cfmDir == true && adcVal > cfmUpperTh) { // If the upper threshold is
reached ...
cfmDir = false; // the next threshold is the
negative one
cfmPeriodsTime = cfmTime; // copy the current time into
the period time
if(cfmTime > 0) {
cfmPeriods++; // If the measurement is
running: increase the number of periods.
}
else {
cfmTime = 1; // Else start the measurement
...
cfmPeriods = 0; // ... and clear the number
of periods
}
}
else if(cfmDir == false && adcVal < cfmLowerTh) { // If the negative
threshold is reached ...
cfmDir = true; // ... the next
threshold is the positive one.
}
if(cfmTime > 0) { // If the frequency
measurement is running ...
cfmTime++; } // increase time.
}
/*** Power calculation ***/
if(muxCnt == 0) { // Result Analog null reference
input ready
nullVal = long(adcInt); // Store null reference
long TadcVal = long(adcVolt) * long(adcCurr); // Calc. the instantanious
power ...
primMeanPow += TadcVal; // ... and primary add it.
if(TadcVal > primMaxPow) { // Store new maximum power
if greater
primMaxPow = TadcVal; }
if(TadcVal < primMinPow) { // Store new minimum power
if smaller
primMinPow = TadcVal; }
}
/*** Transfer primary averaged values ***/
if(muxCnt == 1) { // At a quiet moment ...
(Analog channel 7 isn't used)
primCnt++; // increase primary
averaging counter
if(primCnt >= primAvLength) { // If the required averaging
number is reached ...
primCnt = 0;
primMeanVoltCopy = primMeanVolt; // Make a copy of all the
primary averaging values ...
primMeanVolt = 0; // ... and clear the primary
averaging value.
primSquaredVoltCopy = primSquaredVolt;
primSquaredVolt = 0;
primMeanCurrCopy = primMeanCurr;
primMeanCurr = 0;
primSquaredCurrCopy = primSquaredCurr;
primSquaredCurr = 0;
primMeanPowCopy = primMeanPow;
primMeanPow = 0;
primReady = true; // The primary averaging
values are availeble for the secondary averaging.
}
vCnt++; // value count +1 // Increase value counter
/*** Button debouncing ***/
byte buttons = ~PINB & 0x1E; // Read the four buttons
if(buttons == 0) { // If no button is pressed
...
if(antiBounceCnt == 0) { // ... and anti bounce is
already zero ...
buttonState = false;
buttonPushed = 0; // clear the button pressed
register
}
else {
antiBounceCnt--; } // decrease anti bounce
counter if it is not zero
}
else {
if(antiBounceCnt < 40) {
antiBounceCnt++; } // Increase anti bounce
counter if not maximum
else if(buttonState == false) { // If anti bounce reaches
maximum the first time ...
buttonState = true;
buttonPushed = buttons; // ... copy button status
for further processing
}
}
}
/*** Time ***/
preTimeCnt++;
if(preTimeCnt >= 19231) { // Seconds counter prescaler
preTimeCnt = 0;
timeCnt++; // Seconds counter
}
//bitClear(PORTB, 5); // overflow LED off
}

void lcdInitiate() {
/*** Initialize 2*16 char LCD ***/
/*** LCD DB4...7 => PD4...7 (D4...7), RS => PD2 (D2), R/W => PD3 (D3), E =>
PB0 (D8) ***/
bitClear(PORTB, 0); // LCD enable = low
bitSet(DDRB, 0); // LCD enable = output
PORTD = PORTD & 0x03; // LCD D4...7 & R/W & RS = low
DDRD = DDRD | 0xFC; // LCD D4...7 & R/W & RS = output
muDelay(50000);
byte PD = PORTD & 0x03; // LCD in 4 bit mode
PORTD = PD | 0x20;
__asm__("nop\n\t");
bitSet(PORTB, 0); // E high
__asm__("nop\n\tnop\n\tnop\n\tnop\n\t");
bitClear(PORTB, 0); // E low
__asm__("nop\n\tnop\n\t");
// Write function set three times, else the second line isn't functional.
lcdWrite(true, 0x28); // 2 line mode, 5*7 dots
muDelay(40);
lcdWrite(true, 0x28); // 2 line mode, 5*7 dots
muDelay(40);
lcdWrite(true, 0x28); // 2 line mode, 5*7 dots
muDelay(40);
lcdWrite(true, 0x0C); // display on, cursor off, blink off
lcdWrite(true, 0x01); // display clear
lcdWrite(true, 0x06); // increment mode, entire shift off
}

void lcdPrintString(const char* strArr) {


/*** Write a text string to the LCD ***/
for(byte ci=0; ci<strlen(strArr); ci++) {
lcdWrite(false, strArr[ci]);
}
}

void lcdWrite(boolean reg, byte val) {


/*** write byte to LCD in nibble mode ***/
/*** reg: false = data, true = command ***/
lcdCheckBusyFlag();
byte loni = val << 4; // prepare LS nibble
byte hini = val & 0xF0; // prepare MS nibble
byte PD = PORTD & 0x03; // R/W = 0, RS = 0, DB4...7 = 0
if (reg == false) {
PD = PD | 0x04; } // RS = 1
PORTD = PD | hini; // MS nibble to LCD
DDRD = DDRD | 0xF0; // PD4...7 = output
//__asm__("nop\n\tnop\n\t");
bitSet(PORTB, 0); // E = 1
__asm__("nop\n\tnop\n\tnop\n\t");
bitClear(PORTB, 0); // E = 0
__asm__("nop\n\tnop\n\t");
PORTD = PD | loni; // LS nibble to LCD
//__asm__("nop\n\t");
bitSet(PORTB, 0); // E = 1
__asm__("nop\n\tnop\n\tnop\n\t");
bitClear(PORTB, 0); // E = 0
__asm__("nop\n\tnop\n\t");
DDRD = DDRD & 0x0F; // PD4...7 = input
}

void lcdCheckBusyFlag() {
/*** check until LCD is ready ***/
byte dVal;
DDRD = DDRD & 0x0F; // PD4...7 = input
bitSet(PORTD, 3); // R/W = 1
bitClear(PORTD, 2); // RS=0
do {
bitSet(PORTB, 0); // enable = 1
__asm__("nop\n\tnop\n\t");
dVal = PIND & 0xF0; // Read MS nibble
bitClear(PORTB, 0); // enable = 0
__asm__("nop\n\tnop\n\tnop\n\t");
bitSet(PORTB, 0); // enable = 1
__asm__("nop\n\tnop\n\t");
dVal = dVal | (PIND >> 4); // Read LS nibble
bitClear(PORTB, 0); // enable = 0
__asm__("nop\n\tnop\n\tnop\n\t");
} while(bitRead(dVal, 7) == HIGH);
bitClear(PORTD, 3); // R/W = 0
}
void lcdCustomChars() {
/*** Make some custom characters ***/
lcdWrite(true, 0x48); // Char 0x01 = phi
lcdWrite(false, 0x02);
lcdWrite(false, 0x15);
lcdWrite(false, 0x15);
lcdWrite(false, 0x0E);
lcdWrite(false, 0x04);
lcdWrite(false, 0x04);
lcdWrite(false, 0x04);
lcdWrite(false, 0x00);
lcdWrite(true, 0x50); // Char 0x02 = deg
lcdWrite(false, 0x0C);
lcdWrite(false, 0x12);
lcdWrite(false, 0x12);
lcdWrite(false, 0x0C);
lcdWrite(false, 0x00);
lcdWrite(false, 0x00);
lcdWrite(false, 0x00);
lcdWrite(false, 0x00);
lcdWrite(true, 0x80); // First char address
}

void lcdTechNot(float fval, long lval, int rangeExp, int digits, const char*
unit, boolean overFlow, byte pos, boolean alignR) {
/*** Write a value in Technical Notation on the LCD ***/
/*** fval = float value, ore use lval = long int value ***/
/*** rangeExp = exponent of the limit value (e.g. rangeExp=2 => 10^2=100 as
limit), 99=autorange, 100 = use the long int value ***/
/*** digits = number of digits ***/
/*** unit = the unit: "V", "A",... ***/
/*** overFlow = external overflow indicator, e.g. from ADC. Puts the "^"
char between number and unit ***/
/*** pos & alignR = LCD position & right align: last position if
alignR=true, first position if alignR=false) ***/
byte totChar = 11; // Total number of chars to fill
on the LCD (to ensure the old valu is erased)
char valStr[totChar]; // Text string
int strPnt = 0; // Text string pointer
digits -= 1;
int decpoint = digits + 3; // No decimal point if val is
long
char vvArr[] = {'f','p','n',0xE4,'m',' ','k','M','G','T','P'}; // prefix
array
int expo; // Exponent
if (rangeExp == 99) { // If auto range ...
expo = floor(log10(fabs(fval))); // ... use the value exponent ...
}
else {
expo = rangeExp - 1; // ... else use a fixed exponent
as range
}
if(rangeExp != 100) { // If the float value is used ...
lval = long(round(fval / pow(10, expo - digits))); // ... calculate the
value in N digits.
if(lval >= round(pow(10, digits + 1))) { // Fix floating
point errors
lval /= 10;
expo++;
}
decpoint = digits - ((expo+15) % 3); // Calc. decimal point position
}
else { // If the long int value is used
...
expo = 0; // ... prevent using a prefix.
}
if(lval < 0) { // If the value is negative ...
lval = abs(lval); // ... make the value absolute
...
valStr[strPnt++] = 0x2D; // ... and put a negative sign =>
string
}
long dec;
byte num;
boolean prtStart = false; // Don't push any number into the
string yet (no leading zero's)
for(int nd=digits; nd>=0; nd--) { // For the number of digits the
value is long ...
dec = long(round(pow(10, nd))); // calc. the 10th power number
for the current digit,
num = 0; // start value is 0.
while(lval >= dec) { // While the 10th power number is
smaller than the value ...
lval -= dec; // decrease the value,
num++; // increase the current digit.
if(num > 9) { // If digit > 9 ...
num = 0x3C; // digit is "<" if value doesn't
fit
lval += dec; // for all digit places
break;
}
}
if(prtStart == true || num != 0 || nd == 0) { // If printing is active
or digit is not zero or LS digit
prtStart = true; // printing is active,
valStr[strPnt++] = num | 0x30; // digit => string.
}
else if(prtStart == false && decpoint == nd) { // If no number is printed
yet and decimal point must be printed
prtStart = true; // printing is active,
valStr[strPnt++] = 0x30; // 0 => string.
}
if(decpoint == nd && nd != 0) { // If decimal point must
be printed and it is not the LS digit
valStr[strPnt++] = 0x2E; // decimal point =>
string.
}
}
if(overFlow == true) {
valStr[strPnt++] = 0x5E; // "^" => string if there the
external overflow flag is set
}
else {
valStr[strPnt++] = 0x20; // or a white space => string
}
int ediv = floor(expo / 3.0); // Calc. prefix pointer
if (ediv != 0) {
valStr[strPnt++] = vvArr[ediv+5]; // Prefix => string
}
byte ci;
for(ci=0; ci<strlen(unit); ci++) { // For every unit character ...
valStr[strPnt++] = unit[ci]; // ... unit char = string.
}
if(alignR == true) { // If the alignment is right ...
pos -= totChar; // ... calc. new start position.
}
lcdWrite(true, pos); // Position cursor LCD
for(byte iw=0; iw<(totChar-strPnt); iw++) { // For every unoccupied
preceding character ...
lcdWrite(false, 0x20); // ... send white spaces to LCD to
erase old text
}
for(ci=0; ci<strPnt; ci++) { // For every character ...
lcdWrite(false, valStr[ci]); // ... send character to LCD
}
}

void muDelay(unsigned int mutime) {


/*** micro seconds delay ***/
for(unsigned int ci=0; ci<mutime; ci++) {

__asm__("nop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\t");
}
}

You might also like