Algorithm - PaulMurrayCbr/DebounceInput GitHub Wiki
The DebounceInput algorithm is based on cactusjack's Elegant debouncing solution with software Schmitt trigger emulation. I have made a few modifications.
Basically, we hold a variable simulating the charge in a capacitor. Let's say this value is floating point and changes between 0 and 1. Each time we push a signal into the filter, the value is changed so:
Vt+1 = Vt * .75 + Signal * .25
Rather than a simple threshold value to decide if the filter output is high or low, we use a method with hysteresis.
Firstly, the threshold value depends on the previous state of the debounced signal. If the filter output was HIGH
, then the filter value must drop below a lower bound before it is considered to have fallen low. And conversley it must exceed an upper bound before rising from LOW
to HIGH
.
Secondly, when the debounced output changes state the filter is forced to saturation. This is a modification of the original algorithm. It may be the case that with this saturation, the 'schmitt trigger' part of the algorithm is unneeded.
The samples are rate-limited to one every 4ms. The purpose of this is to provide consistent 'feel' to input buttons irrespective of how fast your sketch is providing samples. This may vary - a sketch may provide samples very quickly while it is idling, and less frequently when it is busy turnng a motor. Provided that you provide samples quick enough (ie, on the order of 2ms or so), the rate at which the filter detects state changes should remain more or less constant.
The algorithm uses a pair of 8-bit filters in sequence. I found that this seemed to work well. It may be that this is unnecessary, that using two filters in sequence is no different to using one filter with a lower weghting given to the incoming sample. However, that's how DebounceFilter currently works.
Rather than using floating point arithmetic, the value of each filter varies between 0 and 255. To multiply the filter by .75, I bit shift and add
(filter >> 1) + (filter >> 2)
Both filters are stored side-by-side in a single word
value. This means that they can be multiplied by .75 as a single operation. The second filter is stored in the high byte. This makes the threshold sensing easier - I can simply check bounds on the entire word, because the first filter will be treated as less significant bits.
Notice that the bitshift and add always discards the lowest bit of the 8-bit value. I take advantage of this by using the lowest bit of the two filters to hold the current state of the output signal and the change bit.
Notice that (255>>1)+(255>>2)+(255>>2) is 2 short of 255. To deal with this, when adding the signal to the first filter, I treat HIGH
as having the value 65 rather than 63.
When adding the first filter * .25 to the second filter, if the current state of the debounced signal is HIGH
, I add in an extra 2. It's not much, but without this the second filter cannot be driven to its maximum value.
Simple as. The threshold values for the 8-bit second filter are in public byte variables named risingThreshhold
and fallingThreshhold
. Default values are 0x90
(to trigger a rise) and 0x70
(to trigger a fall).
A state change from HIGH
to LOW
occurs if the filter value is LESS THAN fallingThreshhold. This means that if you set fallingThreshhold to 0, the state cannot change.
A state change from LOW
to HIGH
occurs if the filter value is GREATER THAN risingThreshhold. This means that if you set risingThreshhold to 255, the state cannot change.
You may need to experiment with these values. Tighter bounds make the filter more responsive, but may mean that it lets noise through. Wider bounds are safer, but may make the sensing of state changes unnaceptably slow.
In any case: if the state changes, then both filters are forced to saturation. This is simply a matter of assigning a constant value to them. If the state does not change, then the 'state changed' bit is cleared.
A subclass of DebounceFilter named DebounceFilter4ms holds a member named mostRecent4ms
. Ths member is a byte - 8 bits. It holds millis()
right-shifted by 2. That is, it holds the value of millis()
accurate to 4ms, but only the lower 10 bits of it with the bottom 2 chopped off. This means that it will cycle every second or so, which is heaps - if you want your button debounced, you need to be reading it way more frequently than that.
If a sample is provided to DebounceFilter4ms#addSampleRateLimited, and the current truncated millis()
is still the same as mostRecent4ms
, then the sample is dropped and not pushed into the filter.
This means that if you push samples into the filter at exactly the right rate - slightly slower than one per second - they will all be ignored. But if you do that, then you are doing it wrong.
Code can be viewed at
Or you can just inspect the contents of the library.