Как найти целое число (n), которое при умножении на (m) будет ближе всего к (x)?
Я спроектировал устройство, использующее шаговые двигатели, и они соединены вместе так, что когда якорь A поворачивается, он соответственно перемещает якорь B в соотношении 1/4. Чтобы удерживать якорь B в том же положении, на каждые 4 шага, которые делает шаговый двигатель A, мне нужно, чтобы шаговый двигатель B делал 1 шаг. Я хочу «округлить» количество требуемых шагов, чтобы A всегда перемещался на величину, кратную 4, в противном случае якорь B будет дрейфовать со временем (эта штука работает по 12 часов за раз). Я пытаюсь найти эффективный способ закодировать это:
Моя формула для вычисления шагов мотора А из градусов: шаги = градусы / 0,45
Итак, допустим, я хочу повернуть на 93 градуса. Это будет steps = 93 / 0.45 = 206.66->
Я не могу просто округлить 206 вверх или вниз, потому что ни 206, ни 207 не делятся нацело на 4. Ближайшее целое число к 206.667, делящееся нацело на 4, — это 208.
Итак, я написал этот алгоритм (звучит гораздо круче, чем функция :), который найдет это ближайшее целое число.
int calcSteps(float targetDegrees)
{
int gearRatio = 4;
int cwna = 0, cwnb = 0; // Ближайшее целое число Альфа/Браво
float targetSteps = (targetDegrees / 0.45);
for (int x = 1; x * gearRatio < targetSteps; x++) cwna = x;
cwnb = (cwna + 1);
int closestWholeNumber = (targetSteps - cwna * gearRatio) < (cwnb * gearRatio - targetSteps) ? cwna : cwnb;
float newAngle = closestWholeNumber * gearRatio * 0.45;
int calculatedSteps = closestWholeNumber * GEAR_RATIO;
Serial.print("CWNA = "); Serial.println(cwna);
Serial.print("CWNB = "); Serial.println(cwnb);
Serial.print("Closest Number = "); Serial.println(closestWholeNumber);
Serial.print("New angle = "); Serial.println(newAngle);
Serial.print("Steps to turn: "); Serial.println(calculatedSteps);
return calculatedSteps;
}
Примеры выходных данных:
calc(93);
CWNA = 51
CWNB = 52
Closest Number = 52
New angle = 93.60
Steps to turn: 208
calc(87);
CWNA = 47
CWNB = 48
Closest Number = 47
New angle = 84.60
Steps to turn: 192
Хорошая новость в том, что это работает, я просто не могу не думать, что есть лучший математический способ сделать это. Есть идеи?
@HavocRC, 👍2
2 ответа
Лучший ответ:
Сначала я покажу вам «ручной» способ.
Вы можете разделить полученные шаги на число, на которое они должны делиться, округлить до следующего целого числа и снова умножить на число. Ниже я назову это число STEPS_PER_INCREMENT
.
const double DEGREES_PER_STEP = 0.45;
const int STEPS_PER_INCREMENT = 4;
//...
int calculatedSteps = (unsigned int)(targetDegrees / DEGREES_PER_STEP / STEPS_PER_INCREMENT + 0.5) * STEPS_PER_INCREMENT;
//...
Конечно, это можно оптимизировать. Но тогда объясните алгоритм в комментарии для следующего читателя источника, включая себя в будущем:
const double DEGREES_PER_STEP = 0.45;
const int STEPS_PER_INCREMENT = 4;
const double DIVISOR = DEGREES_PER_STEP * STEPS_PER_INCREMENT;
//...
int calculatedSteps = (int)(targetDegrees / DIVISOR + 0.5) * STEPS_PER_INCREMENT;
//...
DEGREES_PER_STEP
звучит так, как будто вы используете вычисленный результат 1 / STEPS_PER_REVOLUTION
, где STEPS_PER_REVOLUTION
равен 800. Поэтому следующее предложение показывает факты еще лучше. Исходный код должен прояснять вещи, а не скрывать их. ;-)
const int STEPS_PER_REVOLUTION = 800;
const int STEPS_PER_INCREMENT = 4;
const int DEGREES_PER_REVOLUTION = 360;
const double FACTOR = STEPS_PER_REVOLUTION / STEPS_PER_INCREMENT / DEGREES_PER_REVOLUTION;
//...
int calculatedSteps = (int)(targetDegrees * FACTOR + 0.5) * STEPS_PER_INCREMENT;
//...
В этой таблице показаны некоторые результаты:
целевые градусы | теоретические шаги | рассчитанные шаги |
---|---|---|
90 | 200 | 200 |
91 | 202.222 | 204 |
92 | 204.444 | 204 |
93 | 206.666 | 208 |
94 | 208.888 | 208 |
95 | 211.111 | 212 |
96 | 213.333 | 212 |
97 | 215.555 | 216 |
98 | 217.777 | 216 |
99 | 220 | 220 |
Обратите внимание, что его формула работает только для неотрицательных значений целевых градусов. Если у вас отрицательные значения, вы можете компенсировать это расширением:
const int STEPS_PER_REVOLUTION = 800;
const int STEPS_PER_INCREMENT = 4;
const int DEGREES_PER_REVOLUTION = 360;
const double FACTOR = STEPS_PER_REVOLUTION / STEPS_PER_INCREMENT / DEGREES_PER_REVOLUTION;
//...
int calculatedSteps = (int)(targetDegrees * FACTOR + 0.5) * RESOLUTION;
if (targetDegrees < 0) {
calculatedSteps = calculatedSteps - STEPS_PER_INCREMENT;
}
//...
Или вы используете другие выражения. В этом случае время выполнения может быть более равномерным.
const unsigned int STEPS_PER_REVOLUTION = 800;
const unsigned int STEPS_PER_INCREMENT = 4;
const int DEGREES_PER_REVOLUTION = 360;
const double FACTOR = STEPS_PER_REVOLUTION / STEPS_PER_INCREMENT / DEGREES_PER_REVOLUTION;
//...
if (targetDegrees >= 0) {
calculatedSteps = (int)(targetDegrees * FACTOR + 0.5) * STEPS_PER_INCREMENT;
} else {
calculatedSteps = (int)(targetDegrees * FACTOR - 0.5) * STEPS_PER_INCREMENT;
}
//...
Теперь я покажу вам профессиональный способ с использованием существующих библиотек C++.
Arduino запрограммирован на C++, а компилятор поставляется с несколькими стандартными библиотеками, скорее всего, включая математическую библиотеку. Поскольку у меня нет установленной Arduino, я просто предполагаю, что это сработает:
#include <cmath>
const int STEPS_PER_REVOLUTION = 800;
const int STEPS_PER_INCREMENT = 4;
const int DEGREES_PER_REVOLUTION = 360;
const double FACTOR = STEPS_PER_REVOLUTION / STEPS_PER_INCREMENT / DEGREES_PER_REVOLUTION;
//...
long calculatedSteps = lround(targetDegrees * FACTOR) * STEPS_PER_INCREMENT;
//...
Сказав, что исходный код должен прояснить ситуацию, вы, возможно, захотите использовать это. Вы можете доверить компилятору оптимизацию для вас:
#include <cmath>
const double STEPS_PER_REVOLUTION = 800;
const int STEPS_PER_INCREMENT = 4;
const int DEGREES_PER_REVOLUTION = 360;
const double STEPS_PER_DEGREE = STEPS_PER_REVOLUTION / DEGREES_PER_REVOLUTION;
//...
double theoreticalSteps = targetDegrees * STEPS_PER_DEGREE;
double theoreticalIncrements = theoreticalSteps / STEPS_PER_INCREMENT;
long roundedIncrements = lround(theoreticalIncrements);
long calculatedSteps = roundedIncrements * STEPS_PER_INCREMENT;
//...
Самым простым решением, вероятно, будет провести вычисления в обратном порядке: сначала вычислите количество шагов, которое должен сделать двигатель B, затем умножьте на четыре чтобы получить количество шагов для двигателя A:
int steps_B = round(degrees / 1.8);
int steps_A = 4 * steps_B;
- Как я могу использовать степпер для определенной степени?
- Разные и самые быстрые способы вычисления синусов и косинусов в Arduino
- Arduino uno + cnc Shield v3 + драйвер шагового двигателя A4988 + AccelStepper?
- Шаговый двигатель не поворачивается/не поворачивается против часовой стрелки
- Шаговый двигатель с концевыми выключателями
- Вычисление отклонения от курса по магнитометру и акселерометру
- Преобразование каждой цифры целого числа в соответствующие символы ASCII
- Запустить два степпера одновременно
Теперь это кажется таким простым...это действительно замечательная вещь. Ура!, @HavocRC