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}