├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── LICENSE-MIT ├── Cargo.toml ├── tests ├── spi.rs ├── adc.rs └── i2c.rs ├── CHANGELOG.md ├── README.md ├── src ├── macros.rs ├── mutex.rs ├── manager.rs ├── proxies.rs └── lib.rs └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "cargo" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shared-bus" 3 | version = "0.3.1" 4 | authors = ["Rahix "] 5 | edition = "2018" 6 | 7 | description = "Abstraction for sharing a bus between multiple devices." 8 | repository = "https://github.com/Rahix/shared-bus" 9 | documentation = "https://docs.rs/shared-bus" 10 | readme = "README.md" 11 | keywords = ["embedded-hal", "embedded-hal-impl", "i2c", "spi", "bus"] 12 | categories = ["embedded", "no-std"] 13 | license = "MIT OR Apache-2.0" 14 | 15 | [package.metadata.docs.rs] 16 | all-features = true 17 | 18 | [dependencies] 19 | embedded-hal = { version = "0.2.3", features = ["unproven"] } 20 | nb = "1.0.0" 21 | once_cell = { version = "1.4.0", optional = true } 22 | cortex-m = { version = "0.7.7", optional = true } 23 | xtensa-lx = { version = "0.8.0", optional = true, features = ["spin"] } 24 | spin = { version = "0.9.8", optional = true } 25 | atomic-polyfill = { version = "1.0.1", optional = true } 26 | 27 | embedded-hal-alpha = { package = "embedded-hal", version = "=1.0.0-alpha.9", optional = true } 28 | 29 | [dev-dependencies] 30 | embedded-hal-mock = "0.9" 31 | 32 | [features] 33 | std = ["once_cell"] 34 | xtensa = ["xtensa-lx", "spin"] 35 | cortex-m = ["dep:cortex-m", "atomic-polyfill"] 36 | eh-alpha = ["embedded-hal-alpha"] 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - wip 8 | pull_request: 9 | 10 | jobs: 11 | stable-ci: 12 | name: "Build & Test (on Stable Rust)" 13 | runs-on: ubuntu-latest 14 | env: 15 | STABLE_FEATURES: "cortex-m,std" 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | - name: Install Rust 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | - name: Build shared-bus with all features enabled 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: build 29 | args: "--features ${{ env.STABLE_FEATURES }}" 30 | - name: Test shared-bus with all features enabled 31 | uses: actions-rs/cargo@v1 32 | with: 33 | command: test 34 | args: "--features ${{ env.STABLE_FEATURES }}" 35 | nightly-ci: 36 | name: "Build & Test (on Nightly Rust)" 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout code 40 | uses: actions/checkout@v4 41 | - name: Install Nightly Rust 42 | uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: nightly 46 | override: true 47 | - name: Build shared-bus with all features enabled 48 | uses: actions-rs/cargo@v1 49 | with: 50 | command: build 51 | args: --all-features 52 | - name: Test shared-bus with all features enabled 53 | uses: actions-rs/cargo@v1 54 | with: 55 | command: test 56 | args: --all-features 57 | -------------------------------------------------------------------------------- /tests/spi.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::prelude::*; 2 | use embedded_hal_mock::spi; 3 | 4 | #[test] 5 | fn fake_spi_device() { 6 | let expect = vec![spi::Transaction::write(vec![0xff, 0xee])]; 7 | let mut device = spi::Mock::new(&expect); 8 | device.write(&[0xff, 0xee]).unwrap(); 9 | device.done() 10 | } 11 | 12 | #[test] 13 | fn spi_manager_manual() { 14 | let expect = vec![spi::Transaction::write(vec![0xab, 0xcd, 0xef])]; 15 | let mut device = spi::Mock::new(&expect); 16 | let manager = shared_bus::BusManagerSimple::new(device.clone()); 17 | let mut proxy = manager.acquire_spi(); 18 | 19 | proxy.write(&[0xab, 0xcd, 0xef]).unwrap(); 20 | 21 | device.done(); 22 | } 23 | 24 | #[test] 25 | fn spi_proxy() { 26 | let expect = vec![ 27 | spi::Transaction::write(vec![0xab, 0xcd, 0xef]), 28 | spi::Transaction::transfer(vec![0x01, 0x02], vec![0x03, 0x04]), 29 | ]; 30 | let mut device = spi::Mock::new(&expect); 31 | let manager = shared_bus::BusManagerSimple::new(device.clone()); 32 | let mut proxy = manager.acquire_spi(); 33 | 34 | proxy.write(&[0xab, 0xcd, 0xef]).unwrap(); 35 | 36 | let mut buf = vec![0x01, 0x02]; 37 | proxy.transfer(&mut buf).unwrap(); 38 | assert_eq!(&buf, &[0x03, 0x04]); 39 | 40 | device.done(); 41 | } 42 | 43 | #[test] 44 | fn spi_multi() { 45 | let expect = vec![ 46 | spi::Transaction::write(vec![0xab, 0xcd, 0xef]), 47 | spi::Transaction::transfer(vec![0x01, 0x02], vec![0x03, 0x04]), 48 | ]; 49 | let mut device = spi::Mock::new(&expect); 50 | let manager = shared_bus::BusManagerSimple::new(device.clone()); 51 | let mut proxy1 = manager.acquire_spi(); 52 | let mut proxy2 = manager.acquire_spi(); 53 | 54 | proxy1.write(&[0xab, 0xcd, 0xef]).unwrap(); 55 | 56 | let mut buf = vec![0x01, 0x02]; 57 | proxy2.transfer(&mut buf).unwrap(); 58 | assert_eq!(&buf, &[0x03, 0x04]); 59 | 60 | device.done(); 61 | } 62 | -------------------------------------------------------------------------------- /tests/adc.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::prelude::*; 2 | use embedded_hal_mock::adc; 3 | use std::thread; 4 | 5 | #[test] 6 | fn adc_mock_device() { 7 | let expectations = [ 8 | adc::Transaction::read(0, 0xabcd), 9 | adc::Transaction::read(1, 0xabba), 10 | adc::Transaction::read(2, 0xbaab), 11 | ]; 12 | 13 | let mut device = adc::Mock::new(&expectations); 14 | assert_eq!(0xabcd, device.read(&mut adc::MockChan0).unwrap()); 15 | assert_eq!(0xabba, device.read(&mut adc::MockChan1).unwrap()); 16 | assert_eq!(0xbaab, device.read(&mut adc::MockChan2).unwrap()); 17 | device.done() 18 | } 19 | 20 | #[test] 21 | fn adc_manager_simple() { 22 | let expectations = [ 23 | adc::Transaction::read(0, 0xabcd), 24 | adc::Transaction::read(1, 0xabba), 25 | adc::Transaction::read(2, 0xbaab), 26 | ]; 27 | 28 | let mut device = adc::Mock::new(&expectations); 29 | let manager = shared_bus::BusManagerSimple::new(device.clone()); 30 | let mut proxy = manager.acquire_adc(); 31 | 32 | assert_eq!(0xabcd, proxy.read(&mut adc::MockChan0).unwrap()); 33 | assert_eq!(0xabba, proxy.read(&mut adc::MockChan1).unwrap()); 34 | assert_eq!(0xbaab, proxy.read(&mut adc::MockChan2).unwrap()); 35 | device.done() 36 | } 37 | 38 | #[test] 39 | fn adc_manager_std() { 40 | let expectations = [ 41 | adc::Transaction::read(0, 0xabcd), 42 | adc::Transaction::read(1, 0xabba), 43 | adc::Transaction::read(2, 0xbaab), 44 | ]; 45 | 46 | let mut device = adc::Mock::new(&expectations); 47 | let manager: &'static shared_bus::BusManagerStd<_> = 48 | shared_bus::new_std!(adc::Mock = device.clone()).unwrap(); 49 | let mut proxy = manager.acquire_adc(); 50 | 51 | assert_eq!(0xabcd, proxy.read(&mut adc::MockChan0).unwrap()); 52 | assert_eq!(0xabba, proxy.read(&mut adc::MockChan1).unwrap()); 53 | assert_eq!(0xbaab, proxy.read(&mut adc::MockChan2).unwrap()); 54 | device.done() 55 | } 56 | 57 | #[test] 58 | fn adc_proxy_multi() { 59 | let expectations = [ 60 | adc::Transaction::read(0, 0xabcd), 61 | adc::Transaction::read(1, 0xabba), 62 | adc::Transaction::read(2, 0xbaab), 63 | ]; 64 | 65 | let mut device = adc::Mock::new(&expectations); 66 | let manager = shared_bus::BusManagerSimple::new(device.clone()); 67 | let mut proxy1 = manager.acquire_adc(); 68 | let mut proxy2 = manager.acquire_adc(); 69 | let mut proxy3 = manager.acquire_adc(); 70 | 71 | assert_eq!(0xabcd, proxy1.read(&mut adc::MockChan0).unwrap()); 72 | assert_eq!(0xabba, proxy2.read(&mut adc::MockChan1).unwrap()); 73 | assert_eq!(0xbaab, proxy3.read(&mut adc::MockChan2).unwrap()); 74 | device.done() 75 | } 76 | 77 | #[test] 78 | fn adc_proxy_concurrent() { 79 | let expectations = [ 80 | adc::Transaction::read(0, 0xabcd), 81 | adc::Transaction::read(1, 0xabba), 82 | adc::Transaction::read(2, 0xbaab), 83 | ]; 84 | 85 | let mut device = adc::Mock::new(&expectations); 86 | let manager: &'static shared_bus::BusManagerStd<_> = 87 | shared_bus::new_std!(adc::Mock = device.clone()).unwrap(); 88 | let mut proxy1 = manager.acquire_adc(); 89 | let mut proxy2 = manager.acquire_adc(); 90 | let mut proxy3 = manager.acquire_adc(); 91 | 92 | thread::spawn(move || { 93 | assert_eq!(0xabcd, proxy1.read(&mut adc::MockChan0).unwrap()); 94 | }) 95 | .join() 96 | .unwrap(); 97 | 98 | thread::spawn(move || { 99 | assert_eq!(0xabba, proxy2.read(&mut adc::MockChan1).unwrap()); 100 | }) 101 | .join() 102 | .unwrap(); 103 | 104 | assert_eq!(0xbaab, proxy3.read(&mut adc::MockChan2).unwrap()); 105 | 106 | device.done() 107 | } 108 | -------------------------------------------------------------------------------- /tests/i2c.rs: -------------------------------------------------------------------------------- 1 | use embedded_hal::prelude::*; 2 | use embedded_hal_mock::i2c; 3 | use std::thread; 4 | 5 | #[test] 6 | fn fake_i2c_device() { 7 | let expect = vec![i2c::Transaction::write(0xc0, vec![0xff, 0xee])]; 8 | let mut device = i2c::Mock::new(&expect); 9 | device.write(0xc0, &[0xff, 0xee]).unwrap(); 10 | device.done() 11 | } 12 | 13 | #[test] 14 | fn i2c_manager_manual() { 15 | let expect = vec![i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef])]; 16 | let mut device = i2c::Mock::new(&expect); 17 | let manager = shared_bus::BusManagerSimple::new(device.clone()); 18 | let mut proxy = manager.acquire_i2c(); 19 | 20 | proxy.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); 21 | 22 | device.done(); 23 | } 24 | 25 | #[test] 26 | fn i2c_manager_macro() { 27 | let expect = vec![i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef])]; 28 | let mut device = i2c::Mock::new(&expect); 29 | let manager: &'static shared_bus::BusManagerStd<_> = 30 | shared_bus::new_std!(i2c::Mock = device.clone()).unwrap(); 31 | let mut proxy = manager.acquire_i2c(); 32 | 33 | proxy.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); 34 | 35 | device.done(); 36 | } 37 | 38 | #[test] 39 | fn i2c_proxy() { 40 | let expect = vec![ 41 | i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef]), 42 | i2c::Transaction::read(0xef, vec![0xbe, 0xad, 0xde]), 43 | i2c::Transaction::write_read(0x44, vec![0x01, 0x02], vec![0x03, 0x04]), 44 | ]; 45 | let mut device = i2c::Mock::new(&expect); 46 | 47 | let manager = shared_bus::BusManagerSimple::new(device.clone()); 48 | let mut proxy = manager.acquire_i2c(); 49 | 50 | proxy.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); 51 | 52 | let mut buf = [0u8; 3]; 53 | proxy.read(0xef, &mut buf).unwrap(); 54 | assert_eq!(&buf, &[0xbe, 0xad, 0xde]); 55 | 56 | let mut buf = [0u8; 2]; 57 | proxy.write_read(0x44, &[0x01, 0x02], &mut buf).unwrap(); 58 | assert_eq!(&buf, &[0x03, 0x04]); 59 | 60 | device.done(); 61 | } 62 | 63 | #[test] 64 | fn i2c_multi() { 65 | let expect = vec![ 66 | i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef]), 67 | i2c::Transaction::read(0xef, vec![0xbe, 0xad, 0xde]), 68 | i2c::Transaction::write_read(0x44, vec![0x01, 0x02], vec![0x03, 0x04]), 69 | ]; 70 | let mut device = i2c::Mock::new(&expect); 71 | 72 | let manager = shared_bus::BusManagerSimple::new(device.clone()); 73 | let mut proxy1 = manager.acquire_i2c(); 74 | let mut proxy2 = manager.acquire_i2c(); 75 | let mut proxy3 = manager.acquire_i2c(); 76 | 77 | proxy1.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); 78 | 79 | let mut buf = [0u8; 3]; 80 | proxy2.read(0xef, &mut buf).unwrap(); 81 | assert_eq!(&buf, &[0xbe, 0xad, 0xde]); 82 | 83 | let mut buf = [0u8; 2]; 84 | proxy3.write_read(0x44, &[0x01, 0x02], &mut buf).unwrap(); 85 | assert_eq!(&buf, &[0x03, 0x04]); 86 | 87 | device.done(); 88 | } 89 | 90 | #[test] 91 | fn i2c_concurrent() { 92 | let expect = vec![ 93 | i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef]), 94 | i2c::Transaction::read(0xef, vec![0xbe, 0xad, 0xde]), 95 | ]; 96 | let mut device = i2c::Mock::new(&expect); 97 | 98 | let manager = shared_bus::new_std!(i2c::Mock = device.clone()).unwrap(); 99 | let mut proxy1 = manager.acquire_i2c(); 100 | let mut proxy2 = manager.acquire_i2c(); 101 | 102 | thread::spawn(move || { 103 | proxy1.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); 104 | }) 105 | .join() 106 | .unwrap(); 107 | 108 | thread::spawn(move || { 109 | let mut buf = [0u8; 3]; 110 | proxy2.read(0xef, &mut buf).unwrap(); 111 | assert_eq!(&buf, &[0xbe, 0xad, 0xde]); 112 | }) 113 | .join() 114 | .unwrap(); 115 | 116 | device.done(); 117 | } 118 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | 10 | ## [0.3.1] - 2023-10-31 11 | ### Added 12 | - Implementations of the `WriteIter` and `WriteIterRead` traits from `embedded-hal` ([#53]). 13 | 14 | [#53]: https://github.com/Rahix/shared-bus/pull/53 15 | 16 | 17 | ## [0.3.0] - 2023-08-13 18 | ### Changed 19 | - **BREAKING** Upgraded to `cortex-m` 0.7. 20 | - **BREAKING** Upgraded to `atomic-polyfill` 1.0. 21 | - **BREAKING** Upgraded to `xtensa-lx` 0.8 ([#49]). 22 | 23 | [#49]: https://github.com/Rahix/shared-bus/pull/49 24 | 25 | 26 | ## [0.2.5] - 2022-11-23 27 | ### Changed 28 | - Upgraded to embedded-hal alpha support to version 9. Please pin your 29 | dependency on `shared-bus` to an exact version (using `=0.#.#`) when you make 30 | use of the `eh-alpha` feature flag ([#38]). 31 | 32 | ### Fixed 33 | - Fixed the `new_xtensa!()` macro not compiling ([#40]). 34 | 35 | [#38]: https://github.com/Rahix/shared-bus/pull/38 36 | [#40]: https://github.com/Rahix/shared-bus/pull/40 37 | 38 | 39 | ## [0.2.4] - 2022-06-16 40 | ### Added 41 | - Added support for the I2C traits from the `embedded-hal` alpha 8 version 42 | (`eh-alpha` feature flag) ([#36]). 43 | 44 | ### Fixed 45 | - Fixed the `atomic-polyfill` dependency being pulled in even in cases where it 46 | is not used. 47 | - Fixed a problem with the ADC sharing `embedded-hal` dependency. 48 | 49 | [#36]: https://github.com/Rahix/shared-bus/pull/36 50 | 51 | 52 | ## [0.2.3] - 2022-03-05 53 | ### Added 54 | - A mutex type for Xtensa LX6 micorontrollers (e.g. ESP32) ([#22]). 55 | - A proxy type for sharing an ADC to allow "concurrently" reading its channels 56 | from multiple tasks ([#24]). 57 | - Support for thumbv6 targets by using `atomic-polyfill` instead of libcore 58 | atomics ([#30]). 59 | 60 | [#22]: https://github.com/rahix/shared-bus/pull/22 61 | [#24]: https://github.com/rahix/shared-bus/pull/24 62 | [#30]: https://github.com/rahix/shared-bus/pull/30 63 | 64 | 65 | ## [0.2.2] - 2021-04-20 66 | ### Changed 67 | - Updated the documentation on how to use `shared-bus` with RTIC. 68 | - Removed superfluous trait bounds on the types - the trait bounds are really 69 | only necessary on the `impl`-blocks. 70 | 71 | ### Fixed 72 | - Fixed the `Clone` implementations from 0.2.1 being useless due to an 73 | unnecessary trait bound. 74 | 75 | 76 | ## [0.2.1] - 2021-04-20 77 | ### Added 78 | - Added a new "Mutex" and bus-manager for compatibility with RTIC. Check the 79 | documentation for the `BusManagerAtomicCheck` for details. 80 | 81 | ### Changed 82 | - `I2cProxy` and `SpiProxy` now implement `Clone`. Cloning a proxy might be 83 | useful if a driver wants to split bus access up even further internally 84 | without wrapping the bus-proxy in another bus-manager. 85 | 86 | 87 | ## [0.2.0] - 2020-08-24 88 | Complete rework of the crate, most items have changed at least slightly. 89 | Please read the updated documentation to understand how the new version 90 | works. 91 | 92 | ### Added 93 | - A `BusMutexSimple` for sharing within a single task/thread with minimal 94 | overhead. 95 | - Macros for instanciating a 'global' bus manager which lives for `'static`. 96 | 97 | ### Changed 98 | - The `BusMutex` trait's `lock()` method now passes `&mut` to the closure, 99 | removing the `RefCell` from the manager. 100 | - The generic parameter of `BusMutex` was moved into an associated type. 101 | - Instead of a single proxy-type for everything, separate proxy types were 102 | introduced, to allow different constraints on their creation. 103 | 104 | ### Fixed 105 | - The SPI proxy is now `!Send` to make sure it can only be used from 106 | within a single thread/task. 107 | 108 | 109 | ## 0.1.4 - 2018-11-04 110 | ### Changed 111 | - Documentation fixes. 112 | 113 | 114 | ## 0.1.3 - 2018-10-30 115 | ### Added 116 | - Added an SPI proxy. 117 | 118 | 119 | ## 0.1.2 - 2018-08-14 120 | ### Changed 121 | - Documentation fixes. 122 | 123 | 124 | ## 0.1.1 - 2018-08-13 125 | ### Changed 126 | - Documentation fixes. 127 | 128 | 129 | ## 0.1.0 - 2018-08-13 130 | Initial release 131 | 132 | [Unreleased]: https://github.com/Rahix/shared-bus/compare/v0.3.1...main 133 | [0.3.1]: https://github.com/Rahix/shared-bus/compare/v0.3.0...v0.3.1 134 | [0.3.0]: https://github.com/Rahix/shared-bus/compare/v0.2.5...v0.3.0 135 | [0.2.5]: https://github.com/Rahix/shared-bus/compare/v0.2.4...v0.2.5 136 | [0.2.4]: https://github.com/Rahix/shared-bus/compare/v0.2.3...v0.2.4 137 | [0.2.3]: https://github.com/Rahix/shared-bus/compare/v0.2.2...v0.2.3 138 | [0.2.2]: https://github.com/Rahix/shared-bus/compare/v0.2.1...v0.2.2 139 | [0.2.1]: https://github.com/Rahix/shared-bus/compare/v0.2.0...v0.2.1 140 | [0.2.0]: https://github.com/Rahix/shared-bus/compare/e24defd5c802...v0.2.0 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | shared-bus [![crates.io page](https://img.shields.io/crates/v/shared-bus)](https://crates.io/crates/shared-bus) [![docs.rs](https://docs.rs/shared-bus/badge.svg)](https://docs.rs/shared-bus) [![Continuous Integration](https://github.com/Rahix/shared-bus/actions/workflows/ci.yml/badge.svg)](https://github.com/Rahix/shared-bus/actions/workflows/ci.yml) 2 | ========== 3 | 4 | **shared-bus** is a crate to allow sharing bus peripherals safely between multiple devices. 5 | 6 | In the `embedded-hal` ecosystem, it is convention for drivers to "own" the bus peripheral they 7 | are operating on. This implies that only _one_ driver can have access to a certain bus. That, 8 | of course, poses an issue when multiple devices are connected to a single bus. 9 | 10 | _shared-bus_ solves this by giving each driver a bus-proxy to own which internally manages 11 | access to the actual bus in a safe manner. For a more in-depth introduction of the problem 12 | this crate is trying to solve, take a look at the [blog post][blog-post]. 13 | 14 | There are different 'bus managers' for different use-cases: 15 | 16 | # Sharing within a single task/thread 17 | As long as all users of a bus are contained in a single task/thread, bus sharing is very 18 | simple. With no concurrency possible, no special synchronization is needed. This is where 19 | a [`BusManagerSimple`] should be used: 20 | 21 | ```rust 22 | // For example: 23 | let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); 24 | 25 | let bus = shared_bus::BusManagerSimple::new(i2c); 26 | 27 | let mut proxy1 = bus.acquire_i2c(); 28 | let mut my_device = MyDevice::new(bus.acquire_i2c()); 29 | 30 | proxy1.write(0x39, &[0xc0, 0xff, 0xee]); 31 | my_device.do_something_on_the_bus(); 32 | ``` 33 | 34 | The `BusManager::acquire_*()` methods can be called as often as needed; each call will yield 35 | a new bus-proxy of the requested type. 36 | 37 | # Sharing across multiple tasks/threads 38 | For sharing across multiple tasks/threads, synchronization is needed to ensure all bus-accesses 39 | are strictly serialized and can't race against each other. The synchronization is handled by 40 | a platform-specific [`BusMutex`] implementation. _shared-bus_ already contains some 41 | implementations for common targets. For each one, there is also a macro for easily creating 42 | a bus-manager with `'static` lifetime, which is almost always a requirement when sharing across 43 | task/thread boundaries. As an example: 44 | 45 | ```rust 46 | // For example: 47 | let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); 48 | 49 | // The bus is a 'static reference -> it lives forever and references can be 50 | // shared with other threads. 51 | let bus: &'static _ = shared_bus::new_std!(SomeI2cBus = i2c).unwrap(); 52 | 53 | let mut proxy1 = bus.acquire_i2c(); 54 | let mut my_device = MyDevice::new(bus.acquire_i2c()); 55 | 56 | // We can easily move a proxy to another thread: 57 | # let t = 58 | std::thread::spawn(move || { 59 | my_device.do_something_on_the_bus(); 60 | }); 61 | # t.join().unwrap(); 62 | ``` 63 | 64 | Those platform-specific bits are guarded by a feature that needs to be enabled. Here is an 65 | overview of what's already available: 66 | 67 | | Mutex | Bus Manager | `'static` Bus Macro | Feature Name | 68 | | --- | --- | --- | --- | 69 | | `std::sync::Mutex` | [`BusManagerStd`] | [`new_std!()`] | `std` | 70 | | `cortex_m::interrupt::Mutex` | [`BusManagerCortexM`] | [`new_cortexm!()`] | `cortex-m` | 71 | | `shared_bus::XtensaMutex` (`spin::Mutex` in critical section) | [`BusManagerXtensa`] | [`new_xtensa!()`] | `xtensa` | 72 | | NA | [`BusManagerAtomicCheck`] | [`new_atomic_check!()`] | `cortex-m` | 73 | 74 | # Supported Busses 75 | Currently, the following busses can be shared with _shared-bus_: 76 | 77 | | Bus | Proxy Type | Acquire Method | Comments | 78 | | --- | --- | --- | --- | 79 | | I2C | [`I2cProxy`] | [`.acquire_i2c()`] | | 80 | | SPI | [`SpiProxy`] | [`.acquire_spi()`] | SPI can only be shared within a single task (See [`SpiProxy`] for details). | 81 | | ADC | [`AdcProxy`] | [`.acquire_adc()`] | | 82 | 83 | 84 | [`.acquire_i2c()`]: https://docs.rs/shared-bus/latest/shared_bus/struct.BusManager.html#method.acquire_i2c 85 | [`.acquire_spi()`]: https://docs.rs/shared-bus/latest/shared_bus/struct.BusManager.html#method.acquire_spi 86 | [`.acquire_adc()`]: https://docs.rs/shared-bus/latest/shared_bus/struct.BusManager.html#method.acquire_adc 87 | [`BusManagerCortexM`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerCortexM.html 88 | [`BusManagerSimple`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerSimple.html 89 | [`BusManagerAtomicCheck`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerAtomicCheck.html 90 | [`BusManagerStd`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerStd.html 91 | [`BusManagerXtensa`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerXtensa.html 92 | [`BusMutex`]: https://docs.rs/shared-bus/latest/shared_bus/trait.BusMutex.html 93 | [`I2cProxy`]: https://docs.rs/shared-bus/latest/shared_bus/struct.I2cProxy.html 94 | [`SpiProxy`]: https://docs.rs/shared-bus/latest/shared_bus/struct.SpiProxy.html 95 | [`AdcProxy`]: https://docs.rs/shared-bus/latest/shared_bus/struct.AdcProxy.html 96 | [`new_cortexm!()`]: https://docs.rs/shared-bus/latest/shared_bus/macro.new_cortexm.html 97 | [`new_atomic_check!()`]: https://docs.rs/shared-bus/latest/shared_bus/macro.new_atomic_check.html 98 | [`new_xtensa!()`]: https://docs.rs/shared-bus/latest/shared_bus/macro.new_xtensa.html 99 | [`new_std!()`]: https://docs.rs/shared-bus/latest/shared_bus/macro.new_std.html 100 | [blog-post]: https://blog.rahix.de/001-shared-bus 101 | 102 | ## License 103 | shared-bus is licensed under either of 104 | 105 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 106 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 107 | 108 | at your option. 109 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Macro for creating a `std`-based bus manager with `'static` lifetime. 2 | /// 3 | /// This macro is a convenience helper for creating a bus manager that lives for the `'static` 4 | /// lifetime an thus can be safely shared across threads. 5 | /// 6 | /// This macro is only available with the `std` feature. 7 | /// 8 | /// # Syntax 9 | /// ```ignore 10 | /// let bus = shared_bus::new_std!( = ).unwrap(); 11 | /// ``` 12 | /// 13 | /// The macro returns an Option which will be `Some(&'static bus_manager)` on the first run and 14 | /// `None` afterwards. This is necessary to uphold safety around the inner `static` variable. 15 | /// 16 | /// # Example 17 | /// ``` 18 | /// # struct MyDevice(T); 19 | /// # impl MyDevice { 20 | /// # pub fn new(t: T) -> Self { MyDevice(t) } 21 | /// # pub fn do_something_on_the_bus(&mut self) { } 22 | /// # } 23 | /// # 24 | /// # struct SomeI2cBus; 25 | /// # let i2c = SomeI2cBus; 26 | /// // For example: 27 | /// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); 28 | /// 29 | /// // The bus is a 'static reference -> it lives forever and references can be 30 | /// // shared with other threads. 31 | /// let bus: &'static _ = shared_bus::new_std!(SomeI2cBus = i2c).unwrap(); 32 | /// 33 | /// let mut proxy1 = bus.acquire_i2c(); 34 | /// let mut my_device = MyDevice::new(bus.acquire_i2c()); 35 | /// 36 | /// // We can easily move a proxy to another thread: 37 | /// # let t = 38 | /// std::thread::spawn(move || { 39 | /// my_device.do_something_on_the_bus(); 40 | /// }); 41 | /// # t.join().unwrap(); 42 | /// ``` 43 | #[cfg(feature = "std")] 44 | #[macro_export] 45 | macro_rules! new_std { 46 | ($bus_type:ty = $bus:expr) => {{ 47 | use $crate::once_cell::sync::OnceCell; 48 | 49 | static MANAGER: OnceCell<$crate::BusManagerStd<$bus_type>> = OnceCell::new(); 50 | 51 | let m = $crate::BusManagerStd::new($bus); 52 | match MANAGER.set(m) { 53 | Ok(_) => MANAGER.get(), 54 | Err(_) => None, 55 | } 56 | }}; 57 | } 58 | 59 | /// Macro for creating a Cortex-M bus manager with `'static` lifetime. 60 | /// 61 | /// This macro is a convenience helper for creating a bus manager that lives for the `'static` 62 | /// lifetime an thus can be safely shared across tasks/execution contexts (like interrupts). 63 | /// 64 | /// This macro is only available with the `cortex-m` feature. 65 | /// 66 | /// # Syntax 67 | /// ```ignore 68 | /// let bus = shared_bus::new_cortexm!( = ).unwrap(); 69 | /// ``` 70 | /// 71 | /// The macro returns an Option which will be `Some(&'static bus_manager)` on the first run and 72 | /// `None` afterwards. This is necessary to uphold safety around the inner `static` variable. 73 | /// 74 | /// # Example 75 | /// ```no_run 76 | /// # use embedded_hal::blocking::i2c::Write; 77 | /// # struct MyDevice(T); 78 | /// # impl MyDevice { 79 | /// # pub fn new(t: T) -> Self { MyDevice(t) } 80 | /// # pub fn do_something_on_the_bus(&mut self) { } 81 | /// # } 82 | /// # 83 | /// # struct SomeI2cBus; 84 | /// # impl Write for SomeI2cBus { 85 | /// # type Error = (); 86 | /// # fn write(&mut self, addr: u8, buffer: &[u8]) -> Result<(), Self::Error> { Ok(()) } 87 | /// # } 88 | /// static mut SHARED_DEVICE: 89 | /// Option>>> 90 | /// = None; 91 | /// 92 | /// fn main() -> ! { 93 | /// # let i2c = SomeI2cBus; 94 | /// // For example: 95 | /// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); 96 | /// 97 | /// // The bus is a 'static reference -> it lives forever and references can be 98 | /// // shared with other tasks. 99 | /// let bus: &'static _ = shared_bus::new_cortexm!(SomeI2cBus = i2c).unwrap(); 100 | /// 101 | /// let mut proxy1 = bus.acquire_i2c(); 102 | /// let my_device = MyDevice::new(bus.acquire_i2c()); 103 | /// 104 | /// unsafe { 105 | /// SHARED_DEVICE = Some(my_device); 106 | /// } 107 | /// 108 | /// cortex_m::asm::dmb(); 109 | /// 110 | /// // enable the interrupt 111 | /// 112 | /// loop { 113 | /// proxy1.write(0x39, &[0xaa]); 114 | /// } 115 | /// } 116 | /// 117 | /// fn INTERRUPT() { 118 | /// let dev = unsafe {SHARED_DEVICE.as_mut().unwrap()}; 119 | /// 120 | /// dev.do_something_on_the_bus(); 121 | /// } 122 | /// ``` 123 | #[cfg(feature = "cortex-m")] 124 | #[macro_export] 125 | macro_rules! new_cortexm { 126 | ($bus_type:ty = $bus:expr) => {{ 127 | let m: Option<&'static mut _> = $crate::cortex_m::singleton!( 128 | : $crate::BusManagerCortexM<$bus_type> = 129 | $crate::BusManagerCortexM::new($bus) 130 | ); 131 | 132 | m 133 | }}; 134 | } 135 | 136 | /// Macro for creating a Xtensa-lx6 bus manager with `'static` lifetime. 137 | /// 138 | /// This macro is a convenience helper for creating a bus manager that lives for the `'static` 139 | /// lifetime an thus can be safely shared across tasks/execution contexts (like interrupts). 140 | /// 141 | /// This macro is only available with the `xtensa` feature. 142 | /// 143 | /// # Syntax 144 | /// ```ignore 145 | /// let bus = shared_bus::new_xtensa!( = ).unwrap(); 146 | /// ``` 147 | /// 148 | /// The macro returns an Option which will be `Some(&'static bus_manager)` on the first run and 149 | /// `None` afterwards. This is necessary to uphold safety around the inner `static` variable. 150 | #[cfg(feature = "xtensa")] 151 | #[macro_export] 152 | macro_rules! new_xtensa { 153 | ($bus_type:ty = $bus:expr) => {{ 154 | let m: Option<&'static mut _> = $crate::xtensa_lx::singleton!( 155 | : $crate::BusManagerXtensa<$bus_type> = 156 | $crate::BusManagerXtensa::new($bus) 157 | ); 158 | 159 | m 160 | }}; 161 | } 162 | 163 | /// Construct a statically allocated bus manager. 164 | #[cfg(feature = "cortex-m")] 165 | #[macro_export] 166 | macro_rules! new_atomic_check { 167 | ($bus_type:ty = $bus:expr) => {{ 168 | let m: Option<&'static mut _> = $crate::cortex_m::singleton!( 169 | : $crate::BusManagerAtomicCheck<$bus_type> = 170 | $crate::BusManagerAtomicCheck::new($bus) 171 | ); 172 | 173 | m 174 | }}; 175 | } 176 | -------------------------------------------------------------------------------- /src/mutex.rs: -------------------------------------------------------------------------------- 1 | use core::cell; 2 | 3 | /// Common interface for mutex implementations. 4 | /// 5 | /// `shared-bus` needs a mutex to ensure only a single device can access the bus at the same time 6 | /// in concurrent situations. `shared-bus` already implements this trait for a number of existing 7 | /// mutex types. Most of them are guarded by a feature that needs to be enabled. Here is an 8 | /// overview: 9 | /// 10 | /// | Mutex | Feature Name | Notes | 11 | /// | --- | --- | --- | 12 | /// | [`shared_bus::NullMutex`][null-mutex] | always available | For sharing within a single execution context. | 13 | /// | [`std::sync::Mutex`][std-mutex] | `std` | For platforms where `std` is available. | 14 | /// | [`cortex_m::interrupt::Mutex`][cortexm-mutex] | `cortex-m` | For Cortex-M platforms; Uses a critcal section (i.e. turns off interrupts during bus transactions). | 15 | /// 16 | /// [null-mutex]: ./struct.NullMutex.html 17 | /// [std-mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html 18 | /// [cortexm-mutex]: https://docs.rs/cortex-m/0.6.3/cortex_m/interrupt/struct.Mutex.html 19 | /// 20 | /// For other mutex types, a custom implementation is needed. Due to the orphan rule, it might be 21 | /// necessary to wrap it in a newtype. As an example, this is what such a custom implementation 22 | /// might look like: 23 | /// 24 | /// ``` 25 | /// struct MyMutex(std::sync::Mutex); 26 | /// 27 | /// impl shared_bus::BusMutex for MyMutex { 28 | /// type Bus = T; 29 | /// 30 | /// fn create(v: T) -> Self { 31 | /// Self(std::sync::Mutex::new(v)) 32 | /// } 33 | /// 34 | /// fn lock R>(&self, f: F) -> R { 35 | /// let mut v = self.0.lock().unwrap(); 36 | /// f(&mut v) 37 | /// } 38 | /// } 39 | /// 40 | /// // It is also beneficial to define a type alias for the BusManager 41 | /// type BusManagerCustom = shared_bus::BusManager>; 42 | /// ``` 43 | pub trait BusMutex { 44 | /// The actual bus that is wrapped inside this mutex. 45 | type Bus; 46 | 47 | /// Create a new mutex of this type. 48 | fn create(v: Self::Bus) -> Self; 49 | 50 | /// Lock the mutex and give a closure access to the bus inside. 51 | fn lock R>(&self, f: F) -> R; 52 | } 53 | 54 | /// "Dummy" mutex for sharing in a single task/thread. 55 | /// 56 | /// This mutex type can be used when all bus users are contained in a single execution context. In 57 | /// such a situation, no actual mutex is needed, because a RefCell alone is sufficient to ensuring 58 | /// only a single peripheral can access the bus at the same time. 59 | /// 60 | /// This mutex type is used with the [`BusManagerSimple`] type. 61 | /// 62 | /// To uphold safety, this type is `!Send` and `!Sync`. 63 | /// 64 | /// [`BusManagerSimple`]: ./type.BusManagerSimple.html 65 | #[derive(Debug)] 66 | pub struct NullMutex { 67 | bus: cell::RefCell, 68 | } 69 | 70 | impl BusMutex for NullMutex { 71 | type Bus = T; 72 | 73 | fn create(v: Self::Bus) -> Self { 74 | NullMutex { 75 | bus: cell::RefCell::new(v), 76 | } 77 | } 78 | 79 | fn lock R>(&self, f: F) -> R { 80 | let mut v = self.bus.borrow_mut(); 81 | f(&mut v) 82 | } 83 | } 84 | 85 | #[cfg(feature = "std")] 86 | impl BusMutex for ::std::sync::Mutex { 87 | type Bus = T; 88 | 89 | fn create(v: Self::Bus) -> Self { 90 | ::std::sync::Mutex::new(v) 91 | } 92 | 93 | fn lock R>(&self, f: F) -> R { 94 | let mut v = self.lock().unwrap(); 95 | f(&mut v) 96 | } 97 | } 98 | 99 | /// Alias for a Cortex-M mutex. 100 | /// 101 | /// Based on [`cortex_m::interrupt::Mutex`][cortexm-mutex]. This mutex works by disabling 102 | /// interrupts while the mutex is locked. 103 | /// 104 | /// [cortexm-mutex]: https://docs.rs/cortex-m/0.6.3/cortex_m/interrupt/struct.Mutex.html 105 | /// 106 | /// This type is only available with the `cortex-m` feature. 107 | #[cfg(feature = "cortex-m")] 108 | pub type CortexMMutex = cortex_m::interrupt::Mutex>; 109 | 110 | #[cfg(feature = "cortex-m")] 111 | impl BusMutex for CortexMMutex { 112 | type Bus = T; 113 | 114 | fn create(v: T) -> Self { 115 | cortex_m::interrupt::Mutex::new(cell::RefCell::new(v)) 116 | } 117 | 118 | fn lock R>(&self, f: F) -> R { 119 | cortex_m::interrupt::free(|cs| { 120 | let c = self.borrow(cs); 121 | f(&mut c.borrow_mut()) 122 | }) 123 | } 124 | } 125 | 126 | /// Wrapper for an interrupt free spin mutex. 127 | /// 128 | /// Based on [`spin::Mutex`][spin-mutex]. This mutex works by disabling 129 | /// interrupts while the mutex is locked. 130 | /// 131 | /// [spin-mutex]: https://docs.rs/spin/0.9.2/spin/type.Mutex.html 132 | /// 133 | /// This type is only available with the `xtensa` feature. 134 | #[cfg(feature = "xtensa")] 135 | pub struct XtensaMutex(spin::Mutex); 136 | 137 | #[cfg(feature = "xtensa")] 138 | impl BusMutex for XtensaMutex { 139 | type Bus = T; 140 | 141 | fn create(v: T) -> Self { 142 | XtensaMutex(spin::Mutex::new(v)) 143 | } 144 | 145 | fn lock R>(&self, f: F) -> R { 146 | xtensa_lx::interrupt::free(|_| f(&mut (*self.0.lock()))) 147 | } 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use super::*; 153 | 154 | #[test] 155 | fn std_mutex_api_test() { 156 | let t = "hello ".to_string(); 157 | let m: std::sync::Mutex<_> = BusMutex::create(t); 158 | 159 | BusMutex::lock(&m, |s| { 160 | s.push_str("world"); 161 | }); 162 | 163 | BusMutex::lock(&m, |s| { 164 | assert_eq!("hello world", s); 165 | }); 166 | } 167 | } 168 | 169 | /// A simple coherency checker for sharing across multiple tasks/threads. 170 | /// 171 | /// This mutex type can be used when all bus users are contained in a single structure, and is 172 | /// intended for use with RTIC. When all bus users are contained within a structure managed by RTIC, 173 | /// RTIC will guarantee that bus collisions do not occur. To protect against accidentaly misuse, 174 | /// this mutex uses an atomic bool to determine when the bus is in use. If a bus collision is 175 | /// detected, the code will panic. 176 | /// 177 | /// This mutex type is used with the [`BusManagerAtomicMutex`] type. 178 | /// 179 | /// This manager type is explicitly safe to share across threads because it checks to ensure that 180 | /// collisions due to bus sharing do not occur. 181 | /// 182 | /// [`BusManagerAtomicMutex`]: ./type.BusManagerAtomicMutex.html 183 | #[cfg(feature = "cortex-m")] 184 | #[derive(Debug)] 185 | pub struct AtomicCheckMutex { 186 | bus: core::cell::UnsafeCell, 187 | busy: atomic_polyfill::AtomicBool, 188 | } 189 | 190 | // It is explicitly safe to share this across threads because there is a coherency check using an 191 | // atomic bool comparison. 192 | #[cfg(feature = "cortex-m")] 193 | unsafe impl Sync for AtomicCheckMutex {} 194 | 195 | #[cfg(feature = "cortex-m")] 196 | impl BusMutex for AtomicCheckMutex { 197 | type Bus = BUS; 198 | 199 | fn create(v: BUS) -> Self { 200 | Self { 201 | bus: core::cell::UnsafeCell::new(v), 202 | busy: atomic_polyfill::AtomicBool::from(false), 203 | } 204 | } 205 | 206 | fn lock R>(&self, f: F) -> R { 207 | self.busy 208 | .compare_exchange( 209 | false, 210 | true, 211 | core::sync::atomic::Ordering::SeqCst, 212 | core::sync::atomic::Ordering::SeqCst, 213 | ) 214 | .expect("Bus conflict"); 215 | let result = f(unsafe { &mut *self.bus.get() }); 216 | 217 | self.busy.store(false, core::sync::atomic::Ordering::SeqCst); 218 | 219 | result 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/manager.rs: -------------------------------------------------------------------------------- 1 | /// "Manager" for a shared bus. 2 | /// 3 | /// The manager owns the original bus peripheral (wrapped inside a mutex) and hands out proxies 4 | /// which can be used by device drivers for accessing the bus. Certain bus proxies can only be 5 | /// created with restrictions (see the individual methods for details). 6 | /// 7 | /// Usually the type-aliases defined in this crate should be used instead of `BusManager` directly. 8 | /// Otherwise, the mutex type needs to be specified explicitly. Here is an overview of aliases 9 | /// (some are only available if a certain feature is enabled): 10 | /// 11 | /// | Bus Manager | Mutex Type | Feature Name | Notes | 12 | /// | --- | --- | --- | --- | 13 | /// | [`BusManagerSimple`] | `shared_bus::NullMutex` | always available | For sharing within a single execution context. | 14 | /// | [`BusManagerStd`] | `std::sync::Mutex` | `std` | For platforms where `std` is available. | 15 | /// | [`BusManagerCortexM`] | `cortex_m::interrupt::Mutex` | `cortex-m` | For Cortex-M platforms; Uses a critcal section (i.e. turns off interrupts during bus transactions). | 16 | /// 17 | /// [`BusManagerSimple`]: ./type.BusManagerSimple.html 18 | /// [`BusManagerStd`]: ./type.BusManagerStd.html 19 | /// [`BusManagerCortexM`]: ./type.BusManagerCortexM.html 20 | /// 21 | /// # Constructing a `BusManager` 22 | /// There are two ways to instanciate a bus manager. Which one to use depends on the kind of 23 | /// sharing that is intended. 24 | /// 25 | /// 1. When all bus users live in the same task/thread, a `BusManagerSimple` can be used: 26 | /// 27 | /// ``` 28 | /// # use embedded_hal::blocking::i2c; 29 | /// # use embedded_hal::blocking::i2c::Write as _; 30 | /// # struct MyDevice(T); 31 | /// # impl MyDevice { 32 | /// # pub fn new(t: T) -> Self { MyDevice(t) } 33 | /// # pub fn do_something_on_the_bus(&mut self) { 34 | /// # self.0.write(0xab, &[0x00]); 35 | /// # } 36 | /// # } 37 | /// # 38 | /// # fn _example(i2c: impl i2c::Write) { 39 | /// // For example: 40 | /// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); 41 | /// 42 | /// let bus = shared_bus::BusManagerSimple::new(i2c); 43 | /// 44 | /// let mut proxy1 = bus.acquire_i2c(); 45 | /// let mut my_device = MyDevice::new(bus.acquire_i2c()); 46 | /// 47 | /// proxy1.write(0x39, &[0xc0, 0xff, 0xee]); 48 | /// my_device.do_something_on_the_bus(); 49 | /// # } 50 | /// ``` 51 | /// 52 | /// 2. When users are in different execution contexts, a proper mutex type is needed and the 53 | /// manager must be made `static` to ensure it lives long enough. For this, `shared-bus` 54 | /// provides a number of macros creating such a `static` instance: 55 | /// 56 | /// ``` 57 | /// # struct MyDevice(T); 58 | /// # impl MyDevice { 59 | /// # pub fn new(t: T) -> Self { MyDevice(t) } 60 | /// # pub fn do_something_on_the_bus(&mut self) { } 61 | /// # } 62 | /// # 63 | /// # struct SomeI2cBus; 64 | /// # let i2c = SomeI2cBus; 65 | /// // For example: 66 | /// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); 67 | /// 68 | /// // The bus is a 'static reference -> it lives forever and references can be 69 | /// // shared with other threads. 70 | /// let bus: &'static _ = shared_bus::new_std!(SomeI2cBus = i2c).unwrap(); 71 | /// 72 | /// let mut proxy1 = bus.acquire_i2c(); 73 | /// let mut my_device = MyDevice::new(bus.acquire_i2c()); 74 | /// 75 | /// // We can easily move a proxy to another thread: 76 | /// # let t = 77 | /// std::thread::spawn(move || { 78 | /// my_device.do_something_on_the_bus(); 79 | /// }); 80 | /// # t.join().unwrap(); 81 | /// ``` 82 | /// 83 | /// For other platforms, similar macros exist (e.g. [`new_cortexm!()`]). 84 | /// 85 | /// [`new_cortexm!()`]: ./macro.new_cortexm.html 86 | #[derive(Debug)] 87 | pub struct BusManager { 88 | mutex: M, 89 | } 90 | 91 | impl BusManager { 92 | /// Create a new bus manager for a bus. 93 | /// 94 | /// See the documentation for `BusManager` for more details. 95 | pub fn new(bus: M::Bus) -> Self { 96 | let mutex = M::create(bus); 97 | 98 | BusManager { mutex } 99 | } 100 | } 101 | 102 | impl BusManager { 103 | /// Acquire an [`I2cProxy`] for this bus. 104 | /// 105 | /// [`I2cProxy`]: ./struct.I2cProxy.html 106 | /// 107 | /// The returned proxy object can then be used for accessing the bus by e.g. a driver: 108 | /// 109 | /// ``` 110 | /// # use embedded_hal::blocking::i2c; 111 | /// # use embedded_hal::blocking::i2c::Write as _; 112 | /// # struct MyDevice(T); 113 | /// # impl MyDevice { 114 | /// # pub fn new(t: T) -> Self { MyDevice(t) } 115 | /// # pub fn do_something_on_the_bus(&mut self) { 116 | /// # self.0.write(0xab, &[0x00]); 117 | /// # } 118 | /// # } 119 | /// # 120 | /// # fn _example(i2c: impl i2c::Write) { 121 | /// let bus = shared_bus::BusManagerSimple::new(i2c); 122 | /// 123 | /// let mut proxy1 = bus.acquire_i2c(); 124 | /// let mut my_device = MyDevice::new(bus.acquire_i2c()); 125 | /// 126 | /// proxy1.write(0x39, &[0xc0, 0xff, 0xee]); 127 | /// my_device.do_something_on_the_bus(); 128 | /// # } 129 | /// ``` 130 | pub fn acquire_i2c<'a>(&'a self) -> crate::I2cProxy<'a, M> { 131 | crate::I2cProxy { mutex: &self.mutex } 132 | } 133 | 134 | /// Acquire an [`AdcProxy`] for this hardware block. 135 | /// 136 | /// [`AdcProxy`]: ./struct.AdcProxy.html 137 | /// 138 | /// The returned proxy object can then be used for accessing the bus by e.g. a driver: 139 | /// 140 | /// ```ignore 141 | /// // For example: 142 | /// // let ch0 = gpioa.pa0.into_analog(&mut gpioa.crl); 143 | /// // let ch1 = gpioa.pa1.into_analog(&mut gpioa.crl); 144 | /// // let adc = Adc::adc1(p.ADC1, &mut rcc.apb2, clocks); 145 | /// 146 | /// let adc_bus: &'static _ = shared_bus::new_cortexm!(Adc = adc).unwrap(); 147 | /// let mut proxy1 = adc_bus.acquire_adc(); 148 | /// let mut proxy2 = adc_bus.acquire_adc(); 149 | /// 150 | /// proxy1.read(ch0).unwrap(); 151 | /// proxy2.read(ch1).unwrap(); 152 | /// 153 | /// ``` 154 | 155 | pub fn acquire_adc<'a>(&'a self) -> crate::AdcProxy<'a, M> { 156 | crate::AdcProxy { mutex: &self.mutex } 157 | } 158 | } 159 | 160 | impl BusManager> { 161 | /// Acquire an [`SpiProxy`] for this bus. 162 | /// 163 | /// **Note**: SPI Proxies can only be created from [`BusManagerSimple`] (= bus managers using 164 | /// the [`NullMutex`]). See [`SpiProxy`] for more details why. 165 | /// 166 | /// [`BusManagerSimple`]: ./type.BusManagerSimple.html 167 | /// [`NullMutex`]: ./struct.NullMutex.html 168 | /// [`SpiProxy`]: ./struct.SpiProxy.html 169 | /// 170 | /// The returned proxy object can then be used for accessing the bus by e.g. a driver: 171 | /// 172 | /// ``` 173 | /// # use embedded_hal::blocking::spi; 174 | /// # use embedded_hal::digital::v2; 175 | /// # use embedded_hal::blocking::spi::Write as _; 176 | /// # struct MyDevice(T); 177 | /// # impl> MyDevice { 178 | /// # pub fn new(t: T) -> Self { MyDevice(t) } 179 | /// # pub fn do_something_on_the_bus(&mut self) { 180 | /// # self.0.write(&[0x00]); 181 | /// # } 182 | /// # } 183 | /// # 184 | /// # fn _example(mut cs1: impl v2::OutputPin, spi: impl spi::Write) { 185 | /// let bus = shared_bus::BusManagerSimple::new(spi); 186 | /// 187 | /// let mut proxy1 = bus.acquire_spi(); 188 | /// let mut my_device = MyDevice::new(bus.acquire_spi()); 189 | /// 190 | /// // Chip-select needs to be managed manually 191 | /// cs1.set_high(); 192 | /// proxy1.write(&[0xc0, 0xff, 0xee]); 193 | /// cs1.set_low(); 194 | /// 195 | /// my_device.do_something_on_the_bus(); 196 | /// # } 197 | /// ``` 198 | pub fn acquire_spi<'a>(&'a self) -> crate::SpiProxy<'a, crate::NullMutex> { 199 | crate::SpiProxy { 200 | mutex: &self.mutex, 201 | _u: core::marker::PhantomData, 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/proxies.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "eh-alpha")] 2 | use embedded_hal_alpha::i2c as i2c_alpha; 3 | 4 | use embedded_hal::adc; 5 | use embedded_hal::blocking::i2c; 6 | use embedded_hal::blocking::spi; 7 | 8 | /// Proxy type for I2C bus sharing. 9 | /// 10 | /// The `I2cProxy` implements all (blocking) I2C traits so it can be passed to drivers instead of 11 | /// the bus instance. Internally, it holds reference to the bus via a mutex, ensuring that all 12 | /// accesses are strictly synchronized. 13 | /// 14 | /// An `I2cProxy` is created by calling [`BusManager::acquire_i2c()`][acquire_i2c]. 15 | /// 16 | /// [acquire_i2c]: ./struct.BusManager.html#method.acquire_i2c 17 | #[derive(Debug)] 18 | pub struct I2cProxy<'a, M> { 19 | pub(crate) mutex: &'a M, 20 | } 21 | 22 | impl<'a, M: crate::BusMutex> Clone for I2cProxy<'a, M> { 23 | fn clone(&self) -> Self { 24 | Self { mutex: &self.mutex } 25 | } 26 | } 27 | 28 | impl<'a, M: crate::BusMutex> i2c::Write for I2cProxy<'a, M> 29 | where 30 | M::Bus: i2c::Write, 31 | { 32 | type Error = ::Error; 33 | 34 | fn write(&mut self, addr: u8, buffer: &[u8]) -> Result<(), Self::Error> { 35 | self.mutex.lock(|bus| bus.write(addr, buffer)) 36 | } 37 | } 38 | 39 | impl<'a, M: crate::BusMutex> i2c::Read for I2cProxy<'a, M> 40 | where 41 | M::Bus: i2c::Read, 42 | { 43 | type Error = ::Error; 44 | 45 | fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { 46 | self.mutex.lock(|bus| bus.read(addr, buffer)) 47 | } 48 | } 49 | 50 | impl<'a, M: crate::BusMutex> i2c::WriteRead for I2cProxy<'a, M> 51 | where 52 | M::Bus: i2c::WriteRead, 53 | { 54 | type Error = ::Error; 55 | 56 | fn write_read( 57 | &mut self, 58 | addr: u8, 59 | buffer_in: &[u8], 60 | buffer_out: &mut [u8], 61 | ) -> Result<(), Self::Error> { 62 | self.mutex 63 | .lock(|bus| bus.write_read(addr, buffer_in, buffer_out)) 64 | } 65 | } 66 | 67 | impl<'a, M: crate::BusMutex> i2c::WriteIterRead for I2cProxy<'a, M> 68 | where 69 | M::Bus: i2c::WriteIterRead, 70 | { 71 | type Error = ::Error; 72 | 73 | fn write_iter_read( 74 | &mut self, 75 | address: u8, 76 | bytes: B, 77 | buffer: &mut [u8], 78 | ) -> Result<(), Self::Error> 79 | where 80 | B: IntoIterator, 81 | { 82 | self.mutex 83 | .lock(|bus| bus.write_iter_read(address, bytes, buffer)) 84 | } 85 | } 86 | 87 | impl<'a, M: crate::BusMutex> i2c::WriteIter for I2cProxy<'a, M> 88 | where 89 | M::Bus: i2c::WriteIter, 90 | { 91 | type Error = ::Error; 92 | 93 | fn write(&mut self, address: u8, bytes: B) -> Result<(), Self::Error> 94 | where 95 | B: IntoIterator, 96 | { 97 | self.mutex.lock(|bus| bus.write(address, bytes)) 98 | } 99 | } 100 | 101 | // Implementations for the embedded_hal alpha 102 | 103 | #[cfg(feature = "eh-alpha")] 104 | impl<'a, M: crate::BusMutex> i2c_alpha::ErrorType for I2cProxy<'a, M> 105 | where 106 | M::Bus: i2c_alpha::ErrorType, 107 | { 108 | type Error = ::Error; 109 | } 110 | 111 | #[cfg(feature = "eh-alpha")] 112 | impl<'a, M: crate::BusMutex> i2c_alpha::I2c for I2cProxy<'a, M> 113 | where 114 | M::Bus: i2c_alpha::I2c, 115 | { 116 | fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { 117 | self.mutex.lock(|bus| bus.read(address, buffer)) 118 | } 119 | 120 | fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { 121 | self.mutex.lock(|bus| bus.write(address, bytes)) 122 | } 123 | 124 | fn write_iter(&mut self, address: u8, bytes: B) -> Result<(), Self::Error> 125 | where 126 | B: IntoIterator, 127 | { 128 | self.mutex.lock(|bus| bus.write_iter(address, bytes)) 129 | } 130 | 131 | fn write_read( 132 | &mut self, 133 | address: u8, 134 | bytes: &[u8], 135 | buffer: &mut [u8], 136 | ) -> Result<(), Self::Error> { 137 | self.mutex 138 | .lock(|bus| bus.write_read(address, bytes, buffer)) 139 | } 140 | 141 | fn write_iter_read( 142 | &mut self, 143 | address: u8, 144 | bytes: B, 145 | buffer: &mut [u8], 146 | ) -> Result<(), Self::Error> 147 | where 148 | B: IntoIterator, 149 | { 150 | self.mutex 151 | .lock(|bus| bus.write_iter_read(address, bytes, buffer)) 152 | } 153 | 154 | fn transaction<'b>( 155 | &mut self, 156 | address: u8, 157 | operations: &mut [i2c_alpha::Operation<'b>], 158 | ) -> Result<(), Self::Error> { 159 | self.mutex.lock(|bus| bus.transaction(address, operations)) 160 | } 161 | 162 | fn transaction_iter<'b, O>(&mut self, address: u8, operations: O) -> Result<(), Self::Error> 163 | where 164 | O: IntoIterator>, 165 | { 166 | self.mutex 167 | .lock(|bus| bus.transaction_iter(address, operations)) 168 | } 169 | } 170 | 171 | /// Proxy type for SPI bus sharing. 172 | /// 173 | /// The `SpiProxy` implements all (blocking) SPI traits so it can be passed to drivers instead of 174 | /// the bus instance. An `SpiProxy` is created by calling [`BusManager::acquire_spi()`][acquire_spi]. 175 | /// 176 | /// **Note**: The `SpiProxy` can only be used for sharing **withing a single task/thread**. This 177 | /// is due to drivers usually managing the chip-select pin manually which would be inherently racy 178 | /// in a concurrent environment (because the mutex is locked only after asserting CS). To ensure 179 | /// safe usage, a `SpiProxy` can only be created when using [`BusManagerSimple`] and is `!Send`. 180 | /// 181 | /// [acquire_spi]: ./struct.BusManager.html#method.acquire_spi 182 | /// [`BusManagerSimple`]: ./type.BusManagerSimple.html 183 | #[derive(Debug)] 184 | pub struct SpiProxy<'a, M> { 185 | pub(crate) mutex: &'a M, 186 | pub(crate) _u: core::marker::PhantomData<*mut ()>, 187 | } 188 | 189 | impl<'a, M: crate::BusMutex> Clone for SpiProxy<'a, M> { 190 | fn clone(&self) -> Self { 191 | Self { 192 | mutex: &self.mutex, 193 | _u: core::marker::PhantomData, 194 | } 195 | } 196 | } 197 | 198 | impl<'a, M: crate::BusMutex> spi::Transfer for SpiProxy<'a, M> 199 | where 200 | M::Bus: spi::Transfer, 201 | { 202 | type Error = >::Error; 203 | 204 | fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { 205 | self.mutex.lock(move |bus| bus.transfer(words)) 206 | } 207 | } 208 | 209 | impl<'a, M: crate::BusMutex> spi::Write for SpiProxy<'a, M> 210 | where 211 | M::Bus: spi::Write, 212 | { 213 | type Error = >::Error; 214 | 215 | fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { 216 | self.mutex.lock(|bus| bus.write(words)) 217 | } 218 | } 219 | 220 | /// Proxy type for ADC sharing. 221 | /// 222 | /// The `AdcProxy` implements OneShot trait so it can be passed to drivers instead of 223 | /// ADC instance. Internally, it holds reference to the bus via a mutex, ensuring 224 | /// that all accesses are strictly synchronized. 225 | /// 226 | /// An `AdcProxy` is created by calling [`BusManager::acquire_adc()`][acquire_adc]. 227 | /// 228 | /// **Note**: The [`adc::OneShot`] trait proxied by this type describes a 229 | /// non-blocking contract for ADC read operation. However access to a shared ADC 230 | /// unit can not be arbitrated in a completely non-blocking and concurrency safe way. 231 | /// Any reading from a channel shall be completed before `shared-bus` can allow the 232 | /// next read from the same or another channel. So the current implementation breaks 233 | /// the non-blocking contract of the trait and just busy-spins until a sample is 234 | /// returned. 235 | /// 236 | /// [acquire_adc]: ./struct.BusManager.html#method.acquire_adc 237 | #[derive(Debug)] 238 | pub struct AdcProxy<'a, M> { 239 | pub(crate) mutex: &'a M, 240 | } 241 | 242 | impl<'a, M: crate::BusMutex> Clone for AdcProxy<'a, M> { 243 | fn clone(&self) -> Self { 244 | Self { mutex: &self.mutex } 245 | } 246 | } 247 | 248 | impl<'a, M: crate::BusMutex, ADC, Word, Pin> adc::OneShot for AdcProxy<'a, M> 249 | where 250 | Pin: adc::Channel, 251 | M::Bus: adc::OneShot, 252 | { 253 | type Error = >::Error; 254 | 255 | fn read(&mut self, pin: &mut Pin) -> nb::Result { 256 | self.mutex 257 | .lock(|bus| nb::block!(bus.read(pin)).map_err(nb::Error::Other)) 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/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); 22 | //! # impl MyDevice { 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); 56 | //! # impl MyDevice { 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 | 122 | mod macros; 123 | mod manager; 124 | mod mutex; 125 | mod proxies; 126 | 127 | #[doc(hidden)] 128 | #[cfg(feature = "std")] 129 | pub use once_cell; 130 | 131 | #[doc(hidden)] 132 | #[cfg(feature = "cortex-m")] 133 | pub use cortex_m; 134 | 135 | #[doc(hidden)] 136 | #[cfg(feature = "xtensa")] 137 | pub use xtensa_lx; 138 | 139 | pub use manager::BusManager; 140 | pub use mutex::BusMutex; 141 | #[cfg(feature = "cortex-m")] 142 | pub use mutex::CortexMMutex; 143 | pub use mutex::NullMutex; 144 | #[cfg(feature = "xtensa")] 145 | pub use mutex::XtensaMutex; 146 | pub use proxies::AdcProxy; 147 | pub use proxies::I2cProxy; 148 | pub use proxies::SpiProxy; 149 | 150 | #[cfg(feature = "cortex-m")] 151 | pub 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); 164 | /// # impl MyDevice { 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 | /// ``` 184 | pub type BusManagerSimple = BusManager>; 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")] 196 | pub type BusManagerStd = BusManager<::std::sync::Mutex>; 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")] 209 | pub type BusManagerCortexM = BusManager>; 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")] 222 | pub type BusManagerXtensa = BusManager>; 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 { bus: T }; 247 | /// struct OtherDevice { bus: T }; 248 | /// 249 | /// // the HAL I2C driver type 250 | /// type I2cType = (); 251 | /// type Proxy = shared_bus::I2cProxy<'static, shared_bus::AtomicCheckMutex>; 252 | /// 253 | /// struct SharedBusDevices { 254 | /// device: Device, 255 | /// other_device: OtherDevice, 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")] 286 | pub type BusManagerAtomicCheck = BusManager>; 287 | --------------------------------------------------------------------------------