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}