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
Options for subscribe/2
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
Open one or more GPIOs
Read a GPIO's value
One line GPIO read
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
One line GPIO write
Types
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.
@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.
@type direction() :: :input | :output
The GPIO direction (input or output)
@type drive_mode() :: :push_pull | :open_drain | :open_source
Drive mode for platforms that support push-pull, open-drain, and open-source
@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:
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.{controller_name, line_offset}- Specify a line on a specific GPIO controller. E.g.,{"gpiochip0", 10}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"{controller_name, label}- Specify both GPIO controller and GPIO labels. E.g.,{"gpiochip4", "PIO4"}
@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 toGPIO.open/3.:controller- the name or an alias for the GPIO controller. Empty string if unused
Options for set_interrupt/2
@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.
@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.
@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;:errorreturns the error (default),:take_overcloses 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.
@type pull_mode() :: :not_set | :none | :pullup | :pulldown
Pull mode for platforms that support controllable pullups and pulldowns
@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)
Options for subscribe/2
:receiver- process that should receive notifications. Defaults to the calling process (self()).:tag- a term echoed in the:reffield of every notification instead of the auto-generated reference. Use this to route messages with a domain-specific label.:trigger- send notifications on the:risingedges,:fallingedges, or:both. Defaults to:both.
@type trigger() :: :rising | :falling | :both | :none
Trigger edge for pin change notifications
@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
Return info about the low level GPIO interface
This may be helpful when debugging issues.
@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.
@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.
@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.
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.
@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.
Guard version of gpio_spec?/1
Add require Circuits.GPIO to your source file to use this guard.
@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)
:okAll 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 to0or1(or an integer with one bit per line for a group). Only used for outputs. Defaults to0.:pull_mode- Set to:not_set,:pullup,:pulldown, or:nonefor an input pin.:not_setis the default.:drive_mode- Set to:push_pull,:open_drain, or:open_source.:push_pullis the default. Only used for outputs.:on_busy- Set to:take_overor:error.:take_overwill try closing other GPIO users to allow this call to succeed. Defaults to:error.
Returns {:ok, handle} on success.
@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.
@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.
@spec set_direction(Circuits.GPIO.Handle.t(), direction()) :: :ok | {:error, atom()}
Change the direction of the pin
@spec set_drive_mode(Circuits.GPIO.Handle.t(), drive_mode()) :: :ok | {:error, atom()}
Set the drive mode (push-pull, open-drain, or open-source)
@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.
@spec set_pull_mode(Circuits.GPIO.Handle.t(), pull_mode()) :: :ok | {:error, atom()}
Enable or disable an internal pull-up or pull-down resistor
@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.
@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:reffield instead of the auto-generated reference.:trigger- send notifications on the:rising,:falling, or:bothedges. 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.
@spec unsubscribe(Circuits.GPIO.Handle.t()) :: :ok | {:error, atom()}
Stop GPIO value change notifications started with subscribe/2
@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.
@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.