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