Encoder to stepper motor ratio and remainders

I am making a gear hobber out of a vertical mill.

I am installing an encoder on the spindle of a vertical mill to rotate a stepper on a dividing head with a 40:1 ratio. The stepper will have to spin at a ratio to the spindle of the mill.

1 tooth = 1 rotation of the spindle
40 full rotations of the stepper = 1 full rotation of the part
600 ppr on the encoder
200 steps per rotation on the stepper

1:3 encoder to stepper

I am fairly new to arduino but have pieced together some code that i think should work and seems to. The problem is that I feel that I am losing some crucial numbers in the remainders. I will post the code below. I need the Steps to be the last 0 + the remainder of the last step so that it can keep true.

Thanks to anyone that can help.

// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 9, d5 = 8, d6 = 7, d7 = 6;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

 

#define encoder_a 2    //encoder a = arduino pin 2 
#define encoder_b 3    //encoder b = arduino pin 3 
#define motor_step 4    //motor step = arduino pin 4 
#define motor_direction 5    //motor direction = arduino pin 5

// constants won't change. They're used here to set pin numbers:
const int buttonup = 52;     // the number of the pushbutton pin
const int buttondown = 53;     // the number of the pushbutton pin
const int buttonadvance = 51;     // the number of the pushbutton pin


// variables will change:
int buttonState = 0;   // variable for reading the pushbutton status
int buttonState2 = 0;
int numberofteeth = 80;
int buttonState3 = 0;
int rpm = 0;
int ratio = 0;

volatile long motor_position, encoder, encoderpos, remaining; 
float steps = 0;

void setup () 
{ 
  Serial.begin(9600);
   // initialize the pushbutton pin as an input:
  pinMode(buttonup, INPUT);
  pinMode(buttondown, INPUT);
  pinMode (buttonadvance, INPUT);
    //set up the various outputs 
    pinMode(motor_step, OUTPUT); 
    pinMode(motor_direction, OUTPUT); 
    // then the encoder inputs 
    pinMode(encoder_a, INPUT); 
    pinMode(encoder_b, INPUT); 
    digitalWrite(encoder_a, HIGH); 
    digitalWrite(encoder_b, HIGH);
    // encoder pin on interrupt 0 (pin 2) 
    attachInterrupt(0, encoderPinChangeA, CHANGE); 
    // encoder pin on interrupt 1 (pin 3) 
    attachInterrupt(1, encoderPinChangeB, CHANGE); 
    encoder = 0; 
}

void encoderPinChangeA() 
{
    if (digitalRead(encoder_a) == digitalRead(encoder_b)) 
    {
        encoder-= 1;
        encoderpos-= 1;
    } 
    else
    {
        encoder+= 1;
        encoderpos+= 1;
    } 
} 

void encoderPinChangeB() 
{
    if (digitalRead(encoder_a) != digitalRead(encoder_b)) 
    { 
        encoder-= 1;
        encoderpos-= 1;
    } 
    else 
    { 
        encoder+= 1; 
        encoderpos+= 1;
    } 
}


void loop() 
{
  steps = encoderpos / 3 * 40 / numberofteeth + remaining;

    if (steps > 1) 
    { 
     
        digitalWrite(motor_direction, HIGH); 
        digitalWrite(motor_step, HIGH); 
        digitalWrite(motor_step, LOW);
        motor_position++; 
        encoder = 0;
        encoderpos = 0;
    }
    else if (steps < -1) 
    {
        digitalWrite(motor_direction, LOW); 
        digitalWrite(motor_step, HIGH); 
        digitalWrite(motor_step, LOW); 
        motor_position--;
        encoder = 0; 
        encoderpos = 0;
    }
}

Do I read your problem is you want the stepper to move a fraction of a step?

Paul

I do not want to move the stepper a fraction of a step. I just want the decimal steps to be incorporated into the next step. Such as that at 1.02 the stepper would move one and lose the .02. i want the stepper to step the 1 and then add the .02 to the equation for the next step.

step = 1.02

stepper steps 1

step = .02

MikeGrevers:
I am making a gear hobber out of a vertical mill.

I am installing an encoder on the spindle of a vertical mill to rotate a stepper on a dividing head with a 40:1 ratio. The stepper will have to spin at a ratio to the spindle of the mill.

I have a small lathe so I am not completely ignorant of what you want to do. But I can't envisage your setup. Can you post a diagram of what you are trying to build.

I may even be tempted to copy it :slight_smile:

...R

    // encoder pin on interrupt 0 (pin 2)
    attachInterrupt(0, encoderPinChangeA, CHANGE);
    // encoder pin on interrupt 1 (pin 3)
    attachInterrupt(1, encoderPinChangeB, CHANGE);

So 1 step = 12 encoder pulses? Why not:

Pseudo Code

encoderSteps = encoderPulses % 12; // ?

Is the 40:1 ratio fixed? If you want to work in degrees, the ratio should be 1:36, that would give 7200 steps per rev or 0.05 degrees per step.
Or buy a motor with 180 or 360 steps per rev, a 180 motor would give 7200 steps per rev of the divider.

Any time you use floats, you have to worry about roundoff error. I suggest you figure out how to do it with integer arithmetic. Scale it if you have to, but do all the calculations with integers.

It looks like you're sort of dealing with the remainder, but you use the variable remaining, which starts out at 0 and never gets changed. That isn't useful.

Also, any time you have volatile variables larger than a byte that get changed in an interrupt function and used outside the interrupt function, you have to use critical sections. Otherwise you'll get anomalies with weird data.

Sorry for the delay in getting back on here. Thank you for the help so far. Below is a sketch and some pictures of what I am dealing with. I need the number of teeth to define the number of steps per revolution of the cutter head. I think my drawing and pictures will help to explain that. I did originally have the remaining variable get adjusted at the end of each step routine but when i upload this new code below it throws my stepper driver in fault and looking through the scope it is just continually stepping.





//Arduino code for controlling stepper motor from encoder
//video at http://www.youtube.com/watch?v=2dh6TIkN6jE
//reconstructed code from 5+ years ago
//untested in current form
// include the library code:
#include <LiquidCrystal.h>

// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 9, d5 = 8, d6 = 7, d7 = 6;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

 

#define encoder_a 2    //encoder a = arduino pin 2 
#define encoder_b 3    //encoder b = arduino pin 3 
#define motor_step 4    //motor step = arduino pin 4 
#define motor_direction 5    //motor direction = arduino pin 5

// constants won't change. They're used here to set pin numbers:
const int buttonup = 52;     // the number of the pushbutton pin
const int buttondown = 53;     // the number of the pushbutton pin
const int buttonadvance = 51;     // the number of the pushbutton pin


// variables will change:
int buttonState = 0;   // variable for reading the pushbutton status
int buttonState2 = 0;
int numberofteeth = 80;
int buttonState3 = 0;
int rpm = 0;
int ratio = 0;

volatile long motor_position, encoder, encoderpos, remaining; 
float steps = 0;

void setup () 
{ 
  Serial.begin(9600);
   // initialize the pushbutton pin as an input:
  pinMode(buttonup, INPUT);
  pinMode(buttondown, INPUT);
  pinMode (buttonadvance, INPUT);
    //set up the various outputs 
    pinMode(motor_step, OUTPUT); 
    pinMode(motor_direction, OUTPUT); 
    // then the encoder inputs 
    pinMode(encoder_a, INPUT); 
    pinMode(encoder_b, INPUT); 
    digitalWrite(encoder_a, HIGH); 
    digitalWrite(encoder_b, HIGH);
    // encoder pin on interrupt 0 (pin 2) 
    attachInterrupt(0, encoderPinChangeA, CHANGE); 
    // encoder pin on interrupt 1 (pin 3) 
    attachInterrupt(1, encoderPinChangeB, CHANGE); 
    encoder = 0; 
}

void encoderPinChangeA() 
{
    if (digitalRead(encoder_a) == digitalRead(encoder_b)) 
    {
        encoder-= 1;
        encoderpos-= 1;
    } 
    else
    {
        encoder+= 1;
        encoderpos+= 1;
    } 
} 

void encoderPinChangeB() 
{
    if (digitalRead(encoder_a) != digitalRead(encoder_b)) 
    { 
        encoder-= 1;
        encoderpos-= 1;
    } 
    else 
    { 
        encoder+= 1; 
        encoderpos+= 1;
    } 
}


void loop() 
{
  steps = steps + encoderpos / 3 * 40 / numberofteeth + remaining;

    if (steps > 1) 
    { 
        digitalWrite(motor_direction, HIGH); 
        digitalWrite(motor_step, HIGH); 
        digitalWrite(motor_step, LOW);
        motor_position++; 
        encoder = 0;
        encoderpos = 0;
        remaining = steps - 1;
    }
    else if (steps < -1) 
    {
        digitalWrite(motor_direction, LOW); 
        digitalWrite(motor_step, HIGH); 
        digitalWrite(motor_step, LOW); 
        motor_position--;
        encoder = 0; 
        encoderpos = 0;
        remaining = steps + 1;
    }
}

For some reason my images didn't post here they are.

Sorry, I misread your OP. So, how far should the divider rotate for 1 rev (2400 pulses) of the spindle encoder?

Images from Reply #7. See this Image Guide

...R

It would be real nice if I did not have to turn my laptop on its side to read your diagram.

If I understand the maths in your Original Post you are cutting a 40 tooth gear which will require 40 rotations of the mill or 24000 encoder pulses. Simultaneously there will need to be one revolution of the work piece requiring 40 stepper revolutions at 200 steps per revolution or 8000 stepper steps.

Does that mean that you want the stepper to take one step for every three pulses from the encoder?

You have not said how fast the cutter will be rotating and, consequently, how fast the stepper must move.

Would it be desirable to use microstepping so that the motor moves in smaller increments?

...R

Thanks for the help.

The cutter will run between 25 rpm and 50 rpm depending on the material being cut.

The number of teeth is a variable and right now I have it set to 40 but it could be as low as 12 and as high has 150. Some numbers in this range will result in a fraction of a step.

The encoder makes 600 pulses per rotation / 3 to make it the same as the stepper x 40 for the gear ratio in the dividing head.

Therefor each step would be (pulse / 3 x 40 / number of teeth)

Lets say the gear I have to cut has 17 teeth.

The stepper moves 1 pulse

1 / 3 x 40 / 17 = .7843

The stepper moves a second pulse

step = 1.5686

At this point step is more than 1 so my loop runs.

However I am trying to make sure that I do not lose the .5686 at the end of the step.

I think my program is doing it but now if I spin the encoder in the reverse direction it will occasionally go into an infinite loop of steps. The only thing I can think of is that the Arduino is having a tough time performing the math as quickly as I spin the encoder. Is there a way to limit the decimal points that the Arduino will do the math to (lets say 5 decimal points)?

MikeGrevers:
However I am trying to make sure that I do not lose the .5686 at the end of the step.

One way is to keep a running total of the accurate (i.e. decimal) number of steps to be made and then actually make a step when the integer part increments.

Using your numbers
0.7843 - no step
1.5683 - step
2.3526 - step
3.1369 - step
3.9212 - no step
etc

...R

It would be both faster and more accurate if you use integer Math for this:

Therefor each step would be (pulse / 3 x 40 / number of teeth)

Lets say the gear I have to cut has 17 teeth.

The stepper moves 1 pulse

1 / 3 x 40 / 17 = .7843

The stepper moves a second pulse

step = 1.5686

At this point step is more than 1 so my loop runs.

That comes out to 40 / 51. If you precalculate both the numerator and denominator of this, and scale your calculations up by the denominator, it looks like this:

Declare step as an int. Or even a byte. Every time the stepper moves a pulse, add 40 to step. Then check to see if it is >= 51. If so, run the loop and subtract 51 from step.

The stepper moves 1 pulse
step = 40. Don't run the loop.
The stepper moves a second pulse
step = 80. This is >= 51, so run the loop and subtract 51. step = now 29.
The stepper moves a third pulse
step = 69. Run the loop, and subtract 51. step = 18.

Jimmus:
It would be both faster and more accurate if you use integer Math for this:

I agree completely, but it can make things complicated for a beginner.

...R

I am going to try this next. I will let you know how it goes. Thanks

I changed a few things around below. Seems to work but i won't know until i get it on the mill.

I went to a 360 pulse per rotation encoder but comes to be 720 interrupts per rotation.

200 steps per rotation on stepper gives me 3.6 encoder steps per stepper step 1:1
1/3.6*40= 1.1111111

I make this divided by the teeth a fraction and am using integers.

I know my fraction is improper but it seems to work at this time. Please let me know if you have any additional feedback.

I will try and post a video or more pictures when i get her mounted on the mill.

Thanks for all the help. :slight_smile:

//Arduino code for controlling stepper motor from encoder
//video at http://www.youtube.com/watch?v=2dh6TIkN6jE
//reconstructed code from 5+ years ago
//untested in current form
// include the library code:
#include <LiquidCrystal.h>

// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = 12, en = 11, d4 = 9, d5 = 8, d6 = 7, d7 = 6;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

 

#define encoder_a 2    //encoder a = arduino pin 2 
#define encoder_b 3    //encoder b = arduino pin 3 
#define motor_step 4    //motor step = arduino pin 4 
#define motor_direction 5    //motor direction = arduino pin 5

// variables will change:

volatile long motor_position;

int steps = 0;

// 1.111111 / number of teeth = decimal to be converted
// this number wil be converted to an integer
// for 4 decimal place precision convert to 1/1000
// for example if the number of teeth is 14 then the number is .7936
// 7936/10000
// then each step of the encoder = 7936 and when step becomes more than 10000 the stepper steps and subtracts 10000 leaving a remainder


void setup () 
{ 
  Serial.begin(2000000);
    pinMode(motor_step, OUTPUT); 
    pinMode(motor_direction, OUTPUT); 
    // then the encoder inputs 
    pinMode(encoder_a, INPUT); 
    pinMode(encoder_b, INPUT); 
    digitalWrite(encoder_a, HIGH); 
    digitalWrite(encoder_b, HIGH);
    // encoder pin on interrupt 0 (pin 2) 
    attachInterrupt(0, encoderPinChangeA, CHANGE); 
    // encoder pin on interrupt 1 (pin 3) 

  
}

void encoderPinChangeA() 
{
    if (digitalRead(encoder_a) == digitalRead(encoder_b)) 
    {
      steps-= 7936;
    } 
    else
    {
        steps+= 7936;
    } 
} 

void loop() 
{

    if (steps > 10000) 
    { 
     
        digitalWrite(motor_direction, HIGH); 
        digitalWrite(motor_step, HIGH); 
        digitalWrite(motor_step, LOW);
        motor_position++; 
       steps= steps-10000;
           Serial.print(steps);
    }
    else if (steps < -10000) 
    {
        digitalWrite(motor_direction, LOW); 
        digitalWrite(motor_step, HIGH); 
        digitalWrite(motor_step, LOW); 
        motor_position--;
        steps= steps+10000;
           Serial.print(steps);
    }
}

Nice job. I think that should do better.

Four things. First, your final calculations are correct, but the one number in the post and in the comment is an order of magnitude off. It should be 11.11111 instead of 1.11111.

Second, 4 places of accuracy is very good, but you could be exact if you used actual fractions instead of converting to decimal and back. 1 / 3.6 * 40 is 400 / 36 which is 100 / 9. If you multiply the 9 by the number of teeth you can get an exact fraction. For 14 teeth 9 * 14 = 126, so the fraction is 100 / 126.

Third, you should make steps volatile, and any time you use it it should be in a critical section.

Fourth, what happens if steps is exactly the same as the compare value. I think you want to make the step in this case. Change the < to <= and the > to >=.

void encoderPinChangeA() 
{
    if (digitalRead(encoder_a) == digitalRead(encoder_b)) 
    {
      steps-= 100;
    } 
    else
    {
        steps+= 100;
    } 
} 

void loop() 
{
    cli();                            // Critical section for using steps.
    if (steps >= 126) 
    { 
       steps= steps-126;    // Move this up here to make the critical section as short as possible.
       sei();
        digitalWrite(motor_direction, HIGH); 
        digitalWrite(motor_step, HIGH); 
        digitalWrite(motor_step, LOW);
        motor_position++; 
           Serial.print(steps);
    }
    else if (steps <= -126) 
    {
        steps= steps+126;
        sei();
        digitalWrite(motor_direction, LOW); 
        digitalWrite(motor_step, HIGH); 
        digitalWrite(motor_step, LOW); 
        motor_position--;
           Serial.print(steps);
    }
    else
      sei();
}

Also, depending on how fast you're spinning, those Serial.print() statements may take too much time.

I will give that a try. Makes perfect sense. Thanks.

Hi,

I will make the same gear hob attachment as you.
I see you have the code. Does is work? I've not much experience with arduino so it's a little bit difficult for me.
Kind regards

Jeff