Rotary Encoder Odd Behaviour

Hi,
I’m working on a project that uses a rotary encoder as input. I’m planning on using it later to interface to a menu.

For now, I’m just getting it to count up and down and detect when the button is pressed. I’m using hardware debouncing on the encoder based on this VIDEO

Sometimes when I reset the arduino and rotate the encoder clockwise (which should have the effect of increasing the value being printed to the lcd) the first value is -1, or it does not change from 0.

It seems all subsequent encoder movements read correctly, its just the first reading that’s the problem.

Here is my code:

#define BUTTON_PIN 7
#include <LiquidCrystal.h>


int pulses, A_SIG = 0, B_SIG = 1;
int value = 0, lastPulses = 0;
boolean pulseChange = false;
boolean buttonState = false;
boolean lastButtonState = false;

LiquidCrystal lcd(8, 13, 9, 10, 11, 12);


void setup() {
  attachInterrupt(0, A_RISE, RISING);
  attachInterrupt(1, B_RISE, RISING);
  Serial.begin(115200);
  pinMode(BUTTON_PIN, INPUT);
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("hello, world!");
  delay(1000);
  lcd.clear();

}//setup


void loop() {
  lcd.setCursor(0, 0);

  if (pulseChange == true) {
    pulseChange = false;        // if clockwise, increment counter
    if (pulses > lastPulses) {
      value++;
    }
    else if (pulses < lastPulses) { // if counterclockwise, decrement counter
      value--;
    }


    lcd.print("Value = ");
    lcd.print(value);
    lastPulses = pulses;        // save this for next time around
  }


  buttonState = digitalRead(BUTTON_PIN);
  if (buttonState != lastButtonState)
    Serial.println(buttonState);

  lcd.setCursor(0, 0);

  lcd.print("Encoder = ");
  lcd.print(value);
  lcd.setCursor(0, 1);
  lcd.print("Button : ");
  lcd.print(buttonState);

  lastButtonState = buttonState;
}

void A_RISE() {
  detachInterrupt(0);
  A_SIG = 1;

  if (B_SIG == 0)
    pulses++;//moving forward
  pulseChange = true;

  if (B_SIG == 1)
    pulses--;//moving reverse
  pulseChange = true;
  //Serial.println(pulses);
  attachInterrupt(0, A_FALL, FALLING);
}

void A_FALL() {
  detachInterrupt(0);
  A_SIG = 0;

  if (B_SIG == 1)
    pulses++;//moving forward
  if (B_SIG == 0)
    pulses--;//moving reverse
  //Serial.println(pulses);
  attachInterrupt(0, A_RISE, RISING);
}

void B_RISE() {
  detachInterrupt(1);
  B_SIG = 1;

  if (A_SIG == 1)
    pulses++;//moving forward
  if (A_SIG == 0)
    pulses--;//moving reverse
  //Serial.println(pulses);
  attachInterrupt(1, B_FALL, FALLING);
}

void B_FALL() {
  detachInterrupt(1);
  B_SIG = 0;

  if (A_SIG == 0)
    pulses++;//moving forward
  if (A_SIG == 1)
    pulses--;//moving reverse
  //Serial.println(pulses);
  attachInterrupt(1, B_RISE, RISING);
}

try pinMode both to input_pullup

Try the filtering shown on page 2

You can see the 6 components above the rotary encoder on my board:
http://www.crossroadsfencing.com/BobuinoRev17/


And look at the code here to see how it is read

And see it in action here

Sometimes when I reset the arduino and rotate the encoder clockwise (which should have the effect of increasing the value being printed to the lcd) the first value is -1, or it does not change from 0.

int pulses, A_SIG = 0, B_SIG = 1;

This starting point may not reflect the actual position of the switches. You want to get the starting point from which to read the first pulse. In setup() make each of them equal to the digitalRead() value of the pin they are on.

Why are you calling detachInterrupt? Don't do that unless you really know what you are doing (in which case you'll know to avoid it anyway).

Here is clearly a case of needing

  attachInterrupt (0, my_isr, CHANGE) ;
  attachInterrupt (1, my_isr, CHANGE) ;

Then in the isr read both pins and compare to the previously read state.

knut_ny: try pinMode both to input_pullup

How would I do that on an interrupt pin? I'm already using 10K pullups on pins A & B of the encoder as well as 0.1uF (I also tried 0.01uF with no improvement) as well as 10K resistors. See the video I linked to in the original post at 5:05 to 5:40 for the wiring and components I have used.

CrossRoads: Try the filtering shown on page 2 http://www.bourns.com/docs/Product-Datasheets/PEC12R.pdf You can see the 6 components above the rotary encoder on my board: http://www.crossroadsfencing.com/BobuinoRev17/ |500x440

That's the same wiring and components I have tried using.

CrossRoads: And look at the code here to see how it is read https://github.com/nickgammon/arduino_sketches/tree/master/Atmega_Hex_Uploader_Fixed_Filename

From what I could tell from your code, you are using software debouncing (as well as your hardware debouncing filter?)

Nick Gammon wrote the code to run the board.

#if CROSSROADS_PROGRAMMING_BOARD

#if !NO_ENCODER

volatile boolean fired = false;

 // handle pin change interrupt for D8 to D13 here
ISR (PCINT0_vect)
{
static byte pinA, pinB;  
static boolean ready;
static unsigned long lastFiredTime;

  byte newPinA = digitalRead (Encoder_A_Pin);
  byte newPinB = digitalRead (Encoder_B_Pin);
  
  if (pinA == newPinA && 
      pinB == newPinB)
      return;    // spurious interrupt

  // so we only record a turn on both the same (HH or LL)
  
  // Forward is: LH/HH or HL/LL
  // Reverse is: HL/HH or LH/LL

  if (newPinA == newPinB)
    {
    if (ready)
      {
        
      if (millis () - lastFiredTime >= ROTARY_DEBOUNCE_TIME)
        {
        if (newPinA == HIGH)  // must be HH now
          {
          if (pinA == LOW)
            fileNumber ++;
          else
            fileNumber --;
          }
        else
          {                  // must be LL now
          if (pinA == LOW)  
            fileNumber --;
          else
            fileNumber ++;        
          }
        if (fileNumber > MAX_FILE_NUMBER)
          fileNumber = 0;
        else if (fileNumber < 0)
          fileNumber = MAX_FILE_NUMBER;
        lastFiredTime = millis ();
        fired = true;
        }
        
      ready = false;
      }  // end of being ready
    }  // end of completed click
  else
    ready = true;
    
  pinA = newPinA;
  pinB = newPinB;
    
 }  // end of PCINT2_vect

#endif // NO_ENCODER

Yes, it looks like software debouncing is used, allowing no more than 10 steps a second:

const unsigned long ROTARY_DEBOUNCE_TIME = 100; // milliseconds
:
if (millis () - lastFiredTime >= ROTARY_DEBOUNCE_TIME)