ftd2xx_embedded_hal/
lib.rs

1//! Inspired by [ftdi-embedded-hal] this is an [embedded-hal] implementation
2//! for the for the FTDI chips using the [libftd2xx] drivers.
3//!
4//! This enables development of embedded device drivers without the use of a
5//! microcontroller.
6//! The FTDI D2xx devices interface with your PC via USB, and provide a
7//! multi-protocol synchronous serial engine to interface with most UART, SPI,
8//! and I2C embedded devices.
9//!
10//! **Note:**
11//! This is strictly a development tool.
12//! The crate contains runtime borrow checks and explicit panics to adapt the
13//! FTDI device into the [embedded-hal] traits.
14//!
15//! # Quickstart
16//!
17//! * Enable the "static" feature flag to use static linking.
18//! * Linux users only: Add [udev rules].
19//!
20//! ```toml
21//! [dependencies.ftd2xx-embedded-hal]
22//! version = "~0.9.1"
23//! features = ["static"]
24//! ```
25//!
26//! # Examples
27//!
28//! * [newAM/eeprom25aa02e48-rs]
29//! * [newAM/bme280-rs]
30//!
31//! ## SPI
32//!
33//! ```no_run
34//! use embedded_hal::prelude::*;
35//! use ftd2xx_embedded_hal::Ft232hHal;
36//!
37//! let ftdi = Ft232hHal::new()?.init_default()?;
38//! let mut spi = ftdi.spi()?;
39//! # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
40//! ```
41//!
42//! ## I2C
43//!
44//! ```no_run
45//! use embedded_hal::prelude::*;
46//! use ftd2xx_embedded_hal::Ft232hHal;
47//!
48//! let ftdi = Ft232hHal::new()?.init_default()?;
49//! let mut i2c = ftdi.i2c()?;
50//! # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
51//! ```
52//!
53//! ## GPIO
54//!
55//! ```no_run
56//! use embedded_hal::prelude::*;
57//! use ftd2xx_embedded_hal::Ft232hHal;
58//!
59//! let ftdi = Ft232hHal::new()?.init_default()?;
60//! let mut gpio = ftdi.ad6();
61//! # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
62//! ```
63//!
64//! # Limitations
65//!
66//! * Limited trait support: SPI, I2C, Delay, and OutputPin traits are implemented.
67//! * Limited device support: FT232H, FT2232H, FT4232H.
68//!
69//! [embedded-hal]: https://github.com/rust-embedded/embedded-hal
70//! [ftdi-embedded-hal]: https://github.com/geomatsi/ftdi-embedded-hal
71//! [libftd2xx crate]: https://github.com/newAM/libftd2xx-rs/
72//! [libftd2xx]: https://github.com/newAM/libftd2xx-rs
73//! [newAM/eeprom25aa02e48-rs]: https://github.com/newAM/eeprom25aa02e48-rs/blob/main/examples/ftdi.rs
74//! [newAM/bme280-rs]: https://github.com/newAM/bme280-rs/blob/main/examples/ftdi.rs
75//! [udev rules]: https://github.com/newAM/libftd2xx-rs/#udev-rules
76//! [setup executable]: https://www.ftdichip.com/Drivers/CDM/CDM21228_Setup.zip
77#![doc(html_root_url = "https://docs.rs/ftd2xx-embedded-hal/0.9.1")]
78#![forbid(missing_docs)]
79#![forbid(unsafe_code)]
80
81pub use embedded_hal;
82pub use libftd2xx;
83
84mod delay;
85mod gpio;
86mod i2c;
87mod spi;
88
89pub use delay::Delay;
90pub use gpio::OutputPin;
91pub use i2c::{I2c, I2cError};
92pub use spi::Spi;
93
94use libftd2xx::{
95    DeviceTypeError, Ft2232h, Ft232h, Ft4232h, Ftdi, FtdiCommon, FtdiMpsse, MpsseSettings,
96    TimeoutError,
97};
98use std::convert::TryFrom;
99use std::ops::Drop;
100use std::{cell::RefCell, convert::TryInto, sync::Mutex, time::Duration};
101
102/// State tracker for each pin on the FTDI chip.
103#[derive(Debug, Clone, Copy)]
104enum PinUse {
105    I2c,
106    Spi,
107    Output,
108}
109
110impl std::fmt::Display for PinUse {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        match self {
113            PinUse::I2c => write!(f, "I2C"),
114            PinUse::Spi => write!(f, "SPI"),
115            PinUse::Output => write!(f, "GPIO"),
116        }
117    }
118}
119
120#[derive(Debug)]
121struct FtInner<Device: FtdiCommon> {
122    /// FTDI device.
123    ft: Device,
124    /// GPIO direction.
125    direction: u8,
126    /// GPIO value.
127    value: u8,
128    /// Pin allocation.
129    pins: [Option<PinUse>; 8],
130}
131
132impl<Device: FtdiCommon> Drop for FtInner<Device> {
133    fn drop(&mut self) {
134        self.ft.close().ok();
135    }
136}
137
138impl<Device: FtdiCommon> FtInner<Device> {
139    /// Allocate a pin for a specific use.
140    pub fn allocate_pin(&mut self, idx: u8, purpose: PinUse) {
141        assert!(idx < 8, "Pin index {} is out of range 0 - 7", idx);
142
143        if let Some(current) = self.pins[usize::from(idx)] {
144            panic!(
145                "Unable to allocate pin {} for {}, pin is already allocated for {}",
146                idx, purpose, current
147            );
148        } else {
149            self.pins[usize::from(idx)] = Some(purpose)
150        }
151    }
152}
153
154impl<Device: FtdiCommon> From<Device> for FtInner<Device> {
155    fn from(ft: Device) -> Self {
156        FtInner {
157            ft,
158            direction: 0xFB,
159            value: 0x00,
160            pins: [None; 8],
161        }
162    }
163}
164
165/// Type state for an initialized FTDI HAL.
166///
167/// More information about type states can be found in the [rust-embedded book].
168///
169/// [rust-embedded book]: https://docs.rust-embedded.org/book/static-guarantees/design-contracts.html
170pub struct Initialized;
171
172/// Type state for an uninitialized FTDI HAL.
173///
174/// More information about type states can be found in the [rust-embedded book].
175///
176/// [rust-embedded book]: https://docs.rust-embedded.org/book/static-guarantees/design-contracts.html
177pub struct Uninitialized;
178
179/// FT232H device.
180pub type Ft232hHal<T> = FtHal<Ft232h, T>;
181
182/// FT2232H device.
183pub type Ft2232hHal<T> = FtHal<Ft2232h, T>;
184
185/// FT4232H device.
186pub type Ft4232hHal<T> = FtHal<Ft4232h, T>;
187
188/// FTxxx device.
189#[derive(Debug)]
190pub struct FtHal<Device: FtdiCommon, INITIALIZED> {
191    #[allow(dead_code)]
192    init: INITIALIZED,
193    mtx: Mutex<RefCell<FtInner<Device>>>,
194}
195
196impl<Device: FtdiCommon + TryFrom<Ftdi, Error = DeviceTypeError> + FtdiMpsse>
197    FtHal<Device, Uninitialized>
198{
199    /// Create a new FTxxx structure.
200    ///
201    /// # Example
202    ///
203    /// ```no_run
204    /// use ftd2xx_embedded_hal as hal;
205    ///
206    /// let ftdi = hal::Ft232hHal::new()?.init_default()?;
207    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
208    /// ```
209    pub fn new() -> Result<FtHal<Device, Uninitialized>, DeviceTypeError> {
210        let ft: Device = Ftdi::new()?.try_into()?;
211        Ok(ft.into())
212    }
213
214    /// Create a new FTxxx structure from a serial number.
215    ///
216    /// # Example
217    ///
218    /// ```no_run
219    /// use ftd2xx_embedded_hal as hal;
220    ///
221    /// let ftdi = hal::Ft232hHal::with_serial_number("FT6ASGXH")?.init_default()?;
222    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
223    /// ```
224    pub fn with_serial_number(sn: &str) -> Result<FtHal<Device, Uninitialized>, DeviceTypeError> {
225        let ft: Device = Ftdi::with_serial_number(sn)?.try_into()?;
226        Ok(ft.into())
227    }
228
229    /// Open a `Ftxxx` device by its device description.
230    ///
231    /// # Example
232    ///
233    /// ```no_run
234    /// use libftd2xx::Ft4232h;
235    ///
236    /// Ft4232h::with_description("FT4232H-56Q MiniModule A")?;
237    /// # Ok::<(), libftd2xx::DeviceTypeError>(())
238    /// ```
239    pub fn with_description(
240        description: &str,
241    ) -> Result<FtHal<Device, Uninitialized>, DeviceTypeError> {
242        let ft: Device = Ftdi::with_description(description)?.try_into()?;
243        Ok(ft.into())
244    }
245
246    /// Initialize the FTDI MPSSE with sane defaults.
247    ///
248    /// Default values:
249    ///
250    /// * Reset the FTDI device.
251    /// * 4k USB transfer size.
252    /// * 1s USB read timeout.
253    /// * 1s USB write timeout.
254    /// * 16ms latency timer.
255    /// * 100kHz clock frequency.
256    ///
257    /// # Example
258    ///
259    /// ```no_run
260    /// use ftd2xx_embedded_hal as hal;
261    /// use hal::{Ft232hHal, Initialized, Uninitialized};
262    ///
263    /// let ftdi: Ft232hHal<Uninitialized> = hal::Ft232hHal::new()?;
264    /// let ftdi: Ft232hHal<Initialized> = ftdi.init_default()?;
265    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
266    /// ```
267    pub fn init_default(self) -> Result<FtHal<Device, Initialized>, TimeoutError> {
268        const DEFAULT: MpsseSettings = MpsseSettings {
269            reset: true,
270            in_transfer_size: 4096,
271            read_timeout: Duration::from_secs(1),
272            write_timeout: Duration::from_secs(1),
273            latency_timer: Duration::from_millis(16),
274            mask: 0x00,
275            clock_frequency: Some(100_000),
276        };
277
278        self.init(&DEFAULT)
279    }
280
281    /// Initialize the FTDI MPSSE with custom values.
282    ///
283    /// **Note:** The `mask` field of [`MpsseSettings`] is ignored for this function.
284    ///
285    /// **Note:** The clock frequency will be 2/3 of the specified value when in
286    /// I2C mode.
287    ///
288    /// # Panics
289    ///
290    /// Panics if the `clock_frequency` field of [`MpsseSettings`] is `None`.
291    ///
292    /// # Example
293    ///
294    /// ```no_run
295    /// use ftd2xx_embedded_hal as hal;
296    /// use hal::libftd2xx::MpsseSettings;
297    /// use hal::{Ft232hHal, Initialized, Uninitialized};
298    ///
299    /// let ftdi: Ft232hHal<Uninitialized> = hal::Ft232hHal::new()?;
300    /// let ftdi: Ft232hHal<Initialized> = ftdi.init(&MpsseSettings {
301    ///     clock_frequency: Some(500_000),
302    ///     ..MpsseSettings::default()
303    /// })?;
304    ///
305    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
306    /// ```
307    ///
308    /// [`MpsseSettings`]: libftd2xx::MpsseSettings
309    pub fn init(
310        self,
311        mpsse_settings: &MpsseSettings,
312    ) -> Result<FtHal<Device, Initialized>, TimeoutError> {
313        {
314            let lock = self.mtx.lock().expect("Failed to aquire FTDI mutex");
315            let mut inner = lock.borrow_mut();
316            let mut settings = *mpsse_settings;
317            settings.mask = inner.direction;
318            inner.ft.initialize_mpsse(&mpsse_settings)?;
319        }
320
321        Ok(FtHal {
322            init: Initialized,
323            mtx: self.mtx,
324        })
325    }
326}
327
328impl<Device: FtdiCommon> From<Device> for FtHal<Device, Uninitialized> {
329    /// Create a new FT232H structure from a specific FT232H device.
330    ///
331    /// # Examples
332    ///
333    /// Selecting a device with a specific serial number.
334    ///
335    /// ```no_run
336    /// use ftd2xx_embedded_hal as hal;
337    /// use hal::libftd2xx::Ft232h;
338    /// use hal::Ft232hHal;
339    ///
340    /// let ft = Ft232h::with_serial_number("FT59UO4C")?;
341    /// let ftdi = Ft232hHal::from(ft).init_default()?;
342    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
343    /// ```
344    ///
345    /// Selecting a device with a specific description.
346    ///
347    /// ```no_run
348    /// use ftd2xx_embedded_hal as hal;
349    /// use hal::libftd2xx::Ft232h;
350    /// use hal::FtHal;
351    ///
352    /// let ft = Ft232h::with_description("My device description")?;
353    /// let ftdi = FtHal::from(ft).init_default()?;
354    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
355    /// ```
356    fn from(ft: Device) -> Self {
357        FtHal {
358            init: Uninitialized,
359            mtx: Mutex::new(RefCell::new(ft.into())),
360        }
361    }
362}
363
364impl<Device: FtdiCommon> FtHal<Device, Initialized> {
365    /// Aquire the SPI peripheral for the FT232H.
366    ///
367    /// Pin assignments:
368    /// * AD0 => SCK
369    /// * AD1 => MOSI
370    /// * AD2 => MISO
371    ///
372    /// # Panics
373    ///
374    /// Panics if pin 0, 1, or 2 are already in use.
375    ///
376    /// # Example
377    ///
378    /// ```no_run
379    /// use ftd2xx_embedded_hal as hal;
380    ///
381    /// let ftdi = hal::Ft232hHal::new()?.init_default()?;
382    /// let mut spi = ftdi.spi()?;
383    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
384    /// ```
385    pub fn spi(&self) -> Result<Spi<Device>, TimeoutError> {
386        Spi::new(&self.mtx)
387    }
388
389    /// Aquire the I2C peripheral for the FT232H.
390    ///
391    /// Pin assignments:
392    /// * AD0 => SCL
393    /// * AD1 => SDA
394    /// * AD2 => SDA
395    ///
396    /// Yes, AD1 and AD2 are both SDA.
397    /// These pins must be shorted together for I2C operation.
398    ///
399    /// # Panics
400    ///
401    /// Panics if pin 0, 1, or 2 are already in use.
402    ///
403    /// # Example
404    ///
405    /// ```no_run
406    /// use ftd2xx_embedded_hal as hal;
407    ///
408    /// let ftdi = hal::Ft232hHal::new()?.init_default()?;
409    /// let mut i2c = ftdi.i2c()?;
410    /// # Ok::<(), std::boxed::Box<dyn std::error::Error>>(())
411    /// ```
412    pub fn i2c(&self) -> Result<I2c<Device>, TimeoutError> {
413        I2c::new(&self.mtx)
414    }
415
416    /// Aquire the digital output pin 0 for the FT232H.
417    ///
418    /// # Panics
419    ///
420    /// Panics if the pin is already in-use.
421    pub fn ad0(&self) -> OutputPin<Device> {
422        OutputPin::new(&self.mtx, 0)
423    }
424
425    /// Aquire the digital output pin 1 for the FT232H.
426    ///
427    /// # Panics
428    ///
429    /// Panics if the pin is already in-use.
430    pub fn ad1(&self) -> OutputPin<Device> {
431        OutputPin::new(&self.mtx, 1)
432    }
433
434    /// Aquire the digital output pin 2 for the FT232H.
435    ///
436    /// # Panics
437    ///
438    /// Panics if the pin is already in-use.
439    pub fn ad2(&self) -> OutputPin<Device> {
440        OutputPin::new(&self.mtx, 2)
441    }
442
443    /// Aquire the digital output pin 3 for the FT232H.
444    ///
445    /// # Panics
446    ///
447    /// Panics if the pin is already in-use.
448    pub fn ad3(&self) -> OutputPin<Device> {
449        OutputPin::new(&self.mtx, 3)
450    }
451
452    /// Aquire the digital output pin 4 for the FT232H.
453    ///
454    /// # Panics
455    ///
456    /// Panics if the pin is already in-use.
457    pub fn ad4(&self) -> OutputPin<Device> {
458        OutputPin::new(&self.mtx, 4)
459    }
460
461    /// Aquire the digital output pin 5 for the FT232H.
462    ///
463    /// # Panics
464    ///
465    /// Panics if the pin is already in-use.
466    pub fn ad5(&self) -> OutputPin<Device> {
467        OutputPin::new(&self.mtx, 5)
468    }
469
470    /// Aquire the digital output pin 6 for the FT232H.
471    ///
472    /// # Panics
473    ///
474    /// Panics if the pin is already in-use.
475    pub fn ad6(&self) -> OutputPin<Device> {
476        OutputPin::new(&self.mtx, 6)
477    }
478
479    /// Aquire the digital output pin 7 for the FT232H.
480    ///
481    /// # Panics
482    ///
483    /// Panics if the pin is already in-use.
484    pub fn ad7(&self) -> OutputPin<Device> {
485        OutputPin::new(&self.mtx, 7)
486    }
487}