Porting Elixir/ALE to Circuits.GPIO

The Circuits.GPIO package is the next version of Elixir/ALE's GPIO support. If you're currently using Elixir/ALE, you're encouraged to switch. Here are some benefits:

  1. Supported by both the maintainer of Elixir/ALE and a couple others. They'd prefer to support Circuits.GPIO issues.
  2. Much faster than Elixir/ALE - like it's not even close. The guideline is still to use GPIOs for buttons, LEDs, and other low frequency devices and use specialized device drivers for the rest. However, we know that's not always easy and the extra performance is really nice to have.
  3. Included pull up/pull down support for Raspberry Pi. We'd like to include this for other platforms as well. This was the single-most requested feature for Elixir/ALE.
  4. Timestamped interrupt reports - This is makes it possible to measure time deltas between GPIO changes with higher accuracy by removing variability from messages sitting in queues and passing between processes
  5. Interrupt glitch detection - When the GPIO toggles faster than Circuits.GPIO can handle, it can either synthesize an interrupt event or suppress the event.
  6. Lower resource usage - Elixir/ALE created a GenServer and OS process for each GPIO. Circuits.GPIO creates a NIF resource (Elixir Reference) for each GPIO.

Circuits.GPIO uses Erlang's NIF interface. NIFs have the downside of being able to crash the Erlang VM. Experience with Elixir/ALE has given many of us confidence that this won't be a problem.

Code modifications

Circuits.GPIO is not a GenServer, so if you've added ElixirALE.GPIO to a supervision tree, you'll have to take it out and manually call Circuits.GPIO.open to obtain a reference. A common pattern is to create a GenServer that wraps the GPIO and is descriptive of what the GPIO controls or signals. Put the Circuits.GPIO.open call in your init/1 callback.

The remain modifications should mostly be mechanical:

  1. Rename references to ElixirALE.GPIO to Circuits.GPIO and elixir_ale to circuits_gpio
  2. Change calls to ElixirALE.GPIO.start_link/2 to Circuits.GPIO.open/2. While you're at it, review the arguments to open to not include any GenServer options.
  3. Change calls to ElixirALE.GPIO.set_int/2 to Circuits.GPIO.set_interrupts/3.
  4. Change the pattern match for the GPIO interrupt events to match 4 tuples. They have the form {:circuits_gpio, <pin_number>, <timestamp>, <value>}
  5. Review calls to write/2 to ensure that they pass 0 or 1. ElixirALE allowed users to pass true and false. That won't work. Running Dialyzer should catch this change as well.
  6. Consider adding a call to Circuits.GPIO.close/1 if there's an obvious place to release the GPIO. This is not strictly necessary since the garbage collector will free unreferenced GPIOs.

If you find that you have to make any other changes, please let us know via an issue or PR so that other users can benefit.