Automating Rotary Table

Home Model Engine Machinist Forum

Help Support Home Model Engine Machinist Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.
Hi Ron
Which thread are you referring to? There are quite a few different indexer/dividing threads.

I have a version of the one described in”Rotary Table for Dummies” thread hooked up to a spin index. I’ve thought about mounting it to my rotary table as well. As designed it does not provide power feed. Does the one you’re working on do that?

The only comment I have on the code is; why on earth are you working in seconds? It’s bad enough that there are 360 degrees to a circle without making it any worse. LOL

John

John,

There are a few things to consider about using rotary table positioning for machining. 1st, motor capability. 2nd, longevity of the rotary table gears. 3rd, practicality. I can assure you that the straight cut gear on the table will fail due to the worm having a very small contact patch. In my case my CNC milling machine only has 3 axis so the rotary table cannot be part of the coordinated motion. For me that combined with having circular interpolation makes machining with the rotary table impractical.

Ron
 
At mercy of others, cant write code. Could learn, but would ages, & take away from remaining health. (Oedema)

Qtron,

Programming is something that people consider to hard before they have tried. I have programmed in Visual Basic 6.0 for 20+ years so for me learning C++ was no harder than singing the national anthem in Chinese. I started with the butt stupid "Hello World" lesson and two hours later I compiled the above prototype code.

Ron
 
The code below is my revision of group member, bmac2's code that he published in Arduino Rotary Table for Dummies. To my knowledge there is nothing wrong with his code. Every coder has their own flavor of logic and it's in that logic where the power of the code resides. Two weeks ago I had never written code in C++ so it was a ground zero start up when I compiled my FIRST C++ console code, the butt stupid default "Hello World" program. Today I'm right at home in the Arduino C++ programming language. I started in MS VS 2019 and I was very disappointed when I learned that the Arduino IDE had it's own version of C++ but it wasn't hard to adopt. The hardest part of learning to write code is shaking off the preconceived notion that it's hard to learn. The down side to learning how to write code is that you will not be able to resist writing a program to control everything including your wife. If you have ever wanted to learn how to write computer programs then don't wuss out before you have tried.

Code:
/*
My revision of homemodelenginemachinist member, bmac2's code.
https://www.homemodelenginemachinist.com/threads/arduino-rotary-table-for-dummies.26744/

Auto Indexer Control
     (12/29/19)
     = Given =
Rotary Table gear ratio: 90:1
360 degrees = 1296000 seconds
Servo Motor gear ratio: 5:1
Servo Motor ppr: 2048

Servo Motor ppr per Rotary Table Revolution: ((2048 * 5:1) * 90:1) = 921600
(1296000 / 921600) = 1.40625
Therefore, the Rotary Table will rotate 1.40625 seconds per Motor ppr.
*/

#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <Wire.h>

const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] =
{
 {'1','2','3','A'},
 {'4','5','6','B'},
 {'7','8','9','C'},
 {'.','0','#','D'}
};
byte rowPINS[ROWS] = {11,10,9,8};
byte colPINS[COLS] = {7,6,5,4};

/*
Initialize instance of Classes, Keypad and LiquidCrystal_I2C.
*/
Keypad kypd = Keypad(makeKeymap(keys),rowPINS,colPINS, ROWS, COLS);
LiquidCrystal_I2C lcd(0x27,20,4);

/*
Set Up Variables
*/
const float shorty = (3600 / (1296000 / ((2048 * 90) * 5)));
const int dwell = 1;
float degrees = 0;
float curpos = 0;
long steps = 0;
int evnt = 0;

/*
Assign Pins
*/
const int step = 2;
const int direction = 3;

void setup()
{
  lcd.init(); // Initialize the LCD 
  pinMode(step, OUTPUT);
  pinMode(direction, OUTPUT);
  lcd.backlight();
  lcd.print("Auto Indexer Control"); // Print welcome message to the LCD.
  lcd.setCursor(4,2);
  lcd.print(" ");
  lcd.setCursor(3,3);
  lcd.print("updated 2019");
  delay(2000);
  lcd.init();
  evnt = 0;
  char key = kypd.getKey();
  lcd.print("Enter Selection:"); 
  lcd.setCursor(0,1);
  lcd.print("Degrees   = A");
  lcd.setCursor(0,2);
  lcd.print("Divisions = B");
  lcd.setCursor(0,3);
  lcd.print("JOG       = C");

  while(evnt == 0)
  {
    key = kypd.getKey();

    switch (key)
    {
      case NO_KEY: break;
  
      case 'A': degrees = getdegrees();
                lcd.clear();
                evnt = 1;
                break;
      case 'B': degrees = getdivisions();
                evnt = 2;
                break;
      case 'C': degrees = getjoginc();
                lcd.clear();
                evnt = 3;
                break;
    }
  }
}
 
void loop()
{
  lcd.clear();
  char key = kypd.getKey();
  curpos = 0;
  lcd.setCursor(7,0);
  lcd.print("Total:  ");
  lcd.print(curpos,2); // total steps
  lcd.setCursor(0,3);
  lcd.print("FOR=A   REV=B    X=C");

  while(key != 'C') // C will return to start menu
  {
    lcd.setCursor(0, 0);
    lcd.print(degrees, 2);
    lcd.print((char)223);
    key = kypd.getKey();

    if(key == 'A') // FORWARD
    {
      curpos = curpos + degrees;
      steps = getsteps(degrees);
      digitalWrite(direction, LOW);
      printadvance();
    }

    if(key =='B') // REVERSE
    {
      curpos = curpos - degrees;
      steps = getsteps(degrees);
      digitalWrite(direction, HIGH);
      printadvance();
    }
  }
  lcd.init();
  setup();
}
 
int getjoginc()
{
  int joginc = 0;
  char key = kypd.getKey();

  lcd.clear();
  lcd.setCursor(6,0);
  lcd.print("Jogging");
  lcd.setCursor(0,1);
  lcd.print("A=1 B=10 C=100 Steps");
  lcd.setCursor(0,2);
  lcd.print("Enter Degrees:");
  lcd.setCursor(0,3);
  lcd.print("OK = # ");
  lcd.print((char)60);
  lcd.print((char)45);
  lcd.print(" D");

  while(key != '#')
  {
    switch (key)
    {
      case NO_KEY: break;
      case 'A': joginc = 1;
                lcd.setCursor(14,2);
                lcd.print(degrees);
                break;
      case 'B': joginc = 10;
                lcd.setCursor(14,2);
                lcd.print(degrees);
                break;
      case 'C': joginc = 100;
                lcd.setCursor(14,2);
                lcd.print(degrees);
                break;
      case 'D': lcd.setCursor(14,2);
                lcd.print(" ");
                lcd.setCursor(14,2);
                break;
    }
    key = kypd.getKey();
  }
  return joginc;
}
 
float getdivisions()
{
  char key = kypd.getKey();
  float dgrs = 0;
  String divstr;
  int divs = 0;

  lcd.clear();
  lcd.setCursor(0,1);
  lcd.print("Enter Division:");
  lcd.setCursor(0,3);
  lcd.print("OK = # ");
  lcd.print((char)60);
  lcd.print((char)45);
  lcd.print(" D");
  lcd.setCursor(16,1);

  while(key != '#')
  {
    switch (key)
    {
      case NO_KEY: break;
      case 'D': lcd.setCursor(16,1);
                lcd.print(" ");
                lcd.setCursor(16,1);
                divstr = "";
                break;
      case '0': divstr += ("0");
      case '1': divstr += ("1");
      case '2': divstr += ("2");
      case '3': divstr += ("3");
      case '4': divstr += ("4");
      case '5': divstr += ("5");
      case '6': divstr += ("6");
      case '7': divstr += ("7");
      case '8': divstr += ("8");
      case '9': divstr += ("9");
                lcd.print(key);
                break;
    }
    key = kypd.getKey();
  }
  dgrs = (360 / divstr.toInt());
  divstr = "";
  return dgrs;
}

float getdegrees()
{
  char key = kypd.getKey();
  float dgrs = 0;
  String dgrstr;

  lcd.clear(); // initialize LCD
  lcd.setCursor(0, 1);
  lcd.print("Enter Degrees:");
  lcd.setCursor(0, 3);
  lcd.print("OK = # ");
  lcd.print((char)60);
  lcd.print((char)45);
  lcd.print(" D");
  lcd.setCursor(15, 1);

  while(key != '#')
  {
    switch (key)
    {
      case NO_KEY: break;
      case '.': dgrstr += (".");
                break;
      case 'D': lcd.setCursor(15, 1);
                lcd.print(" ");
                lcd.setCursor(15, 1);
                dgrstr = "";
                break;
      case '0': dgrstr += ("0");
      case '1': dgrstr += ("1");
      case '2': dgrstr += ("2");
      case '3': dgrstr += ("3");
      case '4': dgrstr += ("4");
      case '5': dgrstr += ("5");
      case '6': dgrstr += ("6");
      case '7': dgrstr += ("7");
      case '8': dgrstr += ("8");
      case '9': dgrstr += ("9");
                break;
    }
    key = kypd.getKey();
  }
  dgrs = dgrstr.toFloat();
  dgrstr = "";
  return dgrs;
}
long getsteps(float dgrs)
{
  return round(dgrs * shorty);
}
 
void printadvance()
{
  lcd.setCursor(6, 1);
  lcd.print("Moving");
  lcd.setCursor(4, 2);
  lcd.print("Steps  ");
  lcd.print(steps, 0);
  lcd.setCursor(13, 0);
  lcd.print(curpos, 2);
  index(steps, 0);
  lcd.setCursor(6, 1);
  lcd.print("      ");
}

void index(long stps, int dw)
{
  for(int i = 0; i < steps; i++)
  {
    digitalWrite(step, HIGH);
    delay(dwell);
    digitalWrite(step, LOW);
    delay(dwell);
  }
}

void software_Reset() // Initializes program without resetting the peripherals and registers.
{
  asm volatile ("  jmp 0");
}
 
Last edited:
Your code looks good so far (although maybe a smidge under-commented for my old professors' liking). I know it doesn't make much of a difference unless you're making a lot of consecutive movements, but your rounding errors would eventually compound in the 'getsteps' function:
long getsteps(float dgrs)
{
return round(dgrs * shorty);
}

It could certainly be argued that a missed step here and there was no big deal and also that missed steps would be counteracted by added steps, but if you were (for some reason) to rotate by a known amount, say 1 degree, and repeat for a revolution or two of the table, your error would eventually show up. One way to allow for this would be to accumulate the rounding error in another variable and adjust for the error as it became significant (i.e. add or deduct one step as your rounding figure approached +/- 1 step). Then you'd never be more than a single step (half a step maybe?) from theoretical even if you incremented by half a degree for a thousands of degrees.
 
Personally, I think its smart working in seconds becasue that lets you work with integers.

I'm wondering if the fact you are using a servo with an encoder if you don't have an advantage around accuracy. Are you sending the position error back into your script? Your encoder accuracy may exceed your step accuracy. If each division was resolved to an absolute position around the circle, you should know exactly where you are before you start any move so it would be a matter of programming a move to the next position. If you can get the feedback signals working for this model, you will never need to to count pulses.
 
Personally, I think its smart working in seconds becasue that lets you work with integers.

I'm wondering if the fact you are using a servo with an encoder if you don't have an advantage around accuracy. Are you sending the position error back into your script? Your encoder accuracy may exceed your step accuracy. If each division was resolved to an absolute position around the circle, you should know exactly where you are before you start any move so it would be a matter of programming a move to the next position. If you can get the feedback signals working for this model, you will never need to to count pulses.

rodw,

I will eventually I close the loop. Motor inertia will also have to be dealt with. Barring bad electronics unmanaged motor inertia is the #1 cause of lost or gained steps. Because I will be using a small servo motor so it will be necessary to use a 5:1 gear reduction between the the motor and the table. The up side of that is increased torque and the down side is increased inertia. I had planned on using a 3:1 gear reduction which would have rotated the table 2.34375 seconds per motor pulse. By using 5:1 gear reduction it reduced the table resolution down to 1.40625 seconds. To put things in the proper perspective (1 / (3600 / 1.40625) = 0.000390625º. In Mastercam CAD/CAM configured to 8 places after the decimal point that measures ZERO on the circumference of a 6" diameter circle.

Ron
 
Last edited:
The code below is my revision of group member, bmac2's code that he published in Arduino Rotary Table for Dummies. To my knowledge there is nothing wrong with his code. Every co...snip... code in C++ so it was a ground]

Ron
I found 3 “errors???” in BMACs code. From my memory..
1. There had to be an integer number of steps for 1 degree. For people using rotary tables with ratios 36:1 this wasn’t a concern. IIRC that’s what BMAC has. I think the same is true with other common RT ratios. For those of us using belt driven spindles it just didn’t work.

2. It accumulated errors. With each division there could be an error of up to 1 step. With a reasonable number of divisions and steps per revolution it probably doesn’t matter. Who cares if you’re out 20 steps in 18000? It is an error though and was pretty easy to correct.

3. Its limited to 32k steps per revolution. This is more of a limit than an error, but it’s pretty easy to bump into it. I think your arrangement would hit it without even using microstepping.

John
Ps I still dislike minutes and seconds.
 
Your code looks good so far (although maybe a smidge under-commented for my old professors' liking). I know it doesn't make much of a difference unless you're making a lot of consecutive movements, but your rounding errors would eventually compound in the 'getsteps' function:
long getsteps(float dgrs)
{
return round(dgrs * shorty);
}

It could certainly be argued that a missed step here and there was no big deal and also that missed steps would be counteracted by added steps, but if you were (for some reason) to rotate by a known amount, say 1 degree, and repeat for a revolution or two of the table, your error would eventually show up. One way to allow for this would be to accumulate the rounding error in another variable and adjust for the error as it became significant (i.e. add or deduct one step as your rounding figure approached +/- 1 step). Then you'd never be more than a single step (half a step maybe?) from theoretical even if you incremented by half a degree for a thousands of degrees.

Cogsy,

The code below is from my start in MS VS 2019. Imagine my surprise when I discovered that the Arduino IDE didn't speak that language. It was a minor set back that was easily recovered from. If you have a C++ debugger then run the attached code and I think that you will be impressed by the accuracy. Hard to believe that MS VS 2010 doesn't have the round function. Ceil and Floor are the raw components for the round function.

Ron

Code:
/*
Given:
Degree * Minutes * Seconds = 3600 Seconds
Rotary Table gear ratio: 90:1
Motor ppr: 2048
Motor Gearhead ratio: 5:1
Logic:
One revolution @ 5:1 Gearhead final drive = 10240 Motor ppr (2048 * 5)
One revolution of Rotary Table hand crank = 4º * 3600 = 14400 Seconds
Therefore, 1 Motor ppr rotates the Rotary Table 1.40625 Seconds (14400 / 10240)
Formula: ((Degrees * Seconds) / Indexer Resolution) = pulses
Short Formula: Degress * 2560 = pulses
*/
#include <iostream>
#include <iomanip>
double crums = 0;
double steps = 0;
double degrees = 0;
double shorty = 2560; // Degree(Seconds) * Table resolution.
int main()
  for (degrees = .000;degrees < 360;degrees +=3.09375)
  {
    steps=int(degrees * shorty);
    if((steps-int(steps))>=0.5)
    {
      steps=ceil(steps);
    }
    else if((steps-int(steps))<0.5)
    {
      steps = floor(steps);
    }
    std::cout << " Degrees input: " << degrees << " - Degrees output: " << std::setprecision(9) << (steps / shorty) << " - Steps: " << steps;
    std::cout << "\n";
  }

  std::cin.get();
  return 0;
}
 
Last edited:
rodw,

I will eventually I close the loop. Motor inertia will also have to be dealt with. Barring bad electronics unmanaged motor inertia is the #1 cause of lost or gained steps. Because I will be using a small servo motor so it will be necessary to use a 5:1 gear reduction between the the motor and the table. The up side of that is increased torque and the down side is increased inertia. I had planned on using a 3:1 gear reduction which would have rotated the table 2.34375 seconds per motor pulse. By using 5:1 gear reduction it reduced the table resolution down to 1.40625 seconds. To put things in the proper perspective (1 / (3600 / 1.40625) = 0.000390625º. In Mastercam CAD/CAM configured to 8 places after the decimal point that measures ZERO on the circumference of a 6" diameter circle.

Ron

Ron, you have a servo motor where position is determined by encoder feedback back to the controller from the encoder. There is therefore no need to be concerned with lost steps once the servo drive is tuned. The fact that it can act as a step and direction motor is simply for convenience for you the machine integrator. If you tell it to move 100 steps, it will make sure you move 100 steps. If it gets behind, it will catch up. You should review the documentation for your servo drive, it might be able to be controlled in velocity mode or as an analog servo with a 0-10 volt voltage range determining velocity. Then you will then not have any steps to loose!

You have this opportunity to do this in a different (superior) way to everybody else if you get your head around this and your servo drive's capabilities.

Finally, it would be good if you learn about the Arduino timer interrupts before you get much further. If you use these to generate your step pulses, ramping up at the start of a move and ramping down at the end of a move becomes quite trivial as all you need to do is change the interrupt interval. But you do need to catch the situation where the move is too short to ramp up and ramp down to get to you your target velocity . In this case, you must shorten the ramp up so there is room for a shortened ramp down (eg. You never get to your target velocitty.)

There is already a very good example of how to do interrupts and ramping in an Arduino script I wrote here on this forum
 
Ron, you have a servo motor where position is determined by encoder feedback back to the controller from the encoder. There is therefore no need to be concerned with lost steps once the servo drive is tuned. The fact that it can act as a step and direction motor is simply for convenience for you the machine integrator. If you tell it to move 100 steps, it will make sure you move 100 steps. If it gets behind, it will catch up. You should review the documentation for your servo drive, it might be able to be controlled in velocity mode or as an analog servo with a 0-10 volt voltage range determining velocity. Then you will then not have any steps to loose!

You have this opportunity to do this in a different (superior) way to everybody else if you get your head around this and your servo drive's capabilities.

Finally, it would be good if you learn about the Arduino timer interrupts before you get much further. If you use these to generate your step pulses, ramping up at the start of a move and ramping down at the end of a move becomes quite trivial as all you need to do is change the interrupt interval. But you do need to catch the situation where the move is too short to ramp up and ramp down to get to you your target velocity . In this case, you must shorten the ramp up so there is room for a shortened ramp down (eg. You never get to your target velocitty.)

There is already a very good example of how to do interrupts and ramping in an Arduino script I wrote here on this forum

The drive is a positioning drive and therefore not capable of torque/velocity control. If you wouldn't mind sending me a link to your article then I would appreciate it

Ron
 
Ron
I found 3 “errors???” in BMACs code. From my memory..
1. There had to be an integer number of steps for 1 degree. For people using rotary tables with ratios 36:1 this wasn’t a concern. IIRC that’s what BMAC has. I think the same is true with other common RT ratios. For those of us using belt driven spindles it just didn’t work.

2. It accumulated errors. With each division there could be an error of up to 1 step. With a reasonable number of divisions and steps per revolution it probably doesn’t matter. Who cares if you’re out 20 steps in 18000? It is an error though and was pretty easy to correct.

3. Its limited to 32k steps per revolution. This is more of a limit than an error, but it’s pretty easy to bump into it. I think your arrangement would hit it without even using microstepping.

John
Ps I still dislike minutes and seconds.

John,

I don't care to discuss bmac2's program. If he has an error then he is welcome to copy my code just as I copied his code. There are very few programmer that honestly say that they have never copied code. It doesn't smell bad until a person claims another person's code to be their work

Once the program is finish then all input will be decimal values. As for accuracy, it is not possible to improve with givens that I have to work with. However, I'm quite pleased with the accuracy of my program, Underlined with red is the formula in reverse thatcalculates the degrees output by dividing the steps by shorty.

Ron

Results.jpg
 
Last edited:
Cogsy,

The code below is from my start in MS VS 2019. Imagine my surprise when I discovered that the Arduino IDE didn't speak that language. It was a minor set back that was easily recovered from. If you have a C++ debugger then run the attached code and I think that you will be impressed by the accuracy. Hard to believe that MS VS 2010 doesn't have the round function. Ceil and Floor are the raw components for the round function.

Ron

Ron,

I ran the code and noticed a couple of things.

1. You've changed the 'shorty' to a round figure from your original so there's a slight accuracy loss there.
2. If I alter your loop increment value to something different (I used 3.11111 and 3.08) then indicated accuracy is slightly degraded, although I may have missed something about using that particular increment in your code.
3. Your rounding functions are redundant as you explicitly typecast 'steps' to an integer value with 'steps = int(degrees * shorty)'. So then your following calculations of (steps-int(steps)) effectively become int(steps)-int(steps) which will always return zero.

Finally, what I meant with accumulated errors is not on an absolute move such as your code simulates, but on incremental motion. There's usually not going to be a perfect integer number of steps for a move so rounding must take place but if these 'partial' steps are not accounted for then they can add up. For example, a move requiring 256.4 steps gets rounded down to 256 steps and nothing will be affected, but once we add another required movement of 256.4 steps our total movement should be 256.4 + 256.4 = 512.8 => 513 total steps for best accuracy, except we've only moved 2 * 256 for 512 steps. Only one step difference but continued on for a large number of moves and the error eventually becomes noticeable.

Like I said, not likely a big deal in hobby environments with our requirements but in a production machine it could make a difference (plus I lost so many marks on coding assignments for seemingly minor details like these that I find it hard to go past them without comment).
 
Ron,

I ran the code and noticed a couple of things.

1. You've changed the 'shorty' to a round figure from your original so there's a slight accuracy loss there.
2. If I alter your loop increment value to something different (I used 3.11111 and 3.08) then indicated accuracy is slightly degraded, although I may have missed something about using that particular increment in your code.
3. Your rounding functions are redundant as you explicitly typecast 'steps' to an integer value with 'steps = int(degrees * shorty)'. So then your following calculations of (steps-int(steps)) effectively become int(steps)-int(steps) which will always return zero.

Finally, what I meant with accumulated errors is not on an absolute move such as your code simulates, but on incremental motion. There's usually not going to be a perfect integer number of steps for a move so rounding must take place but if these 'partial' steps are not accounted for then they can add up. For example, a move requiring 256.4 steps gets rounded down to 256 steps and nothing will be affected, but once we add another required movement of 256.4 steps our total movement should be 256.4 + 256.4 = 512.8 => 513 total steps for best accuracy, except we've only moved 2 * 256 for 512 steps. Only one step difference but continued on for a large number of moves and the error eventually becomes noticeable.

Like I said, not likely a big deal in hobby environments with our requirements but in a production machine it could make a difference (plus I lost so many marks on coding assignments for seemingly minor details like these that I find it hard to go past them without comment).

Cogsy,

The number of degrees in the debug code have no relevance. The degree of accuracy is limited by the givens. As for shorty being an integer value:

360º * 60 *60 = 1296000"
Servo motor ppr: 2048
Servo motor gearhead ratio 5:1
Servo motor ppr at final drive: 10240
Rotary table gear ratio: 90:1
Servo motor ppr per rotary table revolution: 10240 * 90 = 921600
1296000 / 921600 = 1.40625
1 º * 60 * 60 = 3600"
3600" / 1.40625 = 2560 (no conversion)

However, thank you for catching (steps = int(steps)) prior to rounding. That was from a prior debugging session and rather than making rounding redundant it made rounding impossible. The error in the results of the code below is spot on up to the 3rd place behind the decimal point. Five thousandths (.005) of a degree measures .00026180" on the circumference of a 6" diameter circle. Rounding up from the 4th place creates a .001º degree error and that is 0.00005236" linear error measured on the circumference of a 6" diameter circle. I could live with 0" to 0.00005236" linear error if I had any way of measuring it. ;)

BTW, MS VS 2010 didn't have round and I didn't make the switch when I upgraded to MS VS 2019

Ron

Code:
/*
Given:
Degree * Minutes * Seconds = 3600 Seconds
Rotary Table gear ratio: 90:1
Motor ppr: 2048
Motor Gearhead ratio: 5:1
Logic:
One revolution @ 5:1 Gearhead final drive = 10240 Motor ppr (2048 * 5)
One revolution of Rotary Table hand crank = 4º * 3600 = 14400 Seconds
Therefore, 1 Motor ppr rotates the Rotary Table 1.40625 Seconds (14400 / 10240)
Formula: ((Degrees * Seconds) / Indexer Resolution) = pulses
Short Formula: Degress * 2560 = pulses
*/
#include <iostream>
#include <iomanip>
long steps = 0;
float degrees = 0;
float shorty = 2560; // Degree(Seconds) * Table resolution.
int main()
{
    for (degrees = 0.0;degrees < 360;degrees += 12.3456789)
    {
        steps = round(degrees * shorty);
        std::cout << " Degrees input: " << degrees << " - Degrees output: " << std::setprecision(8) << (steps / shorty);
        std::cout << "\n";
    }
    std::cin.get();
    return 0;
}
 
Last edited:
John,

I don't care to discuss bmac2's program. If he has an error then he is welcome to copy my code just as I copied his code. There are very few programmer that honestly say that they have never copied code. It doesn't smell bad until a person claims another person's code to be their work

Once the program is finish then all input will be decimal values. As for accuracy, it is not possible to improve with givens that I have to work with. However, I'm quite pleased with the accuracy of my program, Underlined with red is the formula in reverse thatcalculates the degrees output by dividing the steps by shorty.

Ron

View attachment 113122

Ron
My posting was to start a discussion on bmacs code but rather a heads up if you’re using logic similar to bmac’s there are a few things to watch for. I’m actually using a modified version of his that works quite well.

cheers
John
 
Not sure how I messed up the shorty math - initially I had a figure with a few decimal places (I'm guessing I fat-fingered it).

Absolutely your error is small and there's no need to address it in this application, but if you wanted to you could stop it compounding. I most likely would, but purely pedantically because that was what was drummed into us in coding classes (code re-usability is KING they tell us).

It's interesting that you use visual studio and your code compiles fine. I run C++ in codeblocks and have to #include <cmath> to get your code to compile or it doesn't recognise the round(). I guess the .exe would be fine from either application but wasn't aware of different dependencies by application like that. Is this project destined to be Arduino powered? If it is, I'd consider making the switch to the Arduino environment if I was you - it's not the best IDE but it would save on reconfiguring code down the line as Arduino is not precisely C++.
 
Wellll ... since you mentioned being pedantic ... here goes:
BTW, MS VS 2010 didn't have round and I didn't make the switch when I upgraded to MS VS 2019

The distinction between the language and the libraries has gotten fuzzy indeed, but still can be helpful. Strictly speaking, C++ itself does not "have" the round function, not as a built-in part of the language. Rather, the round() function is available in one of the standard libraries - specifically in cmath, as Cogsy has illustrated. Likely MS VS 2010 "had" the round function in one of its libraries - it's not new by any means - but if you didn't include the library header then you couldn't use it. Likely MS VS 2019 is including that library automatically, behind the scenes. Likely, the list of which libraries to include automatically is configurable in the IDE.

Strictly speaking, Arduino is precisely C++, and in fact it is compiled with the standard gnu C++ compiler. Most of what people think of as "Arduino" is simply the libraries that the Arduino IDE automatically includes. So, for example, pinMode() and digitalWrite() and so on are simply library functions, not actually part of the C++ language.

To be sure, depending on the processor chip involved, some of the larger data types may not be available (e.g., double float or long long int). But even that is "standard" C++, since one of the more infamous "features" of C/C++ has always been that the definition of data types is dependent on the hardware. Thus, an int on one hardware platform may be 32 bits, while on another it may be 16 bits. This is why newer specifications for C/C++ include a library header called inttypes.h which defines specifically sized data types such as uint8 or int64 or so on - and yes, you can use these in the Arduino IDE, so long as you include the header; just note that for the 8-bit AVR processors, 64 bit integers will not be defined.

One other thing that the Arduino IDE automatically includes: Most people think that setup() and loop() are the two required Arduino functions. In fact, in keeping with standard C/C++, the only required function is main(). Behind the scenes, the IDE includes a default main() function that does three things: it sets up some of the hardware (e.g., timers); it calls setup(); and then it enters an endless loop calling loop(). Thus, in the Arduino IDE, you can write your program only using setup() and loop() in your Arduino code, but in reality, even here, it is standard C++ at work behind the scenes.

Actually, you can supply your own main() function; if you do, the IDE will not include the default. If you supply your own main() function, you will need to do any hardware initialization and other setup that you require, and you will need to create whatever appropriate loop(s) you need. Most people don't need to do this, but if you are needing to set up the hardware in a very specific way, rather than according to the defaults, or if your logic is more complex than a single loop, then it makes sense to bypass the default.

Okay, pedantic mode off ... :)
 
Last edited:
Wellll ... since you mentioned being pedantic ... here goes:


The distinction between the language and the libraries has gotten fuzzy indeed, but still can be helpful. Strictly speaking, C++ itself does not "have" the round function, not as a built-in part of the language. Rather, the round() function is available in one of the standard libraries - specifically in cmath, as Cogsy has illustrated. Likely MS VS 2010 "had" the round function in one of its libraries - it's not new by any means - but if you didn't include the library header then you couldn't use it. Likely MS VS 2019 is including that library automatically, behind the scenes. Likely, the list of which libraries to include automatically is configurable in the IDE.

Strictly speaking, Arduino is precisely C++, and in fact it is compiled with the standard gnu C++ compiler. Most of what people think of as "Arduino" is simply the libraries that the Arduino IDE automatically includes. So, for example, pinMode() and digitalWrite() and so on are simply library functions, not actually part of the C++ language.

To be sure, depending on the processor chip involved, some of the larger data types may not be available (e.g., double float or long long int). But even that is "standard" C++, since one of the more infamous "features" of C/C++ has always been that the definition of data types is dependent on the hardware. Thus, an int on one hardware platform may be 32 bits, while on another it may be 16 bits. This is why newer specifications for C/C++ include a library header called inttypes.h which defines specifically sized data types such as uint8 or int64 or so on - and yes, you can use these in the Arduino IDE, so long as you include the header; just note that for the 8-bit AVR processors, 64 bit integers will not be defined.

One other thing that the Arduino IDE automatically includes: Most people think that setup() and loop() are the two required Arduino functions. In fact, in keeping with standard C/C++, the only required function is main(). Behind the scenes, the IDE includes a default main() function that does three things: it sets up some of the hardware (e.g., timers); it calls setup(); and then it enters an endless loop calling loop(). Thus, in the Arduino IDE, you can write your program only using setup() and loop() in your Arduino code, but in reality, even here, it is standard C++ at work behind the scenes.

Actually, you can supply your own main() function; if you do, the IDE will not include the default. If you supply your own main() function, you will need to do any hardware initialization and other setup that you require, and you will need to create whatever appropriate loop(s) you need. Most people don't need to do this, but if you are needing to set up the hardware in a very specific way, rather than according to the defaults, or if your logic is more complex than a single loop, then it makes sense to bypass the default.

Okay, pedantic mode off ... :)

MS VS 2019 has the round function. In the code below I'm comparing the Degrees input with the Degrees output, dividing the difference by 360, and multiplying the circumference of a 6" diameter circle by that decimal fraction. I think that it would acceptable in Rocket Science. See attached picture.

Code:
/*
Given:

Degree * Minutes * Seconds = 3600 Seconds
Rotary Table gear ratio: 90:1
Motor ppr: 2048
Motor Gearhead ratio: 5:1

Logic:

One revolution @ 5:1 Gearhead final drive = 10240 Motor ppr (2048 * 5)
One revolution of Rotary Table hand crank = 4º * 3600 = 14400 Seconds
Therefore, 1 Motor ppr rotates the Rotary Table 1.40625 Seconds (14400 / 10240)
Formula: ((Degrees * Seconds) / Indexer Resolution) = pulses
Short Formula: Degress * 2560 = pulses
*/

#include <iostream>
#include <iomanip>

float crums = 0;
float steps = 0;
float degrees = 0;
float shorty = 2560; // (Degree(Seconds) / Table resolution).

int main()
{
    for (degrees = 0.0;degrees < 360;degrees += 23.456789) // index increment chosen to force fractional output
    {
        steps = ((degrees * shorty) + crums); // crums can equal a Cookie
        crums = (steps - round(steps));
        steps = round(steps);
        std::cout << std::fixed;
        std::cout << " Degrees input: " << degrees << " - Degrees output: " << std::setprecision(5) << (int(steps) / shorty) <<
        " - LinearError on Circumference of 6 inch Diameter Circle: " << std::setprecision(5) <<
        ((6 * 3.14159265) * ((degrees - (steps / shorty)) / 360));
        std::cout << "\n";
    }
    std::cin.get();
    return 0;
}

81167454_1091412161207938_4369303417433096192_o.jpg
 
Last edited:
Again, this level of error handling is not required for this application but you've almost got it nailed. Your sample code is based on absolute, rather than iterative movements though, so just for interest I reworked it to be iterative. I didn't use the crums value as explained after the code.

Code:
/*
Given:

Degree * Minutes * Seconds = 3600 Seconds
Rotary Table gear ratio: 90:1
Motor ppr: 2048
Motor Gearhead ratio: 5:1

Logic:

One revolution @ 5:1 Gearhead final drive = 10240 Motor ppr (2048 * 5)
One revolution of Rotary Table hand crank = 4º * 3600 = 14400 Seconds
Therefore, 1 Motor ppr rotates the Rotary Table 1.40625 Seconds (14400 / 10240)
Formula: ((Degrees * Seconds) / Indexer Resolution) = pulses
Short Formula: Degress * 2560 = pulses
*/

#include <iostream>
#include <iomanip>
#include <cmath>

float crums = 0;
float steps = 0; //steps per movement
float absSteps = 0; //steps needed for absolute movement
float actualSteps = 0; //cumulative steps taken
float degrees = 0;
float moveDegrees = 23.456789;
float totalDegrees = 0;
float shorty = 2560; // (Degree(Seconds) / Table resolution).

int main()
{
    for (degrees = 0.0;degrees < 360;degrees += moveDegrees) // index increment chosen to force fractional output
    {
        if (degrees > 1) //initial null movement handler
        {
            steps = ((moveDegrees * shorty));
            //crums = (steps - round(steps)); error handling disabled
            absSteps = round(degrees*shorty);
            steps = round(steps);
            actualSteps += steps;
            std::cout << std::fixed;
            std::cout << " Degrees input: " << degrees << " - Degrees output: " << std::setprecision(5) << (int(actualSteps) / shorty) <<
            " - LinearError on Circumference of 6 inch Diameter Circle: " << std::setprecision(5) <<
            ((6 * 3.14159265) * ((degrees - (actualSteps / shorty)) / 360));
            std::cout << "\n" << "Step difference between absolute value and incremental: " << (absSteps - actualSteps) << "\n";
        }
    }
    std::cin.get();
    return 0;
}

Output example :

Cumulative error.JPG


You have a slight logic error where currently, as each move you make in your code is calculated as an absolute, apart from your first move when crums = 0, crums is actually hurting your accuracy (you work out the absolute closest number of steps to achieve your desired movement, then add the error from the previous 'exact' calculation and add that, which may make your rounded actual steps one higher, or lower, than the closest number). To fix this, I incremented your crums value to accumulate the error at each iteration and used it to adjust the steps value as it varied away from theoretical.

Code:
/*
Given:

Degree * Minutes * Seconds = 3600 Seconds
Rotary Table gear ratio: 90:1
Motor ppr: 2048
Motor Gearhead ratio: 5:1

Logic:

One revolution @ 5:1 Gearhead final drive = 10240 Motor ppr (2048 * 5)
One revolution of Rotary Table hand crank = 4º * 3600 = 14400 Seconds
Therefore, 1 Motor ppr rotates the Rotary Table 1.40625 Seconds (14400 / 10240)
Formula: ((Degrees * Seconds) / Indexer Resolution) = pulses
Short Formula: Degress * 2560 = pulses
*/

#include <iostream>
#include <iomanip>
#include <cmath>

float crums = 0;
float steps = 0; //steps per movement
float absSteps = 0; //steps needed for absolute movement
float actualSteps = 0; //cumulative steps taken
float degrees = 0;
float moveDegrees = 23.456789;
float totalDegrees = 0;
float shorty = 2560; // (Degree(Seconds) / Table resolution).

int main()
{
    for (degrees = 0.0;degrees < 360;degrees += moveDegrees) // index increment chosen to force fractional output
    {
        if (degrees > 1) //initial null movement handler
        {
            steps = ((moveDegrees * shorty));
            crums += (steps - round(steps)); //accrue error
            if(crums <= -0.5) //adjust for total cumulative error as necessary
            {
                steps -= 1;
                crums += 1;
            }
            else if(crums >= 0.5)
            {
                steps += 1;
                crums -= 1;
            }
            absSteps = round(degrees*shorty);
            steps = round(steps);
            actualSteps += steps;
            std::cout << std::fixed;
            std::cout << " Degrees input: " << degrees << " - Degrees output: " << std::setprecision(5) << (int(actualSteps) / shorty) <<
            " - LinearError on Circumference of 6 inch Diameter Circle: " << std::setprecision(5) <<
            ((6 * 3.14159265) * ((degrees - (actualSteps / shorty)) / 360));
            std::cout << "\n" << "Step difference between absolute value and incremental: " << (absSteps - actualSteps) << "  Crums value: " << crums << "\n";
        }
    }
    std::cin.get();
    return 0;
}

Output example :

Iterative error.JPG


That's the sort of approach I would take but being that you're working in seconds it really won't make much difference if you don't handle the error at all.

P.S. I know the "Rocket Science" was tongue-in-cheek but I had to include, one second of arc across the radius of the earth is equivalent to about 30 metres linearly. One second of arc would then make a massive difference heading off to a distant planet.

Edit to add: The unused variable 'totalDegrees' I declared intending to use it in error checking but using actualSteps/shorty was simpler. It bugs me I didn't remove it though (and I hope I didn't make too many more silly errors).
 
Last edited:
Wellll ... since you mentioned being pedantic ... here goes:


The distinction between the language and the libraries has gotten fuzzy indeed, but still can be helpful. Strictly speaking, C++ itself does not "have" the round function, not as a built-in part of the language. Rather, the round() function is available in one of the standard libraries - specifically in cmath, as Cogsy has illustrated. Likely MS VS 2010 "had" the round function in one of its libraries - it's not new by any means - but if you didn't include the library header then you couldn't use it. Likely MS VS 2019 is including that library automatically, behind the scenes. Likely, the list of which libraries to include automatically is configurable in the IDE.

Strictly speaking, Arduino is precisely C++, and in fact it is compiled with the standard gnu C++ compiler. Most of what people think of as "Arduino" is simply the libraries that the Arduino IDE automatically includes. So, for example, pinMode() and digitalWrite() and so on are simply library functions, not actually part of the C++ language.

To be sure, depending on the processor chip involved, some of the larger data types may not be available (e.g., double float or long long int). But even that is "standard" C++, since one of the more infamous "features" of C/C++ has always been that the definition of data types is dependent on the hardware. Thus, an int on one hardware platform may be 32 bits, while on another it may be 16 bits. This is why newer specifications for C/C++ include a library header called inttypes.h which defines specifically sized data types such as uint8 or int64 or so on - and yes, you can use these in the Arduino IDE, so long as you include the header; just note that for the 8-bit AVR processors, 64 bit integers will not be defined.

One other thing that the Arduino IDE automatically includes: Most people think that setup() and loop() are the two required Arduino functions. In fact, in keeping with standard C/C++, the only required function is main(). Behind the scenes, the IDE includes a default main() function that does three things: it sets up some of the hardware (e.g., timers); it calls setup(); and then it enters an endless loop calling loop(). Thus, in the Arduino IDE, you can write your program only using setup() and loop() in your Arduino code, but in reality, even here, it is standard C++ at work behind the scenes.

Actually, you can supply your own main() function; if you do, the IDE will not include the default. If you supply your own main() function, you will need to do any hardware initialization and other setup that you require, and you will need to create whatever appropriate loop(s) you need. Most people don't need to do this, but if you are needing to set up the hardware in a very specific way, rather than according to the defaults, or if your logic is more complex than a single loop, then it makes sense to bypass the default.

Okay, pedantic mode off ... :)

I tried moving MS VS 2010 C++ library files into the Arduino libraries folder taking care not to trample on the native library files and the Arduino IDE would have nothing to do with that. I wrote a recursive folder and file search program and with a little tweaking I was able to compile a list of the #included, #included, #included...... libraries. I had just learned enough MS VS 2010 C++ to write some simple prototypes and I didn't like what turned out to be a minor difference between that and the Arduino language. After failing at building my own library I settled down and adapted to the difference.
 

Latest posts

Back
Top