Как найти целое число (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

Хорошая новость в том, что это работает, я просто не могу не думать, что есть лучший математический способ сделать это. Есть идеи?

, 👍2


2 ответа


Лучший ответ:

3

Сначала я покажу вам «ручной» способ.

Вы можете разделить полученные шаги на число, на которое они должны делиться, округлить до следующего целого числа и снова умножить на число. Ниже я назову это число 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;
//...
,

Теперь это кажется таким простым...это действительно замечательная вещь. Ура!, @HavocRC


2

Самым простым решением, вероятно, будет провести вычисления в обратном порядке: сначала вычислите количество шагов, которое должен сделать двигатель B, затем умножьте на четыре чтобы получить количество шагов для двигателя A:

int steps_B = round(degrees / 1.8);
int steps_A = 4 * steps_B;
,