mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 04:40:52 +00:00
I2C: Fix bus clearing/error recovery (#4000)
* Run test with multiple timeouts * Align hw_bus_clear option with esp-idf * Generate stop condition after HW bus clearing * Tweak software bus clearing algo * Make sure I2C is always reset * Properly set finished flag even on timeouts * Fix reset/clear interactions * Add a note about blocking in Drop * Add changelog entry
This commit is contained in:
parent
b5a7397134
commit
5f1c1feed9
@ -60,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- ESP32, ESP32-S2: Fixed I2C bus clearing algorithm (#3926)
|
||||
- Check serial instead of jtag fifo status in UsbSerialJtag's async flush function (#3957)
|
||||
- ESP32: Enable up to 4M of PSRAM (#3990)
|
||||
- I2C error recovery logic issues (#4000)
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -683,7 +683,7 @@ impl<Dm: DriverMode> embedded_hal::i2c::I2c for I2c<'_, Dm> {
|
||||
I2cAddress::SevenBit(address),
|
||||
operations.iter_mut().map(Operation::from),
|
||||
)
|
||||
.inspect_err(|_| self.internal_recover())
|
||||
.inspect_err(|error| self.internal_recover(error))
|
||||
}
|
||||
}
|
||||
|
||||
@ -824,6 +824,7 @@ struct I2cFuture<'a> {
|
||||
events: EnumSet<Event>,
|
||||
driver: Driver<'a>,
|
||||
deadline: Option<Instant>,
|
||||
/// True if the Future has been polled to completion.
|
||||
finished: bool,
|
||||
}
|
||||
|
||||
@ -882,28 +883,32 @@ impl<'a> I2cFuture<'a> {
|
||||
};
|
||||
let error = self.driver.check_errors();
|
||||
|
||||
if self.is_done() {
|
||||
self.finished = true;
|
||||
let result = if self.is_done() {
|
||||
// Even though we are done, we have to check for NACK and arbitration loss.
|
||||
let result = if error == Err(Error::Timeout) {
|
||||
// We are both done, and timed out. Likely the transaction has completed, but we
|
||||
// checked too late?
|
||||
Ok(())
|
||||
} else {
|
||||
error
|
||||
};
|
||||
Poll::Ready(result)
|
||||
} else if error.is_err() {
|
||||
self.finished = true;
|
||||
Poll::Ready(error)
|
||||
} else if let Some(deadline) = self.deadline
|
||||
&& now > deadline
|
||||
{
|
||||
// If the deadline is reached, we return an error.
|
||||
Poll::Ready(Err(Error::Timeout))
|
||||
} else {
|
||||
if let Some(deadline) = self.deadline
|
||||
&& now > deadline
|
||||
{
|
||||
// If the deadline is reached, we return an error.
|
||||
return Poll::Ready(Err(Error::Timeout));
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
};
|
||||
|
||||
if result.is_ready() {
|
||||
self.finished = true;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@ -926,8 +931,10 @@ impl core::future::Future for I2cFuture<'_> {
|
||||
impl Drop for I2cFuture<'_> {
|
||||
fn drop(&mut self) {
|
||||
if !self.finished {
|
||||
self.driver.reset_fsm();
|
||||
self.driver.clear_bus_blocking();
|
||||
let result = self.poll_completion();
|
||||
if result.is_pending() || result == Poll::Ready(Err(Error::Timeout)) {
|
||||
self.driver.reset_fsm(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -949,7 +956,10 @@ impl<'d> I2c<'d, Async> {
|
||||
}
|
||||
|
||||
#[procmacros::doc_replace]
|
||||
/// Writes bytes to slave with given `address`
|
||||
/// Writes bytes to slave with given `address`.
|
||||
///
|
||||
/// Note that dropping the returned Future will abort the transfer, but doing so will
|
||||
/// block while the driver is finishing clearing and releasing the bus.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
@ -975,7 +985,10 @@ impl<'d> I2c<'d, Async> {
|
||||
}
|
||||
|
||||
#[procmacros::doc_replace]
|
||||
/// Reads enough bytes from slave with `address` to fill `buffer`
|
||||
/// Reads enough bytes from slave with `address` to fill `buffer`.
|
||||
///
|
||||
/// Note that dropping the returned Future will abort the transfer, but doing so will
|
||||
/// block while the driver is finishing clearing and releasing the bus.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
@ -1008,7 +1021,10 @@ impl<'d> I2c<'d, Async> {
|
||||
|
||||
#[procmacros::doc_replace]
|
||||
/// Writes bytes to slave with given `address` and then reads enough
|
||||
/// bytes to fill `buffer` *in a single transaction*
|
||||
/// bytes to fill `buffer` *in a single transaction*.
|
||||
///
|
||||
/// Note that dropping the returned Future will abort the transfer, but doing so will
|
||||
/// block while the driver is finishing clearing and releasing the bus.
|
||||
///
|
||||
/// ## Errors
|
||||
///
|
||||
@ -1045,8 +1061,10 @@ impl<'d> I2c<'d, Async> {
|
||||
}
|
||||
|
||||
#[procmacros::doc_replace]
|
||||
/// Execute the provided operations on the I2C bus as a single
|
||||
/// transaction.
|
||||
/// Execute the provided operations on the I2C bus as a single transaction.
|
||||
///
|
||||
/// Note that dropping the returned Future will abort the transfer, but doing so will
|
||||
/// block while the driver is finishing clearing and releasing the bus.
|
||||
///
|
||||
/// Transaction contract:
|
||||
/// - Before executing the first operation an ST is sent automatically. This is followed by
|
||||
@ -1098,7 +1116,7 @@ impl<'d> I2c<'d, Async> {
|
||||
self.driver()
|
||||
.transaction_impl_async(address.into(), operations.into_iter().map(Operation::from))
|
||||
.await
|
||||
.inspect_err(|_| self.internal_recover())
|
||||
.inspect_err(|error| self.internal_recover(error))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1114,13 +1132,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_recover(&self) {
|
||||
if self.driver().regs().sr().read().bus_busy().bit_is_set() {
|
||||
// Send clock pulses to make sure the slave releases the bus.
|
||||
self.driver().clear_bus_blocking();
|
||||
}
|
||||
|
||||
self.driver().reset_fsm();
|
||||
fn internal_recover(&self, error: &Error) {
|
||||
// Timeout errors mean our hardware is (possibly) working when it gets reset. Clear the bus
|
||||
// in this case, to prevent leaving the I2C device mid-transfer.
|
||||
self.driver().reset_fsm(*error == Error::Timeout)
|
||||
}
|
||||
|
||||
/// Connect a pin to the I2C SDA signal.
|
||||
@ -1293,7 +1308,7 @@ where
|
||||
) -> Result<(), Error> {
|
||||
self.driver()
|
||||
.transaction_impl(address.into(), operations.into_iter().map(Operation::from))
|
||||
.inspect_err(|_| self.internal_recover())
|
||||
.inspect_err(|error| self.internal_recover(error))
|
||||
}
|
||||
|
||||
#[procmacros::doc_replace]
|
||||
@ -1317,7 +1332,7 @@ where
|
||||
pub fn apply_config(&mut self, config: &Config) -> Result<(), ConfigError> {
|
||||
self.config.config = *config;
|
||||
self.driver().setup(config)?;
|
||||
self.driver().reset_fsm();
|
||||
self.driver().reset_fsm(false);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1331,7 +1346,7 @@ impl embedded_hal_async::i2c::I2c for I2c<'_, Async> {
|
||||
self.driver()
|
||||
.transaction_impl_async(address.into(), operations.iter_mut().map(Operation::from))
|
||||
.await
|
||||
.inspect_err(|_| self.internal_recover())
|
||||
.inspect_err(|error| self.internal_recover(error))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1660,15 +1675,7 @@ impl Driver<'_> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resets the I2C controller (FIFO + FSM + command list)
|
||||
// This function implements esp-idf's `s_i2c_hw_fsm_reset`, without the
|
||||
// clear_bus=true parts.
|
||||
// https://github.com/espressif/esp-idf/blob/27d68f57e6bdd3842cd263585c2c352698a9eda2/components/esp_driver_i2c/i2c_master.c#L115
|
||||
//
|
||||
// Make sure you don't call this function in the middle of a transaction. If the
|
||||
// first command in the command list is not a START, the hardware will hang
|
||||
// with no timeouts.
|
||||
fn reset_fsm(&self) {
|
||||
fn do_fsm_reset(&self) {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(i2c_master_has_reliable_fsm_reset)] {
|
||||
// Device has a working FSM reset mechanism
|
||||
@ -1677,9 +1684,7 @@ impl Driver<'_> {
|
||||
// Even though C2 and C3 have a FSM reset bit, esp-idf does not
|
||||
// define SOC_I2C_SUPPORT_HW_FSM_RST for them, so include them in the fallback impl.
|
||||
|
||||
// Do the reset
|
||||
crate::system::PeripheralClockControl::disable(self.info.peripheral);
|
||||
crate::system::PeripheralClockControl::enable(self.info.peripheral);
|
||||
crate::system::PeripheralClockControl::reset(self.info.peripheral);
|
||||
|
||||
// Restore configuration. This operation has succeeded once, so we can
|
||||
// assume that the config is valid and we can ignore the result.
|
||||
@ -1688,15 +1693,34 @@ impl Driver<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the I2C controller (FIFO + FSM + command list)
|
||||
// This function implements esp-idf's `s_i2c_hw_fsm_reset`
|
||||
// https://github.com/espressif/esp-idf/blob/27d68f57e6bdd3842cd263585c2c352698a9eda2/components/esp_driver_i2c/i2c_master.c#L115
|
||||
//
|
||||
// Make sure you don't call this function in the middle of a transaction. If the
|
||||
// first command in the command list is not a START, the hardware will hang
|
||||
// with no timeouts.
|
||||
fn reset_fsm(&self, clear_bus: bool) {
|
||||
if clear_bus {
|
||||
self.clear_bus_blocking(true);
|
||||
} else {
|
||||
self.do_fsm_reset();
|
||||
}
|
||||
}
|
||||
|
||||
fn bus_busy(&self) -> bool {
|
||||
self.regs().sr().read().bus_busy().bit_is_set()
|
||||
}
|
||||
|
||||
fn ensure_idle_blocking(&self) {
|
||||
if self.regs().sr().read().bus_busy().bit_is_set() {
|
||||
if self.bus_busy() {
|
||||
// If the bus is busy, we need to clear it.
|
||||
self.clear_bus_blocking();
|
||||
self.clear_bus_blocking(false);
|
||||
}
|
||||
}
|
||||
|
||||
async fn ensure_idle(&self) {
|
||||
if self.regs().sr().read().bus_busy().bit_is_set() {
|
||||
if self.bus_busy() {
|
||||
// If the bus is busy, we need to clear it.
|
||||
self.clear_bus().await;
|
||||
}
|
||||
@ -1718,8 +1742,8 @@ impl Driver<'_> {
|
||||
/// If a transaction ended incorrectly for some reason, the slave may drive
|
||||
/// SDA indefinitely. This function forces the slave to release the
|
||||
/// bus by sending 9 clock pulses.
|
||||
fn clear_bus_blocking(&self) {
|
||||
let mut future = ClearBusFuture::new(*self);
|
||||
fn clear_bus_blocking(&self, reset_fsm: bool) {
|
||||
let mut future = ClearBusFuture::new(*self, reset_fsm);
|
||||
let start = Instant::now();
|
||||
while future.poll_completion().is_pending() {
|
||||
if start.elapsed() > CLEAR_BUS_TIMEOUT_MS {
|
||||
@ -1729,7 +1753,7 @@ impl Driver<'_> {
|
||||
}
|
||||
|
||||
async fn clear_bus(&self) {
|
||||
let clear_bus = ClearBusFuture::new(*self);
|
||||
let clear_bus = ClearBusFuture::new(*self, true);
|
||||
let start = Instant::now();
|
||||
|
||||
embassy_futures::select::select(clear_bus, async {
|
||||
@ -2364,7 +2388,7 @@ impl Driver<'_> {
|
||||
#[cfg(not(esp32))]
|
||||
fn reset_fifo(&self) {
|
||||
// First, reset the fifo buffers
|
||||
self.regs().fifo_conf().modify(|_, w| unsafe {
|
||||
self.regs().fifo_conf().write(|w| unsafe {
|
||||
w.tx_fifo_rst().set_bit();
|
||||
w.rx_fifo_rst().set_bit();
|
||||
w.nonfifo_en().clear_bit();
|
||||
@ -2390,7 +2414,7 @@ impl Driver<'_> {
|
||||
#[cfg(esp32)]
|
||||
fn reset_fifo(&self) {
|
||||
// First, reset the fifo buffers
|
||||
self.regs().fifo_conf().modify(|_, w| unsafe {
|
||||
self.regs().fifo_conf().write(|w| unsafe {
|
||||
w.tx_fifo_rst().set_bit();
|
||||
w.rx_fifo_rst().set_bit();
|
||||
w.nonfifo_en().clear_bit();
|
||||
@ -2900,6 +2924,8 @@ fn calculate_chunk_size(remaining: usize) -> usize {
|
||||
|
||||
#[cfg(i2c_master_has_hw_bus_clear)]
|
||||
mod bus_clear {
|
||||
use esp_rom_sys::rom::ets_delay_us;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct ClearBusFuture<'a> {
|
||||
@ -2910,7 +2936,13 @@ mod bus_clear {
|
||||
// Number of SCL pulses to clear the bus
|
||||
const BUS_CLEAR_BITS: u8 = 9;
|
||||
|
||||
pub fn new(driver: Driver<'a>) -> Self {
|
||||
pub fn new(driver: Driver<'a>, reset_fsm: bool) -> Self {
|
||||
// If we have a HW implementation, reset FSM to make sure it's not trying to transmit
|
||||
// while we clear the bus.
|
||||
if reset_fsm {
|
||||
driver.do_fsm_reset();
|
||||
}
|
||||
|
||||
let mut this = Self { driver };
|
||||
this.configure(Self::BUS_CLEAR_BITS);
|
||||
this
|
||||
@ -2944,9 +2976,43 @@ mod bus_clear {
|
||||
|
||||
impl Drop for ClearBusFuture<'_> {
|
||||
fn drop(&mut self) {
|
||||
use crate::gpio::AnyPin;
|
||||
if !self.is_done() {
|
||||
self.configure(0);
|
||||
}
|
||||
|
||||
// Generate a stop condition
|
||||
let sda = self
|
||||
.driver
|
||||
.config
|
||||
.sda_pin
|
||||
.pin_number()
|
||||
.map(|n| unsafe { AnyPin::steal(n) });
|
||||
let scl = self
|
||||
.driver
|
||||
.config
|
||||
.scl_pin
|
||||
.pin_number()
|
||||
.map(|n| unsafe { AnyPin::steal(n) });
|
||||
|
||||
if let (Some(sda), Some(scl)) = (sda, scl) {
|
||||
sda.set_output_high(true);
|
||||
scl.set_output_high(false);
|
||||
|
||||
self.driver.info.scl_output.disconnect_from(&scl);
|
||||
self.driver.info.sda_output.disconnect_from(&sda);
|
||||
|
||||
ets_delay_us(5);
|
||||
sda.set_output_high(false);
|
||||
ets_delay_us(5);
|
||||
scl.set_output_high(true);
|
||||
ets_delay_us(5);
|
||||
sda.set_output_high(true);
|
||||
|
||||
self.driver.info.sda_output.connect_to(&sda);
|
||||
self.driver.info.scl_output.connect_to(&scl);
|
||||
}
|
||||
|
||||
// We don't care about errors during bus clearing
|
||||
self.driver.clear_all_interrupts();
|
||||
}
|
||||
@ -2977,6 +3043,7 @@ mod bus_clear {
|
||||
driver: Driver<'a>,
|
||||
wait: Instant,
|
||||
state: State,
|
||||
reset_fsm: bool,
|
||||
pins: Option<(AnyPin<'static>, AnyPin<'static>)>,
|
||||
}
|
||||
|
||||
@ -2986,10 +3053,7 @@ mod bus_clear {
|
||||
// use standard 100kHz data rate
|
||||
const SCL_DELAY: Duration = Duration::from_micros(5);
|
||||
|
||||
pub fn new(driver: Driver<'a>) -> Self {
|
||||
// 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.
|
||||
pub fn new(driver: Driver<'a>, reset_fsm: bool) -> Self {
|
||||
let sda = driver
|
||||
.config
|
||||
.sda_pin
|
||||
@ -3003,10 +3067,14 @@ mod bus_clear {
|
||||
|
||||
let (Some(sda), Some(scl)) = (sda, scl) else {
|
||||
// If we don't have the pins, we can't clear the bus.
|
||||
if reset_fsm {
|
||||
driver.do_fsm_reset();
|
||||
}
|
||||
return Self {
|
||||
driver,
|
||||
wait: Instant::now(),
|
||||
state: State::Idle,
|
||||
reset_fsm: false,
|
||||
pins: None,
|
||||
};
|
||||
};
|
||||
@ -3017,22 +3085,18 @@ mod bus_clear {
|
||||
driver.info.scl_output.disconnect_from(&scl);
|
||||
driver.info.sda_output.disconnect_from(&sda);
|
||||
|
||||
let state = if sda.is_input_high() {
|
||||
// SDA is high, the device is not holding it, we don't need to do anything.
|
||||
State::Idle
|
||||
} else {
|
||||
// Starting from (9, false), becase:
|
||||
// - we start with SCL low
|
||||
// - a complete SCL cycle consists of a high period and a low period
|
||||
// - we decrement the remaining counter at the beginning of a cycle, which gives us
|
||||
// 9 complete SCL cycles.
|
||||
State::SendClock(Self::BUS_CLEAR_BITS, false)
|
||||
};
|
||||
// Starting from (9, false), becase:
|
||||
// - we start with SCL low
|
||||
// - a complete SCL cycle consists of a high period and a low period
|
||||
// - we decrement the remaining counter at the beginning of a cycle, which gives us 9
|
||||
// complete SCL cycles.
|
||||
let state = State::SendClock(Self::BUS_CLEAR_BITS, false);
|
||||
|
||||
Self {
|
||||
driver,
|
||||
wait: Instant::now() + Self::SCL_DELAY,
|
||||
state,
|
||||
reset_fsm,
|
||||
pins: Some((sda, scl)),
|
||||
}
|
||||
}
|
||||
@ -3072,19 +3136,22 @@ mod bus_clear {
|
||||
self.state = State::SendStop;
|
||||
}
|
||||
State::SendClock(n, false) => {
|
||||
if let Some((_sda, scl)) = self.pins.as_ref() {
|
||||
if let Some((sda, scl)) = self.pins.as_ref() {
|
||||
scl.set_output_high(true);
|
||||
if sda.is_input_high() {
|
||||
sda.set_output_high(false);
|
||||
// The device has released SDA, we can move on to generating a STOP
|
||||
// condition
|
||||
self.wait = Instant::now() + Self::SCL_DELAY;
|
||||
self.state = State::SendStop;
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
self.state = State::SendClock(n - 1, true);
|
||||
}
|
||||
State::SendClock(n, true) => {
|
||||
if let Some((sda, scl)) = self.pins.as_ref() {
|
||||
if let Some((_sda, scl)) = self.pins.as_ref() {
|
||||
scl.set_output_high(false);
|
||||
if sda.is_input_high() {
|
||||
// The device has released SDA, we can move on to generating a NACK
|
||||
self.state = State::SendClock(0, false);
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
self.state = State::SendClock(n, false);
|
||||
}
|
||||
@ -3102,6 +3169,12 @@ mod bus_clear {
|
||||
scl.set_output_high(true);
|
||||
sda.set_output_high(true);
|
||||
|
||||
// If we don't have a HW implementation, reset the peripheral after clearing the
|
||||
// bus, but before we reconnect the pins in Drop. This should prevent glitches.
|
||||
if self.reset_fsm {
|
||||
self.driver.do_fsm_reset();
|
||||
}
|
||||
|
||||
self.driver.info.sda_output.connect_to(&sda);
|
||||
self.driver.info.scl_output.connect_to(&scl);
|
||||
|
||||
|
@ -1165,6 +1165,7 @@ impl Chip {
|
||||
"gpio_input_signal_max=\"124\"",
|
||||
"gpio_output_signal_max=\"128\"",
|
||||
"i2c_master_has_fsm_timeouts",
|
||||
"i2c_master_has_hw_bus_clear",
|
||||
"i2c_master_has_bus_timeout_enable",
|
||||
"i2c_master_can_estimate_nack_reason",
|
||||
"i2c_master_has_conf_update",
|
||||
@ -1367,6 +1368,7 @@ impl Chip {
|
||||
"cargo:rustc-cfg=gpio_input_signal_max=\"124\"",
|
||||
"cargo:rustc-cfg=gpio_output_signal_max=\"128\"",
|
||||
"cargo:rustc-cfg=i2c_master_has_fsm_timeouts",
|
||||
"cargo:rustc-cfg=i2c_master_has_hw_bus_clear",
|
||||
"cargo:rustc-cfg=i2c_master_has_bus_timeout_enable",
|
||||
"cargo:rustc-cfg=i2c_master_can_estimate_nack_reason",
|
||||
"cargo:rustc-cfg=i2c_master_has_conf_update",
|
||||
@ -2257,7 +2259,6 @@ impl Chip {
|
||||
"gpio_input_signal_max=\"255\"",
|
||||
"gpio_output_signal_max=\"256\"",
|
||||
"i2c_master_has_fsm_timeouts",
|
||||
"i2c_master_has_hw_bus_clear",
|
||||
"i2c_master_has_bus_timeout_enable",
|
||||
"i2c_master_can_estimate_nack_reason",
|
||||
"i2c_master_has_conf_update",
|
||||
@ -2441,7 +2442,6 @@ impl Chip {
|
||||
"cargo:rustc-cfg=gpio_input_signal_max=\"255\"",
|
||||
"cargo:rustc-cfg=gpio_output_signal_max=\"256\"",
|
||||
"cargo:rustc-cfg=i2c_master_has_fsm_timeouts",
|
||||
"cargo:rustc-cfg=i2c_master_has_hw_bus_clear",
|
||||
"cargo:rustc-cfg=i2c_master_has_bus_timeout_enable",
|
||||
"cargo:rustc-cfg=i2c_master_can_estimate_nack_reason",
|
||||
"cargo:rustc-cfg=i2c_master_has_conf_update",
|
||||
|
@ -121,7 +121,7 @@ macro_rules! property {
|
||||
true
|
||||
};
|
||||
("i2c_master.has_hw_bus_clear") => {
|
||||
false
|
||||
true
|
||||
};
|
||||
("i2c_master.has_bus_timeout_enable") => {
|
||||
true
|
||||
|
@ -121,7 +121,7 @@ macro_rules! property {
|
||||
true
|
||||
};
|
||||
("i2c_master.has_hw_bus_clear") => {
|
||||
true
|
||||
false
|
||||
};
|
||||
("i2c_master.has_bus_timeout_enable") => {
|
||||
true
|
||||
|
@ -434,7 +434,7 @@ instances = [
|
||||
{ name = "i2c0", sys_instance = "I2cExt0", scl = "I2CEXT0_SCL", sda = "I2CEXT0_SDA" },
|
||||
]
|
||||
has_fsm_timeouts = true
|
||||
# has_hw_bus_clear = true # Maybe on >v0.0?
|
||||
has_hw_bus_clear = true
|
||||
ll_intr_mask = 0x3ffff
|
||||
fifo_size = 32
|
||||
has_bus_timeout_enable = true
|
||||
|
@ -404,6 +404,7 @@ instances = [
|
||||
{ name = "i2c1", sys_instance = "I2cExt1", scl = "I2CEXT1_SCL", sda = "I2CEXT1_SDA" },
|
||||
]
|
||||
ll_intr_mask = 0x1ffff
|
||||
has_hw_bus_clear = false
|
||||
fifo_size = 32
|
||||
has_bus_timeout_enable = true
|
||||
max_bus_timeout = 0xFFFFFF
|
||||
|
@ -576,7 +576,7 @@ instances = [
|
||||
{ name = "i2c1", sys_instance = "I2cExt1", scl = "I2CEXT1_SCL", sda = "I2CEXT1_SDA" },
|
||||
]
|
||||
has_fsm_timeouts = true
|
||||
has_hw_bus_clear = true
|
||||
has_hw_bus_clear = false
|
||||
ll_intr_mask = 0x3ffff
|
||||
fifo_size = 32
|
||||
has_bus_timeout_enable = true
|
||||
|
@ -217,34 +217,41 @@ mod tests {
|
||||
// Read a lot of data to ensure that the transaction is cancelled mid-transfer.
|
||||
let mut read_data = [0u8; 220];
|
||||
|
||||
defmt::info!("Running transaction to be cancelled");
|
||||
for n_yield in 1..20 {
|
||||
defmt::info!("Running transaction to be cancelled");
|
||||
|
||||
// Start a transaction that will be cancelled.
|
||||
let select_result = select(
|
||||
i2c.transaction_async(
|
||||
DUT_ADDRESS,
|
||||
&mut [
|
||||
Operation::Write(READ_DATA_COMMAND),
|
||||
Operation::Read(&mut read_data),
|
||||
],
|
||||
),
|
||||
async {
|
||||
// Let the transaction run for a tiny bit.
|
||||
for _ in 0..10 {
|
||||
embassy_futures::yield_now().await;
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
// Start a transaction that will be cancelled.
|
||||
let select_result = select(
|
||||
i2c.transaction_async(
|
||||
DUT_ADDRESS,
|
||||
&mut [
|
||||
Operation::Write(READ_DATA_COMMAND),
|
||||
Operation::Read(&mut read_data),
|
||||
],
|
||||
),
|
||||
async {
|
||||
// Let the transaction run for a tiny bit.
|
||||
for _ in 0..n_yield {
|
||||
embassy_futures::yield_now().await;
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
// Assert that the I2C transaction was cancelled.
|
||||
hil_test::assert!(
|
||||
matches!(select_result, Either::Second(_)),
|
||||
"Transaction completed with {:?}",
|
||||
select_result
|
||||
);
|
||||
if matches!(select_result, Either::First(Ok(_))) {
|
||||
break;
|
||||
}
|
||||
|
||||
defmt::info!("Transaction cancelled");
|
||||
// Assert that the I2C transaction was cancelled.
|
||||
hil_test::assert!(
|
||||
matches!(select_result, Either::Second(_)),
|
||||
"({}) Transaction completed with {:?}",
|
||||
n_yield,
|
||||
select_result
|
||||
);
|
||||
|
||||
defmt::info!("Transaction cancelled");
|
||||
}
|
||||
|
||||
let mut read_data = [0u8; 22];
|
||||
// do the real read which should succeed
|
||||
|
Loading…
x
Reference in New Issue
Block a user