Skip to content

Commit 95de0e0

Browse files
bors[bot]eldruin
andauthored
Merge #223
223: Add transactional I2C interface r=therealprof a=eldruin An example where a transactional I2C interface would be an improvement is when facing the problem of sending an array of data where the first item is the destination address. At the moment this requires copying the data into a bigger array and assigning the first item to the address. With a transactional I2C two `write` operations could be chained into one transaction so that it is possible to send the address and data without an extra copy. This is specially problematic if the data length is unknown e.g. because it comes from the user. You can see the problem [here in the eeprom24x driver](https://github.com/eldruin/eeprom24x-rs/blob/f75770c6fc6dd8d591e3695908d5ef4ce8642566/src/eeprom24x.rs#L220). In this case I am lucky enough to have the page upper limit so that the copy workaround is possible. With this PR a bunch of code and macros could be replaced by doing something similar to this: ```rust let user_data = [0x12, 0x34, ...]; // defined somewhere else. length may be unknown. let target_address = 0xAB; let mut ops = [ Operation::Write(&[target_address]), Operation::Write(&user_data), ]; i2cdev.try_exec(DEV_ADDR, &ops) ``` I added a PoC [here in linux-embedded-hal](https://github.com/eldruin/linux-embedded-hal/blob/7512dbcc09faa58344a5058baab8826a0e0d0160/src/lib.rs#L211) including [an example](https://github.com/eldruin/linux-embedded-hal/blob/transactional-i2c/examples/transactional-i2c.rs) of a driver where a classical combined write/read is performed through the transactional interface. Note: A default implementation of the `Transactional` trait like in #191 is not possible because STOPs would always be sent after each operation. What is possible is to do is the other way around. This includes an implementation of the `Write`, `Read` and `WriteRead` traits for `Transactional` implementers. This is based on previous work from #178 by @ryankurte and it is similar to #191 TODO: - [x] Add changelog entry Co-authored-by: Diego Barrios Romero <[email protected]>
2 parents da78c82 + 88e4133 commit 95de0e0

File tree

3 files changed

+119
-4
lines changed

3 files changed

+119
-4
lines changed

CHANGELOG.md

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

1010
### Added
11-
- `Transactional` SPI interface for executing groups of SPI transactions
11+
- `Transactional` SPI interface for executing groups of SPI transactions.
12+
- `Transactional` I2C interface for executing groups of I2C transactions.
1213

1314
### Changed
1415

@@ -20,7 +21,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2021
### Added
2122
- 10-bit addressing mode for I2C traits.
2223
- `try_set_state` method for `OutputPin` using an input `PinState` value.
23-
- `Transactional` interface for grouping SPI transactions
2424

2525
### Changed
2626

src/blocking/i2c.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,117 @@ pub trait WriteIterRead<A: AddressMode = SevenBitAddress> {
232232
where
233233
B: IntoIterator<Item = u8>;
234234
}
235+
236+
/// Transactional I2C operation.
237+
///
238+
/// Several operations can be combined as part of a transaction.
239+
#[derive(Debug, PartialEq)]
240+
pub enum Operation<'a> {
241+
/// Read data into the provided buffer
242+
Read(&'a mut [u8]),
243+
/// Write data from the provided buffer
244+
Write(&'a [u8]),
245+
}
246+
247+
/// Transactional I2C interface.
248+
///
249+
/// This allows combining operations within an I2C transaction.
250+
pub trait Transactional {
251+
/// Error type
252+
type Error;
253+
254+
/// Execute the provided operations on the I2C bus.
255+
///
256+
/// Transaction contract:
257+
/// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate.
258+
/// - Data from adjacent operations of the same type are sent after each other without an SP or SR.
259+
/// - Between adjacent operations of a different type an SR and SAD+R/W is sent.
260+
/// - After executing the last operation an SP is sent automatically.
261+
/// - If the last operation is a `Read` the master does not send an acknowledge for the last byte.
262+
///
263+
/// - `ST` = start condition
264+
/// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing
265+
/// - `SR` = repeated start condition
266+
/// - `SP` = stop condition
267+
fn try_exec<'a>(
268+
&mut self,
269+
address: u8,
270+
operations: &mut [Operation<'a>],
271+
) -> Result<(), Self::Error>;
272+
}
273+
274+
/// Transactional I2C interface (iterator version).
275+
///
276+
/// This allows combining operation within an I2C transaction.
277+
pub trait TransactionalIter {
278+
/// Error type
279+
type Error;
280+
281+
/// Execute the provided operations on the I2C bus (iterator version).
282+
///
283+
/// Transaction contract:
284+
/// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate.
285+
/// - Data from adjacent operations of the same type are sent after each other without an SP or SR.
286+
/// - Between adjacent operations of a different type an SR and SAD+R/W is sent.
287+
/// - After executing the last operation an SP is sent automatically.
288+
/// - If the last operation is a `Read` the master does not send an acknowledge for the last byte.
289+
///
290+
/// - `ST` = start condition
291+
/// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing
292+
/// - `SR` = repeated start condition
293+
/// - `SP` = stop condition
294+
fn try_exec_iter<'a, O>(&mut self, address: u8, operations: O) -> Result<(), Self::Error>
295+
where
296+
O: IntoIterator<Item = Operation<'a>>;
297+
}
298+
299+
/// Default implementation of `blocking::i2c::Write`, `blocking::i2c::Read` and
300+
/// `blocking::i2c::WriteRead` traits for `blocking::i2c::Transactional` implementers.
301+
pub mod transactional {
302+
use super::{Operation, Read, Transactional, Write, WriteRead};
303+
304+
/// Default implementation of `blocking::i2c::Write`, `blocking::i2c::Read` and
305+
/// `blocking::i2c::WriteRead` traits for `blocking::i2c::Transactional` implementers.
306+
pub trait Default<E> {}
307+
308+
impl<E, S> Write for S
309+
where
310+
S: self::Default<E> + Transactional<Error = E>,
311+
{
312+
type Error = E;
313+
314+
fn try_write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> {
315+
self.try_exec(address, &mut [Operation::Write(bytes)])
316+
}
317+
}
318+
319+
impl<E, S> Read for S
320+
where
321+
S: self::Default<E> + Transactional<Error = E>,
322+
{
323+
type Error = E;
324+
325+
fn try_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
326+
self.try_exec(address, &mut [Operation::Read(buffer)])
327+
}
328+
}
329+
330+
impl<E, S> WriteRead for S
331+
where
332+
S: self::Default<E> + Transactional<Error = E>,
333+
{
334+
type Error = E;
335+
336+
fn try_write_read(
337+
&mut self,
338+
address: u8,
339+
bytes: &[u8],
340+
buffer: &mut [u8],
341+
) -> Result<(), Self::Error> {
342+
self.try_exec(
343+
address,
344+
&mut [Operation::Write(bytes), Operation::Read(buffer)],
345+
)
346+
}
347+
}
348+
}

src/prelude.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ pub use crate::adc::OneShot as _embedded_hal_adc_OneShot;
88
pub use crate::blocking::delay::DelayMs as _embedded_hal_blocking_delay_DelayMs;
99
pub use crate::blocking::delay::DelayUs as _embedded_hal_blocking_delay_DelayUs;
1010
pub use crate::blocking::i2c::{
11-
Read as _embedded_hal_blocking_i2c_Read, Write as _embedded_hal_blocking_i2c_Write,
12-
WriteIter as _embedded_hal_blocking_i2c_WriteIter,
11+
Read as _embedded_hal_blocking_i2c_Read,
12+
Transactional as _embedded_hal_blocking_i2c_Transactional,
13+
Write as _embedded_hal_blocking_i2c_Write, WriteIter as _embedded_hal_blocking_i2c_WriteIter,
1314
WriteIterRead as _embedded_hal_blocking_i2c_WriteIterRead,
1415
WriteRead as _embedded_hal_blocking_i2c_WriteRead,
1516
};

0 commit comments

Comments
 (0)