I bought an incremental/quadrature rotary encoder for a project I'm working on.

The specs: https://www.bourns.com/docs/Product-Datasheets/PEC12R.pdf

They provide a diagram of a suggested filter circuit for debouncing the contacts on the encoder.

Here is the diagram from the PDF as an image: enter image description here

I'm not an electrical engineer, but I get the basic idea of an RC based filter. The resistor values make sense: They pull the switch up to logic 1 value while limiting the current drawn from the 5V rail. How do you select a capacitor value for such a circuit? Do you measure the maximum rate-of-change for the square wave pulses you'd get from this switch, round up a little for safety, and then design a low pass filter that blocks signals at higher frequencies? Do you identify the frequency of the jitter and design a filter that filters that?

The project is built using an Arduino microcontroller and a digital input. I have written an interrupt service routine (ISR) that detects a falling edge on one of the inputs of the encoder, and then compares the value to the value of the other input to determine rotation direction.

I'm not sure how much hysteresis an Arduino digital input line has. The whole arrangement would probably work better with a Schmitt trigger in it, but I don't have a lot of room left on my project board for a DIP package, and don't have the skills for surface mount soldering.

(I'm beginning to think the digital input on the Arduino can't handle the slow-changing signals from the filtered output of the encoder and that I will need to add a Schmitt trigger.)

  • 1
    \$\begingroup\$ If you design your quadrature decoder correctly then it'll be inherently insensitive to bounce, and any debounce filters become irrelevant. If you choose a decoder that's correctly defined then you're also home free. And even though this isn't supposed to be an opinion site, I use the "one true Scottsman" definition of "correctly designed decoder", which is, in part, that it's insensitive to switch bounce. \$\endgroup\$ – TimWescott Feb 22 at 0:01
  • \$\begingroup\$ It's a mechanical encoder, not a magnetic or optical one so I wouldn't count on it not bouncing. \$\endgroup\$ – DKNguyen Feb 22 at 0:03
  • \$\begingroup\$ @Toor it's mechanical. And even an optical encoder will have bounce-like effects in a noisy environment, or under vibration. And the given circuit, unless you feed it into a receiver that has hysteresis, will still, under pathological conditions, bounce. \$\endgroup\$ – TimWescott Feb 22 at 0:04
  • \$\begingroup\$ Yeah, I noticed you had attached the datasheet and removed my original question. \$\endgroup\$ – DKNguyen Feb 22 at 0:05
  • \$\begingroup\$ Please look through this list of related question to see if any apply to your case. Then, if you don't find one, refine your question. In particular, state the method you plan on using do decode the rotation, and whether you need to react to each click of the switch as an event, or if you just need to follow the shaft position. Please also mention whether you intend to decode this thing with a microprocessor, or if you have something simpler in mind. \$\endgroup\$ – TimWescott Feb 22 at 0:09

The bouncing characteristic is not repeatable or consistent. Note that the RC filter will add a delay to your turn on time as the output will need to charge once the voltage is stable. Figure out what is the max delay you can tolerate and design your 5tao RC time constant to be that delay. This will give you the most margin against bounces, as now any bounce less than that 5tao time constant delay will be rejected since it can't charge the capacitor to your threshold value. Otherwise, just pick component values that are reasonable for what you can accommodate size wise and see if that's good enough.


It's simpler than that. You measure (or guess) the time it takes for your switch to stop bouncing and stabilize. That should be equal to 3RC, at the very minimum. That is your lower bound.

The upper bound is just the minimum interval that you expect the switch to be activated at in normal use equalling 3RC.

The tradeoff between the getting closer to the lower and upper bound is tolerance to bounces vs latency in response.

I typically go with the latter criteria since I can't be bothered to measure switch bounce and it varies anyways. I typically wiggle or tap my fingers and see how many times I can do it per second at a fast but reasonable speed. In your case I would just do a spinning motion and see how many revolutions per second it works out to be and then work out how many encoder counts per second that is.

Also make sure whatever your encoder is feeding is well-behaved if the voltage is an "intermediate voltages" that correspond to neither logic 1 or logic 0. The nature of the low-pass filter can cause more time to be spent in this logical gray zone than can be tolerated. Hysteresis/schmidtt triggers inputs are one kind that can tolerate this. Otherwise, you will need to add a comparator to ensure the transition period through the logical gray zone is fast enough so the circuit reading the encoder does not misbehave.

  • \$\begingroup\$ I'm using a GPIO pin on an Arduino configured as an input. My guess is that it gets confused by the slow rise/fall times from my filter, so even though the filter debounces the signal, it causes the signal to linger too long in the "gray zone" between logical low and logical high. I think you may be right that I need a Schmitt trigger (Which I was hoping to avoid.) \$\endgroup\$ – Duncan C Feb 22 at 1:27
  • \$\begingroup\$ Alternatively, you could just feed the encoder signal directly to the Arduino and let it transition as fast as it will and do some fancy programming that samples several readings spaced apart and only accepts the reading when it is stable for n consecutive readings. This is a quick and dirty way to do it but doesn't entirely side step the gray zone issue. It's possible an Arduino is not up to the task though due to the slow speed and limited access to hardware that the Arduino gives you. \$\endgroup\$ – DKNguyen Feb 22 at 2:37

I use 1Ks and 0.1 to drive '328P inputs on this board, and it works quite well, you can see them right above the encoder. enter image description here

And this is the code that reads it:

Some declarations:

const unsigned long ROTARY_DEBOUNCE_TIME = 100; // milliseconds
const byte Encoder_A_Pin = 8;  // PB0, pin 12 (TQFP) on board
const byte Encoder_B_Pin = 9;  // PB1, pin 13 (TQFP) on board

And the ISR for the two pins to count 00 to 99, rolling over to 00, or under to 99, 98, ... and alternately 00 to FF, rolling over to 00, or under to FF, FE, ...

// 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 ++;  
        fileNumber --; 
    { // must be LL now
      if (pinA == LOW)
        fileNumber --;
        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
ready = true;

pinA = newPinA;
pinB = newPinB;

}  // end of PCINT2_vect
  • 2
    \$\begingroup\$ You're using all that code in an ISR? From what I've read you want to avoid DigitalRead() and millis() in ISRs because they are too slow and you will cause timing problems in the rest of your code if your interrupt routines take too much time. My ISR code uses port registers to read the pin values and is only a few lines long. I do the code to check millis() and read the updated encoder value outside the ISR in my loop() function. \$\endgroup\$ – Duncan C Feb 22 at 1:22
  • \$\begingroup\$ The ISR is fine in this application. One dials the encoder and the display changes instantaneously. The 16 MHz processor has no trouble keeping up. Other applications may vary. For a manually operated encoder it's not a problem. \$\endgroup\$ – CrossRoads Feb 22 at 1:27
  • \$\begingroup\$ And you ignore pulses shorter than 100MS? Doesn't that cause it to skip steps if you spin the knob quickly? \$\endgroup\$ – Duncan C Feb 22 at 1:28
  • \$\begingroup\$ Yes. Hard to dial in a select value if it reacts too fast. \$\endgroup\$ – CrossRoads Feb 22 at 1:33
  • \$\begingroup\$ My project is a multi-channel power control module that's doing several things at once. I would be hesitant to put so much code in an ISR because it would introduce lag and inconsistent timing in the rest of the code. \$\endgroup\$ – Duncan C Feb 22 at 11:45

Pertinent Specs:

 Contact resistance:     100 mohm @5V
 Resistive Load          10 mA at 5 V DC  ( 10 uW )
 Hand Soldering          Not recommended  N.B.
 Resolution              0012 = 12 Pulses per 360 ° Rotation  or  0024 = 24 Pulses
 Contact Bounce (15RPM)  2.0 ms. maximum  @ 15 RPM = 0.25 Hz  On=Off= 200ms/12 = 17ms or  8s (24pulse/rev)

Bourns design:

Rpull-up = R1=10k
LPF R2 = 10k
C = 0.01uF

My Analysis:

\$~~~~~~~~~~~~~R2*C=0.1ms = t_{on} ~~ ~\text{@ ΔV=63% from 5V }\$
\$(R1+R2)*C=0.2ms=t_{off}~\text{@ ΔV=63% from} ~ V_{on} = 0V\$

  • Switch current = 1 mA-pk, 0.5 mAdc
  • If bounces end <1ms then there could be several bounces >0.1ms
  • So a better filter change 10k pullup from 10k to use 50k with LPF=10k*0.01uF to Tr=0.6ms to attenuate contact bounce
  • \$\begingroup\$ Wouldn't a 50K pull-up make the rise time longer though? \$\endgroup\$ – Duncan C Feb 22 at 11:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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