Arduino Code - volts/amps/power monitor - Page 1
Forum section: electronics / beginners

author: metrologist
author_rank: Super Contributor
timestamp: March 08, 2018, 10:01:19 pm
content: 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:#include "U8glib.h"//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0);// I2C / TWIU8GLIB_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 ACfloat 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 valueif ( 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 averagingtime0 = millis();count=count+1.0;//a = (analogRead(A0)- 509) * 26.7 / 1023;//v = analogRead(A1) * 4.6 / 1023;a = 60; //temporary test valuev = 10; //temporary test valueamps = 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 loopu8g.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);}

author: rstofer
author_rank: Super Contributor
timestamp: March 08, 2018, 10:40:44 pm
content: 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.

author: Wimberleytech
author_rank: Super Contributor
timestamp: March 08, 2018, 10:53:42 pm
content: 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.

author: frozenfrogz
author_rank: Frequent Contributor
timestamp: March 08, 2018, 11:05:09 pm
content: 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.

author: bitseeker
author_rank: Super Contributor
timestamp: March 08, 2018, 11:05:34 pm
content: 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.

author: frozenfrogz
author_rank: Frequent Contributor
timestamp: March 08, 2018, 11:20:06 pm
content: 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.

author: metrologist
author_rank: Super Contributor
timestamp: March 08, 2018, 11:20:57 pm
content: 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:#include "U8glib.h"//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0);// I2C / TWIU8GLIB_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 ACfloat 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 valueif ( 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 averagingtime0 = millis();count=count+1.0;//a = (analogRead(A0)- 509) * 26.7 / 1023;//v = analogRead(A1) * 4.6 / 1023;a = 60.0; //temporary test valuev = 10.0; //temporary test valueamps = 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 loopu8g.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);}

author: frozenfrogz
author_rank: Frequent Contributor
timestamp: March 08, 2018, 11:49:48 pm
content: 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.

author: newbrain
author_rank: Super Contributor
timestamp: March 09, 2018, 12:05:34 am
content: QuoteAnd 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 typeFromthe 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 otheroperand is converted, without change of type domain, to a type whosecorresponding 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)

author: frozenfrogz
author_rank: Frequent Contributor
timestamp: March 09, 2018, 12:15:09 am
content: Quote from: newbrain on March 09, 2018, 12:05:34 am»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 theArduino reference.Edit: The Arduino reference - as always - is unspecific AF. So who knows, what’s really happening when mixing int and float...

author: Rick Law
author_rank: Super Contributor
timestamp: March 09, 2018, 01:22:28 am
content: Quote from: metrologist on March 08, 2018, 11:20:57 pmI don't think it results incountbeing integer.countis float as declared. But your ="1"is integer. Socompilerwould convert1to 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.Quote from: metrologist on March 08, 2018, 11:20:57 pmAt 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 youradc_int_totaltoadc_float_totalthen do all float arithmetic with onlyadc_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.

author: newbrain
author_rank: Super Contributor
timestamp: March 09, 2018, 09:09:31 am
content: Quote from: frozenfrogz on March 09, 2018, 12:15:09 amThe 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 trustThe 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>.QuoteThe 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, 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.

author: metrologist
author_rank: Super Contributor
timestamp: March 09, 2018, 06:43:21 pm
content: OK, thanks all. I tried to take some of the points into account and clean up the code some.Code:#include "U8glib.h"//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0); // I2C / TWIU8GLIB_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 ACfloat 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 valueif ( 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 ratetime0 = millis();count++;a = a+analogRead(A0)-509; //sensor reads +/- with 0 near midpoint of adcv = 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 loopu8g.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]

author: frozenfrogz
author_rank: Frequent Contributor
timestamp: March 09, 2018, 07:00:36 pm
content: 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.QuoteI’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

author: newbrain
author_rank: Super Contributor
timestamp: March 09, 2018, 07:23:06 pm
content: Looks better!Useunsigned long intoruint32_tfor 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: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: 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.

author: frozenfrogz
author_rank: Frequent Contributor
timestamp: March 09, 2018, 07:36:09 pm
content: 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

author: metrologist
author_rank: Super Contributor
timestamp: March 09, 2018, 08:20:14 pm
content: I fixed this bit.Code: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).

author: newbrain
author_rank: Super Contributor
timestamp: March 09, 2018, 09:15:24 pm
content: Quote from: frozenfrogz on March 09, 2018, 07:36:09 pmThank 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 codeisroll-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 typesThe 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 theC11 draft).For the second point, we must write our comparison (for a monotonically non-decreasing value):Code: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: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: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!

author: ez24
author_rank: Super Contributor
timestamp: March 09, 2018, 09:18:47 pm
content: interesting

author: Rick Law
author_rank: Super Contributor
timestamp: March 10, 2018, 01:49:54 am
content: Quote from: metrologist on March 09, 2018, 06:43:21 pmA further suggestion to aid debugging when needed: use a#definehere, 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 reasonNow 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 putparenthesis 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 justcounts, I would use((float) counts)and same foradcI 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.

author: newbrain
author_rank: Super Contributor
timestamp: March 10, 2018, 11:06:22 am
content: And now that we have perhaps sorted out the major things, let's go with the nitpicking@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.QuoteThe 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.@metrologistA couple of remarks, the first one is very general:We have till now talked aboutfloatconstants, 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 to6.4.4.2 clause 4actuallydouble.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 the328 datasheetthat:$$ADC = {V_{IN} \cdot 1024 \over V_{REF}} $$and:QuoteSo, why divide by 1023 instead of 1024?It's not correct according the DSfor this ADCNote that other ADCs (e.g.LTC2400, Tabe 2) might use an all ones code to represent VREFIt 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...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.

author: Rick Law
author_rank: Super Contributor
timestamp: March 10, 2018, 11:05:42 pm
content: Quote from: newbrain on March 10, 2018, 11:06:22 amHmmm.... I cannot spot the problem why "(float) cast is not applied correctly". You have to enlighten me here. Except I did noticedwhile I say one shouldadd parenthesis around the #define's arguments,I actually did notadd 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...

author: newbrain
author_rank: Super Contributor
timestamp: March 11, 2018, 12:27:23 am
content: 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 thingsIf there's an object-like macro such as:Code:#define z x,ywith 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 adifferent standardI think there is agood learning pointfrom 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: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.

author: Rick Law
author_rank: Super Contributor
timestamp: March 11, 2018, 03:00:21 am
content: Quote from: newbrain on March 11, 2018, 12:27:23 amNo 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!Quote from: newbrain on March 11, 2018, 12:27:23 amre: "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.

author: paulca
author_rank: Super Contributor
timestamp: March 11, 2018, 08:07:30 am
content: 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:readSensorstotalizedisplayThen 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 = 1nano-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;

author: newbrain
author_rank: Super Contributor
timestamp: March 11, 2018, 09:36:32 am
content: Quote from: paulca on March 11, 2018, 08:07:30 amAnd lets not talk about # and ## operators...Quote from: paulca on March 11, 2018, 08:07:30 amI summon the holy Clause 2 in chapter 6.5 of oursacredbook(s)!Let's chant together Annex J.2 and let its informative nature be our light and guidance!Vade retroSatanaUndefined Behaviour!C2011 language has been revised, and is more precise, but much less intuitive, let's stick to C99's 6.5 (2):QuoteNote 73):QuoteAlso, the list of sequence points (in Annex C) has been (slightly) changed between C99 and C2011.In our case, the only sequence points are the semicolon before and after our assignment expression.wrappedCounter is clearly modified twice: once by the assignment operator, and once by the post-increment operator (same as note 73).The ugly thing with undefined behaviour, with respect to unspecified and implementation defined ones is that there's no way to tell what the result will be.The compiler might decide to open a portal towards hell or make you grow a purple beard (or shave you, if you already have one), and it would still be perfectly standard compliant.The results are often more tamed (if less interesting) butthey cannot be relied upon.It is simply wrong code, stay away.Good IDE and compilers can inform you of the wrongness of your ways, but it is not possible to catch all the cases.Note:Unspecified behaviour is where the standard makes no recommendation and leaves the implementation free to do thing the way best suited to the situation, with no need to document it (e.g. inlining or not a function call).Implementation defined behaviour is where a precise choice has to be made and documented by the compiler, e.g. if plain char is signed or unsigned.Edit: readded the bold warning. Simply writing the UB code in VS made it disappear. Or at least, that's my version of facts.

author: paulca
author_rank: Super Contributor
timestamp: March 11, 2018, 11:16:49 am
content: Quote from: newbrain on March 11, 2018, 09:36:32 amSo you got me on that, but it works.I have found it not working in several languages however.If you remove the contractions it will work however.wrappedCounter = (wrappedCounter + 1) % MAX_VALUE;My University tutor in C/C++ went even further and banned us all from using ++ in the first place. That and operator overloading.

author: newbrain
author_rank: Super Contributor
timestamp: March 11, 2018, 11:36:21 pm
content: Quote from: paulca on March 11, 2018, 11:16:49 amI told you! I told you!You invoke undefined behaviour, and the next thing you know the forum goes belly up for hours...Never underestimate the dark side of C.On a more serious note:It is wrong code, stay away.The problem with that kind of code is that one cannot in any way rely on any particular result.Of course the compiler (once you ignore the warnings) will do "something" (it could very well crash, actually), but that something is not documented, and there's no guarantee it will always be the same, from version to version, from platform to platform from compilation to compilation, and it can freely vary depending on the surrounding code!Quote from: paulca on March 11, 2018, 11:16:49 amPostfix ++ is not a contraction of +1, first because it can only be applied to modifiable lvalues, second - and the whole point here - because it modifies its operand.Prefix ++, OTOH is shorthand for (lvalue+=1)Hence, with the +1, that is a perfectly good C statement.It would seem your tutor was teaching C/C++ with training wheels...maybe they should have stressed how to correctly use them.And, now, the "working" code:I suspect your definition of 'working' and mine are radically different.Code:#include <stdio.h>#define MAX_VALUE 12unsigned int wrappedCounter;int main(){for (int i = 0; i < 20; i++){wrappedCounter = wrappedCounter++ % MAX_VALUE;printf("i%4d counter %4u\n", i, wrappedCounter );}return 0;}Result on Raspberry Pi 3, gcc 6.3.0 (same on X86 Ubuntu 16.04, gcc 5.4.0):Code:newbrain@tritium:~ $ ./a.outi 0 counter 0i 1 counter 0i 2 counter 0i 3 counter 0i 4 counter 0i 5 counter 0i 6 counter 0i 7 counter 0...ok you get the gist...Surprising isn'it?The Microsoft compiler gives an incrementing counter from 1 to 12.I seem remember that older gcc (3.??) had a similar behaviour (it did not return all zeros).I don't know whether you would consider this correct: I personally cannot meaningfully parse that statement.

author: paulca
author_rank: Super Contributor
timestamp: March 12, 2018, 07:38:00 am
content: Quote from: newbrain on March 11, 2018, 11:36:21 pmOne interesting reason was the question..."And what if "i" which you "i++" turns out not to be an int and turns out to be macro?"Of course the answer depends on the macro, but the point he was making is it could result in one of C++s less than friendly errors.Anyway, he had just seen so many mistakes using it. He didn't even like single character variable names for loop indexes. He actually stipulated that we should use this style for for loops:for( int Index=0; Index < MAX; Index=Index+1; ) {//...}My own little mistake here suggests I've been away from C/C++ too long and little adventures in arduino don't really count.

author: metrologist
author_rank: Super Contributor
timestamp: March 12, 2018, 05:01:38 pm
content: I was just trying to add SD card logging and work in some points mentioned here - the ones I could understand anyway ...I ran out of room - removing the other font from the LCD screen prints got back 5k of space. Later I want to explore another light sensor (may not be needed) and control at least a one axis motor to keep the panel pointed at the sun.Code:/*SD card dataloggerThis example shows how to log data from three analog sensorsto an SD card using the SD library.The circuit:* analog sensors on analog ins 0, 1, and 2* SD card attached to SPI bus as follows:** MOSI - pin 11** MISO - pin 12** CLK - pin 13** CS - pin 4created 24 Nov 2010modified 9 Apr 2012by Tom IgoeThis example code is in the public domain.*/#include <SD.h>// On the Ethernet Shield, CS is pin 4. Note that even if it's not// used as the CS pin, the hardware CS pin (10 on most Arduino boards,// 53 on the Mega) must be left as an output or the SD library// functions will not work.const int chipSelect = 4;#include "U8glib.h"//U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE|U8G_I2C_OPT_DEV_0); // I2C / TWIU8GLIB_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 ACfloat amps = 0.0;float volts = 0.0;long v = 0;long a = 0;float maxAmps = 0.0;float maxVolts = 0.0;float minAmps = 100.0;float minVolts = 100.0;float ah = 0.0;float watts = 0.0;float whr = 0.0;unsigned long time0 = 0;unsigned long time1 = 0;unsigned long time2 = 0;float count = 0.0;void setup(void) {Serial.begin(9600);Serial.print("Initializing SD card...");// make sure that the default chip select pin is set to// output, even if you don't use it:pinMode(4, OUTPUT);// see if the card is present and can be initialized:if (!SD.begin(chipSelect)) {Serial.println("Card failed, or not present");// don't do anything more:return;}Serial.println("card initialized.");// flip screen, if required// u8g.setRot180();// assign default color valueif ( 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);delay(300);}void loop() {if (millis() - time0 >= 10) adc(); // fudge averaging by controlling sample rateif (millis() - time1 >= 1000) tally(); // compute result on interval}void adc(){time0 = millis();count++;a = a+analogRead(A0)-509;v = v+analogRead(A1);}void tally(){amps = (float)a*29.69/(count*1023.0); //26.7volts = (float)v*142.94/(count*1023.0); //5.0watts = 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);minAmps = min(minAmps, amps);minVolts = min(minVolts, volts);serial();sdc();lcd();count = 0.0;a = 0;v = 0;if (millis()-time2 > 86400000){time2=millis();maxAmps=0;maxVolts=0;}}void serial(){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(minVolts);Serial.print("\tVmin\t");Serial.print(maxAmps);Serial.print("\tAmax\t");Serial.print(minAmps);Serial.print("\tAmin\t");Serial.print(time1/60000.0);Serial.println("\tmin\t");}void sdc(){File dataFile = SD.open("datalog.txt", FILE_WRITE);// if the file is available, write to it:if (dataFile) {dataFile.print(count);dataFile.print("\taverages\t");dataFile.print(volts);dataFile.print("\tV\t");dataFile.print(amps);dataFile.print("\tA\t");dataFile.print(ah);dataFile.print("\tAh\t");dataFile.print(watts);dataFile.print("\tW\t");dataFile.print(whr);dataFile.print("\tWhr\t");dataFile.print(maxVolts);dataFile.print("\tVmax\t");dataFile.print(minVolts);dataFile.print("\tVmin\t");dataFile.print(maxAmps);dataFile.print("\tAmax\t");dataFile.print(minAmps);dataFile.print("\tAmin\t");dataFile.print(time1/60000.0);dataFile.println("\tmin\t");dataFile.close();}}void lcd(){// picture loopu8g.firstPage();do {draw1();}while( u8g.nextPage() );}void draw1() {//graphic commands to redraw the complete screen should be placed here// u8g.setFontPosTop();u8g.setFont(u8g_font_unifont);u8g.setPrintPos(0, 14);u8g.print(volts);u8g.print("V");u8g.setPrintPos(66, 14);u8g.print(amps);u8g.print("A");u8g.setPrintPos(0, 31);u8g.print(ah);u8g.print("AHr");u8g.setPrintPos(0, 48);u8g.print(watts);u8g.print("W ");u8g.print(maxAmps*maxVolts);u8g.setPrintPos(0, 64);u8g.print(whr);u8g.print("WHr ");u8g.print(time1/60000.0);}

author: paulca
author_rank: Super Contributor
timestamp: March 12, 2018, 07:52:00 pm
content: Much better. A few points.This might not do what you think it does:Code:// see if the card is present and can be initialized:if (!SD.begin(chipSelect)) {Serial.println("Card failed, or not present");// don't do anything more:return;}It will just return from the setup() method and the application will proceed onto the loop() anyway. I'm not sure how exactly you would call "halt()" in Arduino, but you could do:while(1){ delay(10000); }Or better yet...while(1){ digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(250);}To signal "error" until the board is reset.Alternatively, you can retry the card. Just wrap the test:Code:while (!SD.begin(chipSelect)) {Serial.println("Card failed, or not present");digitalWrite(LED_BUILTIN, HIGH); delay(100); digitalWrite(LED_BUILTIN, LOW); delay(250);}Then it will wait until you insert the card.Code: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(minVolts);Serial.print("\tVmin\t");Serial.print(maxAmps);Serial.print("\tAmax\t");Serial.print(minAmps);Serial.print("\tAmin\t");Serial.print(time1/60000.0);Serial.println("\tmin\t");Becomes:Code:char buffer;memset( buffer, 0, 1024 );snprintf( buffer, 1024, "%d \t averages %.2fV %.2fA %.2fAh %2.fW %.2fWhr %.2fVmax %.2fVmin %.2fAmax %.2fAmin [%d] minutes",count, volts, amps, ah, watts, whr, maxVolts, minVolts, maxAmps, minAmps, time1/60000);Serial.println(buffer);Or similar. I didn't test compile that, but basically....%d - Integer value%.2f - Decimal value with 2 decimal places.For each "token" you place the value after the string in a list.The only concern with regards an MCU here is you could run out of memory, allocating 1k of memory for the string to built up is nothing on a PC but I believe the ATMega 328p only has 8k total. Of course if you work out the maximum length of the string to only be 256, then you can use a shorter buffer.You can do the same for printing to the SD card, but I would recommend you use CSV format, then you can import the file into Excel or similar and graph it.Note that as you use the same, more or less, print statement in two places, you could make it a function. You will need to create the memory "buffer" in the common parent and pass the pointer to the string creation function and the two functions (serial and sdc) that use it. Or just put thechar buffer; // or 256 if you can fit the string into it.As a global. Don't forget to memset it to 0 each time though

author: frozenfrogz
author_rank: Frequent Contributor
timestamp: March 12, 2018, 08:14:32 pm
content: Quote from: paulca on March 12, 2018, 07:52:00 pmYou wish!Suggesting to use printf or sprintf to a buffer was also my initial thought. Needing hundreds of Serial.print() lines just to get some barely formatted text totally bugs me every time. Arduino sucks big time in that regard!The printf() method is not implemented in the Serial class, so Serial.printf() will not compile.In addition, sprintf() on Arduino does not understand doubles (%f %e %g). I read somewhere that this is on purpose, because of memory limitations of the smaller Atmegas.To make some use of the above, you need to use dtostrf from the stdlib:Code:#include<stdlib.h>dtostrf(FLOAT,WIDTH,PRECISION,BUFFER);If there is a better way, I would love to hear itStill trying to get my head around all of these Arduino »specialties«.Might be another argument against using floats.

author: paulca
author_rank: Super Contributor
timestamp: March 12, 2018, 08:39:59 pm
content: Quote from: frozenfrogz on March 12, 2018, 08:14:32 pmAww balls. I know I've used it before, but not sure if I have used it with %f. Aww well, it's the thought that counts.You could always do:"%d.%d", value, value*100%100or something similarly cheesy.I went hunting for where I was formatting decimal output and sure enough; dtostrf:Code:sensorValue = analogRead(A1);vOut = (sensorValue * AREF) / 102.4;memset(buffer,0, 16);dtostrf(vOut,5,1,buffer);oled.print("Is: "); oled.print(buffer); oled.print("A"); oled.clearToEOL(); oled.println();

author: metrologist
author_rank: Super Contributor
timestamp: March 12, 2018, 08:53:14 pm
content: I started off reading fixed point math and that will have to wait.One idea I had was to build a string and then send that to the serial and SD ports when needed.String data = "";data += count;data += "\tavegs\t";data += volts;data += "\tV\t";...(that ^ wipes out my remaining program space)I also implemented the below, but not because I understand it...#define ADC_AMPS(a,count) (((float)((float)a)*26.7)/(((float)count)*1024.0));#define ADC_VOLTS(v,count) (((float)((float)v)*50.0)/(((float)count)*1024.0));...amps = ADC_AMPS(a,count);volts = ADC_VOLTS(v,count);Now, about 1023. I read more and suspect a routine could be applied. One suggesting is to simply add half a bit to the adc reading, or perhaps with k in the following a variable with value based on some factor.V = (analogRead(pin) + k) * AREF / 1024;with k of0 for floor0.5 for mean1 for ceiling

author: paulca
author_rank: Super Contributor
timestamp: March 12, 2018, 09:00:59 pm
content: Quote from: metrologist on March 12, 2018, 08:53:14 pmThis works in Java, but it will only work in C++ if you use STL string class and it is Satan's niece. Not because it's evil, but when it goes wrong it gives you errors like these:https://codegolf.stackexchange.com/questions/1956/generate-the-longest-error-message-in-cString streams are another option.http://www.dreamincode.net/forums/topic/95826-stringstream-tutorial/Not sure of AVR compat though.

author: Rick Law
author_rank: Super Contributor
timestamp: March 14, 2018, 02:57:52 am
content: Quote from: metrologist on March 12, 2018, 08:53:14 pmre: "I also implemented the below, but not because I understand it..."Actually, I would suggest not to add anything that you don't understand. It is your program so you need to understand the content and the reason(s) of why certain code is there.I suggested #define there really for in case you do it again elsewhere such as in debug-print of info. It would be more harm then good to have things you don't really understand. Wait till you understand it and make the decision on if it is worthwhile.re: "(that ^ wipes out my remaining program space)"Three things that may help you get space back(1)Check your IDE/C++ compiler and see if it have a flag to optimize space utilization. If I recall right,gcc(used in Arduino) has -Os to "optimize for size".(2)Initialization of variables takes a good chuck of space. If you don't really need that, don't initialize it.(3)Your Serial.print is killing you. You have a load of them! Paulca's suggestion (to solve another issue) in using snprintf (reply #30 in this thread) may be of help to solve that other issue and change the situation here, but I doubt you would have the space for it since you are running out now.Quote from: paulca on March 12, 2018, 07:52:00 pmYour serial print appears to be always printing a value then a literal such as this snip I took from your code:void serial(void){Serial.print(amps);Serial.print("A ");Serial.print(ah);Serial.print("Ah ");You have a lot of such pairs, so you will save a lot of space by creating a subroutine that print a pair like this:void serialPrintPair(float value,char *label) {Serial.print(value);Serial.print(label);}Now for each such pair of calls, you reduced it to a single call. That will save you a chunk. Adding a function do add some bytes-used. So it takes a few pairs to first pay for the added overhead of having a new function. After paying the overhead, the rest is pure gravy.(4)Note if there is a generated byte count displayed after compilation. Arduino IDE does that (if indeed you are using that IDE). If you want more information about what use what, there may be a way with your IDE to generates listing files. When I was coding for ATTINY 13 withgcc(directly without IDE), the assembler listing file was critical in help me fit things into 1K space. I don't know what is your IDE. Do some research and see if/how you can grab the listing file. If all you want out of it is to manage space, you don't have to understand the details of the code generated - you just note the space generated for that line of source.

author: metrologist
author_rank: Super Contributor
timestamp: March 14, 2018, 09:05:22 pm
content: I'd rather understand the define than avoid it as it looks like it will have a lot of utility.I also tried creating a float array and string array rather than individual variables. That was so I could loop an index for both serial and SD card outputs. That made the code more difficult to understand and it actually made the compiled code a little larger. I selected a reduced font set and I'm back down to ~23kB.

author: Rick Law
author_rank: Super Contributor
timestamp: March 15, 2018, 04:37:51 am
content: Quote from: metrologist on March 14, 2018, 09:05:22 pmThe way #define is used here is simple to understand so let's see if I can present it as so for you.#define is a way to ask the pre-compiler part of the compile process to do text replacement/modification. The pre-compiler hands over a modified source file to the compiler to compile.// ====================================// Simple case:// ====================================In its simplest form:#definemyDigitalReaddigitalReadV2Every time when it sees the wordmyDigitalReadin your source (other than comments), it is replaced with the worddigitalReadV2Imagine you have these functions in your code:digitalReadV1(value1, value2...) // functioning old versiondigitalReadV2(value1, value2...) // working on this new versionand elsewhere in the codes, you have many calls it to all over the place:result = myDigitalRead(something, something...)All occurrences will turn into:result = digitalReadV2(something, something...)Now, if you want to call the old function again, all you have to do is change the #define statement to:#define myDigitalReaddigitalReadV1Now all occurrences will turn into:result = digitalReadV1(something, something...)// -----------------------------------------------------------// I have two type of I2C with different addresses and size// and the code supports them#define LCDxSize 20#define LCDySize 4#define LCDAddress 0xfb // I2C address of current LCD installed// with all my codes using the #defined value above, I can change LCD without// hunting all over my code when I switch LCD// ====================================// Now more complicated #define with parameter:// ====================================#define myDoublePrint(label,value) Serial.print(label);Serial.print(value);// now, you can just use myDoublePrint and the pre-compiler will convert that// into two Serial.print for you.// This would be a good way to compare memory use by make double calls verses// changing the #define and make it call a function that do the two printing.//// ----------- Example: -----------//make the choice by commenting outone of the #define below#define myDoublePrint(label,value) { Serial.print(label);Serial.print(value); }#define myDoublePrint(label,value) myNewFunction((label),(value))void myNewFunction(char *label,float value) {Serial.print(label);Serial.print(value);}//-------------------------------------------------------Hope that helps you understand simple #define. If it does, you may even use#if def(something)some codes here in between#endifAnd then there are more complicated ways of using #define to achieve more... When you feel comfortable with using #define, lots of fun to research what else can be doneBy the way, which IDE are you using?

author: metrologist
author_rank: Super Contributor
timestamp: March 15, 2018, 02:49:00 pm
content: Thanks, Rick. I had thought of #define as a constant and typically saw it used to define a led pin#, but I see how it's being used now.I am using the Arduino IDE 1.06 and often Notepad++.

author: Rick Law
author_rank: Super Contributor
timestamp: March 15, 2018, 08:17:44 pm
content: Quote from: metrologist on March 15, 2018, 02:49:00 pmYou are welcome. Since you are using Arduino, like most library and IDE, #define is also use to control what source codes are applicable. For example, things like which register and which bits are used for, say, controlling PWM are typically #defined somewhere.It will be helpful if you look into#if,#elif, and#endifalong with#if def()and#if ndef(). When you have some understanding of those pre-processor commands, if will help you understandArduino headers files. You can make out more of what they do.

author: metrologist
author_rank: Super Contributor
timestamp: March 21, 2018, 03:32:49 pm
content: So, I was having trouble with the SD card writes stopping after 7 or so hours. I slowed down the SD card writes with more averaging of the logged data.Code:/*SD card datalogger* SD card attached to SPI bus as follows:** MOSI - pin 11** MISO - pin 12** CLK - pin 13** CS - pin 4created 24 Nov 2010modified 9 Apr 2012by Tom IgoeThis example code is in the public domain.*/#include <SD.h>// On the Ethernet Shield, CS is pin 4. Note that even if it's not// used as the CS pin, the hardware CS pin (10 on most Arduino boards,// 53 on the Mega) must be left as an output or the SD library// functions will not work.const int chipSelect = 4;#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 ACfloat count = 0.0;float volts = 0.0;float minVolts = 100.0;float maxVolts = 0.0;float amps = 0.0;float minAmps = 100.0;float maxAmps = 0.0;float ah = 0.0;float watts = 0.0;float whr = 0.0;long v = 0;long a = 0;unsigned long time0 = 0;unsigned long time1 = 0;unsigned long time2 = 0;unsigned long time3 = 0;#define ADC_AMPS(a,count) (((float)((float)a)*26.7)/(((float)count)*1024.0));#define ADC_VOLTS(v,count) (((float)((float)v)*52.6)/(((float)count)*1024.0));void setup(void) {Serial.begin(9600);Serial.print("Initializing SD card...");// make sure that the default chip select pin is set to// output, even if you don't use it:pinMode(4, OUTPUT);// see if the card is present and can be initialized:if (!SD.begin(chipSelect)) {Serial.println("Card failed, or not present");// don't do anything more:return;}Serial.println("card initialized.");// flip screen, if required// u8g.setRot180();// assign default color valueif ( 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);delay(300);}void loop() {if (millis() - time0 >= 100) adc(); // set adc sample rateif (millis() - time1 >= 1000) tally(); // compute average adc result on interval}void adc(){time0 = millis();count++;a = a+analogRead(A0)-509;v = v+analogRead(A1);}void tally(){amps = ADC_AMPS(a,count);volts = ADC_VOLTS(v,count);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);minAmps = min(minAmps, amps);minVolts = min(minVolts, volts);lcd();if (millis() - time3 >= 10000){time3 = millis();serial();sdc();count = 0.0;a = 0;v = 0;}if (millis()-time2 >= 86400000){time2=millis();maxAmps=0.0;maxVolts=0.0;}}void serial(){Serial.print(count);Serial.print(",averages,");Serial.print(minVolts);Serial.print(",Vmin,");Serial.print(volts);Serial.print(",V,");Serial.print(maxVolts);Serial.print(",Vmax,");Serial.print(minAmps);Serial.print(",Amin,");Serial.print(amps);Serial.print(",A,");Serial.print(maxAmps);Serial.print(",Amax,");Serial.print(ah);Serial.print(",Ah,");Serial.print(watts);Serial.print(",W,");Serial.print(whr);Serial.print(",Whr,");Serial.print(time1/60000.0);Serial.println(",min");}void sdc(){File dataFile = SD.open("datalog.txt", FILE_WRITE);// if the file is available, write to it:if (dataFile) {dataFile.print(count);dataFile.print(",averages,");dataFile.print(minVolts);dataFile.print(",Vmin,");dataFile.print(volts);dataFile.print(",V,");dataFile.print(maxVolts);dataFile.print(",Vmax,");dataFile.print(minAmps);dataFile.print(",Amin,");dataFile.print(amps);dataFile.print(",A,");dataFile.print(maxAmps);dataFile.print(",Amax,");dataFile.print(ah);dataFile.print(",Ah,");dataFile.print(watts);dataFile.print(",W,");dataFile.print(whr);dataFile.print(",Whr,");dataFile.print(time1/60000.0);dataFile.println(",min");dataFile.close();}}void lcd(){// picture loopu8g.firstPage();do {draw1();}while( u8g.nextPage() );}void draw1() {//graphic commands to redraw the complete screen should be placed here// u8g.setFontPosTop();u8g.setFont(u8g_font_unifontr);u8g.setPrintPos(0, 14);u8g.print(volts);u8g.print("V");u8g.setPrintPos(66, 14);u8g.print(amps);u8g.print("A");u8g.setPrintPos(0, 31);u8g.print(ah);u8g.print("AHr");u8g.setPrintPos(0, 48);u8g.print(watts);u8g.print("W ");u8g.print(maxAmps*maxVolts);u8g.setPrintPos(0, 64);u8g.print(whr);u8g.print("WHr ");u8g.print(time1/60000.0);}

author: paulca
author_rank: Super Contributor
timestamp: March 21, 2018, 05:07:00 pm
content: What is the maximum file size? Will probably be found in the SDC driver/library documentation.

author: metrologist
author_rank: Super Contributor
timestamp: March 21, 2018, 06:33:43 pm
content: I read the file size is limited by the FAT file system, or around 4GB.My file had around 30k records (lines of data) and was about 2800kB.I think that if there is any glitch to the SD card, the bus shuts down. At least it will not write to the card if it's removed and reinserted. It needs to restart the SD Card bus in Setup(), so it won't recover. I hope slowing down the data logs to about 6 per minute will alleviate the symptom.

author: metrologist
author_rank: Super Contributor
timestamp: April 18, 2018, 11:58:08 pm
content: Quote from: Rick Law on March 14, 2018, 02:57:52 amIf I implemented that correctly, I saved ~80 bytes converting 22 serial print calls into 11 serialprintpair function calls. I will do the same for the SD card writes, and that is still presenting a problem for me with stopping logging after a few days, around 2-3MB. Maybe the consecutive writes are happening too fast and hanging the card? Or I should try a different card. I'm using Kingston 2GB micro SD in an adapter. Oh, and I just read about using SDFormatter instead of Windows so am trying that now...I've added an error counter if the file is not. I also moved my CS pin to 10, rather that what was specified in the header notes. I also downloaded the SDfat lib that I'll try if this is still failing.Code:void sdc(){time3 = millis();File dataFile = SD.open("datalog.txt", FILE_WRITE);// if the file is available, write to it:if (dataFile) {dataFile.print(count);dataFile.print(",averages,");dataFile.print(minVolts);dataFile.print(",Vmin,");dataFile.print(volts);dataFile.print(",V,");dataFile.print(maxVolts);dataFile.print(",Vmax,");dataFile.print(minAmps);dataFile.print(",Amin,");dataFile.print(amps);dataFile.print(",A,");dataFile.print(maxAmps);dataFile.print(",Amax,");dataFile.print(ah);dataFile.print(",Ah,");dataFile.print(watts);dataFile.print(",W,");dataFile.print(whr);dataFile.print(",Whr,");dataFile.print(time1/60000.0);dataFile.println(",min");dataFile.close();err = 0;}err=err+1;}void serial(){serialPrintPair(count,",averages,");serialPrintPair(minVolts,",Vmin,");serialPrintPair(volts,",V,");serialPrintPair(maxVolts,",Vmax,");serialPrintPair(minAmps,",Amin,");serialPrintPair(amps,",A,");serialPrintPair(maxAmps,",Amax,");serialPrintPair(ah,",Ah,");serialPrintPair(watts,",W,");serialPrintPair(whr,",Whr,");serialPrintPair(time1/60000.0,",min");}void serialPrintPair(float value,char *label) {Serial.print(value);Serial.print(label);}

author: paulca
author_rank: Super Contributor
timestamp: April 19, 2018, 06:48:55 am
content: I have had a few devices that stall on SD card errors, rather than just retrying. SD card writers are supposed to skip over errored sectors, marking them as such, but my dash cam for one get's hung up on errored sectors and goes into a loop speaking to me, "Please check the SD card, Please check the SD card, Please check the SD card". If I depower and repower it, it goes back to normal. I changed the card and this finally stopped.

author: metrologist
author_rank: Super Contributor
timestamp: April 20, 2018, 08:07:52 pm
content: yes, indications are my actions are no help. I was getting errors right off. I reset a couple times and it was still logging after a day. I found an SD Mini card I may solder to a level shifter and go with it. I read resistive dividers on the signal lines is not acceptable for SD cards. I think that is what my LC Soft SD shield has, along with a 3.3V regulator.