Skip to content

Commit

Permalink
Merge #445
Browse files Browse the repository at this point in the history
445: bus/i2c: add RefCell, CriticalSection and Mutex shared bus implementations. r=eldruin a=Dirbaio

Requires #440 

Same as #443 but for I2C.


This adds a few bus sharing implementations, with varying tradeoffs:
- `RefCellDevice`: single thread only
- `CriticalSectionDevice`: thread-safe, coarse locking, nostd.
- `MutexDevice`: thread-safe, fine-grained locking, std only.

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
  • Loading branch information
bors[bot] and Dirbaio committed Mar 28, 2023
2 parents 1d74e89 + 16ac2e6 commit a0f2426
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 35 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/clippy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
- run: cargo clippy --features=embedded-hal-bus/std -- --deny=warnings
10 changes: 5 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')
3 changes: 2 additions & 1 deletion embedded-hal-bus/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions embedded-hal-bus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
34 changes: 20 additions & 14 deletions embedded-hal-bus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
<http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)

at your option.

Expand Down
70 changes: 70 additions & 0 deletions embedded-hal-bus/src/i2c/critical_section.rs
Original file line number Diff line number Diff line change
@@ -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<RefCell<T>>,
}

impl<'a, T> CriticalSectionDevice<'a, T> {
/// Create a new `CriticalSectionDevice`
pub fn new(bus: &'a Mutex<RefCell<T>>) -> 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)
})
}
}
10 changes: 10 additions & 0 deletions embedded-hal-bus/src/i2c/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
59 changes: 59 additions & 0 deletions embedded-hal-bus/src/i2c/mutex.rs
Original file line number Diff line number Diff line change
@@ -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<T>,
}

impl<'a, T> MutexDevice<'a, T> {
/// Create a new `MutexDevice`
pub fn new(bus: &'a Mutex<T>) -> 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)
}
}
59 changes: 59 additions & 0 deletions embedded-hal-bus/src/i2c/refcell.rs
Original file line number Diff line number Diff line change
@@ -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<T>,
}

impl<'a, T> RefCellDevice<'a, T> {
/// Create a new `RefCellDevice`
pub fn new(bus: &'a RefCell<T>) -> 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)
}
}
17 changes: 3 additions & 14 deletions embedded-hal-bus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit a0f2426

Please sign in to comment.