Author Topic: Arduino Code - volts/amps/power monitor  (Read 6984 times)

0 Members and 1 Guest are viewing this topic.

Offline metrologistTopic starter

  • Super Contributor
  • ***
  • Posts: 2199
  • Country: 00
Arduino Code - volts/amps/power monitor
« on: March 08, 2018, 10:01:19 pm »
I've been working on this for my solar system. It will display the panel voltage, current, and power consumed.

I wanted some affirmation that the averaging routing and other calculations are correct. I've padded dummy data for the sensor reads. I'm noticing a steady 0.02 or 0.03 WHr discrepancy.

Code: [Select]
#include "U8glib.h"
//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0); // I2C / TWI
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST); // Fast I2C / TWI
//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NO_ACK); // Display which does not send AC

float amps = 0;
float volts = 0;
float v = 0;
float a = 0;
float maxAmps = 0;
float minAmps = 10;
float maxVolts = 0;
float minVolts = 50;
float lastAmps = 0;
float lastVolts = 0;
float anoise = 0;
float vnoise = 0;
float ah = 0;
float watts = 0;
float whr;
float time0 = 1;
float time1 = 1;
float count = 0;

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

  // flip screen, if required
  // u8g.setRot180();

  // assign default color value
  if ( u8g.getMode() == U8G_MODE_R3G3B2 ) {
    u8g.setColorIndex(255);     // white
  }
  else if ( u8g.getMode() == U8G_MODE_GRAY2BIT ) {
    u8g.setColorIndex(3);         // max intensity
  }
  else if ( u8g.getMode() == U8G_MODE_BW ) {
    u8g.setColorIndex(1);         // pixel on
  }
  else if ( u8g.getMode() == U8G_MODE_HICOLOR ) {
    u8g.setHiColorByRGB(255,255,255);
  }

  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  pinMode(8, OUTPUT);
}

void loop(void) {

  if (millis() - time0 >= 100){ // fudge averaging
    time0 = millis();
    count=count+1.0;
    //a = (analogRead(A0)- 509) * 26.7 / 1023;
    //v = analogRead(A1) * 4.6 / 1023;
    a = 60; //temporary test value
    v = 10; //temporary test value
    amps = amps + a;
    volts = volts + v;
    maxAmps = max(maxAmps, a);
    minAmps = min(minAmps, a);
    anoise = (maxAmps - minAmps);
    maxVolts = max(maxVolts, v);
    minVolts = min(minVolts, v);
    vnoise = (maxVolts - minVolts);
    if (millis() - time1 >= 5000){
      amps = amps/count;
      volts = volts/count;
      watts = amps * volts;
      ah = ah + ((amps) * (millis() - time1)) / 3600000;
      whr = whr + ((watts) * (millis() - time1)) / 3600000;
      time1 = millis();
      serial();
      lcd();   
      count = 0;
      amps = 0;
      volts = 0;
      lastAmps = amps;
      lastVolts = volts;
      maxAmps = 0;
      minAmps = 10;
      maxVolts = 0;
      minVolts = 50;

    }
  }
}

void serial(void){
  Serial.print(amps);
  Serial.print("A  ");
  Serial.print(ah);
  Serial.print("Ah  ");
  Serial.print(volts);
  Serial.print("V  ");
  Serial.print(watts);
  Serial.print("W  ");
  Serial.print(whr);
  Serial.print("Whr    Noise: ");
  Serial.print(vnoise);
  Serial.print("Vn  ");
  Serial.print(anoise);
  Serial.println("An");
  Serial.print(maxVolts);
  Serial.print("Vmax  ");
  Serial.print(minVolts);
  Serial.print("Vmin  ");
  Serial.print(maxAmps);
  Serial.print("Amax  ");
  Serial.print(minAmps);
  Serial.println("Amin  ");
  Serial.println(".");
}

void lcd(void){
  // picture loop
  u8g.firstPage(); 
  do {
    draw1();
  }
  while( u8g.nextPage() );
}

void draw0(void) {
}

void draw1(void) {
  //graphic commands to redraw the complete screen should be placed here 
  //  u8g.setFontPosTop();
  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 14);
  u8g.print(volts);
  u8g.setFont(u8g_font_unifont);
  u8g.print("V");

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(66, 14);
  u8g.print(amps);
  u8g.setFont(u8g_font_unifont);
  u8g.print("A");

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 31);
  u8g.print(ah);
  u8g.setFont(u8g_font_unifont);
  u8g.print(" AHr");
    u8g.print((int)count);

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 48);
  u8g.print(watts);
  u8g.setFont(u8g_font_unifont);
  u8g.print(" watts");

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 64);
  u8g.print(whr);
  u8g.setFont(u8g_font_unifont);
  u8g.print(" WHr");
  u8g.print(millis()/6000.0);

}
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9886
  • Country: us
Re: Arduino Code - volts/amps/power monitor
« Reply #1 on: March 08, 2018, 10:40:44 pm »
You're doing a lot of mixed mode arithmetic (it seems to me!).  If you want to use floating point then make certain that all values in an expression are floating point.  Float your constants or add a decimal point.

We just had a thread where mixed mode arithmetic cause substantial errors.  I would expect the compiler to promote integer constants to floating point when used with other floating point values in an evaluation but I guess we can't guarantee it.
 

Offline Wimberleytech

  • Super Contributor
  • ***
  • Posts: 1133
  • Country: us
Re: Arduino Code - volts/amps/power monitor
« Reply #2 on: March 08, 2018, 10:53:42 pm »
Ditto what rstofer said...
A good example is your use of the variable count.  It is typed as a float and in one case assigned a float and in another an int.

A counter is typically typed as an int but you may have been worried about mixed arithmetic and so you typed it as a float but were not consistent.

You could have typed it as an int and then use a cast to promote it to a float when doing an operation with a float.  Doing so saves some compute cycles.
 

Offline frozenfrogz

  • Frequent Contributor
  • **
  • Posts: 936
  • Country: de
  • Having fun with Arduino and Raspberry Pi
Re: Arduino Code - volts/amps/power monitor
« Reply #3 on: March 08, 2018, 11:05:09 pm »
The discrepancy you are describing is in respect to actual data compared to a power meter or from a fed data set vs. what you calculated yourself?

If I understand correctly, you are polling every 0.1 seconds and calculating the cumulative moving average after 5 seconds.
As rstofer pointed out, feeding integer values to floating point variables might or might not work as expected.
He’s like a trained ape. Without the training.
 

Offline bitseeker

  • Super Contributor
  • ***
  • Posts: 9057
  • Country: us
  • Lots of engineer-tweakable parts inside!
Re: Arduino Code - volts/amps/power monitor
« Reply #4 on: March 08, 2018, 11:05:34 pm »
Floating point arithmetic has many gotchas including inaccuracies (potentially huge ones) and performance penalties. If possible, you'll benefit from using and operating on either integer or fixed-point values instead. For example, if you need 1/1000 resolution, convert everything into millis (ms, mV, mA, etc.), accumulate and compute on them, and convert to "normal" values only on output.
TEA is the way. | TEA Time channel
 
The following users thanked this post: paulca

Offline frozenfrogz

  • Frequent Contributor
  • **
  • Posts: 936
  • Country: de
  • Having fun with Arduino and Raspberry Pi
Re: Arduino Code - volts/amps/power monitor
« Reply #5 on: March 08, 2018, 11:20:06 pm »
I do not think it is very relevant in this case, but Arduino floating point math uses Binary32 for conversion - 4 byte variables. That means, there is always significant conversion error. In other words: 6 to 7 decimal digits of precision.
He’s like a trained ape. Without the training.
 

Offline metrologistTopic starter

  • Super Contributor
  • ***
  • Posts: 2199
  • Country: 00
Re: Arduino Code - volts/amps/power monitor
« Reply #6 on: March 08, 2018, 11:20:57 pm »
I had not realized that "float count=1;" could result in count being an integer.

I think my error was where I divided millis()/6000.0 as that blows my time interval. I should have used time1/6000.0. The error was not accumulating.

Yes, I am polling sensors every 0.1 and adding the values until 2 seconds of data, then I find the average. But, I am not actually polling sensors yet as I wanted to ensure my averaging was going to work correctly, so I just use fixed data that works out math-wise with a simple counter.

I think I corrected all the concerns.

Code: [Select]
#include "U8glib.h"
//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0); // I2C / TWI
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST); // Fast I2C / TWI
//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NO_ACK); // Display which does not send AC

float amps = 0.0;
float volts = 0.0;
float v = 0.0;
float a = 0.0;
float maxAmps = 0.0;
float minAmps = 10.0;
float maxVolts = 0.0;
float minVolts = 50.0;
float lastAmps = 0.0;
float lastVolts = 0.0;
float anoise = 0.0;
float vnoise = 0.0;
float ah = 0.0;
float watts = 0.0;
float whr = 0.0;
float time0 = 1.0;
float time1 = 1.0;
float count = 0.0;

void setup(void) {
  Serial.begin(9600);
  // flip screen, if required
  // u8g.setRot180();

  // assign default color value
  if ( u8g.getMode() == U8G_MODE_R3G3B2 ) {
    u8g.setColorIndex(255);     // white
  }
  else if ( u8g.getMode() == U8G_MODE_GRAY2BIT ) {
    u8g.setColorIndex(3);         // max intensity
  }
  else if ( u8g.getMode() == U8G_MODE_BW ) {
    u8g.setColorIndex(1);         // pixel on
  }
  else if ( u8g.getMode() == U8G_MODE_HICOLOR ) {
    u8g.setHiColorByRGB(255,255,255);
  }

  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  pinMode(8, OUTPUT);
}

void loop(void) {
  if (millis() - time0 >= 100){ // fudge averaging
    time0 = millis();
    count=count+1.0;
    //a = (analogRead(A0)- 509) * 26.7 / 1023;
    //v = analogRead(A1) * 4.6 / 1023;
    a = 60.0; //temporary test value
    v = 10.0; //temporary test value
    amps = amps + a;
    volts = volts + v;
    maxAmps = max(maxAmps, a);
    minAmps = min(minAmps, a);
    anoise = (maxAmps - minAmps);
    maxVolts = max(maxVolts, v);
    minVolts = min(minVolts, v);
    vnoise = (maxVolts - minVolts);
    if (millis() - time1 >= 2000){
      amps = amps/count;
      volts = volts/count;
      watts = amps * volts;
      ah = ah + ((amps) * (millis() - time1)) / 3600000.0;
      whr = whr + ((watts) * (millis() - time1)) / 3600000.0;
      time1 = millis();
      serial();
      lcd();   
      count = 0.0;
      amps = 0.0;
      volts = 0.0;
      lastAmps = amps;
      lastVolts = volts;
      maxAmps = 0.0;
      minAmps = 10.0;
      maxVolts = 0.0;
      minVolts = 50.0;
    }
  }
}

void serial(void){
  Serial.print(amps);
  Serial.print("A  ");
  Serial.print(ah);
  Serial.print("Ah  ");
  Serial.print(volts);
  Serial.print("V  ");
  Serial.print(watts);
  Serial.print("W  ");
  Serial.print(whr);
  Serial.print("Whr    Noise: ");
  Serial.print(vnoise);
  Serial.print("Vn  ");
  Serial.print(anoise);
  Serial.println("An");
  Serial.print(maxVolts);
  Serial.print("Vmax  ");
  Serial.print(minVolts);
  Serial.print("Vmin  ");
  Serial.print(maxAmps);
  Serial.print("Amax  ");
  Serial.print(minAmps);
  Serial.println("Amin  ");
  Serial.println(".");
}

void lcd(void){
  // picture loop
  u8g.firstPage(); 
  do {
    draw1();
  }
  while( u8g.nextPage() );
}

void draw0(void) {
}

void draw1(void) {
  //graphic commands to redraw the complete screen should be placed here 
  //  u8g.setFontPosTop();
  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 14);
  u8g.print(volts);
  u8g.setFont(u8g_font_unifont);
  u8g.print("V");

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(66, 14);
  u8g.print(amps);
  u8g.setFont(u8g_font_unifont);
  u8g.print("A");

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 31);
  u8g.print(ah);
  u8g.setFont(u8g_font_unifont);
  u8g.print("AHr");
  u8g.print((long)count);

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 48);
  u8g.print(watts);
  u8g.setFont(u8g_font_unifont);
  u8g.print("W");

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 64);
  u8g.print(whr);
  u8g.setFont(u8g_font_unifont);
  u8g.print("WHr");
  u8g.print(time1/6000.0);
}
 

Offline frozenfrogz

  • Frequent Contributor
  • **
  • Posts: 936
  • Country: de
  • Having fun with Arduino and Raspberry Pi
Re: Arduino Code - volts/amps/power monitor
« Reply #7 on: March 08, 2018, 11:49:48 pm »
This will also cause problems I guess: The millis() function returns an unsigned long, while you are using float for your other time keeping variables. Very nasty overflow issues incoming...

Personally I would go with bitseekers advice and ditch floating point math in favor of scaled-to-needs integer math.
He’s like a trained ape. Without the training.
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1714
  • Country: se
Re: Arduino Code - volts/amps/power monitor
« Reply #8 on: March 09, 2018, 12:05:34 am »
Quote
I had not realized that "float count=1;" could result in count being an integer.
And you would have been right. If count is defined as float, it will not be an integer in any reasonable context, unless it's cast to one.
It can hold integer values, though.

In any operation involving a floating point type and an integer type, the integer will be converted to the real type
From the Bible, ISO/IEC 9899:1999, Chapter 6.3.1.8 Usual arithmetic conversions:
"[...long double and double conversions...]
Otherwise, if the corresponding real type of either operand is float, the other
operand is converted, without change of type domain, to a type whose
corresponding real type is float.
[...follows with integer conversions...]".

Of course, care must be taken to make sure that all operators where you need a float result have at least one float operand.

I see you have left time0, time1 and count as 32 bit single precision float variables.
This is really not needed, if the expression are written correctly.

Moreover, from your description, this looks like a system that can run for a long time, so I'll give you a little puzzle before I go to bed  :=\:

What happens after about 4h40'?
a couple of hints:
  • millis() returns an unsigned long int (32 bits)
  • A float can only represent exactly integers less than FLT_RADIX^FLT_MANT_DIG (from float.h)




« Last Edit: March 09, 2018, 12:10:05 am by newbrain »
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline frozenfrogz

  • Frequent Contributor
  • **
  • Posts: 936
  • Country: de
  • Having fun with Arduino and Raspberry Pi
Re: Arduino Code - volts/amps/power monitor
« Reply #9 on: March 09, 2018, 12:15:09 am »
In any operation involving a floating point type and an integer type, the integer will be converted to the real type

»If doing math with floats, you need to add a decimal point, otherwise it will be treated as an int. See the Floating point constants page for details.«

Taken from the Arduino reference.

Edit: The Arduino reference - as always - is unspecific AF. So who knows, what’s really happening when mixing int and float... :scared:
« Last Edit: March 09, 2018, 12:23:26 am by frozenfrogz »
He’s like a trained ape. Without the training.
 

Offline Rick Law

  • Super Contributor
  • ***
  • Posts: 3423
  • Country: us
Re: Arduino Code - volts/amps/power monitor
« Reply #10 on: March 09, 2018, 01:22:28 am »
I had not realized that "float count=1;" could result in count being an integer.
...

I don't think it results in count being integer.  count is float as declared.  But your ="1" is integer.  So compiler would convert 1 to float (1.0) to initialize.  However, depending on compiler, the compiler may not convert the same way the run-time library does.

Single number converts reasonably well but when your initialization value is from an expression, result may be a rather unexpected value.
 
...
I think my error was where I divided millis()/6000.0 as that blows my time interval. I should have used time1/6000.0. The error was not accumulating.

Yes, I am polling sensors every 0.1 and adding the values until 2 seconds of data, then I find the average. But, I am not actually polling sensors yet as I wanted to ensure my averaging was going to work correctly, so I just use fixed data that works out math-wise with a simple counter.

I think I corrected all the concerns.
...

At 0.1 second interval for 2 seconds, you have only 20 samples.  If you want to ensure minimal precision lost in accumulation, rather than accumulating an evaluated Volt/mA/whatever, why not accumulate the ADC value in integer and do the conversion at display or averageCalculation time.

With 20 samples of up to 10 bits each, unsigned_int16 will give you 2**6 samples (64 samples) without overflow.  When you get your 20 sample, that is when you assign your adc_int_total to adc_float_total then do all float arithmetic with only adc_float_total.

If you decide to use much more than 20 samples, use int_32.  You can have 2**22 samples of 10 bits each before your adc_total overflows.



« Last Edit: March 09, 2018, 01:25:08 am by Rick Law »
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1714
  • Country: se
Re: Arduino Code - volts/amps/power monitor
« Reply #11 on: March 09, 2018, 09:09:31 am »
In any operation involving a floating point type and an integer type, the integer will be converted to the real type

»If doing math with floats, you need to add a decimal point, otherwise it will be treated as an int. See the Floating point constants page for details.«

Taken from the Arduino reference.

Edit: The Arduino reference - as always - is unspecific AF. So who knows, what’s really happening when mixing int and float... :scared:
The contents of that page are horrible: both unclear and misleading.
Frankly, between the Arduino website and an ISO standard, I know where to put my trust  :horse:
The gcc compiler used in Arduino is standard conforming enough not to play tricks on this simple stuff.
Note: I wrongly referenced a C standard, rather than then a C++ one, but the behaviour for built-in types is the same in this context, as are the contents of <float.h>.

Quote
Floating point numbers are not exact, and may yield strange results when compared. For example 6.0 / 3.0 may not equal 2.0. You should instead check that the absolute value of the difference between the numbers is less than some small number.
[...8<...]
If doing math with floats, you need to add a decimal point, otherwise it will be treated as an int. See the Floating point constants page for details.
The first paragraph above gives correct advice (compare with a small delta), but if you are able to find a floating point implementation that gets 6.0/2.0 wrong, I'll pay you a beer (or other beverage of your choice).
It is true that the standard does not promise much about precision, but any decent FP implementation will give the expected result with integers that are exactly representable in the float (one would really need to go out of their way to have an imprecise result...).

That second paragraph is so lacking in context to be meaningless.
First of all, it switched from talking about the semantic and behaviour of floating point values (e.g. as stored in a variable) to describing a specific syntax accident in float literals (i.e. how you inform the compiler that a literal is FP).
That said, it's also very misleading, as an integer number literal will be happily converted to float in binary operation if the other  operand is a float, and operator overloading is not an option for built-in types.

So "who knows..." is only true if one relies on dodgy documentation - this is not to say that the matter is not confusing for a newcomer!

Finally  :blah:, my personal preference is very often fixed point, as bitseeker suggests, when doing simple arithmetic.
Possibly, I'll add one or two extra digits to help rounding.
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline metrologistTopic starter

  • Super Contributor
  • ***
  • Posts: 2199
  • Country: 00
Re: Arduino Code - volts/amps/power monitor
« Reply #12 on: March 09, 2018, 06:43:21 pm »
OK, thanks all. I tried to take some of the points into account and clean up the code some.

Code: [Select]
#include "U8glib.h"
//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0); // I2C / TWI
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST); // Fast I2C / TWI
//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NO_ACK); // Display which does not send AC

float amps = 0.0;
float volts = 0.0;
long v = 0;
long a = 0;
float maxAmps = 0;
float maxVolts = 0;
float ah = 0.0;
float watts = 0.0;
float whr = 0.0;
long time0 = 0;
long time1 = 0;
float count = 0.0;

void setup(void) {
  Serial.begin(9600);
  // flip screen, if required
  // u8g.setRot180();

  // assign default color value
  if ( u8g.getMode() == U8G_MODE_R3G3B2 ) {
    u8g.setColorIndex(255);     // white
  }
  else if ( u8g.getMode() == U8G_MODE_GRAY2BIT ) {
    u8g.setColorIndex(3);         // max intensity
  }
  else if ( u8g.getMode() == U8G_MODE_BW ) {
    u8g.setColorIndex(1);         // pixel on
  }
  else if ( u8g.getMode() == U8G_MODE_HICOLOR ) {
    u8g.setHiColorByRGB(255,255,255);
  }

  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  pinMode(8, OUTPUT);
}

void loop(void) {
  if (millis() - time0 >= 100){ // fudge averaging by controlling sample rate
    time0 = millis();
    count++;
    a = a+analogRead(A0)-509; //sensor reads +/- with 0 near midpoint of adc
    v = v+analogRead(A1);

    if (millis() - time1 >= 2000){
      amps = (float)a*26.7/(count*1023.0);
      volts = (float)v*5.0/(count*1023.0);
      watts = amps * volts;
      ah = ah + ((amps) * (millis() - time1)) / 3600000.0;
      whr = whr + ((watts) * (millis() - time1)) / 3600000.0;
      time1 = millis();
      maxAmps = max(maxAmps, amps);
      maxVolts = max(maxVolts, volts);
      serial();
      lcd();   
      count = 0.0;
      a = 0;
      v = 0;
    }
  }

  if (millis()>86400000){
    maxAmps=0;
    maxVolts=0;
  }
}

void serial(void){
  Serial.print(count);
  Serial.print("\taverages\t");
  Serial.print(volts);
  Serial.print("\tV\t");
  Serial.print(amps);
  Serial.print("\tA\t");
  Serial.print(ah);
  Serial.print("\tAh\t");
  Serial.print(watts);
  Serial.print("\tW\t");
  Serial.print(whr);
  Serial.print("\tWhr\t");
  Serial.print(maxVolts);
  Serial.print("\tVmax\t");
  Serial.print(maxAmps);
  Serial.print("\tAmax\t");
  Serial.print(time1/60000.0);
  Serial.println("\tmin\t");
}

void lcd(void){
  // picture loop
  u8g.firstPage(); 
  do {
    draw1();
  }
  while( u8g.nextPage() );
}

void draw0(void) {
}

void draw1(void) {
  //graphic commands to redraw the complete screen should be placed here 
  //  u8g.setFontPosTop();
  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 14);
  u8g.print(volts);
  u8g.setFont(u8g_font_unifont);
  u8g.print("V");

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(66, 14);
  u8g.print(amps);
  u8g.setFont(u8g_font_unifont);
  u8g.print("A");

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 31);
  u8g.print(ah);
  u8g.setFont(u8g_font_unifont);
  u8g.print("AHr ");
  u8g.print(maxAmps*maxVolts);

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 48);
  u8g.print(watts);
  u8g.setFont(u8g_font_unifont);
  u8g.print("W");

  u8g.setFont(u8g_font_gdr14);
  u8g.setPrintPos(0, 64);
  u8g.print(whr);
  u8g.setFont(u8g_font_unifont);
  u8g.print("WHr ");
  u8g.print(time1/60000.0);
}
[\code]
 

Offline frozenfrogz

  • Frequent Contributor
  • **
  • Posts: 936
  • Country: de
  • Having fun with Arduino and Raspberry Pi
Re: Arduino Code - volts/amps/power monitor
« Reply #13 on: March 09, 2018, 07:00:36 pm »
Your vars time0/1 should be type «unsigned long» and not «long» since you are going to assign millis() to them (see above).

Also, this does not catch your overflow in a sane way - or I simply do not understand what you want to do here.

Quote
if (millis()>86400000){
    maxAmps=0;
    maxVolts=0;
  }

I’d like to take a closer look at your calculations and propose what I would do, but that will have to wait a couple of hours :)
He’s like a trained ape. Without the training.
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1714
  • Country: se
Re: Arduino Code - volts/amps/power monitor
« Reply #14 on: March 09, 2018, 07:23:06 pm »
Looks better! :-+
Use unsigned long int or uint32_t for the counters and times, as that guarantees you the correct behaviour together with millis().
Some redundant cast and parenthesis, but no major problem at first glance.

Code: [Select]
  if (millis()>86400000){
    maxAmps=0;
    maxVolts=0;
  }
If I understand correctly, this is supposed to reset the maximum values every day.
It will work...a bit aggressively >:D: after one day the values will be kept to 0, as the test will always be verified.

To achieve your goal, implement a mechanism similar to the one used with time0 and time1.
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline frozenfrogz

  • Frequent Contributor
  • **
  • Posts: 936
  • Country: de
  • Having fun with Arduino and Raspberry Pi
Re: Arduino Code - volts/amps/power monitor
« Reply #15 on: March 09, 2018, 07:36:09 pm »
Just a short heads up for you, metrologist, in regard to newbrains answer: I suspect you do not fully understand how the millis() function works. It basically counts the uptime of your Arduino, where 1 tick is 1.024ms if I remember correctly. So it is just somewhat capable of pedantic time keeping (but that is just a side note) ;)
You should not try to implement millis() rollover handling but better go for rollover safe code :)
« Last Edit: March 09, 2018, 08:21:22 pm by frozenfrogz »
He’s like a trained ape. Without the training.
 

Offline metrologistTopic starter

  • Super Contributor
  • ***
  • Posts: 2199
  • Country: 00
Re: Arduino Code - volts/amps/power monitor
« Reply #16 on: March 09, 2018, 08:20:14 pm »
 :-DD I fixed this bit.

Code: [Select]
  if (millis()-time2 > 86400000){
    time2=millis();
    maxAmps=0;
    maxVolts=0;
  }

I thought millis() was actual ms, similar to micros(), and each wavers around 4us accuracy/resolution (according to arduino time).
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1714
  • Country: se
Re: Arduino Code - volts/amps/power monitor
« Reply #17 on: March 09, 2018, 09:15:24 pm »
Just a short heads up for you, metrologist, in regard to newbrains answer: I suspect you do not fully understand how the millis() function works. It basically counts the uptime of your Arduino, where 1 tick is 1,024ms if I remember correctly. So it is just somewhat capable of pedantic time keeping (but that is just a side note) ;)
You should not try to implement millis() rollover handling but better go for rollover safe code :)
Thank for clarifying this!
It is in fact a trap for young and not-so-young players.

Since I saw that - once the correct unsigned types are used - metrologist's time comparison code is roll-over safe, I did not go in much detail.

Maybe, though, an explanation of what we mean by roll-over safe is in order.

To be roll-over safe means that even in the case a variable overflows, the involved comparison still works as expected.

Two main factors contribute to the behaviour of a comparison (such as we have here for time0 and the like) at roll-over:
  • The involved variables, constants and function return values types
  • The way the comparison is written.

For the first point, only unsigned integral types have defined behaviour at arithmetic overflow. Regular integer overflow has undefined behaviour.
(see 6.2.5 Clause 9 in the C11 draft).

For the second point, we must write our comparison (for a monotonically non-decreasing value):
Code: [Select]
if( currentValue-oldValue > delta )
{
    oldValue = currentValue;
    ...rest of the code...
}

Let's take our case as a practical example: the millis() library function returns an unsigned long int counting the ms from the system start.
We have 32 bits unsigned long int in the AVR+gcc environment (it's compiler+platform dependent) what will happen when we pass 0xFFFFFFFFu ms (about 53 days)?
Simply the function will return 0x00000000u, no worries (someone did not get this right: google "time_t 2038 problem"!).

Let's assume that our oldValue is 0xFFFFFFF0u: we have an overflow, as we are trying to subtract a large number from a small one (and obtain a positive numer)!
But this overflow is benign: thanks to the guarantees of the C standard (always be praised) the behaviour is well defined and we get the real "distance" between the two number (0x00000010u), the comparison with our delta will always work.

Would it be correct if we had written a mathematically equivalent form?
Let's try:
Code: [Select]
if( currentValue > oldValue+delta )
We see that if oldValue is e.g. 0xFFFFFF00u, delta 0x00000040u and currentValue has rolled over (0x00000000u) the comparison does not yield the expected result (should have been true, but it's false!).
Similarly, if we'd had:
Code: [Select]
if( currentValue-delta > oldValue )
(finding bad values and intervals left as an exercise for the reader)

I hope this helps metrologist grasping a deeper understanding of how C works!
Nandemo wa shiranai wa yo, shitteru koto dake.
 
The following users thanked this post: bitseeker, frozenfrogz

Offline ez24

  • Super Contributor
  • ***
  • Posts: 3082
  • Country: us
  • L.D.A.
Re: Arduino Code - volts/amps/power monitor
« Reply #18 on: March 09, 2018, 09:18:47 pm »
 :popcorn: interesting
YouTube and Website Electronic Resources ------>  https://www.eevblog.com/forum/other-blog-specific/a/msg1341166/#msg1341166
 

Offline Rick Law

  • Super Contributor
  • ***
  • Posts: 3423
  • Country: us
Re: Arduino Code - volts/amps/power monitor
« Reply #19 on: March 10, 2018, 01:49:54 am »
OK, thanks all. I tried to take some of the points into account and clean up the code some.
...
...
    if (millis() - time1 >= 2000){
      amps = (float)a*26.7/(count*1023.0);
      volts = (float)v*5.0/(count*1023.0);
...
...

A further suggestion to aid debugging when needed: use a #define here, so when debugging is needed, it will likely saves a lot of debugging the debug code.

For "amps = (float)a*26.7/(count*1023.0);"

Before the loop(), I would first add a #define
#define ADC_AMPS(adc,counts)  ( (float)adc*26.7/(counts*1023.0) )

Now the line "amps = (float)a*26.7/(count*1023.0);" becomes:
   amps = ADC_AMPS(a,count);

With that, you can re-invoke the same calculation be it in debugging, or in additional features to show/print the amps, volts, whatever.

***

Now if you are comfortable with that, let me make it a little more complicated.  Sometime in debugging, you may just use a literal such as:
  testValue = ADC_AMPS(a, 1);
That would be fine, but if you do:
  testValue = ADC_AMPS(a, count+2); // add 2 for whatever reason
Now the counts in the #define is replaced with count+2 so
"a*26.7/(counts*1023.0)" becomes "a*26.7/(count+2*1023.0)"
You can see the problem there.

To fix that, and also ensure the math is done exactly the same, I put parenthesis around each argument.  I would also force the conversion right there in this case.  The extra cast may be redundant, but I found it helps me when I need to do code change.  At the very least, it reminds me to consider the variable type.

So on the right side, instead of just counts, I would use ((float) counts) and same for adc I would use ((float) adc)

#define ADC_AMPS(adc,counts)  ( (float)adc*26.7/(counts*1023.0) )
becomes:
#define ADC_AMPS(adc,counts)  ( (float)((float) adc)*26.7/( ((float)counts)*1023.0) )

I found this approach helps me a lot.  I hope this is as useful to you as it is to me.
« Last Edit: March 10, 2018, 01:55:04 am by Rick Law »
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1714
  • Country: se
Re: Arduino Code - volts/amps/power monitor
« Reply #20 on: March 10, 2018, 11:06:22 am »
And now that we have perhaps sorted out the major things, let's go with the nitpicking  :blah:

@Rick Law:
I'm not a fan of extra casts, as I prefer to make sure that the expression is correctly written in the first place.
I treat them as a strong spice in a recipe: it's has its place, in moderation  ;).
Quote
#define ADC_AMPS(adc,counts)  ( (float)((float) adc)*26.7/( ((float)counts)*1023.0) )
The guideline to put parenthesis around a function-like macro parameter is definitely sound and correct.
Unfortunately, the code above does not do that!
Can you spot the case where the (float) cast is not applied correctly to the #define parameter?
It is, I admit, an uncommon, situation, but, it happens, and, is difficult, to spot.
Hint in the period above.
It does not make much difference here (conversion to float is guaranteed by all the other operands), but could in other contexts.



@metrologist
A couple of remarks, the first one is very general:
We have till now talked about float constants, but there isn't any in all the posted code!  :-//

Am I kidding?
Actually not, all the constants here are floating point, but their type is, according to 6.4.4.2 clause 4 actually double.
For the Arduino (AVR+gcc) implementation, floats and doubles are in reality the same type, or to be more precise, have the same internal representation.
In other environment using 100.0 instead of 100.0f can change the type of an expression and lead e.g. to different execution times (floats can be slower or faster than doubles).

The second point is a specific value: 1023.
The ATmega ADC specifications say, in chapter 24.7 of the 328 datasheet that:

$$ADC = {V_{IN} \cdot 1024 \over  V_{REF}} $$


and:
Quote
0x000 represents analog ground, and 0x3FF represents the selected reference voltage minus one LSB.
So, why divide by 1023 instead of 1024?
  • It's not correct according the DS for this ADC
    Note that other ADCs (e.g. LTC2400, Tabe 2) might use an all ones code to represent VREF
  • It could have performance impacts.
    More or less guaranteed when using integers, but a smart compiler can also optimize the FP division by a constant of the form 2n
    (Hint: FLT_RADIX)

Given the relatively low precision of the ATmega ADC (2LSB, IIRC) this also is a moot point, but let an old man rant... :blah:

PS: Picture from Kara no Kyoukai: Fukan Fuukei (The Garden of Sinners: Overlooking View), first instalment of a great anime movie series. Recommended if you like dark supernatural stories. Not for kids.
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline Rick Law

  • Super Contributor
  • ***
  • Posts: 3423
  • Country: us
Re: Arduino Code - volts/amps/power monitor
« Reply #21 on: March 10, 2018, 11:05:42 pm »
And now that we have perhaps sorted out the major things, let's go with the nitpicking  :blah:

@Rick Law:
I'm not a fan of extra casts, as I prefer to make sure that the expression is correctly written in the first place.
I treat them as a strong spice in a recipe: it's has its place, in moderation  ;).
Quote
#define ADC_AMPS(adc,counts)  ( (float)((float) adc)*26.7/( ((float)counts)*1023.0) )
The guideline to put parenthesis around a function-like macro parameter is definitely sound and correct.
Unfortunately, the code above does not do that!
Can you spot the case where the (float) cast is not applied correctly to the #define parameter?
It is, I admit, an uncommon, situation, but, it happens, and, is difficult, to spot.
Hint in the period above.
It does not make much difference here (conversion to float is guaranteed by all the other operands), but could in other contexts.
...
...

Hmmm....  I cannot spot the problem why "(float) cast is not applied correctly".  You have to enlighten me here.  Except I did noticed while I say one should add parenthesis around the #define's arguments, I actually did not add that in the #define shown.  So if a complex expression is used as an argument here, without the needed parenthesis, the -cast- could run into issues.  That was careless of me saying one should do it and then omitted it in the final text.

So,
#define ADC_AMPS(adc,counts)  ( (float)((float) adc)*26.7/( ((float)counts)*1023.0) )
should really be:
#define ADC_AMPS(adc,counts)  ( (float)((float) (adc))*26.7/( ((float)(counts))*1023.0) )
for the original expression: (float)a*26.7/(count*1023.0)

Is that what you are pointing out?  If not, I really would like you to enlighten me as to what I missed.

As to having the extra -cast- you and I have a disagreement.  It is of course good to do it right the first time.  But when code is maintained by unknown number of individuals and done a decade or more later, I assume some time down the road someone would miss something.  This extra -cast- is like an extra layer of bubble-wrap just to add a bit more protection.  It reminds me (or someone else) that there is a reliance on the argument being "of a specific type".

God knows, if people are careful, no fool would talk about needing parenthesis and then didn't put the parenthesis in with the example...
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1714
  • Country: se
Re: Arduino Code - volts/amps/power monitor
« Reply #22 on: March 11, 2018, 12:27:23 am »
I probably was not very clear (in trying to be cheeky), and also thought that you meant that ((float)param) was enough; after all, in the form shown, most integer expressions will work correctly and result in being cast to a float, as at least one operand will.

I apologize for the misunderstanding, I hope you did not take offence.

The corner case I was addressing is quite convoluted, and I would think it would normally not occur...but that's what sometimes break things  :(

If there's an object-like macro such as:
Code: [Select]
#define z x,y with a comma operator, and that's passed as parameter, the (float) cast will only be applied to the first operand.
That operand is then thrown away (treated as a void expression by the comma operator), and the second one (without any cast) used in the final expression: the cast has not been applied at all to the object whose value we are interested in.

The intermediate #define is needed, as it's the only way I was able to find to pass a comma expression without using parenthesis (otherwise the cast would apply) or messing up the parameter list of the function-like macro: as said, contrived and unlikely...

As for the casts we can agree to disagree.
IME, when I see old code with a lot of casts I always wonder whether they are there to shut up some warning, to hide some problem in type choices, or "make it work" without a full understanding of where the problem was.
They may have been put there with good reason, by a reasoning coder, but they always generate a lot of head scratching...

In our specific case, the whole define will be evaluated as double, as the used constants are double, and the first (float) applies to just ((float)(adc)): we need another pair of parenthesis.
My next post will probably need to refer to a different standard  >:D

I think there is a good learning point from all this discussion on macros:
Macros are a powerful instrument, but making them safe is difficult and sometimes impossible. Readability is bound to suffer.
But rejoice, there's hope:
For most function-like macros, C99 (and later) provides a more powerful instrument in the form of inline functions (6.7.4, clause 6):
Code: [Select]
static inline float ADC_AMPS(float adc, float amps) { return (adc*26.7f)/(counts*1024.0f); }Clear, concise, type safe, and it is as fast as the macro in decent compiler implementations.
The comma trick does not work here.  ::)
Nandemo wa shiranai wa yo, shitteru koto dake.
 
The following users thanked this post: bitseeker

Offline Rick Law

  • Super Contributor
  • ***
  • Posts: 3423
  • Country: us
Re: Arduino Code - volts/amps/power monitor
« Reply #23 on: March 11, 2018, 03:00:21 am »
I probably was not very clear (in trying to be cheeky), and also thought that you meant that ((float)param) was enough; after all, in the form shown, most integer expressions will work correctly and result in being cast to a float, as at least one operand will.

I apologize for the misunderstanding, I hope you did not take offence.
...

No offense taken but instead a thank you!
  I was the fool who talked about one should add the parenthesis and then I missed it in the last step...  So thanks for finding the error there!

...
I think there is a good learning point from all this discussion on macros:
Macros are a powerful instrument, but making them safe is difficult and sometimes impossible. Readability is bound to suffer.
But rejoice, there's hope:
For most function-like macros, C99 (and later) provides a more powerful instrument in the form of inline functions (6.7.4, clause 6):
Code: [Select]
static inline float ADC_AMPS(float adc, float amps) { return (adc*26.7f)/(counts*1024.0f); }...
...

re: "Macros are a powerful instrument, but making them safe is difficult"
Yeah, you said it.  But when they are simple ones, it makes life a lot easier.

re: "inline functions"
Back when I was doing more C coding (1980's), it wasn't available then.  Now that I am doing coding just for play, they have the things I love to have then.  There is so much more fun things like in-line functions.  Even better is with appropriate option flag settings, the compiler will make them in-line even if you don't explicitly say so.  So, these days, I let the compiler do that and use functions more liberally.

 
The following users thanked this post: newbrain

Offline paulca

  • Super Contributor
  • ***
  • Posts: 4003
  • Country: gb
Re: Arduino Code - volts/amps/power monitor
« Reply #24 on: March 11, 2018, 08:07:30 am »
floats are evil.
double is double trouble.
macros are often a curse for debugging, though can aid it if used right.  Do not get carried away.  If you have to read someone elses code and they are a macro-ist you will hate them.  Nested, conditional, complex and parameterised macros can make debugging a nightmare and require that you end up running the pre-processor to resolve them to be able to find the bugs.

For the love of God split your code up into functional chunks!  For this kind of thing use "top down".  Take your entry point and write, in english the steps you want it to do:

readSensors
totalize
display

Then stick brackets on them.

readSensors();
totalize();
display();

Go write those functions.  Repeat the pattern until you find you can only realistically replace the english with less than 3 or 4 lines of code.  Write a test main() or loop() which tests those functions independently and outputs PASS or FAIL on Serial.  This relates to globals.... which make independent testing of "separation of concern" almost impossible.  Doing it "nicely" requires you split your code into different files.  You can also use #ifdefs to remove test code from the final build.

I'd be tempted to suggest you avoid globals as well, but as we are in uC land with small single file applications I suppose it won't hurt.  A convention is to prefix them with "g", so everyone (including yourself in 6 weeks time) knows they are global.  The alternative is structs (or classes) and that leads to memory management, side effect arguments and pointers which leads to bugs for new players.

On data integrity of precision:
* Always go up before coming down.  Multiply before divide. 
* Use the highest units/denominations you can.
* Remember that computers suck at maths.  Try this:
double a = 10 / 3;
Serial.println( (a * 3) );

You won't get 10.  More likely 9.999999999999....
Even (1/10.0)*10 will not give you the right answer unless the compiler spots the cancellation.

In your case I would stay away from floating point altogether and work in millivolts, milliamps and milliwatts.  For the energy I would use milli-Joules rather than Ah.   Ah is a large value compared to it's equivalent in joules so you need higher precision for Ah than joules.  Joules = power(watts) * time(seconds).  1Ah @ 1V = 3600J.  Be careful though, 1 mV * 1mA * 1mS = 1 nano-joule.

Calculating Ah in every iteration with a different voltage will potentially lead to non-sense.  When you get to the end of your process, ask the question... how much energy was that in joules?  The answer will always need you specify the volts.... which you now don't know.  For a battery it's often just it's nominal voltage, but you didn't store it as such, so it's non-sense and you will never be able to actually calculate total energy.  Storing watt hours or joules decouples you from the voltage.  The difference is that Ah is a unit of energy at a given voltage.  Watt hours and joules are units of actual energy.  You can convert to Ah at the battery nominal voltage later. The only reason for using Ah is for a battery to say, "I will deliver X amps for Y hours at some voltage between my maximum and my minimum", it will not tell you total energy.  I suppose it depends on what you want.

For wrapping variables you can always use the modulus operator.

wrappedCounter = wrappedCounter++ % MAX_VALUE;

% gives the remainder after division.  So once wrappedCounter reaches MAX_VALUE the modulus returns 0.  Note the post increment not pre-increment or you would never get MAX_VALUE.... unless that's what you want.

In fact, asides for data type rollover you don't even need to reassign the result of modulus for it to work.

counter++;
boundedValue = counter % MAX_VALUE;
"What could possibly go wrong?"
Current Open Projects:  STM32F411RE+ESP32+TFT for home IoT (NoT) projects.  Child's advent xmas countdown toy.  Digital audio routing board.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf