### Example Custom Backend Implementation Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/backend-behaviour.md Provides a complete example of a custom SPI backend module implementing the `Circuits.SPI.Backend` behavior, including `bus_names/1`, `open/2`, and `info/0` callbacks. ```elixir defmodule MyCustomBackend do @behaviour Circuits.SPI.Backend @impl Backend def bus_names(_options) do ["custom_bus_0", "custom_bus_1"] end @impl Backend def open(bus_name, options) do # Custom initialization logic case init_hardware(bus_name, options) do {:ok, handle} -> {:ok, %MyBusReference{handle: handle}} {:error, reason} -> {:error, reason} end end @impl Backend def info() do %{backend: __MODULE__, name: "custom_spi_backend"} end # Private helper defp init_hardware(_bus_name, _options) do {:ok, nil} # Your implementation end end # Then implement the Bus protocol for your reference type defimpl Circuits.SPI.Bus, for: MyBusReference do def config(_bus), do: {:ok, %{mode: 0, bits_per_word: 8, speed_hz: 1_000_000, delay_us: 10, lsb_first: false, sw_lsb_first: false}} def transfer(_bus, data), do: {:ok, data} def close(_bus), do: :ok def max_transfer_size(_bus), do: 0 end ``` -------------------------------- ### Example Info Return Value (NilBackend) Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/backend-behaviour.md Shows a minimal return value for the `info/0` callback from the `Circuits.SPI.NilBackend`. ```elixir %{name: Circuits.SPI.NilBackend} ``` -------------------------------- ### Example SPIDev Backend Implementation Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/backend-behaviour.md A sample implementation of the `Circuits.SPI.Backend` behavior for the `SPIDev` backend, demonstrating the `info/0` callback. ```elixir defmodule Circuits.SPI.SPIDev do @behaviour Circuits.SPI.Backend @impl Backend def info() do Circuits.SPI.Nif.info() |> Map.put(:backend, __MODULE__) end end ``` -------------------------------- ### Example Implementation of Circuits.SPI.Bus Protocol Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/bus-protocol.md An example implementation of the Circuits.SPI.Bus protocol for `Circuits.SPI.SPIDev`. This shows how a backend wraps the actual hardware interface. ```elixir defimpl Circuits.SPI.Bus, for: Circuits.SPI.SPIDev do def config(%Circuits.SPI.SPIDev{ref: ref}) do Circuits.SPI.Nif.config(ref) end def transfer(%Circuits.SPI.SPIDev{ref: ref}, data) do Circuits.SPI.Nif.transfer(ref, data) end def close(%Circuits.SPI.SPIDev{ref: ref}) do Circuits.SPI.Nif.close(ref) end def max_transfer_size(%Circuits.SPI.SPIDev{}) do Circuits.SPI.Nif.max_transfer_size() end end ``` -------------------------------- ### Example Info Return Value (SPIDev) Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/backend-behaviour.md Illustrates a typical return value for the `info/0` callback from the `Circuits.SPI.SPIDev` backend, including hardware and kernel details. ```elixir %{backend: Circuits.SPI.SPIDev, name: "spidev", kernel_module_version: "3.14", ...} ``` -------------------------------- ### Cross-Compilation Setup for Nerves Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Essential environment variables and commands for setting up cross-compilation for Nerves builds. Ensure these are set before fetching dependencies and compiling. ```bash export CROSSCOMPILE=1 export MIX_TARGET=rpi4 # or your board mix deps.get mix compile ``` -------------------------------- ### Open an SPI Device and Read Potentiometer Source: https://github.com/elixir-circuits/circuits_spi/blob/main/README.md This example demonstrates opening an SPI device and reading data from a potentiometer connected to an ADC. It shows how to use binary pattern matching to extract relevant data and convert it to voltage. ```elixir # Make sure that you've enabled or loaded the SPI driver or this will # fail. iex> {:ok, ref} = Circuits.SPI.open("spidev0.0") {:ok, #Reference<...>} # Read the potentiometer # Use binary pattern matching to pull out the ADC counts (low 10 bits) iex> {:ok, <<_::size(6), counts::size(10)>>} = Circuits.SPI.transfer(ref, <<0x78, 0x00>>) {:ok, <<1, 197>>} iex> counts 453 # Convert counts to volts (1023 = 3.3 V) iex> volts = counts / 1023 * 3.3 1.461290322580645 ``` -------------------------------- ### Override Default Backend at Startup Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Configure the default SPI backend by setting an environment variable at application startup. This example shows how to dynamically select a backend based on the SPI_BACKEND environment variable. ```elixir defmodule MyApp.Application do use Application def start(_type, _args) do # Override default backend backend = select_backend() Application.put_env(:circuits_spi, :default_backend, backend) children = [ # ... other children ] Supervisor.start_link(children, strategy: :one_for_one) end defp select_backend do case System.get_env("SPI_BACKEND") do "test" -> {Circuits.SPI.SPIDev, [test: true]} "mock" -> MyApp.MockBackend _ -> Circuits.SPI.SPIDev end end end ``` -------------------------------- ### Configure SPI Backend Conditionally Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Dynamically configure the SPI backend based on the operating system type. This example uses `Circuits.SPI.SPIDev` for Linux and `Circuits.SPI.NilBackend` for other systems, ensuring appropriate behavior across different environments. ```elixir defmodule MyApp.SPIConfig do def configure do backend = case :os.type() do {:unix, :linux} -> Circuits.SPI.SPIDev _ -> Circuits.SPI.NilBackend end Application.put_env(:circuits_spi, :default_backend, backend) end end # In your application startup MyApp.SPIConfig.configure() ``` -------------------------------- ### Configure Multiple SPI Backends Per Application Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Configure different SPI backends for different applications within a project. This example shows `app1` using the real hardware backend and `app2` using a simulated backend. ```elixir # app1 uses real hardware config :circuits_spi, :default_backend, Circuits.SPI.SPIDev # app2 uses simulation config :app2, :spi_backend, Circuits.SIM.SPIDev ``` -------------------------------- ### Open SPI Device (NilBackend) Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/nil-backend.md When the NilBackend is active, attempting to open an SPI device returns an error tuple indicating the operation is unimplemented. This example shows how to handle this error. ```elixir case Circuits.SPI.open("spidev0.0") do {:ok, _spi} -> :ok {:error, :unimplemented} -> IO.puts("SPI not available on this system") end ``` -------------------------------- ### Get SPI Backend Information Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/README.md Retrieves information about the currently configured SPI backend. ```elixir iex> Circuits.SPI.info() %{backend: Circuits.SPI.SPIDev, ...} ``` -------------------------------- ### Handle :einval Error for Invalid SPI Configuration Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/errors.md Shows how to deal with the :einval error, which arises from an invalid SPI configuration not supported by the hardware. The example includes a fallback to default settings if the initial configuration fails. ```elixir case Circuits.SPI.open(bus_name, options) do {:ok, spi} -> {:ok, spi} {:error, :einval} -> IO.puts("Invalid configuration for device") IO.puts("Trying with defaults...") Circuits.SPI.open(bus_name) end ``` -------------------------------- ### Example Usage: Handling Large Data Transfers Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/bus-protocol.md Demonstrates how to use `max_transfer_size/1` to determine if data needs to be split into chunks for transfer. This is useful when dealing with SPI hardware that has a maximum buffer size for single transfers. ```elixir {:ok, spi} = Circuits.SPI.open("spidev0.0") max_size = Circuits.SPI.max_transfer_size(spi) large_data = <<1::size(1024000)>> # 128 KB if max_size > 0 and byte_size(large_data) > max_size do # Split into chunks large_data |> Enum.chunk_every(max_size) |> Enum.each(&Circuits.SPI.transfer(spi, &1)) else # Send in one go Circuits.SPI.transfer(spi, large_data) end ``` -------------------------------- ### Handle :enoent Error When Opening SPI Device Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/errors.md Demonstrates how to handle the :enoent error, which occurs when the SPI device file does not exist. It suggests checking available device names and provides a code example for graceful error handling. ```elixir case Circuits.SPI.open(bus_name) do {:ok, spi} -> {:ok, spi} {:error, :enoent} -> IO.puts("SPI device not found: #{bus_name}") IO.puts("Available devices: #{Enum.join(Circuits.SPI.bus_names(), ", ")}") {:error, "Device not found"} end ``` -------------------------------- ### Get SPI Backend Info Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/circuits-spi.md Retrieves debugging information about the SPI backend. Can query the default backend or a specific one. ```elixir # Get info about the default backend info = Circuits.SPI.info() IO.inspect(info) # => %{backend: Circuits.SPI.SPIDev, name: "spidev", ...} # Get info about a specific backend info = Circuits.SPI.info({Circuits.SPI.SPIDev, []}) ``` -------------------------------- ### Handle :eacces Error When Opening SPI Device Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/errors.md Illustrates how to manage the :eacces error, indicating permission denied for accessing the SPI device. The example suggests checking user group permissions and provides a code snippet for handling this error. ```elixir case Circuits.SPI.open(bus_name) do {:ok, spi} -> {:ok, spi} {:error, :eacces} -> IO.puts("Permission denied. Try:") IO.puts(" sudo usermod -aG spi $USER") IO.puts(" # Then logout and login again") {:error, "Permission denied"} end ``` -------------------------------- ### Get NilBackend Information Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/nil-backend.md Retrieves basic information about the NilBackend, which includes its module name. This is useful for introspection. ```elixir info = Circuits.SPI.info() IO.inspect(info) # => %{name: Circuits.SPI.NilBackend} ``` -------------------------------- ### Select SPI Backend via Environment Variable Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Demonstrates how to set the SPI_BACKEND environment variable to select a specific backend and run the application. This is used in conjunction with the application's startup configuration. ```bash export SPI_BACKEND=test mix run --no-halt ``` -------------------------------- ### Get Available SPI Bus Names (NilBackend) Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/nil-backend.md When the NilBackend is active, this function returns an empty list, indicating no SPI devices are available. ```elixir buses = Circuits.SPI.bus_names() # => [] ``` -------------------------------- ### Conditional Backend Configuration for Circuits.SPI Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/nil-backend.md Demonstrates how to use Mix environments (config/dev.exs, config/test.exs, config/prod.exs) to conditionally configure the default backend for Circuits.SPI, allowing different backends for development, testing, and production. ```elixir # config/dev.exs - for development config :circuits_spi, :default_backend, Circuits.SIM.SPIDev # config/test.exs - for testing config :circuits_spi, :default_backend, MyApp.MockSPIBackend # config/prod.exs - for production config :circuits_spi, :default_backend, Circuits.SPI.SPIDev ``` -------------------------------- ### Get Maximum Transfer Size Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/bus-protocol.md Returns the maximum number of bytes that can be transferred in a single `transfer/2` call. A return value of 0 indicates no limit or an unknown limit. ```elixir @spec max_transfer_size(t()) :: non_neg_integer() def max_transfer_size(bus) ``` -------------------------------- ### Configure Custom SPI Backend with Options Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Configure a custom backend module with specific options. Pass options as a keyword list. ```elixir config :circuits_spi, :default_backend, {MyApp.CustomBackend, [option: :value]} ``` -------------------------------- ### Configure Default Backend Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/backend-behaviour.md Shows how to configure the default SPI backend using the `:default_backend` application environment key in Elixir configuration files. ```elixir config :circuits_spi, :default_backend, Circuits.SPI.SPIDev ``` ```elixir config :circuits_spi, :default_backend, {Circuits.SPI.SPIDev, test: true} ``` -------------------------------- ### Get SPI Bus Configuration Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/bus-protocol.md Retrieves the current SPI bus configuration. Useful for verifying actual settings applied by the backend, as some hardware may adjust requested values. ```elixir iex> {:ok, spi} = Circuits.SPI.open("spidev0.0", speed_hz: 1000000) {:ok, %Circuits.SPI.SPIDev{name: "spidev0.0", ...}} iex> {:ok, config} = Circuits.SPI.config(spi) {:ok, %{bits_per_word: 8, delay_us: 0, lsb_first: false, mode: 0, speed_hz: 1000000, sw_lsb_first: false}} iex> IO.inspect(config.speed_hz) 1000000 ``` -------------------------------- ### Get Maximum SPI Transfer Size Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/spidev-backend.md Returns the maximum transfer size in bytes supported by the Linux spidev driver. Use this to determine if data needs to be split into chunks for transfer. ```elixir @impl Bus def max_transfer_size(%Circuits.SPI.SPIDev{}) :: non_neg_integer() ``` ```elixir {:ok, spi} = Circuits.SPI.open("spidev0.0") max_size = Circuits.SPI.max_transfer_size(spi) # If transferring large amounts of data if max_size > 0 and byte_size(data) > max_size do # Split into chunks data |> Enum.chunk_every(max_size) |> Enum.each(fn chunk -> Circuits.SPI.transfer(spi, chunk) end) else Circuits.SPI.transfer(spi, data) end ``` -------------------------------- ### Configure Default SPI Backend for Production Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/README.md Sets the default SPI backend to Circuits.SPI.SPIDev for production environments on Linux/Nerves. ```elixir config :circuits_spi, :default_backend, Circuits.SPI.SPIDev ``` -------------------------------- ### Configure NilBackend Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Use the NilBackend as a fallback for unsupported platforms. This backend returns errors and acts as a placeholder. ```elixir config :circuits_spi, :default_backend, Circuits.SPI.NilBackend ``` -------------------------------- ### Configure Circuits.SPI to Use CircuitsSim Backend Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/nil-backend.md This configuration snippet shows how to set the default backend for Circuits.SPI to Circuits.SIM.SPIDev, typically used for mocking SPI devices during development or testing. ```elixir config :circuits_spi, :default_backend, Circuits.SIM.SPIDev ``` -------------------------------- ### open/2 Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/backend-behaviour.md Opens an SPI bus device and returns a bus reference that implements the `Circuits.SPI.Bus` protocol. This callback handles the actual device opening and configuration based on provided options. ```APIDOC ## Callback: open/2 ### Description Opens an SPI bus device and returns a bus reference. ### Callback Signature ```elixir @callback open(bus_name :: String.t(), [Circuits.SPI.spi_option()]) :: {:ok, Circuits.SPI.Bus.t()} | {:error, term()} ``` ### Parameters #### Path Parameters - **bus_name** (String.t()) - Required - The name of the bus to open (without /dev/ prefix) - **opts** ([Circuits.SPI.spi_option()]) - Required - List of SPI configuration options ### Return Type `{:ok, Bus.t()} | {:error, term()}` - On success: Returns `{:ok, bus_ref}` where `bus_ref` implements the `Circuits.SPI.Bus` protocol. - On error: Returns `{:error, reason}` where reason is typically an atom like `:enoent`, `:eacces`, `:einval`. ### Supported Options - `mode`: SPI mode (0-3) - `bits_per_word`: Bits per word (usually 8) - `speed_hz`: Bus speed in Hz - `delay_us`: Delay between transfers - `lsb_first`: Send least significant bit first ### Common Error Codes - `:enoent` - Device not found (device file does not exist) - `:eacces` - Permission denied (user does not have read/write permission on device) - `:einval` - Invalid argument (unsupported mode, speed, or other configuration) - `:unimplemented` - Backend does not support this operation - `:eio` - Input/output error (hardware communication failure) ### Example Implementation ```elixir defmodule Circuits.SPI.SPIDev do @behaviour Circuits.SPI.Backend @impl Backend def open(bus_name, options) do mode = Keyword.get(options, :mode, 0) bits_per_word = Keyword.get(options, :bits_per_word, 8) speed_hz = Keyword.get(options, :speed_hz, 1_000_000) delay_us = Keyword.get(options, :delay_us, 10) lsb_first = Keyword.get(options, :lsb_first, false) with {:ok, ref} <- Circuits.SPI.Nif.open(to_string(bus_name), mode, bits_per_word, speed_hz, delay_us, lsb_first) do {:ok, %__MODULE__{ref: ref}} end end end ``` ``` -------------------------------- ### info/0 Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/nil-backend.md Returns basic information about the NilBackend. This is a callback implementation for the Circuits.SPI.Backend behaviour. ```APIDOC ## info/0 ### Description Returns basic information about the NilBackend. ### Method `info/0` ### Parameters None ### Return Type `map()` ### Return Value ```elixir %{name: Circuits.SPI.NilBackend} ``` ### Example ```elixir info = Circuits.SPI.info() IO.inspect(info) # => %{name: Circuits.SPI.NilBackend} ``` ``` -------------------------------- ### Configure Default SPI Backend for Development Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/README.md Sets the default SPI backend to SPIDev with test mode enabled for development environments. ```elixir config :circuits_spi, :default_backend, {Circuits.SPI.SPIDev, [test: true]} ``` -------------------------------- ### Configure Custom Mock SPI Backend in test.exs Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Configure a custom mock SPI backend for testing in `config/test.exs`. This allows for full simulation of SPI devices by providing your own backend implementation, such as `MyApp.TestBackend`. ```elixir config :circuits_spi, :default_backend, MyApp.TestBackend ``` -------------------------------- ### Handle :ebadf Error for Invalid SPI Bus Reference Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/errors.md Demonstrates how to manage the :ebadf error, which occurs when an invalid or closed SPI bus reference is used. The example shows a pattern for wrapping SPI operations within a struct to ensure valid references. ```elixir defmodule MyDevice do defstruct [:spi] def open(bus_name) do case Circuits.SPI.open(bus_name) do {:ok, spi} -> {:ok, %__MODULE__{spi: spi}} {:error, reason} -> {:error, reason} end end def transfer(device, data) do case Circuits.SPI.transfer(device.spi, data) do {:ok, result} -> {:ok, result} {:error, :ebadf} -> {:error, "Device is closed"} {:error, reason} -> {:error, reason} end end def close(device) do Circuits.SPI.close(device.spi) end end ``` -------------------------------- ### Set Default Backend at Runtime Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Set the default SPI backend at runtime using `Application.put_env/3`. This must be done before the first Circuits.SPI function call. ```elixir Application.put_env(:circuits_spi, :default_backend, Circuits.SPI.SPIDev) ``` -------------------------------- ### open/2 Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/spidev-backend.md Opens an SPI device specified by `bus_name` with the given `options` and returns a reference that implements the `Circuits.SPI.Bus` protocol. ```APIDOC ## open/2 ### Description Opens an SPI device and returns a reference implementing the `Circuits.SPI.Bus` protocol. ### Parameters #### Path Parameters - **bus_name** (String.t()) - Required - Bus name without `/dev/` prefix (e.g., "spidev0.0") #### Query Parameters - **options** ([spi_option()]) - Required - SPI configuration options ### Options Details | Option | Type | Default | Description | |---|---|---|---| | mode | 0..3 | 0 | SPI mode (clock polarity and phase) | | bits_per_word | 8..16 | 8 | Bits per word | | speed_hz | pos_integer | 1000000 | Bus speed in Hz | | delay_us | non_neg_integer | 10 | Delay between transfers in microseconds | | lsb_first | boolean | false | Send least significant bit first | ### Return Type `{:ok, Circuits.SPI.SPIDev.t()} | {:error, term()}` ### Error Handling Common errors include `:enoent` (device not found), `:eacces` (permission denied), `:einval` (invalid configuration), and `:eio` (hardware I/O error). ### Example ```elixir # Default settings {:ok, spi} = Circuits.SPI.open("spidev0.0") # Custom configuration {:ok, spi} = Circuits.SPI.open("spidev0.0", mode: 1, speed_hz: 500000, bits_per_word: 16 ) # Error handling case Circuits.SPI.open("spidev9.9") do {:ok, spi} -> {:ok, spi} {:error, :enoent} -> {:error, "Device not found"} {:error, reason} -> {:error, "Failed to open: #{reason}"} end ``` ``` -------------------------------- ### Open SPI Device and Inspect Configuration Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/README.md Opens an SPI device and then retrieves and inspects its configuration. Useful for verifying settings. ```elixir {:ok, spi} = Circuits.SPI.open("spidev0.0") {:ok, config} = Circuits.SPI.config(spi) IO.inspect(config) ``` -------------------------------- ### info/0 Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/spidev-backend.md Returns a map containing information about the SPIDev backend, including its name and other HAL-specific details. ```APIDOC ## info/0 ### Description Returns information about the SPIDev backend. ### Parameters None ### Return Type `map()` ### Example Return Value ```elixir %{ backend: Circuits.SPI.SPIDev, name: "spidev", kernel_driver_version: "3.14" # ... other HAL-specific information } ``` ### Example ```elixir info = Circuits.SPI.info() IO.inspect(info) ``` ``` -------------------------------- ### Configure Custom SPI Backend Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Use a custom backend module that implements the Circuits.SPI.Backend behavior. Ensure your custom module is correctly defined. ```elixir config :circuits_spi, :default_backend, MyApp.MockSPIBackend ``` -------------------------------- ### Check for Software LSB-First Implementation Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/types.md Verify if LSB-first mode is being handled by software, which might occur if the hardware does not directly support it. This check is performed after obtaining the bus configuration. ```elixir if config.sw_lsb_first do IO.puts("Hardware doesn't support LSB-first, using software") end ``` -------------------------------- ### Open SPI Device with Default Options Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Open an SPI device using the default configuration options. This is suitable for most common SPI devices. ```elixir {:ok, spi} = Circuits.SPI.open("spidev0.0", [ mode: 0, bits_per_word: 8, speed_hz: 1_000_000, delay_us: 10, lsb_first: false ]) ``` -------------------------------- ### SPI Backend Behaviour Callback: open/2 Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/backend-behaviour.md This callback opens an SPI bus device and returns a reference that implements the Circuits.SPI.Bus protocol. It handles device-specific configurations like mode, bits per word, and speed, returning an error tuple for issues like device not found or permission errors. ```elixir defmodule Circuits.SPI.SPIDev do @behaviour Circuits.SPI.Backend @impl Backend def open(bus_name, options) do mode = Keyword.get(options, :mode, 0) bits_per_word = Keyword.get(options, :bits_per_word, 8) speed_hz = Keyword.get(options, :speed_hz, 1_000_000) delay_us = Keyword.get(options, :delay_us, 10) lsb_first = Keyword.get(options, :lsb_first, false) with {:ok, ref} <- Circuits.SPI.Nif.open(to_string(bus_name), mode, bits_per_word, speed_hz, delay_us, lsb_first) do {:ok, %__MODULE__{ref: ref}} end end end ``` -------------------------------- ### Open and Configure SPI Bus Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/circuits-spi.md Opens an SPI bus with specified speed and mode, then retrieves its configuration. Useful for verifying settings after opening. ```elixir சிகள் {:ok, spi} = Circuits.SPI.open("spidev0.0", speed_hz: 500000, mode: 2) சிகள் {:ok, config} = Circuits.SPI.config(spi) IO.inspect(config) # => %{ # mode: 2, # bits_per_word: 8, # speed_hz: 500000, # delay_us: 10, # lsb_first: false, # sw_lsb_first: false # } ``` -------------------------------- ### Check SPI Configuration Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/README.md Retrieves the current configuration of the SPI device and prints it to the console. ```elixir {:ok, config} = Circuits.SPI.config(spi) IO.inspect(config) # => %{ # mode: 0, # bits_per_word: 8, # speed_hz: 500_000, # delay_us: 10, # lsb_first: false, # sw_lsb_first: false # } ``` -------------------------------- ### Handle :unimplemented Errors with NilBackend Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/nil-backend.md Demonstrates how to use a case statement to handle the :unimplemented error when opening an SPI device with NilBackend. This pattern is useful when SPI functionality might not be available. ```elixir case Circuits.SPI.open("spidev0.0") do {:ok, spi} -> # Handle SPI operations Circuits.SPI.transfer(spi, <<0x00>>) {:error, :unimplemented} -> # Handle unavailable backend IO.puts("SPI not available - ensure you're on Linux or have a custom backend configured") {:error, reason} -> # Handle other errors IO.puts("Failed to open SPI: #{reason}") end ``` -------------------------------- ### Custom Mock SPI Backend Implementation Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/nil-backend.md Defines a custom Elixir module that implements the Circuits.SPI.Backend behavior to simulate SPI hardware. This includes implementing bus_names, open, and info callbacks, along with the Circuits.SPI.Bus protocol for mock device operations. ```elixir defmodule MyApp.MockSPIBackend do @behaviour Circuits.SPI.Backend @impl Backend def bus_names(_options) do ["mock_spi_0"] end @impl Backend def open(bus_name, options) do {:ok, %MyApp.MockSPI{name: bus_name, config: options}} end @impl Backend def info() do %{backend: __MODULE__, name: "mock_spi"} end end defimpl Circuits.SPI.Bus, for: MyApp.MockSPI do def config(%MyApp.MockSPI{config: config}) do {:ok, %{ mode: Keyword.get(config, :mode, 0), bits_per_word: Keyword.get(config, :bits_per_word, 8), speed_hz: Keyword.get(config, :speed_hz, 1000000), delay_us: Keyword.get(config, :delay_us, 10), lsb_first: Keyword.get(config, :lsb_first, false), sw_lsb_first: false }} end def transfer(_bus, data) do # Return the same data (loopback behavior) {:ok, IO.iodata_to_binary(data)} end def close(_bus), do: :ok def max_transfer_size(_bus), do: 0 end # Configure to use your mock backend config :circuits_spi, :default_backend, MyApp.MockSPIBackend ``` -------------------------------- ### Check Actual SPI Configuration Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Open an SPI device and then retrieve its actual configuration to verify settings like bits per word. This is useful when the kernel driver might adjust requested values. ```elixir {:ok, spi} = Circuits.SPI.open("spidev0.0", bits_per_word: 16) {:ok, config} = Circuits.SPI.config(spi) IO.puts("Actual bits per word: #{config.bits_per_word}") ``` -------------------------------- ### Circuits.SPI.SPIDev info/0 Callback Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/spidev-backend.md Returns a map containing information about the SPIDev backend, including its name and version details. The exact contents may vary based on the underlying HAL. ```elixir @impl Backend def info() :: map() ``` ```elixir %{ backend: Circuits.SPI.SPIDev, name: "spidev", kernel_driver_version: "3.14", # ... other HAL-specific information } ``` ```elixir info = Circuits.SPI.info() IO.inspect(info) ``` -------------------------------- ### Handle Unimplemented SPI Operation Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/errors.md Demonstrates how to handle the :unimplemented error when the backend does not support the operation. This is common on unsupported platforms or with custom backends. ```elixir case Circuits.SPI.open("spidev0.0") do {:ok, spi} -> {:ok, spi} {:error, :unimplemented} -> IO.puts("SPI not available on this platform") IO.puts("Configure a backend in config.exs") {:error, "SPI unavailable"} end ``` -------------------------------- ### Open SPI Bus with Custom Speed Source: https://github.com/elixir-circuits/circuits_spi/blob/main/README.md Configure and open an SPI bus with a specific speed in Hz. Verify the actual speed with a logic analyzer and consult device documentation for limitations. ```elixir {:ok, my_spi} = Circuits.SPI.open("spidev0.0", speed_hz: 122000) ``` -------------------------------- ### List Available SPI Buses Source: https://github.com/elixir-circuits/circuits_spi/blob/main/README.md Check for available SPI buses on the system. If the list is empty, SPI may not be enabled or configured. ```elixir iex> Circuits.SPI.bus_names() ["spidev0.0", "spidev0.1"] ``` -------------------------------- ### List Available SPI Devices Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/README.md Retrieves a list of available SPI device names on the system and prints them to the console. ```elixir buses = Circuits.SPI.bus_names() IO.inspect(buses) # => ["spidev0.0", "spidev0.1"] ``` -------------------------------- ### Set Default Backend in config.exs Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Configure the default backend for Circuits.SPI in your project's config file. This specifies how Circuits.SPI interacts with hardware. ```elixir import Config config :circuits_spi, :default_backend, Circuits.SPI.SPIDev ``` -------------------------------- ### Device Wrapper Module Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/README.md A custom module to wrap SPI device operations, providing a structured interface for device-specific interactions. ```elixir defmodule MyDevice do defstruct [:spi, :name] def open(bus_name) do case Circuits.SPI.open(bus_name, speed_hz: 1_000_000) do {:ok, spi} -> {:ok, %__MODULE__{spi: spi, name: bus_name}} {:error, reason} -> {:error, reason} end end def read(device, register) do case Circuits.SPI.transfer(device.spi, <>) do {:ok, <<_status, value::8>>} -> {:ok, value} {:error, reason} -> {:error, reason} end end def close(device) do Circuits.SPI.close(device.spi) end end ``` -------------------------------- ### info/0 Callback Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/backend-behaviour.md Returns information about the backend for debugging purposes. This callback provides system information that can be useful for diagnosing issues. ```APIDOC ## info/0 Callback ### Description Returns information about this backend for debugging purposes. ### Method Callback ### Parameters None ### Return Type `map()` ### Return Value Description Returns a map containing backend information and debugging details. The exact contents depend on the backend, but typically include: - `backend` or `name`: The name of the backend module - Hardware-specific info (e.g., for SPIDev, information about the kernel driver) - Platform info (e.g., operating system, architecture) ### Example Return Values From `Circuits.SPI.SPIDev`: ```json { "backend": "Circuits.SPI.SPIDev", "name": "spidev", "kernel_module_version": "3.14", "...": "..." } ``` From `Circuits.SPI.NilBackend`: ```json { "name": "Circuits.SPI.NilBackend" } ``` ``` -------------------------------- ### Verify Actual Bits Per Word Setting Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/types.md After opening an SPI bus with a specific `bits_per_word` setting, retrieve the actual configuration to confirm the value used by the hardware. ```elixir IO.puts("Bits per word: #{config.bits_per_word}") ``` -------------------------------- ### Circuits.SPI.SPIDev open/2 Callback Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/spidev-backend.md Opens an SPI device with specified bus name and options, returning a struct implementing the Circuits.SPI.Bus protocol or an error tuple. Supports custom SPI modes, speeds, and bit depths. ```elixir @impl Backend def open(bus_name, options) :: {:ok, Circuits.SPI.SPIDev.t()} | {:error, term()} ``` ```elixir # Default settings {:ok, spi} = Circuits.SPI.open("spidev0.0") # Custom configuration {:ok, spi} = Circuits.SPI.open("spidev0.0", mode: 1, speed_hz: 500000, bits_per_word: 16 ) # Error handling case Circuits.SPI.open("spidev9.9") do {:ok, spi} -> {:ok, spi} {:error, :enoent} -> {:error, "Device not found"} {:error, :eacces} -> {:error, "Permission denied - run as root or add user to spi group"} {:error, reason} -> {:error, "Failed to open: #{reason}"} end ``` -------------------------------- ### Check if LSB-First is Software-Implemented Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/types.md Logs a message indicating that software bit-reversal is being used for LSB-first mode, which implies hardware support might be absent. ```elixir if config.lsb_first and config.sw_lsb_first do IO.puts("Using software bit-reversal for LSB-first mode") end ``` -------------------------------- ### open/2 Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/nil-backend.md Returns an error indicating the operation is unimplemented. This is a callback implementation for the Circuits.SPI.Backend behaviour. ```APIDOC ## open/2 ### Description Returns an error indicating the operation is unimplemented. ### Method `open/2` ### Parameters #### Path Parameters - **bus_name** (String.t()) - Required - Bus name (ignored) - **options** ([spi_option()]) - Required - Options (ignored) ### Return Type `{:error, :unimplemented}` - Always returns an error tuple ### Example ```elixir # When NilBackend is active {:ok, spi} = Circuits.SPI.open("spidev0.0") # => {:error, :unimplemented} # Error handling case Circuits.SPI.open("spidev0.0") do {:ok, _spi} -> :ok {:error, :unimplemented} -> IO.puts("SPI not available on this system") end ``` ``` -------------------------------- ### Error Handling for SPI Operations Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/README.md Demonstrates robust error handling for SPI operations, including device not found, permission denied, and general I/O errors. ```elixir case Circuits.SPI.open("spidev0.0") do {:ok, spi} -> case Circuits.SPI.transfer(spi, <<0xFF>>) do {:ok, data} -> process_data(data) {:error, :eio} -> {:error, "Hardware I/O error"} {:error, reason} -> {:error, "Transfer failed: #{reason}"} end Circuits.SPI.close(spi) {:error, :enoent} -> {:error, "Device not found. Available: #{Enum.join(Circuits.SPI.bus_names(), ", ")}"} {:error, :eacces} -> {:error, "Permission denied. Run: sudo usermod -aG spi $USER"} {:error, reason} -> {:error, "Failed to open: #{reason}"} end ``` -------------------------------- ### config/1 Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/spidev-backend.md Retrieves the current SPI bus configuration by delegating to the NIF. The configuration might differ from the initial settings if the hardware adjusted any parameters. ```APIDOC ## config/1 ### Description Delegates to the NIF to retrieve the current SPI bus configuration. The configuration may differ from what was passed to `open/2` if the hardware adjusted any parameters. ### Function Signature ```elixir config(%Circuits.SPI.SPIDev{ref: ref}) :: {:ok, spi_option_map()} | {:error, term()} ``` ### Example ```elixir {:ok, spi} = Circuits.SPI.open("spidev0.0", speed_hz: 500000) {:ok, config} = Circuits.SPI.config(spi) IO.inspect(config.speed_hz) # May be different if hardware doesn't support exact value ``` ``` -------------------------------- ### Environment-Specific Backend Configuration Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Set the default backend configuration for specific environments like development, test, or production. ```elixir # config/dev.exs import Config config :circuits_spi, :default_backend, {Circuits.SPI.SPIDev, [test: true]} ``` ```elixir # config/test.exs import Config config :circuits_spi, :default_backend, {Circuits.SPI.SPIDev, [test: true]} ``` ```elixir # config/prod.exs import Config config :circuits_spi, :default_backend, Circuits.SPI.SPIDev ``` -------------------------------- ### Circuits.SPI.info/1 Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/circuits-spi.md Returns information about the low-level SPI interface and the active backend implementation, which is useful for debugging purposes. ```APIDOC ## Circuits.SPI.info/1 ### Description Returns information about the low-level SPI interface and the active backend implementation. Useful for debugging. ### Method `info(backend \ nil)` ### Parameters #### Path Parameters - **backend** (backend() | nil) - Optional - Default: `nil` - An optional backend tuple to query; if nil, queries the default backend ### Return Type `map()` - Returns a map containing backend name and debugging information ### Example ```elixir # Get info about the default backend info = Circuits.SPI.info() IO.inspect(info) # => %{backend: Circuits.SPI.SPIDev, name: "spidev", ...} # Get info about a specific backend info = Circuits.SPI.info({Circuits.SPI.SPIDev, []}) ``` ``` -------------------------------- ### Open and Close SPI Bus Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/circuits-spi.md Opens an SPI bus and demonstrates explicit closure. While resources are garbage collected, explicit closure is recommended for timely release. ```elixir சிகள் {:ok, spi} = Circuits.SPI.open("spidev0.0") # ... use spi ... Circuits.SPI.close(spi) ``` -------------------------------- ### Circuits.SPI.open/2 Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/api-reference/circuits-spi.md Opens an SPI bus device and returns a reference for use with other functions. It allows specifying bus name and configuration options. ```APIDOC ## Circuits.SPI.open/2 ### Description Opens an SPI bus device and returns a reference for use with other functions. ### Function Signature ```elixir @spec open(binary(), [spi_option()]) :: {:ok, Bus.t()} | {:error, term()} def open(bus_name, options \ []) ``` ### Parameters #### Path Parameters - **bus_name** (binary) - Required - The name of the SPI bus device (e.g., "spidev0.0") - **options** ([spi_option()]) - Optional - Keyword list of SPI configuration options ### Return Type - `{:ok, Bus.t()}`: On success, returns `{:ok, bus_ref}` where `bus_ref` is a reference implementing the `Circuits.SPI.Bus` protocol. - `{:error, term()}`: On error, returns `{:error, reason}` where `reason` is an atom or term describing the error. ### Throws/Errors - `:eacces` - Permission denied (requires appropriate file permissions) - `:enoent` - Device not found - `:einval` - Invalid argument (unsupported mode, speed, or configuration) - `:unimplemented` - Backend does not implement this operation (e.g., NilBackend) ### Request Example ```elixir # Open SPI bus with default settings {:ok, spi} = Circuits.SPI.open("spidev0.0") # Open SPI bus with custom speed and mode {:ok, spi} = Circuits.SPI.open("spidev0.0", speed_hz: 122000, mode: 1) # Handle error case Circuits.SPI.open("spidev0.0") do {:ok, spi} -> {:ok, spi} {:error, reason} -> IO.puts("Failed to open: #{reason}") end ``` ``` -------------------------------- ### Configure Cross-Compilation for Nerves Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/configuration.md Set the CROSSCOMPILE environment variable to 1 and specify the MIX_TARGET when cross-compiling for Nerves boards. This ensures the build system targets the correct architecture. ```bash export CROSSCOMPILE=1 export MIX_TARGET=rpi4 mix compile ``` -------------------------------- ### Open SPI Device with Specific Parameters Source: https://github.com/elixir-circuits/circuits_spi/blob/main/_autodocs/README.md Opens an SPI bus device with custom configuration for mode, speed, and bits per word. ```elixir {:ok, spi} = Circuits.SPI.open("spidev0.0", mode: 0, speed_hz: 500_000, bits_per_word: 8 ) ```