Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bus/i2c: add RefCell, CriticalSection and Mutex shared bus implementations. #445

Merged
merged 2 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})
}

eldruin marked this conversation as resolved.
Show resolved Hide resolved
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)
}

eldruin marked this conversation as resolved.
Show resolved Hide resolved
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)
}

eldruin marked this conversation as resolved.
Show resolved Hide resolved
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;