CircuitsFT232H.Device (circuits_ft232h v0.1.0)

Copy Markdown

A GenServer that owns the USB handle to one FT232H and serialises all traffic with it.

Started once per physical chip and registered in CircuitsFT232H.Registry by the device's id (currently "<bus>:<address>" — once we read FTDI serial numbers this becomes the serial). All MPSSE traffic flows through transaction/4, so the chip sees commands from at most one process at a time.

The Device enforces two kinds of allocation:

  • I2C/SPI mutual-exclusion lock — at most one of :i2c or :spi may be active on a chip at once. claim_mode/2 returns {:error, {:mode_busy, current}} for a conflicting claim.
  • GPIO pin allocation — individual pins may be opened by GPIO consumers, and the Device tracks which pins are claimed in which direction. Pin claims are checked against the active protocol's reserved pin set so the two don't fight over the same wires.

Summary

Types

Canonical identifier for a physical FT232H.

Protocol modes a chip can be locked into.

Linear pin index. 0..7 map to ADBUS0..ADBUS7 ("AD0".."AD7"); 8..15 map to ACBUS0..ACBUS7 ("AC0".."AC7").

Direction of a GPIO pin.

t()

Internal GenServer state.

Functions

Claims pin for GPIO use in the given direction. Fails if the pin is already claimed, if it's reserved by the active protocol, or if pin is out of range.

Claims one of the mutually-exclusive protocol modes for the chip.

Starts a Device under CircuitsFT232H.DeviceSupervisor if one isn't already running for the descriptor's id.

Returns the direction currently configured for pin, or nil if the pin isn't open.

Returns the canonical id for a USB descriptor.

Returns the current protocol mode the chip is locked into.

Returns the list of pins reserved by the given protocol mode. Pins outside this list are available for GPIO use.

Reads the current state of the low-byte (ADBUS0..7) and high-byte (ACBUS0..7) ports in a single USB round-trip. Used by the GPIO poller to detect edges across many pins without paying per-pin latency.

Reads the current logic level of a previously-opened GPIO pin.

Releases a previously-claimed GPIO pin and switches it back to input.

Releases whichever mode the chip is currently locked into.

Changes the direction of an already-opened GPIO pin. The pin keeps its current driven value when switching to output.

Starts a Device server for the FT232H described by :descriptor.

Stops the Device server for the given id.

Sends command (an MPSSE byte stream) to the chip and optionally reads response_length bytes back.

Verifies the chip is in MPSSE mode and responding to commands by issuing an unrecognised opcode and checking for the standard 0xFA <opcode> echo.

Finds the pid of the Device server with the given id, if one is running.

Writes value (0 or 1) to a previously-opened GPIO output pin.

Types

id()

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

Canonical identifier for a physical FT232H.

Prefers the chip's FTDI-programmed serial number string when one is available (so the id is stable across replugs). Falls back to "<bus>:<address>" if the chip has no serial or it couldn't be read (e.g. permission denied).

mode()

@type mode() :: :none | :i2c | :spi

Protocol modes a chip can be locked into.

pin()

@type pin() :: 0..15

Linear pin index. 0..7 map to ADBUS0..ADBUS7 ("AD0".."AD7"); 8..15 map to ACBUS0..ACBUS7 ("AC0".."AC7").

pin_direction()

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

Direction of a GPIO pin.

t()

@type t() :: %CircuitsFT232H.Device{
  acbus_dir: byte(),
  acbus_value: byte(),
  adbus_dir: byte(),
  adbus_value: byte(),
  extra_reserved_pins: [pin()],
  gpio_pins: %{optional(pin()) => pin_direction()},
  id: id() | nil,
  mode: mode(),
  usb: CircuitsFT232H.USB.t() | nil
}

Internal GenServer state.

Functions

claim_gpio_pin(id, pin, direction, initial_value \\ 0)

@spec claim_gpio_pin(id(), pin(), pin_direction(), 0..1) :: :ok | {:error, term()}

Claims pin for GPIO use in the given direction. Fails if the pin is already claimed, if it's reserved by the active protocol, or if pin is out of range.

On success the pin is initialised — direction set, and for outputs the initial value driven.

claim_mode(id, requested, extra_pins \\ [])

@spec claim_mode(id(), :i2c | :spi, [pin()]) ::
  :ok | {:error, {:mode_busy, mode()} | {:pin_busy, pin()}}

Claims one of the mutually-exclusive protocol modes for the chip.

Returns :ok if the chip is currently unclaimed (or already claimed in the requested mode), or {:error, {:mode_busy, current_mode}} otherwise. Also fails with {:error, {:pin_busy, pin}} if any currently-open GPIO pin would conflict with the new mode.

extra_pins lets a backend reserve additional pins beyond the protocol's defaults — e.g. the I2C backend reserves AD7 when clock stretching is enabled, so a GPIO consumer can't grab the SCL-feedback pin.

find_or_start(descriptor, opts \\ [])

@spec find_or_start(
  CircuitsFT232H.USB.Descriptor.t(),
  keyword()
) :: {:ok, pid()} | {:error, term()}

Starts a Device under CircuitsFT232H.DeviceSupervisor if one isn't already running for the descriptor's id.

Returns the existing pid if one is found. Use this from the I2C/SPI/GPIO backends — multiple buses may map to the same physical chip.

gpio_pin_direction(id, pin)

@spec gpio_pin_direction(id(), pin()) :: pin_direction() | nil

Returns the direction currently configured for pin, or nil if the pin isn't open.

id_for(descriptor)

@spec id_for(CircuitsFT232H.USB.Descriptor.t()) :: id()

Returns the canonical id for a USB descriptor.

mode(id)

@spec mode(id()) :: mode()

Returns the current protocol mode the chip is locked into.

protocol_pins(mode)

@spec protocol_pins(mode()) :: [pin()]

Returns the list of pins reserved by the given protocol mode. Pins outside this list are available for GPIO use.

read_gpio_bytes(id)

@spec read_gpio_bytes(id()) :: {:ok, {byte(), byte()}} | {:error, term()}

Reads the current state of the low-byte (ADBUS0..7) and high-byte (ACBUS0..7) ports in a single USB round-trip. Used by the GPIO poller to detect edges across many pins without paying per-pin latency.

read_gpio_pin(id, pin)

@spec read_gpio_pin(id(), pin()) :: 0..1 | {:error, term()}

Reads the current logic level of a previously-opened GPIO pin.

release_gpio_pin(id, pin)

@spec release_gpio_pin(id(), pin()) :: :ok

Releases a previously-claimed GPIO pin and switches it back to input.

release_mode(id)

@spec release_mode(id()) :: :ok

Releases whichever mode the chip is currently locked into.

set_gpio_pin_direction(id, pin, direction)

@spec set_gpio_pin_direction(id(), pin(), pin_direction()) :: :ok | {:error, term()}

Changes the direction of an already-opened GPIO pin. The pin keeps its current driven value when switching to output.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Starts a Device server for the FT232H described by :descriptor.

Registers under the device's id in CircuitsFT232H.Registry. Subsequent callers can locate the server via whereis/1 or by referring to it by id in any of the public functions on this module.

Options:

stop(id)

@spec stop(id()) :: :ok

Stops the Device server for the given id.

transaction(id, command, response_length \\ 0, timeout \\ 5000)

@spec transaction(id(), iodata(), non_neg_integer(), timeout()) ::
  :ok | {:ok, binary()} | {:error, term()}

Sends command (an MPSSE byte stream) to the chip and optionally reads response_length bytes back.

command may be any iodata. If response_length is 0 the call returns :ok once the bytes have been queued to the chip. Otherwise it returns {:ok, binary} of exactly response_length payload bytes (the USB status prefix is stripped).

Use this for protocol-level MPSSE sequences. For pin-level operations, prefer write_gpio_pin/3, read_gpio_pin/2, and friends — they share the Device's pin-state tracking so GPIO and protocol traffic don't trample each other's bytes.

validate(id)

@spec validate(id()) :: :ok | {:error, term()}

Verifies the chip is in MPSSE mode and responding to commands by issuing an unrecognised opcode and checking for the standard 0xFA <opcode> echo.

whereis(id)

@spec whereis(id()) :: {:ok, pid()} | {:error, :not_started}

Finds the pid of the Device server with the given id, if one is running.

write_gpio_pin(id, pin, value)

@spec write_gpio_pin(id(), pin(), 0..1) :: :ok | {:error, term()}

Writes value (0 or 1) to a previously-opened GPIO output pin.