How to code a polynomial equation?

Hi all,

I have made an Arduino based dashboard for my car, and it is working pretty good. I've just had the new dial made up with the speed and rev markings on, and this is working well for the tachometer and the rpm reading is correctly displayed with a stepper motor being driven by TeensyStep.

When I try to calibrate the speedo though, I'm finding a non-linear error of between 0 and 3mph through the speed range of 0-200. I've plotted roughly what the error is in excel and made a best fit curve that fits it very well, though it is a rather complicated 6th order polynomial. I will go through and check the stepper config to see if anything else might be causing this error, but should I need to test an adjustment scaling factor, how would I program this equation??

"y = -8E-12x6 + 4E-09x5 - 9E-07x4 + 7E-05x3 - 0.0019x2 - 0.0235x + 1.3417"

It is certainly overkill to use a sixth order polynomial to fix that problem, since a pointer type dial can't indicate to precision better than about 0.01 of the full scale value.

But the first term could be written (and so forth)

y = -8.0e-12*pow(x,6) + ...

How much accuracy do you really need between 0 and 3 MPH? Is that a speed limit somewhere? :slight_smile: Also how many tenths of millimeters of difference does the error make on the physical dial?

A look-up table might be overall faster and as accurate, since the AVR lacks an FPU, is essentially integral, and all the orders are with respect to x.

If you must use the poly, rearrange the computations to use the same variable for increasing values of x. So do the x term, multiply xx and use it for the second order term, again by x to make xx*x = x3, and so on… this will greatly reduce the workload for the exponentiation of x. You can even do this using scaled integers, and avoid float calculations entirely.

An LUT is a good idea, and can have a small memory footprint if interpolation is used.

Perehama:
A look-up table might be overall faster and as accurate, since the AVR lacks an FPU, is essentially integral, and all the orders are with respect to x.

Thanks everyone for your answers... I tend to agree with this - perhaps a lookup table would be more useful as I'm only interested in whole numbers of speed... How do you do this?

It looks as though you are working with a 0-1.5% discrepancy in the speedo output, have you consideredthe consequences of other effects on your calculations that would make correcting that discrepancy almost irrelevant? Take for axample tyre wear on a 90cm diameter tyre you will lose 15mm in wear that will be a 1.5% discrepancy, the diameter of the tyre is likely to change with temperature (no idea on figures for that), changing tyres from one set to another will have an effect - especially between manufacturers and different spec tyres.

I know that one of the Jaguars when it first raced in the US seemed to be going faster than the speedo indicated and they found it was due to the tyre expanding at high speeds so having a greater diameter.

If you've managed to get a speedo accurate within 1.5% I think you have done pretty well, trying to improve on that might get you better results for the very short term, but could end up being a never ending task. Just my tuppence worth.

450nick:
Thanks everyone for your answers... I tend to agree with this - perhaps a lookup table would be more useful as I'm only interested in whole numbers of speed... How do you do this?

unsigned long x;// this is the index.
const unsigned long Y[] = {1, 2, 3, 4, 5};//this is the lookup table.
// elsewhere in code...
output = Y[x];

The Teensy has a very powerful processor. I didn’t look but I’m sure it has a single cycle or at least single instruction multiply. I think you could justify calculating the poly. Just use the x and x2 terms and use scaled integer math. I’ll bet it comes really close.

Don’t evaluate the powers of x explicitly, write your polynomial as follows:

a₄ x⁴ + a₃ x³ + a₂ x² + a₁ x + a₀ = x·(x·(x·(x·(a₄) + a₃) + a₂) + a₁) + a₀

This only requires n multiplications and n additions.

You could implement it like this:

template <size_t N>
double poly_eval(const double (&coeff)[N], double x) {
    double result = coeff[N-1];
    for (size_t i = N-1; i --> 0;)
        result = coeff[i] + x * result;
    return result;
}

And use it as follows:

    double poly[] = {1, 2, 3, 4}; // 1 + 2x + 3x² + 4x³
    double x = 2;
    double y = poly_eval(poly, x);
    Serial.println(y); // prints 49

See it in action: Compiler Explorer

Pieter