Является ли странная проблема с сопоставлением поплавков?
У меня есть прерывание таймера, которое управляет шаговым двигателем на плате UNO. В обработчике прерываний он проверяет скорость двигателя и сравнивает его с целевой скоростью. Это позволяет двигателю ускоряться до целевой скорости неблокирующим способом.
Это соответствующая часть обработчика прерываний:
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));
}
В loop()
У меня есть функция, которая проверяет состояние motorSpeed
для управления другими функциями
if (motorSpeed == targetSpeed) {
motorState = RUNNING;
} else if (motorSpeed < targetSpeed) {
motorState = ACCELERATING;
} else if (motorSpeed > targetSpeed) {
motorState = DECELERATING;
}
motorSpeed
и targetSpeed
являются поплавками и варьируется от 0-1, с шагом 0,1 speedIncrement
равно 0.001. Насколько я могу видеть, это должно ускорить скорость до motorSpeed == targetSpeed
, на 100 шагов. И в основном это происходит. Скорость доходит до targetSpeed
и motorState переключается с ACCELERATING
to RUNNING
, как и ожидалось .. в основном. Проблема в том, что она иногда начинает перелистывать между тремя состояниями, без всякой причины, я могу видеть - нет ничего, что должно изменить значение motorSpeed
за пределами обработчика прерываний, поэтому, как только он прекращает ускорение, он должен быть стабильным.
Это потому, что я использую и сравниваю поплавки? Должен ли я масштабировать переменные скорости вверх и использовать int или даже байт?
3 ответа
Самая большая проблема здесь в том, что поплавки плохо сравниваются. Проблема в том, что float не является точным числом. Это в значительной степени приближение.
Некоторые числа не могут быть представлены поплавками, поэтому ваш «100» может быть фактически «100.00001» или «99.999999».
Лучше использовать целые числа, когда это возможно. Сделайте свои расчеты с использованием больших чисел, а затем уменьшите результаты, когда вам нужно их использовать.
Если вы работаете с шагом 0,1 между 0 и 100, вы должны действительно работать с шагом 1 между 0 и 1000, а затем делить на 10 позже, когда вам нужно использовать значение (если вы даже действительно нужно это сделать).
Вот небольшой пример, который я использовал в Linux:
#include <stdio.h>
void main() {
float f = 0;
while (f < 100) {
f += 0.1;
}
printf("%.6f\n",f);
}
Вы ожидали, что он ответит «100.000000». Это не так. Вместо этого вы получаете 100.099045. Это потому, что наиболее близким к 100 является фактически предыдущая итерация: 99.999046.
Если вы хотите сравнить поплавки, лучший способ - использовать операцию «внутри X». Вот небольшой макрос:
#define WITHIN(A,B,DIFF) (fabs((A) - (B)) <= (DIFF))
Затем вы можете:
if (WITHIN(motorSpeed, targetSpeed, 0.1)) {
...
}
Это должно дать вам ИСТИННЫЙ, если motorSpeed и targetSpeed находятся в пределах 0,1 друг от друга.
Вы уже знаете, из предыдущих ответов, почему сравнение не дает вы ожидаете результат: вы предположили, что любое кратное 0,1 также кратное 0,001, что верно для действительных чисел, но не для плавающих.
Чтобы устранить проблему, вам было предложено разрешить некоторые суетливость в сравнении. Я бы предложил другой подход: держать ваши сравнения, как они есть, и вместо этого изменить способ скорости обновлено в ISR:
if (motorSpeed < targetSpeed) {
motorSpeed = min(motorSpeed + speedIncrement, targetSpeed);
}
else if (motorSpeed > targetSpeed) {
motorSpeed = max(motorSpeed - speedIncrement, targetSpeed);
}
С этой логикой motorSpeed
всегда будет ровно равным
на targetSpeed
, независимо от того, является ли изменение скорости множественным
от приращения или нет.
Распространенное заблуждение относительно чисел с плавающей запятой имеет то, что они не точный. Правда, большинство вычислений с плавающей запятой включают округление ошибки, но сами числа с плавающей запятой - точные числа , если не числа, которые вы ожидаете¹. Затем, когда вы выполняете задание
motorSpeed = targetSpeed;
, который в какой-то момент заканчивается выше, вы можете протестировать
if (motorSpeed == targetSpeed) ...
, и это будет правда.
¹ Когда вы пишете «0,1» в исходном коде, вы получаете код float
, который
ближайший к 0,1, который составляет 0,100000001490116119384765625
точно .
Вы не должны сравнивать два поплавка с == или! = напрямую. Причина в том, что float неточно, поэтому, возможно, 1.0 может быть 0.99999999999, а другое значение 1.0 может быть 1.00000000001 (упрощение).
Вместо этого используйте «диапазон», например:
if (fabs(motorSpeed - targetSpeed) < 0.001)
{
// Equal
}
Это означает, что разница должна быть меньше 0,001. fabs означает абсолютное значение поплавка (математический символ | x | для абсолютного значения).
Если вы сделаете больше сравнений таким образом, сделайте 0.001 a #define или const.