ESP32 Tutorial: Debouncing a Button Press using Interrupts

ESP32 Tutorial: Debouncing a Button Press using Interrupts

This the first of a new set of tutorials by SwitchDoc Labs on using the ESP32, the follow on chip to the popular ESP8266. In this article, we will discuss how to debounce a button press using Interrupts and timers. We are using the ESP32 on our new upcoming Kickstarter, the BC24.

Note that we are using the Arduino Core for the ESP32, not the native ESP SDK.   We feel strongly using the Arduino core and IDE is a better choice for a compatible design with the maker ecosystem.

What is the ESP32?

This chip has a dual processor core.   This can increase performance architecturally, for example,  because when one processor is handling communication, the other one is in charge of I/O control.  This feature will prevent a number of problems that the ESP8266 had with dealing with IO at the same times as communications.

The ESP32 has integrated WIFI, BLUETOOTH, DAC (Digital to Analog Converter – think transmitting waveforms such as audio), several 12 bit ADCs (Analog to Digital Converters), capacitive touch sensors.  And the good news is that maximum Power Consumption is almost the same as ESP8266 (but you have low power modes on the ESP32!).

Below a chart showing main characteristics of the ESP32 compared with the ESP8266:

Following is a block diagram of the ESP32.  We will be discussing these blocks in future tutorials.

Especially Interesting features (look at Power Down Mode!)

freeRTOS Addition to the ESP32 Operating System

The addition of freeRTOS (free Real Time Operating System) to the ESP32 is a major development.   We use freeRTOS on a number of projects at SwitchDoc Labs and we were excited to see this included in the default builds, especially with support for both cores in the ESP32.

There is a huge amount of documentation and examples for freeRTOS on the website.

What is an RTOS?

A real time operating system is the type of system which uses maximum time and resources to output exact and on the time result. There is no difference between the results when same problem run on different occasion on same machine. There is no late or early execution on that operating system and is done on fixed time as suggested.

For example, when we are using Windows or Linux, we can have multiple processes and applications open at the same time, and it seems that they are all running at the same time. For the end user, this is transparent.

The difference between a multi-tasking operating system (like linux or the Raspberry Pi) and an RTOS is that the timing of tasks and how often the are run are under control of the user

Debouncing Buttons on the ESP32

Bumbles bounce, Rudolph.  And so do mechanical buttons.  They really do.   The bigger the button, the more they bounce.   The harder you hit the button, the more bouncing it does.

Bouncing happens too quickly for human perception. When a switch or button is toggled, contacts have to physically move from one position to another. As the components of the switch settle into their new position, they mechanically bounce, causing the underlying circuit to be opened and closed several times.   Because your computer (even an 8MHz Arduino) is so fast, it will record multiple interrupts from the single button push.   You can see this on an oscilloscope trace as below:

The Debouncing Software

The following code shows how we are debouncing the buttons on the BC24 ESP32 based project as SwitchDoc Labs.   We are using a freeRTOS task for this button debouncing, but this could as easily be placed inside a loop() in normal Arduino code.

Here is a posting on debouncing switches on the Raspberry Pi.

Note the two defines at the top of the code for which GPIO pin on the ESP32 you are using and then the debounce time (10ms).  Some larger buttons (and reed switches such as with the SDL WeatherRack) may bounce up to about 100ms, so change this if you still have problems with bouncing.



// Debounced button task

#define BUTTONPIN 34

volatile int numberOfButtonInterrupts = 0;
volatile bool lastState;
volatile uint32_t debounceTimeout = 0;

// For setting up critical sections (enableinterrupts and disableinterrupts not available)
// used to disable and interrupt interrupts


// Interrupt Service Routine - Keep it short!
void IRAM_ATTR handleButtonInterrupt() {
  lastState = digitalRead(BUTTONPIN);
  debounceTimeout = xTaskGetTickCount(); //version of millis() that works from interrupt

// RTOS Task for reading button pushes (debounced)

void taskButtonRead( void * parameter)
  String taskMessage = "Debounced ButtonRead Task running on core ";
  taskMessage = taskMessage + xPortGetCoreID();

  // set up button Pin
  pinMode(BUTTONPIN, INPUT_PULLUP);  // Pull up to 3.3V on input - some buttons already have this done

  attachInterrupt(digitalPinToInterrupt(BUTTONPIN), handleButtonInterrupt, FALLING);

  uint32_t saveDebounceTimeout;
  bool saveLastState;
  int save;

  // Enter RTOS Task Loop
  while (1) {

    portENTER_CRITICAL_ISR(&mux); // so that value of numberOfButtonInterrupts,l astState are atomic - Critical Section
    save  = numberOfButtonInterrupts;
    saveDebounceTimeout = debounceTimeout;
    saveLastState  = lastState;
    portEXIT_CRITICAL_ISR(&mux); // end of Critical Section

    bool currentState = digitalRead(BUTTONPIN);

    // This is the critical IF statement
    // if Interrupt Has triggered AND Button Pin is in same state AND the debounce time has expired THEN you have the button push!
    if ((save != 0) //interrupt has triggered
        && (currentState == saveLastState) // pin is still in the same state as when intr triggered
        && (millis() - saveDebounceTimeout > DEBOUNCETIME ))
    { // and it has been low for at least DEBOUNCETIME, then valid keypress
      if (currentState == LOW)
        Serial.printf("Button is pressed and debounced, current tick=%d\n", millis());
        Serial.printf("Button is released and debounced, current tick=%d\n", millis());
      Serial.printf("Button Interrupt Triggered %d times, current State=%u, time since last trigger %dms\n", save, currentState, millis() - saveDebounceTimeout);
      portENTER_CRITICAL_ISR(&mux); // can't change it unless, atomic - Critical section
      numberOfButtonInterrupts = 0; // acknowledge keypress and reset interrupt counter

      vTaskDelay(10 / portTICK_PERIOD_MS);

      vTaskDelay(10 / portTICK_PERIOD_MS);