Skip to content

Commit 83f5bea

Browse files
bors[bot]eldruin
andauthored
Merge #230
230: Make I2C compatible with multiple address sizes r=ryankurte a=eldruin This adds I2C 7-bit and 10-bit address mode compatibility as roughly described [here](#147 (comment)). Discussion issue: #147 I have also added the `SevenBitAddress` as the default address mode to the traits so this is not even a breaking change. Usage broken down per use case: * **Device driver which only supports 7-bit addressing mode:** The driver looks exactly the same as now since the default address mode is 7-bit. ```rust impl<I2C, E> MyDriver<I2C> where I2C: i2c::Write<Error = E> { pub fn do_cool_stuff(&mut self) // ... } ``` * **Device driver which only supports 10-bit addressing mode:** The only difference to a 7-bit-address-only driver is one additional parameter in the I2C trait bound. ```rust impl<I2C, E> MyDriver<I2C> where I2C: i2c::Write<TenBitAddress, Error = E> { pub fn do_cool_stuff(&mut self) // ... } ``` * **Driver for device supporting both addressing modes:** Complexity can be abstracted away into additional internal traits which can handle the addressing stuff. Driver code stays clean. **This is nothing new**. We already do this on drivers for devices compatible with both I2C and SPI. No need for duplicated code. Here a real example: [usage](https://github.com/eldruin/bmi160-rs/blob/3af5637f1df047bb811a4885525cfbe8b44d8ede/src/device_impl.rs#L43), [traits](https://github.com/eldruin/bmi160-rs/blob/master/src/interface.rs) ```rust impl<DI, E> MyDriver<DI> where DI: WriteData<Error = E> { pub fn do_cool_stuff(&mut self) {} // ... } pub trait WriteData { // ... } // it is also possible to just leave the `SevenBitAddress` type out here, // since it is the default. impl<I2C, E> WriteData for I2cInterface<I2C, SevenBitAddress> where I2C: i2c::Write<SevenBitAddress, Error = E>, { // ... } impl<I2C, E> WriteData for I2cInterface<I2C, TenBitAddress> where I2C: i2c::Write<TenBitAddress, Error = E>, { // ... } ``` * **Bus controller impl supporting only 7-bit addressing mode:** Code stays almost the same, just adding one addressing mode parameter. Additionally, _if desired_: * 10-bit addressing can be software-emulated: Emulate by extending and copying payload in separate `TenBitAddress` implementation. Total flexibility to do whatever is necessary in this case since the code is independent. * 10-bit addressing cannot be software-emulated: Implementation does not offer implementation for `TenBitAddress` variant. The user gets a compilation error and everything is clear. * **Bus controller impl supporting both addressing modes:** No problem. Two separate implementations guarantee as much flexibility as necessary. At the same time, sharing generic code is possible. Additional benefits: * No runtime performance cost * No runtime switching, duplicated code or panics for unsupported modes. * Consistent with what we do for code paths that can be determined statically by the compiler. * To my taste elegant, simple and very descriptive. See [here](#147 (comment)) for a comparison to other alternatives. I have also sealed the trait. ## Proof * A HAL implementation of both modes: [bitbang-hal](https://github.com/eldruin/bitbang-hal/tree/i2c-multi-address-mode). [code changes](eldruin/bitbang-hal@embedded-hal-1.0.0-alpha.1...eldruin:i2c-multi-address-mode) * Drivers supporting only 7-bit addresses need **no changes**. For demonstration purposes, explicitly including the `SevenBitAddress` would look like this: [OPT300x](https://github.com/eldruin/opt300x-rs/tree/i2c-multi-address-mode). [code changes](https://github.com/eldruin/opt300x-rs/compare/i2c-multi-address-mode). This would be similar to the case of a 10-bit-only device driver. Co-authored-by: Diego Barrios Romero <[email protected]>
2 parents d983a3d + d8187c3 commit 83f5bea

File tree

3 files changed

+135
-13
lines changed

3 files changed

+135
-13
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
88

99
## [Unreleased]
1010

11+
### Added
12+
- 10-bit addressing mode for I2C traits.
13+
1114
### Changed
1215

16+
- I2C addressing modes are now selected via an `AddressMode` type parameter.
17+
The trait features implementations for marker types `SevenBitAddress` and
18+
`TenBitAddress`. `SevenBitAddress` is the default mode so this is not a
19+
breaking change.
1320
- The method `try_write` from the trait `blocking::i2c::WriteIter` trait
1421
has been renamed `try_write_iter` for consistency.
1522
- Updated `nb` dependency to version `1`.

src/blocking/i2c.rs

+120-13
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,119 @@
11
//! Blocking I2C API
22
//!
3-
//! Slave addresses used by this API are 7-bit I2C addresses ranging from 0 to 127.
3+
//! This API supports 7-bit and 10-bit addresses. Traits feature an `AddressMode`
4+
//! marker type parameter. Two implementation of the `AddressMode` exist:
5+
//! `SevenBitAddress` and `TenBitAddress`.
46
//!
5-
//! Operations on 10-bit slave addresses are not supported by the API yet (but applications might
6-
//! be able to emulate some operations).
7+
//! Through this marker types it is possible to implement each address mode for
8+
//! the traits independently in `embedded-hal` implementations and device drivers
9+
//! can depend only on the mode that they support.
10+
//!
11+
//! Additionally, the I2C 10-bit address mode has been developed to be fully
12+
//! backwards compatible with the 7-bit address mode. This allows for a
13+
//! software-emulated 10-bit addressing implementation if the address mode
14+
//! is not supported by the hardware.
15+
//!
16+
//! Since 7-bit addressing is the mode of the majority of I2C devices,
17+
//! `SevenBitAddress` has been set as default mode and thus can be omitted if desired.
18+
//!
19+
//! ## Examples
20+
//!
21+
//! ### `embedded-hal` implementation for an MCU
22+
//! Here is an example of an embedded-hal implementation of the `Write` trait
23+
//! for both modes:
24+
//! ```
25+
//! # use embedded_hal::blocking::i2c::{SevenBitAddress, TenBitAddress, Write};
26+
//! /// I2C0 hardware peripheral which supports both 7-bit and 10-bit addressing.
27+
//! pub struct I2c0;
28+
//!
29+
//! impl Write<SevenBitAddress> for I2c0
30+
//! {
31+
//! # type Error = ();
32+
//! #
33+
//! fn try_write(&mut self, addr: u8, output: &[u8]) -> Result<(), Self::Error> {
34+
//! // ...
35+
//! # Ok(())
36+
//! }
37+
//! }
38+
//!
39+
//! impl Write<TenBitAddress> for I2c0
40+
//! {
41+
//! # type Error = ();
42+
//! #
43+
//! fn try_write(&mut self, addr: u16, output: &[u8]) -> Result<(), Self::Error> {
44+
//! // ...
45+
//! # Ok(())
46+
//! }
47+
//! }
48+
//! ```
49+
//!
50+
//! ### Device driver compatible only with 7-bit addresses
51+
//!
52+
//! For demonstration purposes the address mode parameter has been omitted in this example.
53+
//!
54+
//! ```
55+
//! # use embedded_hal::blocking::i2c::WriteRead;
56+
//! const ADDR: u8 = 0x15;
57+
//! # const TEMP_REGISTER: u8 = 0x1;
58+
//! pub struct TemperatureSensorDriver<I2C> {
59+
//! i2c: I2C,
60+
//! }
61+
//!
62+
//! impl<I2C, E> TemperatureSensorDriver<I2C>
63+
//! where
64+
//! I2C: WriteRead<Error = E>,
65+
//! {
66+
//! pub fn read_temperature(&mut self) -> Result<u8, E> {
67+
//! let mut temp = [0];
68+
//! self.i2c
69+
//! .try_write_read(ADDR, &[TEMP_REGISTER], &mut temp)
70+
//! .and(Ok(temp[0]))
71+
//! }
72+
//! }
73+
//! ```
74+
//!
75+
//! ### Device driver compatible only with 10-bit addresses
76+
//!
77+
//! ```
78+
//! # use embedded_hal::blocking::i2c::{TenBitAddress, WriteRead};
79+
//! const ADDR: u16 = 0x158;
80+
//! # const TEMP_REGISTER: u8 = 0x1;
81+
//! pub struct TemperatureSensorDriver<I2C> {
82+
//! i2c: I2C,
83+
//! }
84+
//!
85+
//! impl<I2C, E> TemperatureSensorDriver<I2C>
86+
//! where
87+
//! I2C: WriteRead<TenBitAddress, Error = E>,
88+
//! {
89+
//! pub fn read_temperature(&mut self) -> Result<u8, E> {
90+
//! let mut temp = [0];
91+
//! self.i2c
92+
//! .try_write_read(ADDR, &[TEMP_REGISTER], &mut temp)
93+
//! .and(Ok(temp[0]))
94+
//! }
95+
//! }
96+
//! ```
97+
98+
use crate::private;
99+
100+
/// Address mode (7-bit / 10-bit)
101+
///
102+
/// Note: This trait is sealed and should not be implemented outside of this crate.
103+
pub trait AddressMode: private::Sealed {}
104+
105+
/// 7-bit address mode type
106+
pub type SevenBitAddress = u8;
107+
108+
/// 10-bit address mode type
109+
pub type TenBitAddress = u16;
110+
111+
impl AddressMode for SevenBitAddress {}
112+
113+
impl AddressMode for TenBitAddress {}
7114

8115
/// Blocking read
9-
pub trait Read {
116+
pub trait Read<A: AddressMode = SevenBitAddress> {
10117
/// Error type
11118
type Error;
12119

@@ -28,11 +135,11 @@ pub trait Read {
28135
/// - `MAK` = master acknowledge
29136
/// - `NMAK` = master no acknowledge
30137
/// - `SP` = stop condition
31-
fn try_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error>;
138+
fn try_read(&mut self, address: A, buffer: &mut [u8]) -> Result<(), Self::Error>;
32139
}
33140

34141
/// Blocking write
35-
pub trait Write {
142+
pub trait Write<A: AddressMode = SevenBitAddress> {
36143
/// Error type
37144
type Error;
38145

@@ -52,11 +159,11 @@ pub trait Write {
52159
/// - `SAK` = slave acknowledge
53160
/// - `Bi` = ith byte of data
54161
/// - `SP` = stop condition
55-
fn try_write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error>;
162+
fn try_write(&mut self, address: A, bytes: &[u8]) -> Result<(), Self::Error>;
56163
}
57164

58165
/// Blocking write (iterator version)
59-
pub trait WriteIter {
166+
pub trait WriteIter<A: AddressMode = SevenBitAddress> {
60167
/// Error type
61168
type Error;
62169

@@ -65,13 +172,13 @@ pub trait WriteIter {
65172
/// # I2C Events (contract)
66173
///
67174
/// Same as `Write`
68-
fn try_write_iter<B>(&mut self, address: u8, bytes: B) -> Result<(), Self::Error>
175+
fn try_write_iter<B>(&mut self, address: A, bytes: B) -> Result<(), Self::Error>
69176
where
70177
B: IntoIterator<Item = u8>;
71178
}
72179

73180
/// Blocking write + read
74-
pub trait WriteRead {
181+
pub trait WriteRead<A: AddressMode = SevenBitAddress> {
75182
/// Error type
76183
type Error;
77184

@@ -99,14 +206,14 @@ pub trait WriteRead {
99206
/// - `SP` = stop condition
100207
fn try_write_read(
101208
&mut self,
102-
address: u8,
209+
address: A,
103210
bytes: &[u8],
104211
buffer: &mut [u8],
105212
) -> Result<(), Self::Error>;
106213
}
107214

108215
/// Blocking write (iterator version) + read
109-
pub trait WriteIterRead {
216+
pub trait WriteIterRead<A: AddressMode = SevenBitAddress> {
110217
/// Error type
111218
type Error;
112219

@@ -118,7 +225,7 @@ pub trait WriteIterRead {
118225
/// Same as the `WriteRead` trait
119226
fn try_write_iter_read<B>(
120227
&mut self,
121-
address: u8,
228+
address: A,
122229
bytes: B,
123230
buffer: &mut [u8],
124231
) -> Result<(), Self::Error>

src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -700,3 +700,11 @@ pub mod serial;
700700
pub mod spi;
701701
pub mod timer;
702702
pub mod watchdog;
703+
704+
mod private {
705+
use crate::blocking::i2c::{SevenBitAddress, TenBitAddress};
706+
pub trait Sealed {}
707+
708+
impl Sealed for SevenBitAddress {}
709+
impl Sealed for TenBitAddress {}
710+
}

0 commit comments

Comments
 (0)