shared_bus/
lib.rs

1//! **shared-bus** is a crate to allow sharing bus peripherals safely between multiple devices.
2//!
3//! In the `embedded-hal` ecosystem, it is convention for drivers to "own" the bus peripheral they
4//! are operating on.  This implies that only _one_ driver can have access to a certain bus.  That,
5//! of course, poses an issue when multiple devices are connected to a single bus.
6//!
7//! _shared-bus_ solves this by giving each driver a bus-proxy to own which internally manages
8//! access to the actual bus in a safe manner.  For a more in-depth introduction of the problem
9//! this crate is trying to solve, take a look at the [blog post][blog-post].
10//!
11//! There are different 'bus managers' for different use-cases:
12//!
13//! # Sharing within a single task/thread
14//! As long as all users of a bus are contained in a single task/thread, bus sharing is very
15//! simple.  With no concurrency possible, no special synchronization is needed.  This is where
16//! a [`BusManagerSimple`] should be used:
17//!
18//! ```
19//! # use embedded_hal::blocking::i2c;
20//! # use embedded_hal::blocking::i2c::Write as _;
21//! # struct MyDevice<T>(T);
22//! # impl<T: i2c::Write> MyDevice<T> {
23//! #     pub fn new(t: T) -> Self { MyDevice(t) }
24//! #     pub fn do_something_on_the_bus(&mut self) {
25//! #         self.0.write(0xab, &[0x00]);
26//! #     }
27//! # }
28//! #
29//! # fn _example(i2c: impl i2c::Write) {
30//! // For example:
31//! // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1);
32//!
33//! let bus = shared_bus::BusManagerSimple::new(i2c);
34//!
35//! let mut proxy1 = bus.acquire_i2c();
36//! let mut my_device = MyDevice::new(bus.acquire_i2c());
37//!
38//! proxy1.write(0x39, &[0xc0, 0xff, 0xee]);
39//! my_device.do_something_on_the_bus();
40//! # }
41//! ```
42//!
43//! The `BusManager::acquire_*()` methods can be called as often as needed; each call will yield
44//! a new bus-proxy of the requested type.
45//!
46//! # Sharing across multiple tasks/threads
47//! For sharing across multiple tasks/threads, synchronization is needed to ensure all bus-accesses
48//! are strictly serialized and can't race against each other.  The synchronization is handled by
49//! a platform-specific [`BusMutex`] implementation.  _shared-bus_ already contains some
50//! implementations for common targets.  For each one, there is also a macro for easily creating
51//! a bus-manager with `'static` lifetime, which is almost always a requirement when sharing across
52//! task/thread boundaries.  As an example:
53//!
54//! ```
55//! # struct MyDevice<T>(T);
56//! # impl<T> MyDevice<T> {
57//! #     pub fn new(t: T) -> Self { MyDevice(t) }
58//! #     pub fn do_something_on_the_bus(&mut self) { }
59//! # }
60//! #
61//! # struct SomeI2cBus;
62//! # let i2c = SomeI2cBus;
63//! // For example:
64//! // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1);
65//!
66//! // The bus is a 'static reference -> it lives forever and references can be
67//! // shared with other threads.
68//! let bus: &'static _ = shared_bus::new_std!(SomeI2cBus = i2c).unwrap();
69//!
70//! let mut proxy1 = bus.acquire_i2c();
71//! let mut my_device = MyDevice::new(bus.acquire_i2c());
72//!
73//! // We can easily move a proxy to another thread:
74//! # let t =
75//! std::thread::spawn(move || {
76//!     my_device.do_something_on_the_bus();
77//! });
78//! # t.join().unwrap();
79//! ```
80//!
81//! Those platform-specific bits are guarded by a feature that needs to be enabled.  Here is an
82//! overview of what's already available:
83//!
84//! | Mutex | Bus Manager | `'static` Bus Macro | Feature Name |
85//! | --- | --- | --- | --- |
86//! | `std::sync::Mutex` | [`BusManagerStd`] | [`new_std!()`] | `std` |
87//! | `cortex_m::interrupt::Mutex` | [`BusManagerCortexM`] | [`new_cortexm!()`] | `cortex-m` |
88//! | `shared_bus::XtensaMutex` (`spin::Mutex` in critical section) | [`BusManagerXtensa`] | [`new_xtensa!()`] | `xtensa` |
89//! | None (Automatically Managed) | [`BusManagerAtomicCheck`] | [`new_atomic_check!()`] | `cortex-m` |
90//!
91//! # Supported buses and hardware blocks
92//! Currently, the following buses/blocks can be shared with _shared-bus_:
93//!
94//! | Bus/Block | Proxy Type | Acquire Method | Comments |
95//! | --- | --- | --- | --- |
96//! | I2C | [`I2cProxy`] | [`.acquire_i2c()`] | |
97//! | SPI | [`SpiProxy`] | [`.acquire_spi()`] | SPI can only be shared within a single task (See [`SpiProxy`] for details). |
98//! | ADC | [`AdcProxy`] | [`.acquire_adc()`] | |
99//!
100//!
101//! [`.acquire_i2c()`]: ./struct.BusManager.html#method.acquire_i2c
102//! [`.acquire_spi()`]: ./struct.BusManager.html#method.acquire_spi
103//! [`.acquire_adc()`]: ./struct.BusManager.html#method.acquire_adc
104//! [`BusManagerCortexM`]: ./type.BusManagerCortexM.html
105//! [`BusManagerXtensa`]: ./type.BusManagerXtensa.html
106//! [`BusManagerAtomicCheck`]: ./type.BusManagerAtomicCheck.html
107//! [`BusManagerSimple`]: ./type.BusManagerSimple.html
108//! [`BusManagerStd`]: ./type.BusManagerStd.html
109//! [`BusMutex`]: ./trait.BusMutex.html
110//! [`I2cProxy`]: ./struct.I2cProxy.html
111//! [`SpiProxy`]: ./struct.SpiProxy.html
112//! [`AdcProxy`]: ./struct.AdcProxy.html
113//! [`new_cortexm!()`]: ./macro.new_cortexm.html
114//! [`new_xtensa!()`]: ./macro.new_xtensa.html
115//! [`new_std!()`]: ./macro.new_std.html
116//! [`new_atomic_check!()`]: ./macro.new_atomic_check.html
117//! [blog-post]: https://blog.rahix.de/001-shared-bus
118#![doc(html_root_url = "https://docs.rs/shared-bus")]
119#![cfg_attr(not(feature = "std"), no_std)]
120#![warn(missing_docs)]
121
122mod macros;
123mod manager;
124mod mutex;
125mod proxies;
126
127#[doc(hidden)]
128#[cfg(feature = "std")]
129pub use once_cell;
130
131#[doc(hidden)]
132#[cfg(feature = "cortex-m")]
133pub use cortex_m;
134
135#[doc(hidden)]
136#[cfg(feature = "xtensa")]
137pub use xtensa_lx;
138
139pub use manager::BusManager;
140pub use mutex::BusMutex;
141#[cfg(feature = "cortex-m")]
142pub use mutex::CortexMMutex;
143pub use mutex::NullMutex;
144#[cfg(feature = "xtensa")]
145pub use mutex::XtensaMutex;
146pub use proxies::AdcProxy;
147pub use proxies::I2cProxy;
148pub use proxies::SpiProxy;
149
150#[cfg(feature = "cortex-m")]
151pub use mutex::AtomicCheckMutex;
152
153/// A bus manager for sharing within a single task/thread.
154///
155/// This is the bus manager with the least overhead; it should always be used when all bus users
156/// are confined to a single task/thread as it has no side-effects (like blocking or turning off
157/// interrupts).
158///
159/// # Example
160/// ```
161/// # use embedded_hal::blocking::i2c;
162/// # use embedded_hal::blocking::i2c::Write as _;
163/// # struct MyDevice<T>(T);
164/// # impl<T: i2c::Write> MyDevice<T> {
165/// #     pub fn new(t: T) -> Self { MyDevice(t) }
166/// #     pub fn do_something_on_the_bus(&mut self) {
167/// #         self.0.write(0xab, &[0x00]);
168/// #     }
169/// # }
170/// #
171/// # fn _example(i2c: impl i2c::Write) {
172/// // For example:
173/// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1);
174///
175/// let bus = shared_bus::BusManagerSimple::new(i2c);
176///
177/// let mut proxy1 = bus.acquire_i2c();
178/// let mut my_device = MyDevice::new(bus.acquire_i2c());
179///
180/// proxy1.write(0x39, &[0xc0, 0xff, 0xee]);
181/// my_device.do_something_on_the_bus();
182/// # }
183/// ```
184pub type BusManagerSimple<BUS> = BusManager<NullMutex<BUS>>;
185
186/// A bus manager for safely sharing between threads on a platform with `std` support.
187///
188/// This manager internally uses a `std::sync::Mutex` for synchronizing bus accesses.  As sharing
189/// across threads will in most cases require a manager with `'static` lifetime, the
190/// [`shared_bus::new_std!()`][new_std] macro exists to create such a bus manager.
191///
192/// [new_std]: ./macro.new_std.html
193///
194/// This type is only available with the `std` feature.
195#[cfg(feature = "std")]
196pub type BusManagerStd<BUS> = BusManager<::std::sync::Mutex<BUS>>;
197
198/// A bus manager for safely sharing between tasks on Cortex-M.
199///
200/// This manager works by turning off interrupts for each bus transaction which prevents racy
201/// accesses from different tasks/execution contexts (e.g. interrupts).  Usually, for sharing
202/// between tasks, a manager with `'static` lifetime is needed which can be created using the
203/// [`shared_bus::new_cortexm!()`][new_cortexm] macro.
204///
205/// [new_cortexm]: ./macro.new_cortexm.html
206///
207/// This type is only available with the `cortex-m` feature.
208#[cfg(feature = "cortex-m")]
209pub type BusManagerCortexM<BUS> = BusManager<CortexMMutex<BUS>>;
210
211/// A bus manager for safely sharing between tasks on Xtensa-lx6.
212///
213/// This manager works by turning off interrupts for each bus transaction which prevents racy
214/// accesses from different tasks/execution contexts (e.g. interrupts).  Usually, for sharing
215/// between tasks, a manager with `'static` lifetime is needed which can be created using the
216/// [`shared_bus::new_xtensa!()`][new_xtensa] macro.
217///
218/// [new_xtensa]: ./macro.new_xtensa.html
219///
220/// This type is only available with the `xtensa` feature.
221#[cfg(feature = "xtensa")]
222pub type BusManagerXtensa<BUS> = BusManager<XtensaMutex<BUS>>;
223
224/// A bus manager for safely sharing the bus when using concurrency frameworks (such as RTIC).
225///
226/// This manager relies on RTIC or some other concurrency framework to manage resource
227/// contention automatically. As a redundancy, this manager uses an atomic boolean to check
228/// whether or not a resource is currently in use. This is purely used as a fail-safe against
229/// misuse.
230///
231/// ## Warning
232/// If devices on the same shared bus are not treated as a singular resource, it is possible that
233/// pre-emption may occur. In this case, the manger will panic to prevent the race condition.
234///
235/// ## Usage
236/// In order to use this manager with a concurrency framework such as RTIC, all devices on the
237/// shared bus must be stored in the same logic resource. The concurrency framework will require a
238/// resource lock if pre-emption is possible.
239///
240/// In order to use this with RTIC (as an example), all devices on the shared bus must be stored in
241/// a singular resource.  Additionally, a manager with `'static` lifetime is needed which can be
242/// created using the [`shared_bus::new_atomic_check!()`][new_atomic_check] macro.  It should
243/// roughly look like this (there is also a [full example][shared-bus-rtic-example] available):
244///
245/// ```rust
246/// struct Device<T> { bus: T };
247/// struct OtherDevice<T> { bus: T };
248///
249/// // the HAL I2C driver type
250/// type I2cType = ();
251/// type Proxy = shared_bus::I2cProxy<'static, shared_bus::AtomicCheckMutex<I2cType>>;
252///
253/// struct SharedBusDevices {
254///     device: Device<Proxy>,
255///     other_device: OtherDevice<Proxy>,
256/// }
257///
258/// struct Resources {
259///     shared_bus_devices: SharedBusDevices,
260/// }
261///
262///
263/// // in the RTIC init function
264/// fn init() -> Resources {
265///     // init the bus like usual
266///     let i2c: I2cType = ();
267///
268///     let bus_manager: &'static _ = shared_bus::new_atomic_check!(I2cType = i2c).unwrap();
269///
270///     let devices = SharedBusDevices {
271///         device: Device { bus: bus_manager.acquire_i2c() },
272///         other_device: OtherDevice { bus: bus_manager.acquire_i2c() },
273///     };
274///
275///     Resources {
276///         shared_bus_devices: devices,
277///     }
278/// }
279/// ```
280///
281/// [new_atomic_check]: ./macro.new_atomic_check.html
282/// [shared-bus-rtic-example]: https://github.com/ryan-summers/shared-bus-example/blob/master/src/main.rs
283///
284/// This type is only available with the `cortex-m` feature (but this may change in the future!).
285#[cfg(feature = "cortex-m")]
286pub type BusManagerAtomicCheck<T> = BusManager<AtomicCheckMutex<T>>;