Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a Rc<RefCell<Bus>>-based implementation of SpiDevice and I2C #613

Merged
merged 5 commits into from
Jul 26, 2024
Merged
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/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion embedded-hal-bus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
1 change: 1 addition & 0 deletions embedded-hal-bus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
5 changes: 5 additions & 0 deletions embedded-hal-bus/src/i2c/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
75 changes: 75 additions & 0 deletions embedded-hal-bus/src/i2c/rc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
extern crate alloc;
use alloc::rc::Rc;

use core::cell::RefCell;
use embedded_hal::i2c::{ErrorType, I2c};

/// `Rc<RefCell<T>>`-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> {
bus: Rc<RefCell<Bus>>,
}

impl<Bus> RcDevice<Bus> {
/// 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<RefCell<Bus>>`.
#[inline]
pub fn new(bus: Rc<RefCell<Bus>>) -> Self {
Self { bus }
}
}

impl<Bus> ErrorType for RcDevice<Bus>
where
Bus: ErrorType,
{
type Error = Bus::Error;
}

impl<Bus> I2c for RcDevice<Bus>
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)
}
}
5 changes: 5 additions & 0 deletions embedded-hal-bus/src/spi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
90 changes: 90 additions & 0 deletions embedded-hal-bus/src/spi/rc.rs
Original file line number Diff line number Diff line change
@@ -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<RefCell<T>>`.
/// 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` 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, Cs, Delay> {
bus: Rc<RefCell<Bus>>,
cs: Cs,
delay: Delay,
}

impl<Bus, Cs, Delay> RcDevice<Bus, Cs, Delay> {
/// 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 `&Rc<RefCell<Bus>>`.
#[inline]
pub fn new(bus: Rc<RefCell<Bus>>, mut cs: Cs, delay: Delay) -> Result<Self, Cs::Error>
where
Cs: OutputPin,
{
cs.set_high()?;

Ok(Self { bus, cs, delay })
}
}

impl<Bus, Cs> RcDevice<Bus, Cs, super::NoDelay> {
/// 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<RefCell<Bus>>, mut cs: Cs) -> Result<Self, Cs::Error>
where
Cs: OutputPin,
{
cs.set_high()?;

Ok(Self {
bus,
cs,
delay: super::NoDelay,
})
}
}

impl<Bus, Cs, Delay> ErrorType for RcDevice<Bus, Cs, Delay>
where
Bus: ErrorType,
Cs: OutputPin,
{
type Error = DeviceError<Bus::Error, Cs::Error>;
}

impl<Word, Bus, Cs, Delay> SpiDevice<Word> for RcDevice<Bus, Cs, Delay>
where
Word: Copy + 'static,
Bus: SpiBus<Word>,
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)
}
}