rtic-sync: Add SPI bus sharing with arbiter

This commit is contained in:
Nils Fitinghoff 2023-10-09 16:55:55 +02:00 committed by Emil Fresk
parent 96e7704487
commit ff5cad9cd2
4 changed files with 90 additions and 0 deletions

View file

@ -9,6 +9,8 @@ For each category, _Added_, _Changed_, _Fixed_ add new entries at the top!
### Added ### Added
- `arbiter::spi::ArbiterDevice` for sharing SPI buses using `embedded-hal-async`
### Changed ### Changed
### Fixed ### Fixed

View file

@ -21,6 +21,9 @@ heapless = "0.7"
critical-section = "1" critical-section = "1"
rtic-common = { version = "1.0.0", path = "../rtic-common" } rtic-common = { version = "1.0.0", path = "../rtic-common" }
portable-atomic = { version = "1", default-features = false } 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] [dev-dependencies]
tokio = { version = "1", features = ["rt", "macros", "time"] } tokio = { version = "1", features = ["rt", "macros", "time"] }
@ -29,3 +32,4 @@ tokio = { version = "1", features = ["rt", "macros", "time"] }
[features] [features]
default = [] default = []
testing = ["critical-section/std", "rtic-common/testing"] testing = ["critical-section/std", "rtic-common/testing"]
unstable = ["embedded-hal", "embedded-hal-async", "embedded-hal-bus"]

View file

@ -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<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: DelayUs,
{
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::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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -89,6 +89,7 @@ impl Package {
.chain(std::iter::once(None)) .chain(std::iter::once(None))
.collect() .collect()
} }
Package::RticSync => vec![Some("unstable".to_string()), None],
_ => vec![None], _ => vec![None],
} }
} }