UNO hardware interrupt giving erratic encoder readings

Hello everyone,

I’m using these optical encoders to keep track of the wheel turns on a robot. I’ve attached the two hardware interrupts on the Uno to each encoder to count the ticks. However, while INT1 is giving stable, consistent readings, INT0 is giving erratic and noisy results. My code is below, as well as a screenshot of the serial monitor showing the inconsistency.

I’ve tried replacing the encoders, with no luck. I’m quite sure the problem is the INT0 interrupt. I’ve also tried using pullup resistors. I’ve considered using PinChangeInt, but I’m also using SoftwareSerial and the two are incompatible. Any suggestions? Am I missing something obvious here?

#define rightTireInterrupt INT0
#define leftTireInterrupt INT1
#define rightEncPin 2
#define leftEncPin 3
#define leftEncPower 4
#define rightEncPower 5
volatile int leftTick = 0;
volatile int rightTick = 0;

char printBuffer[100];

void handleRightTireInterrupt() {
  rightTick ++;
}

void handleLeftTireInterrupt() {
  leftTick ++;
}

void setup() {
  Serial.begin(9600);
    
  attachInterrupt(rightTireInterrupt, handleRightTireInterrupt, FALLING);
  attachInterrupt(leftTireInterrupt, handleLeftTireInterrupt, FALLING);
  
  pinMode(leftEncPower, OUTPUT); //I've run out of 5v pins, so high signals on these pins provide power to the encoders
  pinMode(rightEncPower, OUTPUT);
  
  pinMode(rightEncPin, INPUT_PULLUP);
  pinMode(leftEncPin, INPUT_PULLUP);
  
  digitalWrite(leftEncPower, HIGH);
  digitalWrite(rightEncPower, HIGH);
}

void loop() {
  sprintf(printBuffer, "Left Encoder: %d   Right Encoder: %d", leftTick, rightTick);
  Serial.println(printBuffer);
}

SerialMonitor.PNG

pinMode(leftEncPower, OUTPUT); //I've run out of 5v pins, so high signals on these pins provide power to the encoders

...which need how much current?

  sprintf(printBuffer, "Left Encoder: %d   Right Encoder: %d", leftTick, rightTick);

Not protecting the two variables with a critical section? That seems like a bad idea.

I don't know how much current the encoders need, but I have tried connecting them to the 5v output with no change in results.

What is a critical section?

Tacoma91: What is a critical section?

http://www.gammon.com.au/interrupts

I don't know how much current the encoders need...

Probably should figure that out before you permanently damage the processor.

Tacoma91: However, while INT1 is giving stable, consistent readings, INT0 is giving erratic and noisy results.

Uh, well, all the left values are 12 and all the right values are 897. That's not a very good example of "erratic and noisy".

Sorry, I should have clarified. The numbers there result after the robot drives forward a short distance (i.e. 12 encoder ticks). INT0 is being triggered exponentially more times, while INT1 is reading accurately

[quote author=Coding Badly date=1427333011 link=msg=2157658] Uh, well, all the left values are 12 and all the right values are 897. That's not a very good example of "erratic and noisy". [/quote] That's probably with the robot at rest. The debug loop just keeps printing over and over.

Most likely, the pulse signals from the encoders are bouncy. You need to debounce them. You just got lucky with one of them.

Interesting. Can I debounce them with code? Or will it only be reliable with a debouncing circuit?

I solved it. Debouncing and adding a critical section to the printout did the trick. My code is below.

Of course, 15 ms is far too much time for debouncing, I'm still playing around with it. Cheers

#define rightTireInterrupt INT1
#define leftTireInterrupt INT0
#define rightEncPin 2
#define leftEncPin 3
#define leftEncPower 4
#define rightEncPower 5
volatile int leftTick = 0;
volatile int rightTick = 0;

long debouncingTime = 15;
volatile unsigned long lastMillisR;
volatile unsigned long lastMillisL;
char printBuffer[100];

void handleLeftTireInterrupt() {
  if ((long)(millis() - lastMillisL) >= debouncingTime) {
    leftTick ++;
    lastMillisL = millis();
  }
}

void handleRightTireInterrupt() {
  if ((long)(millis() - lastMillisR) >= debouncingTime) {
    rightTick ++;
    lastMillisR = millis();
  }
}

void setup() {
  Serial.begin(9600);
  
  pinMode(leftEncPower, OUTPUT); //I've run out of 5v pins, so high signals on these pins provide power to the encoders
  pinMode(rightEncPower, OUTPUT);
  
  pinMode(rightEncPin, INPUT_PULLUP);
  pinMode(leftEncPin, INPUT_PULLUP);
  
  digitalWrite(leftEncPower, HIGH);
  digitalWrite(rightEncPower, HIGH);
  
  attachInterrupt(leftTireInterrupt, handleLeftTireInterrupt, FALLING); 
  attachInterrupt(rightTireInterrupt, handleRightTireInterrupt, FALLING);
}

void loop() {
  noInterrupts(); //Critical section
  sprintf(printBuffer, "Left Encoder: %d   Right Encoder: %d", leftTick, rightTick);
  Serial.println(printBuffer);
  interrupts();
}

What's up with the cast to a long?

  if ((long)(millis() - lastMillisL) >= debouncingTime) {

That's just a piece of code that copied from this Instructable here. Is the cast going to cause a problem?

Tacoma91: Is the cast going to cause a problem?

In the worst case, yes. In the best case, it is a source of confusion.

The correct datatype is unsigned long.

void loop() {
  noInterrupts(); //Critical section
  sprintf(printBuffer, "Left Encoder: %d   Right Encoder: %d", leftTick, rightTick);
  Serial.println(printBuffer);
  interrupts();
}

When Serial.println blocks waiting for the UART interrupt service routine to empty the transmit buffer that code will deadlock (your board will lock up).

You should create get functions for the values you want to obtain from the interrupt handler, such as:

int getLeftEncoder() {
  int retVal;
  noInterrupts(); //Critical section
   retVal = leftTick;
  interrupts();
  return retVal;
}

This way, interrupts are disabled for the shortest possible time.