2

I have a timer interrupt that controls a stepper motor, on an UNO board. In the interrupt handler it checks the speed of the motor and compares it to the target speed. This allows the motor to accelerate to the target speed in a non-blocking way.

This is the relevant part of the interrupt handler:

ISR(TIMER1_COMPA_vect){
    if (targetSpeed > 0 || motorSpeed > 0){
        // leading edge of the step cycle
        if (leadingEdge){
            digitalWrite(STEP_PIN, HIGH);
            stepNumber++;
            // check speed and adjust if necesary
            if (motorSpeed < targetSpeed){
                motorSpeed += speedIncrement;
            }
            else if (motorSpeed > targetSpeed){
                motorSpeed -= speedIncrement;
            }

            leadingEdge = false;
        }
        // trailing edge
        else{
            digitalWrite(STEP_PIN, LOW);
            leadingEdge = true;
        }
    }
    OCR1A = minInteruptsPerStep + (minInteruptsPerStep * (1 - motorSpeed));
}

In the loop() I have a function that checks the state of motorSpeed to control other functions

if (motorSpeed == targetSpeed) {
    motorState = RUNNING;
} else if (motorSpeed < targetSpeed) {
    motorState  = ACCELERATING;
} else if (motorSpeed > targetSpeed) {
    motorState  = DECELERATING;
}

motorSpeed and targetSpeed are floats and range from 0-1, in steps of 0.1 speedIncrement is 0.001 As far I can see that should ramp the speed up until motorSpeed == targetSpeed, in 100 steps. And this mostly what happens. The speed gets to targetSpeed and motorState switches from ACCELERATING to RUNNING, as expected.. mostly. The problem is that it occasionally starts flipping between the three states, for no reason I can see--there is nothing that should modify the value of motorSpeed outside the interrupt handler, so once it stops accelerating it should be stable.

Is this because I'm using and comparing floats? Should I scale the speed variables up and use an int or even a byte?

  • is motorSpeed declared volatile? – Juraj Aug 15 '18 at 12:16
  • Yep, all the variables in the interrupt handler are volatiles – stib Aug 15 '18 at 12:32
  • There is a library for that: airspayce.com/mikem/arduino/AccelStepper – Jot Aug 15 '18 at 13:01
  • I tried that, but had some problems with it and the LCD library I'm using, the two wouldn't co-exist for some reason. – stib Aug 15 '18 at 13:52
  • They do not use the same timer, it should work. Perhaps they use too much memory. Which LCD library is it and is that working now? – Jot Aug 15 '18 at 18:04
2

The biggest problem here is that floats don't compare well. The issue is that a float isn't an exact number. It is, to a large extent, an approximation.

Some numbers cannot be represented by floats, so your "100" may be actually "100.00001", or "99.999999".

It is better to use integers whenever possible. Do your calculations using larger numbers, and then scale down the results when you need to use them.

If you're working in steps of 0.1 between 0 and 100, you should really be working instead in steps of 1 between 0 and 1000, and then divide by 10 later on when you need to use the value (if you even really need to do that).

Here's a little example I knocked up on Linux:

#include <stdio.h>

void main() {
    float f = 0;
    while (f < 100) {
        f += 0.1;
    }
    printf("%.6f\n",f);
}

You'd expect it to give "100.000000" as the answer. It doesn't. Instead you end up with 100.099045. That's because the closest it gets to 100 is actually the previous iteration: 99.999046.

If you do want to compare floats the best way is to use a "within X" operation. Here's a little macro:

#define WITHIN(A,B,DIFF) (fabs((A) - (B)) <= (DIFF))

Then you can:

if (WITHIN(motorSpeed, targetSpeed, 0.1)) { 
    ...
}

That should give you TRUE if the motorSpeed and targetSpeed are within 0.1 of each other.

  • Thanks. I haven't done much low level programming before, so I wasn't aware of the float issue. I might try changing to a more appropriate data type. – stib Aug 15 '18 at 11:37
4

You already know, from previous answers, why the comparison doesn't give you the result you expect: you assumed that any multiple of 0.1 is also a multiple of 0.001, which is true for real numbers but nor for floats.

To fix the issue, it has been suggested to you to allow for some fussiness in the comparison. I would suggest a different approach: keep your comparisons as they are, and instead change the way the speed is updated in the ISR:

if (motorSpeed < targetSpeed) {
    motorSpeed = min(motorSpeed + speedIncrement, targetSpeed);
}
else if (motorSpeed > targetSpeed) {
    motorSpeed = max(motorSpeed - speedIncrement, targetSpeed);
}

With this logic, motorSpeed will always end up being exactly equal to targetSpeed, irrespective of whether the speed change is a multiple of the increment or not.

A common misconception about floating point numbers has it that they are not exact. Truth is, most floating point calculations involve rounding errors, but the floating point numbers themselves are exact numbers, if not the numbers you expect¹. Then, when you do the assignment

motorSpeed = targetSpeed;

which the code above ends up doing at some point, you can test

if (motorSpeed == targetSpeed) ...

and it will be true.

¹ When you write “0.1” in the source code, you get the float which is closest to 0.1, which happens to be 0.100000001490116119384765625 exactly.

  • Nice idea, but it still elaves the problem of the comparison in the main loop() which checks the motor state. I've been reading about floats and I undertand the problem better, but what still puzzles me is that it doesn't seem to be deterministic. Since the value of motorSpeed and the value of targetSpeed is constant once the motor reaches a stable state—and in my tests it does report that motorSpeed == targetSpeed, why then after reporting this as true for hundreds of steps does it suddenly switch to false, even though there's nothing in the code that changes the value of either variable?. – stib Aug 16 '18 at 1:25
  • Note that motorSpeed is not a measurement, it's the speed the arduino is telling the motor to run at, so it's not a mechanical issue. – stib Aug 16 '18 at 1:27
  • I used this technique with ints, which solved the problem of speedIncrement not necessarily being a multiple of targetSpeed, and also the main loop() problem – stib Aug 16 '18 at 2:55
  • @stib: Computations with floats are deterministic. If you witness non deterministic behavior, you will have to search somewhere else in your code. – Edgar Bonet Aug 16 '18 at 8:11
1

You should never compare two floats with == or != directly. The reason is that floats are not accurate, so maybe 1.0 can be 0.99999999999, and the other value 1.0 might be 1.00000000001 (simplification).

Instead, use a 'range', like:

if (fabs(motorSpeed - targetSpeed) < 0.001)
{
   // Equal
}

This means the difference should be smaller than 0.001. fabs means the absolute value of a float (mathematic symbol |x| for the absolute value).

If you make more compares this way, make 0.001 a #define or const.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.