2

I've designed an apparatus that uses stepper motors, and they are linked together so that when armature A turns, it consequently moves armature B by a ratio of 1/4. In order to keep armature B in the same position, for every 4 steps stepper A takes, I need stepper B to take 1 step. I want to "round" the amount of steps required so that A will always move an amount divisible by 4, otherwise armature B drift over time with (this thing runs for 12 hours at a time). I'm trying to find an efficient way to code this:

My formula for deriving motor A's steps from degrees is: steps = degrees / 0.45

So let's say I want to turn 93 degrees. That would be steps = 93 / 0.45 = 206.66-> I cannot simply round 206 up or down because neither 206 or 207 are evenly divisible by 4. The closest whole number to 206.667 evenly divisible by 4 is 208.

So I wrote this algorithm (it sounds much cooler than saying function :) that will find this closest whole number.

int calcSteps(float targetDegrees)
{
  int gearRatio = 4;
  int cwna = 0, cwnb = 0; // Closest Whole Number Alpha/Bravo

  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;
}

Example Outputs:

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

The good news is that this works, I just can't help but think there's a better mathematical way to do this. Any ideas?

2 Answers 2

3

First, I'll show you the "manual" way.

You can divide the resulting steps by the number they should be divisible by, round to the next integer, and multiply that again by the number. I'll call this number STEPS_PER_INCREMENT below.

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;
//...

Of course, this can be optimized. But then explain the algorithm in a comment for the next reader of the source, including your future self:

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;
//...

The DEGREES_PER_STEP sound as if you use a calculated result of 1 / STEPS_PER_REVOLUTION with STEPS_PER_REVOLUTION equal to 800. So the next suggestion shows the facts even better. Source code should make things clear, not hide them. ;-)

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;
//...

This table shows some results:

target degrees theoretical steps calculated steps
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

Please be aware that his formula works only for non-negative values of the target degrees. If you have negative values, you can compensate by this extension:

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;
    }
//...

Or you use different expressions. In this case the runtime might be more even.

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;
    }
//...

Now I'll show you the professional way by using existing C++ libraries.

The Arduino is programmed in C++, and the compiler comes with several standard libraries, most probably including the math library. Since I don't have an Arduino installation here, I'm just supposing that this will work:

#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;
//...

Having said that source code should make things clear, you might want to rather use this. You can trust the compiler to optimize for you:

#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;
//...
1
  • It seems so simple now...this is really great stuff. Cheers! May 15 at 11:27
2

The simplest solution is probably to do the computation backwards: first calculate the number of steps motor B has to turn, then multiply by four to get the step count for motor A:

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

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