diff --git a/embedded-hal-bus/Cargo.toml b/embedded-hal-bus/Cargo.toml index 7f404e3d..a53165d3 100644 --- a/embedded-hal-bus/Cargo.toml +++ b/embedded-hal-bus/Cargo.toml @@ -37,6 +37,7 @@ embedded-hal-async = { version = "1.0.0", path = "../embedded-hal-async", option critical-section = { version = "1.0" } defmt-03 = { package = "defmt", version = "0.3", optional = true } portable-atomic = {version = "1.3", default-features = false, optional = true, features = ["require-cas"]} +mutex = "1.0" [package.metadata.docs.rs] features = ["std", "async"] diff --git a/embedded-hal-bus/src/i2c/mod.rs b/embedded-hal-bus/src/i2c/mod.rs index 5f322631..c7d2e479 100644 --- a/embedded-hal-bus/src/i2c/mod.rs +++ b/embedded-hal-bus/src/i2c/mod.rs @@ -6,6 +6,8 @@ pub use refcell::*; mod mutex; #[cfg(feature = "std")] pub use mutex::*; +mod mutex_trait; +pub use mutex_trait::*; mod critical_section; pub use self::critical_section::*; #[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] diff --git a/embedded-hal-bus/src/i2c/mutex_trait.rs b/embedded-hal-bus/src/i2c/mutex_trait.rs new file mode 100644 index 00000000..994c1a7b --- /dev/null +++ b/embedded-hal-bus/src/i2c/mutex_trait.rs @@ -0,0 +1,65 @@ +use embedded_hal::i2c::{ErrorType, I2c}; +use mutex::{BlockingMutex, RawMutex}; + +type Mutex = BlockingMutex; + +/// `mutex-trait`-based shared bus [`I2c`] implementation. +/// +/// Whether a single bus can be used across multiple threads depends on which +/// implementations of `RawMutex` are used. +pub struct MutexTraitDevice<'a, R, T> { + bus: &'a Mutex, +} + +impl<'a, R: RawMutex, T> MutexTraitDevice<'a, R, T> { + /// Create a new `MutexTraitDevice`. + #[inline] + pub fn new(bus: &'a Mutex) -> Self { + Self { bus } + } +} + +impl ErrorType for MutexTraitDevice<'_, R, T> +where + T: I2c, +{ + type Error = T::Error; +} + +impl I2c for MutexTraitDevice<'_, R, T> +where + T: I2c, +{ + #[inline] + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock(); + bus.read(address, read) + } + + #[inline] + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock(); + bus.write(address, write) + } + + #[inline] + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock(); + bus.write_read(address, write, read) + } + + #[inline] + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock(); + bus.transaction(address, operations) + } +} diff --git a/embedded-hal-bus/src/spi/mod.rs b/embedded-hal-bus/src/spi/mod.rs index 65972628..edd67e57 100644 --- a/embedded-hal-bus/src/spi/mod.rs +++ b/embedded-hal-bus/src/spi/mod.rs @@ -11,6 +11,8 @@ pub use refcell::*; mod mutex; #[cfg(feature = "std")] pub use mutex::*; +mod mutex_trait; +pub use mutex_trait::*; #[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))] mod atomic; mod critical_section; diff --git a/embedded-hal-bus/src/spi/mutex_trait.rs b/embedded-hal-bus/src/spi/mutex_trait.rs new file mode 100644 index 00000000..2f676556 --- /dev/null +++ b/embedded-hal-bus/src/spi/mutex_trait.rs @@ -0,0 +1,95 @@ +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; +use mutex::{BlockingMutex, RawMutex}; + +use super::DeviceError; +use crate::spi::shared::transaction; + +type Mutex = BlockingMutex; + +/// `mutex-trait`-based shared bus [`SpiDevice`] implementation. +/// +/// This allows for sharing an [`SpiBus`], obtaining multiple [`SpiDevice`] instances, +/// each with its own `CS` pin. +/// +/// Whether a single bus can be used across multiple threads depends on which +/// implementations of `RawMutex` are used. +pub struct MutexTraitDevice<'a, R, BUS, CS, D> { + bus: &'a Mutex, + cs: CS, + delay: D, +} + +impl<'a, R: RawMutex, BUS, CS, D> MutexTraitDevice<'a, R, BUS, CS, D> { + /// Create a new [`MutexTraitDevice`]. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + #[inline] + pub fn new(bus: &'a Mutex, mut cs: CS, delay: D) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { bus, cs, delay }) + } +} + +impl<'a, R: RawMutex, BUS, CS> MutexTraitDevice<'a, R, BUS, CS, super::NoDelay> { + /// Create a new [`MutexTraitDevice`] without support for in-transaction delays. + /// + /// This sets the `cs` pin high, and returns an error if that fails. It is recommended + /// to set the pin high the moment it's configured as an output, to avoid glitches. + /// + /// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice` + /// contract, which mandates delay support. It is relatively rare for drivers to use + /// in-transaction delays, so you might still want to use this method because it's more practical. + /// + /// Note that a future version of the driver might start using delays, causing your + /// code to panic. This wouldn't be considered a breaking change from the driver side, because + /// drivers are allowed to assume `SpiDevice` implementations comply with the contract. + /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade + /// the driver crate, you might want to pin the driver's version. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type [`Operation::DelayNs`]. + #[inline] + pub fn new_no_delay(bus: &'a Mutex, mut cs: CS) -> Result + where + CS: OutputPin, + { + cs.set_high()?; + Ok(Self { + bus, + cs, + delay: super::NoDelay, + }) + } +} + +impl ErrorType for MutexTraitDevice<'_, R, BUS, CS, D> +where + R: RawMutex, + BUS: ErrorType, + CS: OutputPin, +{ + type Error = DeviceError; +} + +impl SpiDevice for MutexTraitDevice<'_, R, BUS, CS, D> +where + R: RawMutex, + BUS: SpiBus, + CS: OutputPin, + D: DelayNs, +{ + #[inline] + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock(); + + transaction(operations, bus, &mut self.delay, &mut self.cs) + } +}