calcIrms sampling rate slower than expected

Hi all,

I am trying to sample current at a high rate (~kHz). From an earlier thread, it seems that calcIrms() can sample at around 5kHz. However, when I tested it, I can only get around 400Hz.
The earlier thread: Sampling rate of emonLib

I am quite new to emonPi and Arduino. In general, what is the highest possible sampling rate we can get with the emonPi setup? Any suggestions would be greatly appreciated!

Setup: Recently purchased EmonPi (emonSD-26Oct17)
Code change:

  1. remove everything in ~/emonpi/firmware/src/
  2. copy my own src.ino to ~/emonpi/firmware/src/
  3. compile and upload the firmware with bash ~/emonpi/firmware/upload.sh (shows SUCCESS)
  4. check the output from ttyAMA0 using pio device monitor

Testing code src.ino (modified from an EmonLib example)

#include "EmonLib.h"                   // Include Emon Library
#include <Wire.h>

EnergyMonitor emon1;                   // Create an instance

unsigned long t_start =0;
unsigned long n_samp =0;
double data_rate =0;

void setup()
{
  Serial.begin(9600);

  emon1.current(1, 111.1);             // Current: input pin, calibration.
  t_start = millis();
}

void loop()
{
  double Irms = emon1.calcIrms(1);  // Calculate Irms only
  n_samp++;

  if (n_samp % 1000 == 0) {
    data_rate = (double)n_samp / ((double)(millis()-t_start) / 1000.0);
    Serial.print("data rate\t");
    Serial.println(data_rate);
    t_start = millis();
    n_samp = 0;
  }
}

Test output

pi@emonpi(rw):firmware$ pio device monitor -b 9600

--- Available ports:
---  1: /dev/ttyAMA0         ttyAMA0
--- Enter port index or full name: 1
--- Miniterm on /dev/ttyAMA0  9600,8,N,1 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
data rate       398.88
data rate       399.04
data rate       398.88
data rate       398.88

I am not at all surprised at that, having looked at your code.

It should be exactly the same as an emonTx.

Try using the loop inside calcIrms. As it is, you’re calculating the rms average over one sample, which is not only giving you the overhead of the calculation on every sample, it is also totally meaningless. To get the true rate, set one call up for (say) 100 samples and time that, then for 1100 samples and time that. Subtract the two times, and you get the sample rate for 1000 samples without the processing overhead of calculating the rms value and returning the number.

1 Like

Thanks, @Robert.Wall! What you said makes a lot of sense. I followed the suggestions and now the sample rate (considering only the for loop in calcIrms()) is around 5.5 kHz, similar to what you measured in Sampling rate of emonLib.

Here’s the new test code src.ino:

#include "EmonLib.h"                   // Include Emon Library
#include <Wire.h>

EnergyMonitor emon1;                   // Create an instance

unsigned long t_start, t1, t2, t_out, n_avg = 0;
double t1_avg, t2_avg, t_out_avg, t_in_avg = 0;
int n_inner_loop = 1000;

void setup()
{
  Serial.begin(9600);

  emon1.current(1, 111.1);             // Current: input pin, calibration.
  t_start = millis();
}

void loop()
{
  t_out += millis() - t_start;

  t_start = millis();
  double Irms1 = emon1.calcIrms(100);  // Calculate Irms only
  t1 += millis() - t_start;

  t_start = millis();
  double Irms2 = emon1.calcIrms(100 + n_inner_loop);  // Calculate Irms only
  t2 += millis() - t_start;

  n_avg++;
  if (n_avg == 100) {
    t1_avg = (double)t1 / n_avg ;
    t2_avg = (double)t2 / n_avg ;
    t_out_avg = (double)t_out / n_avg ;
    t_in_avg = (t2_avg - t1_avg) / (double)n_inner_loop;

    Serial.print("n_inner_loop\t\t");
    Serial.println(n_inner_loop);
    Serial.print("100 loops (ms)\t\t");
    Serial.println(t1_avg);
    Serial.print("1100 loops (ms)\t\t");
    Serial.println(t2_avg);
    Serial.print("out side loop (ms)\t");
    Serial.println(t_out_avg);
    Serial.print("total (ms)\t\t");
    Serial.println(t1_avg + t2_avg + t_out_avg);
    Serial.print("1 inner loop (ms)\t");
    Serial.println(t_in_avg);
    Serial.print("sample rate\t\t");
    Serial.println(1.0/(t_in_avg * 1e-3));
    Serial.println();
    t1 = 0;
    t2 = 0;
    t_out = 0;
    n_avg = 0;
  }
  t_start = millis();
}

Test output:

n_inner_loop            1000
100 loops (ms)          20.21
1100 loops (ms)         199.91
out side loop (ms)      0.00
total (ms)              220.12
1 inner loop (ms)       0.18
sample rate             5564.83

For completeness, I also checked why I got 400 Hz with the previous code. It’s because when Number_of_Samples = 1, the codes outside the loop takes almost 93% of the time of calcIrms(). When I made calcIrms to take only 1 and 2 samples (with proper n_avg), i.e., these two lines

  double Irms1 = emon1.calcIrms(1);  // Calculate Irms only
  double Irms2 = emon1.calcIrms(2);  // Calculate Irms only

the test output becomes:

n_inner_loop            1
1 loops (ms)            2.47
2 loops (ms)            2.65
out side loop (ms)      0.00
total (ms)              5.12
1 inner loop (ms)       0.18
sample rate             5452.56

The sample rate computed from the previous codes would be 5452 * 0.18 / 2.47 =~ 400 (Hz)

To get an accurate rms average, you must measure as close as possible over a whole number of cycles of mains electricity. If you do not do that, then even though the current remains constant, you will read a different value each time that depends on where your set of samples start and stop in relation to the a.c. wave. This is why it is important to choose the correct number of samples for calcIrms().

Chen-Yu Hsu Sir ,can you please explain this same process for calcVI(20,2000) because this is important for me.

Please reply Sir.

This is explained in the ‘Learn’ section of this website. You can download the source code for emonLib from GitHub - the link is in the ‘Resources’ section, and read the comments in the code, which explain the calculation in detail.