4. Button-Controlled LED
4.1 Basics of an Independent Button
An independent button is a simple input device that is widely used in various electronic devices for basic user interaction. Its working principle is usually based on a simple mechanical switch that triggers an action when pressed. Independent buttons come in many sizes, shapes, and colors, making them easy for users to identify and use.
4.2 Structure of an Independent Button
The main structure of an independent button includes: a button cap, a housing, a spring, contacts, a conductive plate, and pins. When the button is pressed, the conductive plate touches the contacts, forming a closed circuit.
4.3 Principle of an Independent Button
The principle of an independent button is mainly based on the relationship between mechanical contacts and electrical contacts. When the button is not pressed, the contacts are normally separated and the circuit is open. When the user presses the button, the contacts close under the action of the spring and the conductive plate, the circuit becomes connected, and the microcontroller can read the signal triggered by the button.
4.4 Debouncing
Because mechanical buttons can produce mechanical vibrations (similar to a spring) when they close and open, the switch state may change several times in a short period of time. This is known as button bouncing. Debouncing measures fall into two main categories: software debouncing and hardware debouncing.
- Software debouncing: mainly uses programming methods to set up a delay or timer to ensure that the button state is only read once during a certain period of time, avoiding the impact of bouncing on the program.
- Hardware debouncing: adds components such as resistors and capacitors in the button circuit to form an RC low-pass filter, smoothing the button signal and reducing the impact of bouncing.
4.5 Principle of an Independent Button Driver
The independent button driver allows the microcontroller to recognize the state of the button. Since the microcontroller can recognize high levels and low levels, most buttons are designed so that one terminal of the button is connected to a high level and the other to a GPIO; or one terminal of the button is connected to a low level and the other to a GPIO. By detecting whether the level on the pin connected to the button changes, you can know whether the button is pressed.
4.6 Schematic of an Independent Button
In the schematic of the development board, one terminal of the button is connected to the 3.3 V high level and to pin GPIO0 through the pull-up resistor R14, while the other terminal is connected to GND (low level). The button circuit of the development board can be simplified into the diagram below.
When the development board is powered on, the GPIO0 pin will be at a high level because of the pull-up resistor R14. Therefore, when the button is not pressed, GPIO0 is at high level by default. When the button is pressed, the button is closed and GPIO0 is connected to GND through the button, so GPIO0 becomes low level.
4.7 Independent Button Driver Flow
Using the button can be broken down into the following steps:
- Configure the GPIO as input mode On the ESP32, all IO ports are controlled through GPIOs, so we need to configure the GPIO we want to use as input mode in order to detect the state of the button. For example, if we want to use
GPIO0to detect the button state, we can configure it as input mode as follows:
gpio_config_t io_conf; // Define the GPIO initialization structure
io_conf.mode = GPIO_MODE_INPUT; // Input mode
io_conf.pull_up_en = GPIO_PULLUP_ENABLE; // Enable pull-up
io_conf.pin_bit_mask = (1ULL<<GPIO_NUM_0); // Set the pin to GPIO0
gpio_config(&io_conf); // Write the above configuration to the registers2
3
4
5
Here, pin_bit_mask represents the bit mask of the enabled GPIO pins. Because the ESP32 supports configuring multiple GPIO pins at once, a bit mask is used to specify which GPIO pins to configure. The main change is .mode = GPIO_MODE_INPUT;, which changes the GPIO mode to input. The rest is the same as in the LED chapter. 2. Poll the button state After configuring the GPIO, we need to continuously poll the button state to capture button events and execute the corresponding actions. We can use the following code to obtain the state of the GPIO0 pin:
if(gpio_get_level(0) == 0)
{
// If the logic level of the GPIO0 pin is 0, the button is pressed
// Execute the corresponding action
}2
3
4
5
gpio_get_level() is a function in the ESP-IDF used to get the logic level of a specified GPIO pin. The function prototype is as follows:
int gpio_get_level(gpio_num_t gpio_num);The gpio_num parameter is the GPIO pin number whose level you want to read, of type gpio_num_t. The function returns an int representing the logic level of the specified GPIO pin. A return value of 0 indicates that the pin is at low level (logic level 0), and a return value of 1 indicates that the pin is at high level (logic level 1). Notes:
- Before using
gpio_get_level(), you need to configure the corresponding GPIO pin as input mode; otherwise, you may get an incorrect level value. - Before reading the GPIO level, you need to know whether the GPIO has a pull-up or pull-down resistor configured, so that you can read the level correctly when the button is not connected. For example, if a pull-down resistor is configured, the statement
if(gpio_get_level(0) == 0)cannot make a correct decision because the pull-down resistor keeps the pin at low level all the time.
- If there is button bouncing, apply debouncing measures Buttons need to be debounced because they may produce bouncing when pressed or released. When we press a mechanical button, the contact between the mechanical parts may vibrate due to various factors, which causes the circuit connected to the button to toggle several times in a short period of time. This can make the system mistakenly think that the button has been pressed multiple times, causing repeated execution of button operations or interfering with other commands. The purpose of debouncing is to filter out the bouncing signal instantly when the button signal is generated, so that only one valid button signal is recognized and responded to, avoiding misoperation. To solve the button bouncing problem, we can implement button debouncing using software or hardware solutions. In a software solution, by setting a certain debouncing time and processing logic, the bouncing changes when the button is pressed and released are smoothed into one valid signal.
// If the button is pressed, GPIO0 becomes low level
if( gpio_get_level(0) == 0 )
{
// Delay 100 ms to wait out the button bouncing
vTaskDelay(100 / portTICK_PERIOD_MS);
// Turn on the LED; the LED pin is GPIO48
gpio_set_level(48, 0);
}2
3
4
5
6
7
8
The above is the simplest delay-based debouncing method. There are many other debouncing methods.
- State-judging debouncing: when the button signal changes, record the button state and continue detecting the button state for a period of time. The button trigger is only considered valid after the state has remained stable for that period of time. This can be done by comparing the results of multiple samples.
- Sliding-window debouncing: use a sliding window to record the recent history of button trigger states. Each time the button state is detected, compute the average or majority vote of button states within the window. Only when the average or majority vote indicates that the button is stable is the trigger considered valid.
- Edge-triggered debouncing: detect the rising edge and falling edge of the button signal (also called edge-triggered). The button trigger is only considered valid if no other edge trigger is detected within a period of time after detecting an edge trigger. In a hardware debouncing solution, components such as RC circuits and filters are added to the circuit to filter out the bouncing signal. This reduces the bouncing — note that it reduces it, not eliminates it.
4.8 Button-Controlled LED Verification
Set the LED pin to output mode and the button pin to input mode. When the button is pressed, first debounce it and then check again whether it is pressed. After confirming that the button is pressed, perform an LED state change. (For LED-related code, see the LED chapter.) Create two files, bsp_key.c and bsp_key.h. Add the header file path.
In bsp_key.c, write the following code:
#include "bsp_key.h"
// Configure the pin register
#define GPIO_INPUT_PIN_SEL (1ULL<<KEY_PIN)
/**
* @brief Button pin initialization
* @param None
* @return None
*/
void KeyGpioConfig(void)
{
// Initialize the GPIO configuration structure to zero
gpio_config_t io_conf = {};
// Disable interrupts
io_conf.intr_type = GPIO_INTR_DISABLE;
// Set the input pin
io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
// Set input mode
io_conf.mode = GPIO_MODE_INPUT;
// Enable the pull-up resistor
io_conf.pull_up_en = 1;
// Disable the pull-down resistor
io_conf.pull_down_en = 0;
// Configure the GPIO with the given settings
gpio_config(&io_conf);
}
/**
* @brief Read the level state of the button pin
* @param None
* @return 0 = button pressed 1 = button not pressed
*/
bool GetKeyValue(void)
{
// If the button state is 0
if( gpio_get_level(KEY_PIN) == 0 )
{
// Delay-based debouncing; using this delay requires the corresponding header
vTaskDelay(100 / portTICK_PERIOD_MS);
// If the button state is still 0, the button is really pressed
if( gpio_get_level(KEY_PIN) == 0 )
{
return 0;
}
}
return 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
In bsp_key.h, write the following code:
#ifndef _BSP_KEY_H_
#define _BSP_KEY_H_
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// Set the button pin
#define KEY_PIN 0
/**
* @brief Button pin initialization
* @param
* @return
*/
void KeyGpioConfig(void);
/**
* @brief Read the level state of the button pin
* @param None
* @return 0 = button pressed 1 = button not pressed
*/
bool GetKeyValue(void);
#endif2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
In main.c, write the following code:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "bsp_led.h"
#include "bsp_key.h"
void app_main(void)
{
int cnt = 0;
// LED initialization
LedGpioConfig();
// Button initialization
KeyGpioConfig();
while(1) {
// Because the ESP32S3 runs in RTOS mode, a delay must be added in the infinite loop
// to allow it to run normally
vTaskDelay(20 / portTICK_PERIOD_MS);
// If the button has been pressed
if( GetKeyValue() == 0 )
{
// Toggle the LED state. The LED pin is GPIO48
gpio_set_level(LED_PIN, cnt = !cnt);
}
}
}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
WARNING
📌 Why do we need to add a delay to the infinite loop?
In the ESP-IDF environment on the ESP32, an infinite loop itself does not require a delay. An infinite loop is a loop structure in the program whose condition is always true, so the program keeps executing in the loop and cannot continue to other code afterward. However, to prevent an infinite loop from putting too much load on the system, the core code starts a watchdog to monitor the program's running state. If the program occupies the CPU for too long in a task or infinite loop without feeding the dog (that is, resetting the watchdog timer) in time, the watchdog timer will time out and trigger a system reboot. Therefore, we usually need to add a delay to release the CPU. This is meant to give the system some idle time to handle other tasks, or in other words, to reduce the system load by adding a delay. Adding a delay in an infinite loop avoids over-occupying the CPU. This is especially important on single-core systems. Even if the current task cannot continue, adding a delay frees up time slices for other tasks to run, ensuring a relatively smooth running of the system. The following example code shows an infinite loop with a delay:
while(1) {
// Do some work
vTaskDelay(10 / portTICK_PERIOD_MS); // Delay 10 ms
}2
3
4
5
This code uses the FreeRTOS vTaskDelay() function, which pauses the current task for a specified time and yields the CPU. By adjusting the delay time appropriately, you can control the execution rate of tasks within an infinite loop to avoid over-occupying the CPU.
4.9 Button-Controlled LED Effect
Pressing the button turns the LED on, and pressing it again turns it off, repeatedly.