adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.03k stars 1.19k forks source link

ESP32-S3 I2C timeout with seesaw #9535

Open icebreaker87 opened 3 weeks ago

icebreaker87 commented 3 weeks ago

CircuitPython version

Adafruit CircuitPython 9.1.1 on 2024-07-22; Adafruit Feather ESP32S3 No PSRAM with ESP32S3

Code/REPL

import time
import busio
import board
from micropython import const
from adafruit_seesaw.seesaw import Seesaw

BUTTON_X = const(6)
BUTTON_Y = const(2)
BUTTON_A = const(5)
BUTTON_B = const(1)
BUTTON_SELECT = const(0)
BUTTON_START = const(16)
button_mask = const(
    (1 << BUTTON_X)
    | (1 << BUTTON_Y)
    | (1 << BUTTON_A)
    | (1 << BUTTON_B)
    | (1 << BUTTON_SELECT)
    | (1 << BUTTON_START)
)

i2c_bus = board.STEMMA_I2C()
seesaw = Seesaw(i2c_bus, addr=0x50)
seesaw.pin_mode_bulk(button_mask, seesaw.INPUT_PULLUP)

Behavior

Traceback (most recent call last): File "", line 33, in File "adafruit_seesaw/seesaw.py", line 342, in pin_mode_bulk File "adafruit_seesaw/seesaw.py", line 329, in _pin_mode_bulk_x File "adafruit_seesaw/seesaw.py", line 520, in write OSError: [Errno 116] ETIMEDOUT

Description

This is the example code from adafruits gamepad qt guide. Get this everytime I try to run the code. After multiple tries, there is an error : Traceback (most recent call last): File "", line 27, in RuntimeError: No pull up found on SDA or SCL; check your wiring

Bus this goes away if you unplug the board and power back on. On RP2040 based boards, it works out of the box (same CP version). If I use the same hardware but with arduino code (also from gamepad qt guide), it works always.

Additional information

No response

icebreaker87 commented 3 weeks ago

If I run the I2C scan (https://learn.adafruit.com/scanning-i2c-addresses/circuitpython), it does see the device at 0x50, but can't "speak" to it

deshipu commented 3 weeks ago

It's hard to be sure without looking at the signals on the bus, but it looks like the seesaw device is crashing leaving the SCL line pulled low, that's why you get a timeout when the CircuitPython side waits on clock stretching, and it fails the test for pull-up resistors (because the line is kept low by the other side). Might be some combination of a bug in the seesaw device code, and different timings on different ports?

dhalbert commented 3 weeks ago

Try setting the I2C busio frequency to 400000: Replace:

i2c_bus = board.STEMMA_I2C()

with

import busio
# ...
i2c_bus = busio.I2C(board.SCL, board.SDA, frequency=400000)

There's a thread here: https://forums.adafruit.com/viewtopic.php?p=978381 that suggests that at least in some circumstances, that helps. That thread was about an RPi (not Pico).

As for the motor controller you mentioned in your forum thread, I have written to Kitronik, because I don't know whether there are pull-up resistors on their board. They don't appear to publish a schematic.

icebreaker87 commented 3 weeks ago

I have tried with your code, sadly shows exactly the same behavoir.

@deshipu but with arduino code it seems to work fine. This is why i thought, it would be more on the circuit python side, as the hardware is the same, just running a different code on. Sadly I don't have the equipment for testing the bus, I only own a multimeter.

dhalbert commented 3 weeks ago

I tried a similar seesaw board, Adafruit ANO Rotary Navigation Encoder to I2C Stemma QT Adapter, and was able to reproduce the problem, with both frequency=100000 and frequency=400000. However, the program does not get ETIMEDOUT with:

i2c_bus = busio.I2C(board.SCL, board.SDA, frequency=50000)

Try that and see if it lets you proceed.

bill88t commented 3 weeks ago

This happens to me on the lilygo_twatch_s3 when files are being written with usb and the board is in it's low power state @ 40Mhz. It's not related to a specific i2c library.

icebreaker87 commented 3 weeks ago

i2c_bus = busio.I2C(board.SCL, board.SDA, frequency=50000)

With this speed, it works perfect.

icebreaker87 commented 3 weeks ago

@bill88t if I run this code:

import microcontroller
microcontroller.cpu.frequency

the output is:

240000000

which to me sounds like full speed

icebreaker87 commented 3 weeks ago

@dhalbert If I test again with the motor driver, it's working now too. The only thing I have changed, is a bigger power supply (LiPo wit DC-DC Stepdown). Nothing changed in the code.

dhalbert commented 3 weeks ago

@icebreaker87 Glad to hear on both counts. I will check about the 50000 speed requirement internally. And it sounds like it was power sag on the motor driver.

tannewt commented 3 weeks ago

This could be an artifact of our tick speed change from 100 to 1000. That shortens timeouts like: https://github.com/adafruit/circuitpython/blob/830294ebb533af1691e6afe0d58f5d4733fa099d/ports/espressif/common-hal/busio/I2C.c#L176

We should change the timeouts to be tick speed agnostic.

Casa-Machinalia commented 3 weeks ago

I'm having a very similar issue with I2C on QT-PY-ESP32-S2. I've come at it all kinds of ways, with one or multiple I2C devices and varying the I2C bus speed from 10_000 to 400_000.

With these i2c devices:

When I do a i2c.scan() it never shows any devices, but I can "speak" to the rotaryio. When I enable the ssd1306, I get:

main.py output:
sys.implementation=(name='circuitpython', version=(9, 1, 1), _machine='Adafruit QT Py ESP32S2 with ESP32S2', _mpy=262)
I2C_FREQ       : 100000
I2C_DO_SCAN    : True
I2C_USE_SSD1306: True
I2C_USE_SEESAW : True
Scanning I2C addresses: []
Position: 0
cnt_loops = 16 / sec
Traceback (most recent call last):
  File "main.py", line 69, in <module>
  File "adafruit_ssd1306.py", line 219, in show
  File "adafruit_ssd1306.py", line 287, in write_framebuf
OSError: [Errno 116] ETIMEDOUT

If I never do the i2c.scan(), seesaw-rotaryio and ssd1306 appear to work fine (I've tried different instances of ssd1306 hardware).

In the case where I use only the seesaw-rotaryio and do i2c.scan(), the scan still shows no devices, I can communicate with the seesaw-rotario and my code speed decreases dramatically, e.g. 100+ loops/second becomes ~15 loops/second.

Code below. Can anyone shed some light on this?

Thanks, Casa


# ==============================================================================
#
from micropython import const
import busio
import board
import sys
from adafruit_ticks import ticks_ms, ticks_add, ticks_less

I2C_FREQ = const(100_000)
I2C_DO_SCAN: bool = True
I2C_USE_SSD1306: bool = True
I2C_USE_SEESAW: bool = True

MS_ONE_SECOND = const(1000)

# ------------------------------------------------------------------------------
print(f"{sys.implementation=}")
print(f"I2C_FREQ       : {I2C_FREQ:6d}")
print(f"I2C_DO_SCAN    : {I2C_DO_SCAN}")
print(f"I2C_USE_SSD1306: {I2C_USE_SSD1306}")
print(f"I2C_USE_SEESAW : {I2C_USE_SEESAW}")

# ------------------------------------------------------------------------------
def print_I2C_Scan(i2c: busio.i2c) -> None:
    while not i2c.try_lock():
        pass
    print("Scanning I2C addresses:", [hex(i).upper() for i in i2c.scan()])
    i2c.unlock()

# ------------------------------------------------------------------------------
# Set up i2c0, OLED, seesaw rotaryio
#
i2c0 = busio.I2C(board.SCL, board.SDA, frequency=I2C_FREQ)

if I2C_USE_SSD1306:
    import adafruit_ssd1306
    ADDR = const(0x3C)
    WIDTH = const(128)
    HEIGHT = const(64)
    oled = adafruit_ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c0, addr=ADDR)

if I2C_USE_SEESAW:
    from adafruit_seesaw import seesaw, rotaryio  #, digitalio
    seesaw = seesaw.Seesaw(i2c0, 0x36)
    encoder = rotaryio.IncrementalEncoder(seesaw)
    last_position = None

# ------------------------------------------------------------------------------
if I2C_DO_SCAN:
    print_I2C_Scan(i2c0)    # PROBLEM HERE

# ------------------------------------------------------------------------------
cnt_loops: int = 0
cnt_printed: int = 0
ms_target: int = ticks_add(ticks_ms(), MS_ONE_SECOND)
try:
    while True:
        cnt_loops += 1
        if not ticks_less(ticks_ms(), ms_target):

            ms_target = ticks_add(ticks_ms(), MS_ONE_SECOND)
            if cnt_printed < 10:
                cnt_printed += 1
                print(f"cnt_loops = {cnt_loops} / sec")
            cnt_loops = 0

            if I2C_USE_SSD1306:
                oled.fill(0)
                oled.show()
                # oled.line(X_MAX, Y_MAX, X_MIN, Y_MIN, 1)
                oled.vline(WIDTH // 2, 0, HEIGHT, 1)
                oled.show()

        if I2C_USE_SEESAW:
            if (position := -encoder.position) != last_position:
                last_position = position
                print("Position: {}".format(position))

except KeyboardInterrupt:
    print("Caught KeyboardInterrupt, exiting")
tedder commented 3 weeks ago

as a workaround, I switched to bitbangio.I2C for my qtpy esp32-s2.

dhalbert commented 3 weeks ago

This could be an artifact of our tick speed change from 100 to 1000. That shortens timeouts like:

I did try changing the number of ticks to 1000 in I2C.c. Unfortunately, I still got the same ETIMEDOUT. I agree it should be fixed as you mentioned.

dhalbert commented 2 weeks ago

@Casa-Machinalia I could not duplicate the OP problem on ESP32-S2, so I've made your issue be a separate issue: https://github.com/adafruit/circuitpython/issues/9561.

dhalbert commented 2 weeks ago

I tested with a seesaw board on an ESP32-S3. I get ETIMEDOUT starting at CircuitPython 9.0.0. It works in 8.2.10. There have been no significant changes to ports/espressif/common-hal/busio/I2C.c for several years, so I think it's an ESP-IDF upgrade issue. I also rebuilt 9.1.2 with

-CONFIG_FREERTOS_HZ=1000
+CONFIG_FREERTOS_HZ=100

and it still fails. CONFIG_FREERTOS_HZ was changed to 1000 post 9.0.0 anyway.

dhalbert commented 2 weeks ago

I started working on updating to the new 5.x.x ESP-IDF driver, @tannewt pointed out esp-camera is not yet updated, and the two drivers can't coexist. So I'll put any update on hold. @tannewt has done some of this update already.

tannewt commented 1 week ago

I think this might be the issue: https://github.com/espressif/esp-idf/issues/13962

I'll update the IDF now to 5.3.1.