Asynchronous Emonlib code?

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 :grinning_face_with_smiling_eyes:

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
}



   
 

Welcome, Jim, to the OEM forum.

There is the emonLibCM library - did you know about that? It still reports at preset intervals (every 10 s by default, but it can come down to below 1 s), and it samples continuously (“Continuous Monitoring”). In between the ISR taking and processing samples every 104 µs, the main loop is available for roughly half the time. So provided everything else you want to do in the main loop can be interrupted by the ISR, you have about half the processor time to handle the final calculations and your displays etc.

EmonLibCM is used in the standard emonTx sketch and in the emonPiCM sketch.
emonLibCM is here: EmonLibCM - Version 2.2.1 (re-Released 5/12/2021) I don’t think this version has made it to Github yet.

Hi Robert,

It sounds like you’ve come up with what I’m looking for, but I don’t yet understand how any of the example sketches work. I’m just trying to measure current and voltage with an Arduino for a single appliance for now. I’ll have a better look at it when I get time.

Thanks!