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
//! **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` | //! //! # Supported Busses //! Currently, the following busses can be shared with _shared-bus_: //! //! | Bus | 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). | //! //! //! [`.acquire_i2c()`]: ./struct.BusManager.html#method.acquire_i2c //! [`.acquire_spi()`]: ./struct.BusManager.html#method.acquire_spi //! [`BusManagerCortexM`]: ./type.BusManagerCortexM.html //! [`BusManagerSimple`]: ./type.BusManagerSimple.html //! [`BusManagerStd`]: ./type.BusManagerStd.html //! [`BusMutex`]: ./trait.BusMutex.html //! [`I2cProxy`]: ./struct.I2cProxy.html //! [`SpiProxy`]: ./struct.SpiProxy.html //! [`new_cortexm!()`]: ./macro.new_cortexm.html //! [`new_std!()`]: ./macro.new_std.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 manager; mod mutex; mod proxies; mod macros; #[doc(hidden)] #[cfg(feature = "std")] pub use once_cell; #[doc(hidden)] #[cfg(feature = "cortex-m")] pub use cortex_m; pub use manager::BusManager; pub use mutex::BusMutex; pub use mutex::NullMutex; #[cfg(feature = "cortex-m")] pub use mutex::CortexMMutex; pub use proxies::I2cProxy; pub use proxies::SpiProxy; /// 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>>;