diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8de3f12 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,53 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +Complete rework of the crate, most items have changed at least slightly. +Please read the updated documentation to understand how the new version +works. + +### Added +- A `BusMutexSimple` for sharing within a single task/thread with minimal + overhead. +- Macros for instanciating a 'global' bus manager which lives for `'static`. + +### Changed +- The `BusMutex` trait's `lock()` method now passes `&mut` to the closure, + removing the `RefCell` from the manager. +- The generic parameter of `BusMutex` was moved into an associated type. +- Instead of a single proxy-type for everything, separate proxy types were + introduced, to allow different constraints on their creation. + +### Fixed +- The SPI proxy is now `!Send` to make sure it can only be used from + within a single thread/task. + + +## 0.1.4 - 2018-11-04 +### Changed +- Documentation fixes. + + +## 0.1.3 - 2018-10-30 +### Added +- Added an SPI proxy. + + +## 0.1.2 - 2018-08-14 +### Changed +- Documentation fixes. + + +## 0.1.1 - 2018-08-13 +### Changed +- Documentation fixes. + + +## 0.1.0 - 2018-08-13 +Initial release + + +[Unreleased]: https://github.com/Rahix/shared-bus/compare/e24defd5c802...master diff --git a/Cargo.toml b/Cargo.toml index 464d11b..16325e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "shared-bus" -version = "0.1.4" +version = "0.2.0-alpha.1" authors = ["Rahix "] edition = "2018" description = "Abstraction for sharing a bus between multiple devices." -homepage = "https://github.com/Rahix/shared-bus" repository = "https://github.com/Rahix/shared-bus" documentation = "https://docs.rs/shared-bus" readme = "README.md" @@ -13,20 +12,16 @@ keywords = ["embedded-hal", "embedded-hal-impl", "i2c", "spi", "bus"] categories = ["embedded", "no-std"] license = "MIT/Apache-2.0" +[package.metadata.docs.rs] +all-features = true + [dependencies] embedded-hal = "0.2.3" +once_cell = { version = "1.4.0", optional = true } +cortex-m = { version = "0.6.3", optional = true } [dev-dependencies] embedded-hal-mock = "0.7.0" -[dependencies.cortex-m] -optional = true -version = "0.6.0" - [features] -std = [] -docsrs = [] -cortexm = ["cortex-m"] - -[package.metadata.docs.rs] -all-features = true +std = ["once_cell"] diff --git a/README.md b/README.md index 2074e14..9954dac 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,94 @@ -shared-bus [![crates.io page](http://meritbadge.herokuapp.com/shared-bus)](https://crates.io/crates/shared-bus) [![Build Status](https://travis-ci.org/Rahix/shared-bus.svg?branch=master)](https://travis-ci.org/Rahix/shared-bus) [![docs.rs](https://docs.rs/shared-bus/badge.svg)](https://docs.rs/shared-bus) +shared-bus [![crates.io page](http://meritbadge.herokuapp.com/shared-bus)](https://crates.io/crates/shared-bus) [![docs.rs](https://docs.rs/shared-bus/badge.svg)](https://docs.rs/shared-bus) [![Build Status](https://travis-ci.com/Rahix/shared-bus.svg?branch=master)](https://travis-ci.com/Rahix/shared-bus) ========== **shared-bus** is a crate to allow sharing bus peripherals safely between multiple devices. -Typical usage of this crate might look like this: -```rust -extern crate shared_bus; +In the `embedded-hal` ecosystem, it is convention for drivers to "own" the bus peripheral they +are operating on. This implies that only _one_ driver can have access to a certain bus. That, +of course, poses an issue when multiple devices are connected to a single bus. + +_shared-bus_ solves this by giving each driver a bus-proxy to own which internally manages +access to the actual bus in a safe manner. For a more in-depth introduction of the problem +this crate is trying to solve, take a look at the [blog post][blog-post]. + +There are different 'bus managers' for different use-cases: -// Create your bus peripheral as usual: +# Sharing within a single task/thread +As long as all users of a bus are contained in a single task/thread, bus sharing is very +simple. With no concurrency possible, no special synchronization is needed. This is where +a [`BusManagerSimple`] should be used: + +```rust +// For example: let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); -let manager = shared_bus::BusManager::, _>::new(i2c); +let bus = shared_bus::BusManagerSimple::new(i2c); -// You can now acquire bus handles: -let mut handle = manager.acquire(); -// handle implements `i2c::{Read, Write, WriteRead}`, depending on the -// implementations of the underlying peripheral -let mut mydevice = MyDevice::new(manager.acquire()); +let mut proxy1 = bus.acquire_i2c(); +let mut my_device = MyDevice::new(bus.acquire_i2c()); + +proxy1.write(0x39, &[0xc0, 0xff, 0xee]); +my_device.do_something_on_the_bus(); ``` -## Mutex Implementation -To do its job, **shared-bus** needs a mutex. Because each platform has its own -mutex type, **shared-bus** uses an abstraction: `BusMutex`. This type -needs to be implemented for your platforms mutex type to allow using this -crate. +The `BusManager::acquire_*()` methods can be called as often as needed; each call will yield +a new bus-proxy of the requested type. -* If `std` is available, activate the `std` feature to enable the implementation -of `BusMutex` for `std::sync::Mutex`. -* If your device used `cortex-m`, activate the `cortexm` feature to enable the implementation -of `BusMutex` for `cortex_m::interrupt::Mutex`. -* If neither is the case, you need to implement a mutex yourself: +# Sharing across multiple tasks/threads +For sharing across multiple tasks/threads, synchronization is needed to ensure all bus-accesses +are strictly serialized and can't race against each other. The synchronization is handled by +a platform-specific [`BusMutex`] implementation. _shared-bus_ already contains some +implementations for common targets. For each one, there is also a macro for easily creating +a bus-manager with `'static` lifetime, which is almost always a requirement when sharing across +task/thread boundaries. As an example: ```rust -extern crate shared_bus; -extern crate cortex_m; - -// You need a newtype because you can't implement foreign traits on -// foreign types. -struct MyMutex(cortex_m::interrupt::Mutex); - -impl shared_bus::BusMutex for MyMutex { - fn create(v: T) -> Self { - Self(cortex_m::interrupt::Mutex::new(v)) - } - - fn lock R>(&self, f: F) -> R { - cortex_m::interrupt::free(|cs| { - let v = self.0.borrow(cs); - f(v) - }) - } -} - -type MyBusManager = shared_bus::BusManager, P>; +// For example: +let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); + +// The bus is a 'static reference -> it lives forever and references can be +// shared with other threads. +let bus: &'static _ = shared_bus::new_std!(SomeI2cBus = i2c).unwrap(); + +let mut proxy1 = bus.acquire_i2c(); +let mut my_device = MyDevice::new(bus.acquire_i2c()); + +// We can easily move a proxy to another thread: +# let t = +std::thread::spawn(move || { + my_device.do_something_on_the_bus(); +}); +# t.join().unwrap(); ``` -I am welcoming patches containing mutex implementations for other platforms! +Those platform-specific bits are guarded by a feature that needs to be enabled. Here is an +overview of what's already available: + +| Mutex | Bus Manager | `'static` Bus Macro | Feature Name | +| --- | --- | --- | --- | +| `std::sync::Mutex` | [`BusManagerStd`] | [`new_std!()`] | `std` | +| `cortex_m::interrupt::Mutex` | [`BusManagerCortexM`] | [`new_cortexm!()`] | `cortex-m` | + +# Supported Busses +Currently, the following busses can be shared with _shared-bus_: + +| Bus | Proxy Type | Acquire Method | Comments | +| --- | --- | --- | --- | +| I2C | [`I2cProxy`] | [`.acquire_i2c()`] | | +| SPI | [`SpiProxy`] | [`.acquire_spi()`] | SPI can only be shared within a single task (See [`SpiProxy`] for details). | + + +[`.acquire_i2c()`]: https://docs.rs/shared-bus/latest/shared_bus/struct.BusManager.html#method.acquire_i2c +[`.acquire_spi()`]: https://docs.rs/shared-bus/latest/shared_bus/struct.BusManager.html#method.acquire_spi +[`BusManagerCortexM`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerCortexM.html +[`BusManagerSimple`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerSimple.html +[`BusManagerStd`]: https://docs.rs/shared-bus/latest/shared_bus/type.BusManagerStd.html +[`BusMutex`]: https://docs.rs/shared-bus/latest/shared_bus/trait.BusMutex.html +[`I2cProxy`]: https://docs.rs/shared-bus/latest/shared_bus/struct.I2cProxy.html +[`SpiProxy`]: https://docs.rs/shared-bus/latest/shared_bus/struct.SpiProxy.html +[`new_cortexm!()`]: https://docs.rs/shared-bus/latest/shared_bus/macro.new_cortexm.html +[`new_std!()`]: https://docs.rs/shared-bus/latest/shared_bus/macro.new_std.html +[blog-post]: https://blog.rahix.de/001-shared-bus ## License shared-bus is licensed under either of diff --git a/src/lib.rs b/src/lib.rs index 9f65cbf..f1445e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,70 +1,191 @@ -//! # shared-bus +//! **shared-bus** is a crate to allow sharing bus peripherals safely between multiple devices. //! -//! **shared-bus** is a crate to allow sharing bus peripherals -//! safely between multiple devices. +//! In the `embedded-hal` ecosystem, it is convention for drivers to "own" the bus peripheral they +//! are operating on. This implies that only _one_ driver can have access to a certain bus. That, +//! of course, poses an issue when multiple devices are connected to a single bus. //! -//! To do so, **shared-bus** needs a mutex. Because each platform has its own -//! mutex type, **shared-bus** uses an abstraction: [`BusMutex`]. This type -//! needs to be implemented for your platforms mutex type to allow using this -//! crate. +//! _shared-bus_ solves this by giving each driver a bus-proxy to own which internally manages +//! access to the actual bus in a safe manner. For a more in-depth introduction of the problem +//! this crate is trying to solve, take a look at the [blog post][blog-post]. //! -//! * If `std` is available, activate the `std` feature to enable the implementation -//! of [`BusMutex`] for [`std::sync::Mutex`]. -//! * If you platform is based on `cortex-m`, you can activate the `cortexm` feature -//! to enable the implementation of [`BusMutex`] for [`cortex_m::interrupt::Mutex`] -//! * If neither is the case, take a look at the documentation of [`BusMutex`] for hints -//! on how to implement it yourself. +//! There are different 'bus managers' for different use-cases: +//! +//! # Sharing within a single task/thread +//! As long as all users of a bus are contained in a single task/thread, bus sharing is very +//! simple. With no concurrency possible, no special synchronization is needed. This is where +//! a [`BusManagerSimple`] should be used: //! -//! Typical usage of this crate might look like this: //! ``` -//! extern crate shared_bus; -//! # struct MyDevice; -//! # impl MyDevice { -//! # pub fn new(t: T) -> MyDevice { MyDevice } +//! # use embedded_hal::blocking::i2c; +//! # use embedded_hal::blocking::i2c::Write as _; +//! # struct MyDevice(T); +//! # impl MyDevice { +//! # pub fn new(t: T) -> Self { MyDevice(t) } +//! # pub fn do_something_on_the_bus(&mut self) { +//! # self.0.write(0xab, &[0x00]); +//! # } +//! # } +//! # +//! # fn _example(i2c: impl i2c::Write) { +//! // For example: +//! // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); +//! +//! let bus = shared_bus::BusManagerSimple::new(i2c); +//! +//! let mut proxy1 = bus.acquire_i2c(); +//! let mut my_device = MyDevice::new(bus.acquire_i2c()); +//! +//! proxy1.write(0x39, &[0xc0, 0xff, 0xee]); +//! my_device.do_something_on_the_bus(); //! # } +//! ``` +//! +//! The `BusManager::acquire_*()` methods can be called as often as needed; each call will yield +//! a new bus-proxy of the requested type. //! -//! # let i2c = (); -//! // Create your bus peripheral as usual: +//! # Sharing across multiple tasks/threads +//! For sharing across multiple tasks/threads, synchronization is needed to ensure all bus-accesses +//! are strictly serialized and can't race against each other. The synchronization is handled by +//! a platform-specific [`BusMutex`] implementation. _shared-bus_ already contains some +//! implementations for common targets. For each one, there is also a macro for easily creating +//! a bus-manager with `'static` lifetime, which is almost always a requirement when sharing across +//! task/thread boundaries. As an example: +//! +//! ``` +//! # struct MyDevice(T); +//! # impl MyDevice { +//! # pub fn new(t: T) -> Self { MyDevice(t) } +//! # pub fn do_something_on_the_bus(&mut self) { } +//! # } +//! # +//! # struct SomeI2cBus; +//! # let i2c = SomeI2cBus; +//! // For example: //! // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); //! -//! let manager = shared_bus::BusManager::, _>::new(i2c); +//! // The bus is a 'static reference -> it lives forever and references can be +//! // shared with other threads. +//! let bus: &'static _ = shared_bus::new_std!(SomeI2cBus = i2c).unwrap(); +//! +//! let mut proxy1 = bus.acquire_i2c(); +//! let mut my_device = MyDevice::new(bus.acquire_i2c()); //! -//! // You can now acquire bus handles: -//! let mut handle = manager.acquire(); -//! // handle implements `i2c::{Read, Write, WriteRead}`, depending on the -//! // implementations of the underlying peripheral -//! let mut mydevice = MyDevice::new(manager.acquire()); +//! // We can easily move a proxy to another thread: +//! # let t = +//! std::thread::spawn(move || { +//! my_device.do_something_on_the_bus(); +//! }); +//! # t.join().unwrap(); //! ``` +//! +//! Those platform-specific bits are guarded by a feature that needs to be enabled. Here is an +//! overview of what's already available: +//! +//! | Mutex | Bus Manager | `'static` Bus Macro | Feature Name | +//! | --- | --- | --- | --- | +//! | `std::sync::Mutex` | [`BusManagerStd`] | [`new_std!()`] | `std` | +//! | `cortex_m::interrupt::Mutex` | [`BusManagerCortexM`] | [`new_cortexm!()`] | `cortex-m` | +//! +//! # Supported Busses +//! Currently, the following busses can be shared with _shared-bus_: +//! +//! | Bus | Proxy Type | Acquire Method | Comments | +//! | --- | --- | --- | --- | +//! | I2C | [`I2cProxy`] | [`.acquire_i2c()`] | | +//! | SPI | [`SpiProxy`] | [`.acquire_spi()`] | SPI can only be shared within a single task (See [`SpiProxy`] for details). | +//! +//! +//! [`.acquire_i2c()`]: ./struct.BusManager.html#method.acquire_i2c +//! [`.acquire_spi()`]: ./struct.BusManager.html#method.acquire_spi +//! [`BusManagerCortexM`]: ./type.BusManagerCortexM.html +//! [`BusManagerSimple`]: ./type.BusManagerSimple.html +//! [`BusManagerStd`]: ./type.BusManagerStd.html +//! [`BusMutex`]: ./trait.BusMutex.html +//! [`I2cProxy`]: ./struct.I2cProxy.html +//! [`SpiProxy`]: ./struct.SpiProxy.html +//! [`new_cortexm!()`]: ./macro.new_cortexm.html +//! [`new_std!()`]: ./macro.new_std.html +//! [blog-post]: https://blog.rahix.de/001-shared-bus #![doc(html_root_url = "https://docs.rs/shared-bus")] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "docsrs", feature(extern_prelude))] -#[cfg(feature = "cortexm")] -use cortex_m; -use embedded_hal as hal; +#![warn(missing_docs)] -pub mod mutex; -pub mod proxy; +mod manager; +mod mutex; +mod proxies; +mod macros; +#[doc(hidden)] +#[cfg(feature = "std")] +pub use once_cell; + +#[doc(hidden)] +#[cfg(feature = "cortex-m")] +pub use cortex_m; + +pub use manager::BusManager; pub use mutex::BusMutex; pub use mutex::NullMutex; -pub use proxy::BusManager; -pub use proxy::BusProxy; +#[cfg(feature = "cortex-m")] +pub use mutex::CortexMMutex; +pub use proxies::I2cProxy; +pub use proxies::SpiProxy; -/// Type alias for a bus manager using the [`shared_bus::NullMutex`]. +/// A bus manager for sharing within a single task/thread. +/// +/// This is the bus manager with the least overhead; it should always be used when all bus users +/// are confined to a single task/thread as it has no side-effects (like blocking or turning off +/// interrupts). /// -/// This bus manager can be used when all bus users are contained in a single execution context, -/// i.e. no synchronization between different tasks/threads is needed. -pub type SingleContextBusManager = BusManager, P>; +/// # Example +/// ``` +/// # use embedded_hal::blocking::i2c; +/// # use embedded_hal::blocking::i2c::Write as _; +/// # struct MyDevice(T); +/// # impl MyDevice { +/// # pub fn new(t: T) -> Self { MyDevice(t) } +/// # pub fn do_something_on_the_bus(&mut self) { +/// # self.0.write(0xab, &[0x00]); +/// # } +/// # } +/// # +/// # fn _example(i2c: impl i2c::Write) { +/// // For example: +/// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); +/// +/// let bus = shared_bus::BusManagerSimple::new(i2c); +/// +/// let mut proxy1 = bus.acquire_i2c(); +/// let mut my_device = MyDevice::new(bus.acquire_i2c()); +/// +/// proxy1.write(0x39, &[0xc0, 0xff, 0xee]); +/// my_device.do_something_on_the_bus(); +/// # } +/// ``` +pub type BusManagerSimple = BusManager>; -/// Type alias for a bus manager using [`std::sync::Mutex`]. +/// A bus manager for safely sharing between threads on a platform with `std` support. +/// +/// This manager internally uses a `std::sync::Mutex` for synchronizing bus accesses. As sharing +/// across threads will in most cases require a manager with `'static` lifetime, the +/// [`shared_bus::new_std!()`][new_std] macro exists to create such a bus manager. /// -/// Only available if the `std` feature is active. +/// [new_std]: ./macro.new_std.html +/// +/// This type is only available with the `std` feature. #[cfg(feature = "std")] -pub type StdBusManager = BusManager, P>; +pub type BusManagerStd = BusManager<::std::sync::Mutex>; -/// Type alias for a bus manager using [`cortex_m::interrupt::Mutex`]. +/// A bus manager for safely sharing between tasks on Cortex-M. +/// +/// This manager works by turning off interrupts for each bus transaction which prevents racy +/// accesses from different tasks/execution contexts (e.g. interrupts). Usually, for sharing +/// between tasks, a manager with `'static` lifetime is needed which can be created using the +/// [`shared_bus::new_cortexm!()`][new_cortexm] macro. +/// +/// [new_cortexm]: ./macro.new_cortexm.html /// -/// Only available if the `cortexm` feature is active. -#[cfg(feature = "cortexm")] -pub type CortexMBusManager = BusManager, P>; +/// This type is only available with the `cortex-m` feature. +#[cfg(feature = "cortex-m")] +pub type BusManagerCortexM = BusManager>; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..1c8c782 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,134 @@ +/// Macro for creating a `std`-based bus manager with `'static` lifetime. +/// +/// This macro is a convenience helper for creating a bus manager that lives for the `'static` +/// lifetime an thus can be safely shared across threads. +/// +/// This macro is only available with the `std` feature. +/// +/// # Syntax +/// ```ignore +/// let bus = shared_bus::new_std!( = ).unwrap(); +/// ``` +/// +/// The macro returns an Option which will be `Some(&'static bus_manager)` on the first run and +/// `None` afterwards. This is necessary to uphold safety around the inner `static` variable. +/// +/// # Example +/// ``` +/// # struct MyDevice(T); +/// # impl MyDevice { +/// # pub fn new(t: T) -> Self { MyDevice(t) } +/// # pub fn do_something_on_the_bus(&mut self) { } +/// # } +/// # +/// # struct SomeI2cBus; +/// # let i2c = SomeI2cBus; +/// // For example: +/// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); +/// +/// // The bus is a 'static reference -> it lives forever and references can be +/// // shared with other threads. +/// let bus: &'static _ = shared_bus::new_std!(SomeI2cBus = i2c).unwrap(); +/// +/// let mut proxy1 = bus.acquire_i2c(); +/// let mut my_device = MyDevice::new(bus.acquire_i2c()); +/// +/// // We can easily move a proxy to another thread: +/// # let t = +/// std::thread::spawn(move || { +/// my_device.do_something_on_the_bus(); +/// }); +/// # t.join().unwrap(); +/// ``` +#[cfg(feature = "std")] +#[macro_export] +macro_rules! new_std { + ($bus_type:ty = $bus:expr) => {{ + use $crate::once_cell::sync::OnceCell; + + static MANAGER: OnceCell<$crate::BusManagerStd<$bus_type>> = OnceCell::new(); + + let m = $crate::BusManagerStd::new($bus); + match MANAGER.set(m) { + Ok(_) => MANAGER.get(), + Err(_) => None, + } + }}; +} + +/// Macro for creating a Cortex-M bus manager with `'static` lifetime. +/// +/// This macro is a convenience helper for creating a bus manager that lives for the `'static` +/// lifetime an thus can be safely shared across tasks/execution contexts (like interrupts). +/// +/// This macro is only available with the `cortex-m` feature. +/// +/// # Syntax +/// ```ignore +/// let bus = shared_bus::new_cortexm!( = ).unwrap(); +/// ``` +/// +/// The macro returns an Option which will be `Some(&'static bus_manager)` on the first run and +/// `None` afterwards. This is necessary to uphold safety around the inner `static` variable. +/// +/// # Example +/// ```no_run +/// # use embedded_hal::blocking::i2c::Write; +/// # struct MyDevice(T); +/// # impl MyDevice { +/// # pub fn new(t: T) -> Self { MyDevice(t) } +/// # pub fn do_something_on_the_bus(&mut self) { } +/// # } +/// # +/// # struct SomeI2cBus; +/// # impl Write for SomeI2cBus { +/// # type Error = (); +/// # fn write(&mut self, addr: u8, buffer: &[u8]) -> Result<(), Self::Error> { Ok(()) } +/// # } +/// static mut SHARED_DEVICE: +/// Option>>> +/// = None; +/// +/// fn main() -> ! { +/// # let i2c = SomeI2cBus; +/// // For example: +/// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); +/// +/// // The bus is a 'static reference -> it lives forever and references can be +/// // shared with other tasks. +/// let bus: &'static _ = shared_bus::new_cortexm!(SomeI2cBus = i2c).unwrap(); +/// +/// let mut proxy1 = bus.acquire_i2c(); +/// let my_device = MyDevice::new(bus.acquire_i2c()); +/// +/// unsafe { +/// SHARED_DEVICE = Some(my_device); +/// } +/// +/// cortex_m::asm::dmb(); +/// +/// // enable the interrupt +/// +/// loop { +/// proxy1.write(0x39, &[0xaa]); +/// } +/// } +/// +/// fn INTERRUPT() { +/// let dev = unsafe {SHARED_DEVICE.as_mut().unwrap()}; +/// +/// dev.do_something_on_the_bus(); +/// } +/// ``` +#[cfg(feature = "cortex-m")] +#[macro_export] +macro_rules! new_cortexm { + ($bus_type:ty = $bus:expr) => {{ + let m: Option<&'static mut _> = $crate::cortex_m::singleton!( + : $crate::BusManagerCortexM<$bus_type> = + $crate::BusManagerCortexM::new($bus) + ); + + m + }}; +} diff --git a/src/manager.rs b/src/manager.rs new file mode 100644 index 0000000..397cfab --- /dev/null +++ b/src/manager.rs @@ -0,0 +1,179 @@ +/// "Manager" for a shared bus. +/// +/// The manager owns the original bus peripheral (wrapped inside a mutex) and hands out proxies +/// which can be used by device drivers for accessing the bus. Certain bus proxies can only be +/// created with restrictions (see the individual methods for details). +/// +/// Usually the type-aliases defined in this crate should be used instead of `BusManager` directly. +/// Otherwise, the mutex type needs to be specified explicitly. Here is an overview of aliases +/// (some are only available if a certain feature is enabled): +/// +/// | Bus Manager | Mutex Type | Feature Name | Notes | +/// | --- | --- | --- | --- | +/// | [`BusManagerSimple`] | `shared_bus::NullMutex` | always available | For sharing within a single execution context. | +/// | [`BusManagerStd`] | `std::sync::Mutex` | `std` | For platforms where `std` is available. | +/// | [`BusManagerCortexM`] | `cortex_m::interrupt::Mutex` | `cortex-m` | For Cortex-M platforms; Uses a critcal section (i.e. turns off interrupts during bus transactions). | +/// +/// [`BusManagerSimple`]: ./type.BusManagerSimple.html +/// [`BusManagerStd`]: ./type.BusManagerStd.html +/// [`BusManagerCortexM`]: ./type.BusManagerCortexM.html +/// +/// # Constructing a `BusManager` +/// There are two ways to instanciate a bus manager. Which one to use depends on the kind of +/// sharing that is intended. +/// +/// 1. When all bus users live in the same task/thread, a `BusManagerSimple` can be used: +/// +/// ``` +/// # use embedded_hal::blocking::i2c; +/// # use embedded_hal::blocking::i2c::Write as _; +/// # struct MyDevice(T); +/// # impl MyDevice { +/// # pub fn new(t: T) -> Self { MyDevice(t) } +/// # pub fn do_something_on_the_bus(&mut self) { +/// # self.0.write(0xab, &[0x00]); +/// # } +/// # } +/// # +/// # fn _example(i2c: impl i2c::Write) { +/// // For example: +/// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); +/// +/// let bus = shared_bus::BusManagerSimple::new(i2c); +/// +/// let mut proxy1 = bus.acquire_i2c(); +/// let mut my_device = MyDevice::new(bus.acquire_i2c()); +/// +/// proxy1.write(0x39, &[0xc0, 0xff, 0xee]); +/// my_device.do_something_on_the_bus(); +/// # } +/// ``` +/// +/// 2. When users are in different execution contexts, a proper mutex type is needed and the +/// manager must be made `static` to ensure it lives long enough. For this, `shared-bus` +/// provides a number of macros creating such a `static` instance: +/// +/// ``` +/// # struct MyDevice(T); +/// # impl MyDevice { +/// # pub fn new(t: T) -> Self { MyDevice(t) } +/// # pub fn do_something_on_the_bus(&mut self) { } +/// # } +/// # +/// # struct SomeI2cBus; +/// # let i2c = SomeI2cBus; +/// // For example: +/// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); +/// +/// // The bus is a 'static reference -> it lives forever and references can be +/// // shared with other threads. +/// let bus: &'static _ = shared_bus::new_std!(SomeI2cBus = i2c).unwrap(); +/// +/// let mut proxy1 = bus.acquire_i2c(); +/// let mut my_device = MyDevice::new(bus.acquire_i2c()); +/// +/// // We can easily move a proxy to another thread: +/// # let t = +/// std::thread::spawn(move || { +/// my_device.do_something_on_the_bus(); +/// }); +/// # t.join().unwrap(); +/// ``` +/// +/// For other platforms, similar macros exist (e.g. [`new_cortexm!()`]). +/// +/// [`new_cortexm!()`]: ./macro.new_cortexm.html +#[derive(Debug)] +pub struct BusManager { + mutex: M, +} + +impl BusManager { + /// Create a new bus manager for a bus. + /// + /// See the documentation for `BusManager` for more details. + pub fn new(bus: M::Bus) -> Self { + let mutex = M::create(bus); + + BusManager { mutex } + } +} + +impl BusManager { + /// Acquire an [`I2cProxy`] for this bus. + /// + /// [`I2cProxy`]: ./struct.I2cProxy.html + /// + /// The returned proxy object can then be used for accessing the bus by e.g. a driver: + /// + /// ``` + /// # use embedded_hal::blocking::i2c; + /// # use embedded_hal::blocking::i2c::Write as _; + /// # struct MyDevice(T); + /// # impl MyDevice { + /// # pub fn new(t: T) -> Self { MyDevice(t) } + /// # pub fn do_something_on_the_bus(&mut self) { + /// # self.0.write(0xab, &[0x00]); + /// # } + /// # } + /// # + /// # fn _example(i2c: impl i2c::Write) { + /// let bus = shared_bus::BusManagerSimple::new(i2c); + /// + /// let mut proxy1 = bus.acquire_i2c(); + /// let mut my_device = MyDevice::new(bus.acquire_i2c()); + /// + /// proxy1.write(0x39, &[0xc0, 0xff, 0xee]); + /// my_device.do_something_on_the_bus(); + /// # } + /// ``` + pub fn acquire_i2c<'a>(&'a self) -> crate::I2cProxy<'a, M> { + crate::I2cProxy { mutex: &self.mutex } + } +} + +impl BusManager> { + /// Acquire an [`SpiProxy`] for this bus. + /// + /// **Note**: SPI Proxies can only be created from [`BusManagerSimple`] (= bus managers using + /// the [`NullMutex`]). See [`SpiProxy`] for more details why. + /// + /// [`BusManagerSimple`]: ./type.BusManagerSimple.html + /// [`NullMutex`]: ./struct.NullMutex.html + /// [`SpiProxy`]: ./struct.SpiProxy.html + /// + /// The returned proxy object can then be used for accessing the bus by e.g. a driver: + /// + /// ``` + /// # use embedded_hal::blocking::spi; + /// # use embedded_hal::digital::v2; + /// # use embedded_hal::blocking::spi::Write as _; + /// # struct MyDevice(T); + /// # impl> MyDevice { + /// # pub fn new(t: T) -> Self { MyDevice(t) } + /// # pub fn do_something_on_the_bus(&mut self) { + /// # self.0.write(&[0x00]); + /// # } + /// # } + /// # + /// # fn _example(mut cs1: impl v2::OutputPin, spi: impl spi::Write) { + /// let bus = shared_bus::BusManagerSimple::new(spi); + /// + /// let mut proxy1 = bus.acquire_spi(); + /// let mut my_device = MyDevice::new(bus.acquire_spi()); + /// + /// // Chip-select needs to be managed manually + /// cs1.set_high(); + /// proxy1.write(&[0xc0, 0xff, 0xee]); + /// cs1.set_low(); + /// + /// my_device.do_something_on_the_bus(); + /// # } + /// ``` + pub fn acquire_spi<'a>(&'a self) -> crate::SpiProxy<'a, crate::NullMutex> { + crate::SpiProxy { + mutex: &self.mutex, + _u: core::marker::PhantomData, + } + } +} diff --git a/src/mutex.rs b/src/mutex.rs index c06c8be..ff63224 100644 --- a/src/mutex.rs +++ b/src/mutex.rs @@ -1,84 +1,143 @@ -/// An abstraction over a mutex lock. +use core::cell; + +/// Common interface for mutex implementations. +/// +/// `shared-bus` needs a mutex to ensure only a single device can access the bus at the same time +/// in concurrent situations. `shared-bus` already implements this trait for a number of existing +/// mutex types. Most of them are guarded by a feature that needs to be enabled. Here is an +/// overview: +/// +/// | Mutex | Feature Name | Notes | +/// | --- | --- | --- | +/// | [`shared_bus::NullMutex`][null-mutex] | always available | For sharing within a single execution context. | +/// | [`std::sync::Mutex`][std-mutex] | `std` | For platforms where `std` is available. | +/// | [`cortex_m::interrupt::Mutex`][cortexm-mutex] | `cortex-m` | For Cortex-M platforms; Uses a critcal section (i.e. turns off interrupts during bus transactions). | /// -/// Any type that can implement this trait can be used as a mutex for sharing a bus. +/// [null-mutex]: ./struct.NullMutex.html +/// [std-mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html +/// [cortexm-mutex]: https://docs.rs/cortex-m/0.6.3/cortex_m/interrupt/struct.Mutex.html /// -/// If the `std` feature is enabled, [`BusMutex`] is implemented for [`std::sync::Mutex`]. -/// If the `cortexm` feature is enabled, [`BusMutex`] is implemented for [`cortex_m::interrupt::Mutex`]. +/// For other mutex types, a custom implementation is needed. Due to the orphan rule, it might be +/// necessary to wrap it in a newtype. As an example, this is what such a custom implementation +/// might look like: /// -/// If there is no feature available for your Mutex type, you have to write one yourself. It should -/// look something like this (for [`cortex_m`] as an example): /// ``` -/// use shared_bus; -/// use cortex_m; +/// struct MyMutex(std::sync::Mutex); /// -/// // You need a newtype because you can't implement foreign traits on -/// // foreign types. -/// struct MyMutex(cortex_m::interrupt::Mutex); +/// impl shared_bus::BusMutex for MyMutex { +/// type Bus = T; /// -/// impl shared_bus::BusMutex for MyMutex { /// fn create(v: T) -> Self { -/// Self(cortex_m::interrupt::Mutex::new(v)) +/// Self(std::sync::Mutex::new(v)) /// } /// -/// fn lock R>(&self, f: F) -> R { -/// cortex_m::interrupt::free(|cs| { -/// let v = self.0.borrow(cs); -/// f(v) -/// }) +/// fn lock R>(&self, f: F) -> R { +/// let mut v = self.0.lock().unwrap(); +/// f(&mut v) /// } /// } /// -/// type MyBusManager = shared_bus::BusManager, P>; +/// // It is also beneficial to define a type alias for the BusManager +/// type BusManagerCustom = shared_bus::BusManager>; /// ``` -pub trait BusMutex { - /// Create a new instance of this mutex type containing the value `v`. - fn create(v: T) -> Self; +pub trait BusMutex { + /// The actual bus that is wrapped inside this mutex. + type Bus; - /// Lock the mutex for the duration of the closure `f`. - fn lock R>(&self, f: F) -> R; + /// Create a new mutex of this type. + fn create(v: Self::Bus) -> Self; + + /// Lock the mutex and give a closure access to the bus inside. + fn lock R>(&self, f: F) -> R; } -/// A dummy mutex for bus-sharing in a single task. +/// "Dummy" mutex for sharing in a single task/thread. /// /// This mutex type can be used when all bus users are contained in a single execution context. In /// such a situation, no actual mutex is needed, because a RefCell alone is sufficient to ensuring /// only a single peripheral can access the bus at the same time. /// +/// This mutex type is used with the [`BusManagerSimple`] type. +/// /// To uphold safety, this type is `!Send` and `!Sync`. -pub struct NullMutex(T); +/// +/// [`BusManagerSimple`]: ./type.BusManagerSimple.html +#[derive(Debug)] +pub struct NullMutex { + bus: cell::RefCell, +} -impl BusMutex for NullMutex { - fn create(v: T) -> Self { - NullMutex(v) +impl BusMutex for NullMutex { + type Bus = T; + + fn create(v: Self::Bus) -> Self { + NullMutex { + bus: cell::RefCell::new(v) + } } - fn lock R>(&self, f: F) -> R { - f(&self.0) + fn lock R>(&self, f: F) -> R { + let mut v = self.bus.borrow_mut(); + f(&mut v) } } #[cfg(feature = "std")] -impl BusMutex for ::std::sync::Mutex { - fn create(v: T) -> Self { +impl BusMutex for ::std::sync::Mutex { + type Bus = T; + + fn create(v: Self::Bus) -> Self { ::std::sync::Mutex::new(v) } - fn lock R>(&self, f: F) -> R { - let v = self.lock().unwrap(); - f(&v) + fn lock R>(&self, f: F) -> R { + let mut v = self.lock().unwrap(); + f(&mut v) } } -#[cfg(feature = "cortexm")] -impl BusMutex for ::cortex_m::interrupt::Mutex { - fn create(v: T) -> ::cortex_m::interrupt::Mutex { - ::cortex_m::interrupt::Mutex::new(v) +/// Alias for a Cortex-M mutex. +/// +/// Based on [`cortex_m::interrupt::Mutex`][cortexm-mutex]. This mutex works by disabling +/// interrupts while the mutex is locked. +/// +/// [cortexm-mutex]: https://docs.rs/cortex-m/0.6.3/cortex_m/interrupt/struct.Mutex.html +/// +/// This type is only available with the `cortex-m` feature. +#[cfg(feature = "cortex-m")] +pub type CortexMMutex = cortex_m::interrupt::Mutex>; + +#[cfg(feature = "cortex-m")] +impl BusMutex for CortexMMutex { + type Bus = T; + + fn create(v: T) -> Self { + cortex_m::interrupt::Mutex::new(cell::RefCell::new(v)) } - fn lock R>(&self, f: F) -> R { - ::cortex_m::interrupt::free(|cs| { - let v = self.borrow(cs); - f(v) + fn lock R>(&self, f: F) -> R { + cortex_m::interrupt::free(|cs| { + let c = self.borrow(cs); + f(&mut c.borrow_mut()) }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn std_mutex_api_test() { + let t = "hello ".to_string(); + let m: std::sync::Mutex<_> = BusMutex::create(t); + + BusMutex::lock(&m, |s| { + s.push_str("world"); + }); + + BusMutex::lock(&m, |s| { + assert_eq!("hello world", s); + }); + } +} diff --git a/src/proxies.rs b/src/proxies.rs new file mode 100644 index 0000000..ce4c531 --- /dev/null +++ b/src/proxies.rs @@ -0,0 +1,96 @@ +use embedded_hal::blocking::i2c; +use embedded_hal::blocking::spi; + +/// Proxy type for I2C bus sharing. +/// +/// The `I2cProxy` implements all (blocking) I2C traits so it can be passed to drivers instead of +/// the bus instance. Internally, it holds reference to the bus via a mutex, ensuring that all +/// accesses are strictly synchronized. +/// +/// An `I2cProxy` is created by calling [`BusManager::acquire_i2c()`][acquire_i2c]. +/// +/// [acquire_i2c]: ./struct.BusManager.html#method.acquire_i2c +#[derive(Debug)] +pub struct I2cProxy<'a, M: crate::BusMutex> { + pub(crate) mutex: &'a M, +} + +impl<'a, M: crate::BusMutex> i2c::Write for I2cProxy<'a, M> +where + M::Bus: i2c::Write, +{ + type Error = ::Error; + + fn write(&mut self, addr: u8, buffer: &[u8]) -> Result<(), Self::Error> { + self.mutex.lock(|bus| bus.write(addr, buffer)) + } +} + +impl<'a, M: crate::BusMutex> i2c::Read for I2cProxy<'a, M> +where + M::Bus: i2c::Read, +{ + type Error = ::Error; + + fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.mutex.lock(|bus| bus.read(addr, buffer)) + } +} + +impl<'a, M: crate::BusMutex> i2c::WriteRead for I2cProxy<'a, M> +where + M::Bus: i2c::WriteRead, +{ + type Error = ::Error; + + fn write_read( + &mut self, + addr: u8, + buffer_in: &[u8], + buffer_out: &mut [u8], + ) -> Result<(), Self::Error> { + self.mutex.lock(|bus| bus.write_read(addr, buffer_in, buffer_out)) + } +} + + + +/// Proxy type for SPI bus sharing. +/// +/// The `SpiProxy` implements all (blocking) SPI traits so it can be passed to drivers instead of +/// the bus instance. An `SpiProxy` is created by calling [`BusManager::acquire_spi()`][acquire_spi]. +/// +/// **Note**: The `SpiProxy` can only be used for sharing **withing a single task/thread**. This +/// is due to drivers usually managing the chip-select pin manually which would be inherently racy +/// in a concurrent environment (because the mutex is locked only after asserting CS). To ensure +/// safe usage, a `SpiProxy` can only be created when using [`BusManagerSimple`] and is `!Send`. +/// +/// [acquire_spi]: ./struct.BusManager.html#method.acquire_spi +/// [`BusManagerSimple`]: ./type.BusManagerSimple.html +#[derive(Debug)] +pub struct SpiProxy<'a, M: crate::BusMutex> { + pub(crate) mutex: &'a M, + pub(crate) _u: core::marker::PhantomData<*mut ()>, +} + +impl<'a, M: crate::BusMutex> spi::Transfer for SpiProxy<'a, M> +where + M::Bus: spi::Transfer, +{ + type Error = >::Error; + + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.mutex.lock(move |bus| bus.transfer(words)) + } +} + +impl<'a, M: crate::BusMutex> spi::Write for SpiProxy<'a, M> +where + M::Bus: spi::Write, +{ + type Error = >::Error; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.mutex.lock(|bus| bus.write(words)) + } +} diff --git a/src/proxy.rs b/src/proxy.rs deleted file mode 100644 index 844249a..0000000 --- a/src/proxy.rs +++ /dev/null @@ -1,153 +0,0 @@ -use hal::blocking::i2c; -use hal::blocking::spi; - -use super::*; - -#[cfg(feature = "std")] -use std::cell; - -#[cfg(not(feature = "std"))] -use core::cell; - -#[cfg(feature = "std")] -use std::marker; - -#[cfg(not(feature = "std"))] -use core::marker; - -/// A manager for managing a shared bus. -/// -/// The manager owns the actual peripheral and hands out proxies which can be -/// used by your devices. -/// -/// When creating a bus manager you need to specify which -/// mutex type should be used. -/// -/// # Examples -/// ``` -/// # use shared_bus; -/// # use shared_bus::BusManager; -/// # struct MyDevice; -/// # impl MyDevice { -/// # pub fn new(t: T) -> MyDevice { MyDevice } -/// # } -/// -/// # let i2c = (); -/// // For example: -/// // let i2c = I2c::i2c1(dp.I2C1, (scl, sda), 90.khz(), clocks, &mut rcc.apb1); -/// -/// let manager = BusManager::, _>::new(i2c); -/// -/// // You can now acquire bus handles: -/// let mut handle1 = manager.acquire(); -/// let mut mydevice = MyDevice::new(manager.acquire()); -/// ``` -pub struct BusManager>, T>(M, marker::PhantomData); - -impl>, T> BusManager { - /// Create a new manager for a bus peripheral `d`. - /// - /// When creating the manager you need to specify which mutex type should be used: - /// ``` - /// # extern crate shared_bus; - /// # use shared_bus::BusManager; - /// # let bus = (); - /// let manager = BusManager::, _>::new(bus); - /// ``` - pub fn new(d: T) -> BusManager { - let mutex = M::create(cell::RefCell::new(d)); - - BusManager(mutex, marker::PhantomData) - } - - /// Acquire a proxy for this bus. - pub fn acquire<'a>(&'a self) -> BusProxy<'a, M, T> { - BusProxy(&self.0, marker::PhantomData) - } -} - -/// A proxy type that can be used instead of an actual bus peripheral. -/// -/// `BusProxy` implements all bus traits and can thus be used in place of the -/// actual bus peripheral. -/// -/// `BusProxies` are created by calling [`BusManager::acquire`] -pub struct BusProxy<'a, M: 'a + mutex::BusMutex>, T>( - &'a M, - marker::PhantomData, -); - -impl<'a, M, I2C: i2c::Write> i2c::Write for BusProxy<'a, M, I2C> -where - M: 'a + mutex::BusMutex>, -{ - type Error = I2C::Error; - - fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { - self.0.lock(|lock| { - let mut i = lock.borrow_mut(); - i.write(addr, bytes) - }) - } -} - -impl<'a, M, I2C: i2c::Read> i2c::Read for BusProxy<'a, M, I2C> -where - M: 'a + mutex::BusMutex>, -{ - type Error = I2C::Error; - - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - self.0.lock(|lock| { - let mut i = lock.borrow_mut(); - i.read(address, buffer) - }) - } -} - -impl<'a, M, I2C: i2c::WriteRead> i2c::WriteRead for BusProxy<'a, M, I2C> -where - M: 'a + mutex::BusMutex>, -{ - type Error = I2C::Error; - - fn write_read( - &mut self, - address: u8, - bytes: &[u8], - buffer: &mut [u8], - ) -> Result<(), Self::Error> { - self.0.lock(|lock| { - let mut i = lock.borrow_mut(); - i.write_read(address, bytes, buffer) - }) - } -} - -impl<'a, M, SPI: spi::Transfer> spi::Transfer for BusProxy<'a, M, SPI> -where - M: 'a + mutex::BusMutex>, -{ - type Error = SPI::Error; - - fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { - self.0.lock(move |lock| { - let mut i = lock.borrow_mut(); - i.transfer(words) - }) - } -} - -impl<'a, M, SPI: spi::Write> spi::Write for BusProxy<'a, M, SPI> -where - M: 'a + mutex::BusMutex>, -{ - type Error = SPI::Error; - - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.0.lock(|lock| { - let mut i = lock.borrow_mut(); - i.write(words) - }) - } -} diff --git a/tests/i2c.rs b/tests/i2c.rs new file mode 100644 index 0000000..013ef56 --- /dev/null +++ b/tests/i2c.rs @@ -0,0 +1,117 @@ +use embedded_hal::prelude::*; +use embedded_hal_mock::i2c; +use std::thread; + +#[test] +fn fake_i2c_device() { + let expect = vec![i2c::Transaction::write(0xc0, vec![0xff, 0xee])]; + let mut device = i2c::Mock::new(&expect); + device.write(0xc0, &[0xff, 0xee]).unwrap(); + device.done() +} + +#[test] +fn i2c_manager_manual() { + let expect = vec![ + i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef]), + ]; + let mut device = i2c::Mock::new(&expect); + let manager = shared_bus::BusManagerSimple::new(device.clone()); + let mut proxy = manager.acquire_i2c(); + + proxy.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); + + device.done(); +} + +#[test] +fn i2c_manager_macro() { + let expect = vec![ + i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef]), + ]; + let mut device = i2c::Mock::new(&expect); + let manager: &'static shared_bus::BusManagerStd<_> = + shared_bus::new_std!(i2c::Mock = device.clone()).unwrap(); + let mut proxy = manager.acquire_i2c(); + + proxy.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); + + device.done(); +} + +#[test] +fn i2c_proxy() { + let expect = vec![ + i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef]), + i2c::Transaction::read(0xef, vec![0xbe, 0xad, 0xde]), + i2c::Transaction::write_read(0x44, vec![0x01, 0x02], vec![0x03, 0x04]), + ]; + let mut device = i2c::Mock::new(&expect); + + let manager = shared_bus::BusManagerSimple::new(device.clone()); + let mut proxy = manager.acquire_i2c(); + + proxy.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); + + let mut buf = [0u8; 3]; + proxy.read(0xef, &mut buf).unwrap(); + assert_eq!(&buf, &[0xbe, 0xad, 0xde]); + + let mut buf = [0u8; 2]; + proxy.write_read(0x44, &[0x01, 0x02], &mut buf).unwrap(); + assert_eq!(&buf, &[0x03, 0x04]); + + device.done(); +} + +#[test] +fn i2c_multi() { + let expect = vec![ + i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef]), + i2c::Transaction::read(0xef, vec![0xbe, 0xad, 0xde]), + i2c::Transaction::write_read(0x44, vec![0x01, 0x02], vec![0x03, 0x04]), + ]; + let mut device = i2c::Mock::new(&expect); + + let manager = shared_bus::BusManagerSimple::new(device.clone()); + let mut proxy1 = manager.acquire_i2c(); + let mut proxy2 = manager.acquire_i2c(); + let mut proxy3 = manager.acquire_i2c(); + + proxy1.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); + + let mut buf = [0u8; 3]; + proxy2.read(0xef, &mut buf).unwrap(); + assert_eq!(&buf, &[0xbe, 0xad, 0xde]); + + let mut buf = [0u8; 2]; + proxy3.write_read(0x44, &[0x01, 0x02], &mut buf).unwrap(); + assert_eq!(&buf, &[0x03, 0x04]); + + device.done(); +} + +#[test] +fn i2c_concurrent() { + let expect = vec![ + i2c::Transaction::write(0xde, vec![0xad, 0xbe, 0xef]), + i2c::Transaction::read(0xef, vec![0xbe, 0xad, 0xde]), + ]; + let mut device = i2c::Mock::new(&expect); + + let manager = shared_bus::new_std!(i2c::Mock = device.clone()).unwrap(); + let mut proxy1 = manager.acquire_i2c(); + let mut proxy2 = manager.acquire_i2c(); + + thread::spawn(move || { + proxy1.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); + }).join().unwrap(); + + thread::spawn(move || { + let mut buf = [0u8; 3]; + proxy2.read(0xef, &mut buf).unwrap(); + assert_eq!(&buf, &[0xbe, 0xad, 0xde]); + }).join().unwrap(); + + device.done(); +} diff --git a/tests/shared_bus.rs b/tests/shared_bus.rs deleted file mode 100644 index e4e241a..0000000 --- a/tests/shared_bus.rs +++ /dev/null @@ -1,109 +0,0 @@ -use embedded_hal as hal; -use embedded_hal_mock as hal_mock; -use shared_bus; - -use hal::blocking::i2c::Write; -use hal::blocking::spi::Write as SPIWrite; - -use hal_mock::i2c::{Mock as I2cMock, Transaction as I2cTransaction}; -use hal_mock::spi::{Mock as SpiMock, Transaction as SpiTransaction}; - -#[test] -fn fake_i2c_device() { - let expect = vec![I2cTransaction::write(0xc0, vec![0xff, 0xee])]; - let mut device = I2cMock::new(&expect); - device.write(0xc0, &[0xff, 0xee]).unwrap(); - device.done() -} - -#[test] -fn fake_spi_device() { - let expect = vec![SpiTransaction::write(vec![0xff, 0xee])]; - let mut device = SpiMock::new(&expect); - device.write(&[0xff, 0xee]).unwrap(); - device.done() -} - -#[test] -fn spi_manager() { - let expect = vec![]; - let mut device = SpiMock::new(&expect); - let _manager = shared_bus::StdBusManager::new(device.clone()); - device.done(); -} - -#[test] -fn i2c_manager() { - let expect = vec![]; - let mut device = I2cMock::new(&expect); - let _manager = shared_bus::StdBusManager::new(device.clone()); - device.done(); -} - -#[test] -fn i2c_proxy() { - let expect = vec![I2cTransaction::write(0xde, vec![0xad, 0xbe, 0xef])]; - let mut device = I2cMock::new(&expect); - - let manager = shared_bus::StdBusManager::new(device.clone()); - let mut proxy = manager.acquire(); - - proxy.write(0xde, &[0xad, 0xbe, 0xef]).unwrap(); - - device.done(); -} - -#[test] -fn spi_proxy() { - let expect = vec![SpiTransaction::write(vec![0xde, 0xad, 0xbe, 0xef])]; - let mut device = SpiMock::new(&expect); - - let manager = shared_bus::StdBusManager::new(device.clone()); - let mut proxy = manager.acquire(); - - proxy.write(&[0xde, 0xad, 0xbe, 0xef]).unwrap(); - - device.done(); -} - -#[test] -fn multiple_proxies() { - let expect = vec![ - I2cTransaction::write(0x0a, vec![0xab, 0xcd]), - I2cTransaction::write(0x0b, vec![0x01, 0x23]), - I2cTransaction::write(0x0a, vec![0x00, 0xff]), - ]; - let mut device = I2cMock::new(&expect); - - let manager = shared_bus::StdBusManager::new(device.clone()); - - let mut proxy1 = manager.acquire(); - let mut proxy2 = manager.acquire(); - - proxy1.write(0x0A, &[0xab, 0xcd]).unwrap(); - proxy2.write(0x0B, &[0x01, 0x23]).unwrap(); - proxy1.write(0x0A, &[0x00, 0xFF]).unwrap(); - - device.done() -} - -#[test] -fn null_manager() { - let expect = vec![ - I2cTransaction::write(0x0a, vec![0xab, 0xcd]), - I2cTransaction::write(0x0b, vec![0x01, 0x23]), - I2cTransaction::write(0x0a, vec![0x00, 0xff]), - ]; - let mut device = I2cMock::new(&expect); - - let manager = shared_bus::SingleContextBusManager::new(device.clone()); - - let mut proxy1 = manager.acquire(); - let mut proxy2 = manager.acquire(); - - proxy1.write(0x0A, &[0xab, 0xcd]).unwrap(); - proxy2.write(0x0B, &[0x01, 0x23]).unwrap(); - proxy1.write(0x0A, &[0x00, 0xFF]).unwrap(); - - device.done() -} diff --git a/tests/spi.rs b/tests/spi.rs new file mode 100644 index 0000000..6df30a7 --- /dev/null +++ b/tests/spi.rs @@ -0,0 +1,63 @@ +use embedded_hal::prelude::*; +use embedded_hal_mock::spi; + +#[test] +fn fake_spi_device() { + let expect = vec![spi::Transaction::write(vec![0xff, 0xee])]; + let mut device = spi::Mock::new(&expect); + device.write(&[0xff, 0xee]).unwrap(); + device.done() +} + +#[test] +fn spi_manager_manual() { + let expect = vec![ + spi::Transaction::write(vec![0xab, 0xcd, 0xef]), + ]; + let mut device = spi::Mock::new(&expect); + let manager = shared_bus::BusManagerSimple::new(device.clone()); + let mut proxy = manager.acquire_spi(); + + proxy.write(&[0xab, 0xcd, 0xef]).unwrap(); + + device.done(); +} + +#[test] +fn spi_proxy() { + let expect = vec![ + spi::Transaction::write(vec![0xab, 0xcd, 0xef]), + spi::Transaction::transfer(vec![0x01, 0x02], vec![0x03, 0x04]), + ]; + let mut device = spi::Mock::new(&expect); + let manager = shared_bus::BusManagerSimple::new(device.clone()); + let mut proxy = manager.acquire_spi(); + + proxy.write(&[0xab, 0xcd, 0xef]).unwrap(); + + let mut buf = vec![0x01, 0x02]; + proxy.transfer(&mut buf).unwrap(); + assert_eq!(&buf, &[0x03, 0x04]); + + device.done(); +} + +#[test] +fn spi_multi() { + let expect = vec![ + spi::Transaction::write(vec![0xab, 0xcd, 0xef]), + spi::Transaction::transfer(vec![0x01, 0x02], vec![0x03, 0x04]), + ]; + let mut device = spi::Mock::new(&expect); + let manager = shared_bus::BusManagerSimple::new(device.clone()); + let mut proxy1 = manager.acquire_spi(); + let mut proxy2 = manager.acquire_spi(); + + proxy1.write(&[0xab, 0xcd, 0xef]).unwrap(); + + let mut buf = vec![0x01, 0x02]; + proxy2.transfer(&mut buf).unwrap(); + assert_eq!(&buf, &[0x03, 0x04]); + + device.done(); +}