Use a configurable deadline (#3577)

This commit is contained in:
Dániel Buga 2025-06-03 11:44:19 +02:00 committed by GitHub
parent f5287c3ef9
commit 1767e74cb6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 328 additions and 265 deletions

View File

@ -31,6 +31,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Expose ADC asynchrounous functionalities where applicable (#3443)
- Added `UartInterrupt::RxTimeout` support (#3493)
- UART: Added HW and SW flow control config option (#3435)
- I2C master: `SoftwareTimeout` and `Config::with_software_timeout`. (#3577)
- `esp_hal::time::{Instant, Duration}` now implement `Hash` (#3577)
### Changed
@ -101,6 +103,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix a problem where reading/writing flash didn't work when using PSRAM on ESP32 (#3524)
- Fixed `esp_hal::time::Instant::duration_since_epoch` (#3582)
- Improve PSRAM size detection for the case when no PSRAM is present or unusable (#3554)
- ESP32-S2: I2C operations will now time out if the SCL line is kept low. This timeout is controlled by `Config::software_timeout` (#3571, #3577)
- Asynchronous I2C operations are now cancelled if the Future is dropped (#3572)
- The I2C driver will clear the bus after an error, if necessary (#3570)
### Removed

View File

@ -114,7 +114,7 @@ impl PinGuard {
}
}
#[cfg(esp32)]
#[cfg(any(esp32, esp32s2))]
pub(crate) fn pin_number(&self) -> Option<u8> {
if self.pin == u8::MAX {
None

View File

@ -58,7 +58,6 @@ const I2C_FIFO_SIZE: usize = if cfg!(esp32c2) { 16 } else { 32 };
// Chunk writes/reads by this size
const I2C_CHUNK_SIZE: usize = I2C_FIFO_SIZE - 1;
const TIMEOUT: Duration = Duration::from_millis(10);
const CLEAR_BUS_TIMEOUT_MS: Duration = Duration::from_millis(50);
/// Representation of I2C address.
@ -155,6 +154,34 @@ impl BusTimeout {
}
}
/// Software timeout for I2C operations.
///
/// This timeout is used to limit the duration of I2C operations in software.
/// Note that using this in conjunction with `async` operations will cause the
/// task to be woken up continuously until the operation completes or the
/// timeout is reached. You should prefer using an asynchronous
/// timeout mechanism (like [`embassy_time::with_timeout`]) for better
/// efficiency.
///
/// [`embassy_time::with_timeout`]: https://docs.rs/embassy-time/0.4.0/embassy_time/fn.with_timeout.html
#[instability::unstable]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SoftwareTimeout {
/// No software timeout is set.
None,
/// Define a fixed timeout for I2C operations.
Transaction(Duration),
/// Define a data length dependent timeout for I2C operations.
///
/// The applied timeout is calculated as `data_length * duration_per_byte`.
/// In [`I2c::transaction`] and [`I2c::transaction_async`], the timeout is
/// applied separately for each operation.
PerByte(Duration),
}
/// When the FSM remains unchanged for more than the 2^ the given amount of bus
/// clock cycles a timeout will be triggered.
///
@ -471,6 +498,10 @@ pub struct Config {
/// I2C SCL timeout period.
timeout: BusTimeout,
/// Software timeout.
#[builder_lite(unstable)]
software_timeout: SoftwareTimeout,
/// Sets the threshold value for the unchanged period of the SCL_FSM.
#[cfg(not(any(esp32, esp32s2)))]
#[builder_lite(unstable)]
@ -487,6 +518,7 @@ impl Default for Config {
Config {
frequency: Rate::from_khz(100),
timeout: BusTimeout::BusCycles(10),
software_timeout: SoftwareTimeout::PerByte(Duration::from_millis(1)),
#[cfg(not(any(esp32, esp32s2)))]
scl_st_timeout: Default::default(),
#[cfg(not(any(esp32, esp32s2)))]
@ -682,15 +714,12 @@ pub enum Event {
struct I2cFuture<'a> {
events: EnumSet<Event>,
driver: Driver<'a>,
#[cfg(any(esp32, esp32s2))]
timeout: Duration,
#[cfg(any(esp32, esp32s2))]
start: Instant,
deadline: Option<Instant>,
finished: bool,
}
impl<'a> I2cFuture<'a> {
pub fn new(events: EnumSet<Event>, driver: Driver<'a>, timeout: Duration) -> Self {
pub fn new(events: EnumSet<Event>, driver: Driver<'a>, deadline: Option<Instant>) -> Self {
driver.regs().int_ena().modify(|_, w| {
for event in events {
match event {
@ -713,17 +742,18 @@ impl<'a> I2cFuture<'a> {
w
});
Self::new_blocking(events, driver, timeout)
Self::new_blocking(events, driver, deadline)
}
pub fn new_blocking(events: EnumSet<Event>, driver: Driver<'a>, _timeout: Duration) -> Self {
pub fn new_blocking(
events: EnumSet<Event>,
driver: Driver<'a>,
deadline: Option<Instant>,
) -> Self {
Self {
events,
driver,
#[cfg(any(esp32, esp32s2))]
timeout: _timeout,
#[cfg(any(esp32, esp32s2))]
start: Instant::now(),
deadline,
finished: false,
}
}
@ -735,20 +765,30 @@ impl<'a> I2cFuture<'a> {
fn poll_completion(&mut self) -> Poll<Result<(), Error>> {
let error = self.driver.check_errors();
let now = if self.deadline.is_some() {
Instant::now()
} else {
Instant::EPOCH
};
if self.is_done() {
self.finished = true;
Poll::Ready(Ok(()))
// Even though we are done, we have to check for NACK and arbitration loss.
let result = if error == Err(Error::Timeout) {
Ok(())
} else {
error
};
Poll::Ready(result)
} else if error.is_err() {
self.finished = true;
Poll::Ready(error)
} else {
// TODO: it's more efficient to time out with embassy-time, but we can't
// do that in esp-hal. Maybe we should provide an option to disable this
// looping timeout check.
#[cfg(any(esp32, esp32s2))]
if self.start.elapsed() > self.timeout {
// If the timeout is reached, we return an error.
return Poll::Ready(Err(Error::Timeout));
if let Some(deadline) = self.deadline {
if now > deadline {
// If the deadline is reached, we return an error.
return Poll::Ready(Err(Error::Timeout));
}
}
Poll::Pending
@ -764,8 +804,7 @@ impl core::future::Future for I2cFuture<'_> {
let result = self.poll_completion();
#[cfg(any(esp32, esp32s2))]
if result.is_pending() {
if result.is_pending() && self.deadline.is_some() {
ctx.waker().wake_by_ref();
}
@ -896,6 +935,7 @@ where
// Send clock pulses to make sure the slave releases the bus.
self.driver().clear_bus_blocking();
}
self.driver().reset_fsm();
}
@ -1320,6 +1360,23 @@ impl PartialEq for Info {
unsafe impl Sync for Info {}
#[derive(Clone, Copy)]
enum Deadline {
None,
Fixed(Instant),
PerByte(Duration),
}
impl Deadline {
fn start(self, data_len: usize) -> Option<Instant> {
match self {
Deadline::None => None,
Deadline::Fixed(deadline) => Some(deadline),
Deadline::PerByte(duration) => Some(Instant::now() + duration * data_len as u32),
}
}
}
#[allow(dead_code)] // Some versions don't need `state`
#[derive(Clone, Copy)]
struct Driver<'a> {
@ -1740,65 +1797,16 @@ impl Driver<'_> {
return Err(Error::FifoExceeded);
}
if start {
add_cmd(cmd_iterator, Command::Start)?;
}
let write_len = if start { bytes.len() + 1 } else { bytes.len() };
// don't issue write if there is no data to write
if write_len > 0 {
cfg_if::cfg_if! {
if #[cfg(any(esp32, esp32s2))] {
// try to place END at the same index
if write_len < 2 {
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: write_len as u8,
},
)?;
} else if start {
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: (write_len as u8) - 1,
},
)?;
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: 1,
},
)?;
} else {
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: (write_len as u8) - 2,
},
)?;
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: 1,
},
)?;
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: 1,
},
)?;
}
} else {
if cfg!(any(esp32, esp32s2)) {
// try to place END at the same index
if write_len < 2 {
add_cmd(
cmd_iterator,
Command::Write {
@ -1807,12 +1815,61 @@ impl Driver<'_> {
length: write_len as u8,
},
)?;
} else if start {
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: (write_len as u8) - 1,
},
)?;
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: 1,
},
)?;
} else {
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: (write_len as u8) - 2,
},
)?;
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: 1,
},
)?;
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: 1,
},
)?;
}
} else {
add_cmd(
cmd_iterator,
Command::Write {
ack_exp: Ack::Ack,
ack_check_en: true,
length: write_len as u8,
},
)?;
}
}
self.update_registers();
if start {
// Load address and R/W bit into FIFO
match addr {
@ -1860,6 +1917,7 @@ impl Driver<'_> {
}
if start {
add_cmd(cmd_iterator, Command::Start)?;
// WRITE command
add_cmd(
cmd_iterator,
@ -1872,56 +1930,9 @@ impl Driver<'_> {
}
if initial_len > 0 {
cfg_if::cfg_if! {
if #[cfg(any(esp32,esp32s2))] {
// try to place END at the same index
if initial_len < 2 || start {
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: initial_len as u8,
},
)?;
} else if !will_continue {
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: (initial_len as u8) - 1,
},
)?;
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: 1,
},
)?;
} else {
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: (initial_len as u8) - 2,
},
)?;
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: 1,
},
)?;
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: 1,
},
)?;
}
} else {
if cfg!(any(esp32, esp32s2)) {
// try to place END at the same index
if initial_len < 2 || start {
add_cmd(
cmd_iterator,
Command::Read {
@ -1929,7 +1940,52 @@ impl Driver<'_> {
length: initial_len as u8,
},
)?;
} else if !will_continue {
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: (initial_len as u8) - 1,
},
)?;
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: 1,
},
)?;
} else {
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: (initial_len as u8) - 2,
},
)?;
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: 1,
},
)?;
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: 1,
},
)?;
}
} else {
add_cmd(
cmd_iterator,
Command::Read {
ack_value: Ack::Ack,
length: initial_len as u8,
},
)?;
}
}
@ -1984,85 +2040,78 @@ impl Driver<'_> {
.write(|w| unsafe { w.bits(I2C_LL_INTR_MASK) });
}
async fn wait_for_completion(&self, end_only: bool, timeout: Duration) -> Result<(), Error> {
// TODO: the timeout should depend on the bus frequency and the length of the
// write operation, or it should be user-configurable.
let event = if end_only {
Event::EndDetect.into()
} else {
Event::TxComplete | Event::EndDetect
};
I2cFuture::new(event, *self, timeout).await?;
self.check_all_commands_done().await
async fn wait_for_completion(&self, deadline: Option<Instant>) -> Result<(), Error> {
I2cFuture::new(Event::TxComplete | Event::EndDetect, *self, deadline).await?;
self.check_all_commands_done(deadline).await
}
/// Waits for the completion of an I2C transaction.
fn wait_for_completion_blocking(&self, end_only: bool, timeout: Duration) -> Result<(), Error> {
let event = if end_only {
Event::EndDetect.into()
} else {
Event::TxComplete | Event::EndDetect
};
let mut future = I2cFuture::new_blocking(event, *self, timeout);
fn wait_for_completion_blocking(&self, deadline: Option<Instant>) -> Result<(), Error> {
let mut future =
I2cFuture::new_blocking(Event::TxComplete | Event::EndDetect, *self, deadline);
loop {
if let Poll::Ready(result) = future.poll_completion() {
result?;
return self.check_all_commands_done_blocking();
return self.check_all_commands_done_blocking(deadline);
}
}
}
fn all_commands_done(&self) -> bool {
fn all_commands_done(&self, deadline: Option<Instant>) -> Result<bool, Error> {
// NOTE: on esp32 executing the end command generates the end_detect interrupt
// but does not seem to clear the done bit! So we don't check the done
// status of an end command
let now = if deadline.is_some() {
Instant::now()
} else {
Instant::EPOCH
};
for cmd_reg in self.regs().comd_iter() {
let cmd = cmd_reg.read();
// if there is a valid command which is not END, check if it's marked as done
if cmd.bits() != 0x0 && !cmd.opcode().is_end() && !cmd.command_done().bit_is_set() {
// Let's retry
return false;
if let Some(deadline) = deadline {
if now > deadline {
return Err(Error::ExecutionIncomplete);
}
}
return Ok(false);
}
// once we hit END or STOP we can break the loop
if cmd.opcode().is_end() || cmd.opcode().is_stop() {
if cmd.opcode().is_end() {
break;
}
if cmd.opcode().is_stop() {
#[cfg(esp32)]
// wait for STOP - apparently on ESP32 we otherwise miss the ACK error for an
// empty write
if self.regs().sr().read().scl_state_last() == 6 {
self.check_errors()?;
} else {
continue;
}
break;
}
}
true
Ok(true)
}
/// Checks whether all I2C commands have completed execution.
fn check_all_commands_done_blocking(&self) -> Result<(), Error> {
// NOTE: on esp32 executing the end command generates the end_detect interrupt
// but does not seem to clear the done bit! So we don't check the done
// status of an end command
fn check_all_commands_done_blocking(&self, deadline: Option<Instant>) -> Result<(), Error> {
// loop until commands are actually done
let start = Instant::now();
while !self.all_commands_done() {
if Instant::now() - start > TIMEOUT {
return Err(Error::ExecutionIncomplete);
}
}
while !self.all_commands_done(deadline)? {}
Ok(())
}
/// Checks whether all I2C commands have completed execution.
async fn check_all_commands_done(&self) -> Result<(), Error> {
// NOTE: on esp32 executing the end command generates the end_detect interrupt
// but does not seem to clear the done bit! So we don't check the done
// status of an end command
async fn check_all_commands_done(&self, deadline: Option<Instant>) -> Result<(), Error> {
// loop until commands are actually done
let start = Instant::now();
while !self.all_commands_done() {
if Instant::now() - start > TIMEOUT {
return Err(Error::ExecutionIncomplete);
}
while !self.all_commands_done(deadline)? {
embassy_futures::yield_now().await;
}
@ -2190,19 +2239,22 @@ impl Driver<'_> {
address: I2cAddress,
bytes: &[u8],
start: bool,
) -> Result<(), Error> {
stop: bool,
deadline: Deadline,
) -> Result<Option<Instant>, Error> {
let cmd_iterator = &mut self.regs().comd_iter();
if start {
add_cmd(cmd_iterator, Command::Start)?;
}
self.setup_write(address, bytes, start, cmd_iterator)?;
add_cmd(cmd_iterator, Command::End)?;
if stop {
add_cmd(cmd_iterator, Command::Stop)?;
} else {
add_cmd(cmd_iterator, Command::End)?;
}
self.start_transmission();
let deadline = deadline.start(bytes.len() + start as usize);
Ok(())
Ok(deadline)
}
/// Executes an I2C read operation.
@ -2221,18 +2273,22 @@ impl Driver<'_> {
buffer: &mut [u8],
start: bool,
will_continue: bool,
) -> Result<(), Error> {
stop: bool,
deadline: Deadline,
) -> Result<Option<Instant>, Error> {
let cmd_iterator = &mut self.regs().comd_iter();
if start {
add_cmd(cmd_iterator, Command::Start)?;
}
self.setup_read(address, buffer, start, will_continue, cmd_iterator)?;
add_cmd(cmd_iterator, Command::End)?;
if stop {
add_cmd(cmd_iterator, Command::Stop)?;
} else {
add_cmd(cmd_iterator, Command::End)?;
}
self.start_transmission();
Ok(())
let deadline = deadline.start(buffer.len() + start as usize);
Ok(deadline)
}
/// Executes an I2C write operation.
@ -2249,6 +2305,7 @@ impl Driver<'_> {
bytes: &[u8],
start: bool,
stop: bool,
deadline: Deadline,
) -> Result<(), Error> {
address.validate()?;
self.reset_before_transmission();
@ -2259,12 +2316,8 @@ impl Driver<'_> {
return Ok(());
}
self.start_write_operation(address, bytes, start)?;
self.wait_for_completion_blocking(true, TIMEOUT)?;
if stop {
self.stop_operation_blocking()?;
}
let deadline = self.start_write_operation(address, bytes, start, stop, deadline)?;
self.wait_for_completion_blocking(deadline)?;
Ok(())
}
@ -2286,6 +2339,7 @@ impl Driver<'_> {
start: bool,
stop: bool,
will_continue: bool,
deadline: Deadline,
) -> Result<(), Error> {
address.validate()?;
self.reset_before_transmission();
@ -2296,33 +2350,11 @@ impl Driver<'_> {
return Ok(());
}
self.start_read_operation(address, buffer, start, will_continue)?;
self.wait_for_completion_blocking(true, TIMEOUT)?;
let deadline =
self.start_read_operation(address, buffer, start, will_continue, stop, deadline)?;
self.wait_for_completion_blocking(deadline)?;
self.read_all_from_fifo(buffer)?;
if stop {
self.stop_operation_blocking()?;
}
Ok(())
}
/// Perform a single STOP
fn stop_operation_blocking(&self) -> Result<(), Error> {
let cmd_iterator = &mut self.regs().comd_iter();
add_cmd(cmd_iterator, Command::Stop)?;
self.start_transmission();
self.wait_for_completion_blocking(false, TIMEOUT)?;
#[cfg(esp32)]
loop {
// wait for STOP - apparently on ESP32 we otherwise miss the ACK error for an
// empty write
if self.regs().sr().read().scl_state_last() == 0b110 {
self.check_errors()?;
break;
}
}
Ok(())
}
@ -2340,6 +2372,7 @@ impl Driver<'_> {
bytes: &[u8],
start: bool,
stop: bool,
deadline: Deadline,
) -> Result<(), Error> {
address.validate()?;
self.reset_before_transmission();
@ -2350,12 +2383,8 @@ impl Driver<'_> {
return Ok(());
}
self.start_write_operation(address, bytes, start)?;
self.wait_for_completion(true, TIMEOUT).await?;
if stop {
self.stop_operation().await?;
}
let deadline = self.start_write_operation(address, bytes, start, stop, deadline)?;
self.wait_for_completion(deadline).await?;
Ok(())
}
@ -2377,6 +2406,7 @@ impl Driver<'_> {
start: bool,
stop: bool,
will_continue: bool,
deadline: Deadline,
) -> Result<(), Error> {
address.validate()?;
self.reset_before_transmission();
@ -2387,35 +2417,11 @@ impl Driver<'_> {
return Ok(());
}
self.start_read_operation(address, buffer, start, will_continue)?;
self.wait_for_completion(true, TIMEOUT).await?;
let deadline =
self.start_read_operation(address, buffer, start, will_continue, stop, deadline)?;
self.wait_for_completion(deadline).await?;
self.read_all_from_fifo(buffer)?;
if stop {
self.stop_operation().await?;
}
Ok(())
}
/// Perform a single STOP
async fn stop_operation(&self) -> Result<(), Error> {
let cmd_iterator = &mut self.regs().comd_iter();
add_cmd(cmd_iterator, Command::Stop)?;
self.start_transmission();
self.wait_for_completion(false, TIMEOUT).await?;
#[cfg(esp32)]
loop {
// wait for STOP - apparently on ESP32 we otherwise miss the ACK error for an
// empty write
if self.regs().sr().read().scl_state_last() == 0b110 {
self.check_errors()?;
embassy_futures::yield_now().await;
break;
}
}
Ok(())
}
@ -2426,6 +2432,7 @@ impl Driver<'_> {
start: bool,
stop: bool,
will_continue: bool,
deadline: Deadline,
) -> Result<(), Error> {
let chunk_count = VariableChunkIterMut::new(buffer).count();
for (idx, chunk) in VariableChunkIterMut::new(buffer).enumerate() {
@ -2435,6 +2442,7 @@ impl Driver<'_> {
start && idx == 0,
stop && idx == chunk_count - 1,
will_continue || idx < chunk_count - 1,
deadline,
)?;
}
@ -2447,9 +2455,10 @@ impl Driver<'_> {
buffer: &[u8],
start: bool,
stop: bool,
deadline: Deadline,
) -> Result<(), Error> {
if buffer.is_empty() {
return self.write_operation_blocking(address, &[], start, stop);
return self.write_operation_blocking(address, &[], start, stop, deadline);
}
let chunk_count = VariableChunkIter::new(buffer).count();
@ -2459,6 +2468,7 @@ impl Driver<'_> {
chunk,
start && idx == 0,
stop && idx == chunk_count - 1,
deadline,
)?;
}
@ -2472,6 +2482,7 @@ impl Driver<'_> {
start: bool,
stop: bool,
will_continue: bool,
deadline: Deadline,
) -> Result<(), Error> {
let chunk_count = VariableChunkIterMut::new(buffer).count();
for (idx, chunk) in VariableChunkIterMut::new(buffer).enumerate() {
@ -2481,6 +2492,7 @@ impl Driver<'_> {
start && idx == 0,
stop && idx == chunk_count - 1,
will_continue || idx < chunk_count - 1,
deadline,
)
.await?;
}
@ -2494,9 +2506,12 @@ impl Driver<'_> {
buffer: &[u8],
start: bool,
stop: bool,
deadline: Deadline,
) -> Result<(), Error> {
if buffer.is_empty() {
return self.write_operation(address, &[], start, stop).await;
return self
.write_operation(address, &[], start, stop, deadline)
.await;
}
let chunk_count = VariableChunkIter::new(buffer).count();
@ -2506,6 +2521,7 @@ impl Driver<'_> {
chunk,
start && idx == 0,
stop && idx == chunk_count - 1,
deadline,
)
.await?;
}
@ -2521,6 +2537,12 @@ impl Driver<'_> {
address.validate()?;
self.ensure_idle_blocking();
let mut deadline = Deadline::None;
if let SoftwareTimeout::Transaction(timeout) = self.config.config.software_timeout {
deadline = Deadline::Fixed(Instant::now() + timeout);
}
let mut last_op: Option<OpKind> = None;
// filter out 0 length read operations
let mut op_iter = operations
@ -2535,14 +2557,21 @@ impl Driver<'_> {
// execute a write operation:
// - issue START/RSTART if op is different from previous
// - issue STOP if op is the last one
if let SoftwareTimeout::PerByte(timeout) = self.config.config.software_timeout {
deadline = Deadline::PerByte(timeout);
}
self.write_blocking(
address,
buffer,
!matches!(last_op, Some(OpKind::Write)),
next_op.is_none(),
deadline,
)?;
}
Operation::Read(buffer) => {
if let SoftwareTimeout::PerByte(timeout) = self.config.config.software_timeout {
deadline = Deadline::PerByte(timeout);
}
// execute a read operation:
// - issue START/RSTART if op is different from previous
// - issue STOP if op is the last one
@ -2553,6 +2582,7 @@ impl Driver<'_> {
!matches!(last_op, Some(OpKind::Read)),
next_op.is_none(),
matches!(next_op, Some(OpKind::Read)),
deadline,
)?;
}
}
@ -2571,6 +2601,12 @@ impl Driver<'_> {
address.validate()?;
self.ensure_idle().await;
let mut deadline = Deadline::None;
if let SoftwareTimeout::Transaction(timeout) = self.config.config.software_timeout {
deadline = Deadline::Fixed(Instant::now() + timeout);
}
let mut last_op: Option<OpKind> = None;
// filter out 0 length read operations
let mut op_iter = operations
@ -2582,6 +2618,9 @@ impl Driver<'_> {
let kind = op.kind();
match op {
Operation::Write(buffer) => {
if let SoftwareTimeout::PerByte(timeout) = self.config.config.software_timeout {
deadline = Deadline::PerByte(timeout);
}
// execute a write operation:
// - issue START/RSTART if op is different from previous
// - issue STOP if op is the last one
@ -2590,10 +2629,14 @@ impl Driver<'_> {
buffer,
!matches!(last_op, Some(OpKind::Write)),
next_op.is_none(),
deadline,
)
.await?;
}
Operation::Read(buffer) => {
if let SoftwareTimeout::PerByte(timeout) = self.config.config.software_timeout {
deadline = Deadline::PerByte(timeout);
}
// execute a read operation:
// - issue START/RSTART if op is different from previous
// - issue STOP if op is the last one
@ -2604,6 +2647,7 @@ impl Driver<'_> {
!matches!(last_op, Some(OpKind::Read)),
next_op.is_none(),
matches!(next_op, Some(OpKind::Read)),
deadline,
)
.await?;
}
@ -2688,7 +2732,7 @@ fn calculate_chunk_size(remaining: usize) -> usize {
}
}
#[cfg(not(esp32))]
#[cfg(not(any(esp32, esp32s2)))]
mod bus_clear {
use super::*;
@ -2739,7 +2783,7 @@ mod bus_clear {
}
}
#[cfg(esp32)]
#[cfg(any(esp32, esp32s2))]
mod bus_clear {
use super::*;
use crate::gpio::AnyPin;
@ -2774,8 +2818,9 @@ mod bus_clear {
const SCL_DELAY: Duration = Duration::from_micros(5);
pub fn new(driver: Driver<'a>) -> Self {
// The chip is lacking hardware support for this, so we
// implement it in software.
// ESP32: The chip is lacking hardware support for bus clearing.
// ESP32-S2: The hardware bus clearing doesn't seem to work
// -> so we implement it in software.
let sda = driver
.config
.sda_pin
@ -2829,6 +2874,7 @@ mod bus_clear {
State::Idle => return Poll::Ready(()),
_ if now < self.wait => {
// Still waiting for the end of the SCL pulse
return Poll::Pending;
}
State::SendStop => {
if let Some(sda) = self.sda.as_ref() {
@ -2844,23 +2890,21 @@ mod bus_clear {
scl.set_output_high(true);
}
self.state = State::SendStop;
self.wait = now + Self::SCL_DELAY;
}
State::SendClock(n, false) => {
if let Some(scl) = self.scl.as_ref() {
scl.set_output_high(true);
}
self.state = State::SendClock(n - 1, true);
self.wait = now + Self::SCL_DELAY;
}
State::SendClock(n, true) => {
if let Some(scl) = self.scl.as_ref() {
scl.set_output_high(false);
}
self.state = State::SendClock(n, false);
self.wait = now + Self::SCL_DELAY;
}
}
self.wait = Instant::now() + Self::SCL_DELAY;
Poll::Pending
}

View File

@ -155,6 +155,13 @@ impl defmt::Format for Instant {
}
}
impl core::hash::Hash for Instant {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.duration_since_epoch().hash(state);
}
}
impl Instant {
/// Represents the moment the system booted.
pub const EPOCH: Instant = Instant(InnerInstant::from_ticks(0));
@ -257,6 +264,13 @@ impl defmt::Format for Duration {
}
}
impl core::hash::Hash for Duration {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_micros().hash(state);
}
}
impl Duration {
/// A duration of zero time.
pub const ZERO: Self = Self(InnerDuration::from_ticks(0));