mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-10-02 22:55:26 +00:00
Check for ST_TOUT / MAIN_ST_TOUT (#3333)
* Check for ST_TOUT / MAIN_ST_TOUT * Fix & CHANGELOG * cfg gate * Field renaming in PACs * Introduce FsmTimeout type * `new_const` * Review comments * Async I2C tests * Remove FSM timeout for S2 * Add test * Don't run `async_test_timeout_when_scl_kept_low` on S2 - we know it fails * Fix --------- Co-authored-by: Scott Mabin <scott@mabez.dev>
This commit is contained in:
parent
b33b877592
commit
4bc9ef2f5b
@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Moved numbered GPIO pin types from `esp_hal::gpio::GpioPin<N>` to `esp_hal::peripherals::GPION<'_>` (#3349)
|
- Moved numbered GPIO pin types from `esp_hal::gpio::GpioPin<N>` to `esp_hal::peripherals::GPION<'_>` (#3349)
|
||||||
- Moved DMA channel types from `esp_hal::dma::DmaChannelN`/`esp_hal::dma::XYDmaChannel` to `esp_hal::peripherals::DMA_XY` (#3372)
|
- Moved DMA channel types from `esp_hal::dma::DmaChannelN`/`esp_hal::dma::XYDmaChannel` to `esp_hal::peripherals::DMA_XY` (#3372)
|
||||||
- `ParlIoFullDuplex`, `ParlIoTxOnly` and `ParlIoRxOnly` have been merged into `ParlIo` (#3366)
|
- `ParlIoFullDuplex`, `ParlIoTxOnly` and `ParlIoRxOnly` have been merged into `ParlIo` (#3366)
|
||||||
|
- I2C checks ST_TOUT / MAIN_ST_TOUT where available (#3333)
|
||||||
- All `Camera` pins are now configured using `with_*()` methods (#3237)
|
- All `Camera` pins are now configured using `with_*()` methods (#3237)
|
||||||
- The `ESP_HAL_CONFIG_PLACE_SPI_DRIVER_IN_RAM` configuration option has been renamed to `ESP_HAL_CONFIG_PLACE_SPI_MASTER_DRIVER_IN_RAM`. (#3402)
|
- The `ESP_HAL_CONFIG_PLACE_SPI_DRIVER_IN_RAM` configuration option has been renamed to `ESP_HAL_CONFIG_PLACE_SPI_MASTER_DRIVER_IN_RAM`. (#3402)
|
||||||
- Made the `ParlIo` traits for `TxPins`, `RxPins`, `ConfigurePins` public (#3398)
|
- Made the `ParlIo` traits for `TxPins`, `RxPins`, `ConfigurePins` public (#3398)
|
||||||
|
@ -165,6 +165,59 @@ impl BusTimeout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When the FSM remains unchanged for more than the 2^ the given amount of bus
|
||||||
|
/// clock cycles a timeout will be triggered.
|
||||||
|
///
|
||||||
|
/// The default value is 0x10
|
||||||
|
#[instability::unstable]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
pub struct FsmTimeout {
|
||||||
|
value: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
impl FsmTimeout {
|
||||||
|
const FSM_TIMEOUT_MAX: u8 = 23;
|
||||||
|
|
||||||
|
const FSM_TIMEOUT_DEFAULT: u8 = 0x10;
|
||||||
|
|
||||||
|
/// Creates a new timeout.
|
||||||
|
///
|
||||||
|
/// The meaning of the value and the allowed range of values is different
|
||||||
|
/// for different chips.
|
||||||
|
pub const fn new_const<const VALUE: u8>() -> Self {
|
||||||
|
const {
|
||||||
|
core::assert!(VALUE <= Self::FSM_TIMEOUT_MAX, "Invalid timeout value");
|
||||||
|
}
|
||||||
|
Self { value: VALUE }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new timeout.
|
||||||
|
///
|
||||||
|
/// The meaning of the value and the allowed range of values is different
|
||||||
|
/// for different chips.
|
||||||
|
pub fn new(value: u8) -> Result<Self, ConfigError> {
|
||||||
|
if value > Self::FSM_TIMEOUT_MAX {
|
||||||
|
return Err(ConfigError::TimeoutInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { value })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> u8 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
impl Default for FsmTimeout {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new_const::<{ Self::FSM_TIMEOUT_DEFAULT }>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// I2C-specific transmission errors
|
/// I2C-specific transmission errors
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
@ -425,6 +478,16 @@ pub struct Config {
|
|||||||
|
|
||||||
/// I2C SCL timeout period.
|
/// I2C SCL timeout period.
|
||||||
timeout: BusTimeout,
|
timeout: BusTimeout,
|
||||||
|
|
||||||
|
/// Sets the threshold value for the unchanged period of the SCL_FSM.
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
#[builder_lite(unstable)]
|
||||||
|
scl_st_timeout: FsmTimeout,
|
||||||
|
|
||||||
|
/// Sets the threshold for the unchanged duration of the SCL_MAIN_FSM.
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
#[builder_lite(unstable)]
|
||||||
|
scl_main_st_timeout: FsmTimeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
@ -432,6 +495,10 @@ impl Default for Config {
|
|||||||
Config {
|
Config {
|
||||||
frequency: Rate::from_khz(100),
|
frequency: Rate::from_khz(100),
|
||||||
timeout: BusTimeout::BusCycles(10),
|
timeout: BusTimeout::BusCycles(10),
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
scl_st_timeout: Default::default(),
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
scl_main_st_timeout: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -634,6 +701,10 @@ impl<'a> I2cFuture<'a> {
|
|||||||
w.arbitration_lost().set_bit();
|
w.arbitration_lost().set_bit();
|
||||||
w.time_out().set_bit();
|
w.time_out().set_bit();
|
||||||
w.nack().set_bit();
|
w.nack().set_bit();
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
w.scl_main_st_to().set_bit();
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
w.scl_st_to().set_bit();
|
||||||
|
|
||||||
w
|
w
|
||||||
});
|
});
|
||||||
@ -669,6 +740,16 @@ impl<'a> I2cFuture<'a> {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
if r.scl_st_to().bit_is_set() {
|
||||||
|
return Err(Error::Timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
if r.scl_main_st_to().bit_is_set() {
|
||||||
|
return Err(Error::Timeout);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(esp32))]
|
#[cfg(not(esp32))]
|
||||||
if r.trans_complete().bit_is_set() && self.info.regs().sr().read().resp_rec().bit_is_clear()
|
if r.trans_complete().bit_is_set() && self.info.regs().sr().read().resp_rec().bit_is_clear()
|
||||||
{
|
{
|
||||||
@ -1147,6 +1228,10 @@ fn async_handler(info: &Info, state: &State) {
|
|||||||
w.trans_complete().clear_bit();
|
w.trans_complete().clear_bit();
|
||||||
w.arbitration_lost().clear_bit();
|
w.arbitration_lost().clear_bit();
|
||||||
w.time_out().clear_bit();
|
w.time_out().clear_bit();
|
||||||
|
#[cfg(not(esp32))]
|
||||||
|
w.scl_main_st_to().clear_bit();
|
||||||
|
#[cfg(not(esp32))]
|
||||||
|
w.scl_st_to().clear_bit();
|
||||||
|
|
||||||
#[cfg(not(any(esp32, esp32s2)))]
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
w.txfifo_wm().clear_bit();
|
w.txfifo_wm().clear_bit();
|
||||||
@ -1428,6 +1513,16 @@ impl Driver<'_> {
|
|||||||
// Configure frequency
|
// Configure frequency
|
||||||
self.set_frequency(config, config.timeout)?;
|
self.set_frequency(config, config.timeout)?;
|
||||||
|
|
||||||
|
// Configure additional timeouts
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
self.regs()
|
||||||
|
.scl_st_time_out()
|
||||||
|
.write(|w| unsafe { w.scl_st_to().bits(config.scl_st_timeout.value()) });
|
||||||
|
#[cfg(not(any(esp32, esp32s2)))]
|
||||||
|
self.regs()
|
||||||
|
.scl_main_st_time_out()
|
||||||
|
.write(|w| unsafe { w.scl_main_st_to().bits(config.scl_main_st_timeout.value()) });
|
||||||
|
|
||||||
self.update_config();
|
self.update_config();
|
||||||
|
|
||||||
// Reset entire peripheral (also resets fifo)
|
// Reset entire peripheral (also resets fifo)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! I2C test
|
//! I2C test
|
||||||
|
|
||||||
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
||||||
//% FEATURES: unstable
|
//% FEATURES: unstable embassy
|
||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
@ -29,7 +29,7 @@ const DUT_ADDRESS: u8 = 0x77;
|
|||||||
const NON_EXISTENT_ADDRESS: u8 = 0x6b;
|
const NON_EXISTENT_ADDRESS: u8 = 0x6b;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[embedded_test::tests(default_timeout = 3)]
|
#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -122,4 +122,88 @@ mod tests {
|
|||||||
|
|
||||||
assert_ne!(read_data, [0u8; 22])
|
assert_ne!(read_data, [0u8; 22])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn async_empty_write_returns_ack_error_for_unknown_address(ctx: Context) {
|
||||||
|
let mut i2c = ctx.i2c.into_async();
|
||||||
|
|
||||||
|
// on some chips we can determine the ack-check-failed reason but not on all
|
||||||
|
// chips
|
||||||
|
cfg_if::cfg_if! {
|
||||||
|
if #[cfg(any(esp32,esp32s2,esp32c2,esp32c3))] {
|
||||||
|
assert_eq!(
|
||||||
|
i2c.write_async(NON_EXISTENT_ADDRESS, &[]).await,
|
||||||
|
Err(Error::AcknowledgeCheckFailed(
|
||||||
|
AcknowledgeCheckFailedReason::Unknown
|
||||||
|
))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert_eq!(
|
||||||
|
i2c.write_async(NON_EXISTENT_ADDRESS, &[]).await,
|
||||||
|
Err(Error::AcknowledgeCheckFailed(
|
||||||
|
AcknowledgeCheckFailedReason::Address
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(i2c.write_async(DUT_ADDRESS, &[]).await, Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn async_test_read_cali(ctx: Context) {
|
||||||
|
let mut i2c = ctx.i2c.into_async();
|
||||||
|
let mut read_data = [0u8; 22];
|
||||||
|
|
||||||
|
// have a failing read which might could leave the peripheral in an undesirable
|
||||||
|
// state
|
||||||
|
i2c.write_read_async(NON_EXISTENT_ADDRESS, &[0xaa], &mut read_data)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
// do the real read which should succeed
|
||||||
|
i2c.write_read_async(DUT_ADDRESS, &[0xaa], &mut read_data)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
assert_ne!(read_data, [0u8; 22])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
async fn async_test_read_cali_with_transactions(ctx: Context) {
|
||||||
|
let mut i2c = ctx.i2c.into_async();
|
||||||
|
let mut read_data = [0u8; 22];
|
||||||
|
|
||||||
|
// do the real read which should succeed
|
||||||
|
i2c.transaction_async(
|
||||||
|
DUT_ADDRESS,
|
||||||
|
&mut [Operation::Write(&[0xaa]), Operation::Read(&mut read_data)],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
assert_ne!(read_data, [0u8; 22])
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is still an issue on ESP32-S2
|
||||||
|
#[cfg(not(esp32s2))]
|
||||||
|
#[test]
|
||||||
|
async fn async_test_timeout_when_scl_kept_low(_ctx: Context) {
|
||||||
|
let mut i2c = I2c::new(
|
||||||
|
unsafe { esp_hal::peripherals::I2C0::steal() },
|
||||||
|
Config::default(),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.with_sda(unsafe { esp_hal::peripherals::GPIO4::steal() })
|
||||||
|
.with_scl(unsafe { esp_hal::peripherals::GPIO5::steal() })
|
||||||
|
.into_async();
|
||||||
|
|
||||||
|
esp_hal::gpio::InputSignal::I2CEXT0_SCL.connect_to(&esp_hal::gpio::Level::Low);
|
||||||
|
|
||||||
|
let mut read_data = [0u8; 22];
|
||||||
|
// will run into an error but it should return at least
|
||||||
|
i2c.write_read_async(DUT_ADDRESS, &[0xaa], &mut read_data)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user