Interrupt-Handling for Incremental-Encoding

Hi
I would like to read an Incremental-Encoder.
To do this, I use an ISR for the two Encode-Pins (Clock and Data)
My code below works, but not reliably.
EG: If I turn the know counterclockwise, the read value changes between 0 and 1.
I suspect a missing interrupt enable/disable like cli() and sei() in the ISR routine.
I tryed successless to manage it with noInterrupt() and interrupt().
But maybe the problem is somewhere else.
Many thanks for an advice.

#define ClockPin 6    //Incremental Encoder
#define DataPin  5    //Incremental Encoder

volatile uint8_t Speed=0;
uint8_t oldSpeed=0;
volatile uint8_t CurrentStateClock=0;
volatile uint8_t StateData=0;
volatile uint8_t LastStateClock=0;

void setup()
{ Serial.begin(9600);
  pinMode(ClockPin, INPUT_PULLUP);
  pinMode(DataPin,  INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ClockPin), SpeedISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(DataPin),  SpeedISR, CHANGE);
// Same behavior with FALLING
  LastStateClock = digitalRead(ClockPin);  //Init
}

void loop()
{ if(oldSpeed != Speed)
  { Serial.println(Speed);
      oldSpeed=Speed;
  }
}

void SpeedISR()
{ CurrentStateClock = digitalRead(ClockPin);
  StateData = digitalRead(DataPin);
  if (CurrentStateClock != LastStateClock)
  { if (StateData != CurrentStateClock)  //Rotating clockwise
    { if (Speed < 24) Speed++;                    
      LastStateClock = CurrentStateClock;  
    }
    else  //Rotating counterclockwise
    { if (Speed > 0) Speed--;
      LastStateClock = CurrentStateClock;
    } 
  }  
}

Please post a link to the incremental encoder data sheet.

I'm using this IncrementalEncoder:
PEC11R-4215F-S0024 - Incremental Encoder 24 PPR ... 5V 60min-1, Bourns

BTW : Maybe the problem is the digitalRead() within an ISR?

That looks to be a standard quadrature encoder. I suggest to use the Arduino encoder library.

Wire it according to the schematic diagram in the data sheet, to reduce switch bounce.

I suspect a missing interrupt enable/disable like cli() and sei() in the ISR routine

Those function calls are almost never useful in an ISR.

We need to know which Arduino you are using. The interrupt options that are available depend on the choice of processor pin.

Sorry I forgot to mention it:
ATSAMD21G18 ARM Cortex M0
( Adafruit Feather M0 RFM69 Packet Radio)

This test is true only if the clock pin changes. The data pin interrupt cannot do anything and can be omitted.

This update should occur always, not only under certain conditions.

I have modified my SAMD21-Code, and yes, I'll add 10nF-debouncing-caps as soon as I am within reach of a soldering iron. But for now: debouncing has to be done by software:

#define CHNApin          6    //Inkrementalgeber Channel-A
#define CHNBpin          5    //Inkrementalgeber Channel-B
#define TimeoutEncoder  50    //Debouncing-Timer Timeout
volatile unsigned long CHNApinDebouncing=0;    //Debouncing-Timer
volatile unsigned long CHNBpinDebouncing=0;  //Debouncing-Timer

void setup()
{ Serial.begin(115200);
  pinMode(CHNApin, INPUT_PULLUP);
  pinMode(CHNBpin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(CHNApin), CHNApinISR, FALLING);
  attachInterrupt(digitalPinToInterrupt(CHNBpin), CHNBpinISR, FALLING);
}

void loop() { }

void CHNApinISR()
{ if((millis() - CHNApinDebouncing) > TimeoutEncoder) //Debouncing-TimeOut zum Tasten entprellen
  { CHNApinDebouncing = millis();
    if(digitalRead(CHNBpin)==1)
      Serial.print("X");
  }
}

void CHNBpinISR()
{ if((millis() - CHNBpinDebouncing) > TimeoutEncoder) //Debouncing-TimeOut zum Tasten entprellen
  { CHNBpinDebouncing = millis();
    if(digitalRead(CHNApin)==1)
       Serial.print("-");
  }
}

For turning the encoder clockwise (more or less constantly 1rps) , the result is instead of XXXXXXXXXXX... e.g. XXXXXXXXX-XXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-XX-XXXXXXXXXX (non reproducible), what means, there are in a seemingly random order counterclockwise signals (-).
In my opinion, bouncing and Serial.print() (uC-Timing) should not be the problem.

This is a problem! Don't print anything in an ISR.

OK, done:

#define CHNApin          6    //Encoder Channel-A
#define CHNBpin          5    //Encoder Channel-B
#define TimeoutEncoder  50    //Debouncing-TimeOut
volatile unsigned long CHNApinDebouncing=0;    //Debouncing-Timer
volatile unsigned long CHNBpinDebouncing=0;  //Debouncing-Timer
volatile long Counter=0;
long lCounter=-1;

void setup()
{ Serial.begin(115200);
  pinMode(CHNApin, INPUT_PULLUP);
  pinMode(CHNBpin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(CHNApin), CHNApinISR, FALLING);
  attachInterrupt(digitalPinToInterrupt(CHNBpin), CHNBpinISR, FALLING);
}

void loop()
{ if(Counter != lCounter)
  { Serial.println(Counter);
    lCounter=Counter;
  }
}

void CHNApinISR()
{ if((millis() - CHNApinDebouncing) > TimeoutEncoder)
  { CHNApinDebouncing = millis();
    if(digitalRead(CHNBpin)==1)
      Counter++;
  }
}

void CHNBpinISR()
{ if((millis() - CHNBpinDebouncing) > TimeoutEncoder)
  { CHNBpinDebouncing = millis();
    if(digitalRead(CHNApin)==1)
       Counter--;
  }
}

And the same flickering...

Disable interrupts while reading Counter.

(Thanks DrDiettrich for your help)

OK, done:

#define CHNApin            6    //Encoder Channel-A
#define CHNBpin            5    //Encoder Channel-B
#define DebouncingTimeout 50    //Debouncing-TimeOut
volatile unsigned long CHNADebouncing=0; //DebouncingTimer Channel-A
volatile unsigned long CHNBDebouncing=0; //DebouncingTimer Channel-B
volatile long Counter=0;
long lCounter=-1;

void setup()
{ Serial.begin(115200);
  pinMode(CHNApin, INPUT_PULLUP);
  pinMode(CHNBpin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(CHNApin), EncoderCHNA_ISR, FALLING);
  attachInterrupt(digitalPinToInterrupt(CHNBpin), EncoderCHNB_ISR, FALLING);
}

void loop()
{ if(Counter != lCounter)
  { Serial.println(Counter);
    lCounter=Counter;
  }
}

void EncoderCHNA_ISR()
{ noInterrupts();
  if((millis() - CHNADebouncing) > DebouncingTimeout)
  { CHNADebouncing = millis();
    if(digitalRead(CHNBpin)==1)
      Counter++;
  }
  interrupts();
}

void EncoderCHNB_ISR()
{ noInterrupts();
  if((millis() - CHNBDebouncing) > DebouncingTimeout)
  { CHNBDebouncing = millis();
    if(digitalRead(CHNApin)==1)
       Counter--;
  }
  interrupts();
}

Same behavior.

But basically you agree with me?
If you only look at the clockwise direction:
The first time a negative edge occurs on channel A and inactive channel B, this means increasing the counter.
I don't see when the state that evokes counterclockwise behavior occurs.

Interrupts already are disabled in the ISR.

In the other ISR. With opposite direction raising and falling edges are exchanged.

You wrote: In the other ISR. With opposite direction raising and falling edges are exchanged.

But both, Channel-A and Channel B generate falling signaledges when they activated, because they switch to GND
And my ISR's only evaluate falling signaledges. (!?)

if the encoder stops after a raising edge and then rotates back the same edge is a falling one.

Sorry DrDiettrich, I don't see the error:

Another version which not correct works:

#define CHNApin 6    //Encoder Channel-A
#define CHNBpin 5    //Encoder Channel-B
volatile long Counter=0;
volatile uint8_t CHNAexpected=1; //1=>CHNAexpected 0=>CHNBexpected
long lCounter=-1;

void setup()
{ Serial.begin(115200);
  pinMode(CHNApin, INPUT_PULLUP);
  pinMode(CHNBpin, INPUT_PULLUP);
  //2-Bit Quadraturcode IncrementalEncoder Bourns PEC11R-4215F-S0024
  //24 Pulses per Fullrotation, 24 Detent, ContactBounce(15RPM max.2ms, RPM max.60
  attachInterrupt(digitalPinToInterrupt(CHNApin), EncoderCHNA_ISR, FALLING);
  attachInterrupt(digitalPinToInterrupt(CHNBpin), EncoderCHNB_ISR, FALLING);
}

void loop()
{ if(Counter != lCounter)
  { Serial.println(Counter);
    lCounter=Counter;
  }
}

void EncoderCHNA_ISR()
{ if(CHNAexpected)
  { CHNAexpected=0;
    if(digitalRead(CHNBpin)==1)
      if(Counter<24)
        Counter++;
  }
}

void EncoderCHNB_ISR()
{ if(!CHNAexpected)
  { CHNAexpected=1;
    if(digitalRead(CHNApin)==1)
      if(Counter>0)
        Counter--;
  }
}

And I don't see your problem:

If there still is a problem then please explain.

Most probably you are fighting too many effects at a time. Everything were easier if you replaced your encoder by an optical one which can not bounce.

This is guaranteed to fail on occasion:

You must disable interrupts when reading a variable shared with an interrupt routine, or it WILL be corrupted. As follows

noInterrupts();
long count_copy = Counter;
interrupts();
Serial.println(count_copy);
lCounter = count_copy;

Or just substitute lCounter for count_copy.

Thank you very much DrDiettrich for taking the time for my problem.
You wrote:

And I don't see your problem
If there still is a problem then please explain

It's a pleasure for me to explain:

  1. Felt like millions of producers successful use the cheaper version of the incremental switch-encoder instead of the optical one.

  2. The poblem with the current version is the flickering around the zero-value

  3. The Theory as shown in the diagram in post #15 should be right (Falling/Rising). Agree?

  4. My workaraund without flickering arround zero for now, is to count to 48 instead of 24 an divide the result by 2. This is accurate enough for my application, a replacement for a Potentiometer which controlls the speed of a toyengine.

Thank you jremington for your help.
I replaced my loop-Function as follows:

void loop()
{ if(Counter != lCounter)
  { noInterrupts();
    long count_copy = Counter;
    interrupts();
    Serial.println(count_copy);
    lCounter = count_copy;
  }
}

And the result: Unfortunately the same behavior as bevor :frowning: