mnishiguchi

mnishiguchi

Nerves Core Team

I detect the button push by using Circuits.GPIO.set_interrupts(gpio_ref, :rising); however it is too sensitive that I want to debounce it. I am thinking about setting a condition using the second data item in the message tuple {:circuits_gpio, 17, 1233456, 1} for controlling the sensitivity. Is it good? Is there a better way?

My target is Rpi4. My button is a 6mm mini button.

defmodule LedBlinker.Button do
  use GenServer, restart: :temporary

  @debounce_time 200_000_000

  def start_link({gpio_pin, on_push_fn}) when is_number(gpio_pin) and is_function(on_push_fn) do
    GenServer.start_link(__MODULE__, {gpio_pin, on_push_fn})
  end

  def init({gpio_pin, on_push_fn}) do
    # Get a ref to the GPIO pin.
    {:ok, gpio_ref} = Circuits.GPIO.open(gpio_pin, :input)

    # Get messages every time the button is pushed.
    Circuits.GPIO.set_interrupts(gpio_ref, :rising)

    {
      :ok,
      %{
        gpio_pin: gpio_pin,
        gpio_ref: gpio_ref,
        last_pushed_at: 0,
        on_push_fn: on_push_fn
      }
    }
  end

  def handle_info({:circuits_gpio, _, at, 1} = message, state) do
    %{last_pushed_at: last_pushed_at, on_push_fn: on_push_fn} = state

    # Debounce the message.
    if @debounce_time < at - last_pushed_at do
      on_push_fn.(message)
    end

    {:noreply, %{state | last_pushed_at: at}}
  end
end

Showing Posts 1 to 5

ondrej-tucek

ondrej-tucek

I’d use send_after & cancel_timer or sleep functions instead of if statement. For example: Process.send_after(self(), {:debounce, on_push_fn, msg}, @debounce_time) + handle_info for :debounce.

mattludwigs

mattludwigs

I think either way should get you what you need and probably more up to person prefference.

I like the if statement appoarch. Speaking from my expereince, having to manage timers and sleeps normally does not work out too well for me. However, that might just be user error on my part :slight_smile:.

mnishiguchi

mnishiguchi OP

Nerves Core Team

@ondrej-tucek @mattludwigs Thank you for your input. It was helpful.

After all, I realized that I could use Rpi’s internal pull-down resister, which I had not known is used for at that time. Circuits.GPIO.set_pull_mode(gpio, pull_mode) handles it all without adding any physical resister! I mean sensitivity feels right without any tuning. It makes my genserver simpler.

defmodule LedBlinker.GpioButton do
  use GenServer, restart: :temporary

  def start_link({gpio_pin, on_push_fn}) when is_number(gpio_pin) and is_function(on_push_fn) do
    GenServer.start_link(__MODULE__, {gpio_pin, on_push_fn})
  end

  def init({gpio_pin, on_push_fn}) do
    # Get a ref to the GPIO pin.
    {:ok, gpio_ref} = Circuits.GPIO.open(gpio_pin, :input)

    # Use internal pull-down resister.
    :ok = Circuits.GPIO.set_pull_mode(gpio_ref, :pulldown)

    # Get messages every time the button is pushed.
    Circuits.GPIO.set_interrupts(gpio_ref, :rising)

    {
      :ok,
      %{
        gpio_pin: gpio_pin,
        gpio_ref: gpio_ref,
        on_push_fn: on_push_fn
      }
    }
  end

  def handle_info({:circuits_gpio, _, at, 1}, %{on_push_fn: on_push_fn} = state) do
    on_push_fn.(at)
    {:noreply, state}
  end
end

It was still a great opportunity to learn how I could control the sensitivity of incoming data.

dominicletz

dominicletz

Creator of Elixir Desktop

Cool, I think you’ve found the best solution. Just in case though I wanted to provider another option. I’ve had a couple of cases where I needed debounce behaviour and implement a module:

if you add this module to your dependencies:

def deps do
   [{:debouncer, "~> 0.1.3"}]
end

you can just do:

  def handle_info({:circuits_gpio, _, at, 1}, %{on_push_fn: on_push_fn} = state) do
    Debouncer.immediate(:circuits_gpio, fn -> on_push_fn.(at) end, 5_000)
    {:noreply, state}
  end 

And it will ensure that the event identified by the first argument (:circuits_gpio) is not triggered more often than every 5_000 millisecond.

There is actually a range of different debounce behaviors I needed in some of my projects so the module implements four different functions. All of them take 3 argument: (event_key, function, timeout)

  • apply() - executes an event after TIMEOUT and so creates a regular interval from frequent events
  • immediate() - is the same as apply() but triggers also immediate on the first event
  • immediate2() - executes the first event as well immediately but mutes all further events until after TIMEOUT
  • delay() - every event delays the trigger by TIMEOUT, will issue an event only once there has been no event for TIMEOUT milliseconds

Or here in a text graph:

  EVENT        X1---X2------X3-------X4----------
  TIMEOUT      ----------|----------|----------|-
  ===============================================
  apply()      ----------X2---------X3---------X4
  immediate()  X1--------X2---------X3---------X4
  immediate2() X1-----------X3-------------------
  delay()      --------------------------------X4

Hope this helps some readers.

mnishiguchi

mnishiguchi OP

Nerves Core Team

Wow, nice! Thanks. I will definitely keep that in my toolbox.

— All posts loaded —

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews