embedded_hal_bus/spi/
refcell.rs

1use core::cell::RefCell;
2use embedded_hal::delay::DelayNs;
3use embedded_hal::digital::OutputPin;
4use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice};
5
6use super::DeviceError;
7use crate::spi::shared::transaction;
8
9/// `RefCell`-based shared bus [`SpiDevice`] implementation.
10///
11/// This allows for sharing an [`SpiBus`], obtaining multiple [`SpiDevice`] instances,
12/// each with its own `CS` pin.
13///
14/// Sharing is implemented with a `RefCell`. This means it has low overhead, but `RefCellDevice` instances are not `Send`,
15/// so it only allows sharing within a single thread (interrupt priority level). If you need to share a bus across several
16/// threads, use [`CriticalSectionDevice`](super::CriticalSectionDevice) instead.
17pub struct RefCellDevice<'a, BUS, CS, D> {
18    bus: &'a RefCell<BUS>,
19    cs: CS,
20    delay: D,
21}
22
23impl<'a, BUS, CS, D> RefCellDevice<'a, BUS, CS, D> {
24    /// Create a new [`RefCellDevice`].
25    ///
26    /// This sets the `cs` pin high, and returns an error if that fails. It is recommended
27    /// to set the pin high the moment it's configured as an output, to avoid glitches.
28    #[inline]
29    pub fn new(bus: &'a RefCell<BUS>, mut cs: CS, delay: D) -> Result<Self, CS::Error>
30    where
31        CS: OutputPin,
32    {
33        cs.set_high()?;
34        Ok(Self { bus, cs, delay })
35    }
36}
37
38impl<'a, BUS, CS> RefCellDevice<'a, BUS, CS, super::NoDelay> {
39    /// Create a new [`RefCellDevice`] without support for in-transaction delays.
40    ///
41    /// This sets the `cs` pin high, and returns an error if that fails. It is recommended
42    /// to set the pin high the moment it's configured as an output, to avoid glitches.
43    ///
44    /// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice`
45    /// contract, which mandates delay support. It is relatively rare for drivers to use
46    /// in-transaction delays, so you might still want to use this method because it's more practical.
47    ///
48    /// Note that a future version of the driver might start using delays, causing your
49    /// code to panic. This wouldn't be considered a breaking change from the driver side, because
50    /// drivers are allowed to assume `SpiDevice` implementations comply with the contract.
51    /// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade
52    /// the driver crate, you might want to pin the driver's version.
53    ///
54    /// # Panics
55    ///
56    /// The returned device will panic if you try to execute a transaction
57    /// that contains any operations of type [`Operation::DelayNs`].
58    #[inline]
59    pub fn new_no_delay(bus: &'a RefCell<BUS>, mut cs: CS) -> Result<Self, CS::Error>
60    where
61        CS: OutputPin,
62    {
63        cs.set_high()?;
64        Ok(Self {
65            bus,
66            cs,
67            delay: super::NoDelay,
68        })
69    }
70}
71
72impl<'a, BUS, CS, D> ErrorType for RefCellDevice<'a, BUS, CS, D>
73where
74    BUS: ErrorType,
75    CS: OutputPin,
76{
77    type Error = DeviceError<BUS::Error, CS::Error>;
78}
79
80impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for RefCellDevice<'a, BUS, CS, D>
81where
82    BUS: SpiBus<Word>,
83    CS: OutputPin,
84    D: DelayNs,
85{
86    #[inline]
87    fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
88        let bus = &mut *self.bus.borrow_mut();
89
90        transaction(operations, bus, &mut self.delay, &mut self.cs)
91    }
92}