Skip to content

Commit

Permalink
Merge pull request #613 from adri326/rc-spi-device
Browse files Browse the repository at this point in the history
Add a Rc<RefCell<Bus>>-based implementation of SpiDevice and I2C
  • Loading branch information
Dirbaio authored Jul 26, 2024
2 parents 4e9f3ed + 21e82bb commit f91fcbc
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 2 deletions.
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)
}
}

0 comments on commit f91fcbc

Please sign in to comment.