Circuits.SPI (circuits_spi v2.2.0)

Copy Markdown View Source

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.Backend

This 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 and raise on error

Types

backend()

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

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.

spi_option()

@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.

spi_option_map()

@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

bus_names()

@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"]

close(spi_bus)

@spec close(Circuits.SPI.Bus.t()) :: :ok

Release any resources associated with the given SPI bus

config(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.

info(backend \\ nil)

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

Return information about the low-level SPI interface

This may be helpful when debugging SPI issues.

max_transfer_size(bus \\ nil)

@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.

open(bus_name, options \\ [])

@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_name is the name of the bus (e.g., "spidev0.0"). See bus_names/0
  • options is 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 to 0:
    • 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, from 8 to 16. Defaults to 8.
  • :speed_hz - Set the clock frequency in Hz. Must be a positive integer. Defaults to 1_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 to 0.
  • :lsb_first - Set to true to send the least significant bit first rather than the most significant bit. Defaults to false. If the hardware does not support LSB-first mode, Circuits.SPI reverses the bits in software. The hardware may print unsupported mode bits 8 in 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)

read(spi_bus, len)

@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.

read!(spi_bus, len)

@spec read!(Circuits.SPI.Bus.t(), pos_integer()) :: binary()

Read data and raise on error

transfer(spi_bus, data)

@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

transfer!(spi_bus, data)

@spec transfer!(Circuits.SPI.Bus.t(), iodata()) :: binary()

Transfer data and raise on error

write(spi_bus, data)

@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.

write!(spi_bus, data)

@spec write!(Circuits.SPI.Bus.t(), iodata()) :: :ok

Write data and raise on error