-
Notifications
You must be signed in to change notification settings - Fork 264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Frequent I2C timeouts when running I2C alongside WiFi stack #3167
Comments
Link this issue: #3034 |
Problem seems to be that the scheduler will switch to another task while the I2C driver tries to catch-up filling the FIFO (which it needs to do in-time) Here is some simplified code showing this effect w/o esp-wifi //! Read calibration data from BMP180 sensor
//!
//! This example dumps the calibration data from a BMP180 sensor
//!
//! The following wiring is assumed:
//! - SDA => GPIO4
//! - SCL => GPIO5
//% FEATURES: esp-hal/unstable embassy
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% TAG: bmp180
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use esp_backtrace as _;
use esp_hal::{
delay::Delay,
handler,
i2c::master::{Config, I2c},
time::Duration,
timer::{systimer::SystemTimer, timg::TimerGroup, PeriodicTimer},
};
use esp_println::println;
#[esp_hal_embassy::main]
async fn main(_spawner: Spawner) -> ! {
let peripherals = esp_hal::init(esp_hal::Config::default());
esp_alloc::heap_allocator!(size: 72 * 1024);
let syst = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init(syst.alarm0);
let timg0 = TimerGroup::new(peripherals.TIMG0);
let mut periodic = PeriodicTimer::new(timg0.timer0);
periodic.set_interrupt_handler(handler);
periodic.start(Duration::from_millis(10)).unwrap();
periodic.enable_interrupt(true);
// Create a new peripheral object with the described wiring and standard
// I2C clock speed:
let mut i2c = I2c::new(peripherals.I2C0, Config::default())
.unwrap()
.with_sda(peripherals.GPIO4)
.with_scl(peripherals.GPIO5)
.into_async();
loop {
let mut data = [0u8; 220];
println!(
"{:?}",
i2c.write_read_async(0x77, &[0xaa; 64], &mut data).await
);
}
}
#[handler]
fn handler() {
unsafe {
let timg0 = TimerGroup::new(esp_hal::peripherals::TIMG0::steal());
let mut periodic = PeriodicTimer::new(timg0.timer0);
let mut _d = Delay::new();
_d.delay_millis(1);
periodic.clear_interrupt();
}
} In that code lowering the 64 bytes written to something that fits the FIFO should make the errors disappear. Removing the delay from the interrupt handler, too. Looking at the driver here: https://github.com/aaron-hardin/sh1107/blob/26bb3020fa825ef706b303ae017911e0aeb71931/src/interface/i2c.rs#L46 it's using 64 byte chunks while the FIFO is 32 bytes - probably just lowering that might do the trick in your case |
I tried changing the chunk length in the driver to 32 but the errors still seem to occur at the same rate as before. |
Could you try setting |
As far as I can tell It hasn't made a difference. Maybe with the environment variable set it causes bursts of errors to occur more often? I wouldn't count on that observation though, as it's a very random occurrence so it would be very difficult to quantify. Something I've noticed (unrelated to the env variable) is that it seems like the errors occur more frequently when more is happening with the WiFi. There tend to be a couple of bursts of errors initially when the stack is being spun up, then it calms down a bit, but when connection to an AP is established (the board is operating in STA mode), the errors seem to become more common. Probably not surprising, but thought it was worth mentioning anyway. |
Would make sense - if there is nothing to do for the WiFi driver we quickly return to the main task
Had another look at the code - they take that 32 and add 1 to it the next line for the actual data buffer to be written - so 31 here might be worth a try |
Setting I'm sure the driver could be modified to properly handle different buffer lengths, but this would be an entirely separate endeavour. |
I've been working on a minimal example to exhibit what I've seen and it's led to some interesting observations. The key takeaway from what I've seen is that I can recreate the issue with a very small example which doesn't even require the wifi: Timeouts with
|
For the WiFi case it might be a workaround to wrap the flushing in a critical section like this critical_section::with(|_| {
let _ = display.flush().inspect_err(|err| {
errors += 1;
warn!("Error: {}", format!("{err:?}").as_str())
});
}); That's not a great solution - I wouldn't even call it a solution t.b.h. - but maybe WiFi will tolerate the latency resulting from blocking interrupts for a short time |
I thought I'd give it a try with my original project which actually does something with the wifi. This does stop the I2C errors, but it also seems to stop the application from being able to accept connections and serve web pages, which I guess isn't that surprising. |
Mind trying #3199 to see if it actually fixes this issue? |
Bug description
Bug was seen when driving a display with a flush mechanism (full screen redraw) at the same time as running the WiFi stack on ProS3. If the WiFi is not running, I2C timeouts are not seen.
To Reproduce
Bug was observed on a ProS3 connected via I2C to an Adafruit FeatherWing display. The display driver used was https://github.com/aaron-hardin/sh1107
Minimal examples of the timeouts being caused by WiFi and by
defmt
are in this commentExpected behavior
No I2C timeouts caused by running alongside WiFi.
Environment
The text was updated successfully, but these errors were encountered: