Short and Long button press

Looking good!

script_exec((char *)button_script_files[button_code-1]

I’m not entirely sure this is correct after I changed variable type. the (char*) may not be needed/throw a compiler warning.

i think you are right because button_script_files[button_code-1] is already of type char * … im not sure we have to explicitly cast it. It didnt show any warnings tho

Am I the only noticing butt tap? Xd

1 Like

If you find that offensive, avoid the button section of the enclosure OpenScad source… I didn’t realize just how awful the double entendre was until I had to make revisions. Unintentionally just awful :slight_smile:

as discussed in chat, i have cleaned up based on Ian’s recommendations and the implementation works, but we ran into a bug where accessing command history (Pressing UP on cli) after running a script with the button. causes an infinite loop.

Here is my latest compare Comparing DangerousPrototypes:main...jrelo:button_longpress2 · DangerousPrototypes/BusPirate5-firmware · GitHub

But we will be looking into the issue.

1 Like

I’ll do a stop gap and get it going first thing tomorrow and merge the button press updates. It’s gonna be amazing to support multiple scripts from the “just one button”.

In the beginning there were three buttons. But the SPI DAC that did the adjustable voltage and current limit (3 models from TI, three models from Microchip) became unobtainable. So, we lost a pin to a PWM channel. Thus, just one button…

i am glad you steered me in the right direction to make it more robust and expandable. i already started writing some snippets for double tap, but wanted to get this out first.

2 Likes

No worries!

That is the long answer.

The short answer is:

  1. Add a new key and string to en-us.h
  2. Run h2json.py, it will build all the translations with defaults.
  3. Done! Crude but fast!

ok thanks. i was just going to add it manually based on your commit here is why i deleted the message:

:stuck_out_tongue:

1 Like

Adding it manually is a bit of a pain. The Python script was the only way I could force myself to consistently use the translation system. I couldn’t figure out a way to substitute “default” values missing from translations using the precompiler, but maybe it’s possible.

All the header files are rebuilt from source (JSON) the next time someone runs the Python script. If it’s done manually there’s no issue as long as it compiles, the next time the script is built it will add the missing values using the default in en-us.h

translation stuff it was easy enough now that i know.
i have double tap sort of working, but some issues with timing and when/if button_pressed=true.
i have some other projects im working on but plan to come back to it this weekend.

void button_irq_callback(uint gpio, uint32_t events) {
    static absolute_time_t last_press_time;
    static bool double_tap_wait = false;
    static absolute_time_t press_start_time;

    if (events & GPIO_IRQ_EDGE_RISE) {
        press_start_time = get_absolute_time();
    }

    if (events & GPIO_IRQ_EDGE_FALL) {
        absolute_time_t press_end_time = get_absolute_time();
        int64_t duration_ms = absolute_time_diff_us(press_start_time, press_end_time) / 1000;

        if (duration_ms >= BP_BUTTON_SHORT_PRESS_MS) {
            button_code = BP_BUTT_LONG_PRESS;
            button_pressed = true;
            double_tap_wait = false;
        } else {
            if (double_tap_wait) {
                int64_t double_press_duration = absolute_time_diff_us(last_press_time, press_end_time) / 1000;
                if (double_press_duration <= BP_BUTTON_DOUBLE_PRESS_MS) {
                    button_code = BP_BUTT_DOUBLE_PRESS;
                    button_pressed = true;
                    double_tap_wait = false;
                } else {
                    button_code = BP_BUTT_SHORT_PRESS;
                    button_pressed = true;
                    double_tap_wait = false;
                }
            } else {
                double_tap_wait = true;
                last_press_time = press_end_time;
                button_pressed = false;
            }
        }
    }

    gpio_acknowledge_irq(gpio, events);
    gpio_set_irq_enabled(gpio, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true);
}

i need to add a timer that will time out if waiting for >BP_BUTTON_DOUBLE_PRESS_MS after short press

1 Like

This is going to be an interesting bit of logic when it’s done. Did you see any of the comments on social media related to the long/short press? There are some major button enthusiasts out there!

1 Like

no way…
on twitter?

I am spinning tires here.
a) i need some kind of timer mechanism to set short press if a second tap isnt detected in < BP_BUTTON_DOUBLE_TAP_MS
b) other strange behavior.

I uploaded my crappy attempt to a test branch if anyone wants to look:

1 Like

What is the basic logic to sort the press types? Just a general representation?

on the first button press it sets double_tap_wait and records time. on the next press it will know that its the 2nd tap if that variable is set and check if the time between the 2 presses is within 250ms.

I am trying to solve the problem of it setting it as a short press if you press once, and >BP_BUTTON_DOUBLE_TAP_MS elapses without another press

long press stays the same and is the first thing it checks for since it will know immediately on GPIO_IRQ_EDGE_FALL if the duration between RISE and FALL is >= BP_BUTTON_SHORT_PRESS_MS

Hum. I think you’re almost there.

It seems like you’re trying to process everything within the gpio interrupt function. Would it help to have a separate interrupt handler function for the short press timeout?

  • first Short press detected
  • Start double tap timeout timer that calls bool button_timer_callback(struct repeating_timer* t) (see rgb.c and pirate.c for timer examples)
  • Next event:
    – If next IRQ is timer callback, then set button_press=true and type = short
    – if next IRQ is GPIO callback, then setup your timer to measure second tap, reset the timeout interrupt
  • Next event:
    – if next IRQ is timer: short plus long press? error? ignore? whats the default action
    –if next IRQ is GPIO: didn’t time out, have 2 short taps - double tap!

I might also turn the if else statements into a statemachine with switch() to make it easier to debug. A better coder than I probably has an even smarter way to unwrap that.

1 Like

I think you are exactly right about the separate interrupt function. ill find some time to really sit down and think it over and review everything

2 Likes

Unless you’ve connected an oscilliscope, and ian absolutely verifies that each and every button will have specific bounce behavior (or proper hardware debounce), connecting an interrupt directly to a mechanical switch … has been shown to be problematic. Yes, even with schottky pins … mechanical switch bounces don’t follow simple rules.

I recommend finding MIT-licensed, polling switch libraries. Look for ones that separate the following concepts:

  1. Debouncing the transition … e.g., N consecutive identical values == switch state changed
  2. For double-press and long-press support, the library should clealry explain which choices were made:
    a. does a double-press result in two callbacks/events (press + double-press), or does the library wait to verify no second press is coming before the initial press callback/event?
    b. does a long-press result in two callbacks/events ( press + long-press), or does the library wait to report short-press until the button is released?

Once you know which model you want to support, the code itself is actually fairly straightforward when looking at each layer individually:

Layer0: Hardware signal debouncing
Layer1: Handling of double/long press

Good luck!

1 Like

Good suggestions @henrygab, I imagine debouncing will come into focus pretty quickly with double tap. Some logic for too-small press rejection and too-close together rejection.

Generic code.

Some RP2040 specific:

Putting it in the PIO is amazing! But it seems a wasted resource…

Simple debounce that could be added

Just for inspiration.

I think the secondary timer interrupt for double tap can also be roped into debouncing a bit.

The code ian posted is great for multiple buttons, but more complex than is needed here for a single button.

Here’s a very simple, efficient, single-switch debouncer (i.e. layer 0):

bool DebounceSwitch()
{
    // As shown, requires twelve (12x) consecutive reads of zero
    const uint16_t STATE_MASK = 0xF000;
    static uint16_t state_history = 0; // last twelve reads of the button
    state_history =
        (state_history << 1) |  // make room in the history
        !RawKeyPressed()     |  // one bit, unless button is pressed
        STATE_MASK           ;
    return (state_history == STATE_MASK);
}

Based on: https://www.ganssle.com/debouncing-pt2.htm

Wrap the above function with the logic needed for layer1 (double/long presses), as needed … which is called from a single timer callback. Note that the Layer1 code can be simpler if the timer fires once per millisecond. :slight_smile:

2 Likes