What is the best practice for push-button event tracking/handling without any hardware debouncing circuit

User avatar
Gfast2
Posts: 182
Joined: Fri Aug 11, 2017 1:52 am

What is the best practice for push-button event tracking/handling without any hardware debouncing circuit

Postby Gfast2 » Thu May 30, 2019 2:37 pm

Hi ESP32:

I think ESP32 is really cool & powerful. Thanks to its dual-core hardware & RTOS(much important then the previous one) I successfully made really something I've been pround of and would like to use. (And may be later I print the circuit and sells it, because my project is really awesome & useful I think)

My humboldt stone in the project right now is something about "user interaction" ;) -> How to deal with a push-button!
Right now I use the single push-button in this project for two different thing: one click: make a gps coordinate snapshot save to sd card, a long press button click for different display mode change. Everything works most of the time. But sometimes for unknown reason it just won't do anything or even worse in some rare chance it will even crash the whole RTOS.
This is the original POST describe how the project looks like. Here is the part that I read the button:

Code: Select all

int btn_old = 0;
  int btn_now = 0;
  uint32_t btn_long_press_old = 0;
  // Flag marking if btn press a long press to preventing long press trigger a
  // short btn press event at the same time.
  int longPressFlag = false;
  while(1) {
    btn_now = gpio_get_level(BTN);
    if(btn_now != btn_old){
      if(btn_now == 1){ // btn pressed down
        ESP_LOGI(TAG, "BTN get pressed down.");
        btn_long_press_old = xTaskGetTickCount();
        longPressFlag = false;
      } else { // btn released
        ESP_LOGI(TAG, "BTN get released.");
        // When button get released in <1 second, do the gps coordinate snapshot
        if(abs(xTaskGetTickCount()-btn_long_press_old) < 1000/portTICK_PERIOD_MS
            && !longPressFlag)
        {
          ESP_LOGI(TAG, "Triggered a snapshot");
          snapshot_handler();
        }
      }
      btn_old = btn_now;
      btn_long_press_old = xTaskGetTickCount();
    }
    else if(btn_now == 1) { // Check if this is a long press btn event
      // When the button get released after more then 1 second, then go to the next display mode.
      if(abs(xTaskGetTickCount()-btn_long_press_old) > 1000/portTICK_PERIOD_MS){
        ESP_LOGI(TAG, "long btn press get triggered");
        mode_switch_handler();
        btn_long_press_old=xTaskGetTickCount(); // Update timer preparing trigger the next long btn press event
        longPressFlag = true;
      }
    }
    else {
//      ESP_LOGI(TAG, "Button stays unpressed.");
    }
    vTaskDelay(100 / portTICK_PERIOD_MS);
  }
It can be found in this project's GitHub Repo

This is pretty what I've learned from the Arduino World. Because I do not have any hardware debouncing circuit, I have to "slow down" the read speed. Yeh, I know its kinda amateur. But this make me wanna ask, why the problem like above happens (push button long reaction time & system crash may happen), and is there still possible without hardware debouncing using GPIO's interrupt routine to handling push button debouncing, and how?

Thanks a lot!

Gfast2
Last edited by Gfast2 on Thu May 30, 2019 2:43 pm, edited 2 times in total.

User avatar
gunar.kroeger
Posts: 68
Joined: Fri Jul 27, 2018 6:48 pm

Re: What is the best practice for push-button event tracking/handling without any hardware debouncing circuit

Postby gunar.kroeger » Fri May 31, 2019 4:04 pm

You don't need to wait for a long button press to debounce it.
Just save the esp_log_timestamp of when the button is released and ignore any new button press for the next say 100ms
This should still be responsive enough and very simple to implement

Also, use gpio interrupt to check for the button press instead of polling with get_level
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."

User avatar
Gfast2
Posts: 182
Joined: Fri Aug 11, 2017 1:52 am

Re: What is the best practice for push-button event tracking/handling without any hardware debouncing circuit

Postby Gfast2 » Sat Jun 01, 2019 4:46 am

gunar.kroeger wrote:
Fri May 31, 2019 4:04 pm
You don't need to wait for a long button press to debounce it.
Just save the esp_log_timestamp of when the button is released and ignore any new button press for the next say 100ms
This should still be responsive enough and very simple to implement

Also, use gpio interrupt to check for the button press instead of polling with get_level
Hi gunar.kroeger,

Thank you for your suggestion. I will try the interruption suggestion from you later. Thanks 8-)
What I still didn't get the "A-Ha" Moment is: How you tell the button press is a "long button press" or a "short button press" (Aka. one Click). As I said above, I'd like to define two functionality for the button. The short click is for a "save right now GPS Coordinate to SD Card) and the a "button holding down more then one second" for "changing different Display Mode". On my "Nokia 5110" Display, I've defined 5 different display mode:
  • Display Temperature & Humidity
  • Display Detailed GPS Infos (in very small font for a whole load of GPS infos)
  • Display right now speed only
  • Display right now datetime only
  • Display last two GPS Coordinate Record from SD Card
Through holding the button down, I can let the system looping through different mode without release the button at all (I think its user friendly ;) ) So I can not really get very much benifit from using intterrupt for this. I believe.

Pseudo Code: Through the button state pulling, I read when the button get pressed down -> timestamp1, then when the user released the button -> Timestamp2. When their difference is less then one second, its a "short-click", else its a "mode-changing long button press". If after timestamp1 more then 2 second no button released get detected, change to the next mode (imeplmentated as array of function pointers, -> fully generalized, loopable) & update timestamp1 to the right now timestamp.

I hope some one would like to and have the time to read this and may even interested enough to think about it. ;) :idea:

Cheers

Gfast2

tommeyers
Posts: 63
Joined: Tue Apr 17, 2018 1:51 pm
Location: Santiago, Dominican Republic

Re: What is the best practice for push-button event tracking/handling without any hardware debouncing circuit

Postby tommeyers » Sat Jun 01, 2019 9:45 pm

If you have an oscilloscope take a look.

If not, draw a trace with bounce when first pressed, then stable, then bounce when released.

Draw two traces. a long press and a short press.

bounce bounce bounce stable0 stable0 stable0 bounce bounce bounce stable1 stable1 ...
............................^ got a press (short long don't know)
......................................................................................^ it was short

bounce bounce bounce stable0 stable0 stable0 stable0 stable0 stable0stable0 stable0 stable0 bounce bounce bounce stable1 stable1 ...
.............................^ got a press (short or long don't know)
...................................................................................................................^ it was long
IT Professional, Maker
Santiago, Dominican Republic

mikemoy
Posts: 343
Joined: Fri Jan 12, 2018 9:10 pm

Re: What is the best practice for push-button event tracking/handling without any hardware debouncing circuit

Postby mikemoy » Sun Jun 02, 2019 6:48 am

I did try the IRQ way first but sadly using GPIO_PIN_INTR_LOLEVEL, caused it to reboot.
Even when using GPIO_PIN_INTR_NEGEDGE caused multiple triggers which I expected. I tried gpio_intr_disable() in the IRQ so once it fired and signaled the function I could read it wait a de-bounce time and re-enable it. But it worked strangely. So I resorted to the simple approach.

Code: Select all

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"

static const char *TAG = "BTN";

#define	BUTTON	GPIO_NUM_22 

#define LONG_PRESS_IN_SECONDS 1

static void buttonScanTask(void* arg)
{
	uint16_t ticks = 0;

	ESP_LOGI(TAG, "Waiting For Press.");
	
	for (;;) 
	{

		// Wait here to detect press
		while( gpio_get_level(BUTTON) )
		{
			vTaskDelay(125 / portTICK_PERIOD_MS);
		}
		
		// Debounce
		vTaskDelay(50 / portTICK_PERIOD_MS);

		// Re-Read Button State After Debounce
		if (!gpio_get_level(BUTTON)) 
		{
			ESP_LOGI(TAG, "BTN Pressed Down.");
			
			ticks = 0;
		
			// Loop here while pressed until user lets go, or longer that set time
			while ((!gpio_get_level(BUTTON)) && (++ticks < LONG_PRESS_IN_SECONDS * 100))
			{
				vTaskDelay(10 / portTICK_PERIOD_MS);
			} 

			// Did fall here because user held a long press or let go for a short press
			if (ticks >= LONG_PRESS_IN_SECONDS * 100)
			{
				ESP_LOGI(TAG, "Long Press");
			}
			else
			{
				ESP_LOGI(TAG, "Short Press");
			}

			// Wait here if they are still holding it
			while(!gpio_get_level(BUTTON))
			{
				vTaskDelay(100 / portTICK_PERIOD_MS);
			}
			
			ESP_LOGI(TAG, "BTN Released.");
		}

	}
}

void app_main()
{
	gpio_set_direction(BUTTON, GPIO_MODE_INPUT);
	gpio_pullup_en(BUTTON);
	
	//start the buton scan task
	xTaskCreate(buttonScanTask, "buttonScanTask", 2048, NULL, 10, NULL);


	while (1) 
	{
		vTaskDelay(1000 / portTICK_RATE_MS);
	}
}

User avatar
gunar.kroeger
Posts: 68
Joined: Fri Jul 27, 2018 6:48 pm

Re: What is the best practice for push-button event tracking/handling without any hardware debouncing circuit

Postby gunar.kroeger » Mon Jun 03, 2019 2:31 pm

Sorry, I missed the dual functionality for long pressing the button.

How about:
- Checking for interrupts on rising and falling edge

- onButtonPress => save esp_log_timestamp to uint32_t pressTimestamp;

- onButtonRelease =>

Code: Select all

 save esp_log_timestamp to uint32_t releaseTimestamp;
    uint32_t pressMs = releaseTimestamp - pressTimestamp;
    if(pressMs > LONG_PRESS_MS)
        longPress = true;
    else if(pressMs > DEBOUNCE_MS)
        shortPress = true;
- inside your User Interface task, check for this flags and clear them when ready
"Running was invented in 1612 by Thomas Running when he tried to walk twice at the same time."

User avatar
Gfast2
Posts: 182
Joined: Fri Aug 11, 2017 1:52 am

Re: What is the best practice for push-button event tracking/handling without any hardware debouncing circuit

Postby Gfast2 » Wed Jun 05, 2019 7:14 am

gunar.kroeger wrote:
Mon Jun 03, 2019 2:31 pm
Sorry, I missed the dual functionality for long pressing the button.

How about:
- Checking for interrupts on rising and falling edge

- onButtonPress => save esp_log_timestamp to uint32_t pressTimestamp;

- onButtonRelease =>

Code: Select all

 save esp_log_timestamp to uint32_t releaseTimestamp;
    uint32_t pressMs = releaseTimestamp - pressTimestamp;
    if(pressMs > LONG_PRESS_MS)
        longPress = true;
    else if(pressMs > DEBOUNCE_MS)
        shortPress = true;
- inside your User Interface task, check for this flags and clear them when ready
Hi Herr kroeger,

I will investe time to give this a try! And I may tweak this button with a 10nF Capacitor, too. I believe some untrackable problem is caused by the button bouncing eventually. (It's really the pain in the ass...) . :oops:

goneflyin2002
Posts: 1
Joined: Thu Jun 13, 2019 6:15 pm

Re: What is the best practice for push-button event tracking/handling without any hardware debouncing circuit

Postby goneflyin2002 » Thu Jun 13, 2019 6:26 pm

Hi Gfast2.
Use a library called JC Button.
I know it's made for AVR but it works on ESP32. I have used it very successfully. It will throw a warning 'not compatible' but I believe it's only because the internal pullup resistors don't work with ESP with this library. You just need to add the resistors externally.
It will give you short push, long push and all sorts of event handlers with the buttons and it works beautifully.
Have a look.
D
https://github.com/JChristensen/JC_Button

Who is online

Users browsing this forum: Bing [Bot] and 24 guests