I am working on an ESP32-C3 project where a button is connected to GPIO9 and used for power control and factory reset. However, I am experiencing an issue where the button randomly triggers itself, even when not pressed.
Despite adding debounce logic, the issue persists. The ESP32-C3 board also uses GPIO2 for power management, which might be influencing behavior.
I would appreciate any suggestions on debouncing techniques or handling GPIO9/GPIO2 correctly to prevent false triggers.
Hardware Setup
Board: ESP32-C3
Button Pin: GPIO9 (Configured as INPUT_PULLUP)
Power Control Pin: GPIO2 (Keeps ESP32 powered on)
Debounce Logic: Software debounce with millis(), 50ms delay
Issue: Button "presses itself" without user input
void check_reset_and_power_button() {
static bool lastButtonState = HIGH;
static unsigned long lastDebounceTime = 0;
static unsigned long pressStartTime = 0;
const unsigned long debounceDelay = 50; // 50ms debounce time
bool currentButtonState = digitalRead(DEV_FACTORY_RESET_PIN);
// Debounce logic
if (currentButtonState != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay && currentButtonState != lastButtonState) {
if (currentButtonState == LOW) {
pressStartTime = millis();
Serial.println("Button PRESSED.");
}
if (currentButtonState == HIGH) {
unsigned long pressDuration = millis() - pressStartTime;
Serial.print("Button HELD for: ");
Serial.print(pressDuration);
Serial.println(" ms");
if (pressDuration >= BTN_FACTORY_RESET_TIME) {
Serial.println("Factory reset triggered!");
device_config.valid = 0;
save_device_config();
startProvisioning();
}
else if (pressDuration >= 5000) {
Serial.println("Powering OFF the device...");
power_off_device();
}
else if (pressDuration >= 1000) {
if (!devicePoweredOn) {
Serial.println("Powering ON the device...");
power_on_device();
}
}
}
lastButtonState = currentButtonState;
}
}
What I Have Tried
Confirmed digitalRead(GPIO9) detects button presses correctly. Added INPUT_PULLUP to keep GPIO9 HIGH when unpressed. Implemented debounce logic with a 50ms delay. Ensured GPIO2 is properly handled for power control.
Issues Observed
Button sometimes triggers itself, even when not pressed. Power control using GPIO2 might be affecting GPIO9 stability. Occasional multiple button presses detected due to bouncing.
FWIW, I was unable to get the code to work at all without modification. With a few changes, it works more like I expect. I am not sure overloading 1 button with 3 functions is the best UI, without feedback. I can see it being easy to do factory reset instead of power off.
Anyway, here is my version:
#define DEV_FACTORY_RESET_PIN 9
#define BTN_FACTORY_RESET_TIME 250
bool devicePoweredOn;
void check_reset_and_power_button()
{
static bool lastButtonState = HIGH;
static bool debouncedState = HIGH;
static unsigned long lastDebounceTime = 0;
static unsigned long pressStartTime = 0;
const unsigned long debounceDelay = 50; // 50ms debounce time
bool currentButtonState = digitalRead(DEV_FACTORY_RESET_PIN);
// Debounce logic
if (currentButtonState != lastButtonState)
{
lastDebounceTime = millis();
}
// if ((millis() - lastDebounceTime) > debounceDelay && currentButtonState != lastButtonState)
if ((millis() - lastDebounceTime) > debounceDelay)
{
if (debouncedState == HIGH)
{
if (currentButtonState == LOW)
{
// HIGH to LOW transition
pressStartTime = millis();
Serial.println("Button PRESSED.");
}
}
else // (debouncedState == LOW)
{
if (currentButtonState == HIGH)
{
// LOW to HIGH transition
unsigned long pressDuration = millis() - pressStartTime;
Serial.print("Button HELD for: ");
Serial.print(pressDuration);
Serial.println(" ms");
// test in reverse order of duration
if (pressDuration >= 5000)
{
Serial.println("Powering OFF the device...");
//power_off_device();
}
else if (pressDuration >= 1000)
{
if (!devicePoweredOn)
{
Serial.println("Powering ON the device...");
//power_on_device();
}
}
else if (pressDuration >= BTN_FACTORY_RESET_TIME)
{
Serial.println("Factory reset triggered!");
//device_config.valid = 0;
//save_device_config();
//startProvisioning();
}
}
}
debouncedState = currentButtonState;
}
lastButtonState = currentButtonState;
}
void setup()
{
pinMode(DEV_FACTORY_RESET_PIN, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("\nStarted\n");
}
void loop()
{
check_reset_and_power_button();
}
Edit: it seems I made a bad guess at BTN_FACTORY_RESET_TIME, so one of my changes is unnecessary.
You can try to add an additional external pullup resistor. Maybe try 2,2K. The internal value may be too high for reliable operation in your environment.
Hi, just my 2ā¬c worth. Can you use a normally closed button?
I have just read a piece on limit switches, & they suggest using NC so that the input doesn't pick up noise (I feel I should have known about that a long time ago)..
I have used NO contacts (keypads, microswitches) in projects, & never had a problem with bounce. As soon as a contact was detected, the sofware changed to another state, i.e. went off to do something else, not hanging about to see if the contact was stable. There was a 125ms delay in the main loop used for checking contacts & counting down non-zero timers.
E.g. in a program for a security cash drawer cabinet, once a 3- or 4-digit code had been entered, the state for the selected drawer (device) was set to Wait (pre-set seconds). On time-out, the state for the device was set to Solenoid for 15 seconds, & the drawer solenoid energised. Etc. etc.
I first came across using states when learning about a Burroughs TC500 terminal computer, way back in 1968.
Perhaps electromagnetic interference from outside is making the button wiring experience transient spikes?
if the wires are long enough and not shielded, it's possible.
The simplest way to defeat not large interference is to have the button wires normally HIGH and go LOW when pressed, which is recommended here (pin mode INPUT_PULLUP; LOW means pressed, HIGH means up) since you can hook the button straight to GND without needing a resistor... as opposed to wiring a resistor between the button and GND as a pulldown when the other side of the button goes to power/VCC/5V or 3.3V.
The next hardware thing is to use shielded wires.
In software you could make a debounce that only detects a change in state and simply ignores the pin for often 20ms but may vary. That would false easily.
I have debounce where each button has the last 8 digitalReads of the button pin as a byte variable...
but it only works in non-blocking sketches.
It's possible to detect a pin change by interrupt or by polling the pin, then use delay(xx) and read state again to determine end state. The delay should be longer than any false will last. This blocks execution during the delay.
If the pin is floating in a charging environment, run it HIGH with INPUT_PULLUP as a hardware solution.