Any ready-to-go code for ESP32 GPIO button debouncing?

Digital Larry
Posts: 13
Joined: Sat Mar 07, 2020 4:20 am

Any ready-to-go code for ESP32 GPIO button debouncing?

Postby Digital Larry » Thu Apr 09, 2020 12:48 am

I found some articles talking about different ways to go about this, but it seems to me like it should be a basic function. I'm using ESP-IDF at the moment.

Thanks,

DL

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

Re: Any ready-to-go code for ESP32 GPIO button debouncing?

Postby mikemoy » Thu Apr 09, 2020 5:18 am

As the old saying goes. There is more than one way to skin a cat.
FWIW, it is a basic thing with really the only difference is some switches are more "dirty" than others requiring a longer delay.
It also depends on the circuit design as well. Did the user put a cap & pull up/down resistor, and what value resistor was it.
These things all make the difference in the code.

Digital Larry
Posts: 13
Joined: Sat Mar 07, 2020 4:20 am

Re: Any ready-to-go code for ESP32 GPIO button debouncing?

Postby Digital Larry » Thu Apr 09, 2020 3:14 pm

I am using the ESP32-A1S AIThinker audio development board. The switch buttons are pulled up through on board resistors, value not marked in the schematic, in what looks to me like an R/2R ladder arrangement? I've never seen that for button input but I guess this would let you input a binary value. Or I could be completely wrong about that. There is some RC filtering (values not shown, I'll try to find the BOM) and these are just small PCB mount clicky push button switches.

See "KEYS" section at lower right on this schematic.
http://wiki.ai-thinker.com/_media/esp32 ... custom.pdf

I did find this code, which seems a step in the right direction.
https://github.com/craftmetrics/esp32-button

I'm learning about tasks and that sort of thing, and admittedly I don't have much experience with this. I started by using Faust (audio effects code generator) and then merged in the wifi smart_config code, which appears to work. So now I'm trying to see if I can get the buttons to adjust parameters of the audio algorithms since:

a) the buttons are there
b) this dev board does not appear to have any general purpose A/D GPIO inputs brought out to the headers.

My main program, and the Faust generated stuff, are in C++ while the wifi and button code are in C. I added the button code to the project, and it compiles and links OK, however I haven't asked it to do anything yet.

I'll keep plugging away trial and error (not a bad way to learn). Any insight is of course welcome.

Thanks,

DL

Digital Larry
Posts: 13
Joined: Sat Mar 07, 2020 4:20 am

Re: Any ready-to-go code for ESP32 GPIO button debouncing?

Postby Digital Larry » Sun Apr 12, 2020 1:34 am

OK here's where I am.

I'm using the AiThinker A1S WiFi/BT/Audio board.
The schematic is here:
http://wiki.ai-thinker.com/_media/esp32 ... custom.pdf

There are 6 buttons which route to IO5, IO13, IO18, IO19, IO23, IO36. They are pulled up via an R/2R ladder which eventually goes to IO36, but it also appears that each button can be used for an individual on/off input.

Then I'm using the craftmetrics code from here:

https://github.com/craftmetrics/esp32-button

And I've written a C++ "main" file because eventually I'm trying to merge this in with a C++ based project.

main.cpp:

Code: Select all

/* button press Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_spi_flash.h"
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"

extern "C" {
#include "button.h"
}

extern "C" {
    void app_main(void);
    QueueHandle_t * button_init(unsigned long long );
}

void app_main(void)
{
button_event_t ev;
QueueHandle_t button_events = button_init(PIN_BIT(5) | PIN_BIT(13) | PIN_BIT(18)  | PIN_BIT(19) | PIN_BIT(23));
								        

    while (true) {
		if (xQueueReceive(button_events, &ev, 1000/portTICK_PERIOD_MS)) {
			if ((ev.pin == 1) && (ev.event == BUTTON_DOWN)) {
				ESP_LOGI("Button 1", "Down");
			}
			if ((ev.pin == 2) && (ev.event == BUTTON_DOWN)) {
				ESP_LOGI("Button 2", "Down");
			}
		}
    }
}

button.c:

Code: Select all

#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_log.h"

#include "button.h"

#define TAG "BUTTON"

typedef struct {
	uint8_t pin;
    bool inverted;
	uint16_t history;
    uint64_t down_time;
} debounce_t;

int pin_count = -1;
debounce_t * debounce;
QueueHandle_t * queue;

static void update_button(debounce_t *d) {
    d->history = (d->history << 1) | gpio_get_level(d->pin);
}

#define MASK   0b1111000000111111
static bool button_rose(debounce_t *d) {
    if ((d->history & MASK) == 0b0000000000111111) {
        d->history = 0xffff;
        return 1;
    }
    return 0;
}
static bool button_fell(debounce_t *d) {
    if ((d->history & MASK) == 0b1111000000000000) {
        d->history = 0x0000;
        return 1;
    }
    return 0;
}
static bool button_down(debounce_t *d) {
    if (d->inverted) return button_fell(d);
    return button_rose(d);
}
static bool button_up(debounce_t *d) {
    if (d->inverted) return button_rose(d);
    return button_fell(d);
}

#define LONG_PRESS_DURATION (2000)

static uint32_t millis() {
    return esp_timer_get_time() / 1000;
}

static void send_event(debounce_t db, int ev) {
    button_event_t event = {
        .pin = db.pin,
        .event = ev,
    };
    xQueueSend(queue, &event, portMAX_DELAY);
}

static void button_task(void *pvParameter)
{
    while (1) {
        for (int idx=0; idx<pin_count; idx++) {
            update_button(&debounce[idx]);
            if (debounce[idx].down_time && (millis() - debounce[idx].down_time > LONG_PRESS_DURATION)) {
                debounce[idx].down_time = 0;
                ESP_LOGI(TAG, "%d LONG", debounce[idx].pin);
                int i=0;
                while (!button_up(&debounce[idx])) {
                    if (!i) send_event(debounce[idx], BUTTON_DOWN);
                    i++;
                    if (i>=5) i=0;
                    vTaskDelay(10/portTICK_PERIOD_MS);
                    update_button(&debounce[idx]);
                }
                ESP_LOGI(TAG, "%d UP", debounce[idx].pin);
                send_event(debounce[idx], BUTTON_UP);
            } else if (button_down(&debounce[idx])) {
                debounce[idx].down_time = millis();
                ESP_LOGI(TAG, "%d DOWN", debounce[idx].pin);
                send_event(debounce[idx], BUTTON_DOWN);
            } else if (button_up(&debounce[idx])) {
                debounce[idx].down_time = 0;
                ESP_LOGI(TAG, "%d UP", debounce[idx].pin);
                send_event(debounce[idx], BUTTON_UP);
            }
        }
        vTaskDelay(10/portTICK_PERIOD_MS);
    }
}

QueueHandle_t * button_init(unsigned long long pin_select) {
    if (pin_count != -1) {
        ESP_LOGI(TAG, "Already initialized");
        return NULL;
    }

    // Configure the pins
    gpio_config_t io_conf;
    io_conf.mode = GPIO_MODE_INPUT;
    io_conf.pin_bit_mask = pin_select;
    gpio_config(&io_conf);

    // Scan the pin map to determine number of pins
    pin_count = 0;
    for (int pin=0; pin<=39; pin++) {
        if ((1ULL<<pin) & pin_select) {
            pin_count++;
        }
    }

    // Initialize global state and queue
    debounce = calloc(pin_count, sizeof(debounce_t));
    queue = xQueueCreate(4, sizeof(button_event_t));

    // Scan the pin map to determine each pin number, populate the state
    uint32_t idx = 0;
    for (int pin=0; pin<=39; pin++) {
        if ((1ULL<<pin) & pin_select) {
            ESP_LOGI(TAG, "Registering button input: %d", pin);
            debounce[idx].pin = pin;
            debounce[idx].down_time = 0;
            debounce[idx].inverted = true;
            if (debounce[idx].inverted) debounce[idx].history = 0xffff;
            idx++;
        }
    }

    // Spawn a task to monitor the pins
    xTaskCreate(&button_task, "button_task", 4096, NULL, 10, NULL);

    return queue;
}

button.h:

Code: Select all

#define PIN_BIT(x) (1ULL<<x)

#define BUTTON_DOWN (1)
#define BUTTON_UP (2)

typedef struct {
	uint8_t pin;
    uint8_t event;
} button_event_t;

QueueHandle_t * button_init(unsigned long long );

Then, when I build, upload, and run the program, I get this output:

Code: Select all

I (0) cpu_start: App cpu up.
I (1151) spiram: SPI SRAM memory test OK
I (1151) heap_init: Initializing. RAM available for dynamic allocation:
I (1151) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (1157) heap_init: At 3FFB3038 len 0002CFC8 (179 KiB): DRAM
I (1164) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (1170) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (1177) heap_init: At 4008C0A0 len 00013F60 (79 KiB): IRAM
I (1183) cpu_start: Pro cpu start user code
I (1201) spi_flash: detected chip: generic
I (1202) spi_flash: flash io: dio
I (1211) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (1212) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1222) gpio: GPIO[13]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1222) gpio: GPIO[18]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1232) gpio: GPIO[19]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1242) gpio: GPIO[23]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (1252) BUTTON: Registering button input: 5
I (1262) BUTTON: Registering button input: 13
I (1262) BUTTON: Registering button input: 18
I (1272) BUTTON: Registering button input: 19
I (1272) BUTTON: Registering button input: 23
I (1332) BUTTON: 13 DOWN
I (1332) BUTTON: 18 DOWN
I (1332) BUTTON: 23 DOWN
I (3342) BUTTON: 13 LONG
Keep in mind that I am not holding any of the buttons down. Also, when I press the buttons, I don't get any additional output on the console.

The example wasn't written specifically for the ESP32-A1s board, but GPIO numbers should be accurate. I went off to the AI-Thinker website, which I was able to partially translate to English using the Chrome browser, and looked as hard I could for any ESP32-A1S specific example/demo code. Unfortunately all links just go back to Espressif application notes for the Lyra boards. Seems like AIThinker didn't bother to put together any specific code examples for their own boards, oddly enough. However, I'm skeptical that has anything to do with it.

Ideas? Suggestions?

Thanks,

DL

Digital Larry
Posts: 13
Joined: Sat Mar 07, 2020 4:20 am

Re: Any ready-to-go code for ESP32 GPIO button debouncing?

Postby Digital Larry » Sun Apr 12, 2020 8:28 am

Here's another clue. (Note, I added IO36 to the list).

If I hold down key 6, which is connected to IO5, and reboot, I get:

Code: Select all

I (1272) BUTTON: Registering button input: 5
I (1272) BUTTON: Registering button input: 13
I (1282) BUTTON: Registering button input: 18
I (1282) BUTTON: Registering button input: 19
I (1292) BUTTON: Registering button input: 23
I (1292) BUTTON: Registering button input: 36
I (1352) BUTTON: 5 DOWN
I (1352) BUTTON: 13 DOWN
I (1352) BUTTON: 18 DOWN
I (1352) BUTTON: 23 DOWN
I (3362) BUTTON: 5 LONG
and then nothing else.

If I hold down key 1, connected to IO36, and reboot, I get:

Code: Select all

I (1272) BUTTON: Registering button input: 5
I (1272) BUTTON: Registering button input: 13
I (1282) BUTTON: Registering button input: 18
I (1282) BUTTON: Registering button input: 19
I (1292) BUTTON: Registering button input: 23
I (1292) BUTTON: Registering button input: 36
I (1352) BUTTON: 13 DOWN
I (1352) BUTTON: 18 DOWN
I (1352) BUTTON: 23 DOWN
I (1352) BUTTON: 36 DOWN
I (3362) BUTTON: 13 LONG
Pressing any of the other buttons at boot time does not change the behavior, which is:

Code: Select all

I (1272) BUTTON: Registering button input: 5
I (1272) BUTTON: Registering button input: 13
I (1282) BUTTON: Registering button input: 18
I (1282) BUTTON: Registering button input: 19
I (1292) BUTTON: Registering button input: 23
I (1292) BUTTON: Registering button input: 36
I (1352) BUTTON: 13 DOWN
I (1352) BUTTON: 18 DOWN
I (1352) BUTTON: 23 DOWN
I (3362) BUTTON: 13 LONG
So, the "LONG" press is the last thing that gets logged, and for IO5 and IO36 at any rate, it does look like it's reading the pins right after boot but that's it.

Looking in the "button_task()" in button.c, I see:

Code: Select all

                ESP_LOGI(TAG, "%d LONG", debounce[idx].pin);
                int i=0;
                while (!button_up(&debounce[idx])) {
                    if (!i) send_event(debounce[idx], BUTTON_DOWN);
                    i++;
                    if (i>=5) i=0;
                    vTaskDelay(10/portTICK_PERIOD_MS);
                    update_button(&debounce[idx]);
                }
                ESP_LOGI(TAG, "%d UP", debounce[idx].pin);
                send_event(debounce[idx], BUTTON_UP);
             
which makes it look like this is where it's getting stuck, as I never see the "UP" debug message come out after the first key to report "LONG".

So, a couple things are strange.
a) Buttons report "down" even though I am not pressing them - I'll check with a voltmeter tomorrow
b) The code I found seems to have a bug somewhere, which I can try to trace out, I suppose. I think the code as designed will get stuck if a key is held down. You do get the repeats sent to the queue but no other key is ever scanned. So looks like solving (a) is an important first step.

Digital Larry
Posts: 13
Joined: Sat Mar 07, 2020 4:20 am

Re: Any ready-to-go code for ESP32 GPIO button debouncing? ESP32-A1S audio dev board

Postby Digital Larry » Sun Apr 12, 2020 3:24 pm

Some progress... I guess the onboard pullup through the R/2R ladder is actually not adequate.

Code: Select all

    io_conf.mode = GPIO_MODE_INPUT;    
	io_conf.pull_up_en = 1;
	io_conf.pull_down_en = 0;
I added the last 2 lines to button_init() in button.c and no longer have the stuck buttons.

Everything works as expected. Key down and key up events are sent separately and long press auto-repeats at an adjustable rate. Cool!!

The only thing it doesn't do, which is not a deal breaker, is N-key rollover. If you hold a key down, all other keys are blocked until you release it.

Who is online

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