Skip to content

Commit

Permalink
Adopt type states to manage bus sharing
Browse files Browse the repository at this point in the history
In rust-embedded/embedded-hal#35 one of the
proposals for dealing with sharing the buses is to use this pattern.

The most appealing aspect of this is that it doesn't require any
specific knowledge of synchronization or concurrency used by
the embedding application.

The `Owned::borrow` method feels a little warty and un-ergonomic.
Ideally we'd be able to use `Deref` and `DerefMut` to have the
compiler automatically `borrow()` when doing something like
`owned.get_bank_a_data()`, but it doesn't appear to be possible
to have it meet our lifetime requirements.
  • Loading branch information
wez committed Apr 21, 2018
1 parent 9dddf4e commit a063f2c
Showing 1 changed file with 108 additions and 13 deletions.
121 changes: 108 additions & 13 deletions src/lib.rs
Expand Up @@ -58,15 +58,34 @@ and then instantiate the driver:
```no_run
let mut expander = sx1509::Sx1509::new(&mut i2c, sx1509::DEFAULT_ADDRESS);
expander.software_reset()?;
expander.set_bank_a_direction(0)?;
expander.borrow(&mut i2c).software_reset()?;
expander.borrow(&mut i2c).set_bank_a_direction(0)?;
// read the pins from bank a
let pins = expander.get_bank_a_data()?;
let pins = expander.borrow(&mut i2c).get_bank_a_data()?;
```
Note that you must `borrow` the i2c bus for the duration of each operation.
This allows the bus to be shared and used to address other devices in between
operations.
It is possible to take ownership of the bus for a longer period of time. That
is required for `Future` based usage. While `Future` based usage isn't supported
at this time, we do have support for this mode of operation:
```no_run
let mut owned = expander.take(i2c);
// Now you can borrow and perform the IO when you are ready:
let pins = owned.borrow().get_bank_a_data()?;
// and relinquish the bus when done
let (expander, i2c) = owned.release();
```
*/
#![no_std]
extern crate embedded_hal as hal;
use core::marker::PhantomData;

use hal::blocking::i2c::{Write, WriteRead};

Expand Down Expand Up @@ -293,32 +312,108 @@ pub enum Register {
/// applied to alter its address.
pub const DEFAULT_ADDRESS: u8 = 0x3e;

pub struct Sx1509<'a, I2C: 'a> {
i2c: &'a mut I2C,
/// The `Sx1509` struct encapsulates the basic data about the device.
/// You need to `borrow` or `take` the I2C bus to issue IO.
pub struct Sx1509<I2C>
where
I2C: WriteRead + Write,
{
address: u8,
i2c: PhantomData<I2C>,
}

/// The `Owned` struct encapsulates the device and owns the bus.
/// You need to `borrow` to issue IO, or `release` to release
/// ownership of the bus.
pub struct Owned<I2C>
where
I2C: WriteRead + Write,
{
i2c: I2C,
state: Sx1509<I2C>,
}

impl<'a, I2C, E> Sx1509<'a, I2C>
/// The `Borrowed` struct encapsulates the device and a borrowed
/// bus. This is the struct which holds the actual `impl` for
/// the device driver.
pub struct Borrowed<'a, I2C: 'a>
where
I2C: WriteRead<Error = E> + Write<Error = E>,
I2C: WriteRead + Write,
{
i2c: &'a mut I2C,
state: &'a mut Sx1509<I2C>,
}

impl<I2C> Sx1509<I2C>
where
I2C: WriteRead + Write,
{
/// Create an instance. No implicit initialization is performed.
/// You will likely want to perform a `software_reset` as the
/// next step.
pub fn new(i2c: &'a mut I2C, address: u8) -> Self {
Self { i2c, address }
pub fn new(_i2c: &mut I2C, address: u8) -> Self {
Self {
i2c: PhantomData,
address,
}
}

/// Take ownership of the bus and return an `Owned` instance.
/// This is best suited for asynchronous usage where you initiate IO
/// and later test to see whether it is complete. This doesn't make
/// a lot of sense with the current implementation of the Sx1509 driver
/// as no such workflows are supported today.
/// You probably want the `borrow` method instead.
pub fn take(self, i2c: I2C) -> Owned<I2C> {
Owned {
state: self,
i2c: i2c,
}
}

/// Borrow ownership of the bus and return a `Borrowed` instance that
/// can be used to perform IO.
pub fn borrow<'a>(&'a mut self, i2c: &'a mut I2C) -> Borrowed<'a, I2C> {
Borrowed { state: self, i2c }
}
}

impl<I2C> Owned<I2C>
where
I2C: WriteRead + Write,
{
/// Release ownership of the bus and decompose the `Owned` instance back
/// into its constituent `Sx1509` and `I2C` components.
pub fn release(self) -> (Sx1509<I2C>, I2C) {
(self.state, self.i2c)
}

/// Create a `Borrowed` instance from this `Owned` instance so that you
/// can perform IO on it.
/// Ideally we'd impl `Deref` and `DerefMut` instead of doing this, but
/// it seems impossible to do this and preserve appropriate lifetimes.
pub fn borrow<'a>(&'a mut self) -> Borrowed<'a, I2C> {
Borrowed {
state: &mut self.state,
i2c: &mut self.i2c,
}
}
}

impl<'a, I2C, E> Borrowed<'a, I2C>
where
I2C: WriteRead<Error = E> + Write<Error = E> + 'a,
{
/// Write `value` to `register`
fn write(&mut self, register: Register, value: u8) -> Result<(), E> {
self.i2c.write(self.address, &[register as u8, value])
self.i2c.write(self.state.address, &[register as u8, value])
}

/// Read a 16-bit value from the register and its successor
pub fn read_16(&mut self, register: Register) -> Result<u16, E> {
let mut buf = [0u8; 2];
self.i2c
.write_read(self.address, &[register as u8], &mut buf)?;
.write_read(self.state.address, &[register as u8], &mut buf)?;
Ok((buf[0] as u16) << 8 | buf[1] as u16)
}

Expand Down Expand Up @@ -360,7 +455,7 @@ where
pub fn get_bank_a_data(&mut self) -> Result<u8, E> {
let mut res = [0u8; 1];
self.i2c
.write_read(self.address, &[Register::RegDataA as u8], &mut res)?;
.write_read(self.state.address, &[Register::RegDataA as u8], &mut res)?;
Ok(res[0])
}

Expand All @@ -369,7 +464,7 @@ where
pub fn get_bank_b_data(&mut self) -> Result<u8, E> {
let mut res = [0u8; 1];
self.i2c
.write_read(self.address, &[Register::RegDataB as u8], &mut res)?;
.write_read(self.state.address, &[Register::RegDataB as u8], &mut res)?;
Ok(res[0])
}

Expand Down

0 comments on commit a063f2c

Please sign in to comment.