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    ///
29    /// This sets the `cs` pin high, and returns an error if that fails. It is recommended
30    /// to set the pin high the moment it's configured as an output, to avoid glitches.
31    #[inline]
32    pub fn new(bus: &'a Mutex<RefCell<BUS>>, mut cs: CS, delay: D) -> Result<Self, CS::Error>
33    where
34        CS: OutputPin,
35    {
36        cs.set_high()?;
37        Ok(Self { bus, cs, delay })
38    }
39}
40
41impl<'a, BUS, CS> CriticalSectionDevice<'a, BUS, CS, super::NoDelay> {
42    /// Create a new [`CriticalSectionDevice`] without support for in-transaction delays.
43    ///
44    /// This sets the `cs` pin high, and returns an error if that fails. It is recommended
45    /// to set the pin high the moment it's configured as an output, to avoid glitches.
46    ///
47    /// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice`
48    /// contract, which mandates delay support. It is relatively rare for drivers to use
49    /// in-transaction delays, so you might still want to use this method because it's more practical.
50    ///
51    /// Note that a future version of the driver might start using delays, causing your
52    /// code to panic. This wouldn't be considered a breaking change from the driver side, because
53    /// drivers are allowed to assume `SpiDevice` implementations comply with the contract.
54    /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade
55    /// the driver crate, you might want to pin the driver's version.
56    ///
57    /// # Panics
58    ///
59    /// The returned device will panic if you try to execute a transaction
60    /// that contains any operations of type [`Operation::DelayNs`].
61    #[inline]
62    pub fn new_no_delay(bus: &'a Mutex<RefCell<BUS>>, mut cs: CS) -> Result<Self, CS::Error>
63    where
64        CS: OutputPin,
65    {
66        cs.set_high()?;
67        Ok(Self {
68            bus,
69            cs,
70            delay: super::NoDelay,
71        })
72    }
73}
74
75impl<'a, BUS, CS, D> ErrorType for CriticalSectionDevice<'a, BUS, CS, D>
76where
77    BUS: ErrorType,
78    CS: OutputPin,
79{
80    type Error = DeviceError<BUS::Error, CS::Error>;
81}
82
83impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for CriticalSectionDevice<'a, BUS, CS, D>
84where
85    BUS: SpiBus<Word>,
86    CS: OutputPin,
87    D: DelayNs,
88{
89    #[inline]
90    fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
91        critical_section::with(|cs| {
92            let bus = &mut *self.bus.borrow_ref_mut(cs);
93
94            transaction(operations, bus, &mut self.delay, &mut self.cs)
95        })
96    }
97}