embedded_hal_bus/spi/critical_section.rs
1use core::cell::RefCell;
2use critical_section::Mutex;
3use embedded_hal::delay::DelayNs;
4use embedded_hal::digital::OutputPin;
5use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice};
6
7use super::DeviceError;
8use crate::spi::shared::transaction;
9
10/// `critical-section`-based shared bus [`SpiDevice`] implementation.
11///
12/// This allows for sharing an [`SpiBus`], obtaining multiple [`SpiDevice`] instances,
13/// each with its own `CS` pin.
14///
15/// Sharing is implemented with a `critical-section` [`Mutex`]. A critical section is taken for
16/// the entire duration of a transaction. This allows sharing a single bus across multiple threads (interrupt priority levels).
17/// The downside is critical sections typically require globally disabling interrupts, so `CriticalSectionDevice` will likely
18/// negatively impact real-time properties, such as interrupt latency. If you can, prefer using
19/// [`RefCellDevice`](super::RefCellDevice) instead, which does not require taking critical sections.
20pub struct CriticalSectionDevice<'a, BUS, CS, D> {
21 bus: &'a Mutex<RefCell<BUS>>,
22 cs: CS,
23 delay: D,
24}
25
26impl<'a, BUS, CS, D> CriticalSectionDevice<'a, BUS, CS, D> {
27 /// Create a new [`CriticalSectionDevice`].
28 #[inline]
29 pub fn new(bus: &'a Mutex<RefCell<BUS>>, cs: CS, delay: D) -> Self {
30 Self { bus, cs, delay }
31 }
32}
33
34impl<'a, BUS, CS> CriticalSectionDevice<'a, BUS, CS, super::NoDelay> {
35 /// Create a new [`CriticalSectionDevice`] without support for in-transaction delays.
36 ///
37 /// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice`
38 /// contract, which mandates delay support. It is relatively rare for drivers to use
39 /// in-transaction delays, so you might still want to use this method because it's more practical.
40 ///
41 /// Note that a future version of the driver might start using delays, causing your
42 /// code to panic. This wouldn't be considered a breaking change from the driver side, because
43 /// drivers are allowed to assume `SpiDevice` implementations comply with the contract.
44 /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade
45 /// the driver crate, you might want to pin the driver's version.
46 ///
47 /// # Panics
48 ///
49 /// The returned device will panic if you try to execute a transaction
50 /// that contains any operations of type [`Operation::DelayNs`].
51 #[inline]
52 pub fn new_no_delay(bus: &'a Mutex<RefCell<BUS>>, cs: CS) -> Self {
53 Self {
54 bus,
55 cs,
56 delay: super::NoDelay,
57 }
58 }
59}
60
61impl<'a, BUS, CS, D> ErrorType for CriticalSectionDevice<'a, BUS, CS, D>
62where
63 BUS: ErrorType,
64 CS: OutputPin,
65{
66 type Error = DeviceError<BUS::Error, CS::Error>;
67}
68
69impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for CriticalSectionDevice<'a, BUS, CS, D>
70where
71 BUS: SpiBus<Word>,
72 CS: OutputPin,
73 D: DelayNs,
74{
75 #[inline]
76 fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
77 critical_section::with(|cs| {
78 let bus = &mut *self.bus.borrow_ref_mut(cs);
79
80 transaction(operations, bus, &mut self.delay, &mut self.cs)
81 })
82 }
83}