10042010, 12:48 AM

#1 (permalink)

EcoModding Lurker
Join Date: Sep 2010
Location: Fresno, CA
Posts: 78
Thanks: 4
Thanked 9 Times in 7 Posts

Math functions in MPGuino giving me a migraine  HELP!!
So I've made a lot of threads here lately... would help if I wasn't the only one making threads though.
Hacking away at the MPGuino code, I've spent ALL DAY trying to figure out the convoluted (but probably nice and efficient) "64bit" math code in MPGuino. Well, first I don't understand that part, since the only math really needed here is 32bit, but... *shrug*.
I don't get it. What are the types of values being used in these functions?! format() only takes a 5digit number, which is really low precision. Yet, it seems like I can't get a straight answer out of things like tank.mpg() or tank.gallons() because their result was produced by the 64bit math functions.
I can't figure out what tmp1 and tmp2 are used for (not to mention that the Arduino IDE's font is terrible, no difference between "i" and "1"), or how data flows in and out of them. I made a guess but the system totally EXPLODED with compiler errors about "invalid conversion from 'long unsigned int' to 'long unsigned int*'" and "initializing argument 2 of 'void sub64(long unsigned int*, long unsigned int*'"... along with a nice dose of "incompatible types in assignment of 'double' to 'long unsigned int [2]'"... and every time I apply one solution I found in the code, the list of errors grows.
Here's the code that explodes:
Code:
void doDisplayF4(void) {
unsigned long tmp1[2];
unsigned long tmp2[2];
LCD::gotoXY(0,0);
tmp1 = 17.9999999; // tank mpg temp
tmp2 = parms[tankSizeIdx];
if (tank.mpg() > 0) tmp1 = tank.mpg();
LCD::print("Rem miles:");
sub64(tmp2,tank.gallons());
mul64(tmp2,tmp1);
LCD::print(format(tmp2));
}
Makes no sense. None of this stuff makes sense. The Arduino docs don't help a hell of a lot either, basically saying that "*" is an advanced topic we're not going to cover.
And as I mentioned in a previous topic, apparently just doing standard math doesn't get me anywhere. It works for a brief moment but once any of the values depart from zero, it explodes and the math goes haywire (something like 80495 beyond the limits of format()).
I'd love just a little commenting on the math functions... :/



Today



Other popular topics in this forum...



10042010, 08:25 AM

#2 (permalink)

needs more cowbell
Join Date: Feb 2008
Location: ÿ
Posts: 5,038
Thanks: 157
Thanked 266 Times in 209 Posts

All the variables are 32bit, which holds a nice big number (4 gig) you don't have to worry about running out with right away.
However, in order to retain precision after a math operation, like where you need to multiply by one thing and divide by another, you should always do the multiplication parts first. i.e. in integers, 50x21/3 is different than 50/3x21.
So the big size problem comes in when doing the multiplication parts. If you were to multiply 65536 by 65536, boom! you just went over your 32 bit budget, even though it only takes 17 bits to represent an unsigned 65536.
To put 65536 in perspective, lets say you have set pulsesPerMile to 10000, the trip accumulator will have reached 65536 pulses after 6.5536 miles. Injector open time is measured in microseconds, so it doesn't take long to accumulate rather large numbers there either.
So the scheme here is to use 64 bit math "Temporarily" inside functions that do computations. But the functions still have to be arranged and organized internally so that they are reasonably sure that they will return 32 bit numbers under ordinary circumstances, while not losing precision to scaling.
Ok, so we have a scheme for dealing with fairly large numbers, the next issue is space/resources. It turns out that the compiler does have a long long type, but it soaked up so much space when it was included that it was impractical to use. So I put some simple routines (add/div/mul/sub,init).
Init usually is used to "promote" a 32 bit number to the 64 bit representation. So lets look at a snippet:
init64(tmp1, 0, 1000ul);
init64(tmp2, 0, imph);
mul64(tmp1, tmp2);
init64(tmp2, 0, igph);
div64(tmp1,tmp2);
return tmp1[1];
so: we multiply imph (instant mph) by 1000, then divide it by igph.
note also that some variables have an implied 3 decimal places. So that 45.502 mph is represented as 45502 , floating point math is no good on these chips, space pigs and slow so this is a common approach. So here we multiplied by 1000 first so that the result would also have the implied 3 decimal places.
so these should use init, and careful though about when to multiply/divide and if the variable is already in implied decimal or not.
i.e. this is not how to initialize one of these 64 bit variables, it isn't array notation and it introduced a floating point into our land of happy integers:
tmp1 = 17.9999999; // tank mpg temp
instead it should arguably be:
init64(tmp1, 0, 17999ul);
I always have to sketch out the whole function, to figure out the right order of operations, and do some sanity checks on it, then double check the cpu screen after it is implemented to ensure we have not depleted our resource budget.
Even miles remaining is not contention free. Earlier discussion on it were concerned if it should start with the gallons remaining in the tank, then perhaps it should use the current trip mpg to give a more "how am I driving today" feel to the miles remaining number. Some thought it should only be the last 5 minutes of driving (have fun tracking that), and I never saw it as being a terribly interesting metric since there is a lot of fudge involved and you would have to be naive to rely on it completely. I think there are still some interesting functionality to implement, i.e. coastdown/cda/rolling resistance calculator or some measure of "realtime" bsfc, which is where I usually think of to poke at it next, and given limited cpu resources I prefer to think about what items will lead to the most efficient drivers myself, i.e. what tools could a dedicated hypermiler use to be more efficient that they don't have?
__________________
WINDMILLS DO NOT WORK THAT WAY!!!



10042010, 08:49 AM

#3 (permalink)

EcoModding Lurker
Join Date: Sep 2010
Location: Fresno, CA
Posts: 78
Thanks: 4
Thanked 9 Times in 7 Posts

Have I mentioned to you lately that you're an awesome, godlike entity? I mean, in the most literal sense. Think about it. Everyone always says God works in mysterious ways you may not understand. So while I'm sitting here scouring over your code and being like "what on earth is going on here?", I only later realize just what, exactly, on earth is going on there.
It just took a little help from a simple green plant friend to get my mind in "code mode" where I can figure out all these interrelated systems. Standing next to my fridge with a huge case of the munchies I figured out that the math probably isn't floatingpoint math at all, and that the results I saw by running all the params through format() showed such erratic figures because format() *creates* decimal points based on val*1000. Oy.
Then there was the mystery of pointers and arrays, though. Where I come from in PHPland, an array is a type of data that can contain any other sort of data, and PHP knows what to do with it (feed an integer into a character/string function and it magically becomes a numeric "string"). Now I'm beginning to understand how arrays work. Pointers? Hell, I'm lost on that. I can't figure out for the life of me why this code:
Code:
LCD::print(getStr(PSTR("OpenGauge ")));
LCD::gotoXY(0, 1);
LCD::print(getStr(PSTR(" MPGuino v0.86")));
...creates a pointer to, for example, the string "OpenGauge" (+spaces), then immediately passes that pointer to getStr, which itself calls a function that reads the specified pointer from Flash memory...
Instead of simply:
Code:
LCD::print("OpenGauge ");
LCD::gotoXY(0, 1);
LCD::print(" MPGuino v0.86");
... which is the way it's done everywhere else in the program. *scratches head* Is there a reason behind that?
I modified the hell out of the tank miles code (the problem code above) and made use of init64 (which I seem to have figured out as you were writing that reply!), and it compiles fine now using tmp13, but the display is still 0. Quite likely because I still had no concept about the 1000based computations, so base MPG is still 17 .9999999...
Fun, fun!



10042010, 10:31 AM

#4 (permalink)

needs more cowbell
Join Date: Feb 2008
Location: ÿ
Posts: 5,038
Thanks: 157
Thanked 266 Times in 209 Posts

the PSTR function is a way to define a string (or other data), such that it isn't automatically loaded into ram but only in flash.
Code:
the following takes up an extra 16 bytes of ram, always:
LCD::print("OpenGauge ");
whereas this tells the compiler to not preallocate ram for the string, it will be fetched by getStr only when needed:
LCD::print(getStr(PSTR("OpenGauge ")));
You should make a visit to avrfreaks.net sometime, there is a plethora of tricks and gotchas with microcontrollers.
__________________
WINDMILLS DO NOT WORK THAT WAY!!!



