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

Make I2C compatible with multiple address sizes #230

Merged
merged 1 commit into from
Jul 16, 2020

Conversation

eldruin
Copy link
Member

@eldruin eldruin commented Jul 4, 2020

This adds I2C 7-bit and 10-bit address mode compatibility as roughly described here.
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.
 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.
 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, traits
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 for a comparison to other alternatives.

I have also sealed the trait.

Proof

  • A HAL implementation of both modes: bitbang-hal. code changes
  • Drivers supporting only 7-bit addresses need no changes.
    For demonstration purposes, explicitly including the SevenBitAddress would look like this: OPT300x. code changes.
    This would be similar to the case of a 10-bit-only device driver.

@rust-highfive
Copy link

r? @ryankurte

(rust_highfive has picked a reviewer for you, use r? to override)

src/blocking/i2c.rs Outdated Show resolved Hide resolved
BroderickCarlin
BroderickCarlin previously approved these changes Jul 4, 2020
@eldruin
Copy link
Member Author

eldruin commented Jul 4, 2020

I have now also added the SevenBitAddress as the default address mode to the traits so this is not even a breaking change.

@ryankurte
Copy link
Contributor

seems generally good to me! the trait indirection approach is clever, though, feels a touch complex (and i have been bitten by trying to do overly clever things with the type system 🙃)

does this give us a strong benefit over providing a couple of public type aliases and documentation of the convention (pub type I2cAddrSevenBit = u8;, pub type I2cAddrTenBit = u16;)? i suppose the perk is you cannot implement the I2C interfaces over external types, with the disadvantage it makes bounds moderately more complex, and either way this must be communicated by documentation.

(i'm not opposed to this approach, but, would like to know we have considered all possibilities)

@eldruin eldruin force-pushed the i2c-multi-address-mode branch 2 times, most recently from 263a5bf to 7b88d29 Compare July 5, 2020 15:24
@eldruin
Copy link
Member Author

eldruin commented Jul 5, 2020

does this give us a strong benefit over providing a couple of public type aliases and documentation of the convention (pub type I2cAddrSevenBit = u8;, pub type I2cAddrTenBit = u16;)?
i suppose the perk is you cannot implement the I2C interfaces over external types, with the disadvantage it makes bounds moderately more complex, and either way this must be communicated by documentation.

Yes, technically without a trait bound it would be possible to misuse the I2C traits and pass some random object through the address parameter if both HAL implementation and driver agree on a complex "address" type.

It would also be possible to do this simplification:

pub trait AddressMode: private::Sealed {}

pub type SevenBitAddress = u8;
pub type TenBitAddress = u16;

impl AddressMode for SevenBitAddress {}
impl AddressMode for TenBitAddress {}

I think this would be fine, since we probably will not need any additional type other than Address in the AddressMode trait.

Would you prefer that?

I have added some documentation now on how this would work for a HAL implementation and device drivers. I could also write some documentation about how to write a driver for a device compatible with both modes but that might be too much. Opinions?

@burrbull
Copy link
Member

burrbull commented Jul 5, 2020

Looks good to me. 👍

@ryankurte
Copy link
Contributor

ryankurte commented Jul 6, 2020

It would also be possible to do this simplification:

ooh, i was under the impression you can't implement traits on type aliases? though, this may be mitigated by the trait being defined in the same place.

Would you prefer that?

hmm i think so? it seems a bit simpler (which is excellent imo), neat that the Address type is actually the type of the address (rather than a marker), and i think this will simplify/clarify the docs too. so, weak preference unless there's a good reason for the more complex approach.

@eldruin
Copy link
Member Author

eldruin commented Jul 6, 2020

Would you prefer that?

hmm i think so? it seems a bit simpler (which is excellent imo), neat that the Address type is actually the type of the address (rather than a marker), and i think this will simplify/clarify the docs too. so, weak preference unless there's a good reason for the more complex approach.

Done!

@eldruin eldruin added this to the v1.0.0 milestone Jul 15, 2020
@thejpster thejpster mentioned this pull request Jul 15, 2020
@ryankurte
Copy link
Contributor

i think this looks good to me, @therealprof any thoughts?

@therealprof
Copy link
Contributor

Haven't had time to play with it in person but looks and sounds great.

Copy link
Contributor

@ryankurte ryankurte left a comment

Choose a reason for hiding this comment

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

well, let's get it out and in the alpha eh? i guess we should look to another (alpha) release shortly

bors r+

@bors bors bot merged commit 83f5bea into rust-embedded:master Jul 16, 2020
@eldruin eldruin deleted the i2c-multi-address-mode branch July 17, 2020 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants