From ff5cad9cd2a3d6c6618bf54e0b126ed9d68ef71a Mon Sep 17 00:00:00 2001 From: Nils Fitinghoff Date: Mon, 9 Oct 2023 16:55:55 +0200 Subject: [PATCH] rtic-sync: Add SPI bus sharing with arbiter --- rtic-sync/CHANGELOG.md | 2 + rtic-sync/Cargo.toml | 4 ++ rtic-sync/src/arbiter.rs | 83 +++++++++++++++++++++++++++++++++++ xtask/src/argument_parsing.rs | 1 + 4 files changed, 90 insertions(+) diff --git a/rtic-sync/CHANGELOG.md b/rtic-sync/CHANGELOG.md index 465f0de549..7047378b45 100644 --- a/rtic-sync/CHANGELOG.md +++ b/rtic-sync/CHANGELOG.md @@ -9,6 +9,8 @@ For each category, _Added_, _Changed_, _Fixed_ add new entries at the top! ### Added +- `arbiter::spi::ArbiterDevice` for sharing SPI buses using `embedded-hal-async` + ### Changed ### Fixed diff --git a/rtic-sync/Cargo.toml b/rtic-sync/Cargo.toml index 064b9fa4be..39f62dc8f7 100644 --- a/rtic-sync/Cargo.toml +++ b/rtic-sync/Cargo.toml @@ -21,6 +21,9 @@ heapless = "0.7" critical-section = "1" rtic-common = { version = "1.0.0", path = "../rtic-common" } portable-atomic = { version = "1", default-features = false } +embedded-hal = { version = "1.0.0-rc.1", optional = true } +embedded-hal-async = { version = "1.0.0-rc.1", optional = true } +embedded-hal-bus = { version = "0.1.0-rc.1", optional = true, features = ["async"] } [dev-dependencies] tokio = { version = "1", features = ["rt", "macros", "time"] } @@ -29,3 +32,4 @@ tokio = { version = "1", features = ["rt", "macros", "time"] } [features] default = [] testing = ["critical-section/std", "rtic-common/testing"] +unstable = ["embedded-hal", "embedded-hal-async", "embedded-hal-bus"] diff --git a/rtic-sync/src/arbiter.rs b/rtic-sync/src/arbiter.rs index 2d66a6772c..5683f93d97 100644 --- a/rtic-sync/src/arbiter.rs +++ b/rtic-sync/src/arbiter.rs @@ -191,6 +191,89 @@ impl<'a, T> DerefMut for ExclusiveAccess<'a, T> { } } +#[cfg(feature = "unstable")] +/// SPI bus sharing using [`Arbiter`] +pub mod spi { + use super::Arbiter; + use embedded_hal::digital::OutputPin; + use embedded_hal_async::{ + delay::DelayUs, + 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, + cs: CS, + delay: D, + } + + impl<'a, BUS, CS, D> ArbiterDevice<'a, BUS, CS, D> { + /// Create a new [`ArbiterDevice`]. + pub fn new(bus: &'a Arbiter, 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; + } + + impl<'a, Word, BUS, CS, D> SpiDevice for ArbiterDevice<'a, BUS, CS, D> + where + Word: Copy + 'static, + BUS: SpiBus, + CS: OutputPin, + D: DelayUs, + { + async fn transaction( + &mut self, + operations: &mut [Operation<'_, Word>], + ) -> Result<(), DeviceError> { + 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::DelayUs(us) => match bus.flush().await { + Err(e) => Err(e), + Ok(()) => { + self.delay.delay_us(*us).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(()) + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/xtask/src/argument_parsing.rs b/xtask/src/argument_parsing.rs index 88ac1e5d8d..b4dcd904e9 100644 --- a/xtask/src/argument_parsing.rs +++ b/xtask/src/argument_parsing.rs @@ -89,6 +89,7 @@ impl Package { .chain(std::iter::once(None)) .collect() } + Package::RticSync => vec![Some("unstable".to_string()), None], _ => vec![None], } }