ESP32: I2C transfers block the scheduler, and each i2c call costs about 1 ms

Abierto
#2,430 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 2 días

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
35/100
Tipo de issue
Nueva funcionalidad
Claridad
Necesita aclaración
Estado de actividad
Activo
Stack tecnológico
c, erlang

Línea de trabajo

Start by reading the ESP32 I2C port driver and event loop; the issue does not name specific files or tests. The report identifies synchronous transfers and delayed event polling as possible bottlenecks, but leaves open whether to add a non-blocking path, change scheduler wakeups, or expose batched sensor results. Done would require agreeing on the desired behavior and validating its effect on scheduler delays.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

I've been writing firmware for a little robot with an ESP32-C6 (Microbots CodeCell C6 Drive) that has a BNO085 IMU and a VCNL4040 proximity sensor on the same I2C bus at 400 kHz. The BNO085 clock-stretching timeouts were the first problem, and the new I2C driver PR sorts those out. But once the IMU was talking, my control loops still crawled, so I spent some time measuring where the time goes. I'm not sure this counts as a bug, but the numbers might be useful.

I wrote a small Erlang module that does nothing except read the BNO085: soft reset, turn on the rotation vector at 100 Hz, then read each packet (a 4-byte header read, then the full packet) whenever INT goes low. Each loop it also reads two bytes from the VCNL4040, which never stretches the clock, as a baseline. A second process just runs timer:sleep(1) in a loop and records how late it wakes up. This is all on v0.7.0-beta.0 with ESP-IDF 5.5.5.

With the VM otherwise idle, a two-byte i2c:read_bytes/4 from the VCNL4040 takes about 1.1 ms on the release image. For comparison, a gpio:read/2 (a port:call to a port that doesn't touch any hardware) takes about 0.7 ms, and a plain message round trip to another process takes about 270 µs. I put some timers inside the port driver (debug build, so a bit slower than release) and the bus transfer itself, i2c_master_cmd_begin, averages about 0.57 ms across all transfers including the BNO085's, while the whole i2cdriver_consume_mailbox handler averages about 0.82 ms. So for a simple read, most of the cost is the round trip through the port rather than the bus.
With the IMU streaming at 100 Hz, each BNO085 read takes about 2.5–3 ms and the VCNL4040 read about 2.2 ms, and the sleep(1) process wakes up 0.7–1.8 ms late on average and occasionally 18–35 ms late, depending on the build (legacy driver with the timeout raised, or the new driver PR). Because the transfers run synchronously on the scheduler, nothing else gets to run while one is in flight. In the real firmware, with a handful of other processes going, it all piled up: a GenServer.call with no I/O took 8–20 ms and loops meant to run at 20 Hz ran at about 9 Hz.

I tried moving the legacy port driver's i2c_master_cmd_begin onto a FreeRTOS worker task and replying from an event listener on a queue in event_set. That stopped the scheduler blocking, but the replies then sat around until the scheduler's next event poll. On a busy VM an idle two-byte read went from about 1.7 ms to 19 ms, so it made things worse overall. I don't know the event loop well enough to tell whether there's a cheap way for a finished job to wake the scheduler straight away. Changing the build from -Og to -O2 shaved 10–25% off everything but didn't change the overall picture.

A few things I'm curious about. Is a ~270 µs message round trip normal on a single-core C6, or does that suggest something's off in how I'm building? Would a non-blocking I2C path be welcome, and if so, how should a completed transfer wake the scheduler? And for sensors like this, would you rather people batch into fewer, larger reads, or move the protocol into a native task and just expose the results?

Happy to share the test module, or to try things on the hardware.

Lenguaje dominante
Erlang
Estrellas
2.3k
Forks
171
Merge medio
5 d 11 h
PR fusionados (30 d)
19

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de atomvm/AtomVM

Todos los issues de atomvm/AtomVM

Issues similares

Más issues de Embedded & IoT

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.