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}