Hello, I’m new here
I’m trying to create an energy monitor using a single Arduino using the Emonlib library and an LCD display + current and voltage sensor. I want to be able to seamlessly flick through a menu; between current, voltage, watts, ect, while also having the energy being monitored in the background on the arduino. i.e. I want the calculation of the current and voltage to be non-blocking or Asynchronous . Basically exactly like an ordinary wattage monitor, like a Kill-A-Watt.
I did end up having a thorough look through the library and edited a version of the code (using Arduino IDE) that gets rid of the for and while loops. It also uses a non-blocking version of the analogRead function (found here; https://www.gammon.com.au/adc). The internal supplyvoltage calculation also gets rid of a while loop.
However, would this method even work, or does having multi-tasking just screw with the readings too much? If so, I can easily just hook up an Attiny to the arduino and communicate with I2C…
I tested my code by comparing the current (Irms) readings between the blocking and non-blocking code. I used a 100amp non-invasive sensor, with a 7 amp hairdryer. Both methods gave nearly identical results, but I really can’t be sure if I’m doing it correctly as I’m unfamiliar with a lot of these functions pertaining to analog readings.
Let me know if I’ve just wasted hours of my time
Thank you!
#include "EmonLib.h"
#include "Arduino.h"
long SupplyVoltage;
bool adc_conversion_working = false;
boolean Measure_Supply_volts = true;
bool working;
const byte adcPin = 1; // = 14 (pins_arduino.h)
int Number_of_Samples = 1480;
double offsetI;
double sampleI;
double filteredI;
double sqI;
double sumI;
float ICAL = 60.3;
double NON_BLOCK_CURRENT;
int n = 0;
boolean startnewsample = true;
boolean BlockingCode = false;
EnergyMonitor emon1;
void setup() {
Serial.begin(9600);
offsetI = ADC_COUNTS>>1;
// this statement used before every voltage reading
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__) || defined (__AVR_ATmega328P__)
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _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
}
void loop() {
//VERSION 1; NON-BLOCKING FUNCTION;
if(BlockingCode == false){
//STEP 1; collect supply volts
if(Measure_Supply_volts == true){
// non-blocking voltage measuremeant inferred from https://www.arduinoslovakia.eu/blog/2018/9/a/d-prevodnik---meranie-bez-blokovania?lang=en
// this gets rid of the while "(bit_is_set(ADCSRA, ADSC)); function"
if (!adc_conversion_working) {
ADCSRA |= _BV(ADSC);
bitSet(ADCSRA, ADSC); // Start a conversion
adc_conversion_working = true;
}
// The ADC clears the bit when done
if (bit_is_clear(ADCSRA, ADSC)) {
SupplyVoltage = ADCL; // Read result
SupplyVoltage |= ADCH<<8;
SupplyVoltage = READVCC_CALIBRATION_CONST/SupplyVoltage;
adc_conversion_working = false;
Serial.println(" ");
Serial.println("non-blocking supply voltage");
Serial.print(SupplyVoltage); Serial.println(" VOLTS");
Serial.println(" ");
Measure_Supply_volts = false;
//voltage acquired, now start getting ADC samples
ADCSRA = bit (ADEN); // turn ADC on
ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2); // Prescaler of 128
ADMUX = bit (REFS0) | (adcPin & 0x07); // AVcc
}
}
//STEP 2: get samples
if(Measure_Supply_volts == false){
if (n < Number_of_Samples) //start collecting samples
{
if(startnewsample == true){ //aquire a single analog reading
if (!working)
{
bitSet (ADCSRA, ADSC); // start a conversion
working = true;
}
// the ADC clears the bit when done
if (bit_is_clear(ADCSRA, ADSC))
{
sampleI = ADC; // read result
working = false;
//Serial.println (value);
//delay (500);
startnewsample = false; //acquired sample, now get the next sample, or start calculation
}
}
else{
offsetI = (offsetI + (sampleI-offsetI)/1024);
filteredI = sampleI - offsetI;
// Root-mean-square method current
// 1) square current values
sqI = filteredI * filteredI;
// 2) sum
sumI += sqI;
n = n + 1;
startnewsample = true;
}
}
else if(n == Number_of_Samples){ //collected all 1480 samples
// STEP 3: calculate the current
double I_RATIO = ICAL *((SupplyVoltage/1000.0) / (ADC_COUNTS));
NON_BLOCK_CURRENT = I_RATIO * sqrt(sumI / Number_of_Samples);
//Reset accumulators
sumI = 0;
n = 0;
//
Serial.println(" ");
Serial.println("non-blocking current");
Serial.print(NON_BLOCK_CURRENT);Serial.println(" AMPS");
Serial.println(" ");
delay(500);
Measure_Supply_volts = true;
BlockingCode = true; //Commence blocking code
}
}
}
//VERSION 2; BLOCKING FUNCTION
if(BlockingCode == true){
emon1.current(1 ,60.3);
double Irms = emon1.calcIrms(1480);
int BlockingSupplyVoltage = readVcc();
Serial.println(BlockingSupplyVoltage);
Serial.println("");
Serial.println("blocking current");
Serial.print(Irms); Serial.println(" AMPS");
Serial.println("");
Serial.println("blocking Voltage reading");
Serial.print(BlockingSupplyVoltage);Serial.println(" VOLTS");
Serial.println("");
delay(500);
BlockingCode = false;
//commence the non-blocking code again
}
}
long 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_ATmega644__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _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
}