Skip to content

Commit a0f2426

Browse files
bors[bot]Dirbaio
andauthored
Merge #445
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 <[email protected]>
2 parents 1d74e89 + 16ac2e6 commit a0f2426

File tree

10 files changed

+233
-35
lines changed

10 files changed

+233
-35
lines changed

.github/workflows/clippy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ jobs:
1515
# Use a pinned version to avoid spontaneous breakages (new clippy lints are added often)
1616
toolchain: nightly-2022-11-22
1717
components: clippy
18-
- run: cargo clippy -- --deny=warnings
18+
- run: cargo clippy --features=embedded-hal-bus/std -- --deny=warnings

.github/workflows/test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ jobs:
1313
runs-on: ubuntu-latest
1414
strategy:
1515
matrix:
16-
# All generated code should be running on stable now
1716
rust:
1817
- stable
1918
- 1.59.0 # MSRV
2019
- nightly
21-
22-
# The default target we're compiling on and for
2320
target:
2421
- x86_64-unknown-linux-gnu
2522
- thumbv6m-none-eabi
2623
- thumbv7m-none-eabi
24+
include:
25+
- target: x86_64-unknown-linux-gnu
26+
features: embedded-hal-bus/std
2727

2828
steps:
2929
- uses: actions/checkout@v3
@@ -35,7 +35,7 @@ jobs:
3535
- run: sed -i '/nightly-only/d' Cargo.toml
3636
if: matrix.rust != 'nightly'
3737

38-
- run: cargo check --target=${{ matrix.target }}
38+
- run: cargo check --target=${{ matrix.target }} --features=${{ matrix.features }}
3939

40-
- run: cargo test --target=${{ matrix.target }}
40+
- run: cargo test --target=${{ matrix.target }} --features=${{ matrix.features }}
4141
if: contains(matrix.target, 'linux')

embedded-hal-bus/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10-
...
10+
### Added
11+
- i2c: add bus sharing implementations.
1112

1213
## [v0.1.0-alpha.1] - 2022-09-28
1314

embedded-hal-bus/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,9 @@ readme = "README.md"
1313
repository = "https://github.com/rust-embedded/embedded-hal"
1414
version = "0.1.0-alpha.1"
1515

16+
[features]
17+
std = []
18+
1619
[dependencies]
1720
embedded-hal = { version = "=1.0.0-alpha.9", path = "../embedded-hal" }
21+
critical-section = { version = "1.0" }

embedded-hal-bus/README.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,47 @@
55

66
# `embedded-hal-bus`
77

8-
Bus/Device connection mechanisms for [`embedded-hal`](https://crates.io/crates/embedded-hal), a Hardware Abstraction Layer (HAL) for embedded systems.
8+
Bus sharing utilities for [`embedded-hal`](https://crates.io/crates/embedded-hal), a Hardware Abstraction Layer (HAL) for embedded systems.
99

10-
It is possible to connect several peripherals to a bus like SPI or I2C.
11-
To support this, `embedded-hal` provides the `SpiBus` and `SpiDevice` traits in the case of SPI, for example.
10+
`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.
1211

13-
`embedded-hal` trait implementations for microcontrollers should implement the `...Bus` traits.
14-
However, device drivers should use the `...Device` traits, _not the `...Bus` traits_ if at all possible
12+
This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team).
13+
14+
## SPI
15+
16+
To support bus sharing, `embedded-hal` provides the `SpiBus` and `SpiDevice` traits. `SpiBus` represents an entire bus,
17+
while `SpiDevice` represents a device on that bus. For further details on these traits, please consult the
18+
[`embedded-hal` documentation](https://docs.rs/embedded-hal/1.0.0-alpha.9/embedded_hal/spi/index.html).
19+
20+
`embedded-hal` trait implementations for microcontrollers should implement the `SpiBus` trait.
21+
However, device drivers should use the `SpiDevice` traits, _not the `SpiBus` traits_ if at all possible
1522
in order to allow for sharing of the bus they are connected to.
1623

17-
This crate provides mechanisms to connect a `...Bus` and a `...Device`.
24+
This crate provides mechanisms to connect a `SpiBus` and a `SpiDevice`.
1825

19-
For further details on these traits, please consult the [`embedded-hal` documentation](https://docs.rs/embedded-hal).
26+
## I2C
2027

21-
This project is developed and maintained by the [HAL team](https://github.com/rust-embedded/wg#the-hal-team).
28+
In the case of I2C, the same `I2c` `embedded-hal` trait represents either an entire bus, or a device on a bus. This crate
29+
provides mechanisms to obtain multiple `I2c` instances out of a single `I2c` instance, sharing the bus.
2230

23-
## [API reference]
31+
## Features
2432

25-
[API reference]: https://docs.rs/embedded-hal-bus
33+
- `std`: enable shared bus implementations using `std::sync::Mutex`.
2634

2735
## Minimum Supported Rust Version (MSRV)
2836

29-
3037
This crate is guaranteed to compile on stable Rust 1.59 and up. It *might*
3138
compile with older versions but that may change in any new patch release.
3239

3340
See [here](../docs/msrv.md) for details on how the MSRV may be upgraded.
3441

35-
3642
## License
3743

3844
Licensed under either of
3945

4046
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
41-
http://www.apache.org/licenses/LICENSE-2.0)
42-
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
47+
<http://www.apache.org/licenses/LICENSE-2.0>)
48+
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
4349

4450
at your option.
4551

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use core::cell::RefCell;
2+
use critical_section::Mutex;
3+
use embedded_hal::i2c::{ErrorType, I2c};
4+
5+
/// `critical-section`-based shared bus [`I2c`] implementation.
6+
///
7+
/// Sharing is implemented with a `critical-section` [`Mutex`](critical_section::Mutex). A critical section is taken for
8+
/// the entire duration of a transaction. This allows sharing a single bus across multiple threads (interrupt priority levels).
9+
/// The downside is critical sections typically require globally disabling interrupts, so `CriticalSectionDevice` will likely
10+
/// negatively impact real-time properties, such as interrupt latency. If you can, prefer using
11+
/// [`RefCellDevice`](super::RefCellDevice) instead, which does not require taking critical sections.
12+
pub struct CriticalSectionDevice<'a, T> {
13+
bus: &'a Mutex<RefCell<T>>,
14+
}
15+
16+
impl<'a, T> CriticalSectionDevice<'a, T> {
17+
/// Create a new `CriticalSectionDevice`
18+
pub fn new(bus: &'a Mutex<RefCell<T>>) -> Self {
19+
Self { bus }
20+
}
21+
}
22+
23+
impl<'a, T> ErrorType for CriticalSectionDevice<'a, T>
24+
where
25+
T: I2c,
26+
{
27+
type Error = T::Error;
28+
}
29+
30+
impl<'a, T> I2c for CriticalSectionDevice<'a, T>
31+
where
32+
T: I2c,
33+
{
34+
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
35+
critical_section::with(|cs| {
36+
let bus = &mut *self.bus.borrow_ref_mut(cs);
37+
bus.read(address, read)
38+
})
39+
}
40+
41+
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
42+
critical_section::with(|cs| {
43+
let bus = &mut *self.bus.borrow_ref_mut(cs);
44+
bus.write(address, write)
45+
})
46+
}
47+
48+
fn write_read(
49+
&mut self,
50+
address: u8,
51+
write: &[u8],
52+
read: &mut [u8],
53+
) -> Result<(), Self::Error> {
54+
critical_section::with(|cs| {
55+
let bus = &mut *self.bus.borrow_ref_mut(cs);
56+
bus.write_read(address, write, read)
57+
})
58+
}
59+
60+
fn transaction(
61+
&mut self,
62+
address: u8,
63+
operations: &mut [embedded_hal::i2c::Operation<'_>],
64+
) -> Result<(), Self::Error> {
65+
critical_section::with(|cs| {
66+
let bus = &mut *self.bus.borrow_ref_mut(cs);
67+
bus.transaction(address, operations)
68+
})
69+
}
70+
}

embedded-hal-bus/src/i2c/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//! `I2c` shared bus implementations.
2+
3+
mod refcell;
4+
pub use refcell::*;
5+
#[cfg(feature = "std")]
6+
mod mutex;
7+
#[cfg(feature = "std")]
8+
pub use mutex::*;
9+
mod critical_section;
10+
pub use self::critical_section::*;

embedded-hal-bus/src/i2c/mutex.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use embedded_hal::i2c::{ErrorType, I2c};
2+
use std::sync::Mutex;
3+
4+
/// `std` `Mutex`-based shared bus [`I2c`] implementation.
5+
///
6+
/// Sharing is implemented with an `std` [`Mutex`](std::sync::Mutex). It allows a single bus across multiple threads,
7+
/// with finer-grained locking than [`CriticalSectionDevice`](super::CriticalSectionDevice). The downside is that
8+
/// it is only available in `std` targets.
9+
pub struct MutexDevice<'a, T> {
10+
bus: &'a Mutex<T>,
11+
}
12+
13+
impl<'a, T> MutexDevice<'a, T> {
14+
/// Create a new `MutexDevice`
15+
pub fn new(bus: &'a Mutex<T>) -> Self {
16+
Self { bus }
17+
}
18+
}
19+
20+
impl<'a, T> ErrorType for MutexDevice<'a, T>
21+
where
22+
T: I2c,
23+
{
24+
type Error = T::Error;
25+
}
26+
27+
impl<'a, T> I2c for MutexDevice<'a, T>
28+
where
29+
T: I2c,
30+
{
31+
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
32+
let bus = &mut *self.bus.lock().unwrap();
33+
bus.read(address, read)
34+
}
35+
36+
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
37+
let bus = &mut *self.bus.lock().unwrap();
38+
bus.write(address, write)
39+
}
40+
41+
fn write_read(
42+
&mut self,
43+
address: u8,
44+
write: &[u8],
45+
read: &mut [u8],
46+
) -> Result<(), Self::Error> {
47+
let bus = &mut *self.bus.lock().unwrap();
48+
bus.write_read(address, write, read)
49+
}
50+
51+
fn transaction(
52+
&mut self,
53+
address: u8,
54+
operations: &mut [embedded_hal::i2c::Operation<'_>],
55+
) -> Result<(), Self::Error> {
56+
let bus = &mut *self.bus.lock().unwrap();
57+
bus.transaction(address, operations)
58+
}
59+
}

embedded-hal-bus/src/i2c/refcell.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use core::cell::RefCell;
2+
use embedded_hal::i2c::{ErrorType, I2c};
3+
4+
/// `RefCell`-based shared bus [`I2c`] implementation.
5+
///
6+
/// Sharing is implemented with a `RefCell`. This means it has low overhead, but `RefCellDevice` instances are not `Send`,
7+
/// so it only allows sharing within a single thread (interrupt priority level). If you need to share a bus across several
8+
/// threads, use [`CriticalSectionDevice`](super::CriticalSectionDevice) instead.
9+
pub struct RefCellDevice<'a, T> {
10+
bus: &'a RefCell<T>,
11+
}
12+
13+
impl<'a, T> RefCellDevice<'a, T> {
14+
/// Create a new `RefCellDevice`
15+
pub fn new(bus: &'a RefCell<T>) -> Self {
16+
Self { bus }
17+
}
18+
}
19+
20+
impl<'a, T> ErrorType for RefCellDevice<'a, T>
21+
where
22+
T: I2c,
23+
{
24+
type Error = T::Error;
25+
}
26+
27+
impl<'a, T> I2c for RefCellDevice<'a, T>
28+
where
29+
T: I2c,
30+
{
31+
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
32+
let bus = &mut *self.bus.borrow_mut();
33+
bus.read(address, read)
34+
}
35+
36+
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
37+
let bus = &mut *self.bus.borrow_mut();
38+
bus.write(address, write)
39+
}
40+
41+
fn write_read(
42+
&mut self,
43+
address: u8,
44+
write: &[u8],
45+
read: &mut [u8],
46+
) -> Result<(), Self::Error> {
47+
let bus = &mut *self.bus.borrow_mut();
48+
bus.write_read(address, write, read)
49+
}
50+
51+
fn transaction(
52+
&mut self,
53+
address: u8,
54+
operations: &mut [embedded_hal::i2c::Operation<'_>],
55+
) -> Result<(), Self::Error> {
56+
let bus = &mut *self.bus.borrow_mut();
57+
bus.transaction(address, operations)
58+
}
59+
}

embedded-hal-bus/src/lib.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,6 @@
1-
//! Bus/Device connection mechanisms for [`embedded-hal`], a Hardware Abstraction Layer (HAL) for embedded systems.
2-
//!
3-
//! It is possible to connect several peripherals to a bus like SPI or I2C.
4-
//! To support this, `embedded-hal` provides the `SpiBus` and `SpiDevice` traits in the case of SPI, for example.
5-
//!
6-
//! `embedded-hal` trait implementations for microcontrollers should implement the `...Bus` traits.
7-
//! However, device drivers should use the `...Device` traits, _not the `...Bus` traits_ if at all possible
8-
//! in order to allow for sharing of the bus they are connected to.
9-
//!
10-
//! This crate provides mechanisms to connect a `...Bus` and a `...Device`.
11-
//!
12-
//! For further details on these traits, please consult the [`embedded-hal` documentation](https://docs.rs/embedded-hal).
13-
1+
#![doc = include_str!("../README.md")]
142
#![warn(missing_docs)]
15-
#![no_std]
3+
#![cfg_attr(not(feature = "std"), no_std)]
164

5+
pub mod i2c;
176
pub mod spi;

0 commit comments

Comments
 (0)