Liquid Crystal Clashing with Interrupts

In a recent project I’ve had to employ interrupts to track two different encoder wheels/strips to ensure I know the exact positions of two stepper motors. The encoder strips have fairly high resolution, and change even with very slight movements – which is perfect for my project.

However, I encounter a problem that I’ve nailed down to be a conflict between the liquid crystal library and the interrupts themselves. If I have an lcd.print() statement within my loop, or any other call to an lcd method, the values I get for the counts are extraneous. A slight bump to the table sends them out of whack – they’ll count up (or down) 10, 20, 100 steps, very rapidly.

If I remove the lcd method calls, and repeat the experiment. A slight bump to the table still causes a change in the count value, but it’s what you would expect from such a vibration: it will go from 0 to -1 back to 0, to -1, and then settle down at 0, as an example.

I presume that the lcd method calls prevent interrupts from within, and somehow the counts stack up in one direction, rather than settling down, going back and forth. Anyone have any insight on how I might solve this problem? I found a few older threads mentioning the calls to “cli()” in wiring.c, but commenting those out did not help.

I’m not sure if there’s a better way to keep track of the encoder positions that would avoid the use of interrupts. I worry that if something gets jostled slightly while the code is off doing something else, the program will have no knowledge of this unexpected movement, and will no longer have an accurate idea of how the steppers are positioned.

Here’s the code boiled down to bare minimums for debugging:

#include <Wire.h>
#include <LiquidCrystal_SR.h>

//LCD display setup
LiquidCrystal_SR lcd(4, 6, 7);
//                   | |
//                   | \-- Clock Pin
//                   \---- Data/Enable Pin

#define encoderAI 0
#define encoderAQ 1
#define encoderBI 2
#define encoderBQ 3

volatile int countA;
volatile int countB;

int lastVal;
int curVal;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  countA = 0;
  pinMode(encoderAI, INPUT);
  pinMode(encoderAQ, INPUT);
  pinMode(encoderBI, INPUT);
  pinMode(encoderBQ, INPUT);
  
  attachInterrupt(2, handleEncoderA, CHANGE);
  attachInterrupt(0, handleEncoderB, CHANGE);
}

void loop() {
  // put your main code here, to run repeatedly:
  curVal = countA;
  if (lastVal != curVal) {
    Serial.println(countA);
    lcd.setCursor(0,0);
    lcd.print("This Is A Test");
  }
  lastVal = countA;
}

void handleEncoderA() {
  if(digitalRead(encoderAI) == digitalRead(encoderAQ)) {
    countA ++;
  } else {
    countA --;
  }
}

void handleEncoderB() {
  if(digitalRead(encoderBI) == digitalRead(encoderBQ)) {
    countB ++;
  } else {
    countB --;
  }
}
  attachInterrupt(2, handleEncoderA, CHANGE);
  attachInterrupt(0, handleEncoderB, CHANGE);

What Arduino board are you using ?

Whoops! Should have mentioned that. I'm using the Leonardo for this project. I believe interrupt 2 is on pin 0, and interrupt 0 is on pin 3. I could also use interrupts 1 or 3 on pins 2 and 1 if needed. For the encoders, it doesn't really matter if I use the Q or the I outputs to trigger the interrupt.

Look what else uses pins 0 and 1 though.

Try using pins 2 and 3 to avoid Serial comms interfering with your interrupts.

I was thinking this might be the issue. I guess I was confused about the pin mapping.

Does this imply that when the Arduino is trying to send serial data to the external shift register (through the LiquidCrystal library), it's also transmitting/listening on pins 0 and 1? The serial data sent through the USB does not seem to clash in the same way (i.e. Serial.print() statements do not cause conflicts -- that I've noticed at least).

I'll try and only use the interrupts on pins 2 and 3 and see what results I get.

There are 2 types of serial communication mixed together in this discussion.

  1. Hardware Serial. Used by Serial.print(), Serial.read() and other methods of the serial library, Communication is via pins 0 and 1 on the Arduino. These pins are also used when uploading a program to the Arduino.

  2. Serial communication using the Wire library. This uses pins 2 and 3 on the Leonardo and is not used for Serial.print() etc

As you are using a Leonardo that implies to me that if you want to use the Wire library and Hardware Serial you cannot use interrupts 0 to 3 because all 4 interrupt pins are being used for other purposes. If you can do without Hardware Serial for debugging output or user input then you should be able to use interrupts 2 and 3 as well as interrupt 4 on pin 7.

I guess I have a bit of a dilemma then, as I need both hardware serial, and software serial.

I need the hardware serial because the program will need to constantly communicate with a RaspberryPi (or other Linux machine) for instructions from a Python program. Basically Python is doing all the heavy lifting (calculations), pulling information from the web, and interpreting user input (PS3 Dual Shock Controller).

And I need the software serial -- well, mostly for debugging right now. It might be feasible for me to rewrite the Python module to receive status/debugging data from the Arduino as well and eliminate the need for this LCD.

From what you said though, the hardware Serial still interferes with two of the interrupts (on pins 0 and 1), but any clues as to why I don't observe any problems when only using Serial.print() commands, while simultaneously having the interrupt attached to a conflicting pin? Whereas software serial raises an issue?

More confusion on the serial front.
You are not using SoftwareSerial in the program you posted you are using the Wire library, but I know what you mean.

I don't understand why Hardware Serial is not interfering with interrupts 2 and 3 on pins 0 and 1 respectively.

I finally had some time to get back to this issue and try some debugging based on the comments.

I reassigned the pins so that I could use the interrupt on pin 7 (interrupt 4):

//LCD display setup
LiquidCrystal_SR lcd(4, 5, 6);
//                   | |
//                   | \-- Clock Pin
//                   \---- Data/Enable Pin

#define encoderAI 7
#define encoderAQ 8

and I removed the second interrupt for simplicity:

  attachInterrupt(4, handleEncoderA, CHANGE);
  //attachInterrupt(0, handleEncoderB, CHANGE);

Yet the results I get for countA are erratic when I have lcd.print statements within my loop, and stable when I do not, and now there's no direct pin interference (that I'm aware of).

Any ideas?