Debouncing in real time

Fang Jin
4 min readSep 18, 2023

Debounce is a technique casually used for UI applications to prevent premature actions issued by user actions. In the device world, it is a much more common problem, and everyone encounters it because the problem has something to do with the basic switches or keys.

Say we have a keyboard key, when the key is released, we read the signal denoted as o , and when it’s pressed, we should read the signal as - . You can simply related the symbol as open status and close status. Thus if I press and then release a key, I should get the following reading:

ooooooooooo|---oooooooooooo

I added a symbol | to indicate where the press should be detected. However more than often I get one of the following readings:

oooooooooo|----|oooooooooo
oooooooooo|----|||oooooooooooo
oooooooooo|||||||||||||||||||||||||--|oooooooooooo

Depending on the size of the switch and how hard you press on it, you can get different readings. And interestingly in each of the readings, it encounters | more than once. Of course sometimes the count can go as high as 50 for a single press.

Yes, you press once, and it believes you pressed 50 times. And moreover it happens within 5ms or less. Thus if you do the reading a little bit later, say after 100ms, you will not notice this process at all. There’s nothing physically wrong with the switch, except when the switch touches the wire, it takes small amount of time bouncing between high voltage and low voltage and then settling to the final voltage.

Ok, if you happen to work within that 5ms, say your code monitors to the key interrupts etc, then you need to worry about this issue. Because otherwise you might run your code too many times more than what it supposed to.

Debouncing is what we do

In that case, we apply debouncing to it. I have written debouncing algorithm for the UI application, and lately I have discovered when it comes to device, the debouncing can be written similarity but with different flavors.

Delay a bit

The simplest one as I discovered, you can just wait a little bit:

void loop() {

// wait 5ms
delay(5);

// read the signal
int curr = digitalRead();

}

In the device firmware, we have full control of the time as in a game engine. If you want to wait 5ms, you just go head, the system will do nothing for the next 5ms. When it wakes up again, we just move to read the signal as usual. To our benefits, it will miss all the things happened in the past 5ms, which isn’t too bad.

Skip actions

The above delay way doesn’t work for everyone especially 5ms is quite important in device world, lots of actions can happen already. In that case, we can resort to the classical debounce strategy, which is to add a timer.

void myAction() {
// keep track of curr and prev time
if (currTime - prevTime > 5) {
// do the regular action
...
// update prev time
prevTime = currTime
} else {
// do nothing
}
}

Say I take an action every time I detect the key is pressed, inside the action, I check whether the wait has been longer than 5ms: if not, do nothing, which will allow the system to handle other things; if yes, execute the intended action. This is fulfilled by using a comparison between current time and previous time when the action was triggered. Another way to think of this process is that, premature actions triggered within that 5ms window get interrupted and handling is skipped. This is essentially the idea of debouching.

Note: the action is handled 5ms later than what’s intended.

Smooth actions

Since we are working under 5ms window, sometimes I have seen different implementation of debouncing. One of them is to collect all the past readings and then add them together and hope the collective value can decay to a stable value. This is referred as Shift Register Debounce.

void myAction() {
// calculate signal based on previous value
signal =(signal << 1) | digitalRead() | 0xfe00;

if (btndbc==0xff00) {
// take the action
} else {
// do nothing
}
}

The code structure looks similar except it doesn’t rely on a comparison of time. Instead, it uses eight past readings with shift operations. Past event bits signal will be shift one bit upper in each round, thus if the digitalRead does not provide 1 , the signal will eventually becomes stable to a base value. As long as new events keep triggering, the signal will deviate more from the base value thus skipping the action.

IMHO, the reason why this can work practically is that this action is pretty much tired to the clock. So waiting 4ms might be equivalent to wait for n clock cycles.

--

--

Fang Jin
Fang Jin

Written by Fang Jin

Front-end Engineer, book author of “Designing React Hooks the Right Way” and "Think in Recursion"

Responses (1)