Setting Up the Push Button Control
Now that the LED is functional, we’ll take care of the push button. As a reminder, the button is connected to GPIO 22 of the ESP32, which is also associated with a pull-up resistor to prevent the signal from floating.
The bouncing problem
When using a push button, you must be aware that it is an imperfect mechanical device and very unstable given the time scale at which electrical phenomena occur.
To you and me, when you press a pushbutton, it goes from an open circuit to a closed one, and that single transition defines the button “press”. But the reason that it’s so clear-cut to us is because we perceive things pretty slowly. To a microcontroller, which is able to test a button’s state as often as a few million times per second, it’s not on-off at all.
When you physically press a normal pushbutton, two pieces of metal come into contact with each other. If these two tiny sheets of metal aren’t perfectly flat or perfectly aligned (and they’re not) then they can make and break contact a few times before being firmly enough squished together that they’re always conducting. To a microcontroller the button appears to be pressed many times for extremely short durations, when you think you’ve just pressed it once. Debouncing is all about making sure that you and the microcontroller agree about when a button push or release event happened.
Contact bounce can be entirely eliminated with a little bit of hardware. You will find many examples that deal with this issue on the Net. But what I’m going to show you here allows you to simply solve the problem with a bit of code.
Implementation of a Button component
Let’s start by setting the pin for reading the status of the button:
// ----------------------------------------------------------------------------
// Definition of macros
// ----------------------------------------------------------------------------
#define LED_PIN 26
#define BTN_PIN 22
As you can see from the figure above, the bounce problem occurs over a short period of time during which the signal is unstable. We will therefore have to inhibit the signal over a period that covers this instability. To do this, let’s define the waiting time beyond which we will be able to take the signal into account without risking a reading error:
// ----------------------------------------------------------------------------
// Definition of global constants
// ----------------------------------------------------------------------------
const uint8_t DEBOUNCE_DELAY = 10; // in milliseconds
This delay is set empirically and depends on your button. If you observe any instability later, you can simply extend this time.
As for the Led
component, we are going to create a data structure to handle the button:
// ----------------------------------------------------------------------------
// Definition of the Button component
// ----------------------------------------------------------------------------
struct Button {
// state variables
uint8_t pin;
bool lastReading;
uint32_t lastDebounceTime;
uint16_t state;
};
pin
defines the input pin to which the button is connectedlastReading
defines the last read value of the signal (HIGH
orLOW
)lastDebounceTime
defines the last time, in milliseconds, at which the last reading was performedstate
is a 16-bit integer that defines the state of the button as follows:
A 16-bit integer is used to encode values between 0 and 65535 (between 0x0000 and 0xffff). This simple modeling makes it possible to distinguish two instantaneous pressed
and released
states, as well as a durable held
state whose value tells us the number of loop()
cycles during which the button has been held down.
As for the Led
component, we will also define some methods on the Button
component to easily determine its state:
// ----------------------------------------------------------------------------
// Definition of the Button component
// ----------------------------------------------------------------------------
struct Button {
// state variables
uint8_t pin;
bool lastReading;
uint32_t lastDebounceTime;
uint16_t state;
// methods determining the logical state of the button
bool pressed() { return state == 1; }
bool released() { return state == 0xffff; }
bool held(uint16_t count = 0) { return state > 1 + count && state < 0xffff; }
};
The Button
component now has methods that can inform us directly about its pressed
, released
or held
state. All these states can be deducted from the integer value of the state variable state
, according to the diagram above.
Note in particular that the held()
function accepts an optional count
argument to specify the number of cycles of the loop()
function during which the button must be held down for the true
value to be returned.
We still have to define a read()
method to be able to read the physical state of the button and determine the value of the state
variable:
// ----------------------------------------------------------------------------
// Definition of the Button component
// ----------------------------------------------------------------------------
struct Button {
// state variables
uint8_t pin;
bool lastReading;
uint32_t lastDebounceTime;
uint16_t state;
// methods determining the logical state of the button
bool pressed() { return state == 1; }
bool released() { return state == 0xffff; }
bool held(uint16_t count = 0) { return state > 1 + count && state < 0xffff; }
// method for reading the physical state of the button
void read() {
// reads the voltage on the pin connected to the button
bool reading = digitalRead(pin);
// if the logic level has changed since the last reading,
// we reset the timer which counts down the necessary time
// beyond which we can consider that the bouncing effect
// has passed.
if (reading != lastReading) {
lastDebounceTime = millis();
}
// from the moment we're out of the bouncing phase
// the actual status of the button can be determined
if (millis() - lastDebounceTime > DEBOUNCE_DELAY) {
// don't forget that the read pin is pulled-up
bool pressed = reading == LOW;
if (pressed) {
if (state < 0xfffe) state++;
else if (state == 0xfffe) state = 2;
} else if (state) {
state = state == 0xffff ? 0 : 0xffff;
}
}
// finally, each new reading is saved
lastReading = reading;
}
};
Here we go, the Button
component is ready. Now let’s see how to use it.
How to use the Button component
Let’s start by defining a Button
object:
// ----------------------------------------------------------------------------
// Definition of global variables
// ----------------------------------------------------------------------------
Led led = { LED_PIN, false };
Button button = { BTN_PIN, HIGH, 0, 0 };
Then let’s initialize the reading pin of the button:
// ----------------------------------------------------------------------------
// Initialization
// ----------------------------------------------------------------------------
void setup() {
pinMode(led.pin, OUTPUT);
pinMode(button.pin, INPUT);
}
It is now very easy to switch the LED on and off with the button. For example, suppose you want to turn the LED on while the button is held down, and off when it is released:
// ----------------------------------------------------------------------------
// Main control loop
// ----------------------------------------------------------------------------
void loop() {
button.read();
if (button.held()) led.on = true;
else if (button.released()) led.on = false;
led.update();
}
Let’s compile and upload the program and see what happens:
Now, instead, let’s make sure that every time the button is pressed, the LED status is reversed :
// ----------------------------------------------------------------------------
// Main control loop
// ----------------------------------------------------------------------------
void loop() {
button.read();
if (button.pressed()) led.on = !led.on;
led.update();
}
And here’s what happens now:
You can update the baseline project by going to the remote-control-with-websocket
directory and executing the following git
command:
git checkout v0.2
You can also download the updated main.cpp
file and replace yours in your own project:
Here is the complete code for this chapter:
/**
* ----------------------------------------------------------------------------
* ESP32 Remote Control with WebSocket
* ----------------------------------------------------------------------------
* © 2020 Stéphane Calderoni
* ----------------------------------------------------------------------------
*/
#include <Arduino.h>
// ----------------------------------------------------------------------------
// Definition of macros
// ----------------------------------------------------------------------------
#define LED_PIN 26
#define BTN_PIN 22
// ----------------------------------------------------------------------------
// Definition of global constants
// ----------------------------------------------------------------------------
const uint8_t DEBOUNCE_DELAY = 10; // in milliseconds
// ----------------------------------------------------------------------------
// Definition of the LED component
// ----------------------------------------------------------------------------
struct Led {
// state variables
uint8_t pin;
bool on;
// methods
void update() {
digitalWrite(pin, on ? HIGH : LOW);
}
};
// ----------------------------------------------------------------------------
// Definition of the Button component
// ----------------------------------------------------------------------------
struct Button {
// state variables
uint8_t pin;
bool lastReading;
uint32_t lastDebounceTime;
uint16_t state;
// methods determining the logical state of the button
bool pressed() { return state == 1; }
bool released() { return state == 0xffff; }
bool held(uint16_t count = 0) { return state > 1 + count && state < 0xffff; }
// method for reading the physical state of the button
void read() {
// reads the voltage on the pin connected to the button
bool reading = digitalRead(pin);
// if the logic level has changed since the last reading,
// we reset the timer which counts down the necessary time
// beyond which we can consider that the bouncing effect
// has passed.
if (reading != lastReading) {
lastDebounceTime = millis();
}
// from the moment we're out of the bouncing phase
// the actual status of the button can be determined
if (millis() - lastDebounceTime > DEBOUNCE_DELAY) {
// don't forget that the read pin is pulled-up
bool pressed = reading == LOW;
if (pressed) {
if (state < 0xfffe) state++;
else if (state == 0xfffe) state = 2;
} else if (state) {
state = state == 0xffff ? 0 : 0xffff;
}
}
// finally, each new reading is saved
lastReading = reading;
}
};
// ----------------------------------------------------------------------------
// Definition of global variables
// ----------------------------------------------------------------------------
Led led = { LED_PIN, false };
Button button = { BTN_PIN, HIGH, 0, 0 };
// ----------------------------------------------------------------------------
// Initialization
// ----------------------------------------------------------------------------
void setup() {
pinMode(led.pin, OUTPUT);
pinMode(button.pin, INPUT);
}
// ----------------------------------------------------------------------------
// Main control loop
// ----------------------------------------------------------------------------
void loop() {
button.read();
if (button.pressed()) led.on = !led.on;
led.update();
}