Skip to content

Debouncing

Ignoring a noisy button until its reading has stayed the same for a number of ticks in a row.

When you press a mechanical button, its metal contacts do not close cleanly. They touch, spring apart, touch again, and only settle after a few milliseconds; releasing the button does the same. Nobody notices that by eye, but a chart that ticks every millisecond reads it as a burst of presses. Debouncing means accepting a new reading only once it has been stable for a while.

This page builds a debouncer that counts ticks: first an attempt with a count delay that looks right and is not, then a state machine that works, then the same idea with an explicit counter, and finally a debouncer and a toggle working together in two regions. It builds on edges and uses count delays from the previous tick.

Here is a toggle that switches on every rising edge of its input, fed with one press and one release of a bouncy button. Each true after a false is a rising edge:

BouncyToggleSimulation

Diagram

Diagram of BouncyToggleStates: Off (initial), On. Transitions: Off to On: raw && !pre(raw) / light = true; On to Off: raw && !pre(raw) / light = false.BouncyToggleinput boolrawoutput boollightOffOnraw && !pre(raw)/ light = trueraw && !pre(raw)/ light = false

Code

scchart BouncyToggle {
input bool raw
output bool light
initial state Off
if raw && !pre(raw) do light = true go to On
state On
if raw && !pre(raw) do light = false go to Off
}
Tick 0 of 17
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it.
Timing diagram of BouncyToggle
Tickinput bool rawoutput bool lightActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 17 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 17 ticks:

  1. raw = falseTick 1: the button is up, the light is off.
  2. raw = trueThe contacts touch for the first time: a rising edge, and the light goes on.
  3. raw = falseThey bounce apart again.
  4. raw = trueTouch again: another rising edge, and the light goes off.
  5. raw = trueContact.
  6. raw = falseAnother bounce.
  7. raw = trueContact once more: on.
  8. raw = trueFrom here the contacts stay closed: the press has settled.
  9. raw = trueHeld.
  10. raw = trueHeld.
  11. raw = trueHeld.
  12. raw = falseThe release starts with a bounce of its own ...
  13. raw = true... which is another rising edge: off.
  14. raw = falseReleased.
  15. raw = falseReleased.
  16. raw = falseReleased.
  17. raw = falseOne press and one release, four toggles: the light is back where it started.
One press (ticks 2 to 11) and one release (from tick 12): four toggles, and the light ends up where it started.

The edge detection is correct. The input is the problem: the chart has no way to tell a bounce from a real press, unless you tell it how long a real press lasts.

A debouncer turns the raw reading raw into a clean one, pressed:

pressed changes only when raw has disagreed with it for N readings in a row.

A single disagreeing reading followed by an agreeing one is noise and is forgotten. The examples on this page use N = 3.

A count delay, if 3 raw, fires in the third tick in which raw is true. That sounds like “three readings”, so it is tempting:

DebounceNaiveSimulation

Diagram

Diagram of DebounceNaiveStates: Released (initial), Pressed. Transitions: Released to Pressed: 3 raw / pressed = true; Pressed to Released: !raw / pressed = false.DebounceNaiveinput boolrawoutput boolpressedReleasedPressed3 raw/ pressed = true!raw/ pressed = false

Code

scchart DebounceNaive {
input bool raw
output bool pressed
initial state Released
if 3 raw do pressed = true go to Pressed
state Pressed
if !raw do pressed = false go to Released
}
Tick 0 of 17
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it.
Timing diagram of DebounceNaive
Tickinput bool rawoutput bool pressedActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 17 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 17 ticks:

  1. raw = falseTick 1: released.
  2. raw = trueFirst contact: the count delay if 3 raw counts its first tick.
  3. raw = falseA bounce. The count delay does not care: it counts ticks in which raw is true, not ticks in a row.
  4. raw = trueSecond tick with raw true.
  5. raw = trueThird tick with raw true: pressed is accepted, although the readings were true, false, true, true. Too early.
  6. raw = falseThe next bounce releases it at once: the release side has no filter at all.
  7. raw = trueCounting again from scratch: one ...
  8. raw = true... two ...
  9. raw = true... three: pressed again. One physical press has produced two.
  10. raw = trueHeld.
  11. raw = trueHeld.
  12. raw = falseThe release bounce releases pressed immediately.
  13. raw = trueCounting: one.
  14. raw = falseReleased.
  15. raw = falseReleased.
  16. raw = falseReleased.
  17. raw = falseReleased.
Tick 5: the press is accepted after the readings true, false, true, true. Tick 6 releases it on a single false reading, and tick 9 accepts a second press from the same bounce.

It accepts the press in tick 5, after the readings true, false, true, true. A count delay counts the ticks in which its trigger holds while its state is active; they do not have to be consecutive, and the count only restarts when the state is entered again. So if 3 raw means “the third tick with raw since I got here”, not “three in a row”. (This first attempt also releases on the first false reading; that is the second bug, and the same fix cures it.)

To demand readings in a row, restart the count whenever a reading disagrees. Leaving a state and coming back does exactly that, because every entry of a state restarts its count delays. So add a “maybe” state on each side:

DebounceSimulation

Diagram

Diagram of DebounceStates: Released (initial), MaybePressed, Pressed, MaybeReleased. Transitions: Released to MaybePressed: raw; MaybePressed to Released: 1: !raw; MaybePressed to Pressed: 2: 2 raw / pressed = true; Pressed to MaybeReleased: !raw; MaybeReleased to Pressed: 1: raw; MaybeReleased to Released: 2: 2 !raw / pressed = false.Debounceinput boolrawoutput boolpressedReleasedMaybePressedPressedMaybeReleasedraw1: !raw2: 2 raw/ pressed = true!raw1: raw2: 2 !raw/ pressed = false

Code

scchart Debounce {
input bool raw
output bool pressed
initial state Released
if raw go to MaybePressed
state MaybePressed
if !raw go to Released
if 2 raw do pressed = true go to Pressed
state Pressed
if !raw go to MaybeReleased
state MaybeReleased
if raw go to Pressed
if 2 !raw do pressed = false go to Released
}
Tick 0 of 17
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it.
Timing diagram of Debounce
Tickinput bool rawoutput bool pressedActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 17 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 17 ticks:

  1. raw = falseTick 1: Released, pressed is false.
  2. raw = trueFirst contact: Released moves to MaybePressed. Nothing is accepted yet.
  3. raw = falseA bounce: if !raw goes straight back to Released, and the count is forgotten.
  4. raw = trueContact: MaybePressed again, entered afresh.
  5. raw = trueSecond reading in a row: if 2 raw has counted one tick.
  6. raw = falseA bounce: back to Released.
  7. raw = trueContact: MaybePressed.
  8. raw = trueSecond true reading in a row.
  9. raw = trueThird true reading in a row: if 2 raw completes, and pressed becomes true.
  10. raw = trueHeld: Pressed waits for a false reading.
  11. raw = trueHeld.
  12. raw = falseThe release starts: MaybeReleased. pressed stays true.
  13. raw = trueA bounce on the way out: back to Pressed, and the release count is forgotten.
  14. raw = falseFalse: MaybeReleased.
  15. raw = falseSecond false reading in a row.
  16. raw = falseThird false reading in a row: pressed becomes false.
  17. raw = falseReleased. One press, one pressed pulse of seven ticks.
Bounces in ticks 3, 6 and 13 send the chart back without changing pressed. The press is accepted in tick 9, the release in tick 16.

MaybePressed is entered on the first true. Every false sends the chart back to Released, which throws the count away; the next true enters MaybePressed afresh, with a fresh count. Only if raw stays true in the next two ticks does if 2 raw fire, in the third reading in a row. The release side is the mirror image. Here is the press, tick by tick:

Tick raw State after pressed
1 false Released false
2 true MaybePressed false
3 false Released false
4 true MaybePressed false
5 true MaybePressed false
6 false Released false
7 true MaybePressed false
8 true MaybePressed false
9 true Pressed true

Why if 2 raw and not if 3 raw? The count delay is delayed: it does not count the tick in which MaybePressed is entered, and that tick already brought the first true. So the transition fires in the second counted tick, which is the third reading in a row. For N readings, write if N − 1 raw. With N = 2 the count delay disappears: MaybePressed needs only a plain if raw go to Pressed, because a delayed transition already waits for the next reading.

N has to cover the bounce. If the contacts bounce for at most b milliseconds and the chart ticks every t milliseconds, a bounce lasts up to b / t ticks, so pick N comfortably larger than that. Do not overdo it: the debouncer accepts a change in the N-th stable reading. That is N − 1 ticks after the first stable reading and N ticks after the last bounce: in the tour above, the last bounce is in tick 6, the first stable reading in tick 7, and the press is accepted in tick 9. So the user feels a delay of about N ticks. Button datasheets quote bounce times; when in doubt, measure.

For a large N, say 20 readings at one tick per millisecond, write the count as a number instead of as states. run counts how many readings in a row have disagreed with pressed:

DebounceCounterSimulation

Diagram

Diagram of DebounceCounterStates: Filter (initial). Transitions: Filter to Filter: 1: raw == pressed / run = 0; Filter to Filter: 2: run < N - 1 / run++; Filter to Filter: 3: / pressed = raw; run = 0.DebounceCounterinput boolrawoutput boolpressedconst intN = 3intrun = 0Filter1: raw == pressed/ run = 02: run < N - 1/ run++3: / pressed = raw;run = 0

Code

scchart DebounceCounter {
input bool raw
output bool pressed
const int N = 3 // readings in a row needed to accept a change
int run = 0 // readings in a row that disagree with pressed
initial state Filter
if raw == pressed do run = 0 go to Filter
if run < N - 1 do run++ go to Filter
do pressed = raw; run = 0 go to Filter
}
Tick 0 of 17
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it.
Timing diagram of DebounceCounter
Tickinput bool rawoutput bool pressedlocal int runActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 17 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 17 ticks:

  1. raw = falseTick 1 enters Filter. Its transitions are delayed, so the first reading is compared in tick 2; run is 0.
  2. raw = trueFirst disagreement: run is 1.
  3. raw = falseraw agrees again: run goes back to 0.
  4. raw = trueDisagrees: 1.
  5. raw = trueDisagrees: 2.
  6. raw = falseAgrees: 0.
  7. raw = trueDisagrees: 1.
  8. raw = trueDisagrees: 2.
  9. raw = trueDisagrees for the third tick in a row: run < N - 1 is false, so the last transition fires, sets pressed to raw and restarts run.
  10. raw = trueAgrees.
  11. raw = trueAgrees.
  12. raw = falseDisagrees: 1.
  13. raw = trueAgrees: 0.
  14. raw = falseDisagrees: 1.
  15. raw = falseDisagrees: 2.
  16. raw = falseThird in a row: pressed becomes false.
  17. raw = falseAgrees. The same result as Debounce, tick for tick.
run climbs to 2 and falls back to 0 on every bounce; on the third disagreeing reading in a row, pressed changes.

The single state Filter loops to itself in every tick, and its three transitions are tried in priority order:

  1. raw agrees with pressed: nothing to do, forget any run of disagreements.
  2. It disagrees, but the run is not long enough yet: run++.
  3. Otherwise this is the N-th disagreeing reading in a row: accept it and start again.

The result is identical to Debounce, tick for tick, and the same chart debounces both edges. run is a local declared at the top of the chart, not inside Filter: a variable declared in a state is initialised again every time the state is entered, and Filter is entered in every tick, so a counter declared inside it would never count past 1.

A debouncer is most useful as a separate piece that other parts of the chart listen to. Here one region debounces and emits a local signal press when it accepts a press; a second region toggles a light on press:

DebouncedToggleSimulation

Diagram

Diagram of DebouncedToggleStates: Released (initial), MaybePressed, Pressed, MaybeReleased, Off (initial), On. Transitions: Released to MaybePressed: raw; MaybePressed to Released: 1: !raw; MaybePressed to Pressed: 2: 2 raw / press; Pressed to MaybeReleased: !raw; MaybeReleased to Pressed: 1: raw; MaybeReleased to Released: 2: 2 !raw; Off to On: press / light = true; On to Off: press / light = false.DebouncedToggleinput boolrawoutput boollightsignalpressDebounceReleasedMaybePressedPressedMaybeReleasedraw1: !raw2: 2 raw/ press!raw1: raw2: 2 !rawToggleOffOnpress/ light = truepress/ light = false

Code

scchart DebouncedToggle {
input bool raw
output bool light
signal press
region Debounce {
initial state Released
if raw go to MaybePressed
state MaybePressed
if !raw go to Released
if 2 raw do press go to Pressed
state Pressed
if !raw go to MaybeReleased
state MaybeReleased
if raw go to Pressed
if 2 !raw go to Released
}
region Toggle {
initial state Off
if press do light = true go to On
state On
if press do light = false go to Off
}
}
Tick 0 of 17
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it.
Timing diagram of DebouncedToggle
Tickinput bool rawoutput bool lightActive state of region DebounceActive state of region Toggle
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 17 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 17 ticks:

  1. raw = falseTick 1: off.
  2. raw = trueFirst contact: Debounce goes to MaybePressed.
  3. raw = falseBounce.
  4. raw = trueContact.
  5. raw = trueContact.
  6. raw = falseBounce.
  7. raw = trueContact.
  8. raw = trueContact.
  9. raw = trueThird reading in a row: Debounce emits press, and Toggle, which reads press in the same tick, switches the light on.
  10. raw = trueHeld.
  11. raw = trueHeld.
  12. raw = falseRelease begins.
  13. raw = trueRelease bounce: back to Pressed, no press.
  14. raw = falseReleased.
  15. raw = falseReleased.
  16. raw = falseThird false reading in a row: Debounce goes back to Released.
  17. raw = falseOne press, one toggle.
The same bouncy input as at the top of the page. One press, one toggle, in tick 9.

The two regions run in the same tick. In tick 9, Debounce emits press, and Toggle sees it in that same tick: KIELER schedules every emission of a signal before every test of it (see Concurrent regions). Note the order: debounce first, then react to the debounced event. Edge detection on the raw input counts the bounces, as the first example on this page shows.

Slow to press, quick to release

Change the debouncer so that a press needs four readings in a row, but a release only two. To test it, use the bouncy input of the Debounce tour above: raw true in ticks 2, 4 and 5, 7 to 11, and 13. The starter accepts the press in tick 9 and the release in tick 16; your version should accept the press in tick 10 and the release in tick 15.

Show the starter chart
scchart Debounce {
input bool raw
output bool pressed
initial state Released
if raw go to MaybePressed
state MaybePressed
if !raw go to Released
if 2 raw do pressed = true go to Pressed
state Pressed
if !raw go to MaybeReleased
state MaybeReleased
if raw go to Pressed
if 2 !raw do pressed = false go to Released
}
Show a solution
scchart Debounce {
input bool raw
output bool pressed
initial state Released
if raw go to MaybePressed
state MaybePressed
if !raw go to Released
if 3 raw do pressed = true go to Pressed
state Pressed
if !raw go to MaybeReleased
state MaybeReleased
if raw go to Pressed
if !raw do pressed = false go to Released
}

Two buttons

The starter debounces rawA into pressedA and ignores rawB. Make it debounce rawB into pressedB as well, independently, with the same rule. To test it: with rawB true from tick 5 to tick 8, pressedB should be true from tick 7 to tick 10, whatever rawA does. Hint: give each button its own region.

Show the starter chart
scchart TwoButtons {
input bool rawA, rawB
output bool pressedA, pressedB
initial state Released
if rawA go to MaybePressed
state MaybePressed
if !rawA go to Released
if 2 rawA do pressedA = true go to Pressed
state Pressed
if !rawA go to MaybeReleased
state MaybeReleased
if rawA go to Pressed
if 2 !rawA do pressedA = false go to Released
}
Show a solution
scchart TwoButtons {
input bool rawA, rawB
output bool pressedA, pressedB
region ButtonA {
initial state Released
if rawA go to MaybePressed
state MaybePressed
if !rawA go to Released
if 2 rawA do pressedA = true go to Pressed
state Pressed
if !rawA go to MaybeReleased
state MaybeReleased
if rawA go to Pressed
if 2 !rawA do pressedA = false go to Released
}
region ButtonB {
initial state Released
if rawB go to MaybePressed
state MaybePressed
if !rawB go to Released
if 2 rawB do pressedB = true go to Pressed
state Pressed
if !rawB go to MaybeReleased
state MaybeReleased
if rawB go to Pressed
if 2 !rawB do pressedB = false go to Released
}
}
DebouncePrediction

Diagram

Diagram of DebounceStates: Released (initial), MaybePressed, Pressed, MaybeReleased. Transitions: Released to MaybePressed: raw; MaybePressed to Released: 1: !raw; MaybePressed to Pressed: 2: 2 raw / pressed = true; Pressed to MaybeReleased: !raw; MaybeReleased to Pressed: 1: raw; MaybeReleased to Released: 2: 2 !raw / pressed = false.Debounceinput boolrawoutput boolpressedReleasedMaybePressedPressedMaybeReleasedraw1: !raw2: 2 raw/ pressed = true!raw1: raw2: 2 !raw/ pressed = false

Code

scchart Debounce {
input bool raw
output bool pressed
initial state Released
if raw go to MaybePressed
state MaybePressed
if !raw go to Released
if 2 raw do pressed = true go to Pressed
state Pressed
if !raw go to MaybeReleased
state MaybeReleased
if raw go to Pressed
if 2 !raw do pressed = false go to Released
}

The working debouncer, Debounce, with N = 3. What is pressed after ticks 6, 7, 12 and 13?

Inputs per tick. A value in grey was not set in that tick: it holds from an earlier one.

Tickraw
1false
2true
3true
4false
5true
6true
7true
8true
9false
10true
11false
12false
13false
Plays the scenario on the diagram, then marks each answer.
Explanation

pressed is false after tick 6 and true after tick 7; it is still true after tick 12 and false after tick 13. The bounce in tick 4 sent the chart back to Released, so the three true readings in a row are those of ticks 5, 6 and 7, and the press is accepted in tick 7. On the way back, the bounce in tick 10 returns to Pressed, and the release is accepted on the third false in a row, in tick 13.

A state has the transition if 3 raw go to Pressed, and is entered in tick 1. In ticks 2 to 6, raw reads true, false, true, false, true. When does the transition fire?
Show the answer

Answer C: In tick 6, the third tick with raw

A count delay counts the ticks in which its trigger holds while the state is active, consecutive or not, starting after the entry tick. raw is true in ticks 2, 4 and 6, so the transition fires in tick 6. To require readings in a row, leave the state on a disagreeing reading, so that the next entry restarts the count.

Key ideas

  • A bouncing contact produces several edges for one press. Debounce before detecting edges.
  • Accept a change only after N readings in a row. Pick N from the bounce time and the tick period; the user feels a delay of about N ticks.
  • A count delay counts ticks in which its trigger holds, not ticks in a row. Leaving and re-entering a state restarts its count delays, which is how the “maybe” states demand consecutive readings.
  • For large N, count in a variable declared outside the looping state.
  • Run the debouncer in its own region and let it emit a signal; other regions react to it in the same tick.

Next, Counters and timeouts: counting ticks to measure time, with count delays and with variables.

Report a mistake on this page