Lomont.org

(Also lomonster.com and clomont.com)

Button debouncing and pattern matching

Published

A robust, portable, extensible method for handling button presses when interfacing software and hardware.

Button debouncing and pattern matching

Chris Lomont Nov 2022

This is a collection of notes on designing and implementing button debouncing and pattern recognition. These notes were developed from many gadgets I’ve designed and built over many years.

The result is a very flexible button framework evolved over many projects.

Features include:

  • Arbitrary single- and multi- button patterns
  • Tunable performance
  • Easily portable
  • Buttons can pull low or high electrically

It consists of four pieces:

  1. a low level timing interrupt to sample button pins,
  2. per-button debouncers to smooth out ripples,
  3. button objects that handle single buttons and their patterns,
  4. and an optional larger multi-button pattern handler.

This article collects factors useful for designing such systems as well as explains design requirements, decision reasons, and tradeoffs in the one I made. If you want a quick jump to code check out my implementation on GitHub https://github.com/ChrisLomont/ButtonDebouncer.

Let’s start at the lowest level of button implementation: button noise debouncing.

Button debouncing

Button debouncing is the act of turning noisy electrical signals from button up and down switching into crisper up and down states for device usage. This is needed since button up/down is noisy, and naïve sampling/listening to pins results in lots of false signals and poor device behavior. The following image (Wikipedia, CC0) shows such noise.

img

Jack Gannsle has two very good articles [1,2] detailing a lot of background and advice. I recommend you start there if you really want debouncing hardware in depth.

Here I collect useful knowledge about debouncers:

  • Do not use pin change notifications, since the noise in the bouncing contacts do not meet interrupt needs on voltage or timing, and will lead to problems. Such code is brittle and prone to failure.
  • Do use a timer interrupt to poll the button states.
  • Design all pieces to use milliseconds (or faster), never use simple counters tied to device CPU performance or other non-time units. Delays and numbers in units of time, not in units of ’ticks’, keeps code portable and reusable.
  • Jack tested 18 switches, found most had bouncing under 10ms, but a few had much longer bounces up to 157 ms! Some twin switches varied by over a factor of 2 in timing. Others had asymmetric up and down bouncing times.
  • Solutions can be hardware or firmware (or a combination). A RC circuit is solid. I prefer software due to low cost, easier to tune if needed, easier to adapt in production firmware updates if parts change during production due to availability, cost, etc.
  • There are hardware debouncers, such as the MC14490 (Digikey here) which handles 6 buttons at a time. These are ~$5 as of 2022, quite pricey for something you can do in software.
  • Test your button!

Example articles and code

There are many articles about and examples of examples of debouncer code. Here are some

  • From Hackaday 2010, a massive list of code examples [3].
  • Another from Hackaday 2015, in two parts, with explanation and a derivation [4,5].
  • Adafruit [6].
  • Two commonly referenced pieces of code: tcleg’s implementation [7] of Jack Ganssle’s article and Kenneth Kuhn’s [8].

Unfortunately, none easily support the more complex button interactions I wanted. Most are difficult to move beyond the platforms they worked due to not being cleanly based on time, and none support a need I’ll detail below.

Timing

To design timing information, pick a unit time interval and stick to it. Make sure you can fit times into. Hardware needs sometime make it difficult to balance these needs. Tradeoffs:

  • The biggest problem is time rollover, and how you test or handle it. It can lead to quite subtle bugs in commercial devices.
  • Device architecture (RAM, efficient bit widths) may push you towards a counter size.
  • Using floating point variables for time is often difficult or not supported in hardware, making float computations slow and large.
  • Some time representation sizes:
    • A 16-bit unsigned integer using milliseconds rolls over in 1 minute.
    • A 32-bit unsigned integer using milliseconds rolls over in 49.7 days.
    • A 32-bit unsigned integer using microseconds rolls over around 72 minutes.
    • A 64-bit unsigned integer using nanoseconds rolls over in 584 years.

The main question for debouncing is what timing works? The goal is to pick two values: SAMPLE_MS (milliseconds between pin samples) and DEBOUNCE_MS (milliseconds of consistent reading for the debouncer to switch to button up or down). Some switches require different up and down debounce times since they are asymmetric in behavior, so you could have different DEBOUNCE_UP_MS and DEBOUNCE_DOWN_MS choices.

SAMPLE_MS tradeoffs:

  • lower values sample more often, which can give lower latency, but increases CPU load to handle updates.
  • higher values sample less often, which gives higher latency at the benefit of decreased CPU load.

DEBOUNCE_MS tradeoffs

  • higher values handle longer debouncing switches, but increases latency.
  • lower values decrease latency, but can fail on more noisy switches.

So for example, in the worst case, a debouncer will signal a button down state in (SAMPLE_MS + DEBOUNCE_DOWN_MS) ms after the user pressed down. To simplify this note I’ll use the same value in both directions.

RAM and ROM loads are somewhat independent of these timing choices, except some debouncers use more RAM for longer buffer lengths for longer times.

Depending on project needs, latency can be a big issue. A big factor is what usage you expect, and what users of your device expect. There is a huge literature on human factors like this, and different uses need different speeds to feel nice to users. Illustrating some overall button clicking times helps guide debouncer needs:

  • A kiosk can handle a 100 ms button down delay, and not feel too terrible.

  • A musical instrument needs vastly quicker button response (order of 1-5 ms).

  • Typical typist types 100 words/minute, so about 10 chars per sec, so need 100ms button clicks for this fast, which means debouncers far below this.

  • Top typists may reach in excess of 200 English words per minute.

  • Morse code: long click 187.5 ms, short 62.5 ms.

  • Tests of clicks per second (CPS) show a 3-6 average clicking rate. Some pro gamers surpass 14 CPS.

  • According to Microsoft’s MSDN website, the default timing in Windows is 500 ms for a mouse click .

So to handle a specific rate, your debouncer needs to process an up and down in well under that rate.

Common debouncer timing choices listed online includes:

  • For fast response, 1 to 5 ms works well.
  • 10-20ms is good
  • Overall user response of 50ms is usable, so debounce times (up and down) in the 5 to 50 ms range work.

Design summary shortlist

Summary list of choices and recommendations:

  1. Design with a timer interrupt polling inputs and providing debounce information.
  2. Use milliseconds (or faster, no need below microseconds) everywhere.
  3. Ensure interrupts only use atomic operations to communicate with user code to prevent rare data “tearing”.
  4. Decide on system needs
    1. Button needs: up/down only, single (maybe double and triple too) clicks, other click patterns
    2. Latency for needs drives interrupt and debounce bounds
  5. Pick the two debouncer two times in milliseconds. 1 ms interrupts and 5 ms debounce is a good start.
  6. Test
    1. System responsiveness and behavior
    2. CPU usage
  7. Iterate

Finally, leave room for error - parts often change in production or change in tolerances in usage, age, temperature, or climate.

Using a millisecond timer interrupt is a good place to place an elapsed system time counter. Putting the time in the fast interrupt instead of slower button pattern code allows more precise timing needed in many applications. Always remember that 32-bit unsigned integers overflow at 49 days, so design carefully.

Finally, the factor I mentioned above that I nearly always end up requiring: I want the debouncer to track the time a change happened, because higher button pattern requirements nearly always need to know how long something has been going on. So my debouncers create debounced up and down states and the time the change occurred. This has to be done atomically at the interrupt level to ensure user code doesn’t, for example, read a button as down that has just happened, but obtain a large time down from the previous up state. This type of error will cause all sorts of nightmares in debugging. Using volatile in C or C++ is completely the wrong choice! std::atomic in C++11 is the correct choice. Or use some platform specific hardware instructions to handle this - but it should be very high performance since it’s in an interrupt.

So this gets tricky.

Button click patterns

To make using buttons useful for many uses, the simple up/down from a debouncer is insufficient: you need to respond to user clicks, double clicks, and other patterns. This section provides some background and notation useful to understand and design such patterns.

Common click patterns

Events like clicks, double clicks, triple clicks, medium presses, long presses, and repeat clicks on hold down, can be defined as up/down patterns and time ranges. Here is an illustration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Click --------+  d1  +-----------       d1 = 1-click down time
              |      |   e1             e1 = 1-click end time
              +------+


Double Click --------+  d2  +------+  d2  +--------   d2 = 2-click down time
                     |      |  u2  |      |  e2       u2 = 2-click up time
                     +------+      +------+           e2 = 2-click end time (> u1, u2)


Triple Click --------+  d3  +------+  d3  +-------+  d3  +---------  d3 = 3-click down time
                     |      |  u3  |      |  d3   |      |  e3       u3 = 3-click up time
                     +------+      +------+       +-------           e3 = 3-click end time (> u1, u2, u3)


N-Click      --------+  dN  +------+  dN  +--       --+  dN  +---------  dN = N-click down time
                     |      |  uN  |      |    ...    |      |  eN       uN = N-click up time
                     +------+      +------+           +-------           eN = N-click end time (> u_....)


Repeat Click--------+  dR  #  d2+u2  #   d2+u2  #  ....       dR = repeat down time before first click
                    |                                         clicks # spaced by d2+u2 (double click rate)
                    +-----------------------------------


Medium Hold --------+   --- dM --->                      dM = time to hold before medium hold triggered
                    |      
                    +--------------------------


Long Hold    --------+  --- dL --->                      dL = time to hold before Long Hold triggered
                     |      
                     +------------------------

Clicks are defined with desired up and down patterns (and we’ll add time ranges [low, high] in a moment). Patterns often need an end time; for example, to have both single and double clicks supported, the up state end time for single click should be decently longer than the up time between clicks in a double click to separate them.

Note some of the times (such as the various d3 values in the triple-click) need not be the same. I have simplified somewhat. The proper way to select them is to have users do them, gather enough data across many users, and design your ranges to catch 95% or 99% or whatever percent of your userbase you require. But this is costly, so simply cheat and copy numbers from this document, which work pretty well in my experience.

Empirical data on good timings can be found in the paper [9]. Some useful facts and data I extrapolated for my uses:

  • Using the same timings for up/down in single, double, triple clicks is acceptable: people click in fairly regular timings.

  • However, people do click faster on double clicks than on clicks by a little bit.

  • Timings vary as a Gaussian across users and across time, which is great, since it allows you to design for wide user behavior without needing more complex time distributions.

    • Given the desired time above, a solid range is between 1 (~68%) and 2 (~95%) standard deviations from center. This is nice, since you can design using mean and standard deviation numbers for events, and design decent pattern timing ranges.
  • From the paper, and merging slight variation in components, I will simplify and use all down times di and up times ui as

    • mean 150ms
    • 120 standard deviation
    • This means to catch ~68% of clicks, use $150\pm \frac{120}{2}$ ms (range [90,210]) . To catch ~95%, use $150\pm120$ ms (range[30,270]).
    • This was for mouse style clicks. Press buttons may be different.
  • To complete a click and be sure you’re not starting a double click, you need the end timing e1 to be larger than the high end of the range for up states in the middle of a multi-click. Thus set the ei larger than 150+120, say at 300.

  • Medium press and long press times should be much longer, say 600 ms and 1500 ms

  • Repeat down should be longer than a click down, but can be close, so again, > 150+120, say 300 ms.

  • These choices are safe, but may seem slow. Tune as you see fit and what your hardware supports.

Complex click patterns

Some projects require more complex patterns, recognizing things including

  • Button A down, then button B down, A up, B up, etc.
  • “Chording”: where multiple buttons pressed (close) together
  • Knock patterns on a single button, for example the “Shave and a haircut” knock, for hidden features
  • The famous “Konami code” for game controllers UUDDLRLRBA

Complex patterns often need to know multiple button up and down history, timing history, use low and high time bounds, and do complex pattern recognition on these sequences. This can be costly in both memory for button history and cpu cost to evaluate patterns over and over.

If all you have is one or two button click patterns to recognize, coding them directly is reasonable. As things become more complex, hand crafting lots of timing and if/then code to recognize click types becomes extremely messy and brittle. Over many projects, hand coding each time is tedious and error prone.

There is a better way.

General pattern recognition

For supporting general patterns, there are competing goals as usual:

  • Low resource (CPU, RAM, ROM)
  • Flexibility and extensibility
  • Low latency
  • Ease of development and testing

A button pattern matcher should support:

  • Simple patterns: single click, double click, triple click, N-click, Repeat Click, Medium hold, Long hold
  • Timing ranges to handle knock patterns
  • Easily timing tunable for varying design needs
  • Able to handle single and multi-button patterns
  • Able to handle or require (nearly) simultaneous presses

To recognize long, complex patterns, one solution requires buttons to store a history of up down times. As patterns become complex, this history become more costly in RAM usage. Pattern matching over long patterns is computationally costly when done at button rates.

A much better solution is to use a finite state machine (FSM) for each pattern. A FSM most simply can store a current state index in RAM, and have the rest in ROM. However I’ll augment it with a little more to support button pattern matching needs.

A button pattern Finite State Machine

Designing a finite state machine capable of recognizing the simple patterns, with multiple button matching, leads to the following system. The high level is:

  • A Pattern Matcher is a collection of states with arrows from one state to another.
  • Each Arrow has a pattern to match before being selected
  • Each Arrow has 0 or more Actions to perform, such as notifying a click or other higher level need.

This design can be extended easily: if a pattern needs developed that the system cannot handle cleanly or easily, more pattern or action types can be added, and a relevant FSM designed.

The FSM is defined more precisely as:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Button pattern recognizer:
Buttons have a unique integer buttonId 1,2,3,...
A button has a list of Patterns to match
Each Pattern is represented as a Finite State Machine (FSM)
Fairly often (10-20ms) each button updates patterns

A higher level multi-button matcher can be built on the 
same kind of FSM

A Finite State Machine (FSM) is    
	A list of States 0,1,...N
	A state index pointing to the current state
	A number (0+) of integer counters used to track things in the pattern
	The FSM starts in state 0
	Each button timestep (20-30ms?) the button sends: up/down and time in that state
	   the current state tests arrows in order for a match
	   a match is executed by executing actions on the arrow, and updating the state index
	
A State is 
    An ordered list of Arrows, each leaving the State and pointing to a destination state
    The first matching Arrow is processed, allowing ordering Arrows in complex layers
    
An Arrow is
   A Match Pattern on how to accept the arrow
   A destination State this arrow points to on accept match
   A list of Actions to execute on accept match
   
An Action is:
   Two integers p and q
   An action to perform: 
      increment counter p by q   : [p] += q
      decrement counter p by q   : [p] -= q
      set counter p to q         : [p] = q
      set counter p to counter q : [p] = [q]

A Match Pattern is a list of accept macth requirements
  A button identity (1+) to match, or 0 for any button
  A button state Up/Down to match or ignore direction
  A a time requirement (and integer timeBound to test against):
    0 = ignore time match
    1 = time in button state <= timeBound
    2 = time in button state >= timeBound
    3 = time in FSM state >= timeBound

Operation:
1. FSM starts in state 0, assumes the button is up (can enforce in pattern too)
2. Each update, the current state has each arrow checked in order for a match
3. Any arrow match performs associated actions and updates the current state

To support this, the button needs to track
  - current up/down state
  - system elpased time when current state was set

Default FSMs for common buttons

Given the above, here are a few recognizers for common button patterns:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
Each matches any button

Times: 

UCL, UCH = 50, 250 ms  (up time length 50 to 250 ms)
UCL, UCH = 50, 250 ms  (down time length 50 to 250 ms)

MD = ?? medium time length down

FSM states:

Click-N (count N clicks in counters[0])
		// state 0
		if button up more than clickUpLowMs goto 1
        
        // state 1
        if button down more than clickDownLowMs clear hidden counter 1 and goto 2
        
        // state 2:
        if button down more than clickDownHighMs publish hidden clicks (copy 1 to 0) and goto 0
        if button up more than clickUpLowMs increment hidden counter 1 and goto 3
        
        // state 3
        if button down more than clickDownLowMs goto 2
        if button up more than clickUpHighMs publish hidden clicks (copy 1 to 0) and goto 0

Medium (or long) hold (counter 0 set to 1 on occurs, replace MD with LD for long hold)
		// state 0
		if button up more than clickUpLowMs goto 1
		
		// state 1
		if button down more than holdLengthMs increment click counter and goto 2
		
		// state 2
		if button up more than 0 goto 0
        
Repeat click: 
        // state 0 - ensure up some time
        if button up more than clickUpLowMs goto 1

        // state 1 - held long enough to trigger repeat
        if button down more than repeatClickDelayMs increment click counter and goto 2

		// state 2 - repeat click until button up
		if in state more than clickDelay increment click counter counter, goto 2 (last resets in state timer)
		if button up more than 0 ms goto 0

Code implementation

I have implemented this as a C++17 library at https://github.com/ChrisLomont/ButtonDebouncer. This section details design nuances and choices I made.

Features

Here is a list of features I obtained

  • User settable timings for debouncer sample and debounce rate
    • default 1 ms sampling, 5 ms debounce
  • Arbitrary button clicking patterns and timings
  • Buttons can pull high or low electrically on down state
  • Built in (yet optional) patterns (to show how to make the pattern Finite State Machines)
    • Single button: click-N, medium hold, long hold, repeat click
    • Multi button: 1 down, 2 down, 1 up, 2 up, in two different timing requirements
  • User settable timings for all pattern parameters
  • Portable: write 4 functions per platform and the rest works.
    • ElapsedMs gets elapsed system time in milliseconds
    • StartDebouncerInterrupt - start the timer interrupt
    • StopDebouncerInterrupt - stop the timer interrupt
    • SetPinHardware - do any per pin initialization, such as allocating/opening GPIO, setting pullups/downs, etc.
  • Add/remove buttons on the fly
  • Add/remove patterns on the fly
  • Example systems given
    • ESP32 using the ESP-IDF
    • Windows Win32 for easy poking and debugging
  • Small
    • 4 files, simply include a header in your code, link in one C++ file
    • ~800 lines total

Usage

Code usage (example follows):

  1. Implement 4 needed functions on your platform. See example below as well as the ESP32.cpp and Win32.cpp examples.
  2. Change button timings as needed. Defaults are usually ok.
  3. Make buttons.
    1. (Optional) Make/change patterns recognized
  4. (Optional) make multi-button pattern watcher
    1. Add patterns or use default ones for testing
  5. Process buttons every 20-30ms depending on latency needs
    1. Can check bool IsDown() as is
    2. Call button UpdatePatternMatches to update pattern watching
    3. Call int Clicks(int pattern) for each pattern index to find matches
  6. Destroy buttons when done to free up resources and remove interrupt.

Example code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// example button code, C++17
#include <memory> // make_shared
#include <string>
#include <iostream>

#include "Button.h"

using namespace std;
using namespace Lomont;

// some buttons to make later
ButtonPtr button1{ nullptr };
ButtonPtr button2{ nullptr };

// a pattern watcher to make later
ButtonMultiPatternPtr multiPattern{ nullptr };


// Step 0. be sure to have implemented the four system functions in ButtonHW namespace
/*
	// get elapsed time from the button system
	uint64_t ElapsedMs();

	// Start/Stop the button interrupt
	// interrupt should
	// 1 - get elapsed time in ms from Button::ElapsedMs
	// 2 - for each button in system (via buttonPtrs),
	//     1 - read pin, process whether pins high or low means button down
	//     2 - call base class Debouncer DebounceInput with isDown and elapsedMs
	void StartDebouncerInterrupt();
	void StopDebouncerInterrupt();

	// prepare hardware as needed for a new pin to watch
	void SetPinHardware(int gpioPinNumber, bool downIsHigh);
*/


// Step 1. add some buttons
// pin2 are usually GPIO numbers, downIsHigh is if your button physcially pulls to low voltage when down, or high voltage when down
void StartButtons(int pin1, bool pin1DownIsHigh, int pin2, bool pin2DownIsHigh)
{
	// 1. set any system timings first in ButtonTimings namespace in ButtonHelp.h
	//

	// 2. Make some buttons. Starts the timer interrupt. Button ids start at 1 internally and auto increment
	button1 = make_shared<Button>(pin1,pin1DownIsHigh);
	button2 = make_shared<Button>(pin2, pin2DownIsHigh);


	// 3. Make an optional multi-button pattern watcher if desired
	multiPattern = make_shared<ButtonMultiPattern>();
	// add default testing patterns, or create and add your own (see code)
	multiPattern->AddTestPatterns(); 
}

// Step 2. Sample often. 20-30 ms should be fine
void ProcessButtons()
{
	// default single click patterns
	const string singleClickNames[] = {"ClickN","Medium hold","Long hold","Repeat"};

	// update button patterns, see which patterns have occurred
	for (const auto& b : Button::buttonPtrs)
	{
		// can sample this if desired, no need for pattern updates
		// bool isDown = b->IsDown();

		// update any patterns being watched
		b->UpdatePatternMatches();

		// walk patterns to look for matches
		for (auto patternIndex = 0U; patternIndex < b->patterns.size(); ++patternIndex)
		{
			const auto clicks = b->Clicks(patternIndex);
			if (clicks > 0)
			{
				cout << "button (id:" << b->buttonId << ", pin:" << b->GpioNum() << ") saw ";
				cout << "click type " << singleClickNames[patternIndex] << " with count " << clicks;
				cout << endl;
			}
		}
	}


	// default multi click patterns
	const string multiClickNames[] = {
		"Slow ABAB", // 1 down, 2 down, 1 up, 2 up, slowly
		"Fast ABAB",  // 1 down, 2 down, 1 up, 2 up, quickly
	};

	// update and check optional multi button patterns
	if (multiPattern)
	{
		// update patterns
		multiPattern->UpdatePatternMatches();
		for (auto patternIndex = 0U; patternIndex < multiPattern->patterns.size(); ++patternIndex)
		{
			const auto clicks = multiPattern->Clicks(patternIndex);
				if (clicks > 0)
				{
					cout << "Multi button click type " << multiClickNames[patternIndex] << " with count " << clicks;
					cout << endl;
				}
		}
	}
}

// Step 3. Shut down when desired by calling all item destructors
void StopButtons()
{
	// releases shared pointers, last holder triggers destructor
	multiPattern = nullptr;
	button2 = nullptr;
	button1 = nullptr;
}

Goals

Here is a list of goals, some competing or even mutually exclusive, that I tried to obtain, roughly in order of importance, for reference. These drove the features above.

  • Button Up/Down debounced

  • Common patterns users expect supported as needed: Single Click, Double Click, Triple Click (Click-N ok for all), Medium hold, Long hold, Repeat click

  • Handles any number of buttons

  • Arbitrary single- and multi- button patterns for special project needs

  • Latency/error/noise tunable for needs from very low latency to very noisy buttons

  • Low latency so users don’t hate it or bang on buttons trying to get response

  • No (or low) errors

  • Easy to port to new systems and projects

  • Easily tunable for ease of use in new projects (timing of sampling, debouncing, pattern thresholds)

  • Time based for portability (milliseconds or finer grained)

  • Low resource usage as much as possible (RAM,ROM,CPU, interrupts)

  • Thread safety between interrupt and user code

  • Buttons physically can pull voltage high or low when down

  • Can add/remove buttons from system on the fly

  • Can add/remove patterns on the fly

Design Notes

Finally, here are a detailed system design notes:

The system is four main pieces:

  1. Timer Interrupt - on 1 ms ticks samples pins and tells debouncers the results
  2. Debouncers - debounce on 5 ms windows using an integrator, provide Up/Down and system elapsed ms at last change.
  3. Buttons - sample debouncers (20-30ms), applying info into patterns, to match single-button events
  4. Multi-button matcher - samples all debouncers (20-30ms), applying patterns, to match multi-button

Patterns take as inputs current button id, button up/down state, and time that state has persisted. Each runs a finite state machine to determine events of interest.

Design choices

  1. time as uint64_t in ms to avoid wraparound hassles and testing
  2. 1ms debouncer samples, 5 ms debounce
  3. Each time tick, the interrupt:
    1. Get elapsed milliseconds for system
    2. for each debouncer in the system:
      1. reads the physical input high/low
      2. converts to up/down as button requires
      3. calls debouncer->DebounceInput(isDown, elapsedMs)
  4. The debouncer gets an input from the interrupt and
    1. uses a local integrator to track up/down (if/then and +- per step)
    2. marks state changes atomically to prevent data tearing
      1. To make as portable as possible to many systems, this is done with an atomic pointer swap.
      2. All reads and writes go through two functions in Debouncer to enforce data consistency
      3. Any errors from systems not supporting features can be fixed by addressing all items at this choke point
  5. Buttons, updated every so often (20-30ms as user desires)
    1. obtains debouncer state atomically to prevent errors
    2. updates all patterns
  6. User then check each button, each pattern, for relevant clicks, other things.

Future Directions

Some ideas I was unable to implement so far, in no particular order

  1. “Compile” FSMs into byte code , export tables, to include in ROM for low memory systems

History

1
v0.5 - Nov 23, 2022 - Initial release

References

[1] http://www.ganssle.com/debouncing.htm

[2] http://www.ganssle.com/debouncing-pt2.htm

[3] https://hackaday.com/2010/11/09/debounce-code-one-post-to-rule-them-all/

[4] https://hackaday.com/2015/12/09/embed-with-elliot-debounce-your-noisy-buttons-part-i/

[5] https://hackaday.com/2015/12/10/embed-with-elliot-debounce-your-noisy-buttons-part-ii/

[6] https://learn.adafruit.com/make-it-switch/debouncing

[7] https://github.com/tcleg/Button_Debouncer

[8] http://www.kennethkuhn.com/electronics/debounce.c

[9] Alistair Edwards, “How many ways can you use one button? Timing data for button presses,” 2002.

Categories: Algorithms Numerical Math

Tags: Software

Comments

Comment is disabled to avoid unwanted discussions from 'localhost:1313' on your Disqus account...