Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion embedded-hal-bus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ version = "0.3.0"

[features]
# Enable shared bus implementations using `std::sync::Mutex`
std = ["alloc"]
std = ["alloc", "mutex/std"]
# Use `portable-atomic` to enable `atomic-device` on devices without native atomic CAS
#
# `portable-atomic` emulates atomic CAS functionality, allowing `embedded-hal-bus` to use `atomic-device` on hardware
Expand All @@ -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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused:

The mutex crate itself contains the following explanation of the relationship between mutex and mutex-traits:

The mutex-traits crate should be used by library crates that want to be generic over different ways of exclusive access.

The mutex crate should be used by applications that need to select which implementation is appropriate for their use case.

As embedded-hal-bus is a library, shouldn't it depend on mutex-traits?

But then, I don't get how code like MutexTraitsDevice implementations should be written without using the mutex crate. So this is more likely a documentation issue in mutex and mutex-traits (or just me being particularly slow today) than an actual issue with this pull request.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mutex-traits is used in the library that wants to provide a mutex implementation. For example a RTOS library. embedded-hal-bus should use mutex crate.


[package.metadata.docs.rs]
features = ["std", "async"]
Expand Down
2 changes: 2 additions & 0 deletions embedded-hal-bus/src/i2c/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub use refcell::*;
mod mutex;
#[cfg(feature = "std")]
pub use mutex::*;
mod mutex_traits;
pub use mutex_traits::*;
mod critical_section;
pub use self::critical_section::*;
#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))]
Expand Down
65 changes: 65 additions & 0 deletions embedded-hal-bus/src/i2c/mutex_traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use embedded_hal::i2c::{ErrorType, I2c};
use mutex::{BlockingMutex, RawMutex};

type Mutex<R, T> = BlockingMutex<R, T>;

/// `mutex-traits`-based shared bus [`I2c`] implementation.
///
/// Whether a single bus can be used across multiple threads depends on which
/// implementation of `RawMutex` is used.
pub struct MutexTraitsDevice<'a, R, T> {
bus: &'a Mutex<R, T>,
}

impl<'a, R: RawMutex, T> MutexTraitsDevice<'a, R, T> {
/// Create a new `MutexTraitsDevice`.
#[inline]
pub fn new(bus: &'a Mutex<R, T>) -> Self {
Self { bus }
}
}

impl<R, T> ErrorType for MutexTraitsDevice<'_, R, T>
where
T: I2c,
{
type Error = T::Error;
}

impl<R: RawMutex, T> I2c for MutexTraitsDevice<'_, 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)
}
}
2 changes: 2 additions & 0 deletions embedded-hal-bus/src/spi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ pub use refcell::*;
mod mutex;
#[cfg(feature = "std")]
pub use mutex::*;
mod mutex_traits;
pub use mutex_traits::*;
#[cfg(any(feature = "portable-atomic", target_has_atomic = "8"))]
mod atomic;
mod critical_section;
Expand Down
95 changes: 95 additions & 0 deletions embedded-hal-bus/src/spi/mutex_traits.rs
Original file line number Diff line number Diff line change
@@ -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<R, T> = BlockingMutex<R, T>;

/// `mutex-traits`-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
/// implementation of `RawMutex` is used.
pub struct MutexTraitsDevice<'a, R, BUS, CS, D> {
bus: &'a Mutex<R, BUS>,
cs: CS,
delay: D,
}

impl<'a, R: RawMutex, BUS, CS, D> MutexTraitsDevice<'a, R, BUS, CS, D> {
/// Create a new [`MutexTraitsDevice`].
///
/// 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<R, BUS>, mut cs: CS, delay: D) -> Result<Self, CS::Error>
where
CS: OutputPin,
{
cs.set_high()?;
Ok(Self { bus, cs, delay })
}
}

impl<'a, R: RawMutex, BUS, CS> MutexTraitsDevice<'a, R, BUS, CS, super::NoDelay> {
/// Create a new [`MutexTraitsDevice`] 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<R, BUS>, mut cs: CS) -> Result<Self, CS::Error>
where
CS: OutputPin,
{
cs.set_high()?;
Ok(Self {
bus,
cs,
delay: super::NoDelay,
})
}
}

impl<R, BUS, CS, D> ErrorType for MutexTraitsDevice<'_, R, BUS, CS, D>
where
R: RawMutex,
BUS: ErrorType,
CS: OutputPin,
{
type Error = DeviceError<BUS::Error, CS::Error>;
}

impl<Word: Copy + 'static, R, BUS, CS, D> SpiDevice<Word> for MutexTraitsDevice<'_, R, BUS, CS, D>
where
R: RawMutex,
BUS: SpiBus<Word>,
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)
}
}