mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-27 12:20:56 +00:00
Use a configurable deadline (#3577)
This commit is contained in:
parent
f5287c3ef9
commit
1767e74cb6
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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));
|
||||
|
Loading…
x
Reference in New Issue
Block a user