├── .github └── workflows │ ├── format.yml │ └── main.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md └── src └── lib.rs /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [ master ] 4 | pull_request: 5 | branches: [ master ] 6 | 7 | name: Formatting check 8 | 9 | jobs: 10 | fmt: 11 | name: Rustfmt 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | - run: rustup component add rustfmt 21 | - uses: actions-rs/cargo@v1 22 | with: 23 | command: fmt 24 | args: --all -- --check 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Arm Build 2 | 3 | on: 4 | push: 5 | branches: [ master] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | thumbv7: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Install Rust 16 | uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: stable 19 | target: thumbv7em-none-eabihf 20 | override: true 21 | - name: Check 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: check 25 | args: --verbose 26 | - name: Build Debug 27 | uses: actions-rs/cargo@v1 28 | with: 29 | command: build 30 | - name: Build Release 31 | uses: actions-rs/cargo@v1 32 | with: 33 | command: build 34 | args: --release 35 | 36 | thumbv6: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v2 40 | - name: Install Rust 41 | uses: actions-rs/toolchain@v1 42 | with: 43 | toolchain: stable 44 | target: thumbv6m-none-eabi 45 | override: true 46 | - name: Check 47 | uses: actions-rs/cargo@v1 48 | with: 49 | command: check 50 | args: --verbose 51 | - name: Build Debug 52 | uses: actions-rs/cargo@v1 53 | with: 54 | command: build 55 | args: --features thumbv6 56 | - name: Build Release 57 | uses: actions-rs/cargo@v1 58 | with: 59 | command: build 60 | args: --release --features thumbv6 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This file contains all changes to the shared-bus-rtic library. 4 | 5 | ## [0.2.3+deprecated] - 2023-07-03 6 | 7 | ### Added 8 | - This crate is now deprecated. Users should use 9 | [`embedded-hal-bus`](https://crates.io/crates/embedded-hal-bus) instead. 10 | 11 | ### Fixed 12 | - Removed a call to `expect` which pulled in unnecessary fmt bloat 13 | 14 | ## [0.2.2] - 2020-07-21 15 | 16 | ### Added 17 | - Initial release of library on crates.io 18 | - AtomicBool support for thumbv6 platforms 19 | - SPI full-duplex support 20 | - SPI data-types support 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shared-bus-rtic" 3 | version = "0.2.3+deprecated" 4 | authors = ["Ryan Summers "] 5 | edition = "2018" 6 | 7 | description = "Provides utilities for sharing peripheral communication buses in an RTIC application" 8 | homepage = "https://github.com/ryan-summers/shared-bus-rtic" 9 | repository = "https://github.com/ryan-summers/shared-bus-rtic" 10 | documentation = "https://docs.rs/shared-bus-rtic" 11 | 12 | categories = ["embedded", "concurrency", "no-std", "hardware-support"] 13 | keywords = ["shared-bus", "rtic", "i2c", "spi", "embedded-hal"] 14 | license = "MIT" 15 | readme = "README.md" 16 | 17 | [dependencies] 18 | embedded-hal = "0.2.4" 19 | nb = "1.0.0" 20 | cortex-m = { version = "0.6", optional = true } 21 | 22 | [features] 23 | thumbv6 = ["cortex-m"] 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ryan Summers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shared-bus-rtic 2 | Provides macros and type definitions for using a shared peripheral bus in an RTIC application 3 | 4 | ## :warning: Deprecation Notice :warning: 5 | 6 | With the release of `embedded-hal` v1.0, this crate is no longer necessary. Instead, users should 7 | migrate to using `embedded-hal` v1.0 traits alongside the [`embedded-hal-bus`](https://crates.io/crates/embedded-hal-bus). 8 | 9 | ## Description 10 | 11 | Note that all of the drivers that use the same underlying bus **must** be stored within a single 12 | resource (e.g. as one larger `struct`) within the RTIC resources. This ensures that RTIC will 13 | prevent one driver from interrupting another while they are using the same underlying bus. The crate 14 | provides a detection mechanism that will panic if the shared bus is used in multiple task priorities 15 | without proper locking. 16 | 17 | ## Features 18 | 19 | This crate is compatible with thumbv6 architectures. To enable support for thumbv6 20 | devices, enable the `thumbv6` feature in your `Cargo.toml`: 21 | ``` 22 | [dependencies.shared-bus-rtic] 23 | features = ["thumbv6"] 24 | ``` 25 | 26 | ## Usage Example 27 | 28 | ```rust 29 | use shared_bus_rtic::SharedBus; 30 | 31 | struct SharedBusResources { 32 | device: Device>, 33 | other_device: OtherDevice>, 34 | } 35 | 36 | // ... 37 | 38 | // Replace this type with the type of your bus (e.g. hal::i2c::I2c<...>). 39 | type BusType = (); 40 | 41 | struct Resources { 42 | shared_bus_resources: SharedBusResources, 43 | } 44 | 45 | #[init] 46 | fn init(c: init::Context) -> init::LateResources { 47 | let manager = shared_bus_rtic::new!(bus, BusType); 48 | let device = Device::new(manager.acquire()); 49 | let other_device = OtherDevice::new(manager.acquire()); 50 | 51 | init::LateResources { 52 | shared_bus_resources: SharedBusResources { device, other_device }, 53 | } 54 | } 55 | ``` 56 | 57 | ### Valid Example 58 | 59 | ```rust 60 | struct SharedBusResources { 61 | device_on_shared_bus: Device, 62 | other_device_on_shared_bus: OtherDevice, 63 | } 64 | 65 | // ... 66 | 67 | struct Resources { 68 | shared_bus_resources: SharedBusResources, 69 | } 70 | 71 | #[task(resources=[shared_bus_resources], priority=5) 72 | pub fn high_priority_task(c: high_priority_task::Context) { 73 | // Good - This task cannot interrupt the lower priority task that is using the bus because of a 74 | // resource lock. 75 | c.resources.shared_bus_resources.device_on_shared_bus.read(); 76 | } 77 | 78 | #[task(resources=[shared_bus_resources], priority=0) 79 | pub fn low_priority_task(c: low_priority_task::Context) { 80 | // Good - RTIC properly locks the entire shared bus from concurrent access. 81 | c.resources.shared_bus_resources.lock(|bus| bus.other_device_on_shared_bus.read()); 82 | } 83 | ``` 84 | 85 | In the above example, it can be seen that both devices on the bus are stored as a single resource 86 | (in a shared `struct`). Because of this, RTIC properly locks the resource when either the high or 87 | low priority task is using the bus. 88 | 89 | ### Unsound Example 90 | 91 | The following example is unsound and should not be repeated. When a resource is interrupted, the 92 | crate will panic. 93 | 94 | ```rust 95 | struct Resources { 96 | // INVALID - DO NOT DO THIS. 97 | device_on_shared_bus: Device, 98 | other_device_on_shared_bus: OtherDevice, 99 | } 100 | 101 | #[task(resources=[device_on_shared_bus], priority=5) 102 | pub fn high_priority_task(c: high_priority_task::Context) { 103 | // ERROR: This task might interrupt the read on the other device! 104 | // If it does interrupt the low priority task, the shared bus manager will panic. 105 | c.resources.device_on_shared_bus.read(); 106 | } 107 | 108 | #[task(resources=[other_device_on_shared_bus], priority=0) 109 | pub fn low_priority_task(c: low_priority_task::Context) { 110 | // Attempt to read data from the device. 111 | c.resources.other_device_on_shared_bus.read(); 112 | } 113 | ``` 114 | 115 | In the above incorrect example, RTIC may interrupt the low priority task to complete the high 116 | priority task. However, the low priority task may be using the shared bus. In this case, the 117 | communication may be corrupted by multiple devices using the bus at the same time. To detect this, 118 | `shared-bus-rtic` will detect any bus contention and panic if the bus is already in use. 119 | -------------------------------------------------------------------------------- /src/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 { 18 | //! device: Device>, 19 | //! other_device: OtherDevice>, 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, 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 | 49 | use core::sync::atomic::{AtomicBool, Ordering}; 50 | use embedded_hal::{ 51 | blocking::{self, i2c}, 52 | spi, 53 | }; 54 | 55 | /// A convenience type to use for declaring the underlying bus type. 56 | pub type SharedBus = &'static CommonBus; 57 | 58 | pub struct CommonBus { 59 | bus: core::cell::UnsafeCell, 60 | busy: AtomicBool, 61 | } 62 | 63 | impl CommonBus { 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>(&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 | 91 | unsafe impl Sync for CommonBus {} 92 | 93 | impl i2c::Read for &CommonBus { 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 | 101 | impl i2c::Write for &CommonBus { 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 | 109 | impl i2c::WriteRead for &CommonBus { 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 | 122 | macro_rules! spi { 123 | ($($T:ty),*) => { 124 | $( 125 | impl> blocking::spi::Write<$T> for &CommonBus { 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> blocking::spi::Transfer<$T> for &CommonBus { 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> spi::FullDuplex<$T> for &CommonBus { 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 | 156 | spi!(u8, u16, u32, u64); 157 | 158 | #[cfg(feature = "thumbv6")] 159 | mod 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 { 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"))] 183 | mod 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 { 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] 212 | macro_rules! new { 213 | ($bus:ident, $T:ty) => { 214 | unsafe { 215 | static mut _MANAGER: core::mem::MaybeUninit> = 216 | core::mem::MaybeUninit::uninit(); 217 | _MANAGER = core::mem::MaybeUninit::new(shared_bus_rtic::CommonBus::new($bus)); 218 | &*_MANAGER.as_ptr() 219 | }; 220 | }; 221 | } 222 | --------------------------------------------------------------------------------