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

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
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
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews