using interrupts to read encoder ticks

Hello,

I am building a differential drive robot and I am using Arduino to drive my robot the arduino mission is simple reading encoder ticks and give the motor driver the commands to drive the motors my code is working correctly each piece of it bacuase I have tested each seperatly

The problem is that I am using Interrupts to update the encoder ticks, the mechanism is whenever a pin A of any encoder is high execute the interrupt callback function, so what seems causing the problem that the motors is spinning and pin A will always be HIGH the result that I am getting stucked there in the callback function forever and never execute the other commands in the main loop

how can I get out of the interrupt callback function?
or that there is no such thing ?

I am using that code to get the encoder ticks using interrupts

// pins for the encoder inputs
#define RH_ENCODER_A 3 
#define RH_ENCODER_B 5
#define LH_ENCODER_A 2
#define LH_ENCODER_B 4
 
// variables to store the number of encoder pulses
// for each motor
volatile unsigned long leftCount = 0;
volatile unsigned long rightCount = 0;
 
void setup() {
  pinMode(LH_ENCODER_A, INPUT_PULLUP);
  pinMode(LH_ENCODER_B, INPUT_PULLUP);
  pinMode(RH_ENCODER_A, INPUT_PULLUP);
  pinMode(RH_ENCODER_B, INPUT_PULLUP);
  
  // initialize hardware interrupts
  attachInterrupt(0, leftEncoderEvent, CHANGE);
  attachInterrupt(1, rightEncoderEvent, CHANGE);
  
  Serial.begin(9600);
}
 
void loop() {
  Serial.print("Right Count: ");
  Serial.println(rightCount);
  Serial.print("Left Count: ");
  Serial.println(leftCount);
  Serial.println();
  delay(500);
}
 
// encoder event for the interrupt call
void leftEncoderEvent() {
  if (digitalRead(LH_ENCODER_A) == HIGH) {
    if (digitalRead(LH_ENCODER_B) == LOW) {
      leftCount++;
    } else {
      leftCount--;
    }
  } else {
    if (digitalRead(LH_ENCODER_B) == LOW) {
      leftCount--;
    } else {
      leftCount++;
    }
  }
}
 
// encoder event for the interrupt call
void rightEncoderEvent() {
  if (digitalRead(RH_ENCODER_A) == HIGH) {
    if (digitalRead(RH_ENCODER_B) == LOW) {
      rightCount++;
    } else {
      rightCount--;
    }
  } else {
    if (digitalRead(RH_ENCODER_B) == LOW) {
      rightCount--;
    } else {
      rightCount++;
    }
  }
}

What encoder are you using - how many PPR?
How fast is the motor spinning - range?

DKWatson:
What encoder are you using - how many PPR?
How fast is the motor spinning - range?

I am using this motor https://www.pololu.com/product/2824

which has:

  • 64 CPR Encoder
  • 50:1 Metal Gearmotor

resolution of 64 counts per revolution of the motor shaft, which corresponds to 3200 counts per revolution of the gearbox’s output shaft

DKWatson:
How fast is the motor spinning - range?

gfvalvo:

200 RPM

ShehabAldeen:
200 RPM

I presume that refers to the output shaft so it means 3200 * 200 / 60 = 10667 pulses per second. That is going to keep the Arduino busy, and if there are two encoders giving over 21,000 pulses per second?

I think you would better with EITHER an encoder on the output shaft OR an encoder on the motor shaft that just produces one pulse per revolution.

And make the code in the ISR as short as possible. There is no need to figure out the direction because you will already know which way the motor is turning.

...R

You appear to have plenty of resolution, so you can cut the number of interrupts in half by using a RISING interrupt on Pin A instead of CHANGE.

Then the ISR becomes

void leftEncoderEvent() //occurs when A goes HIGH
{
  if (digitalRead(LH_ENCODER_B) == LOW) {
    leftCount++;
  } else {
    leftCount--;
  }
}

cattledog:
You appear to have plenty of resolution, so you can cut the number of interrupts in half by using a RISING interrupt on Pin A instead of CHANGE.

Yes but ... that means my estimates of pulses per second in Reply #5 were only 50% of what they should have been

Then the ISR becomes

void leftEncoderEvent() //occurs when A goes HIGH

{
  if (digitalRead(LH_ENCODER_B) == LOW) {
    leftCount++;
  } else {
    leftCount--;
  }
}

As I said earlier, the program already knows the direction.

...R

Yes but ... that means my estimates of pulses per second in Reply #5 were only 50% of what they should have been

When looking at the Pololu webpage for the motor Pololu - 50:1 Metal Gearmotor 37Dx70L mm 12V with 64 CPR Encoder (Spur Pinion)

I see this

The quadrature encoder provides a resolution of 64 counts per revolution of the motor shaft when counting both edges of both channels.

Therefore, if you only count one edge on one channel there are 16 counts per motor revolution which gives woith a 50:1 gear ratio and 200 rpm
16x50x200/60 =2667 interrupts/second. With two encoders there are less then 6000 interrupts/second.

I think that this should be achievable on a 16 MHz Arduino. The ISR can certainly be speeded up with direct port readings replacing the digitalRead() and unidirectional logic.

I am getting stucked there in the callback function forever and never execute the other commands in the main loop

What else is going on in loop()? Can you post more complete code?

cattledog:
The ISR can certainly be speeded up with direct port readings replacing the digitalRead()

Or digitalReadFast() from the digitalWriteFast library - it is nearly as quick and a lot easier to use.

Silly me.

if the direction is known then there is no need to read any I/O pin - the interrupt has already done that.

...R