Rotary encoder too fast for arduino to keep up?

Hi,

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?

Thanks.

Could the problem be in the sketch that you have not posted ?

Sketch:

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

Repeat after me, slowly.
I. Will. Never. Use. A. Serial. Print. of. any. form. in. an. interrupt. routine.

Not for any reason.

2 Likes

I will set a Flag in the ISR and do nothing else.

In loop( ), when I see the Flag is set, I will deal with it.

I'd a got there, but thanks! I was expecting to have to explain why no "Serial.print", first.

Note reply #4!

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.

Is that it? I was expecting a much bigger problem lol.

So from that, I presume you can mark the topic solved, then?

I need to test the new code still, to see if that solved it.

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.

yep. Pumping 30-40 chars per pulse into Serial at 9600 will cause grief, for sure!
C

Amen to that.

As soon as the term "interrupt" appears in a subject, it's on! :roll_eyes:

Exactly.
And forgetting to use volatile for variables changed by the ISR.

1 Like

6 posts were split to a new topic: Load shedding and power cuts

New code works great! Thanks everyone for the help.

And it’s usual to share your solutions so future readers can see your solution, i.e. giving back to the community.
:wink:

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

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.