shared_bus_rtic/
lib.rs

1#![no_std]
2//! # Introduction
3//! This crate provides a means of sharing an I2C or SPI bus between multiple drivers.
4//!
5//! ## Notice
6//! Note that all of the drivers that use the same underlying bus **must** be stored within a single
7//! resource (e.g. as one larger `struct`) within the RTIC resources. This ensures that RTIC will
8//! prevent one driver from interrupting another while they are using the same underlying bus.
9//!
10//! This crate also provides convenience types for working with `shared-bus` RTIC resources.
11//!
12//! ## Usage Example
13//! ```rust
14//!
15//! use shared_bus_rtic::SharedBus;
16//!
17//! struct SharedBusResources<T> {
18//!     device: Device<SharedBus<T>>,
19//!     other_device: OtherDevice<SharedBus<T>>,
20//! }
21//!
22//! // ...
23//!
24//! // Replace this type with the type of your bus (e.g. hal::i2c::I2c<...>).
25//! type BusType = ();
26//!
27//! struct Resources {
28//!     shared_bus_resources: SharedBusResources<BusType>,
29//! }
30//!
31//! #[init]
32//! fn init(c: init::Context) -> init::LateResources {
33//!     // TODO: Define your custom bus here.
34//!     let bus: BusType = ();
35//!
36//!     // Construct the bus manager.
37//!     let manager = shared_bus_rtic::new!(bus, BusType);
38//!
39//!     // Construct all of your devices that use the shared bus.
40//!     let device = Device::new(manager.acquire());
41//!     let other_device = OtherDevice::new(manager.acquire());
42//!
43//!     init::LateResources {
44//!         shared_bus_resources: SharedBusResources { device, other_device },
45//!     }
46//! }
47//! ```
48
49use core::sync::atomic::{AtomicBool, Ordering};
50use embedded_hal::{
51    blocking::{self, i2c},
52    spi,
53};
54
55/// A convenience type to use for declaring the underlying bus type.
56pub type SharedBus<T> = &'static CommonBus<T>;
57
58pub struct CommonBus<BUS> {
59    bus: core::cell::UnsafeCell<BUS>,
60    busy: AtomicBool,
61}
62
63impl<BUS> CommonBus<BUS> {
64    pub fn new(bus: BUS) -> Self {
65        CommonBus {
66            bus: core::cell::UnsafeCell::new(bus),
67            busy: AtomicBool::from(false),
68        }
69    }
70
71    fn lock<R, F: FnOnce(&mut BUS) -> R>(&self, f: F) -> R {
72        let compare =
73            atomic::compare_exchange(&self.busy, false, true, Ordering::SeqCst, Ordering::SeqCst)
74                .is_err();
75        if compare {
76            panic!("Bus conflict");
77        }
78
79        let result = f(unsafe { &mut *self.bus.get() });
80
81        self.busy.store(false, Ordering::SeqCst);
82
83        result
84    }
85
86    pub fn acquire(&self) -> &Self {
87        self
88    }
89}
90
91unsafe impl<BUS> Sync for CommonBus<BUS> {}
92
93impl<BUS: i2c::Read> i2c::Read for &CommonBus<BUS> {
94    type Error = BUS::Error;
95
96    fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
97        self.lock(|bus| bus.read(address, buffer))
98    }
99}
100
101impl<BUS: i2c::Write> i2c::Write for &CommonBus<BUS> {
102    type Error = BUS::Error;
103
104    fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Self::Error> {
105        self.lock(|bus| bus.write(address, buffer))
106    }
107}
108
109impl<BUS: i2c::WriteRead> i2c::WriteRead for &CommonBus<BUS> {
110    type Error = BUS::Error;
111
112    fn write_read(
113        &mut self,
114        address: u8,
115        bytes: &[u8],
116        buffer: &mut [u8],
117    ) -> Result<(), Self::Error> {
118        self.lock(|bus| bus.write_read(address, bytes, buffer))
119    }
120}
121
122macro_rules! spi {
123    ($($T:ty),*) => {
124        $(
125        impl<BUS: blocking::spi::Write<$T>> blocking::spi::Write<$T> for &CommonBus<BUS> {
126            type Error = BUS::Error;
127
128            fn write(&mut self, words: &[$T]) -> Result<(), Self::Error> {
129                self.lock(|bus| bus.write(words))
130            }
131        }
132
133        impl<BUS: blocking::spi::Transfer<$T>> blocking::spi::Transfer<$T> for &CommonBus<BUS> {
134            type Error = BUS::Error;
135
136            fn transfer<'w>(&mut self, words: &'w mut [$T]) -> Result<&'w [$T], Self::Error> {
137                self.lock(move |bus| bus.transfer(words))
138            }
139        }
140
141        impl<BUS: spi::FullDuplex<$T>> spi::FullDuplex<$T> for &CommonBus<BUS> {
142            type Error = BUS::Error;
143
144            fn read(&mut self) -> nb::Result<$T, Self::Error> {
145                self.lock(|bus| bus.read())
146            }
147
148            fn send(&mut self, word: $T) -> nb::Result<(), Self::Error> {
149                self.lock(|bus| bus.send(word))
150            }
151        }
152        )*
153    }
154}
155
156spi!(u8, u16, u32, u64);
157
158#[cfg(feature = "thumbv6")]
159mod atomic {
160    use core::sync::atomic::{AtomicBool, Ordering};
161
162    #[inline(always)]
163    pub fn compare_exchange(
164        atomic: &AtomicBool,
165        current: bool,
166        new: bool,
167        _success: Ordering,
168        _failure: Ordering,
169    ) -> Result<bool, bool> {
170        cortex_m::interrupt::free(|_cs| {
171            let prev = atomic.load(Ordering::Acquire);
172            if prev == current {
173                atomic.store(new, Ordering::Release);
174                Ok(prev)
175            } else {
176                Err(false)
177            }
178        })
179    }
180}
181
182#[cfg(not(feature = "thumbv6"))]
183mod atomic {
184    use core::sync::atomic::{AtomicBool, Ordering};
185
186    #[inline(always)]
187    pub fn compare_exchange(
188        atomic: &AtomicBool,
189        current: bool,
190        new: bool,
191        success: Ordering,
192        failure: Ordering,
193    ) -> Result<bool, bool> {
194        atomic.compare_exchange(current, new, success, failure)
195    }
196}
197
198/// Provides a method of generating a shared bus.
199///
200/// ## Args:
201/// * `bus` - The actual bus that should be shared
202/// * `T` - The full type of the bus that is being shared.
203///
204/// ## Example:
205/// ```rust
206/// let bus: I2C = ();
207/// let manager = shared_bus_rtic::new!(bus, I2C);
208///
209/// let device = Device::new(manager.acquire());
210/// ```
211#[macro_export]
212macro_rules! new {
213    ($bus:ident, $T:ty) => {
214        unsafe {
215            static mut _MANAGER: core::mem::MaybeUninit<shared_bus_rtic::CommonBus<$T>> =
216                core::mem::MaybeUninit::uninit();
217            _MANAGER = core::mem::MaybeUninit::new(shared_bus_rtic::CommonBus::new($bus));
218            &*_MANAGER.as_ptr()
219        };
220    };
221}