From 7bc6afb73a3b8b86f2b4d283baef8125c11257e4 Mon Sep 17 00:00:00 2001 From: agarof Date: Thu, 26 Oct 2023 21:53:08 +0200 Subject: [PATCH 1/4] Add embedded-hal dependency --- crates/flipperzero/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/flipperzero/Cargo.toml b/crates/flipperzero/Cargo.toml index 71e35ae0..e92ee59c 100644 --- a/crates/flipperzero/Cargo.toml +++ b/crates/flipperzero/Cargo.toml @@ -35,6 +35,10 @@ lock_api = "0.4" digest = "0.10" bitflags = "1.0" +# Embedded-hal +embedded-hal = { version = "1.0.0-rc.1", optional = true } +embedded-hal-0 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"], optional = true } + # Docs document-features = { workspace = true, optional = true } From 5aa09a09ccf803066feafd224e361b4604755327 Mon Sep 17 00:00:00 2001 From: agarof Date: Thu, 26 Oct 2023 21:53:34 +0200 Subject: [PATCH 2/4] Implement embedded-hal's I2C traits --- crates/flipperzero/src/gpio/i2c.rs | 397 +++++++++++++++++++++++++++++ 1 file changed, 397 insertions(+) diff --git a/crates/flipperzero/src/gpio/i2c.rs b/crates/flipperzero/src/gpio/i2c.rs index 8dd1e186..e5507bf4 100644 --- a/crates/flipperzero/src/gpio/i2c.rs +++ b/crates/flipperzero/src/gpio/i2c.rs @@ -309,6 +309,199 @@ impl BusHandle { Err(Error::TransferFailed) } } + + /// Reads data from `device` and writes it to the `data` buffer. + /// + /// `timeout` is in milliseconds. + pub fn tx( + &mut self, + device: DeviceAddress, + data: &[u8], + timeout: Duration, + ) -> Result<(), Error> { + unsafe { + sys::furi_hal_i2c_tx( + self.handle, + device.0, + data.as_ptr(), + data.len(), + timeout.as_millis() as u32, + ) + } + .then_some(()) + .ok_or(Error::TransferFailed) + } + + /// Writes the given data to `device`. + /// + /// `timeout` is in milliseconds. + pub fn rx( + &mut self, + device: DeviceAddress, + data: &mut [u8], + timeout: Duration, + ) -> Result<(), Error> { + unsafe { + sys::furi_hal_i2c_rx( + self.handle, + device.0, + data.as_mut_ptr(), + data.len(), + timeout.as_millis() as u32, + ) + } + .then_some(()) + .ok_or(Error::TransferFailed) + } + + /// Writes the data in `write` to `device` and then reads from it into the `read` buffer. + /// + /// `timeout` is in milliseconds. + pub fn trx( + &mut self, + device: DeviceAddress, + write: &[u8], + read: &mut [u8], + timeout: Duration, + ) -> Result<(), Error> { + unsafe { + sys::furi_hal_i2c_trx( + self.handle, + device.0, + write.as_ptr(), + write.len(), + read.as_mut_ptr(), + read.len(), + timeout.as_millis() as u32, + ) + } + .then_some(()) + .ok_or(Error::TransferFailed) + } + + /// Execute the provided operations on the I2C bus. + /// + /// Transaction contract: + /// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate. + /// - Data from adjacent operations of the same type are sent after each other without an SP or SR. + /// - Between adjacent operations of a different type an SR and SAD+R/W is sent. + /// - After executing the last operation an SP is sent automatically. + /// - If the last operation is a `Read` the master does not send an acknowledge for the last byte. + /// + /// - `ST` = start condition + /// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing + /// - `SR` = repeated start condition + /// - `SP` = stop condition + pub fn transaction( + &mut self, + device: DeviceAddress, + operations: &mut [Operation], + timeout: Duration, + ) -> Result<(), Error> { + self.transaction_impl(device, operations, timeout) + } + + // This is similar to `trx`, the only difference being that it sends a RESTART condition + // between the two transfers instead of STOP + START + #[cfg(any(feature = "embedded-hal", feature = "embedded-hal-0"))] + fn write_read_impl( + &mut self, + device: DeviceAddress, + write: &[u8], + read: &mut [u8], + timeout: Duration, + ) -> Result<(), Error> { + unsafe { + sys::furi_hal_i2c_tx_ext( + self.handle, + device.0.into(), + false, + write.as_ptr(), + write.len(), + sys::FuriHalI2cBegin_FuriHalI2cBeginStart, + sys::FuriHalI2cEnd_FuriHalI2cEndAwaitRestart, + timeout.as_millis() as u32, + ) && sys::furi_hal_i2c_rx_ext( + self.handle, + device.0.into(), + false, + read.as_mut_ptr(), + read.len(), + sys::FuriHalI2cBegin_FuriHalI2cBeginRestart, + sys::FuriHalI2cEnd_FuriHalI2cEndStop, + timeout.as_millis() as u32, + ) + } + .then_some(()) + .ok_or(Error::TransferFailed) + } + + // This function is generic to allow the implementation of both versions of the embedded_hal + // transaction traits + fn transaction_impl<'a, O>( + &mut self, + device: DeviceAddress, + operations: &mut [O], + timeout: Duration, + ) -> Result<(), Error> + where + O: OperationLike + 'a, + { + use sys::{ + FuriHalI2cBegin_FuriHalI2cBeginRestart as BeginRestart, + FuriHalI2cBegin_FuriHalI2cBeginResume as BeginResume, + FuriHalI2cBegin_FuriHalI2cBeginStart as BeginStart, + FuriHalI2cEnd_FuriHalI2cEndAwaitRestart as EndAwaitRestart, + FuriHalI2cEnd_FuriHalI2cEndPause as EndPause, + FuriHalI2cEnd_FuriHalI2cEndStop as EndStop, + }; + + let mut operations = operations.iter_mut().peekable(); + let mut start = BeginStart; + let address = device.0.into(); + + while let Some(op) = operations.next() { + let (end, next_start) = match (op.kind(), operations.peek().map(|next| next.kind())) { + (OperationKind::Read, Some(OperationKind::Read)) + | (OperationKind::Write, Some(OperationKind::Write)) => (EndPause, BeginResume), + (_, Some(_)) => (EndAwaitRestart, BeginRestart), + (_, None) => (EndStop, BeginStart), + }; + + let result = unsafe { + match op.as_op() { + Operation::Read(buffer) => flipperzero_sys::furi_hal_i2c_rx_ext( + self.handle, + address, + false, + buffer.as_mut_ptr(), + buffer.len(), + start, + end, + timeout.as_millis() as u32, + ), + Operation::Write(buffer) => flipperzero_sys::furi_hal_i2c_tx_ext( + self.handle, + address, + false, + buffer.as_ptr(), + buffer.len(), + start, + end, + timeout.as_millis() as u32, + ), + } + }; + + if !result { + return Err(Error::TransferFailed); + } + + start = next_start; + } + + Ok(()) + } } #[derive(Debug, PartialEq)] @@ -316,6 +509,210 @@ pub enum Error { TransferFailed, } +/// I2C operation. +/// +/// Several operations can be combined as part of a transaction. +pub enum Operation<'a> { + /// Read data into the provided buffer + Read(&'a mut [u8]), + /// Write data from the provided buffer + Write(&'a [u8]), +} + +// These exist to allow compatibility with both embedded_hal 1.0 and 0.2 versions of the Operation +// enum + +enum OperationKind { + Read, + Write, +} + +trait OperationLike { + fn as_op(&mut self) -> Operation; + fn kind(&self) -> OperationKind; +} + +impl OperationLike for Operation<'_> { + fn as_op(&mut self) -> Operation { + match self { + Operation::Read(buffer) => Operation::Read(buffer), + Operation::Write(buffer) => Operation::Write(buffer), + } + } + + fn kind(&self) -> OperationKind { + match self { + Operation::Read(_) => OperationKind::Read, + Operation::Write(_) => OperationKind::Write, + } + } +} + +// embedded_hal specific + +/// An I2C bus implementing the embedded-hal traits +/// +/// It acquires and releases a handle to the underlying bus for each function, similar to +/// [embedded-hal-bus](https://docs.rs/embedded-hal-bus/0.1.0-rc.1/embedded_hal_bus/index.html)' +/// [MutexDevice](https://docs.rs/embedded-hal-bus/0.1.0-rc.1/embedded_hal_bus/i2c/struct.MutexDevice.html). +/// It uses the same timeout duration for each operation. +#[cfg(any(feature = "embedded-hal", feature = "embedded-hal-0"))] +pub struct EmbeddedHalBus { + bus: Bus, + /// The timeout used for each operation + timeout: Duration, +} + +#[cfg(any(feature = "embedded-hal", feature = "embedded-hal-0"))] +impl EmbeddedHalBus { + pub fn new(bus: Bus, timeout: Duration) -> Self { + Self { bus, timeout } + } + + pub fn set_timeout(&mut self, timeout: Duration) { + self.timeout = timeout + } +} + +// embedded_hal 1.0 implementations + +#[cfg(feature = "embedded-hal")] +impl embedded_hal::i2c::Error for Error { + fn kind(&self) -> embedded_hal::i2c::ErrorKind { + embedded_hal::i2c::ErrorKind::Other + } +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal::i2c::ErrorType for EmbeddedHalBus { + type Error = Error; +} + +#[cfg(feature = "embedded-hal")] +impl OperationLike for embedded_hal::i2c::Operation<'_> { + fn as_op(&mut self) -> Operation { + match self { + embedded_hal::i2c::Operation::Read(buffer) => Operation::Read(buffer), + embedded_hal::i2c::Operation::Write(buffer) => Operation::Write(buffer), + } + } + + fn kind(&self) -> OperationKind { + match self { + embedded_hal::i2c::Operation::Read(_) => OperationKind::Read, + embedded_hal::i2c::Operation::Write(_) => OperationKind::Write, + } + } +} + +#[cfg(feature = "embedded-hal")] +impl embedded_hal::i2c::I2c for EmbeddedHalBus { + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.bus + .acquire() + .transaction_impl(DeviceAddress::new(address), operations, self.timeout) + } + + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.bus + .acquire() + .rx(DeviceAddress::new(address), read, self.timeout) + } + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.bus + .acquire() + .tx(DeviceAddress::new(address), write, self.timeout) + } + + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + self.bus + .acquire() + .write_read_impl(DeviceAddress::new(address), write, read, self.timeout) + } +} + +// embedded_hal 0.2 implementations + +#[cfg(feature = "embedded-hal-0")] +impl OperationLike for embedded_hal_0::blocking::i2c::Operation<'_> { + fn as_op(&mut self) -> Operation { + match self { + embedded_hal_0::blocking::i2c::Operation::Read(buffer) => Operation::Read(buffer), + embedded_hal_0::blocking::i2c::Operation::Write(buffer) => Operation::Write(buffer), + } + } + + fn kind(&self) -> OperationKind { + match self { + embedded_hal_0::blocking::i2c::Operation::Read(_) => OperationKind::Read, + embedded_hal_0::blocking::i2c::Operation::Write(_) => OperationKind::Write, + } + } +} + +#[cfg(feature = "embedded-hal-0")] +impl embedded_hal_0::blocking::i2c::Read for EmbeddedHalBus { + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.bus + .acquire() + .rx(DeviceAddress::new(address), buffer, self.timeout) + } +} + +#[cfg(feature = "embedded-hal-0")] +impl embedded_hal_0::blocking::i2c::Write for EmbeddedHalBus { + type Error = Error; + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.bus + .acquire() + .tx(DeviceAddress::new(address), bytes, self.timeout) + } +} + +#[cfg(feature = "embedded-hal-0")] +impl embedded_hal_0::blocking::i2c::WriteRead for EmbeddedHalBus { + type Error = Error; + + fn write_read( + &mut self, + address: u8, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), Self::Error> { + self.bus + .acquire() + .write_read_impl(DeviceAddress::new(address), bytes, buffer, self.timeout) + } +} + +#[cfg(feature = "embedded-hal-0")] +impl embedded_hal_0::blocking::i2c::Transactional for EmbeddedHalBus { + type Error = Error; + + fn exec( + &mut self, + address: u8, + operations: &mut [embedded_hal_0::blocking::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.bus + .acquire() + .transaction_impl(DeviceAddress::new(address), operations, self.timeout) + } +} + #[flipperzero_test::tests] mod tests { use super::{ From 52827533f190fc25547e31e0110516a33c6cdf96 Mon Sep 17 00:00:00 2001 From: agarof Date: Mon, 30 Oct 2023 23:23:38 +0100 Subject: [PATCH 3/4] Add standard derives to the Operation enum --- crates/flipperzero/src/gpio/i2c.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/flipperzero/src/gpio/i2c.rs b/crates/flipperzero/src/gpio/i2c.rs index e5507bf4..e62fbea9 100644 --- a/crates/flipperzero/src/gpio/i2c.rs +++ b/crates/flipperzero/src/gpio/i2c.rs @@ -512,6 +512,7 @@ pub enum Error { /// I2C operation. /// /// Several operations can be combined as part of a transaction. +#[derive(Debug, PartialEq, Eq)] pub enum Operation<'a> { /// Read data into the provided buffer Read(&'a mut [u8]), From 2aaacce2f2d97f311b80ebdad3fec698ccb09594 Mon Sep 17 00:00:00 2001 From: agarof Date: Mon, 30 Oct 2023 23:38:28 +0100 Subject: [PATCH 4/4] Remove mentions of milliseconds in i2c methods --- crates/flipperzero/src/gpio/i2c.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/crates/flipperzero/src/gpio/i2c.rs b/crates/flipperzero/src/gpio/i2c.rs index e62fbea9..3d134f09 100644 --- a/crates/flipperzero/src/gpio/i2c.rs +++ b/crates/flipperzero/src/gpio/i2c.rs @@ -134,8 +134,6 @@ impl BusHandle { } /// Enumerates the devices that are present and ready on this bus. - /// - /// `per_device_timeout` is in milliseconds. pub fn enumerate_devices( &mut self, per_device_timeout: Duration, @@ -149,8 +147,6 @@ impl BusHandle { /// Checks if the device with address `i2c_addr` is present and ready on the bus. /// - /// `timeout` is in milliseconds. - /// /// Returns `true` if the device is present and ready, false otherwise. pub fn is_device_ready(&mut self, device: DeviceAddress, timeout: Duration) -> bool { unsafe { @@ -159,8 +155,6 @@ impl BusHandle { } /// Reads the 8-bit register at `reg_addr` on `device`. - /// - /// `timeout` is in milliseconds. pub fn read_u8( &mut self, device: DeviceAddress, @@ -184,8 +178,6 @@ impl BusHandle { } /// Reads the 16-bit register at `reg_addr` on `device`. - /// - /// `timeout` is in milliseconds. pub fn read_u16( &mut self, device: DeviceAddress, @@ -209,8 +201,6 @@ impl BusHandle { } /// Reads `device`'s memory starting at `mem_addr` into the given buffer. - /// - /// `timeout` is in milliseconds. pub fn read_exact( &mut self, device: DeviceAddress, @@ -235,8 +225,6 @@ impl BusHandle { } /// Writes the given value into the 8-bit register at `reg_addr` on `device`. - /// - /// `timeout` is in milliseconds. pub fn write_u8( &mut self, device: DeviceAddress, @@ -260,8 +248,6 @@ impl BusHandle { } /// Writes the given value into the 16-bit register at `reg_addr` on `device`. - /// - /// `timeout` is in milliseconds. pub fn write_u16( &mut self, device: DeviceAddress, @@ -285,8 +271,6 @@ impl BusHandle { } /// Writes the given data into `device`'s memory starting at `mem_addr`. - /// - /// `timeout` is in milliseconds. pub fn write_all( &mut self, device: DeviceAddress, @@ -311,8 +295,6 @@ impl BusHandle { } /// Reads data from `device` and writes it to the `data` buffer. - /// - /// `timeout` is in milliseconds. pub fn tx( &mut self, device: DeviceAddress, @@ -333,8 +315,6 @@ impl BusHandle { } /// Writes the given data to `device`. - /// - /// `timeout` is in milliseconds. pub fn rx( &mut self, device: DeviceAddress, @@ -355,8 +335,6 @@ impl BusHandle { } /// Writes the data in `write` to `device` and then reads from it into the `read` buffer. - /// - /// `timeout` is in milliseconds. pub fn trx( &mut self, device: DeviceAddress,