diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index fec8c2e0d..9fa0c3bed 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -15,4 +15,4 @@ jobs: # Use a pinned version to avoid spontaneous breakages (new clippy lints are added often) toolchain: nightly-2022-11-22 components: clippy - - run: cargo clippy -- --deny=warnings \ No newline at end of file + - run: cargo clippy --features=embedded-hal-bus/std -- --deny=warnings \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a12eeb6c..ff0013f53 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,17 +13,17 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - # All generated code should be running on stable now rust: - stable - 1.59.0 # MSRV - nightly - - # The default target we're compiling on and for target: - x86_64-unknown-linux-gnu - thumbv6m-none-eabi - thumbv7m-none-eabi + include: + - target: x86_64-unknown-linux-gnu + features: embedded-hal-bus/std steps: - uses: actions/checkout@v3 @@ -35,7 +35,7 @@ jobs: - run: sed -i '/nightly-only/d' Cargo.toml if: matrix.rust != 'nightly' - - run: cargo check --target=${{ matrix.target }} + - run: cargo check --target=${{ matrix.target }} --features=${{ matrix.features }} - - run: cargo test --target=${{ matrix.target }} + - run: cargo test --target=${{ matrix.target }} --features=${{ matrix.features }} if: contains(matrix.target, 'linux') diff --git a/embedded-hal-bus/CHANGELOG.md b/embedded-hal-bus/CHANGELOG.md index c43d3898e..a173d3769 100644 --- a/embedded-hal-bus/CHANGELOG.md +++ b/embedded-hal-bus/CHANGELOG.md @@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -... +### Added +- i2c: add bus sharing implementations. ## [v0.1.0-alpha.1] - 2022-09-28 diff --git a/embedded-hal-bus/Cargo.toml b/embedded-hal-bus/Cargo.toml index 54a9396e9..94d471848 100644 --- a/embedded-hal-bus/Cargo.toml +++ b/embedded-hal-bus/Cargo.toml @@ -13,5 +13,9 @@ readme = "README.md" repository = "https://github.com/rust-embedded/embedded-hal" version = "0.1.0-alpha.1" +[features] +std = [] + [dependencies] embedded-hal = { version = "=1.0.0-alpha.9", path = "../embedded-hal" } +critical-section = { version = "1.0" } diff --git a/embedded-hal-bus/README.md b/embedded-hal-bus/README.md index a94b03874..16ee78842 100644 --- a/embedded-hal-bus/README.md +++ b/embedded-hal-bus/README.md @@ -5,41 +5,47 @@ # `embedded-hal-bus` -Bus/Device connection mechanisms for [`embedded-hal`](https://crates.io/crates/embedded-hal), a Hardware Abstraction Layer (HAL) for embedded systems. +Bus sharing utilities for [`embedded-hal`](https://crates.io/crates/embedded-hal), a Hardware Abstraction Layer (HAL) for embedded systems. -It is possible to connect several peripherals to a bus like SPI or I2C. -To support this, `embedded-hal` provides the `SpiBus` and `SpiDevice` traits in the case of SPI, for example. +`embedded-hal` provides traits for SPI and I2C buses and devices. This crate provides hardware-independent adapters for sharing a single bus between multiple devices, compatible with the traits. -`embedded-hal` trait implementations for microcontrollers should implement the `...Bus` traits. -However, device drivers should use the `...Device` traits, _not the `...Bus` traits_ if at all possible +This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). + +## SPI + +To support bus sharing, `embedded-hal` provides the `SpiBus` and `SpiDevice` traits. `SpiBus` represents an entire bus, +while `SpiDevice` represents a device on that bus. For further details on these traits, please consult the +[`embedded-hal` documentation](https://docs.rs/embedded-hal/1.0.0-alpha.9/embedded_hal/spi/index.html). + +`embedded-hal` trait implementations for microcontrollers should implement the `SpiBus` trait. +However, device drivers should use the `SpiDevice` traits, _not the `SpiBus` traits_ if at all possible in order to allow for sharing of the bus they are connected to. -This crate provides mechanisms to connect a `...Bus` and a `...Device`. +This crate provides mechanisms to connect a `SpiBus` and a `SpiDevice`. -For further details on these traits, please consult the [`embedded-hal` documentation](https://docs.rs/embedded-hal). +## I2C -This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team). +In the case of I2C, the same `I2c` `embedded-hal` trait represents either an entire bus, or a device on a bus. This crate +provides mechanisms to obtain multiple `I2c` instances out of a single `I2c` instance, sharing the bus. -## [API reference] +## Features -[API reference]: https://docs.rs/embedded-hal-bus +- `std`: enable shared bus implementations using `std::sync::Mutex`. ## Minimum Supported Rust Version (MSRV) - This crate is guaranteed to compile on stable Rust 1.59 and up. It *might* compile with older versions but that may change in any new patch release. See [here](../docs/msrv.md) for details on how the MSRV may be upgraded. - ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. diff --git a/embedded-hal-bus/src/i2c/critical_section.rs b/embedded-hal-bus/src/i2c/critical_section.rs new file mode 100644 index 000000000..32334cbcc --- /dev/null +++ b/embedded-hal-bus/src/i2c/critical_section.rs @@ -0,0 +1,70 @@ +use core::cell::RefCell; +use critical_section::Mutex; +use embedded_hal::i2c::{ErrorType, I2c}; + +/// `critical-section`-based shared bus [`I2c`] implementation. +/// +/// Sharing is implemented with a `critical-section` [`Mutex`](critical_section::Mutex). A critical section is taken for +/// the entire duration of a transaction. This allows sharing a single bus across multiple threads (interrupt priority levels). +/// The downside is critical sections typically require globally disabling interrupts, so `CriticalSectionDevice` will likely +/// negatively impact real-time properties, such as interrupt latency. If you can, prefer using +/// [`RefCellDevice`](super::RefCellDevice) instead, which does not require taking critical sections. +pub struct CriticalSectionDevice<'a, T> { + bus: &'a Mutex>, +} + +impl<'a, T> CriticalSectionDevice<'a, T> { + /// Create a new `CriticalSectionDevice` + pub fn new(bus: &'a Mutex>) -> Self { + Self { bus } + } +} + +impl<'a, T> ErrorType for CriticalSectionDevice<'a, T> +where + T: I2c, +{ + type Error = T::Error; +} + +impl<'a, T> I2c for CriticalSectionDevice<'a, T> +where + T: I2c, +{ + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + critical_section::with(|cs| { + let bus = &mut *self.bus.borrow_ref_mut(cs); + bus.read(address, read) + }) + } + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + critical_section::with(|cs| { + let bus = &mut *self.bus.borrow_ref_mut(cs); + bus.write(address, write) + }) + } + + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + critical_section::with(|cs| { + let bus = &mut *self.bus.borrow_ref_mut(cs); + bus.write_read(address, write, read) + }) + } + + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + critical_section::with(|cs| { + let bus = &mut *self.bus.borrow_ref_mut(cs); + bus.transaction(address, operations) + }) + } +} diff --git a/embedded-hal-bus/src/i2c/mod.rs b/embedded-hal-bus/src/i2c/mod.rs new file mode 100644 index 000000000..8eaf2882f --- /dev/null +++ b/embedded-hal-bus/src/i2c/mod.rs @@ -0,0 +1,10 @@ +//! `I2c` shared bus implementations. + +mod refcell; +pub use refcell::*; +#[cfg(feature = "std")] +mod mutex; +#[cfg(feature = "std")] +pub use mutex::*; +mod critical_section; +pub use self::critical_section::*; diff --git a/embedded-hal-bus/src/i2c/mutex.rs b/embedded-hal-bus/src/i2c/mutex.rs new file mode 100644 index 000000000..56f922c8c --- /dev/null +++ b/embedded-hal-bus/src/i2c/mutex.rs @@ -0,0 +1,59 @@ +use embedded_hal::i2c::{ErrorType, I2c}; +use std::sync::Mutex; + +/// `std` `Mutex`-based shared bus [`I2c`] implementation. +/// +/// Sharing is implemented with an `std` [`Mutex`](std::sync::Mutex). It allows a single bus across multiple threads, +/// with finer-grained locking than [`CriticalSectionDevice`](super::CriticalSectionDevice). The downside is that +/// it is only available in `std` targets. +pub struct MutexDevice<'a, T> { + bus: &'a Mutex, +} + +impl<'a, T> MutexDevice<'a, T> { + /// Create a new `MutexDevice` + pub fn new(bus: &'a Mutex) -> Self { + Self { bus } + } +} + +impl<'a, T> ErrorType for MutexDevice<'a, T> +where + T: I2c, +{ + type Error = T::Error; +} + +impl<'a, T> I2c for MutexDevice<'a, T> +where + T: I2c, +{ + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock().unwrap(); + bus.read(address, read) + } + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock().unwrap(); + bus.write(address, write) + } + + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock().unwrap(); + bus.write_read(address, write, read) + } + + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.lock().unwrap(); + bus.transaction(address, operations) + } +} diff --git a/embedded-hal-bus/src/i2c/refcell.rs b/embedded-hal-bus/src/i2c/refcell.rs new file mode 100644 index 000000000..cc72df692 --- /dev/null +++ b/embedded-hal-bus/src/i2c/refcell.rs @@ -0,0 +1,59 @@ +use core::cell::RefCell; +use embedded_hal::i2c::{ErrorType, I2c}; + +/// `RefCell`-based shared bus [`I2c`] implementation. +/// +/// Sharing is implemented with a `RefCell`. This means it has low overhead, but `RefCellDevice` instances are not `Send`, +/// so it only allows sharing within a single thread (interrupt priority level). If you need to share a bus across several +/// threads, use [`CriticalSectionDevice`](super::CriticalSectionDevice) instead. +pub struct RefCellDevice<'a, T> { + bus: &'a RefCell, +} + +impl<'a, T> RefCellDevice<'a, T> { + /// Create a new `RefCellDevice` + pub fn new(bus: &'a RefCell) -> Self { + Self { bus } + } +} + +impl<'a, T> ErrorType for RefCellDevice<'a, T> +where + T: I2c, +{ + type Error = T::Error; +} + +impl<'a, T> I2c for RefCellDevice<'a, T> +where + T: I2c, +{ + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.read(address, read) + } + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.write(address, write) + } + + fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.write_read(address, write, read) + } + + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let bus = &mut *self.bus.borrow_mut(); + bus.transaction(address, operations) + } +} diff --git a/embedded-hal-bus/src/lib.rs b/embedded-hal-bus/src/lib.rs index 566e74b61..ca76c09c8 100644 --- a/embedded-hal-bus/src/lib.rs +++ b/embedded-hal-bus/src/lib.rs @@ -1,17 +1,6 @@ -//! Bus/Device connection mechanisms for [`embedded-hal`], a Hardware Abstraction Layer (HAL) for embedded systems. -//! -//! It is possible to connect several peripherals to a bus like SPI or I2C. -//! To support this, `embedded-hal` provides the `SpiBus` and `SpiDevice` traits in the case of SPI, for example. -//! -//! `embedded-hal` trait implementations for microcontrollers should implement the `...Bus` traits. -//! However, device drivers should use the `...Device` traits, _not the `...Bus` traits_ if at all possible -//! in order to allow for sharing of the bus they are connected to. -//! -//! This crate provides mechanisms to connect a `...Bus` and a `...Device`. -//! -//! For further details on these traits, please consult the [`embedded-hal` documentation](https://docs.rs/embedded-hal). - +#![doc = include_str!("../README.md")] #![warn(missing_docs)] -#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] +pub mod i2c; pub mod spi;