Skip to content

Commit

Permalink
Fix i2c write (#52)
Browse files Browse the repository at this point in the history
I2C write was unreliable for a few reasons.

    timeouts were very short
    we were only waiting for i2c_bus_busy to be cleared, which sometimes happens before the fifo is empty.

By switching timeouts to use elapsed microseconds (estimated via McycleDelay) and waiting for an empty fifo, I can reliably use a SSD1306 screen now. This was impossible before.

* Add ms_since function to McycleDelay

* Use McycleDelay for i2c timeouts

* Wait for i2c tx fifo empty to disable xmit

* Update i2c timeout docs

* I2C: Replace millisecond with microsecond timeouts
  • Loading branch information
9names authored Jan 11, 2024
1 parent c9f660e commit f1a1f23
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 9 deletions.
10 changes: 10 additions & 0 deletions src/delay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ impl McycleDelay {

while McycleDelay::cycles_since(start_cycle_count) <= cycle_count {}
}

pub fn ms_since(&mut self, previous_cycle_count: u64) -> u64 {
let elapsed_cycles = Self::cycles_since(previous_cycle_count);
(elapsed_cycles * 1000) / self.core_frequency as u64
}

pub fn us_since(&mut self, previous_cycle_count: u64) -> u64 {
let elapsed_cycles = Self::cycles_since(previous_cycle_count);
(elapsed_cycles * 1_000_000) / self.core_frequency as u64
}
}

// embedded-hal 1.0 traits
Expand Down
37 changes: 28 additions & 9 deletions src/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use embedded_hal_zero::blocking::i2c::Read as ReadZero;
use embedded_hal_zero::blocking::i2c::Write as WriteZero;
use embedded_time::rate::Hertz;

use crate::delay::McycleDelay;
use crate::{clock::Clocks, pac};

use self::private::Sealed;
Expand Down Expand Up @@ -98,8 +99,11 @@ where

/// I2C peripheral operating in master mode supporting seven bit addressing
pub struct I2c<I2C, PINS> {
/// i2c peripheral instance
i2c: I2C,
/// sda and scl pins for this i2c interface
pins: PINS,
/// timeout (in microseconds)
timeout: u16,
}

Expand Down Expand Up @@ -174,9 +178,8 @@ where
(self.i2c, self.pins)
}

/// Set the timeout when waiting for fifo (rx and tx).
/// It's not a time unit but the number of cycles to wait.
/// This defaults to 2048
/// Set the timeout (in microseconds) when waiting for fifo (rx and tx).
/// This defaults to 2000us (2 milliseconds)
pub fn set_timeout(&mut self, timeout: u16) {
self.timeout = timeout;
}
Expand Down Expand Up @@ -237,13 +240,15 @@ where
.set_bit()
});

// We don't know what the CPU frequency is. Assume maximum of 192Mhz
// This might make our timeouts longer than expected if frequency is lower.
let mut delay = McycleDelay::new(192_000_000);
for value in tmp.iter_mut() {
let mut timeout_countdown = self.timeout;
let start_time = McycleDelay::get_cycle_count();
while self.i2c.i2c_fifo_config_1.read().rx_fifo_cnt().bits() == 0 {
if timeout_countdown == 0 {
if delay.us_since(start_time) > self.timeout.into() {
return Err(Error::Timeout);
}
timeout_countdown -= 1;
}
*value = self.i2c.i2c_fifo_rdata.read().i2c_fifo_rdata().bits();
}
Expand Down Expand Up @@ -304,21 +309,35 @@ where
.set_bit()
});

// We don't know what the CPU frequency is. Assume maximum of 192Mhz
// This might make our timeouts longer than expected if frequency is lower.
let mut delay = McycleDelay::new(192_000_000);
for value in tmp.iter() {
let mut timeout_countdown = self.timeout;
let start_time = McycleDelay::get_cycle_count();
while self.i2c.i2c_fifo_config_1.read().tx_fifo_cnt().bits() == 0 {
if timeout_countdown == 0 {
if delay.us_since(start_time) > self.timeout.into() {
return Err(Error::Timeout);
}
timeout_countdown -= 1;
}
self.i2c
.i2c_fifo_wdata
.write(|w| unsafe { w.i2c_fifo_wdata().bits(*value) });
}

let start_time = McycleDelay::get_cycle_count();
while self.i2c.i2c_fifo_config_1.read().tx_fifo_cnt().bits() < 2 {
// wait for write fifo to be empty
if delay.us_since(start_time) > self.timeout.into() {
return Err(Error::Timeout);
}
}

let start_time = McycleDelay::get_cycle_count();
while self.i2c.i2c_bus_busy.read().sts_i2c_bus_busy().bit_is_set() {
// wait for transfer to finish
if delay.us_since(start_time) > self.timeout.into() {
return Err(Error::Timeout);
}
}

self.i2c
Expand Down

0 comments on commit f1a1f23

Please sign in to comment.