Skip to content

Commit

Permalink
Merge #542
Browse files Browse the repository at this point in the history
542: Transactional I2C r=burrbull a=SpeedCrash100

A transactional HAL API implementation for I2C. Closes #445.

I have to rework i2c code to implement this:
  * `prepare_read`, `prepare_write` - methods to send start and address only
  * `write_wo_prepare`, `read_wo_prepare` - methods  like old `read`, `write` but they do not send start and address.
  * `read_bytes`, `write_bytes` - this methods now only for send/receive. The do not send start, stop or address.
  * All public api's behaviour is untouched so there are no breaking changes for users.

Note: I have to create duplicate methods `transaction_slice` and `transaction` because `Operation` doesn't implement Clone to send slice as iterator.

Co-authored-by: SpeedCrash100 <[email protected]>
  • Loading branch information
bors[bot] and SpeedCrash100 authored Oct 18, 2022
2 parents 99b34a0 + 1eacc02 commit 6d0c292
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 43 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- example of using i2s in out with rtic and interrupt.
- example of using USB CDC with interrupts.
- Added non-blocking I2C based on DMA [#534]
- Added Transactional I2C API [#542]

[#481]: https://github.com/stm32-rs/stm32f4xx-hal/pull/481
[#489]: https://github.com/stm32-rs/stm32f4xx-hal/pull/489
Expand All @@ -59,6 +60,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
[#536]: https://github.com/stm32-rs/stm32f4xx-hal/pull/536
[#534]: https://github.com/stm32-rs/stm32f4xx-hal/pull/529
[#540]: https://github.com/stm32-rs/stm32f4xx-hal/pull/540
[#542]: https://github.com/stm32-rs/stm32f4xx-hal/pull/542

## [v0.13.2] - 2022-05-16

Expand Down
194 changes: 156 additions & 38 deletions src/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::gpio::{Const, OpenDrain, PinA, SetAlternate};
use crate::pac::RCC;

use crate::rcc::Clocks;
use embedded_hal_one::i2c::blocking::Operation;
use fugit::{HertzU32 as Hertz, RateExtU32};

mod hal_02;
Expand Down Expand Up @@ -313,7 +314,9 @@ impl<I2C: Instance, PINS> I2c<I2C, PINS> {
Ok(sr1)
}

fn write_bytes(&mut self, addr: u8, bytes: impl Iterator<Item = u8>) -> Result<(), Error> {
/// Sends START and Address for writing
#[inline(always)]
fn prepare_write(&self, addr: u8) -> Result<(), Error> {
// Send a START condition
self.i2c.cr1.modify(|_, w| w.start().set_bit());

Expand Down Expand Up @@ -351,6 +354,46 @@ impl<I2C: Instance, PINS> I2c<I2C, PINS> {
// Clear condition by reading SR2
self.i2c.sr2.read();

Ok(())
}

/// Sends START and Address for reading
fn prepare_read(&self, addr: u8) -> Result<(), Error> {
// Send a START condition and set ACK bit
self.i2c
.cr1
.modify(|_, w| w.start().set_bit().ack().set_bit());

// Wait until START condition was generated
while self.i2c.sr1.read().sb().bit_is_clear() {}

// Also wait until signalled we're master and everything is waiting for us
while {
let sr2 = self.i2c.sr2.read();
sr2.msl().bit_is_clear() && sr2.busy().bit_is_clear()
} {}

// Set up current address, we're trying to talk to
self.i2c
.dr
.write(|w| unsafe { w.bits((u32::from(addr) << 1) + 1) });

// Wait until address was sent
loop {
self.check_and_clear_error_flags()
.map_err(Error::nack_addr)?;
if self.i2c.sr1.read().addr().bit_is_set() {
break;
}
}

// Clear condition by reading SR2
self.i2c.sr2.read();

Ok(())
}

fn write_bytes(&mut self, bytes: impl Iterator<Item = u8>) -> Result<(), Error> {
// Send bytes
for c in bytes {
self.send_byte(c)?;
Expand Down Expand Up @@ -400,43 +443,29 @@ impl<I2C: Instance, PINS> I2c<I2C, PINS> {
Ok(value)
}

pub fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> {
if let Some((last, buffer)) = buffer.split_last_mut() {
// Send a START condition and set ACK bit
self.i2c
.cr1
.modify(|_, w| w.start().set_bit().ack().set_bit());

// Wait until START condition was generated
while self.i2c.sr1.read().sb().bit_is_clear() {}
fn read_bytes(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
// Receive bytes into buffer
for c in buffer {
*c = self.recv_byte()?;
}

// Also wait until signalled we're master and everything is waiting for us
while {
let sr2 = self.i2c.sr2.read();
sr2.msl().bit_is_clear() && sr2.busy().bit_is_clear()
} {}
Ok(())
}

// Set up current address, we're trying to talk to
self.i2c
.dr
.write(|w| unsafe { w.bits((u32::from(addr) << 1) + 1) });

// Wait until address was sent
loop {
self.check_and_clear_error_flags()
.map_err(Error::nack_addr)?;
if self.i2c.sr1.read().addr().bit_is_set() {
break;
}
}
pub fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> {
if buffer.is_empty() {
return Err(Error::Overrun);
}

// Clear condition by reading SR2
self.i2c.sr2.read();
self.prepare_read(addr)?;
self.read_wo_prepare(buffer)
}

// Receive bytes into buffer
for c in buffer {
*c = self.recv_byte()?;
}
/// Reads like normal but does'n genereate start and don't send address
fn read_wo_prepare(&mut self, buffer: &mut [u8]) -> Result<(), Error> {
if let Some((last, buffer)) = buffer.split_last_mut() {
// Read all bytes but not last
self.read_bytes(buffer)?;

// Prepare to send NACK then STOP after next byte
self.i2c
Expand All @@ -457,7 +486,13 @@ impl<I2C: Instance, PINS> I2c<I2C, PINS> {
}

pub fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> {
self.write_bytes(addr, bytes.iter().cloned())?;
self.prepare_write(addr)?;
self.write_wo_prepare(bytes)
}

/// Writes like normal but does'n genereate start and don't send address
fn write_wo_prepare(&mut self, bytes: &[u8]) -> Result<(), Error> {
self.write_bytes(bytes.iter().cloned())?;

// Send a STOP condition
self.i2c.cr1.modify(|_, w| w.stop().set_bit());
Expand All @@ -473,7 +508,8 @@ impl<I2C: Instance, PINS> I2c<I2C, PINS> {
where
B: IntoIterator<Item = u8>,
{
self.write_bytes(addr, bytes.into_iter())?;
self.prepare_write(addr)?;
self.write_bytes(bytes.into_iter())?;

// Send a STOP condition
self.i2c.cr1.modify(|_, w| w.stop().set_bit());
Expand All @@ -486,15 +522,97 @@ impl<I2C: Instance, PINS> I2c<I2C, PINS> {
}

pub fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> {
self.write_bytes(addr, bytes.iter().cloned())?;
self.prepare_write(addr)?;
self.write_bytes(bytes.iter().cloned())?;
self.read(addr, buffer)
}

pub fn write_iter_read<B>(&mut self, addr: u8, bytes: B, buffer: &mut [u8]) -> Result<(), Error>
where
B: IntoIterator<Item = u8>,
{
self.write_bytes(addr, bytes.into_iter())?;
self.prepare_write(addr)?;
self.write_bytes(bytes.into_iter())?;
self.read(addr, buffer)
}

pub fn transaction<'a>(
&mut self,
addr: u8,
mut ops: impl Iterator<Item = Operation<'a>>,
) -> Result<(), Error> {
if let Some(mut prev_op) = ops.next() {
// 1. Generate Start for operation
match &prev_op {
Operation::Read(_) => self.prepare_read(addr)?,
Operation::Write(_) => self.prepare_write(addr)?,
};

for op in ops {
// 2. Execute previous operations.
match &mut prev_op {
Operation::Read(rb) => self.read_bytes(*rb)?,
Operation::Write(wb) => self.write_bytes(wb.iter().cloned())?,
};
// 3. If operation changes type we must generate new start
match (&prev_op, &op) {
(Operation::Read(_), Operation::Write(_)) => self.prepare_write(addr)?,
(Operation::Write(_), Operation::Read(_)) => self.prepare_read(addr)?,
_ => {} // No changes if operation have not changed
}

prev_op = op;
}

// 4. Now, prev_op is last command use methods variations that will generate stop
match prev_op {
Operation::Read(rb) => self.read_wo_prepare(rb)?,
Operation::Write(wb) => self.write_wo_prepare(wb)?,
};
}

// Fallthrough is success
Ok(())
}

pub fn transaction_slice<'a>(
&mut self,
addr: u8,
ops_slice: &mut [Operation<'a>],
) -> Result<(), Error> {
let mut ops = ops_slice.iter_mut();

if let Some(mut prev_op) = ops.next() {
// 1. Generate Start for operation
match &prev_op {
Operation::Read(_) => self.prepare_read(addr)?,
Operation::Write(_) => self.prepare_write(addr)?,
};

for op in ops {
// 2. Execute previous operations.
match &mut prev_op {
Operation::Read(rb) => self.read_bytes(*rb)?,
Operation::Write(wb) => self.write_bytes(wb.iter().cloned())?,
};
// 3. If operation changes type we must generate new start
match (&prev_op, &op) {
(Operation::Read(_), Operation::Write(_)) => self.prepare_write(addr)?,
(Operation::Write(_), Operation::Read(_)) => self.prepare_read(addr)?,
_ => {} // No changes if operation have not changed
}

prev_op = op;
}

// 4. Now, prev_op is last command use methods variations that will generate stop
match prev_op {
Operation::Read(rb) => self.read_wo_prepare(rb)?,
Operation::Write(wb) => self.write_wo_prepare(wb)?,
};
}

// Fallthrough is success
Ok(())
}
}
11 changes: 6 additions & 5 deletions src/i2c/hal_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,18 @@ mod blocking {

fn transaction<'a>(
&mut self,
_addr: u8,
_operations: &mut [Operation<'a>],
addr: u8,
operations: &mut [Operation<'a>],
) -> Result<(), Self::Error> {
todo!()
self.transaction_slice(addr, operations)
}

fn transaction_iter<'a, O>(&mut self, _addr: u8, _operations: O) -> Result<(), Self::Error>
fn transaction_iter<'a, O>(&mut self, addr: u8, operations: O) -> Result<(), Self::Error>
where
O: IntoIterator<Item = Operation<'a>>,
{
todo!()
let it = operations.into_iter();
self.transaction(addr, it)
}
}
}

0 comments on commit 6d0c292

Please sign in to comment.