How do you pick component values for a debouncing filter?

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

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:

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.)

• 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. Commented Feb 22, 2019 at 0:01
• It's a mechanical encoder, not a magnetic or optical one so I wouldn't count on it not bouncing. Commented Feb 22, 2019 at 0:03
• @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. Commented Feb 22, 2019 at 0:04
• I disagree. The main reason debouncing is done in software is because it's cheaper and more convenient. But there are can be some really nasty instances where software debouncing doesn't work or isn't practical. I think the OP's instance might be one of them not due to nasty bouncing, but speed of input since it's an encoder and the Arduino might not be able to spare the CPU cycles to tend to the encoder without stalling every other process. Commented Feb 22, 2019 at 5:15
• Tim Wescott's comment is right on. Bounce looks identical to going back and forth over one of the encoder thresholds. This need not introduce any error if the decode logic is correct. I am too lazy to explain it more fully. But if you think about it you will see it is correct. I am only assuming that any bouncing of contact A will settle before contact B switches. As long as both contacts do not bounce at the same time, there will be no miscount. Commented Feb 23, 2019 at 5:14

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.

• 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.) Commented Feb 22, 2019 at 1:27
• 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. Commented Feb 22, 2019 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.

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 unsigned long lastFiredTime;

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 (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;
}

}  // end of being ready
}  // end of completed click
else

pinA = newPinA;
pinB = newPinB;

}  // end of PCINT2_vect

• 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. Commented Feb 22, 2019 at 1:22
• 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. Commented Feb 22, 2019 at 1:27
• And you ignore pulses shorter than 100MS? Doesn't that cause it to skip steps if you spin the knob quickly? Commented Feb 22, 2019 at 1:28
• Yes. Hard to dial in a select value if it reacts too fast. Commented Feb 22, 2019 at 1:33
• 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. Commented Feb 22, 2019 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
• Wouldn't a 50K pull-up make the rise time longer though? Commented Feb 22, 2019 at 11:46