Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"embedded-hal-async",
"embedded-hal-nb",
"embedded-hal-bus",
"embedded-hal-i3c",
"embedded-can",
"embedded-io",
"embedded-io-async",
Expand Down
30 changes: 30 additions & 0 deletions embedded-hal-i3c/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

[package]
authors = [
"The Embedded HAL Team and Contributors <embedded-hal@teams.rust-embedded.org>",
]
categories = ["embedded", "hardware-support", "no-std"]
description = "Traits for abstracting I3C Databusses"
documentation = "https://docs.rs/embedded-hal-i3c"
edition = "2021"
rust-version = "1.81"
keywords = ["hal", "IO"]
license = "MIT OR Apache-2.0"
name = "embedded-hal-i3c"
readme = "README.md"
repository = "https://github.com/rust-embedded/embedded-hal"
version = "0.1.0"

[features]
async = ["dep:embedded-hal-async"]
# Derive `defmt::Format` from `defmt` 0.3 for enums and structs. See https://github.com/knurling-rs/defmt for more info
defmt-03 = ["dep:defmt-03", "embedded-hal/defmt-03", "embedded-hal-async?/defmt-03"]

[dependencies]
embedded-hal = { version = "1.0.0", path = "../embedded-hal" }
embedded-hal-async = { version = "1.0.0", path = "../embedded-hal-async", optional = true }
defmt-03 = { package = "defmt", version = "0.3", optional = true }

[package.metadata.docs.rs]
features = ["async"]
rustdoc-args = ["--cfg", "docsrs"]
80 changes: 80 additions & 0 deletions embedded-hal-i3c/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
pub use embedded_hal::i2c::Operation;

/// I3C error.
pub trait Error: core::fmt::Debug {
/// Convert error to a generic I3C error kind.
///
/// By using this method, I3C errors freely defined by HAL implementations
/// can be converted to a set of generic I3C errors upon which generic
/// code can act.
fn kind(&self) -> ErrorKind;
}

impl Error for core::convert::Infallible {
#[inline]
fn kind(&self) -> ErrorKind {
match *self {}
}
}

#[non_exhaustive]
pub enum ErrorKind {
Bus,
ArbitrationLoss,
Ovverun,
Comment thread
Tremoneck marked this conversation as resolved.
Outdated
Other,
}

/// I3C error type trait.
///
/// This just defines the error type, to be used by the other traits.
pub trait ErrorType {
type Error: Error;
}

impl<T: ErrorType + ?Sized> ErrorType for &mut T {
type Error = T::Error;
}

pub trait I3c<S: SpeedMode = Sdr>: ErrorType {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I agree with the Speed mode typestate here. We can switch between DDR and SDR in runtime, right? Will you require folks to consume and recreate the I3C (presumably with helpers into_sdr() and into_ddr())?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about the controller implementing all supported speeds for the same object. The client can then use the different supported speeds depending on which trait is used to call transaction. In this design the whole transaction has the same speed. The switch from i2c to i3c would happen after the first address byte has been sent and arbitration is over.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently my idea on how I would implement this didn't work out or I'm missing on how to constrain the types properly. As can be seen in the test. Ideas on another Idea. Different traits for the different speeds could work, but they'd be unwieldy.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it'll be hard to come up with something sensible. But I think a single trait that lets you do a transaction and the transaction knows if it's supposed to be I2c, I3cSdr or I3cDdr would be simpler. Then, anything implementing I3c gets an I2c implementation for free by calling I3c methods with all transactions set to I2c.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea behin the typestates was to ensure that a user is never able to combine a slave and controller, where the speed is not suported by both.

// Here we also add read, write read_write like I2C removed for brevity

/// Execute the provided operations on the I3C bus.
///
/// Transaction contract:
/// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate.
/// - After the first time the address is send the bus switches to the specified speed
/// - Data from adjacent operations of the same type are sent after each other without an SP or SR.
/// - Between adjacent operations of a different type an SR and SAD+R/W is sent.
/// - After executing the last operation an SP is sent automatically.
/// - If the last operation is a `Read` the master does not send an acknowledge for the last byte.
///
/// - `ST` = start condition
/// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing
/// - `SR` = repeated start condition
/// - `SP` = stop condition
fn transaction(
&mut self,
address: u8,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error>;
}

trait Sealed {}

#[allow(private_bounds)]
pub trait SpeedMode: Sealed + 'static {}

/// Single data rate
pub struct Sdr {}

impl Sealed for Sdr {}

impl SpeedMode for Sdr {}

/// Double data rate
pub struct Ddr {}

impl Sealed for Ddr {}

impl SpeedMode for Ddr {}
Loading