Detecting a button double click?

We are new to Nordic and have been making great progress with our nrf52840DK board and successfully running many of the Nordic SDK examples. We can't seem to find a single example of an API that will notify on a variety of button events (more than just a single click). Our code needs to respond differently for single-click, double-click, press-and-hold. Sure seems that the Nordic SDK would have something for that. Yes? Any help would be appreciated.

Thanks,

Steve K, PuzL Labs, LLC

Parents
  • Hi Priyanka,

    Thanks, good to know that v2.4.1 supports sensing button click events. I went looking within my v2.4.1 Nordic SDK installation to find a code example that might show the use of the "CAF: Buttons module". I couldn’t find anything. Being new to Nordic I guess it's possible I am looking in the wrong place. If you happen to have a tip I'd be grateful. Thanks in advance.

    Regards,

    Steve K, PuzL Labs, LLC

  • Hi Steve.

    To address the CAF front, here's a sample I modified from the CAF sample to use single and double clicks: droidecahedron/caf_click

    However, you can alternatively continue using your i/o callback and add some logic with Timers — Zephyr Project Documentation to determine single vs double vs long. These can be pretty cheap.

    I have a generic i/o practice repo that utilizes the logic pretty similarly here to the CAF example: droidecahedron/nrf_io_practice

    (You can take whatever is used in io.c and roll it into your main.c however you like. I just abstracted it away to another file in that project to keep main empty.)

    Both need some clean up but either of them should get you most of the way there. I am partial to using timers over CAF for this use case. Pick whichever you prefer. One benefit of using timers is you can specify a bit more what the "double click" duration is in a straightforward way. Another is knowing pretty directly which peripherals are actually being used.

  • Hi Johnny N,

    Yes, thank you very much. I think that is a great starting point for me. I downloaded your sample application using:

    $ git clone https://github.com/droidecahedron/nrf_io_practice.git

    And then I used the Visual Studio "Create an existing application" and pointed it at that sample application and after Adding a Build Config it built/flashed and is running on my nrf52840-DK. I will fiddle with this working sample and alter as needed for the particular system we are developing. Thanks again.

    Steve K, PuzL Labs, LLC

  • Sounds great, Steve.

    As you'll see in the comments in that repo, there is some cleanup to do on my end in there, but it is functional. (You can see some of the cleanup type of changes I reference in the init comment here in this PR)

    The Zephyr Timer documentation page I linked has some other examples on timer usage, my timer implementation is certainly less than optimal for button detection and is quite barebones, but you can extrapolate for more complex and robust logic, the application logic is up to you. (Such as accounting for bouncy buttons, etc.)

    Glad it could be helpful to you! I like to keep the io.c as a separate file so you can drag/drop it into your other applications. (I also like to put my BLE services into their own source files as well.)

    Do not hesitate to reach out if there is anything further.

    Best regards,

  • Hi Johnny N.

    As it turned out your implementation was good to study and gave me some ideas and I learned something about timers along the way. Today I went in a bit of a different direction and implemented a full button.c and button.h that de-bounces the button as well as detects single click, double click and long press events. I think I have what I need for the moment. Your files gave me the nudge that I needed. Thanks again.

    Steve K., PuzL Labs, LLC

  • Hi Steve, a junior (and fairly inexperienced) embedded dev here, who also just recently started programming Nordic devices.

    I would be so grateful if you could share the code that you wrote for the button implementation (button.c and button.h). See I tried my own hand at this because my project required detecting and distinguishing between only LONG and DOUBLE presses. Here is what I could come up with, based on the very helpful example code: https://github.com/droidecahedron/nrf_io_practice.git, shared by Johnny N above:

    typedef enum {
        SLEEPING,
        LISTENING,
        CLOSED
    } gpio_activation_state_t;
    
    /* Timeout windows for detecting long and double press. */
    static const k_timeout_t long_press_window = K_MSEC(5000);
    static const k_timeout_t double_press_window = K_MSEC(1000);
    
    /* Flags for detecting long and double press. */
    volatile bool double_press_detected;
    volatile bool long_press_detected;
    volatile gpio_activation_state_t input_state;
    
    /* Callback when long press timer expires. */
    static void long_press_timer_exp_fn(struct k_timer *timer_id)
    {
        if (input_state == LISTENING)
        {
            if (1 == gpio_pin_get_dt(&button)) {
                long_press_detected = true;
            }
            else {
                /* Single press! (though not a very good way to detect it as you must wait 5 seconds!)*/
            }
            input_state = CLOSED;
        }
    }
    
    /* Define timers for double and long press detection. */
    K_TIMER_DEFINE(double_press_timer, NULL, NULL);
    K_TIMER_DEFINE(long_press_timer, long_press_timer_exp_fn, NULL);
    
    /* Callback when button is pressed. NOTE: No debouncing logic! */
    void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
    {
        switch (input_state)
        {
            case SLEEPING: /* Logic for the first time the key is pressed. */
                input_state = LISTENING;
                /* Clear flags for double/long press detection. */
                double_press_detected = false;
                long_press_detected = false;
                /* Start one shot timers for double and long press windows. */
                k_timer_start(&double_press_timer, double_press_window, K_NO_WAIT);
                k_timer_start(&long_press_timer, long_press_window, K_NO_WAIT);
                break;
            
            case LISTENING: /* Button has been pressed a second time. */
                if (k_timer_status_get(&double_press_timer) > 0) /* Double press timer expired. */
                {
                    LOG_WRN("Double press window expired.");
                }
                else if (k_timer_remaining_get(&double_press_timer) > 0) /* Double press timer still running.*/
                {
                    double_press_detected = true;
                    k_timer_stop(&long_press_timer);
                }
                input_state = CLOSED;
                break;
    
            case CLOSED: default:
                /* Lock up GPIO when a transaction is underway. */
                break;
        }
    }

    I made use of a very basic state machine and two Zephyr timers to detect double presses (within at most 1000 ms) and a long press (after at least 5000ms). It works pretty well even though there is no debouncing logic, for now. I am very curious to see your implementation if you would be willing to share it.

    Thanks,

    Shovnik.

Reply
  • Hi Steve, a junior (and fairly inexperienced) embedded dev here, who also just recently started programming Nordic devices.

    I would be so grateful if you could share the code that you wrote for the button implementation (button.c and button.h). See I tried my own hand at this because my project required detecting and distinguishing between only LONG and DOUBLE presses. Here is what I could come up with, based on the very helpful example code: https://github.com/droidecahedron/nrf_io_practice.git, shared by Johnny N above:

    typedef enum {
        SLEEPING,
        LISTENING,
        CLOSED
    } gpio_activation_state_t;
    
    /* Timeout windows for detecting long and double press. */
    static const k_timeout_t long_press_window = K_MSEC(5000);
    static const k_timeout_t double_press_window = K_MSEC(1000);
    
    /* Flags for detecting long and double press. */
    volatile bool double_press_detected;
    volatile bool long_press_detected;
    volatile gpio_activation_state_t input_state;
    
    /* Callback when long press timer expires. */
    static void long_press_timer_exp_fn(struct k_timer *timer_id)
    {
        if (input_state == LISTENING)
        {
            if (1 == gpio_pin_get_dt(&button)) {
                long_press_detected = true;
            }
            else {
                /* Single press! (though not a very good way to detect it as you must wait 5 seconds!)*/
            }
            input_state = CLOSED;
        }
    }
    
    /* Define timers for double and long press detection. */
    K_TIMER_DEFINE(double_press_timer, NULL, NULL);
    K_TIMER_DEFINE(long_press_timer, long_press_timer_exp_fn, NULL);
    
    /* Callback when button is pressed. NOTE: No debouncing logic! */
    void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
    {
        switch (input_state)
        {
            case SLEEPING: /* Logic for the first time the key is pressed. */
                input_state = LISTENING;
                /* Clear flags for double/long press detection. */
                double_press_detected = false;
                long_press_detected = false;
                /* Start one shot timers for double and long press windows. */
                k_timer_start(&double_press_timer, double_press_window, K_NO_WAIT);
                k_timer_start(&long_press_timer, long_press_window, K_NO_WAIT);
                break;
            
            case LISTENING: /* Button has been pressed a second time. */
                if (k_timer_status_get(&double_press_timer) > 0) /* Double press timer expired. */
                {
                    LOG_WRN("Double press window expired.");
                }
                else if (k_timer_remaining_get(&double_press_timer) > 0) /* Double press timer still running.*/
                {
                    double_press_detected = true;
                    k_timer_stop(&long_press_timer);
                }
                input_state = CLOSED;
                break;
    
            case CLOSED: default:
                /* Lock up GPIO when a transaction is underway. */
                break;
        }
    }

    I made use of a very basic state machine and two Zephyr timers to detect double presses (within at most 1000 ms) and a long press (after at least 5000ms). It works pretty well even though there is no debouncing logic, for now. I am very curious to see your implementation if you would be willing to share it.

    Thanks,

    Shovnik.

Children
No Data
Related