Skip to content

Commit

Permalink
Add blocking version of ArbiterDevice
Browse files Browse the repository at this point in the history
  • Loading branch information
korken89 committed Jun 23, 2024
1 parent 861a63d commit 0d1319a
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 197 deletions.
1 change: 1 addition & 0 deletions rtic-sync/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ For each category, _Added_, _Changed_, _Fixed_ add new entries at the top!

- `defmt v0.3` derives added and forwarded to `embedded-hal(-x)` crates.
- signal structure
- Add `arbiter::spi::BlockingArbiterDevice` that helps during initialization of RTIC apps. After initialization is complete, convert an `BlockingArbiterDevice` into an `ArbiterDevice` using `BlockingArbiterDevice::into_non_blocking()`

## v1.2.0 - 2024-01-10

Expand Down
210 changes: 13 additions & 197 deletions rtic-sync/src/arbiter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@
//! }
//! ```

use core::cell::UnsafeCell;
use core::future::poll_fn;
use core::ops::{Deref, DerefMut};
use core::pin::Pin;
use core::task::{Poll, Waker};
use core::{
cell::UnsafeCell,
future::poll_fn,
ops::{Deref, DerefMut},
pin::Pin,
task::{Poll, Waker},
};
use portable_atomic::{fence, AtomicBool, Ordering};
use rtic_common::{
dropper::OnDrop,
wait_queue::{Link, WaitQueue},
};

use rtic_common::dropper::OnDrop;
use rtic_common::wait_queue::{Link, WaitQueue};
pub mod i2c;
pub mod spi;

/// This is needed to make the async closure in `send` accept that we "share"
/// the link possible between threads.
Expand Down Expand Up @@ -191,196 +197,6 @@ impl<'a, T> DerefMut for ExclusiveAccess<'a, T> {
}
}

/// SPI bus sharing using [`Arbiter`]
pub mod spi {
use super::Arbiter;
use embedded_hal::digital::OutputPin;
use embedded_hal_async::{
delay::DelayNs,
spi::{ErrorType, Operation, SpiBus, SpiDevice},
};
use embedded_hal_bus::spi::DeviceError;

/// [`Arbiter`]-based shared bus implementation.
pub struct ArbiterDevice<'a, BUS, CS, D> {
bus: &'a Arbiter<BUS>,
cs: CS,
delay: D,
}

impl<'a, BUS, CS, D> ArbiterDevice<'a, BUS, CS, D> {
/// Create a new [`ArbiterDevice`].
pub fn new(bus: &'a Arbiter<BUS>, cs: CS, delay: D) -> Self {
Self { bus, cs, delay }
}
}

impl<'a, BUS, CS, D> ErrorType for ArbiterDevice<'a, BUS, CS, D>
where
BUS: ErrorType,
CS: OutputPin,
{
type Error = DeviceError<BUS::Error, CS::Error>;
}

impl<'a, Word, BUS, CS, D> SpiDevice<Word> for ArbiterDevice<'a, BUS, CS, D>
where
Word: Copy + 'static,
BUS: SpiBus<Word>,
CS: OutputPin,
D: DelayNs,
{
async fn transaction(
&mut self,
operations: &mut [Operation<'_, Word>],
) -> Result<(), DeviceError<BUS::Error, CS::Error>> {
let mut bus = self.bus.access().await;

self.cs.set_low().map_err(DeviceError::Cs)?;

let op_res = 'ops: {
for op in operations {
let res = match op {
Operation::Read(buf) => bus.read(buf).await,
Operation::Write(buf) => bus.write(buf).await,
Operation::Transfer(read, write) => bus.transfer(read, write).await,
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await,
Operation::DelayNs(ns) => match bus.flush().await {
Err(e) => Err(e),
Ok(()) => {
self.delay.delay_ns(*ns).await;
Ok(())
}
},
};
if let Err(e) = res {
break 'ops Err(e);
}
}
Ok(())
};

// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush().await;
let cs_res = self.cs.set_high();

op_res.map_err(DeviceError::Spi)?;
flush_res.map_err(DeviceError::Spi)?;
cs_res.map_err(DeviceError::Cs)?;

Ok(())
}
}
}

/// I2C bus sharing using [`Arbiter`]
///
/// An Example how to use it in RTIC application:
/// ```text
/// #[app(device = some_hal, peripherals = true, dispatchers = [TIM16])]
/// mod app {
/// use core::mem::MaybeUninit;
/// use rtic_sync::{arbiter::{i2c::ArbiterDevice, Arbiter},
///
/// #[shared]
/// struct Shared {}
///
/// #[local]
/// struct Local {
/// ens160: Ens160<ArbiterDevice<'static, I2c<'static, I2C1>>>,
/// }
///
/// #[init(local = [
/// i2c_arbiter: MaybeUninit<Arbiter<I2c<'static, I2C1>>> = MaybeUninit::uninit(),
/// ])]
/// fn init(cx: init::Context) -> (Shared, Local) {
/// let i2c = I2c::new(cx.device.I2C1);
/// let i2c_arbiter = cx.local.i2c_arbiter.write(Arbiter::new(i2c));
/// let ens160 = Ens160::new(ArbiterDevice::new(i2c_arbiter), 0x52);
///
/// i2c_sensors::spawn(i2c_arbiter).ok();
///
/// (Shared {}, Local { ens160 })
/// }
///
/// #[task(local = [ens160])]
/// async fn i2c_sensors(cx: i2c_sensors::Context, i2c: &'static Arbiter<I2c<'static, I2C1>>) {
/// use sensor::Asensor;
///
/// loop {
/// // Use scope to make sure I2C access is dropped.
/// {
/// // Read from sensor driver that wants to use I2C directly.
/// let mut i2c = i2c.access().await;
/// let status = Asensor::status(&mut i2c).await;
/// }
///
/// // Read ENS160 sensor.
/// let eco2 = cx.local.ens160.eco2().await;
/// }
/// }
/// }
/// ```
pub mod i2c {
use super::Arbiter;
use embedded_hal::i2c::{AddressMode, ErrorType, Operation};
use embedded_hal_async::i2c::I2c;

/// [`Arbiter`]-based shared bus implementation for I2C.
pub struct ArbiterDevice<'a, BUS> {
bus: &'a Arbiter<BUS>,
}

impl<'a, BUS> ArbiterDevice<'a, BUS> {
/// Create a new [`ArbiterDevice`] for I2C.
pub fn new(bus: &'a Arbiter<BUS>) -> Self {
Self { bus }
}
}

impl<'a, BUS> ErrorType for ArbiterDevice<'a, BUS>
where
BUS: ErrorType,
{
type Error = BUS::Error;
}

impl<'a, BUS, A> I2c<A> for ArbiterDevice<'a, BUS>
where
BUS: I2c<A>,
A: AddressMode,
{
async fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> {
let mut bus = self.bus.access().await;
bus.read(address, read).await
}

async fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> {
let mut bus = self.bus.access().await;
bus.write(address, write).await
}

async fn write_read(
&mut self,
address: A,
write: &[u8],
read: &mut [u8],
) -> Result<(), Self::Error> {
let mut bus = self.bus.access().await;
bus.write_read(address, write, read).await
}

async fn transaction(
&mut self,
address: A,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
let mut bus = self.bus.access().await;
bus.transaction(address, operations).await
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
106 changes: 106 additions & 0 deletions rtic-sync/src/arbiter/i2c.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! I2C bus sharing using [`Arbiter`]
//!
//! An Example how to use it in RTIC application:
//! ```text
//! #[app(device = some_hal, peripherals = true, dispatchers = [TIM16])]
//! mod app {
//! use core::mem::MaybeUninit;
//! use rtic_sync::{arbiter::{i2c::ArbiterDevice, Arbiter},
//!
//! #[shared]
//! struct Shared {}
//!
//! #[local]
//! struct Local {
//! ens160: Ens160<ArbiterDevice<'static, I2c<'static, I2C1>>>,
//! }
//!
//! #[init(local = [
//! i2c_arbiter: MaybeUninit<Arbiter<I2c<'static, I2C1>>> = MaybeUninit::uninit(),
//! ])]
//! fn init(cx: init::Context) -> (Shared, Local) {
//! let i2c = I2c::new(cx.device.I2C1);
//! let i2c_arbiter = cx.local.i2c_arbiter.write(Arbiter::new(i2c));
//! let ens160 = Ens160::new(ArbiterDevice::new(i2c_arbiter), 0x52);
//!
//! i2c_sensors::spawn(i2c_arbiter).ok();
//!
//! (Shared {}, Local { ens160 })
//! }
//!
//! #[task(local = [ens160])]
//! async fn i2c_sensors(cx: i2c_sensors::Context, i2c: &'static Arbiter<I2c<'static, I2C1>>) {
//! use sensor::Asensor;
//!
//! loop {
//! // Use scope to make sure I2C access is dropped.
//! {
//! // Read from sensor driver that wants to use I2C directly.
//! let mut i2c = i2c.access().await;
//! let status = Asensor::status(&mut i2c).await;
//! }
//!
//! // Read ENS160 sensor.
//! let eco2 = cx.local.ens160.eco2().await;
//! }
//! }
//! }
//! ```

use super::Arbiter;
use embedded_hal::i2c::{AddressMode, ErrorType, Operation};
use embedded_hal_async::i2c::I2c;

/// [`Arbiter`]-based shared bus implementation for I2C.
pub struct ArbiterDevice<'a, BUS> {
bus: &'a Arbiter<BUS>,
}

impl<'a, BUS> ArbiterDevice<'a, BUS> {
/// Create a new [`ArbiterDevice`] for I2C.
pub fn new(bus: &'a Arbiter<BUS>) -> Self {
Self { bus }
}
}

impl<'a, BUS> ErrorType for ArbiterDevice<'a, BUS>
where
BUS: ErrorType,
{
type Error = BUS::Error;
}

impl<'a, BUS, A> I2c<A> for ArbiterDevice<'a, BUS>
where
BUS: I2c<A>,
A: AddressMode,
{
async fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> {
let mut bus = self.bus.access().await;
bus.read(address, read).await
}

async fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> {
let mut bus = self.bus.access().await;
bus.write(address, write).await
}

async fn write_read(
&mut self,
address: A,
write: &[u8],
read: &mut [u8],
) -> Result<(), Self::Error> {
let mut bus = self.bus.access().await;
bus.write_read(address, write, read).await
}

async fn transaction(
&mut self,
address: A,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
let mut bus = self.bus.access().await;
bus.transaction(address, operations).await
}
}
Loading

0 comments on commit 0d1319a

Please sign in to comment.