Circuits.GPIO (circuits_gpio v2.3.0)

Copy Markdown View Source

Control GPIOs from Elixir

See the Readme for a tutorial and the porting guide if updating from Circuits.GPIO v1.x.

Simple example:

# GPIO 2 is connected to GPIO 3
iex> {:ok, my_output_gpio} = Circuits.GPIO.open({"gpiochip0", 2}, :output)
iex> {:ok, my_input_gpio} = Circuits.GPIO.open({"gpiochip0", 3}, :input)
iex> Circuits.GPIO.write(my_output_gpio, 1)
:ok
iex> Circuits.GPIO.read(my_input_gpio)
1
iex> Circuits.GPIO.close(my_output_gpio)
iex> Circuits.GPIO.close(my_input_gpio)

Summary

Types

Backends specify an implementation of a Circuits.GPIO.Backend behaviour

GPIO controller

The GPIO direction (input or output)

Drive mode for platforms that support push-pull, open-drain, and open-source

An identifier for a GPIO

Ways of referring to a GPIO

Options for set_interrupt/2

A GPIO controller label or GPIO label

GPIO line offset on a controller

Options for open/3

Pull mode for platforms that support controllable pullups and pulldowns

Dynamic GPIO configuration and status

Trigger edge for pin change notifications

Value of one or more GPIOs

Functions

Return info about the low level GPIO interface

Release the resources associated with a GPIO

Return a list of accessible GPIOs

Close GPIO handles by gpio spec or identifier

Return if a term looks like a gpio_spec

Return identifying information about a GPIO

Guard version of gpio_spec?/1

Read a GPIO's value

Change the direction of the pin

Set the drive mode (push-pull, open-drain, or open-source)

Enable or disable GPIO value change notifications

Enable or disable an internal pull-up or pull-down resistor

Return dynamic configuration and status information about a GPIO or handle

Subscribe to GPIO value change notifications

Stop GPIO value change notifications started with subscribe/2

Set the value of a GPIO

Types

backend()

@type backend() :: {module(), keyword()}

Backends specify an implementation of a Circuits.GPIO.Backend behaviour

The second parameter of the Backend 2-tuple is a list of options. These are passed to the behaviour function call implementations.

controller()

@type controller() :: String.t()

GPIO controller

GPIO controllers manage one or more GPIO lines. They're referred to by strings. For example, controllers are named "gpiochip0", etc. for the Linux cdev backend. Other backends may have similar conventions or use the empty string if there's only one controller.

direction()

@type direction() :: :input | :output

The GPIO direction (input or output)

drive_mode()

@type drive_mode() :: :push_pull | :open_drain | :open_source

Drive mode for platforms that support push-pull, open-drain, and open-source

gpio_spec()

@type gpio_spec() ::
  non_neg_integer()
  | {controller(), line_offset()}
  | label()
  | {controller(), label()}

An identifier for a GPIO

Call Circuits.GPIO.enumerate/0 to see what GPIOs are available on your device. Several ways exist to refer to GPIOs due to variations in devices and programmer preference. Most Raspberry Pi models have labels like "GPIO26". The Raspberry Pi 5 has labels based on physical location (e.g., "PIN37" for GPIO 26.)

Options:

  1. index - Many examples exist where GPIOs are referred to by a GPIO number. There are issues with this strategy since GPIO indices can change. It is so common that it's still supported. Prefer other ways when you're able to change code.
  2. {controller_name, line_offset} - Specify a line on a specific GPIO controller. E.g., {"gpiochip0", 10}
  3. label - Specify a GPIO label. The first controller that has a matching GPIO is used. This lets you move the mapping of GPIOs to peripheral connections to a device tree file or other central place. E.g., "LED_ENABLE"
  4. {controller_name, label} - Specify both GPIO controller and GPIO labels. E.g., {"gpiochip4", "PIO4"}

identifiers()

@type identifiers() :: %{
  location: {controller(), non_neg_integer()},
  controller: controller(),
  label: label()
}

Ways of referring to a GPIO

It's possible to refer to a GPIOs in many ways and this map contains information for doing that. See enumerate/1 and identifiers/1 for querying Circuits.GPIO for these maps.

The information in this map is backend specific. At a minimum, all backends provide the :location field which is an unambiguous gpio_spec/0 for use with open/3.

When provided, the :label field is a string name for the GPIO that should be unique to the system but this isn't guaranteed. A common convention is to label GPIOs by their pin names in documentation or net names in schematics. The Linux cdev backend uses labels from the device tree file.

Fields:

  • :location - this is the canonical gpio_spec for a GPIO.
  • :label - an optional label for the GPIO that may indicate what the GPIO is connected to or be more helpful that the :location. It may be passed to GPIO.open/3.
  • :controller - the name or an alias for the GPIO controller. Empty string if unused

interrupt_options()

@type interrupt_options() :: [suppress_glitches: boolean(), receiver: pid() | atom()]

Options for set_interrupt/2

label()

@type label() :: String.t()

A GPIO controller label or GPIO label

Labels provide aliases for GPIO lines and controllers. They're system-specific. On Linux, labels are provided in device tree files.

line_offset()

@type line_offset() :: non_neg_integer()

GPIO line offset on a controller

GPIOs are numbered based on how they're connected to a controller. The details are controller specific, but usually the first one is 0, then 1, etc.

open_options()

@type open_options() :: [
  initial_value: value(),
  pull_mode: pull_mode(),
  drive_mode: drive_mode(),
  on_busy: :take_over | :error,
  force_enumeration: boolean()
]

Options for open/3

  • :initial_value - the initial value of an output GPIO
  • :pull_mode - the initial pull mode for an input GPIO
  • :drive_mode - the drive mode of an output GPIO
  • :on_busy - behavior when an open fails because the GPIO is already open; :error returns the error (default), :take_over closes other references and retries.
  • :force_enumeration - Linux cdev-specific option to force a scan of available GPIOs rather than using the cache. This is only for test purposes since the GPIO cache should refresh as needed.

pull_mode()

@type pull_mode() :: :not_set | :none | :pullup | :pulldown

Pull mode for platforms that support controllable pullups and pulldowns

status()

@type status() :: %{
  consumer: String.t(),
  direction: direction(),
  pull_mode: pull_mode(),
  drive_mode: drive_mode()
}

Dynamic GPIO configuration and status

Fields:

  • :consumer - if this GPIO is in use, this optional string gives a hint as to who is using it.
  • :direction - whether this GPIO is an input or output
  • :pull_mode - if this GPIO is an input, then this is the pull mode
  • :drive_mode - how the GPIO is driven (push-pull, open-drain, or open-source)

subscribe_options()

@type subscribe_options() :: [
  trigger: trigger(),
  receiver: pid() | atom(),
  tag: term()
]

Options for subscribe/2

  • :receiver - process that should receive notifications. Defaults to the calling process (self()).
  • :tag - a term echoed in the :ref field of every notification instead of the auto-generated reference. Use this to route messages with a domain-specific label.
  • :trigger - send notifications on the :rising edges, :falling edges, or :both. Defaults to :both.

trigger()

@type trigger() :: :rising | :falling | :both | :none

Trigger edge for pin change notifications

value()

@type value() :: non_neg_integer()

Value of one or more GPIOs

In the common single-GPIO case, the value is either 0 (low) or 1 (high). When opening groups of GPIOs, the constituent GPIOs are ordered starting from least significant bit (bit 0).

Functions

backend_info(backend \\ nil)

@spec backend_info(backend() | nil) :: map()

Return info about the low level GPIO interface

This may be helpful when debugging issues.

close(handle)

@spec close(Circuits.GPIO.Handle.t()) :: :ok

Release the resources associated with a GPIO

This is optional. The garbage collector will free GPIO resources that aren't in use, but this will free them sooner.

enumerate(backend \\ nil)

@spec enumerate(backend() | nil) :: [identifiers()]

Return a list of accessible GPIOs

Each GPIO is described in a identifiers/0 map. Some fields in the map like :location and :label may be passed to open/3 to use the GPIO. The map itself can also be passed to open/3 and the function will figure out how to access the GPIO.

force_close(gpio_specs)

@spec force_close(gpio_spec() | identifiers() | [gpio_spec() | identifiers()]) ::
  :ok | {:error, atom()}

Close GPIO handles by gpio spec or identifier

This is useful when a process has lost its handles or when GPIO hardware is being reconfigured. After this call, any use of the old file handles will raise an exception.

See the :on_busy option in the open_options/0 docs for automatically taking over GPIOs when calling open/3.

gpio_spec?(x)

@spec gpio_spec?(any()) :: boolean()

Return if a term looks like a gpio_spec

This function only verifies that the term has the right shape to be a gpio_spec/0. Whether or not it refers to a usable GPIO is checked by Circuits.GPIO.open/3.

identifiers(gpio_spec)

@spec identifiers(gpio_spec()) :: {:ok, identifiers()} | {:error, atom()}

Return identifying information about a GPIO

See gpio_spec/0 for the ways of referring to GPIOs. If the GPIO is found, this function returns information about the GPIO.

is_gpio_spec(x)

(macro)

Guard version of gpio_spec?/1

Add require Circuits.GPIO to your source file to use this guard.

open(gpio_spec_or_line_info, direction, options \\ [])

@spec open(
  gpio_spec() | identifiers() | [gpio_spec() | identifiers()],
  direction(),
  open_options()
) :: {:ok, Circuits.GPIO.Handle.t()} | {:error, atom()}

Open one or more GPIOs

See gpio_spec/0 for the ways of referring to GPIOs. Set direction to either :input or :output. If opening as an output, then be sure to set the :initial_value option to minimize the time the GPIO is in the default state.

Groups

Passing a list of GPIO specs opens them together as a group. read/1 then returns a single integer and write/2 takes one. The first GPIO in the list is the least significant bit (bit 0), the second is bit 1, and so on:

iex> {:ok, bus} = Circuits.GPIO.open([{"gpiochip0", 2}, {"gpiochip0", 4}], :output)
iex> Circuits.GPIO.write(bus, 0b10)   # GPIO 2 -> 0, GPIO 4 -> 1
:ok
iex> Circuits.GPIO.close(bus)
:ok

All GPIOs in a group must be on the same controller. :initial_value is interpreted as an integer with the same one-bit-per-line layout, and :pull_mode/:drive_mode apply to every line in the group.

Exclusivity

Each GPIO may only have one open handle at a time. This is enforced by the backend, so while one backend may support it, it's not guaranteed. Library authors should assume exclusivity on GPIOs to be cross platform. In practice, this isn't a big deal except when debugging.

Use the :on_busy option to :take_over a GPIO from another open handle when it's busy. This is useful for GenServers when they restart to avoid waiting for the BEAMs GC to clean up the old handle. It's also handy when experimenting at the IEx prompt when you lose a reference. The :on_busy option only works for handles known to the running BEAM instance.

Troubleshooting

The most common issue is figuring out the names or labels on GPIOs. See enumerate/0 for available GPIOs. On Linux, labels are defined in the device tree.

If you suspect a hardware or driver issue, see Circuits.GPIO.Diagnostics.

If you're getting {:error, :already_open}, try passing on_busy: :take_over to try taking over the GPIO from a lost or outdated reference.

Options:

  • :initial_value - Set to 0 or 1 (or an integer with one bit per line for a group). Only used for outputs. Defaults to 0.
  • :pull_mode - Set to :not_set, :pullup, :pulldown, or :none for an input pin. :not_set is the default.
  • :drive_mode - Set to :push_pull, :open_drain, or :open_source. :push_pull is the default. Only used for outputs.
  • :on_busy - Set to :take_over or :error. :take_over will try closing other GPIO users to allow this call to succeed. Defaults to :error.

Returns {:ok, handle} on success.

read(handle)

@spec read(Circuits.GPIO.Handle.t()) :: value()

Read a GPIO's value

For a group opened with a list of GPIO specs, this returns an integer with the first GPIO as bit 0, the second as bit 1, and so on. See value/0.

The value returned for GPIO's that are configured as outputs is undefined. Backends may choose not to support this.

read_one(gpio_spec, options \\ [])

@spec read_one(gpio_spec(), open_options()) :: value() | {:error, atom()}

One line GPIO read

This is a convenience function that opens, reads, and closes a GPIO. It's intended to simplify one-off reads in code and for IEx prompt use.

Prefer using handles in other situations.

set_direction(handle, direction)

@spec set_direction(Circuits.GPIO.Handle.t(), direction()) :: :ok | {:error, atom()}

Change the direction of the pin

set_drive_mode(gpio, drive_mode)

@spec set_drive_mode(Circuits.GPIO.Handle.t(), drive_mode()) :: :ok | {:error, atom()}

Set the drive mode (push-pull, open-drain, or open-source)

set_interrupts(handle, trigger, options \\ [])

@spec set_interrupts(Circuits.GPIO.Handle.t(), trigger(), interrupt_options()) ::
  :ok | {:error, atom()}

Enable or disable GPIO value change notifications

New code should prefer subscribe/2, which delivers a map (with the value and previous value), works with GPIO groups, and returns a reference for matching messages. set_interrupts/3 remains for backwards compatibility and sends the tuple described below. It cannot be used with a group handle.

Notifications are sent based on the trigger:

  • :none - No notifications are sent
  • :rising - Send a notification when the pin changes from 0 to 1
  • :falling - Send a notification when the pin changes from 1 to 0
  • :both - Send a notification on all changes

Available Options:

  • :suppress_glitches - Not supported in Circuits.GPIO v2
  • :receiver - Process which should receive the notifications. Defaults to the calling process (self())

Notification messages look like:

{:circuits_gpio, gpio_spec, timestamp, value}

Where gpio_spec is the gpio_spec/0 passed to open/3, timestamp is an OS monotonic timestamp in nanoseconds, and value is the new value.

Timestamps are not necessarily the same as from System.monotonic_time/0. For example, with the cdev backend, they come the Linux kernel. It's also possible for them to come from a monotonic hardware timer. Both may be different from Erlang's monotonic time. The takeaway is that these timestamps can be compared with each other, but be careful when comparing them to anything else.

NOTE: You will need to store the Circuits.GPIO reference somewhere (like your GenServer's state) so that it doesn't get garbage collected. Event messages stop when it gets collected. If you only get one message and you are expecting more, this is likely the case.

set_pull_mode(gpio, pull_mode)

@spec set_pull_mode(Circuits.GPIO.Handle.t(), pull_mode()) :: :ok | {:error, atom()}

Enable or disable an internal pull-up or pull-down resistor

status(handle)

@spec status(gpio_spec() | Circuits.GPIO.Handle.t()) ::
  {:ok, status()} | {:error, atom()}

Return dynamic configuration and status information about a GPIO or handle

Pass a gpio_spec/0 to query a GPIO without opening it, or a t:Handle.t/0 to query an open GPIO. If the GPIO is found, this function returns information about the GPIO. Status for GPIO groups is not supported.

subscribe(handle, options \\ [])

@spec subscribe(Circuits.GPIO.Handle.t(), subscribe_options()) ::
  {:ok, term()} | {:error, atom()}

Subscribe to GPIO value change notifications

This is the preferred way to receive change notifications and the only way to receive them for a group (a handle opened with a list of GPIO specs). It returns {:ok, ref} where ref is a reference echoed in every notification so you can match messages to this subscription.

Available options:

  • :receiver - process that should receive notifications. Defaults to the calling process (self()).
  • :tag - a term echoed in the :ref field instead of the auto-generated reference.
  • :trigger - send notifications on the :rising, :falling, or :both edges. Defaults to :both.

Notification messages are maps:

{:circuits_gpio, %{ref: ref, timestamp: timestamp, value: value, previous_value: previous_value}}

Where ref is the reference returned by this function (or your :tag), timestamp is an OS monotonic timestamp in nanoseconds, value is the new value (an integer with one bit per line for a group), and previous_value is the previously reported value. Bitwise.bxor(value, previous_value) has the changed bits. It's possible to receive reports with no changes due to transients.

Timestamps are not necessarily the same as from System.monotonic_time/0. For example, with the cdev backend, they're applied by the Linux kernel or can be come from a hardware timer. Erlang's monotonic time is adjusted so it's not the same as OS monotonic time. The result is that these timestamps can be compared with each other, but not with anything else.

NOTE: You will need to keep the handle from open/3 (for example, in your GenServer's state) so that it isn't garbage collected. Notifications stop when it is collected.

unsubscribe(handle)

@spec unsubscribe(Circuits.GPIO.Handle.t()) :: :ok | {:error, atom()}

Stop GPIO value change notifications started with subscribe/2

write(handle, value)

@spec write(Circuits.GPIO.Handle.t(), value()) :: :ok

Set the value of a GPIO

The GPIO must be configured as an output.

For a group opened with a list of GPIO specs, pass an integer with the first GPIO as bit 0, the second as bit 1, and so on. See value/0.

write_one(gpio_spec, value, options \\ [])

@spec write_one(gpio_spec(), value(), open_options()) :: :ok | {:error, atom()}

One line GPIO write

This is a convenience function that opens, writes, and closes a GPIO. It's intended to simplify one-off writes in code and for IEx prompt use.

Prefer using handles in other situations.