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