From aded8f1c59ba88c407958b697eae27b57ee4a261 Mon Sep 17 00:00:00 2001 From: Alex M Date: Mon, 28 Dec 2020 13:53:52 -0800 Subject: [PATCH] Change read/write methods to return number of bytes. --- CHANGELOG.md | 11 ++++- Cargo.toml | 2 +- README.md | 2 +- src/errors.rs | 6 +-- src/lib.rs | 119 +++++++++++++++++++++++++++++++------------------- src/mpsse.rs | 69 ++++++++++++++--------------- 6 files changed, 125 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fb5e77..a48a1cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.24.0] - 2020-12-28 +### Changed +- **BREAKING CHANGE** `read` and `write` methods now return + `Result` where `usize` is the the number of bytes read or + written. + Previous `read` and `write` functionality that returned + `Result<(), TimeoutError>` is replaced by `read_all` and `write_all`. + ## [0.23.0] - 2020-10-23 ### Changed - `synchronize_mpsse` will now timeout if no read data is received and a read @@ -56,7 +64,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Prior releases A changelog was not kept for prior releases. -[Unreleased]: https://github.com/newAM/libftd2xx-rs/compare/0.23.0...HEAD +[Unreleased]: https://github.com/newAM/libftd2xx-rs/compare/0.24.0...HEAD +[0.24.0]: https://github.com/newAM/libftd2xx-rs/releases/tag/0.24.0 [0.23.0]: https://github.com/newAM/libftd2xx-rs/releases/tag/0.23.0 [0.22.0]: https://github.com/newAM/libftd2xx-rs/releases/tag/0.22.0 [0.21.1]: https://github.com/newAM/libftd2xx-rs/releases/tag/0.21.1 diff --git a/Cargo.toml b/Cargo.toml index 65f9afd..6fecb59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libftd2xx" -version = "0.23.0" # remember to update html_root_url +version = "0.24.0" # remember to update html_root_url authors = ["Alex M. "] edition = "2018" description = "Rust safe wrapper around the libftd2xx-ffi crate." diff --git a/README.md b/README.md index c1e36e4..13f8685 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ permission from FTDI. ```toml [dependencies] -libftd2xx = "~0.23.0" +libftd2xx = "~0.24.0" ``` This is a basic example to get your started. diff --git a/src/errors.rs b/src/errors.rs index 217411e..eab9a1d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -18,10 +18,10 @@ use libftd2xx_ffi::{ /// FTDI timeout errors. /// -/// This is used by the [`read`] and [`write`] functions. +/// This is used by the [`read_all`] and [`write_all`] methods. /// -/// [`read`]: trait.FtdiCommon.html#method.read -/// [`write`]: trait.FtdiCommon.html#method.read +/// [`read_all`]: trait.FtdiCommon.html#method.read_all +/// [`write_all`]: trait.FtdiCommon.html#method.read_all #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum TimeoutError { /// FTDI status errors. diff --git a/src/lib.rs b/src/lib.rs index c5908d1..5e55c39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ //! //! ```toml //! [dependencies] -//! libftd2xx = "~0.23.0" +//! libftd2xx = "~0.24.0" //! ``` //! //! This is a basic example to get your started. @@ -74,7 +74,7 @@ //! [libftd2xx-ffi]: https://github.com/newAM/libftd2xx-ffi-rs //! [setup executable]: https://www.ftdichip.com/Drivers/CDM/CDM21228_Setup.zip //! [udev]: https://en.wikipedia.org/wiki/Udev -#![doc(html_root_url = "https://docs.rs/libftd2xx/0.23.0")] +#![doc(html_root_url = "https://docs.rs/libftd2xx/0.24.0")] #![deny(missing_docs)] mod errors; @@ -1226,20 +1226,53 @@ pub trait FtdiCommon { ) } + /// Read data from the device, returning the number of bytes read. + /// + /// See [`read_all`] for more information about reading from the device. + /// + /// # Example + /// + /// ```no_run + /// use libftd2xx::{Ftdi, FtdiCommon}; + /// + /// const BUF_SIZE: usize = 256; + /// let mut buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; + /// let mut ft = Ftdi::new()?; + /// let bytes_read: usize = ft.read(&mut buf)?; + /// assert_eq!(bytes_read, BUF_SIZE); + /// # Ok::<(), libftd2xx::FtStatus>(()) + /// ``` + /// + /// [`read_all`]: #method.read_all + fn read(&mut self, buf: &mut [u8]) -> Result { + let mut bytes_returned: u32 = 0; + let len: u32 = u32::try_from(buf.len()).unwrap(); + trace!("FT_Read({:?}, _, {}, _)", self.handle(), len); + let status: FT_STATUS = unsafe { + FT_Read( + self.handle(), + buf.as_mut_ptr() as *mut c_void, + len, + &mut bytes_returned, + ) + }; + ft_result(usize::try_from(bytes_returned).unwrap(), status) + } + /// Read data from the device. /// /// This method does not return until the buffer has been filled, if no /// timeout has been set. /// The number of bytes in the receive queue can be determined by calling /// [`queue_status`], and then an buffer equal to the length of that - /// value can be passed to [`read`] so that the function reads the device - /// and returns immediately. + /// value can be passed to [`read_all`] so that the function reads the + /// device and returns immediately. /// /// When a read timeout value has been specified in a previous call to - /// [`set_timeouts`], [`read`] returns when the timer expires or when the - /// buffer has been filled, whichever occurs first. - /// If the timeout occurred, [`read`] reads available data into the buffer - /// and returns [`TimeoutError`] error. + /// [`set_timeouts`], [`read_all`] returns when the timer expires or when + /// the buffer has been filled, whichever occurs first. + /// If the timeout occurred, [`read_all`] reads available data into the + /// buffer and returns the [`TimeoutError`] error. /// /// # Examples /// @@ -1253,7 +1286,7 @@ pub trait FtdiCommon { /// let rx_bytes = ft.queue_status()?; /// /// if rx_bytes > 0 { - /// ft.read(&mut buf[0..rx_bytes])?; + /// ft.read_all(&mut buf[0..rx_bytes])?; /// } /// # Ok::<(), libftd2xx::TimeoutError>(()) /// ``` @@ -1270,7 +1303,7 @@ pub trait FtdiCommon { /// /// ft.set_timeouts(Duration::from_millis(5000), Duration::from_millis(0))?; /// - /// let valid_data = match ft.read(&mut buf) { + /// let valid_data = match ft.read_all(&mut buf) { /// Err(e) => match e { /// TimeoutError::Timeout { /// actual: actual, @@ -1288,28 +1321,12 @@ pub trait FtdiCommon { /// # Ok::<(), libftd2xx::TimeoutError>(()) /// ``` /// - /// [`read`]: #method.read + /// [`read_all`]: #method.read_all /// [`queue_status`]: #method.queue_status /// [`set_timeouts`]: #method.set_timeouts /// [`TimeoutError`]: ./enum.TimeoutError.html - fn read(&mut self, buf: &mut [u8]) -> Result<(), TimeoutError> { - let mut bytes_returned: u32 = 0; - let len: u32 = u32::try_from(buf.len()).unwrap(); - trace!("FT_Read({:?}, _, {}, _)", self.handle(), len); - let status: FT_STATUS = unsafe { - FT_Read( - self.handle(), - buf.as_mut_ptr() as *mut c_void, - len, - &mut bytes_returned, - ) - }; - - if status != 0 { - return Err(TimeoutError::FtStatus(status.into())); - } - - let num_read = usize::try_from(bytes_returned).unwrap(); + fn read_all(&mut self, buf: &mut [u8]) -> Result<(), TimeoutError> { + let num_read = self.read(buf)?; if num_read != buf.len() { Err(TimeoutError::Timeout { expected: buf.len(), @@ -1330,10 +1347,36 @@ pub trait FtdiCommon { /// const BUF_SIZE: usize = 256; /// let buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; /// let mut ft = Ftdi::new()?; - /// ft.write(&buf)?; + /// ft.write_all(&buf)?; /// # Ok::<(), libftd2xx::TimeoutError>(()) /// ``` - fn write(&mut self, buf: &[u8]) -> Result<(), TimeoutError> { + fn write_all(&mut self, buf: &[u8]) -> Result<(), TimeoutError> { + let num_written = self.write(buf)?; + if num_written != buf.len() { + Err(TimeoutError::Timeout { + expected: buf.len(), + actual: num_written, + }) + } else { + Ok(()) + } + } + + /// Write data to the device, returning how many bytes were written. + /// + /// # Example + /// + /// ```no_run + /// use libftd2xx::{Ftdi, FtdiCommon}; + /// + /// const BUF_SIZE: usize = 256; + /// let buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; + /// let mut ft = Ftdi::new()?; + /// let bytes_written: usize = ft.write(&buf)?; + /// assert_eq!(bytes_written, BUF_SIZE); + /// # Ok::<(), libftd2xx::FtStatus>(()) + /// ``` + fn write(&mut self, buf: &[u8]) -> Result { let mut bytes_written: u32 = 0; let len: u32 = u32::try_from(buf.len()).unwrap(); trace!("FT_Write({:?}, _, {}, _)", self.handle(), len); @@ -1345,19 +1388,7 @@ pub trait FtdiCommon { &mut bytes_written, ) }; - if status != 0 { - return Err(TimeoutError::FtStatus(status.into())); - } - - let num_written = usize::try_from(bytes_written).unwrap(); - if num_written != buf.len() { - Err(TimeoutError::Timeout { - expected: buf.len(), - actual: num_written, - }) - } else { - Ok(()) - } + ft_result(usize::try_from(bytes_written).unwrap(), status) } /// This function purges the transmit buffers in the device. diff --git a/src/mpsse.rs b/src/mpsse.rs index 1885d63..8ce3714 100644 --- a/src/mpsse.rs +++ b/src/mpsse.rs @@ -461,7 +461,7 @@ pub trait FtdiMpsse: FtdiCommon { buf.push((value & 0xFF) as u8); buf.push(((value >> 8) & 0xFF) as u8); - self.write(&buf.as_slice()) + self.write_all(&buf.as_slice()) } /// Initialize the MPSSE. @@ -520,7 +520,7 @@ pub trait FtdiMpsse: FtdiCommon { if let Some(frequency) = settings.clock_frequency { mpsse_cmd = mpsse_cmd.set_clock(frequency, Self::DEVICE_TYPE); } - self.write(mpsse_cmd.as_slice())?; + self.write_all(mpsse_cmd.as_slice())?; Ok(()) } @@ -555,13 +555,13 @@ pub trait FtdiMpsse: FtdiCommon { fn synchronize_mpsse(&mut self) -> Result<(), TimeoutError> { self.purge_rx()?; debug_assert_eq!(self.queue_status()?, 0); - self.write(&[ECHO_CMD_2])?; + self.write_all(&[ECHO_CMD_2])?; // the FTDI MPSSE basics polls the queue status here // we purged the RX buffer so the response should always be 2 bytes // this allows us to leverage the timeout built into read let mut buf: [u8; 2] = [0; 2]; - self.read(&mut buf)?; + self.read_all(&mut buf)?; if buf[0] == 0xFA && buf[1] == ECHO_CMD_2 { Ok(()) @@ -583,7 +583,7 @@ pub trait FtdiMpsse: FtdiCommon { /// # Ok::<(), std::boxed::Box>(()) /// ``` fn enable_loopback(&mut self) -> Result<(), TimeoutError> { - self.write(&[MpsseCmd::EnableLoopback.into()]) + self.write_all(&[MpsseCmd::EnableLoopback.into()]) } /// Disable the MPSSE loopback state. @@ -599,7 +599,7 @@ pub trait FtdiMpsse: FtdiCommon { /// # Ok::<(), std::boxed::Box>(()) /// ``` fn disable_loopback(&mut self) -> Result<(), TimeoutError> { - self.write(&[MpsseCmd::DisableLoopback.into()]) + self.write_all(&[MpsseCmd::DisableLoopback.into()]) } /// Set the pin direction and state of the lower byte (0-7) GPIO pins on the @@ -626,7 +626,7 @@ pub trait FtdiMpsse: FtdiCommon { /// # Ok::<(), std::boxed::Box>(()) /// ``` fn set_gpio_lower(&mut self, state: u8, direction: u8) -> Result<(), TimeoutError> { - self.write(&[MpsseCmd::SetDataBitsLowbyte.into(), state, direction]) + self.write_all(&[MpsseCmd::SetDataBitsLowbyte.into(), state, direction]) } /// Get the pin state state of the lower byte (0-7) GPIO pins on the MPSSE @@ -647,12 +647,12 @@ pub trait FtdiMpsse: FtdiCommon { /// # Ok::<(), std::boxed::Box>(()) /// ``` fn gpio_lower(&mut self) -> Result { - self.write(&[ + self.write_all(&[ MpsseCmd::GetDataBitsLowbyte.into(), MpsseCmd::SendImmediate.into(), ])?; let mut buf: [u8; 1] = [0]; - self.read(&mut buf)?; + self.read_all(&mut buf)?; Ok(buf[0]) } @@ -678,7 +678,7 @@ pub trait FtdiMpsse: FtdiCommon { /// /// [`set_gpio_lower`]: #method.set_gpio_lower fn set_gpio_upper(&mut self, state: u8, direction: u8) -> Result<(), TimeoutError> { - self.write(&[MpsseCmd::SetDataBitsHighbyte.into(), state, direction]) + self.write_all(&[MpsseCmd::SetDataBitsHighbyte.into(), state, direction]) } /// Get the pin state state of the upper byte (8-15) GPIO pins on the MPSSE @@ -692,12 +692,12 @@ pub trait FtdiMpsse: FtdiCommon { /// [`gpio_lower`]: #method.gpio_lower /// [`set_gpio_upper`]: #method.set_gpio_upper fn gpio_upper(&mut self) -> Result { - self.write(&[ + self.write_all(&[ MpsseCmd::GetDataBitsHighbyte.into(), MpsseCmd::SendImmediate.into(), ])?; let mut buf: [u8; 1] = [0]; - self.read(&mut buf)?; + self.read_all(&mut buf)?; Ok(buf[0]) } @@ -729,7 +729,7 @@ pub trait FtdiMpsse: FtdiCommon { assert!(len <= 65536); let mut payload = vec![mode.into(), (len & 0xFF) as u8, ((len >> 8) & 0xFF) as u8]; payload.extend_from_slice(&data); - self.write(&payload.as_slice()) + self.write_all(&payload.as_slice()) } /// Clock data in. @@ -743,8 +743,8 @@ pub trait FtdiMpsse: FtdiCommon { } len -= 1; assert!(len <= 65536); - self.write(&[mode.into(), (len & 0xFF) as u8, ((len >> 8) & 0xFF) as u8])?; - self.read(data) + self.write_all(&[mode.into(), (len & 0xFF) as u8, ((len >> 8) & 0xFF) as u8])?; + self.read_all(data) } /// Clock data in and out at the same time. @@ -757,8 +757,8 @@ pub trait FtdiMpsse: FtdiCommon { assert!(len <= 65536); let mut payload = vec![mode.into(), (len & 0xFF) as u8, ((len >> 8) & 0xFF) as u8]; payload.extend_from_slice(&data); - self.write(&payload.as_slice())?; - self.read(data) + self.write_all(&payload.as_slice())?; + self.read_all(data) } } @@ -794,7 +794,7 @@ pub trait Ftx232hMpsse: FtdiMpsse { /// # Ok::<(), std::boxed::Box>(()) /// ``` fn enable_3phase_data_clocking(&mut self) -> Result<(), TimeoutError> { - self.write(&[MpsseCmd::Enable3PhaseClocking.into()]) + self.write_all(&[MpsseCmd::Enable3PhaseClocking.into()]) } /// Disable 3 phase data clocking. @@ -817,7 +817,7 @@ pub trait Ftx232hMpsse: FtdiMpsse { /// # Ok::<(), std::boxed::Box>(()) /// ``` fn disable_3phase_data_clocking(&mut self) -> Result<(), TimeoutError> { - self.write(&[MpsseCmd::Disable3PhaseClocking.into()]) + self.write_all(&[MpsseCmd::Disable3PhaseClocking.into()]) } } @@ -826,15 +826,16 @@ pub trait Ftx232hMpsse: FtdiMpsse { /// For details about the MPSSE read the [FTDI MPSSE Basics]. /// /// This structure is a `Vec` that the methods push bytewise commands onto. -/// These commands can then be written to the device with the [`write`] method. +/// These commands can then be written to the device with the [`write_all`] +/// method. /// /// This is useful for creating commands that need to do multiple operations -/// quickly, since individual [`write`] calls can be expensive. +/// quickly, since individual [`write_all`] calls can be expensive. /// For example, this can be used to set a GPIO low and clock data out for /// SPI operations. /// /// [FTDI MPSSE Basics]: https://www.ftdichip.com/Support/Documents/AppNotes/AN_135_MPSSE_Basics.pdf -/// [`write`]: ./trait.FtdiCommon.html#method.write +/// [`write_all`]: ./trait.FtdiCommon.html#method.write_all pub struct MpsseCmdBuilder(pub Vec); impl MpsseCmdBuilder { @@ -874,7 +875,7 @@ impl MpsseCmdBuilder { /// let cmd = MpsseCmdBuilder::new().set_clock(100_000, DeviceType::FT232H); /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// # Ok::<(), std::boxed::Box>(()) /// ``` pub fn as_slice(&self) -> &[u8] { @@ -903,7 +904,7 @@ impl MpsseCmdBuilder { /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; /// ft.initialize_mpsse_default()?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// # Ok::<(), std::boxed::Box>(()) /// ``` pub fn set_clock(mut self, frequency: u32, device_type: DeviceType) -> Self { @@ -932,7 +933,7 @@ impl MpsseCmdBuilder { /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; /// ft.initialize_mpsse_default()?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// # Ok::<(), std::boxed::Box>(()) /// ``` pub fn enable_loopback(mut self) -> Self { @@ -951,7 +952,7 @@ impl MpsseCmdBuilder { /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; /// ft.initialize_mpsse_default()?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// # Ok::<(), std::boxed::Box>(()) /// ``` pub fn disable_loopback(mut self) -> Self { @@ -979,7 +980,7 @@ impl MpsseCmdBuilder { /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; /// ft.initialize_mpsse_default()?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// # Ok::<(), std::boxed::Box>(()) /// ``` pub fn disable_3phase_data_clocking(mut self) -> Self { @@ -1010,7 +1011,7 @@ impl MpsseCmdBuilder { /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; /// ft.initialize_mpsse_default()?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// # Ok::<(), std::boxed::Box>(()) /// ``` pub fn enable_3phase_data_clocking(mut self) -> Self { @@ -1041,7 +1042,7 @@ impl MpsseCmdBuilder { /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; /// ft.initialize_mpsse_default()?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// # Ok::<(), std::boxed::Box>(()) /// ``` pub fn set_gpio_lower(mut self, state: u8, direction: u8) -> Self { @@ -1079,7 +1080,7 @@ impl MpsseCmdBuilder { /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; /// ft.initialize_mpsse_default()?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// # Ok::<(), std::boxed::Box>(()) /// ``` pub fn set_gpio_upper(mut self, state: u8, direction: u8) -> Self { @@ -1100,9 +1101,9 @@ impl MpsseCmdBuilder { /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; /// ft.initialize_mpsse_default()?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// let mut buf: [u8; 1] = [0; 1]; - /// ft.read(&mut buf)?; + /// ft.read_all(&mut buf)?; /// println!("GPIO lower state: 0x{:02X}", buf[0]); /// # Ok::<(), std::boxed::Box>(()) /// ``` @@ -1126,9 +1127,9 @@ impl MpsseCmdBuilder { /// /// let mut ft = Ft232h::with_serial_number("FT5AVX6B")?; /// ft.initialize_mpsse_default()?; - /// ft.write(cmd.as_slice())?; + /// ft.write_all(cmd.as_slice())?; /// let mut buf: [u8; 1] = [0; 1]; - /// ft.read(&mut buf)?; + /// ft.read_all(&mut buf)?; /// println!("GPIO upper state: 0x{:02X}", buf[0]); /// # Ok::<(), std::boxed::Box>(()) /// ```