Rotary Encoder to Stepper Motor control

Hello everyone,

First time poster here. Relatively new to arduino and components, I've been learning and browsing forums and testing my equipment and boards for a couple months now.

Current project and code is to control a 28byj-48 Stepper Motor with a 360 pulse incremental rotary encoder. After a lot of YouTube videos and code searching and code manipulation I have come up with the code attached below, mainly originating from zhut.com.

The stepper motor is 2048 steps per revolution. In my code I have been forced to program 12 steps per pulse of the encoder which is not (2048/360=5.68) and rather (2048/(360/2)=11.38) rounded up. This is the number that gets my stepper motor in sync with the rotary encoder in physical position.

You'll see where I have integer "s" is equal to the encoder position which is what makes the stepper motor turn that many steps.

My two questions are these;

  1. Why does it seem to me that my 360 pulse rotary encoder (and is labeled as such) is actually a 180 pulse? (360/2)

  2. Why is it when I pull up the serial monitor, in clockwise(+) rotation I am consistency getting counters of 12 (int s) but in counterclockwise (-) I get -12 but will sometimes get a -256?

Code-

// http://thezhut.com/?page_id=22

#include <Stepper.h>
const int encoderPinA = 2;   // right
const int encoderPinB = 3;   // left
int encoderPos = 0;    // counter
unsigned int lastReportedPos = 1;   // change
static boolean rotating = false;    // debounce
boolean A_set = false;
boolean B_set = false;
const int stepsPerRevolution = 32;
int s=12;
Stepper myStepper(stepsPerRevolution, 8, 10, 9, 11);  //h-bridge pins

void setup() {

myStepper.setSpeed(600);
pinMode(encoderPinA, INPUT_PULLUP); //enabling pullups
pinMode(encoderPinB, INPUT_PULLUP);
attachInterrupt(0, doEncoderA, CHANGE); //pin 2
attachInterrupt(1, doEncoderB, CHANGE); //pin 3
Serial.begin(9600);

}

void loop() {
rotating = true;  // reset the debouncer

if (lastReportedPos != encoderPos) {
Serial.println(encoderPos);
lastReportedPos = encoderPos;                                                       
myStepper.step(encoderPos);         
encoderPos = 0;
}
}

void doEncoderA() {
// debounce
if ( rotating ) delay (4);  // wait a little until the bouncing is done
// Test transition
if ( digitalRead(encoderPinA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set )
encoderPos = s;  //change the 1 to steps to take when encoder turned
rotating = false;  // no more debouncing until loop() hits again
}
}
// Interrupt on B changing state
void doEncoderB() {
if ( rotating ) delay (4);
if ( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
//  adjust counter – 1 if B leads A
if ( B_set && !A_set )
encoderPos = -s; //change the 1 to steps to take when encoder turned

rotating = false;
}
}

I don't much time to look at your program but I see 3 immediate issues.

  1. You should never delay in an ISR

  2. encoderPos is an int which means it is a multibyte variable. In loop() you should disable interrupts, make a temporary copy of encoderPos and re-enable interrupts. This ensures you don't get interrupted while trying to use encoderPos.

  3. the declaration for encoderPos should be preceded by volatile so the compiler does not try to optimize it out.

Post a link to the encoder's datasheet.

Let me know if this gives you the 360 pulses.

#define ClockPin 2 // Must be pin 2 
#define DataPin 3 // Must be pin 3
#define readA bitRead(PIND,2)//faster than digitalRead()  (((value) >> (bit)) & 0x01)
#define readB bitRead(PIND,3)//faster than digitalRead()  (((value) >> (bit)) & 0x01)
volatile long count = 0;

void setup() {
  Serial.begin(9600); //9600
  pinMode(ClockPin, INPUT);
  pinMode(DataPin, INPUT);
  /* Full 4 Step Count*/
  attachInterrupt(digitalPinToInterrupt(ClockPin), [] {(readA == readB)  ? count++ : count--;}, CHANGE);
  attachInterrupt(digitalPinToInterrupt(DataPin ), [] {(!readA == readB)  ? count++ : count--;}, CHANGE);
  */
}

void loop() {
  long Counter;
  static long lastCtr;
  noInterrupts ();
  Counter = count;
  interrupts ();
  if (lastCtr != Counter) {
    Serial.println(Counter);
    SpareCycles = 0;
  }
  lastCtr = Counter;
}

I simplified it more and haven't fully tested it
Z

Hi,
Welcome to the forum.

Added karma for using code tags.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Thanks.. Tom... :slight_smile:

Ok everybody here's the update.

Here's a link for the encoders specs

First, Zhomeslice your code was broken. There's a reference to what seems to be a variable that is never established and it over all just didn't work. Thank you for the try though.

Second to ToddL1962's recommendations. I believe pulling the isr out of the loop and putting them before the loop as well as making he encoderPos Volatile is what fixed the 360 pulse issue. As such in the codes to follow you will see integer "s" has been changed to 6 (2048/360 rounded up)

I could not find changes to much anything when canceling the delays. In my research I found the Delays in the isr's are what debounce to keep from missing encoder readings. However in my code trials I could not find a difference between the delay being there or not.

Here are 3 new versions of the code. The last one is my current official code.

This first code removes the stepper motor just to check the encoder reading in the serial monitor. Here the encoder reads the appropriate 360 pulses in one rotation. Proving the encoder works. No numbers in the serial monitor are skipped. Here taking out the delay in the isr (doencoder a or b) is what stopped another issue I had where spinning too fast caused the numbers not to skip but to read at incorrect intervals.

// http://thezhut.com/?page_id=22

const int encoderPinA = 2;   // right
const int encoderPinB = 3;   // left
volatile int encoderPos = 0;    // counter
unsigned int lastReportedPos = 1;   // change
static boolean rotating = false;    // debounce
boolean A_set = false;
boolean B_set = false;
const int stepsPerRevolution = 32;
int s=6;

void setup() {

pinMode(encoderPinA, INPUT_PULLUP); //enabling pullups
pinMode(encoderPinB, INPUT_PULLUP);
attachInterrupt(0, doEncoderA, CHANGE); //pin 2
attachInterrupt(1, doEncoderB, CHANGE); //pin 3
Serial.begin(9600);

}

void doEncoderA() {
// debounce
if ( rotating );  // wait a little until the bouncing is done
// Test transition
if ( digitalRead(encoderPinA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set )
encoderPos ++;  //change the 1 to steps to take when encoder turned
rotating = false;  // no more debouncing until loop() hits again
}
}
// Interrupt on B changing state

void doEncoderB() {
if ( rotating );
if ( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
//  adjust counter – 1 if B leads A
if ( B_set && !A_set )
encoderPos --; //change the 1 to steps to take when encoder turned

rotating = false;
}
}


void loop() {
rotating = true;  // reset the debouncer

if (lastReportedPos != encoderPos) {
Serial.println(encoderPos);
lastReportedPos = encoderPos;                                                       
}
}

Now here is the second code which includes the stepper motor. The 360 pulses issue is resolved here as well. It affects the encoder position integer by incrementing and decrementing the integer just like the code above. That prevents the stepper motor from working properly but I don't care about that here I'm just using this code to read from the serial monitor and see how the encoder is being read. Here there can be serious skipping of pulses especially if I spin too fast. Could this be a debouncing issue? Or perhaps just the fact that my stepper motor is not fast (the 28byj-48 stepper motor has a top speed of 15rpm) Why is the inclusion of the stepper motor in the code affecting the pulse intake on the serial monitor?

// http://thezhut.com/?page_id=22

#include <Stepper.h>
const int encoderPinA = 2;   // right
const int encoderPinB = 3;   // left
volatile int encoderPos = 0;    // counter
unsigned int lastReportedPos = 1;   // change
static boolean rotating = false;    // debounce
boolean A_set = false;
boolean B_set = false;
const int stepsPerRevolution = 32;
int s=6;
Stepper myStepper(stepsPerRevolution, 8, 10, 9, 11);  //h-bridge pins

void setup() {

myStepper.setSpeed(500);
pinMode(encoderPinA, INPUT_PULLUP); //enabling pullups
pinMode(encoderPinB, INPUT_PULLUP);
attachInterrupt(0, doEncoderA, CHANGE); //pin 2
attachInterrupt(1, doEncoderB, CHANGE); //pin 3
Serial.begin(9600);

}

void doEncoderA() {
// debounce
if ( rotating );  // wait a little until the bouncing is done
// Test transition
if ( digitalRead(encoderPinA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set )
encoderPos ++;  //change the 1 to steps to take when encoder turned
rotating = false;  // no more debouncing until loop() hits again
}
}

// Interrupt on B changing state
void doEncoderB() {
if ( rotating );
if ( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
//  adjust counter – 1 if B leads A
if ( B_set && !A_set )
encoderPos --; //change the 1 to steps to take when encoder turned

rotating = false;
}
}

void loop() {
rotating = true;  // reset the debouncer

if (lastReportedPos != encoderPos) {
Serial.println(encoderPos);
lastReportedPos = encoderPos;                                                       
myStepper.step(encoderPos);         
}
}

And last but not least this is the actual code I am using. Here the encoder and stepper motor just cant get synched very well. ( I know the difference in my "s" integer between 5.689(2048/360) and 6 add up eventually but its off more than that) I think my ultimate question at this point is about proper debouncing should I resort to hardware debouncing rather than this attempt at software debouncing?

// http://thezhut.com/?page_id=22

#include <Stepper.h>
const int encoderPinA = 2;   // right
const int encoderPinB = 3;   // left
volatile int encoderPos = 0;    // counter
unsigned int lastReportedPos = 1;   // change
static boolean rotating = false;    // debounce
boolean A_set = false;
boolean B_set = false;
const int stepsPerRevolution = 32;
int s=6;
Stepper myStepper(stepsPerRevolution, 8, 10, 9, 11);  //h-bridge pins

void setup() {

myStepper.setSpeed(500);
pinMode(encoderPinA, INPUT_PULLUP); //enabling pullups
pinMode(encoderPinB, INPUT_PULLUP);
attachInterrupt(0, doEncoderA, CHANGE); //pin 2
attachInterrupt(1, doEncoderB, CHANGE); //pin 3
Serial.begin(9600);

}

void doEncoderA() {
// debounce
if ( rotating );  // wait a little until the bouncing is done
// Test transition
if ( digitalRead(encoderPinA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set )
encoderPos ++;  //change the 1 to steps to take when encoder turned
rotating = false;  // no more debouncing until loop() hits again
}
}

// Interrupt on B changing state
void doEncoderB() {
if ( rotating );
if ( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
//  adjust counter – 1 if B leads A
if ( B_set && !A_set )
encoderPos --; //change the 1 to steps to take when encoder turned

rotating = false;
}
}

void loop() {
rotating = true;  // reset the debouncer

if (lastReportedPos != encoderPos) {
Serial.println(encoderPos);
lastReportedPos = encoderPos;                                                       
myStepper.step(encoderPos);         
}
}

fryj1992:
Here's a link for the encoders specs

That's hardly a datasheet. Try again.

From spec sheet:

Output Type NPN Open Collector

Do you know what that means?

fryj1992:
First, Zhomeslice your code was broken. There's a reference to what seems to be a variable that is never established and it over all just didn't work

There is probably a very small error - @Zhomeslice is an experienced programmer - but every programmer makes mistakes. Fixing his/her mistakes is the mark of a good programmer :slight_smile:

However you will need to post the exact error message if we (or @Zhomeslice) are to help you.

...R

Hi,
This might help;

Tom..... :slight_smile:

gfvalvo:
That's hardly a datasheet. Try again.

Both the pages I and @TomGeorge provided give you all the information you should need. But its a two phase(A&B), 360 pulse, incremental, optical rotary encoder.

Robin2:
There is probably a very small error - @Zhomeslice is an experienced programmer - but every programmer makes mistakes. Fixing his/her mistakes is the mark of a good programmer :slight_smile:

However you will need to post the exact error message if we (or @Zhomeslice) are to help you.

...R

The main error is

sketch_dec01a:25:5: error: 'SpareCycles' was not declared in this scope

SpareCycles = 0;

The variable is not referenced anywhere else in the sketch. That and the lonely "*/" which also provided an error message suggests that this was cut from some existing code but pieces were accidentally left out.

JCA34F:
From spec sheet:Do you know what that means?

I did not but I now believe it means something along the lines of in order for proper output signal the encoder needs to be grounded properly???

I attempted to change the grounding situation and received no difference.

This code I have is almost perfect here is the new question.

Why would a serial monitor read the encoder perfectly when interrupts are used and the arduino uno has nothing to output to, but then the serial monitor begins skipping pulse reads (and that is reflected in a stepper motor missing steps) when the uno is focused on outputting that data to a stepper motor?

fryj1992:
I did not but I now believe it means something along the lines of in order for proper output signal the encoder needs to be grounded properly???

Nope. Open Collector.

BTW, the State Transition Method is by far the best technique for handling rotary encoders that I've ever found.

fryj1992:
The main error is

sketch_dec01a:25:5: error: 'SpareCycles' was not declared in this scope

SpareCycles = 0;

The variable is not referenced anywhere else in the sketch. That and the lonely "*/" which also provided an error message suggests that this was cut from some existing code but pieces were accidentally left out.

I does look like that. Usually @Zhomeslice does not make that sort of mistake.

...R

Hey all,

So after tinkering and the like I have devised a code that works well with a rotary encoder to the 28-byj-48 unipolar stepper motor. This code derived from zhut.com but Ive made a few changes and it works great. I am also working on updating this code to work with my nema 23 stepper motors and thier kl-4030 drivers.

// http://thezhut.com/?page_id=22
and also Joseph Fry - Papa Pink

#include <Stepper.h>
const int encoderPinA = 2;   // right
const int encoderPinB = 3;   // left
volatile int encoderPos = 0;    // counter
unsigned int lastReportedPos = 1;   // change
static boolean rotating = false;    // debounce
boolean A_set = false;
boolean B_set = false;
const int stepsPerRevolution = 32;
int s=-6; // number of steps per one pulse of the rotary encoder
Stepper myStepper(stepsPerRevolution, 8, 10, 9, 11);  //  pins from the arduino board to the stepper motor driver

void setup() {

myStepper.setSpeed(700); // stepper speed
pinMode(encoderPinA, INPUT_PULLUP); //enabling pullups
pinMode(encoderPinB, INPUT_PULLUP);
attachInterrupt(0, doEncoderA, CHANGE); //pin 2
attachInterrupt(1, doEncoderB, CHANGE); //pin 3
Serial.begin(9600);

}

void doEncoderA() {
// debounce
if ( rotating );  // wait a little until the bouncing is done
// Test transition
if ( digitalRead(encoderPinA) != A_set ) { // debounce once more
A_set = !A_set;
// adjust counter + if A leads B
if ( A_set && !B_set )
encoderPos = s;  //change the 1 to steps to take when encoder turned
rotating = false;  // no more debouncing until loop() hits again
}
}

// Interrupt on B changing state
void doEncoderB() {
if ( rotating );
if ( digitalRead(encoderPinB) != B_set ) {
B_set = !B_set;
//  adjust counter – 1 if B leads A
if ( B_set && !A_set )
encoderPos = -s; //change the 1 to steps to take when encoder turned

rotating = false;
}
}

void loop() {
rotating = true;  // reset the debouncer

if (lastReportedPos != encoderPos) {
Serial.println(encoderPos);
lastReportedPos = encoderPos;                                                       
myStepper.step(encoderPos);         
encoderPos = 0;
}
}