Button causes repeated interrupt callback

I'm trying to make the basic Button sample work but am having problems.

The physical button is a latching button. Press it and it moves in with a click and physically stays there. Press again and with a click, the latch releases and the button pops out.

This is the button: 

Button in "out" state

Out state

Button in "in" state

In state

In terms of code, what happens is this: When I press the button in, I don't think a callback is received. When it is released to its "out" position, the button's interrupt callback function is repeatedly called, over and over again, and its state is always 1. I never see a state of 0 which is why I assume there's no callback when the button is put into its "in" state.

Here's a video:

And here's the code:

/*
Shows how to control a push button with built-in LED like this one:
https://thepihut.com/products/16mm-illuminated-pushbutton-green-latching-on-off-switch?variant=27739372497
 */

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/printk.h>
#include <inttypes.h>

#define SLEEP_TIME_MS	1000

#define START_STOP_BTN_NODE	DT_ALIAS(btnpin)
/* Check if the node is valid before proceeding */
#if !DT_NODE_HAS_STATUS(START_STOP_BTN_NODE, okay)
#error "Start/Stop button pin devicetree node is disabled or not found"
#endif

#define START_STOP_LED_NODE	DT_ALIAS(btnled)
/* Check if the node is valid before proceeding */
#if !DT_NODE_HAS_STATUS(START_STOP_LED_NODE, okay)
#error "Start/Stop LED devicetree node is disabled or not found"
#endif

static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(START_STOP_BTN_NODE, gpios,
							      {0});
static struct gpio_callback button_cb_data;

static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET_OR(START_STOP_LED_NODE, gpios,
							      {0});

void set_led() {
    int state = 1 - gpio_pin_get_dt(&button);
    printk("Setting LED state to %d\n",state);
    gpio_pin_set_dt(&led, state);    
}

void button_pressed(const struct device *dev, struct gpio_callback *cb,
		    uint32_t pins)
{
    printk("Button! State=%d\n",gpio_pin_get_dt(&button));
	printk("Button pressed at %" PRIu32 "\n", k_cycle_get_32());
    // set_led();
}

int main(void)
{
    printk("button test v20\n");
    
	int ret;

	if (!gpio_is_ready_dt(&button)) {
		printk("Error: button device %s is not ready\n",
		       button.port->name);
		return 0;
	}
    printk("Button is ready \n");

	ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
	if (ret != 0) {
		printk("Error %d: failed to configure %s pin %d\n",
		       ret, button.port->name, button.pin);
		return 0;
	}
    printk("Button configured for input\n");

    if (!gpio_is_ready_dt(&led)) {
        printk("Error: LED device %s is not ready\n",
                led.port->name);
        return 0;
	}
    printk("LED is ready \n");

	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT);
	if (ret != 0) {
		printk("Error %d: failed to configure %s pin %d\n",
		       ret, led.port->name, led.pin);
		return 0;
	}
    printk("LED configured for output \n");

    // set_led();

	ret = gpio_pin_interrupt_configure_dt(&button,
					      GPIO_INT_EDGE_TO_ACTIVE);

	if (ret != 0) {
		printk("Error %d: failed to configure interrupt on %s pin %d\n",
			ret, button.port->name, button.pin);
		return 0;
	}
    printk("Button attached to interrupt\n");

	gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
	gpio_add_callback(button.port, &button_cb_data);
	printk("Set up button at %s pin %d\n", button.port->name, button.pin);

	printk("Press the button\n");
	return 0;
}

Overlay with DTS definitions for the button and its integral LED as follows:

/ {
    /* Define a new node within the root of the devicetree */
    custom_gpios {
        /* Use the generic 'gpio-leds' compatible binding */
        compatible = "gpio-leds";

        /* Define a button node with a unique label 'start_stop_btn' */
        start_stop_btn: start_stop_gpio_connector {
            /* 
             * 'gpios' property is a phandle-array that takes:
             * 1. Phandle to the GPIO controller (&gpio0 refers to the node with label 'gpio0')
             * 2. The pin number (e.g., 13)
             * 3. The flags (e.g., GPIO_ACTIVE_LOW for an active-low LED)
             */
            gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
            label = "Start/stop button";
        };
        /* Define an LED node with a unique label 'btn_led' */
        start_stop_led: btn_led_gpio_connector {
            /* 
             * 'gpios' property is a phandle-array that takes:
             * 1. Phandle to the GPIO controller (&gpio0 refers to the node with label 'gpio0')
             * 2. The pin number (e.g., 13)
             * 3. The flags (e.g., GPIO_ACTIVE_LOW for an active-low LED)
             */
            gpios = <&gpio0 8 GPIO_ACTIVE_LOW>;
            label = "Button LED";
        };
    };

    /* Add an alias for easy access in application code */
    aliases {
        btnpin = &start_stop_btn;
        btnled = &start_stop_led;
    };
};

I assume the issue is that this is a latching button rather than a simple momentary push button but I'm not sure how to handle this in code.

Advice and guidance appreciated as always :-)

Update

Tested with various GPIO interrupt flag combinations with the following results:

    // Button Out: repeated callbacks. Button In: no callback. State=1
	// ret = gpio_pin_interrupt_configure_dt(&button,
	// 				      GPIO_INT_EDGE_TO_ACTIVE);

    // Button Out: repeated callbacks. Button In: no callback. State=0
	// ret = gpio_pin_interrupt_configure_dt(&button,
	// 				      GPIO_INT_LEVEL_INACTIVE|GPIO_INT_LEVEL_ACTIVE);

    // Button Out: repeated callbacks. Button In: no callback. State=0
	// ret = gpio_pin_interrupt_configure_dt(&button,
	// 				      GPIO_INT_EDGE_RISING);

    // Button Out: repeated callbacks. Button In: no callback. State=1
	ret = gpio_pin_interrupt_configure_dt(&button,
					      GPIO_INT_EDGE_FALLING);

    // Button Out: repeated callbacks. Button In: no callback. State=1 and 0 alternating i.e. 2 callbacks each time button released to OUT state.
	// ret = gpio_pin_interrupt_configure_dt(&button,
	// 				      GPIO_INT_EDGE_BOTH);                          

    // Button Out: repeated callbacks with state=0. Button In: callback with state 1.
	ret = gpio_pin_interrupt_configure_dt(&button,
					      GPIO_INT_LEVEL_LOW|GPIO_INT_LEVEL_HIGH);  

Parents Reply Children
  • Thank you! This sounds very likely the problem but with the following in my DTS file, the behaviour still occurs:

            start_stop_btn: start_stop_gpio_connector {
                /* 
                 * 'gpios' property is a phandle-array that takes:
                 * 1. Phandle to the GPIO controller (&gpio0 refers to the node with label 'gpio0')
                 * 2. The pin number (e.g., 13)
                 * 3. The flags (e.g., GPIO_ACTIVE_LOW for an active-low LED)
                 */
                gpios = <&gpio0 6 (GPIO_ACTIVE_LOW  | GPIO_PULL_UP)>;
                label = "Start/stop button";
            };

    --- 9999 messages dropped ---
    Button pressed at 422836
    Button state=0
    Button pressed at 422838
    Button state=0
    Button pressed at 422841
    Button state=0
    Button pressed at 422843
    Button state=0
    Button pressed at 422846
    Button state=0
    Button pressed at 422848
    Button state=0
    Button pressed at 422851
    Button state=0
    Button pressed at 422853
    Button state=0
    Button pressed at 422856
    Button state=0
    Button pressed at 422858
    Button state=0
    Button pressed at 422861
    Button state=0
    Button pressed at 422863
    Button state=0
    Button pressed at 422866
    Button state=0
    Button pressed at 422868
    Button state=0
    Button pressed at 422871
    Button state=1
    Button pressed at 422873

    FYI I tried PULL_DOWN as well but got no callbacks at all with this flag.

  • Hello,

    Debouncing is a normal problem with buttons, normally adding a capacitor on the input or in software add a timer on button press that prevent debounce to trigger multiple key presses. If that is not the problem, check if it's related to using edge vs. level when configuring the pin. And if that it not the problem, then I suggest to take a look at any example project in ncs to compare with your own project, also measuring on the the pin using a logic analyzer in case you are not overlooking something can be a good idea.

    Kenneth

  • Hi  , thanks for your response. I'm aware of debouncing but only ever seen one or two "extra" callbacks where debouncing needs to be applied. This is a rapid stream of hundreds.

    The code I'm using is basically the Button sample that ships with Zephyr:

    https://github.com/zephyrproject-rtos/zephyr/tree/main/samples/basic/button

    I think I've tried all possible interrupt configuration flags (see above) but in the off/button out state always get massive numbers of callbacks.

    I'm not sure this is a simple case of needing to debounce. I can do this in code but there will still be a storm of callbacks being processed. Debouncing in code will just mask this.

    The pullup/antenna theory seems more plausible but the pullup flag didn't solve the problem.

    Mysterious.

  • Have you verified that simply connecting the button pin directly to vdd or gnd works without any problem?

    Kenneth

  • I initially tested with a battery and breadboard and it seemed fine. I'm going to double check that though now that you've mentioned it. 

Related