FlashForth: Debouncing Buttons -

11 minute read

Where I discus debouncing buttons in Flashforth as well as indexing arrays.

Introduction

Elliott Williams had a great post several years ago as to the best solution to debouncing buttons. The technique worked extremely well in C, and I wanted to attempt it in Forth. It actually works better in Forth, as Forth easily handles the 16-bit data to de-bounce properly. I’ll work through the technique in FlashForth as well as spend some time at the end as to the execution timing of the technique.

Debouncing

I won’t spend much time as to why it is important to de-bounce buttons. Elliott does this in part 1 quite well. Simply put, electromechanical buttons will “bounce” up to several milliseconds prior to settling down to a down state. The precision of a microcontroller will count the bounces as a press, introducing a button which registers multiple “presses” instead of a single press. There are various ways to fix the problem, however, I have found Elliott’s solution (particularly, this one in Forth) to work quite well.

The algorithm is this: (in an ISR)

  1. Read the button pin
  2. Save its state in a history buffer
  3. Mask the history buffer to remove bounces
  4. Compare to a expected good press
  5. If equal, set a flag for a function to check

It does seem like a lot to do in an interrupt service routine (ISR). Without optimization, it takes 41.5 microseconds, however, that is with using a variable to connect the pin to a button. This allows maximum flexibility and I kept it as a general case, as it is easier to use in development. In practice, I would hard code the pin. If I needed additional speed, the first step (read pin) takes 20 microseconds, I would rewrite in assembly code.

Development

Having the ease of a Python Read-Evaluate-Loop-Print (REPL) in Forth eases development. A main concept of Forth is to write and debug words which you use to build up into an executable program. It’s a bit different than the top-down approach you take in other languages.

The first words, I want to write and test are those for the timer generating the 8ms interrupt and the ISR handling the interrupt.

Setup the Timer

As we know we will want to execute the above algorithm in an ISR, we need to setup a clock to interrupt every 8-10 milliseconds. I used a prior entry to help me do this. Using what I knew about configuring the PWM, I configured the clock scalar and counter to hit 8 milliseconds on the nose:

 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
\ Timer 0 - Used for debouncing buttons
\ Button de-bounce code based on Elliot Williams article
\ https://hackaday.com/2015/12/10/embed-with-elliot
\ -debounce-your-noisy-buttons-part-ii
\ Initialize Timer/Counter 0 to be a 2 button de-bouncer @125Hz
\ Counter increments every 8ms
\ TCCR0A [ COM0A1 COM0A0 COM0B1 COM0B0 0 0 WGM01 WGM00 ] = 00000001
\ TCCR0B [ FOC0A FOC0B 0 0 WGM02 CS02 CS01 CS00 ] = 00001100
\ WGM02 WGM00 => Mode 5, PWM, Phase Correct, TOP = OCRA
\ CS02 => scalar of 256
\ OCR0A = 250 ($fa)
\ Frequency = 16 x 10^6 / 256 / 250 = 250Hz
\ Counter performs another divide by 2 => 125 count/sec
\ Every interrupt incr by 1 and toggles D6, 2 toggles = 1 period
\ Oscilloscope shows D6 toggles every 8.01ms
\ See examples/button.fs for how to use

\ Timer 0 definitions from m328pdef.inc
#250 constant clock0_per \ clock period for comparison
$11 constant T0_OVF_VEC

\ Timer/Counter 0 - Debounce Clock (dbnce_clk) definitions
\ disable or enable clock
: dbnce_clk 1 timsk0 ;

\ Disable interrupt before removing the interrupt code
dbnce_clk disable

\ Debounce Clock ISR: Check button interrupt routine, runs every 8ms
: dbnce_clk_ISR
    right check_button
    left check_button

    \ (optional) used to confirm ISR execution rate
    \ if used be sure to adjust mask for same port bits being used for buttons
    \ mset uses a mask to set bits, so mask must include 
    \ bits used in same port, in this case: D5, D6 and D7
    BIT5 BIT6 or BIT7 or ddrd tog
;i

\ initialize Debounce Clock interrupt
: init_dbnce_clk  ( OCR0A -- )
  \ Store the interrupt vector
  ['] dbnce_clk_ISR T0_OVF_VEC int!

  \ Activate T/C 0 for an 8ms interrupt
  %0000.0001 tccr0a c!
  %0000.1100 tccr0b c!
  clock0_per ocr0a c!

  D7 out \ (optional) used to show ISR execution rate
  \ Activate Debounce Clock interrupt
  dbnce_clk enable
;

While this might appear to be a lot of code, it is a lot of comments. The code is highlighted.

Lines 44-56 are the initialization of the timer to create interrupts, every 8 ms.

There is also a word, test_timer ( n – ) which accepts a value to store at OCR0A which changes the TOP value of the counter. You can load buttons.fs then run 250 test_timer to see a 16 ms square wave on an oscilloscope. As the every up/down of the signal causes an interrupt, this results in an 8 ms interrupt timing.

Read a Pin

This is relatively simple, as it can be a single word read, that said, its best to use the pin in input pullup mode, meaning 5V is not-pressed and 0V is pressed, this results in negative logic. I added if/then to make it easier to understand, and in testing this only added .5 microseconds of execution time.

\ state? determines if button is down, (in pullup mode, so 0 is true)
: state? ( button -- f ) \ return True if button down
    button_pin 2@ read if 0 else 1 then 
;

I reflected the first step in the algorithm by naming the word state?. It returns the state of the pin via flag. All of my words, which return a flag, end with a question mark.

And as I said earlier, this word consumes 20 microseconds due to its flexibility in reading an array containing a pin. By hard coding the pin and using assembly language, this word might be reduced to 2-3 microseconds.

Save the State

The state of the pin is saved in a history buffer. As Elliot’s article outlines, by keeping track of the history, one can de-bounce the press. Several of the variables are tracked by an array, enabling you to setup several buttons to be de-bounced.

Creating Arrays

Here’s a short description as to work with indexing arrays in Forth. The FlashForth documentation references this method to index an array:

\ A defining word for creating arrays.
: mk-cell-array ( u --)
  create cells allot
  does> swap cells + 
;

\ Make an array and access it.
5 mk-cell-array my-cells  ok<$,ram>
3000 0 my-cells !  ok<$,ram>
3001 1 my-cells !  ok<$,ram>
3002 2 my-cells !  ok<$,ram>
1 my-cells @ . 3001  ok<$,ram>

I need two types of arrays, one, just like above for 16-bit data and one, for 32-bit data as in the pin references. The latter is created in a similar fashion, here’s the code for both types:

\ creates an indexed cell array as in "n name" with n as the index
: array: ( n "name" -- )
  create cells allot 
  does> swap cells + 
;

\ creates an indexed 2cell array as in "n name" with n as the index
: 2array: ( n "name" -- )
  create 2* cells allot 
  does> swap 2* cells + 
;

By using the button names as an index, as in right = 0 and left = 1, I’m able to reference an array using a easy description such as the history for the right button is right history or the left button pressed flag is left pressed. Its important to think about how to name things, so its easy to remember what the word does or how to reference it.

Using the Array

To setup each array, I use the appropriate word from above. The size of each array is defined by button_count, which allows me to quickly adjust the number of buttons to de-bounce. Once the array is defined, I am able to index it with the words left or right and the array name, as in history, pressed, button_pin, or times.

Each array needs to be initialized, so I create an initialization word for each array as well. And finally, I have a global initialization routine which sets up all the arrays properly. The global initialization ties the pin to a button and clears or sets the values in the buttons arrays, so it needs to be called for each button. This last step is executed as part of application which will be using the buttons.

 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
\ history tracks the state history of the button
ram button_count array: history
: history_init ( button -- ) history $ffff swap ! ;

\ pressed indicates if the button has been pressed, initialized to 0 (false)
ram button_count array: pressed
: pressed_init ( button -- ) pressed $0 swap ! ;
: pressed_true ( button -- ) pressed $ffff swap ! ;

\ times tracks the number of times the button has been pressed
ram button_count array: times
: times_init ( button -- ) times $0000 swap ! ;

\ button_pin attaches a pin to a button, use the Arduino pin references
ram button_count 2array: button_pin
: pin_init ( pin button -- ) button_pin  2! ;

\ ties pin to button and initializes all arrays associated with button
: init ( pin button -- )
    dup history_init
    dup pressed_init
    dup times_init
    rot rot 2dup pullup
    rot pin_init
;

\ store_state - stores the current button state in history
: store_state ( button f -- button ) \ store button state in history
    over history @  2* or  over history !
;

The code which stores the state is lines 28-30, all the code which precedes it, is there to setup the arrays or initialize the arrays to specific values.

Testing for a Press

Everything has been setup, our ISR is firing every 8ms and when it does, it checks each button. In that check, we’ve already examined reading the pin for its state then saving the state in a history array. It is this history value which helps us determine if the button is bouncing or it is pressed.

We are now in the set_pressed ( button – ) word. If it determines that the conditions are such that the button is pressed, it will set the pressed variable, reset the history to ready for the next press. If the button appears to be bouncing, it continues in the loop.

It uses down? (button – button f) to determine if the button is bouncing. It does so in 3 steps: (To ensure it is able to pass the button back to the calling word, it starts all of this off with a dup.)

  1. Get the history for the desired button - history @
  2. Mask it to remove the “glitches” - BTN_MASK and
  3. Compare to the signature press, to determine if “pressed” - BTN_DOWN =

The word pressed now uses this flag with an if/then, where if the flag is true, the button has been pressed and do the following, set the button’s pressed variable and clear the button’s history.

All of the above code is executed with the word check_button ( button – ). For each button, there is a button check_button word executed in the ISR.

An Application

A very simple and effective way to test the functionality is a button count application. It checks for button presses, if pressed, increments a counter and displays the number of times the button has been pressed. This is a good way to understand how to use the de-bounce functionality and to test if its working properly.

The application algorithm is simple, initialize buttons then start an infinite loop checking if a button has been pressed. If pressed, increment counter, reset pressed value and print the number of presses. It looks like this:

 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
\ Application code, in this case tracks number of times a button has been pressed

\ when button has been pressed, increment the variable times
: incr_times ( button -- )
    dup times @ 1+ swap times !
;

\ display the number of times a button has been pressed
: .times ( button -- )
    dup . ." button pressed "
    times @ .
    ." times" cr
;

\ when a button has been pressed, reset its pressed status
\ increment its times variable and print its times value
: button ( button -- )
    dup pressed_init
    dup incr_times
    .times
;

\ main application loop, continues to check for button presses
: btn_count ( -- )
    begin
        right pressed @ 
        if 
            right button
        then
        left pressed @ 
        if 
            left button
        then
    again
;

\ application: count_buttons - initialize then count button presses
: count_buttons
    init_dbnce_clk
    D5 right init 
    D6 left init
    cr btn_count

The application is a good example application. There are three sets of words, the application, main application loop and helper words. These are defined in reverse order, as they need to be defined prior to invoking them.

Helper words

These words are all button dependent and provide actions with the specified button

  • incr_times ( button – ) - increments the times variable for the specific button
  • .times ( button – ) - displays the number of times the button has been pressed
  • button ( button – ) - occurs when a button has been pressed, clears the pressed variable, increments the times counter and displays the count

Main Application Loop

This is main application sans the initialization code, btn_count. It is an infinite loop which continually checks the buttons for being pressed. If button has been pressed, it takes specific action. This loop could easily be adjusted for a myriad of application uses.

Application

This is the application, the word which I need to enter to make everything happen. Its called count_buttons and it provides the initialization, required to start the application including connecting a specific pin to the name of a button. Then it turns over running the application to the main application loop, btn_count.

Comments powered by Talkyard.