macro_rules! mpsse {
    (@replace_expr $_t:tt $sub:expr) => { ... };
    (@count_elements $($tts:expr),* $(,)*) => { ... };
    (@assert ((let, $_user_passthru:tt), $_read_len:expr), $e:expr, $msg:expr) => { ... };
    (@assert ((const, $_user_passthru:tt), $_read_len:expr), $e:expr, $_msg:expr) => { ... };
    () => { ... };
    (let $id:ident = {$($commands:tt)*}; $($tail:tt)*) => { ... };
    (const $id:ident = {$($commands:tt)*}; $($tail:tt)*) => { ... };
    (let ($id:ident, $read_len_id:ident) = {$($commands:tt)*}; $($tail:tt)*) => { ... };
    (const ($id:ident, $read_len_id:ident) = {$($commands:tt)*}; $($tail:tt)*) => { ... };
    ($passthru:tt {enable_loopback(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {disable_loopback(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {enable_3phase_data_clocking(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {disable_3phase_data_clocking(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {enable_adaptive_data_clocking(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {disable_adaptive_data_clocking(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {set_gpio_lower($state:expr, $direction:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {set_gpio_upper($state:expr, $direction:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {gpio_lower(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {const $idx_id:ident = gpio_lower(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {gpio_upper(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {const $idx_id:ident = gpio_upper(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {send_immediate(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {wait_on_io_high(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {wait_on_io_low(); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {clock_data_out($mode:expr, [$($data:expr),* $(,)*]); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {clock_data_in($mode:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {const $range_id:ident = clock_data_in($mode:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {clock_data($mode:expr, [$($data:expr),* $(,)*]); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {const $range_id:ident = clock_data($mode:expr, [$($data:expr),* $(,)*]); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {clock_bits_out($mode:expr, $data:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {clock_bits_in($mode:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {const $idx_id:ident = clock_bits_in($mode:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {clock_bits($mode:expr, $data:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {const $idx_id:ident = clock_bits($mode:expr, $data:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ($passthru:tt {clock_tms_out($mode:expr, $data:expr, $tdi:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {clock_tms($mode:expr, $data:expr, $tdi:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    (($passthru:tt, $read_len:tt) {const $idx_id:ident = clock_tms($mode:expr, $data:expr, $tdi:expr, $len:expr); $($tail:tt)*} -> [$($out:tt)*]) => { ... };
    ((($const_let:tt, ($id:tt, _)), $read_len:expr) {} -> [$($out:tt)*]) => { ... };
    ((($const_let:tt, ($id:tt, $read_len_id:tt)), $read_len:expr) {} -> [$($out:tt)*]) => { ... };
}
Expand description

Construct an MPSSE command array at compile-time.

Alternative to MpsseCmdBuilder. Parses a specialized grammar that gathers MPSSE commands into pseudo-statements contained within zero or more assigned blocks. The pseudo-assignment syntax of each block creates a fixed-length [u8; N] array that is bound with let or const1.

Syntax

mpsse! { let command_data = { command1(); command2(); /* ... */ commandN(); }; }

or

mpsse! { let (command_data, READ_LEN) = { command1(); command2(); /* ... */ commandN(); }; }

The second form provides the caller with a constant size value of the expected data length to read after writing the commands to the device.

Commands

Command pseudo-statements that read data from the device may optionally have the form:

mpsse! {
    // command_data and DATA_IN_RANGE are both declared in the scope of the macro expansion.
    let command_data = {
        const DATA_IN_RANGE = clock_data_in(ClockDataIn::MsbNeg, 3);
    };
}

This provides a constant Range or usize index value that may be used to subscript the data read from the device.

clock_data and clock_data_out require that the second argument is a fixed-length, square bracketed list of u8 values. Compile-time limitations make arbitrary array concatenation or coercion infeasible.

Asserts

For let bindings, the standard assert macro is used for validating parameter size inputs. For const bindings, const_assert is used instead.

const_assert lacks the ability to provide meaningful compile errors, so it may be useful to temporarily use a let binding within function scope to diagnose failing macro expansions.

User Abstractions

With macro shadowing, it is possible to extend the macro with additional rules for abstract, device-specific commands.

Comments within the implementation of this macro contain hints on how to implement these rules.

For example, a SPI device typically delineates transfers with the CS line. Fundamental commands like cs_high and cs_low can be implemented this way, along with other device-specific abstractions.

macro_rules! mpsse {
    // Practical abstraction of CS line for SPI devices.
    ($passthru:tt {cs_low(); $($tail:tt)*} -> [$($out:tt)*]) => {
        mpsse!($passthru {
            set_gpio_lower(0x0, 0xb);
            $($tail)*
        } -> [$($out)*]);
    };
    ($passthru:tt {cs_high(); $($tail:tt)*} -> [$($out:tt)*]) => {
        mpsse!($passthru {
            set_gpio_lower(0x8, 0xb);
            $($tail)*
        } -> [$($out)*]);
    };

    // Hypothetical device-specific command. Leverages both user and libftd2xx commands.
    ($passthru:tt
     {const $idx_id:ident = command_42([$($data:expr),* $(,)*]); $($tail:tt)*} ->
     [$($out:tt)*]) => {
        mpsse!($passthru {
            cs_low();
            const $idx_id = clock_data(::ftdi_mpsse::ClockData::MsbPosIn, [0x42, $($data,)*]);
            cs_high();
            $($tail)*
        } -> [$($out)*]);
    };

    // Everything else handled by libftd2xx crate implementation.
    ($($tokens:tt)*) => {
        ::ftdi_mpsse::mpsse!($($tokens)*);
    };
}

mpsse! {
    const (COMMAND_DATA, READ_LEN) = {
        wait_on_io_high();
        const COMMAND_42_RESULT_RANGE = command_42([11, 22, 33]);
        send_immediate();
    };
}

Example

use ftdi_mpsse::{mpsse, ClockDataIn, ClockDataOut};
use libftd2xx::{Ft232h, FtdiCommon, FtdiMpsse};

mpsse! {
    const (COMMAND_DATA, READ_LEN) = {
        set_gpio_lower(0xFA, 0xFB);
        set_gpio_lower(0xF2, 0xFB);
        clock_data_out(ClockDataOut::MsbNeg, [0x12, 0x34, 0x56]);
        const DATA_IN_RANGE = clock_data_in(ClockDataIn::MsbNeg, 3);
        set_gpio_lower(0xFA, 0xFB);
        send_immediate();
    };
}

let mut ft = Ft232h::with_serial_number("FT5AVX6B")?;
ft.initialize_mpsse_default()?;
ft.write_all(&COMMAND_DATA)?;
let mut buf: [u8; READ_LEN] = [0; READ_LEN];
ft.read_all(&mut buf)?;
println!("Data slice in: {:?}", &buf[DATA_IN_RANGE]);

  1. In const bindings, all values used as command parameters and data must be const.