eval() type function?

Status
Not open for further replies.

humjaba

Active member
Hi all

I'm attempting to build a digital gauge for my car (specifically, my subaru, using the SSM serial protocol). Many of the responses from the ECU require scaling of some kind, the algebraic expressions for which I've conveniently stolen from the open-source tuning tool RomRaider. I was wondering if there was an elegant way to evaluate these without writing a separate function, or switch/case list for each variable? Here's what I would like to make work:

Code:
struct ecuDataObject {
	char name[10];
	byte a1; //address1
	byte a2; //address2
	byte a3; //address3
	char conversion[20]; //conversion str (no eval, can't use this?)
	char units[5];
};

const ecuDataObject ecuData[] = {
	{"RPM", 0x00, 0x00, 0x0e, "x/4", "RPM"},
	{"VehSpeed", 0x00, 0x00, 0x10, "x*0.621371192", "MPH"},
	{"CLT", 0x00, 0x00, 0x08, "32+9*(x-40)/5", "F"},
	{"IAT", 0x00, 0x00, 0x12, "32+9*(x-40)/5", "F"},
	{"AFR", 0x00, 0x00, 0x46, "x/128*14.7", ":1"},
	{"WGDC", 0x00, 0x00, 0x30, "x*100/255", "%"},
	{"MRP", 0x00, 0x00, 0x24, "(x-128)*37/255", "psi"},
	{"TotTiming", 0x00, 0x00, 0x11, "(x-128)/2", "deg"},
	{"KnockSum", 0xFF, 0xBF, 0xAC, "x", ""}
};

As you can see, they're not just scalar multiples, so I can't just store a multiplier for each. I really want to avoid a big long list (not that long, but it could be if I add more variables), because it's ugly, but I can't see any other way. Thanks for the help.
 
As stench says there's no eval() built into C but I think you could do what you want by using function pointers in your array instead of strings, e.g. instead of

Code:
{"VehSpeed", 0x00, 0x00, 0x10, "x*0.621371192", "MPH"},

do something like this

Code:
{"VehSpeed", 0x00, 0x00, 0x10, MPHFunction, "MPH"},

int MPHFunction(int x) {
     return(x*0.621371192);
     }

Your math is simple enough that you could do this with inline functions or #defines, if you prefer that look.

Good luck!

[edit - typos]
 
Last edited:
Each expression is ultimately x*constant1+constant2. So just store those constant1 & constant2 values for each variable and apply the same expression for each.

For instance, for RPM, constant1 is 0.25, and constant2 is 0
For CLT, ( 32+9*(x-40)/5 ), constant1 is 1.8, and constant2 is -40
etc.
 
Assuming your application doesn't need to evaluate new expressions at runtime, I suggest you write one C function per expression and use function pointers in the 'conversion' field of your struct. For example,
Code:
struct ecuDataObject {
	char name[10];
	byte a1; //address1
	byte a2; //address2
	byte a3; //address3
	float (* conversion)(float x); // function pointer
	char units[5];
};

float fn_to_RPM(float x) { return x/4.0; }

float fn_to_MPH(float x) { return x * 0.6213; }

// and so on for the other functions.

const ecuDataObject ecuData[] = {
	{"RPM", 0x00, 0x00, 0x0e, fn_to_RPM, "RPM"},
	{"VehSpeed", 0x00, 0x00, 0x10, fn_to_MPH, "MPH"},
	{"CLT", 0x00, 0x00, 0x08, fn_to_F, "F"},
	{"IAT", 0x00, 0x00, 0x12, fn_to_F, "F"},
	{"AFR", 0x00, 0x00, 0x46, fn_to_colon_1, ":1"},
	{"WGDC", 0x00, 0x00, 0x30, fn_to_percent, "%"},
	{"MRP", 0x00, 0x00, 0x24, fn_to_psi, "psi"},
	{"TotTiming", 0x00, 0x00, 0x11, fn_to_deg, "deg"},
	{"KnockSum", 0xFF, 0xBF, 0xAC, id, ""}
};

If you want to learn about other approaches, you'll find many on the net and in Computer Science text books. Examples include recursive descent parsers, parser generators, embedded scripting languages, generating machine code on the fly, crafty C++ operator overloading, and more.
 
And, if you look at the formulae, you'll discover that it seems to be just a matter of units:

VehSpeed: x*0.621371192: This is the factor to convert from km/h to mph (1/1.60934)
CLT and IAT: 32+9*(x-40)/5 is the formula to convert from Celsius to Fahrenheit

Some of those however seem to be interval scaling:
WGDC: x*100/255: scales values from 0-255 to 0-100%
MRP: (x-128)*37/255: Interval scaling, probably using a signed value

Anyhow, it's all linear scaling, so the generic solution by Jp3141 serves it all, but might give you some overhead
 
Thanks everyone for the suggestions!

Each expression is ultimately x*constant1+constant2. So just store those constant1 & constant2 values for each variable and apply the same expression for each.

For instance, for RPM, constant1 is 0.25, and constant2 is 0
For CLT, ( 32+9*(x-40)/5 ), constant1 is 1.8, and constant2 is -40
etc.
This is a good point - they're all first order algebraic so 2 constants should make this possible.


And, if you look at the formulae, you'll discover that it seems to be just a matter of units:

VehSpeed: x*0.621371192: This is the factor to convert from km/h to mph (1/1.60934)
CLT and IAT: 32+9*(x-40)/5 is the formula to convert from Celsius to Fahrenheit

Some of those however seem to be interval scaling:
WGDC: x*100/255: scales values from 0-255 to 0-100%
MRP: (x-128)*37/255: Interval scaling, probably using a signed value

Anyhow, it's all linear scaling, so the generic solution by Jp3141 serves it all, but might give you some overhead
Yes, the ECU responds to each request with a byte. Sometimes that byte is in real units with an offset, but always metric. I'm more comfortable with imperial, thus the conversion.

So which is best practice - individual functions or adding two scaling constants and having one function that takes the two constants, the ecu value, and spits out the real value? Author's choice?
 
Last edited:
It is one of those questions where the answer is it depends. Unless the code is super critical, it probably won't matter either way. Since you are reading from a serial port and that is a choke point that essentially will have vast stretches of cycles where the microprocessor is just waiting for input. This means if you do some amount of calculations, and then go to sleep waiting for new input, those calculations won't have that much effect on the program runtime, since it must still wait for the serial port.

Given your data is coming in integer, if you always convert to floating point for the few cases that use floating point, it will penalize all of the other cases, particularly since floating point is emulated on the Teensy. As a rule of thumb, figure floating point adds and subtracts involve 10's of instructions, multiplies perhaps 100's of instructions, and divide/square root 1,000's of instructions. Also, if you have the constants in memory, it prevents the compiler from optimizing the operation (but on the other hand, you have single cycle multiplies, so divide by a constant is the only place the compiler could optimize).

I tend to think pointers to functions are perhaps best, considering the different operations you are wanting to do.
 
Status
Not open for further replies.
Back
Top