rotary encoder, and I don't think it's bouncing

I have a problem with my rotary encoder, sometimes it skips a number. At first it looked like bounce to me, but the thing is: It only happens after a fresh restart of the board (but not always after a fresh start) Also, it doesn’t happen with the original code example I have used, but when I change the code to use a lcd with backpack instead of the serial monitor, it goes wrong.

I have included my sketch and the orginal one. Does anyone has an idea? Could it be that the interrupt routine is too long?

My Sketch.ino (2.47 KB)

Original.ino (2.42 KB)

don’t know it it’s helpful to you, but I use a different encoder reading program which is very accurate (IRQ polling speed: 4kHz):

/************************************************************
*
* Demo-Programm zur Auswertung eines händisch betriebenen
* Drehencoders (Quadraturencoder) mit dem Arduino im
* Timer-Interrupt mit einer Abfragefrequenz von rd. 4 kHz
*
* Kann von jederman frei verwendet werden, aber bitte den
* Hinweis: "Entnommen aus http://www.meinDUINO.de" einfügen
*
************************************************************/

// An die Pins 2 und 3 ist der Encoder angeschlossen
#define encoderA 2
#define encoderB 3

// Globale Variablen zur Auswertung in der
// Interrupt-Service-Routine (ISR)
volatile int8_t  altAB = 0;
volatile int32_t encoderWert = 0;

// Die beiden Schritt-Tabellen für volle oder 1/4-Auflösung
// 1/1 Auflösung
//int8_t schrittTab[16] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};

// 1/2 Auflösung ergibt bei Lego-Motoren 1 tick pro Grad (standard wie bei Lego)
int8_t schrittTab[16] = {0, 0,0,0,1,0,0,-1, 0,0,0,1,0,0,-1,0};


// 1/4 Auflösung
//int8_t schrittTab[16] = {0,0,0,0,0,0,0,-1,0,0,0,0,0,1,0,0};


/*************************************************************
*
* Interrupt Service Routine
*
* Wird aufgerufen, wenn der entsprechende Interrupt
* ausgelöst wird
*
*************************************************************/
ISR(TIMER1_COMPA_vect) {
  altAB <<= 2;
  altAB &= B00001100;
  altAB |= (digitalRead(encoderA) << 1) | digitalRead(encoderB);
  encoderWert += schrittTab[altAB];
}


/*************************************************************
*
* void setup()
*
* Wird einmal beim Programmstart ausgeführt
*
*************************************************************/
void setup() {
  pinMode(encoderA, INPUT);
  pinMode(encoderB, INPUT);
 
  noInterrupts(); // Jetzt keine Interrupts
  TIMSK1 |= (1<<OCIE1A);  // Timer 1 Output Compare A Match Interrupt Enable

  TCCR1A = 0; // "Normaler" Modus

  // WGM12: CTC-Modus einschalten (Clear Timer on Compare match)
  //        Stimmen OCR1A und Timer überein, wird der Interrupt
  //        ausgelöst
  // Bit CS12 und CS10 setzen = Vorteiler: 1024
  TCCR1B = (1<<WGM12) | (1<<CS12) | (1<<CS10);

  // Frequenz = 16000000 / 1024 / 15 = rd. 1kHz Abtastfrequenz;
  // Überlauf bei 14, weil die Zählung bei 0 beginnt
  OCR1A = 14;
 
  interrupts(); // Interrupts wieder erlauben

  Serial.begin(115200);
}


/*************************************************************
*
* void loop()
*
* Wird immer wieder durchlaufen
*
*************************************************************/
void loop() {
 
  while(true) {
    Serial.println(encoderWert);
    delay(100);
  }
}

Thanks, I'll try it! It definitely looks efficient with the assembler-like instructions! I might try some other libraries as well, or otherwise I'll try to see if writing my own code for interfacing the lcd will shorten the interrupt.

Hey, in your code, do you need pull-up or pull-down resistors? It has other problems for me, sometimes the value starts changing at its own.

Also, it doesn't happen with the original code example I have used, but when I change the code to use a lcd with backpack instead of the serial monitor, it goes wrong.

If the code was working to your satisfaction before you changed from the Serial monitor to the lcd, I would take out the lcd.clear() statement from loop and manage your formatting with cursor position and printing spaces if necessary.

lcd.clear() is relatively slow.

I usually just add a few small capacitors (0.1uF) to the encoder and be done with the bounce issue:

The resistor seen in the photo pulls the encoder switch low so it doesn't float.

cattledog:
If the code was working to your satisfaction before you changed from the Serial monitor to the lcd, I would take out the lcd.clear() statement from loop and manage your formatting with cursor position and printing spaces if necessary.

lcd.clear() is relatively slow.

Hmmm.. thanks. I was also looking in to assembly. Quite complex, but for writing some bits to a port it might be not to hard.

I now use my old code, but I added encoderPos = 0; in the setup function. This seems to work so far. O wait, I’m now using an 16x2 lcd without serial backpack. For fair comparison i’ll try the 20x4 lcd with backpack too.

No idea what goes wrong, apparantly the variable encoderPos does not always go back to the original state after a reset.

// interrupt routine for Rotary Encoders: raf@synapps.de 20120107

#include <LiquidCrystal.h>
LiquidCrystal lcd(8,9,4,5,6,7);
 
// usually the rotary encoders three pins have the ground pin in the middle
enum PinAssignments 
{
  encoderPinA = 2,   // right
  encoderPinB = 3,   // left
  clearButton = 1,   //push
};

volatile unsigned int encoderPos = 0;  // a counter for the dial
unsigned int lastReportedPos = 1;   // change management
static boolean rotating=false;      // debounce management

// interrupt service routine vars
boolean A_set = false;              
boolean B_set = false;


void setup() 
{
  pinMode(encoderPinA, INPUT); 
  pinMode(encoderPinB, INPUT); 
  pinMode(clearButton, INPUT);
  // turn on pullup resistors
  digitalWrite(encoderPinA, HIGH);
  digitalWrite(encoderPinB, HIGH);
  digitalWrite(clearButton, HIGH);

  // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(0, doEncoderA, CHANGE);
  // encoder pin on interrupt 1 (pin 3)
  attachInterrupt(1, doEncoderB, CHANGE);
 
  encoderPos =0;
     
  lcd.begin(16, 2); //16x2 lcd 
  lcd.clear();
  lcd.print(encoderPos);
}

// main loop, work is done by interrupt service routines, this one only prints stuff
void loop() 
{ 
   rotating = true;  // reset the debouncer

   if (lastReportedPos != encoderPos) 
      {
      lcd.clear();
      lcd.print(encoderPos);
      lastReportedPos = encoderPos;
      }
  if (digitalRead(clearButton) == LOW ) 
      encoderPos = 0;
  }


// Interrupt on A changing state
  void doEncoderA()
{
  // debounce
  if ( rotating ) delay (1);  // wait a little until the bouncing is done

  // Test transition, did things really change? 
  if( digitalRead(encoderPinA) != A_set )   // debounce once more
      {
        A_set = !A_set;

      // adjust counter + if A leads B
      if ( A_set && !B_set ) 
          encoderPos ++;

      rotating = false;  // no more debouncing until loop() hits again
  }
}

// Interrupt on B changing state, same as A above
void doEncoderB(){
  if ( rotating ) delay (1);
  if( digitalRead(encoderPinB) != B_set ) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if( B_set && !A_set ) 
      encoderPos --;

    rotating = false;