CircuitsFT232H.MPSSE (circuits_ft232h v0.1.0)

Copy Markdown

Pure-function encoder for the FT232H Multi-Protocol Synchronous Serial Engine (MPSSE) command stream.

This module knows nothing about USB or transports. It builds binaries you hand to CircuitsFT232H.USB.write/3, and helps interpret bytes you read back via CircuitsFT232H.USB.read/3.

Opcode names and semantics mirror FTDI Application Note AN_108.

Summary

Types

Order in which a byte's bits are clocked out / in.

Which MPSSE base clock to compute against.

Clock edge on which a data bit is driven or sampled.

Functions

The MPSSE byte that precedes the echo of an unrecognised opcode.

Computes the MPSSE divisor that produces target_hz SCK, given the base clock.

Disables 3-phase data clocking (normal 2-phase SPI behaviour).

Disables adaptive clocking (the chip's default).

Selects the 60 MHz MPSSE base clock (H-series chips only). Required for SCK frequencies above 6 MHz.

Enables 3-phase data clocking. Required for I2C, and for SPI modes that drive on one edge and sample on the same edge (CPHA = 1).

Enables adaptive clocking — MPSSE pauses after each clock pulse until GPIOL3 (ADBUS7) is driven high.

Selects the legacy 12 MHz MPSSE base clock (for compatibility with FT2232D-style code).

Configures pins to operate in open-drain mode.

Scans response for a BAD_COMMAND marker (0xFA) and returns the offending opcode if found.

Reads the current state of ACBUS0..ACBUS7. Response is 1 byte.

Reads the current state of ADBUS0..ADBUS7. Response is 1 byte.

Disables internal loopback (the normal state).

Enables internal TDI→TDO loopback. Useful for init self-tests.

Clocks count bits in from TDO/DI. Response is one byte; the relevant bits are right-justified (MSB-first) or left-justified (LSB-first) depending on :bit_order.

Reads count bytes in on the TDO/DI pin while clocking SCK. No data is driven out.

Returns the SCK frequency produced by the given divisor on the given base clock. Inverse of clock_divisor/2 (with rounding).

Flushes any pending bytes in the chip's internal IN buffer back to the host. Append this whenever you need to read a response right away.

Sets the high-byte GPIO port (ACBUS0..ACBUS7) state. See set_bits_low/2.

Sets the low-byte GPIO port (ADBUS0..ADBUS7) state.

Sets the SCK clock divisor. Get the right divisor via clock_divisor/2.

Full-duplex bit transfer. Drives the bottom count bits of value while sampling TDO/DI. Response is one byte.

Full-duplex byte transfer — drives data out on TDI/DO and simultaneously samples TDO/DI back. The response is byte_size(data) bytes long.

Writes the bottom count bits of value out the TDI/DO pin while clocking SCK. count must be in 1..8.

Writes data out the TDI/DO pin while clocking SCK.

Types

bit_order()

@type bit_order() :: :msb_first | :lsb_first

Order in which a byte's bits are clocked out / in.

clock_base()

@type clock_base() :: :high_speed | :legacy

Which MPSSE base clock to compute against.

clock_edge()

@type clock_edge() :: :positive | :negative

Clock edge on which a data bit is driven or sampled.

Functions

bad_command_byte()

@spec bad_command_byte() :: byte()

The MPSSE byte that precedes the echo of an unrecognised opcode.

clock_divisor(target_hz, base \\ :high_speed)

@spec clock_divisor(pos_integer(), clock_base()) :: 0..65535

Computes the MPSSE divisor that produces target_hz SCK, given the base clock.

Use :high_speed (default) for the 60 MHz base (FT232H, requires disable_clock_divide_by_5/0) and :legacy for the 12 MHz base (FT2232D-compatible, requires enable_clock_divide_by_5/0).

The formula is SCK = base / ((1 + divisor) × 2). Result is rounded and clamped to 0..0xFFFF.

disable_3_phase_clocking()

@spec disable_3_phase_clocking() :: binary()

Disables 3-phase data clocking (normal 2-phase SPI behaviour).

disable_adaptive_clocking()

@spec disable_adaptive_clocking() :: binary()

Disables adaptive clocking (the chip's default).

disable_clock_divide_by_5()

@spec disable_clock_divide_by_5() :: binary()

Selects the 60 MHz MPSSE base clock (H-series chips only). Required for SCK frequencies above 6 MHz.

enable_3_phase_clocking()

@spec enable_3_phase_clocking() :: binary()

Enables 3-phase data clocking. Required for I2C, and for SPI modes that drive on one edge and sample on the same edge (CPHA = 1).

enable_adaptive_clocking()

@spec enable_adaptive_clocking() :: binary()

Enables adaptive clocking — MPSSE pauses after each clock pulse until GPIOL3 (ADBUS7) is driven high.

Originally designed so MPSSE could synchronise with an ARM target's RTCK signal, this gives a free clock-stretching mechanism for I2C if ADBUS0 (SCL) is externally jumpered to ADBUS7: the slave can hold SCL low and MPSSE will wait for it.

enable_clock_divide_by_5()

@spec enable_clock_divide_by_5() :: binary()

Selects the legacy 12 MHz MPSSE base clock (for compatibility with FT2232D-style code).

enable_drive_zero(low_mask, high_mask)

@spec enable_drive_zero(byte(), byte()) :: binary()

Configures pins to operate in open-drain mode.

Pins set in the masks drive low when their output bit is 0, and are tristated when their output bit is 1. Combined with external pull-up resistors this provides true open-drain behaviour — the key trick that makes I2C work on the FT232H.

Available on FT232H only (not FT2232D / FT2232C).

find_bad_command(response)

@spec find_bad_command(binary()) :: {:bad_command, byte()} | :ok

Scans response for a BAD_COMMAND marker (0xFA) and returns the offending opcode if found.

get_bits_high()

@spec get_bits_high() :: binary()

Reads the current state of ACBUS0..ACBUS7. Response is 1 byte.

get_bits_low()

@spec get_bits_low() :: binary()

Reads the current state of ADBUS0..ADBUS7. Response is 1 byte.

loopback_off()

@spec loopback_off() :: binary()

Disables internal loopback (the normal state).

loopback_on()

@spec loopback_on() :: binary()

Enables internal TDI→TDO loopback. Useful for init self-tests.

read_bits(count, opts \\ [])

@spec read_bits(
  1..8,
  keyword()
) :: binary()

Clocks count bits in from TDO/DI. Response is one byte; the relevant bits are right-justified (MSB-first) or left-justified (LSB-first) depending on :bit_order.

Same options as read_bytes/2.

read_bytes(count, opts \\ [])

@spec read_bytes(
  pos_integer(),
  keyword()
) :: iodata()

Reads count bytes in on the TDO/DI pin while clocking SCK. No data is driven out.

Options:

  • :edge (default :positive) — clock edge on which to sample each bit.
  • :bit_order (default :msb_first).

sck_frequency(divisor, base \\ :high_speed)

@spec sck_frequency(0..65535, clock_base()) :: float()

Returns the SCK frequency produced by the given divisor on the given base clock. Inverse of clock_divisor/2 (with rounding).

send_immediate()

@spec send_immediate() :: binary()

Flushes any pending bytes in the chip's internal IN buffer back to the host. Append this whenever you need to read a response right away.

set_bits_high(value, direction)

@spec set_bits_high(byte(), byte()) :: binary()

Sets the high-byte GPIO port (ACBUS0..ACBUS7) state. See set_bits_low/2.

set_bits_low(value, direction)

@spec set_bits_low(byte(), byte()) :: binary()

Sets the low-byte GPIO port (ADBUS0..ADBUS7) state.

value is the level each output pin should drive (bit 0 = ADBUS0). direction is the per-pin direction mask — 1 for output, 0 for input. In MPSSE mode the engine overrides direction for SCK / MOSI / MISO so it is safe to leave those as outputs in the direction mask.

set_tck_divisor(divisor)

@spec set_tck_divisor(0..65535) :: binary()

Sets the SCK clock divisor. Get the right divisor via clock_divisor/2.

transfer_bits(value, count, opts \\ [])

@spec transfer_bits(byte(), 1..8, keyword()) :: binary()

Full-duplex bit transfer. Drives the bottom count bits of value while sampling TDO/DI. Response is one byte.

Same options as transfer_bytes/2.

transfer_bytes(data, opts \\ [])

@spec transfer_bytes(
  binary(),
  keyword()
) :: iodata()

Full-duplex byte transfer — drives data out on TDI/DO and simultaneously samples TDO/DI back. The response is byte_size(data) bytes long.

Options:

  • :write_edge (default :negative) — clock edge on which to drive.
  • :read_edge (default :positive) — clock edge on which to sample.
  • :bit_order (default :msb_first).

write_bits(value, count, opts \\ [])

@spec write_bits(byte(), 1..8, keyword()) :: binary()

Writes the bottom count bits of value out the TDI/DO pin while clocking SCK. count must be in 1..8.

Same options as write_bytes/2.

write_bytes(data, opts \\ [])

@spec write_bytes(
  binary(),
  keyword()
) :: iodata()

Writes data out the TDI/DO pin while clocking SCK.

Options:

  • :edge (default :negative) — clock edge on which to drive each bit.
  • :bit_order (default :msb_first) — bit order within each byte.

The chip transmits byte_size(data) bytes. The command auto-splits into multiple 0x1? blocks if data is longer than 65536 bytes.