While a button is simple mechanically, there are many considerations to using them correctly in an embedded system. Like the LED API, the button API abstracts the hardware details from the firmware so the quantity, polarity, port, and pin number of the buttons does not have to be known. The driver also provides debouncing and simple functions to provide typical button operations.
Signal bouncing is the multiple make-and-break connections that occur in a mechanical switch before the signal is stable. The image below shows an oscilloscope capture of one of the development board buttons being released which clearly shows the intermediate signals that occur before the switch state stabilizes. Note the time scale is 50us per division, so if the first rising edge is when the button is first released, it’s 300us before the signal is stable.
Managing contact bounce is especially important for interrupt-driven button sensing as literally every signal transition (rising or falling edge) will cause an interrupt. If firmware doesn’t handle it properly, some very strange behavior can occur.
For the input shown above, the processor would see at least three (but probably four) rising edge inputs even though all the edges occurred in just 300us. A debouncing algorithm starts when the processor sees the first signal edge then simply waits a period of time to look at the signal state again when it should be stable to decide if there was a legitimate state change. It is not a difficult piece of code to write, but if you just want to use the buttons and not worry about this sort of thing, it’s nice if the API does all the work.
Note that the reset button on the EiE development boards is not included in this driver as it is purely a hardware function on the processor’s reset line.
There are no public button-specific types. However, the button names applicable to the target development board are required wherever a u32Button_ parameter is requested and are predefined below. BUTTON0 is on the left side of the development board.
ASCII development board: BUTTON0, BUTTON1, BUTTON2, BUTTON3
The following functions may be used by any application in the system at any time.
- bool IsButtonPressed(u32 u32Button_) – Returns TRUE if a particular button is currently pressed (and debounced).
- bool WasButtonPressed(u32 u32Button_) – Returns TRUE if a particular button was pressed since last time it was checked even if it is no longer pressed. ButtonAcknowledge() is typically called immediately after WasButtonPressed() returns TRUE to clear the button pressed state.
- void ButtonAcknowledge(u32 u32Button_) – Clears the New Press state of a button — generally always called after WasButtonPressed() returns TRUE to clear the “New Press” state.
- bool IsButtonHeld(u32 u32Button_, u32 u32ButtonHeldTime_) – Returns TRUE if a button has been held for u32ButtonHeldTime_ time in milliseconds.
Only WasButtonPressed() needs to be used slightly carefully. When a button is pressed, the driver sets a flag. Even if the button is released, the flag will remain set until it is cleared in firmware. This is called “latching” the signal. Latching ensures the signal is not missed. WasButtonPressed() checks this flag and ButtonAcknowledge() clears the flag. If it is not cleared, then the flag is still available for other tasks. This is very different than simply checking if a button is pressed or held as it ensures that a single button press can result in a single event – a very important capability that requires quite a few lines of code to implement manually, so it was included in the API.
Start with a clean branch of the Master firmware. Add the following examples in user_app1.c. Remember to try to write the code before looking at the solution provided.
First, make sure all LEDs are properly initialized in UserApp1Initialize():
In UserApp1SM_Idle(), add code to turn on the WHITE LED if BUTTON0 is currently pressed. Think about how this should work since the Idle state runs every millisecond (hint: there’s no trick here).
Toggle blinking on/off of the YELLOW LED when BUTTON1 has been pressed. ButtonAcknowledge() must be called after WasButtonPressed(). Again, make sure this will work considering that the Idle state runs every 1ms.
Turn on the CYAN LED if BUTTON3 has been held for 2 seconds. The LED should turn off once the button is released.
Now add code to do the following:
- Instantly turn on/off the PURPLE and BLUE LEDs corresponding to BUTTON1 and BUTTON2 (this is just a repeat of the first example in the module).
- Change the YELLOW blink rate to a variable value: if the YELLOW LED is currently on, then BUTTON2 will select the next fastest blink rate for the YELLOW LED. Use LED_1HZ, LED_2HZ, LED_4HZ, and LED_8HZ. Hint: you can NOT simply increment these ENUM values – they need to be specified explicitly.
For the variable speed blinking you might use an array to hold the blink rates and then a variable index value that will be adjusted to select different rates. A boolean variable bLedBlink can track if the LED is currently blinking and therefore if the rate can change.
The solution is presented below, but you are strongly encouraged to stop reading at this point and try to do the exercise yourself.
Define variables to use:
Adjust the code to access aeBlinkRate[ ] for the blinking rate:
Check if an update to the blinking rate should take place and select the next array value:
Build and test your code to ensure all of the buttons behave as expected.
The Button API is obviously very useful and having the input available immediately adds a new dynamic to what you can do with the development board. You should have all that you need to work successfully with the buttons now.
Part 1: Create a password system that is entered using BUTTON0, BUTTON1, and BUTTON2; press BUTTON3 to enter. The code should start in the “locked state” with the red LED on while the user enters the password with B0,B1,B2. Once B3 is pressed, blink red if the password is wrong, or blink green. Blink until any button is pressed and then return to the locked state. The code must support any length of password up to 10 buttons. The default password is B0,B1,B2 but make sure you design the code so this can be updated fairly easily (even though a code compile and download would be required).
Part 2: When the board starts, give the user 3 seconds to press BUTTON3 to enter a setting state where the user can create their own password. The chance to enter the setting state should be indicated by the yellow LED on. The normal “locked” state should be entered if BUTTON3 is not pressed. If BUTTON3 is pressed, blink the red and green LED while the user enters the new password. Only BUTTON0, BUTTON1, and BUTTON 2 can be used in the password. BUTTON3 completes entry. When a new password has been entered, go to the locked state.
Password setting should only be offered when the board starts. If the user does not enter a password, use the default password. Decide and document what happens if the user tries to enter more than 10 buttons when setting their password. Give your “user guide” document with your system to someone to test to see if it makes sense to them and confirm they can set and use the new password. Note that if you reset the board, the default password will be restored. You could use a held button combination to re-enter password setting mode if you wanted to.
[LAST UPDATE: 2023-OCT-26]