libftd2xx/
mpsse.rs

1#![deny(missing_docs, unsafe_code)]
2
3use super::{BitMode, DeviceType, FtStatus, FtdiCommon, TimeoutError};
4use ftdi_mpsse::mpsse;
5use ftdi_mpsse::{ClockData, ClockDataIn, ClockDataOut};
6use ftdi_mpsse::{MpsseCmdBuilder, MpsseSettings};
7use std::convert::From;
8
9// seemingly arbitrary values from libmpsse
10// const ECHO_CMD_1: u8 = 0xAA;
11const ECHO_CMD_2: u8 = 0xAB;
12
13fn check_limits(device: DeviceType, frequency: u32, max: u32) {
14    const MIN: u32 = 92;
15    assert!(
16        frequency >= MIN,
17        "frequency of {frequency} exceeds minimum of {MIN} for {device:?}"
18    );
19    assert!(
20        frequency <= max,
21        "frequency of {frequency} exceeds maximum of {max} for {device:?}"
22    );
23}
24
25// calculate the clock divisor from a frequency
26fn clock_divisor(device: DeviceType, frequency: u32) -> (u32, Option<bool>) {
27    match device {
28        // FT2232D appears as FT2232C in FTD2XX
29        DeviceType::FT2232C => {
30            check_limits(device, frequency, 6_000_000);
31            (6_000_000 / frequency - 1, None)
32        }
33        DeviceType::FT2232H | DeviceType::FT4232H | DeviceType::FT4232HA | DeviceType::FT232H => {
34            check_limits(device, frequency, 30_000_000);
35            if frequency <= 6_000_000 {
36                (6_000_000 / frequency - 1, Some(true))
37            } else {
38                (30_000_000 / frequency - 1, Some(false))
39            }
40        }
41        _ => panic!("Unknown device type: {device:?}"),
42    }
43}
44
45#[cfg(test)]
46mod clock_divisor {
47    use super::*;
48
49    macro_rules! pos {
50        ($NAME:ident, $DEVICE:expr, $FREQ:expr, $OUT:expr) => {
51            #[test]
52            fn $NAME() {
53                assert_eq!(clock_divisor($DEVICE, $FREQ), $OUT);
54            }
55        };
56    }
57
58    macro_rules! neg {
59        ($NAME:ident, $DEVICE:expr, $FREQ:expr) => {
60            #[test]
61            #[should_panic]
62            fn $NAME() {
63                clock_divisor($DEVICE, $FREQ);
64            }
65        };
66    }
67
68    pos!(ft232c_min, DeviceType::FT2232C, 92, (65216, None));
69    pos!(ft232c_max, DeviceType::FT2232C, 6_000_000, (0, None));
70    pos!(min, DeviceType::FT2232H, 92, (65216, Some(true)));
71    pos!(
72        max_with_div,
73        DeviceType::FT2232H,
74        6_000_000,
75        (0, Some(true))
76    );
77    pos!(
78        min_without_div,
79        DeviceType::FT2232H,
80        6_000_001,
81        (3, Some(false))
82    );
83    pos!(
84        ft4232h_max,
85        DeviceType::FT4232H,
86        30_000_000,
87        (0, Some(false))
88    );
89    pos!(
90        ft4232ha_max,
91        DeviceType::FT4232HA,
92        30_000_000,
93        (0, Some(false))
94    );
95
96    neg!(panic_unknown, DeviceType::Unknown, 1_000);
97    neg!(panic_ft232c_min, DeviceType::FT2232C, 91);
98    neg!(panic_ft232c_max, DeviceType::FT2232C, 6_000_001);
99    neg!(panic_min, DeviceType::FT232H, 91);
100    neg!(panic_max, DeviceType::FT232H, 30_000_001);
101}
102
103/// FTDI Multi-Protocol Synchronous Serial Engine (MPSSE).
104///
105/// For details about the MPSSE read the [FTDI MPSSE Basics].
106///
107/// [FTDI MPSSE Basics]: https://www.ftdichip.com/Support/Documents/AppNotes/AN_135_MPSSE_Basics.pdf
108pub trait FtdiMpsse: FtdiCommon {
109    /// Set the clock frequency.
110    ///
111    /// # Frequency Limits
112    ///
113    /// | Device Type              | Minimum | Maximum |
114    /// |--------------------------|---------|---------|
115    /// | FT2232D                  | 92 Hz   | 6 MHz   |
116    /// | FT4232H, FT2232H, FT232H | 92 Hz   | 30 MHz  |
117    ///
118    /// Values outside of these limits will result in panic.
119    ///
120    /// # Example
121    ///
122    /// ```no_run
123    /// use libftd2xx::{Ft4232h, FtdiMpsse};
124    ///
125    /// let mut ft = Ft4232h::with_serial_number("FT4PWSEOA")?;
126    /// ft.initialize_mpsse_default()?;
127    /// ft.set_clock(100_000)?;
128    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
129    /// ```
130    fn set_clock(&mut self, frequency: u32) -> Result<(), TimeoutError> {
131        let (divisor, clkdiv) = clock_divisor(Self::DEVICE_TYPE, frequency);
132        debug_assert!(divisor <= 0xFFFF);
133
134        let cmd = MpsseCmdBuilder::new().set_clock(divisor, clkdiv);
135        self.write_all(cmd.as_slice())
136    }
137
138    /// Initialize the MPSSE.
139    ///
140    /// This method does the following:
141    ///
142    /// 1. Optionally [`reset`]s the device.
143    /// 2. Sets USB transfer sizes using values provided.
144    /// 3. Disables special characters.
145    /// 4. Sets the transfer timeouts using values provided.
146    /// 5. Sets latency timers using values provided.
147    /// 6. Sets the flow control to RTS CTS.
148    /// 7. Resets the bitmode, then sets it to MPSSE.
149    /// 8. Enables loopback.
150    /// 9. Synchronizes the MPSSE.
151    /// 10. Disables loopback.
152    /// 11. Optionally sets the clock frequency.
153    ///
154    /// Upon failure cleanup is not guaranteed.
155    ///
156    /// # Example
157    ///
158    /// Initialize the MPSSE with a 5 second read timeout.
159    ///
160    /// ```no_run
161    /// use ftdi_mpsse::MpsseSettings;
162    /// use libftd2xx::{Ft232h, FtdiMpsse};
163    /// use std::time::Duration;
164    ///
165    /// let mut settings = MpsseSettings::default();
166    /// settings.read_timeout = Duration::from_secs(5);
167    /// let mut ft = Ft232h::with_serial_number("FT59UO4C")?;
168    /// ft.initialize_mpsse(&settings)?;
169    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
170    /// ```
171    ///
172    /// [`reset`]: FtdiCommon::reset
173    fn initialize_mpsse(&mut self, settings: &MpsseSettings) -> Result<(), TimeoutError> {
174        if settings.reset {
175            self.reset()?;
176        }
177        self.purge_rx()?;
178        debug_assert_eq!(self.queue_status()?, 0);
179        self.set_usb_parameters(settings.in_transfer_size)?;
180        self.set_chars(0, false, 0, false)?;
181        self.set_timeouts(settings.read_timeout, settings.write_timeout)?;
182        self.set_latency_timer(settings.latency_timer)?;
183        self.set_flow_control_rts_cts()?;
184        self.set_bit_mode(0x0, BitMode::Reset)?;
185        self.set_bit_mode(settings.mask, BitMode::Mpsse)?;
186        self.enable_loopback()?;
187        self.synchronize_mpsse()?;
188        self.disable_loopback()?;
189
190        if let Some(frequency) = settings.clock_frequency {
191            self.set_clock(frequency)?;
192        }
193
194        Ok(())
195    }
196
197    /// Initializes the MPSSE to default settings.
198    ///
199    /// This simply calles [`initialize_mpsse`] with the default
200    /// [`MpsseSettings`].
201    ///
202    /// # Example
203    ///
204    /// ```no_run
205    /// use libftd2xx::{Ft232h, FtdiMpsse};
206    ///
207    /// let mut ft = Ft232h::with_serial_number("FT59UO4C")?;
208    /// ft.initialize_mpsse_default()?;
209    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
210    /// ```
211    ///
212    /// [`initialize_mpsse`]: FtdiMpsse::initialize_mpsse
213    fn initialize_mpsse_default(&mut self) -> Result<(), TimeoutError> {
214        self.initialize_mpsse(&MpsseSettings::default())
215    }
216
217    /// Synchronize the MPSSE port with the application.
218    ///
219    /// There are various implementations of the synchronization flow, this
220    /// uses the flow from [FTDI MPSSE Basics].
221    ///
222    /// [FTDI MPSSE Basics]: https://www.ftdichip.com/Support/Documents/AppNotes/AN_135_MPSSE_Basics.pdf
223    fn synchronize_mpsse(&mut self) -> Result<(), TimeoutError> {
224        self.purge_rx()?;
225        debug_assert_eq!(self.queue_status()?, 0);
226        self.write_all(&[ECHO_CMD_2])?;
227
228        // the FTDI MPSSE basics polls the queue status here
229        // we purged the RX buffer so the response should always be 2 bytes
230        // this allows us to leverage the timeout built into read
231        let mut buf: [u8; 2] = [0; 2];
232        self.read_all(&mut buf)?;
233
234        if buf[0] == 0xFA && buf[1] == ECHO_CMD_2 {
235            Ok(())
236        } else {
237            Err(TimeoutError::from(FtStatus::OTHER_ERROR))
238        }
239    }
240
241    /// Enable the MPSSE loopback state.
242    ///
243    /// # Example
244    ///
245    /// ```no_run
246    /// use libftd2xx::{Ft4232h, FtdiMpsse};
247    ///
248    /// let mut ft = Ft4232h::with_serial_number("FT4PWSEOA")?;
249    /// ft.initialize_mpsse_default()?;
250    /// ft.enable_loopback()?;
251    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
252    /// ```
253    fn enable_loopback(&mut self) -> Result<(), TimeoutError> {
254        mpsse! {
255            let cmd = { enable_loopback(); };
256        }
257
258        self.write_all(&cmd)
259    }
260
261    /// Disable the MPSSE loopback state.
262    ///
263    /// # Example
264    ///
265    /// ```no_run
266    /// use libftd2xx::{Ft4232h, FtdiMpsse};
267    ///
268    /// let mut ft = Ft4232h::with_serial_number("FT4PWSEOA")?;
269    /// ft.initialize_mpsse_default()?;
270    /// ft.disable_loopback()?;
271    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
272    /// ```
273    fn disable_loopback(&mut self) -> Result<(), TimeoutError> {
274        mpsse! {
275            let cmd = { disable_loopback(); };
276        }
277
278        self.write_all(&cmd)
279    }
280
281    /// Set the pin direction and state of the lower byte (0-7) GPIO pins on the
282    /// MPSSE interface.
283    ///
284    /// The pins that this controls depends on the device.
285    ///
286    /// * On the FT232H this will control the AD0-AD7 pins.
287    ///
288    /// # Arguments
289    ///
290    /// * `state` - GPIO state mask, `0` is low (or input pin), `1` is high.
291    /// * `direction` - GPIO direction mask, `0` is input, `1` is output.
292    ///
293    /// # Example
294    ///
295    /// ```no_run
296    /// use libftd2xx::{Ft232h, FtdiMpsse};
297    ///
298    /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?;
299    /// ft.initialize_mpsse_default()?;
300    /// ft.set_gpio_lower(0xFF, 0xFF)?;
301    /// ft.set_gpio_lower(0x00, 0xFF)?;
302    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
303    /// ```
304    fn set_gpio_lower(&mut self, state: u8, direction: u8) -> Result<(), TimeoutError> {
305        let cmd = MpsseCmdBuilder::new().set_gpio_lower(state, direction);
306        self.write_all(cmd.as_slice())
307    }
308
309    /// Get the pin state state of the lower byte (0-7) GPIO pins on the MPSSE
310    /// interface.
311    ///
312    /// # Example
313    ///
314    /// Set the first GPIO, without modify the state of the other GPIOs.
315    ///
316    /// ```no_run
317    /// use libftd2xx::{Ft232h, FtdiMpsse};
318    ///
319    /// let mut ft = Ft232h::with_serial_number("FT59UO4C")?;
320    /// ft.initialize_mpsse_default()?;
321    /// let mut gpio_state: u8 = ft.gpio_lower()?;
322    /// gpio_state |= 0x01;
323    /// ft.set_gpio_lower(gpio_state, 0xFF)?;
324    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
325    /// ```
326    fn gpio_lower(&mut self) -> Result<u8, TimeoutError> {
327        let cmd = MpsseCmdBuilder::new().gpio_lower().send_immediate();
328        let mut buf: [u8; 1] = [0];
329
330        self.write_all(cmd.as_slice())?;
331        self.read_all(&mut buf)?;
332
333        Ok(buf[0])
334    }
335
336    /// Set the pin direction and state of the upper byte (8-15) GPIO pins on
337    /// the MPSSE interface.
338    ///
339    /// The pins that this controls depends on the device.
340    /// This method may do nothing for some devices, such as the FT4232H that
341    /// only have 8 pins per port.
342    ///
343    /// See [`set_gpio_lower`] for an example.
344    ///
345    /// # Arguments
346    ///
347    /// * `state` - GPIO state mask, `0` is low (or input pin), `1` is high.
348    /// * `direction` - GPIO direction mask, `0` is input, `1` is output.
349    ///
350    /// # FT232H Corner Case
351    ///
352    /// On the FT232H only CBUS5, CBUS6, CBUS8, and CBUS9 can be controlled.
353    /// These pins confusingly map to the first four bits in the direction and
354    /// state masks.
355    ///
356    /// [`set_gpio_lower`]: FtdiMpsse::set_gpio_lower
357    fn set_gpio_upper(&mut self, state: u8, direction: u8) -> Result<(), TimeoutError> {
358        let cmd = MpsseCmdBuilder::new().set_gpio_upper(state, direction);
359        self.write_all(cmd.as_slice())
360    }
361
362    /// Get the pin state state of the upper byte (8-15) GPIO pins on the MPSSE
363    /// interface.
364    ///
365    /// See [`gpio_lower`] for an example.
366    ///
367    /// See [`set_gpio_upper`] for additional information about physical pin
368    /// mappings.
369    ///
370    /// [`gpio_lower`]: FtdiMpsse::gpio_lower
371    /// [`set_gpio_upper`]: FtdiMpsse::set_gpio_upper
372    fn gpio_upper(&mut self) -> Result<u8, TimeoutError> {
373        let cmd = MpsseCmdBuilder::new().gpio_upper().send_immediate();
374        let mut buf: [u8; 1] = [0];
375
376        self.write_all(cmd.as_slice())?;
377        self.read_all(&mut buf)?;
378
379        Ok(buf[0])
380    }
381
382    /// Clock data out.
383    ///
384    /// This will clock out bytes on TDI/DO.
385    /// No data is clocked into the device on TDO/DI.
386    ///
387    /// # Example
388    ///
389    /// ```no_run
390    /// use ftdi_mpsse::ClockDataOut;
391    /// use libftd2xx::{Ft232h, FtdiMpsse};
392    ///
393    /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?;
394    /// ft.initialize_mpsse_default()?;
395    /// ft.set_clock(100_000)?;
396    /// ft.set_gpio_lower(0xFA, 0xFB)?;
397    /// ft.set_gpio_lower(0xF2, 0xFB)?;
398    /// ft.clock_data_out(ClockDataOut::MsbNeg, &[0x12, 0x34, 0x56])?;
399    /// ft.set_gpio_lower(0xFA, 0xFB)?;
400    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
401    /// ```
402    fn clock_data_out(&mut self, mode: ClockDataOut, data: &[u8]) -> Result<(), TimeoutError> {
403        if data.is_empty() {
404            return Ok(());
405        }
406
407        let cmd = MpsseCmdBuilder::new().clock_data_out(mode, data);
408        self.write_all(cmd.as_slice())
409    }
410
411    /// Clock data in.
412    ///
413    /// This will clock in bytes on TDO/DI.
414    /// No data is clocked out of the device on TDI/DO.
415    fn clock_data_in(&mut self, mode: ClockDataIn, data: &mut [u8]) -> Result<(), TimeoutError> {
416        if data.is_empty() {
417            return Ok(());
418        }
419
420        let cmd = MpsseCmdBuilder::new().clock_data_in(mode, data.len());
421        self.write_all(cmd.as_slice())?;
422        self.read_all(data)
423    }
424
425    /// Clock data in and out at the same time.
426    fn clock_data(&mut self, mode: ClockData, data: &mut [u8]) -> Result<(), TimeoutError> {
427        if data.is_empty() {
428            return Ok(());
429        }
430
431        let cmd = MpsseCmdBuilder::new().clock_data(mode, data);
432        self.write_all(cmd.as_slice())?;
433        self.read_all(data)
434    }
435}
436
437/// This contains MPSSE commands that are only available on the the FT232H,
438/// FT2232H, and FT4232H(A) devices.
439///
440/// For details about the MPSSE read the [FTDI MPSSE Basics].
441///
442/// [FTDI MPSSE Basics]: https://www.ftdichip.com/Support/Documents/AppNotes/AN_135_MPSSE_Basics.pdf
443pub trait Ftx232hMpsse: FtdiMpsse {
444    /// Enable 3 phase data clocking.
445    ///
446    /// This will give a 3 stage data shift for the purposes of supporting
447    /// interfaces such as I2C which need the data to be valid on both edges of
448    /// the clock.
449    ///
450    /// It will appears as:
451    ///
452    /// 1. Data setup for 1/2 clock period
453    /// 2. Pulse clock for 1/2 clock period
454    /// 3. Data hold for 1/2 clock period
455    ///
456    /// # Example
457    ///
458    /// # Example
459    ///
460    /// ```no_run
461    /// use libftd2xx::{Ft232h, FtdiMpsse, Ftx232hMpsse};
462    ///
463    /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?;
464    /// ft.initialize_mpsse_default()?;
465    /// ft.enable_3phase_data_clocking()?;
466    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
467    /// ```
468    fn enable_3phase_data_clocking(&mut self) -> Result<(), TimeoutError> {
469        mpsse! {
470            let cmd = { enable_3phase_data_clocking(); };
471        }
472
473        self.write_all(&cmd)
474    }
475
476    /// Disable 3 phase data clocking.
477    ///
478    /// This will give a 2 stage data shift which is the default state.
479    ///
480    /// It will appears as:
481    ///
482    /// 1. Data setup for 1/2 clock period
483    /// 2. Pulse clock for 1/2 clock period
484    ///
485    /// # Example
486    ///
487    /// ```no_run
488    /// use libftd2xx::{Ft232h, FtdiMpsse, Ftx232hMpsse};
489    ///
490    /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?;
491    /// ft.initialize_mpsse_default()?;
492    /// ft.disable_3phase_data_clocking()?;
493    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
494    /// ```
495    fn disable_3phase_data_clocking(&mut self) -> Result<(), TimeoutError> {
496        mpsse! {
497            let cmd = { disable_3phase_data_clocking(); };
498        }
499
500        self.write_all(&cmd)
501    }
502}