CircuitsFT232H.GPIO.Poller (circuits_ft232h v0.1.0)

Copy Markdown

Per-chip polling GenServer that emulates GPIO interrupts.

These are NOT hardware interrupts

The FT232H has no hardware-generated pin-change notifications. We sample the pin state on a fixed interval and emit {:circuits_gpio, gpio_spec, timestamp, value} messages when subscribed pins change in the desired direction.

Pulses shorter than the poll interval (default 10 ms) will be missed. Multiple edges within a single interval are collapsed into one notification with the final state.

One Poller per chip is started lazily on the first interrupt subscription and registered in CircuitsFT232H.Registry under {:poller, device_id}. When the last subscription goes away the Poller stops itself.

Each poll tick is one USB round-trip (~1 ms) that reads both the ADBUS and ACBUS ports in a single MPSSE transaction. All subscribed pins on the chip are sampled together, so adding more subscribed pins does not increase USB load.

Tuning the poll interval

The default interval is 10 ms (~100 Hz sampling). To change it:

config :circuits_ft232h, gpio_poll_interval_ms: 5

Lower values reduce the size of pulses you can miss but cost more USB bandwidth and CPU. Practical floor is ~2 ms (the USB round-trip time). For mechanical buttons 10-20 ms is plenty; for fast signals you should look at an actual microcontroller.

Other notes

  • :suppress_glitches is accepted but currently a no-op — there's only one sample per poll tick. Filtering glitches shorter than the poll interval would require over-sampling, which isn't implemented.
  • Subscriptions are auto-removed if the receiving process dies.

Summary

Types

Per-pin subscription record.

t()

Poller GenServer state.

Functions

Subscribes the given receiver to edge notifications for pin on the chip identified by device_id. Starts the Poller if it isn't running.

Returns the current subscriptions table for inspection in tests.

Removes any active subscription for pin on the chip identified by device_id. The Poller stops itself once the last subscription goes away.

Types

subscription()

@type subscription() :: %{
  trigger: :rising | :falling | :both,
  receiver: pid(),
  suppress_glitches: boolean(),
  last_value: 0..1
}

Per-pin subscription record.

t()

@type t() :: %CircuitsFT232H.GPIO.Poller{
  device_id: CircuitsFT232H.Device.id(),
  interval_ms: pos_integer(),
  monitors: %{optional(pid()) => reference()},
  subscriptions: %{optional(CircuitsFT232H.Device.pin()) => subscription()},
  timer: reference() | nil
}

Poller GenServer state.

Functions

subscribe(device_id, pin, trigger, opts \\ [])

@spec subscribe(
  CircuitsFT232H.Device.id(),
  CircuitsFT232H.Device.pin(),
  :rising | :falling | :both,
  keyword()
) :: :ok | {:error, term()}

Subscribes the given receiver to edge notifications for pin on the chip identified by device_id. Starts the Poller if it isn't running.

trigger is one of :rising, :falling, :both.

Options:

  • :receiver (default the calling process) — the pid (or registered name) that will receive {:circuits_gpio, gpio_spec, timestamp, value} messages.
  • :suppress_glitches (default true) — accepted but currently a no-op; see the moduledoc.

subscriptions(device_id)

@spec subscriptions(CircuitsFT232H.Device.id()) :: %{
  optional(CircuitsFT232H.Device.pin()) => subscription()
}

Returns the current subscriptions table for inspection in tests.

unsubscribe(device_id, pin)

@spec unsubscribe(CircuitsFT232H.Device.id(), CircuitsFT232H.Device.pin()) :: :ok

Removes any active subscription for pin on the chip identified by device_id. The Poller stops itself once the last subscription goes away.

whereis(device_id)

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