From be43383966f562ef42cc9de3126f90f7a51fff68 Mon Sep 17 00:00:00 2001 From: Emilie Burgun Date: Fri, 5 Jul 2024 15:46:51 +0200 Subject: [PATCH 1/5] Add a Rc>-based implementation of SpiDevice --- embedded-hal-bus/Cargo.toml | 4 +- embedded-hal-bus/src/spi/mod.rs | 5 ++ embedded-hal-bus/src/spi/rc.rs | 90 +++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 embedded-hal-bus/src/spi/rc.rs diff --git a/embedded-hal-bus/Cargo.toml b/embedded-hal-bus/Cargo.toml index 2c54bfbd..6ab831fb 100644 --- a/embedded-hal-bus/Cargo.toml +++ b/embedded-hal-bus/Cargo.toml @@ -15,9 +15,11 @@ repository = "https://github.com/rust-embedded/embedded-hal" version = "0.2.0" [features] -std = [] +std = ["alloc"] async = ["dep:embedded-hal-async"] defmt-03 = ["dep:defmt-03", "embedded-hal/defmt-03", "embedded-hal-async?/defmt-03"] +# Enables additional utilities requiring a global allocator. +alloc = [] [dependencies] embedded-hal = { version = "1.0.0", path = "../embedded-hal" } diff --git a/embedded-hal-bus/src/spi/mod.rs b/embedded-hal-bus/src/spi/mod.rs index d55fa5ea..b654a54c 100644 --- a/embedded-hal-bus/src/spi/mod.rs +++ b/embedded-hal-bus/src/spi/mod.rs @@ -16,6 +16,11 @@ mod critical_section; mod shared; pub use atomic::*; +#[cfg(feature = "alloc")] +mod rc; +#[cfg(feature = "alloc")] +pub use rc::*; + pub use self::critical_section::*; #[cfg(feature = "defmt-03")] diff --git a/embedded-hal-bus/src/spi/rc.rs b/embedded-hal-bus/src/spi/rc.rs new file mode 100644 index 00000000..1d58db37 --- /dev/null +++ b/embedded-hal-bus/src/spi/rc.rs @@ -0,0 +1,90 @@ +extern crate alloc; +use alloc::rc::Rc; + +use core::cell::RefCell; +use embedded_hal::delay::DelayNs; +use embedded_hal::digital::OutputPin; +use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; + +use super::DeviceError; +use crate::spi::shared::transaction; + +/// Implementation of [`SpiDevice`] around a bus shared with `Rc>`. +/// This is the reference-counting equivalent of [`RefCellDevice`](super::RefCellDevice), requiring allocation. +/// +/// A single [`SpiBus`] is shared via [`RefCell`], and its ownership is handled by [`Rc`]. +/// Both of these mechanisms only allow sharing within a single thread (or interrupt priority level). +/// For this reason, this does not implement [`Send`]. +/// +/// When this structure is dropped, the reference count of the `Bus` will be decremented, +/// and the bus driver will be cleaned up when that count reaches zero. +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +pub struct RcDevice { + bus: Rc>, + cs: Cs, + delay: Delay, +} + +impl RcDevice { + /// Creates a new [`RcDevice`]. + /// + /// This sets the `cs` pin high, and returns an error if that fails. + /// It is recommended to have already set that pin high the moment it has been configured as an output, to avoid glitches. + /// + /// This function does not increment the reference count: + /// you will need to call `Rc::clone(&bus)` if you only have a `&RefCell`. + #[inline] + pub fn new(bus: Rc>, mut cs: Cs, delay: Delay) -> Result + where + Cs: OutputPin, + { + cs.set_high()?; + + Ok(Self { bus, cs, delay }) + } +} + +impl RcDevice { + /// Creates a new [`RcDevice`] without support for in-transaction delays. + /// + /// **Warning**: It's advised to prefer [`RcDevice::new`], + /// as the contract of [`SpiDevice`] requests support for in-transaction delays. + /// + /// Refer to [`RefCellDevice::new_no_delay`](super::RefCellDevice::new_no_delay) for more information. + #[inline] + pub fn new_no_delay(bus: Rc>, mut cs: Cs) -> Result + where + Cs: OutputPin, + { + cs.set_high()?; + + Ok(Self { + bus, + cs, + delay: super::NoDelay, + }) + } +} + +impl ErrorType for RcDevice +where + Bus: ErrorType, + Cs: OutputPin, +{ + type Error = DeviceError; +} + +impl SpiDevice for RcDevice +where + Word: Copy + 'static, + Bus: SpiBus, + Cs: OutputPin, + Delay: DelayNs, +{ + #[inline] + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + + transaction(operations, bus, &mut self.delay, &mut self.cs) + } +} From aaa29c0ec44e1a65fa5b4a34d1deb3a21d0ab3e6 Mon Sep 17 00:00:00 2001 From: Emilie Burgun Date: Fri, 5 Jul 2024 17:01:28 +0200 Subject: [PATCH 2/5] Fix small mistake in the documentation of RcDevice::new --- embedded-hal-bus/src/spi/rc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/embedded-hal-bus/src/spi/rc.rs b/embedded-hal-bus/src/spi/rc.rs index 1d58db37..5a1c558b 100644 --- a/embedded-hal-bus/src/spi/rc.rs +++ b/embedded-hal-bus/src/spi/rc.rs @@ -16,8 +16,8 @@ use crate::spi::shared::transaction; /// Both of these mechanisms only allow sharing within a single thread (or interrupt priority level). /// For this reason, this does not implement [`Send`]. /// -/// When this structure is dropped, the reference count of the `Bus` will be decremented, -/// and the bus driver will be cleaned up when that count reaches zero. +/// When this structure is dropped, the reference count of the `Bus` instance will be decremented, +/// and it will be cleaned up once the reference count reaches zero. #[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] pub struct RcDevice { bus: Rc>, @@ -32,7 +32,7 @@ impl RcDevice { /// It is recommended to have already set that pin high the moment it has been configured as an output, to avoid glitches. /// /// This function does not increment the reference count: - /// you will need to call `Rc::clone(&bus)` if you only have a `&RefCell`. + /// you will need to call `Rc::clone(&bus)` if you only have a `&Rc>`. #[inline] pub fn new(bus: Rc>, mut cs: Cs, delay: Delay) -> Result where From 9c609615afe25e9c7ee05fee7225da53ed309e84 Mon Sep 17 00:00:00 2001 From: Emilie Burgun Date: Fri, 5 Jul 2024 17:15:54 +0200 Subject: [PATCH 3/5] Add Rc>-based implementation of shared I2c buses --- embedded-hal-bus/Cargo.toml | 2 + embedded-hal-bus/src/i2c/mod.rs | 5 +++ embedded-hal-bus/src/i2c/rc.rs | 75 +++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 embedded-hal-bus/src/i2c/rc.rs diff --git a/embedded-hal-bus/Cargo.toml b/embedded-hal-bus/Cargo.toml index 6ab831fb..836f050b 100644 --- a/embedded-hal-bus/Cargo.toml +++ b/embedded-hal-bus/Cargo.toml @@ -20,6 +20,8 @@ async = ["dep:embedded-hal-async"] defmt-03 = ["dep:defmt-03", "embedded-hal/defmt-03", "embedded-hal-async?/defmt-03"] # Enables additional utilities requiring a global allocator. alloc = [] +# TODO: remove this +default = ["alloc"] [dependencies] embedded-hal = { version = "1.0.0", path = "../embedded-hal" } diff --git a/embedded-hal-bus/src/i2c/mod.rs b/embedded-hal-bus/src/i2c/mod.rs index 5295492f..6420d06c 100644 --- a/embedded-hal-bus/src/i2c/mod.rs +++ b/embedded-hal-bus/src/i2c/mod.rs @@ -10,3 +10,8 @@ mod critical_section; pub use self::critical_section::*; mod atomic; pub use atomic::*; + +#[cfg(feature = "alloc")] +mod rc; +#[cfg(feature = "alloc")] +pub use rc::*; diff --git a/embedded-hal-bus/src/i2c/rc.rs b/embedded-hal-bus/src/i2c/rc.rs new file mode 100644 index 00000000..2f162168 --- /dev/null +++ b/embedded-hal-bus/src/i2c/rc.rs @@ -0,0 +1,75 @@ +extern crate alloc; +use alloc::rc::Rc; + +use core::cell::RefCell; +use embedded_hal::i2c::{ErrorType, I2c}; + +/// `Rc>`-based shared bus [`I2c`] implementation. +/// This is the reference-counting equivalent of [`RefCellDevice`](super::RefCellDevice). +/// +/// Sharing is implemented with a [`RefCell`] and ownership is managed by [`Rc`]. +/// Like [`RefCellDevice`](super::RefCellDevice), `RcDevice` instances are not [`Send`], +/// so they can only be shared within a single thread (interrupt priority level). +/// +/// When this `RcDevice` is dropped, the reference count of the I2C bus will be decremented. +/// Once that reference count hits zero, it will be cleaned up. +#[cfg_attr(docsrs, doc(cfg(any(feature = "std", feature = "alloc"))))] +pub struct RcDevice { + bus: Rc>, +} + +impl RcDevice { + /// Creates a new `RcDevice`. + /// + /// This function does not increment the reference count for the bus: + /// you will need to call `Rc::clone(&bus)` if you only have a `&Rc>`. + #[inline] + pub fn new(bus: Rc>) -> Self { + Self { bus } + } +} + +impl ErrorType for RcDevice +where + Bus: ErrorType, +{ + type Error = Bus::Error; +} + +impl I2c for RcDevice +where + Bus: I2c, +{ + #[inline] + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.read(address, read) + } + + #[inline] + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + 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.borrow_mut(); + 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.borrow_mut(); + bus.transaction(address, operations) + } +} From 1c02ef9f8644d388cafc46cd391dab6e89e8506b Mon Sep 17 00:00:00 2001 From: Shad Amethyst Date: Fri, 26 Jul 2024 11:45:03 +0200 Subject: [PATCH 4/5] Remove the alloc feature from default for testing Co-authored-by: Diego Barrios Romero --- embedded-hal-bus/Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/embedded-hal-bus/Cargo.toml b/embedded-hal-bus/Cargo.toml index 836f050b..6ab831fb 100644 --- a/embedded-hal-bus/Cargo.toml +++ b/embedded-hal-bus/Cargo.toml @@ -20,8 +20,6 @@ async = ["dep:embedded-hal-async"] defmt-03 = ["dep:defmt-03", "embedded-hal/defmt-03", "embedded-hal-async?/defmt-03"] # Enables additional utilities requiring a global allocator. alloc = [] -# TODO: remove this -default = ["alloc"] [dependencies] embedded-hal = { version = "1.0.0", path = "../embedded-hal" } From 21e82bb21d7b02b299299c0bdae493ac27d71c56 Mon Sep 17 00:00:00 2001 From: Emilie Burgun Date: Fri, 26 Jul 2024 11:52:08 +0200 Subject: [PATCH 5/5] Document changes and document the `alloc` feature --- embedded-hal-bus/CHANGELOG.md | 3 ++- embedded-hal-bus/README.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/embedded-hal-bus/CHANGELOG.md b/embedded-hal-bus/CHANGELOG.md index d92532d3..45d0f6cc 100644 --- a/embedded-hal-bus/CHANGELOG.md +++ b/embedded-hal-bus/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -(Add unreleased changes here) +- Added the `alloc` feature. +- Added a new `RcDevice` for I2C and SPI, a reference-counting equivalent to `RefCellDevice`. ## [v0.2.0] - 2024-04-23 diff --git a/embedded-hal-bus/README.md b/embedded-hal-bus/README.md index ce71eec9..2089cb8a 100644 --- a/embedded-hal-bus/README.md +++ b/embedded-hal-bus/README.md @@ -34,6 +34,7 @@ provides mechanisms to obtain multiple `I2c` instances out of a single `I2c` ins `std::error::Error` for `DeviceError`. - **`async`**: enable `embedded-hal-async` support. - **`defmt-03`**: Derive `defmt::Format` from `defmt` 0.3 for enums and structs. +- **`alloc`**: enable implementations using `alloc` (for instance, `spi::RcDevice`, which makes use of `alloc::rc::Rc`) ## Minimum Supported Rust Version (MSRV)