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}