This module enables Elixir programs to interact with hardware that's connected via an SPI bus.
Backends
The :default_backend application configuration selects the backend used by
open/2, bus_names/0, and info/0. Set it in config/config.exs before
compiling dependencies. For example, to use simulated SPI devices:
import Config
config :circuits_spi, default_backend: CircuitsSim.SPI.BackendThis requires the :circuits_sim dependency and configured simulated devices.
See the README's CircuitsSim example
for a complete setup.
The value may be a backend module or a {backend_module, default_options}
tuple. Options passed to open/2 override the backend's default options.
Alternative backends implement Circuits.SPI.Backend and return bus values
that implement the Circuits.SPI.Bus protocol.
Summary
Types
Backends specify an implementation of the Circuits.SPI.Backend behaviour
SPI bus options
SPI bus options as returned by config/1.
Functions
Return a list of available SPI bus names. If the list is empty,
it's possible that the kernel driver for that SPI bus is not enabled or the
kernel's device tree is not configured. On Raspbian, run raspi-config and
look in the advanced options.
Release any resources associated with the given SPI bus
Return the configuration for this SPI bus
Return information about the low-level SPI interface
Return the maximum size of a single low-level transfer in bytes
Open an SPI bus device
Read len bytes
Read data and raise on error
Transfer data
Transfer data and raise on error
Write data
Write data and raise on error
Types
Backends specify an implementation of the Circuits.SPI.Backend behaviour
The second element of the backend 2-tuple is a list of options. These are passed to the backend's callback implementations.
@type spi_option() :: {:mode, 0..3} | {:bits_per_word, 8..16} | {:speed_hz, pos_integer()} | {:delay_us, non_neg_integer()} | {:lsb_first, boolean()}
SPI bus options
See open/2 for option descriptions and defaults.
@type spi_option_map() :: %{ mode: 0..3, bits_per_word: 8..16, speed_hz: pos_integer(), delay_us: non_neg_integer(), lsb_first: boolean(), sw_lsb_first: boolean() }
SPI bus options as returned by config/1.
These mirror the options that can be passed to open/2. :sw_lsb_first
is set if :lsb_first is true, but Circuits.SPI is doing this in software.
Functions
@spec bus_names() :: [binary()]
Return a list of available SPI bus names. If the list is empty,
it's possible that the kernel driver for that SPI bus is not enabled or the
kernel's device tree is not configured. On Raspbian, run raspi-config and
look in the advanced options.
iex> Circuits.SPI.bus_names
["spidev0.0", "spidev0.1"]
@spec close(Circuits.SPI.Bus.t()) :: :ok
Release any resources associated with the given SPI bus
@spec config(Circuits.SPI.Bus.t()) :: {:ok, spi_option_map()} | {:error, term()}
Return the configuration for this SPI bus
The configuration could differ from what was given to open/2 if
the device had to adjust it to work.
Return information about the low-level SPI interface
This may be helpful when debugging SPI issues.
@spec max_transfer_size(Circuits.SPI.Bus.t() | nil) :: non_neg_integer()
Return the maximum size of a single low-level transfer in bytes
Pass a bus returned by open/2 to query its backend's limit. For example,
the Linux spidev driver limits each transfer based on its bufsiz parameter.
This is not necessarily the maximum message size accepted by the backend.
The Linux backend automatically splits larger transfer/2, write/2, and
read/2 operations into multiple transfers. Chip select is deasserted between
them, so use this limit to determine whether a command fits in one transfer.
See transfer/2 for details.
Calling this function without a bus, or with nil, returns 0. It does not
query the configured backend.
@spec open(binary(), [spi_option()]) :: {:ok, Circuits.SPI.Bus.t()} | {:error, term()}
Open an SPI bus device
On success, open/2 returns {:ok, bus}, where bus is a backend-specific
value that implements Circuits.SPI.Bus. Pass it to transfer/2 and the
other bus operations.
The Linux backend releases the device when the bus's underlying resource is
garbage collected. Call close/1 to release it promptly rather than waiting
for garbage collection.
Parameters
bus_nameis the name of the bus (e.g., "spidev0.0"). Seebus_names/0optionsis a keyword list to configure the bus
Options
The Linux backend supports the following options and defaults. Supported settings depend on the controller and peripheral. Other backends may have different defaults or support additional options.
:mode- Set the clock polarity and phase. Defaults to0:- Mode 0 (CPOL=0, CPHA=0) - Clock idle low; sample on the leading edge
- Mode 1 (CPOL=0, CPHA=1) - Clock idle low; sample on the trailing edge
- Mode 2 (CPOL=1, CPHA=0) - Clock idle high; sample on the leading edge
- Mode 3 (CPOL=1, CPHA=1) - Clock idle high; sample on the trailing edge
:bits_per_word- Set the number of bits per word, from8to16. Defaults to8.:speed_hz- Set the clock frequency in Hz. Must be a positive integer. Defaults to1_000_000(1 MHz). Supported speeds are device-specific.:delay_us- Set the delay after a transfer, before chip select is deasserted, in microseconds. Must be a non-negative integer. Defaults to0.:lsb_first- Set totrueto send the least significant bit first rather than the most significant bit. Defaults tofalse. If the hardware does not support LSB-first mode, Circuits.SPI reverses the bits in software. The hardware may printunsupported mode bits 8in this case; this message can be ignored.
Options passed here override the configured backend's default options.
Use config/1 to check the configuration actually selected by the backend.
Examples
{:ok, spi} = Circuits.SPI.open("spidev0.0", mode: 0, speed_hz: 500_000)
@spec read(Circuits.SPI.Bus.t(), pos_integer()) :: {:ok, binary()} | {:error, term()}
Read len bytes
This works identically to transfer/2 except that the bits written are whatever
the controller chooses. The expectation is that the device on the other side
is ignoring them anyway.
@spec read!(Circuits.SPI.Bus.t(), pos_integer()) :: binary()
Read data and raise on error
@spec transfer(Circuits.SPI.Bus.t(), iodata()) :: {:ok, binary()} | {:error, term()}
Transfer data
Since each SPI transfer sends and receives simultaneously, success returns
{:ok, received}, where received is a binary with the same byte count as
data. For nested iodata, this is IO.iodata_length(data).
The Linux backend automatically splits large data buffers into chunks no
larger than max_transfer_size/1. Manual splitting is not required. Each
chunk is a separate SPI transfer: chip select is deasserted between chunks,
and there may be a short pause. If your device requires chip select to remain
asserted throughout a command, keep the command within this limit. Other
backends may have different transfer limits and segmentation behavior.
If you have an operation that writes a number of bytes and then reads data back,
a common pattern is to use iodata/0. This example writes 0x1 and 0xff and
then reads 100 bytes all in one transfer.
iex> {:ok, <<_, _, result::binary>>} = Circuits.SPI.transfer(spi, [<<0x1, 0xff>>, :binary.copy(<<0>>, 100)])
iex> byte_size(result)
100
@spec transfer!(Circuits.SPI.Bus.t(), iodata()) :: binary()
Transfer data and raise on error
@spec write(Circuits.SPI.Bus.t(), iodata()) :: :ok | {:error, term()}
Write data
This works identically to transfer/2 except that it ignores all received data.
@spec write!(Circuits.SPI.Bus.t(), iodata()) :: :ok
Write data and raise on error