I am trying to track the position of a rotary encoder. Everything works great at really slow speeds, but when I start to spin the encoder quickly, the counter I have assigned in my program to represent the position of the encoder does not change as fast as I am turning the encoder. It looks to me like the Arduino is missing pulses from the encoder, but shouldn't the Arduino be plenty fast enough to keep up with me spinning the encoder by hand?
I am using a magnetic rotary encoder with 600 pulses per rotation, and for my application it only needs to spin at 40 rpm(although I am spinning it way slower than that by hand, and it is already having problems). I am using 1k pullup resistors on the encoder. Also my program uses interrupts.(I don't know if that matters).
Can the 1k resistors switch fast enough to keep up with the encoder, is the Arduino too slow, or is there something in my code that's causing this?
// Rotary Encoder Inputs
#define CLK 3
#define DT 2
int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
void setup() {
// put your setup code here, to run once:
// Set encoder pins as inputs
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
// Setup Serial Monitor
Serial.begin(9600);
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
// Call updateEncoder() when any high/low changed seen
// on interrupt 0 (pin 2), or interrupt 1 (pin 3)
attachInterrupt(digitalPinToInterrupt(CLK), updateEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(DT), updateEncoder, CHANGE);
}
void loop() {
// put your main code here, to run repeatedly:
}
void updateEncoder(){
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
counter --;
currentDir ="CCW";
} else {
// Encoder is rotating CW so increment
counter ++;
currentDir ="CW";
}
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
}
Declare 2 global variabels, direction, and counter. Assign those variables their values in the ISR. Eventually also a flag telling an interrupt has passed. Poll that flag in loop and print out the 2 variables.
The biggest problem is using interrupts but not knowing enough.
Often members know not much but still think interrupt is the magic solution, but face magic problems instead.
That's only 400 pulses per second at max speed. That's 2.5 milliseconds or 40,000 instruction cycles. You must be doing something incorrectly if it takes more than 40,000 instructions to count a pulse.
My bad. New sketch attached. I just moved the serial printing to the main loop. I didn't bother with the flag, because for my application I am going to skip all of the serial communication anyway. Thanks again for the help.
// Rotary Encoder Inputs
#define CLK 3
#define DT 2
volatile long counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";
void setup() {
// put your setup code here, to run once:
// Set encoder pins as inputs
pinMode(CLK,INPUT);
pinMode(DT,INPUT);
// Setup Serial Monitor
Serial.begin(9600);
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
// Call updateEncoder() when any high/low changed seen
// on interrupt 0 (pin 2), or interrupt 1 (pin 3)
attachInterrupt(digitalPinToInterrupt(CLK), updateEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(DT), updateEncoder, CHANGE);
}
void loop() {
// put your main code here, to run repeatedly:
Serial.print("Direction: ");
Serial.print(currentDir);
Serial.print(" | Counter: ");
Serial.println(counter);
}
void updateEncoder(){
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1){
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
counter --;
currentDir ="CCW";
} else {
// Encoder is rotating CW so increment
counter ++;
currentDir ="CW";
}
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
}