Page 1 of 1

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

Posted: Thu May 30, 2019 2:37 pm
by Gfast2
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

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

Posted: Fri May 31, 2019 4:04 pm
by gunar.kroeger
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

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

Posted: Sat Jun 01, 2019 4:46 am
by Gfast2
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

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

Posted: Sat Jun 01, 2019 9:45 pm
by tommeyers
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

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

Posted: Sun Jun 02, 2019 6:48 am
by mikemoy
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);
	}
}

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

Posted: Mon Jun 03, 2019 2:31 pm
by gunar.kroeger
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

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

Posted: Wed Jun 05, 2019 7:14 am
by Gfast2
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:

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

Posted: Thu Jun 13, 2019 6:26 pm
by goneflyin2002
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