Button debounce issue

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.

code tags are with back ticks not

but kudos for trying

āžœ I fixed it for you

Is the pin GPIO9 really configured as INPUT_PULLUP?

try with one of the numerous button library such as Button in easyRun or OneButton or Toggle or EasyButton or Bounce2, ... to see if the button works.

It would help a lot if you
a) fix the code tags
b) provide a complete minimal sketch that illustrates the problem
c) show a schematic

Thanks a lot! This is my first time posting in a forum, and I really appreciate your help. I'll try everything you've listed!

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.

Okay, i’d try this also. Thank you so much!

  • Always show us good images of the actual circuit wiring.

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.

try a button based on Hall effect - tends to remove the problem of bounce one gets with mechanical switches

1 Like

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.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.