I bought some incremental rotary encoders online https://www.bourns.com/docs/Product-Datasheets/PEC12R.pdf

The spec sheet for those suggests a filter circuit for the outputs of the encoder that looks like this:

enter image description here

Adding that filter reduces, but doesn't eliminate, spurious signals from the encoder. I wanted to avoid adding Schmitt triggers or other solid state components since I don't have a lot of room left on my proto board for an IC, but I'm beginning to think I will have to add something with hysteresis since any filter that reduces jitter will also slow the rise time/fall time for the output of the encoder and keep it in the "gray area" between LOW and HIGH for a digital signal for too long.

Is there a way to wire these encoders and/or software that lets you read from them with simple passive filtering and/or software rather than adding something like a latch or Schmitt trigger for debouncing?


3 Answers 3


Having just chased a problem using a software based debounce for several days, what I'll add is that for the encoders I was using (green no-name Chinese ones), if you put 10mA though the contacts then the signal is a LOT better. So change the 10k to 470 Ohms.

Or if using 3.3V use 330 Ohms. Solved all my problems.


The answer seems to be no. I added the RC filter suggested by the manufacturer and the results still jump around (The values sometimes increase by several steps at once, or decrease even when turning the decoder clockwise.)

I added a CMOS Schmitt trigger and it resolves the problem.

I really don't understand why that is, since apparently Arduino digital inputs have integrated Schmitt triggers. This thread from the Arduino forum discusses it at length:


The only thing I can think of is that the hysteresis band of the built-in Schmitt triggers is too narrow (2.08 - 2.63V at VCC of 5V and typical operating conditions) and the filtered voltage still bounces between those values.

I'm using a MC14584B Schmitt trigger that I had lying around, and it looks like it's hysteresis band is ≈2.1V to 2.7V (typical at 25°C) which is slightly wider and goes slightly higher than the hysteresis range of the Arduino itself.

The differences seem quite small though, so I'm confused as to why the Schmitt trigger eliminates the "bobbling" of values I'm seeing.

I could add delay based debouncing code to my Arduino program, but the decoder handler is written as an ISR and those are supposed to be as fast as possible, and the millis() function is supposed to be quite slow, making it difficult.


I asked a related question regarding filter component values on the EE stack exchange, and somebody there pointed out that I should only be seeing "bounce" on one pin at a time, and that I should be able to reject spurious changes purely through software. I'm trying to do that now.

My new code looks like this:

bool rotaryValueChanged = false;

bool pinAHasChanged = false;
bool pinBHasChanged = false;

enum ChangeTypes {
ChangeTypes changesToDetect = fallingA;

void encode() {
  pinAHasChanged = true;
  //Read the PinA & PinB (Digital Pins 2 & 3) using port register PINE, 4th and 5th bit
  //Fast equivalent to pinAState = digitalRead(rotaryPinA) == LOW
  bool pinAState = (PINE & (1 << 4)) == 0;
  bool pinBState = (PINE & (1 << 5)) == 0;
  //Ignore state changes to pinA unless pinB has changed since the last change.
  //Also only pay attention if this is a falling edge 
  //or we're tracking both rising and falling edge changes
  if (pinBHasChanged && (pinAState == false || changesToDetect != fallingA)) {
    rotaryValue +=  (pinAState == pinBState) ? -1 : 1;
    rotaryValueChanged = true;
    pinBHasChanged = false;

void encodePinB() {
  pinBHasChanged = true;

  //Unless we're counting 48 ticks/rotation, don't count PinB changes.
  if (changesToDetect != changingAOrB) { 
  //Read the PinA & PinB (Digital Pins 2 & 3) using port register PINE, 4th and 5th bit
  bool pinAState = (PINE & (1 << 4)) == 0;
  bool pinBState = (PINE & (1 << 5)) == 0;

  //Ignore state changes to pinB unless pinA has changed since the last change
  if (pinAHasChanged) {
    rotaryValue +=  (pinAState != pinBState) ? -1 : 1;
    rotaryValueChanged = true;

void setup() {
  attachInterrupt(digitalPinToInterrupt(rotaryPinA), encode, CHANGE);//FALLING
  attachInterrupt(digitalPinToInterrupt(rotaryPinB), encodePinB, CHANGE);
  //More setup code here...

This code is written to handle 3 possible options:

  • Only falling edge state changes on pin A (12 steps/rotation)
  • Rising or falling changes on pin A (24 steps/rotation)
  • Rising or falling state changes on either pin (48 steps/rotation.)

The key part of this is to have interrupts on both pins. When one pin changes, its interrupt handler sets a "this pin changed" bool to true. In the ISR (Interrupt Service Routine) for the other pin, it only pays attention to a state change if the other pin has changed. That way, when Pin A changes states, if it bounces, the other pin should not bounce at the same time and so unless pin B has changed, I can simply ignore additional changes to Pin A and handle my debouncing in software, and without having to do timing delays which are too slow for an ISR.

However, the code above doesn't quite work. Sometimes I get bogus reverse rotation readings. If I'm rotating the encoder clockwise, the values I get increase one by one as expected, but occasionally decrement by 1 and then go back to incrementing.

What am I doing wrong?

Since the bouncing should only ever occur on one pin at a time, the above code should give correct readings from a bare rotary encoder without any hardware debouncing needed, but it doesn't.

My test case is only counting falling edge transitions for Pin A of my encoder, so picking up spurious counterclockwise rotation readings suggests that sometimes Pin B is changing state at the same time as pin B. That shouldn't happen, so I'm confused.

  • Have a look at MC14490 hardware debouncer.
    – Alexander
    Commented Apr 5, 2019 at 9:06

First of all, I would question the usefulness of debouncing a rotary encoder. I would expect to have contact bounce only when the contact is transitioning from HIGH to LOW or vice-versa. At this transition point, both the HIGH and LOW readings are in a sense correct. A non-debounced reading should then display a rotary angle that briefly changes back and forth between two consecutive positions, with no cumulative error.

I would suggest you forget about debouncing and just look at the state changes. If you think you do need debouncing, handle it downstream, by periodically querying the “raw” angle and updating the “debounced” angle:

  • if the raw angle is more than one unit apart from the debounced angle, bring this difference back to one unit.

  • if the raw angle has not changed since a fixed count of milliseconds, then make the debounced angle equal to the raw angle.

Second point: you are not looking at the falling edged of input A, you are looking at the rising edges. This is because pinAState uses negative logic: it is true when the pin is LOW. It shouldn't make a big difference though in terms of the logic of the code.

Third point: there is some flaw in your counting logic. I will not try to analyze it in detail, as the whole debouncing concept you use does not make sense to me, but only provide an example of how it can fail. Let's assume the encoder is in the position where both inputs are LOW, and you wiggle it a little bit so that each input, in turn, gives a positive pulse, like this:

                _       _       _       _       _       _
input A      __/ \_____/ \_____/ \_____/ \_____/ \_____/ \_____
                    _       _       _       _       _       _
input B      ______/ \_____/ \_____/ \_____/ \_____/ \_____/ \_

rotaryValue      0     |   1   |   2   |   3   |   4   |   5

Here, rotaryValue is incremented on each rising edge of input A, save for the very first one. In this case, the expected behavior is for this variable to stay, say, within [−1, +1].

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.