adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
MIT License
3.97k stars 1.16k forks source link

displayio.I2CDisplay raises TypeError when called with TCA9548A I2C port #9172

Open timchinowsky opened 3 months ago

timchinowsky commented 3 months ago

CircuitPython version

Adafruit CircuitPython 9.0.0 on 2024-03-19; Waveshare RP2040-Zero with rp2040

Code/REPL

import adafruit_tca9548a                                                                                                                               
import board                                                                                                                                           
import busio                                                                                                                                           
import displayio                                                                                                                                       

i2c = busio.I2C(board.GP1, board.GP0)                                                                                                                  
tca = adafruit_tca9548a.TCA9548A(i2c)                                                                                                                  
display_bus = displayio.I2CDisplay(tca[3], device_address=0x3C)

Behavior

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: i2c_bus must be of type I2C, not TCA9548A_Channel

Description

Looks like I2CDisplay is not tolerant of the I2C abstraction returned by the driver for the TCA9548A I2C multiplexer.

Additional information

Guessing this is due to type checking added in https://github.com/adafruit/circuitpython/commit/b799b2e8467ea706b54685242a72fe8fe06ace68

dhalbert commented 2 months ago

@tannewt is it possible to use duck-typing here?

tannewt commented 2 months ago

@tannewt is it possible to use duck-typing here?

Not in the short term. Generally native modules need native implementations because they call functions on the other native object directly. Displays are extra difficult currently because we assume they all can run without a VM. Python-implemented APIs won't work in this case.

There is a future I can see where we do allow Python I2C busses but that requires us to use the Python function lookup instead of direct calls and it requires us to allow VM-only displays.

timchinowsky commented 2 months ago

Unfamiliar with the history and architecture here, can you explain how/why displayio.I2CDisplay(tca[3], device_address=0x3C) differs in this respect from the older adafruit_ssd1306.SSD1306_I2C(128, 32, tca[3]), which works just fine? Thanks!

tannewt commented 2 months ago

Unfamiliar with the history and architecture here, can you explain how/why displayio.I2CDisplay(tca[3], device_address=0x3C) differs in this respect from the older adafruit_ssd1306.SSD1306_I2C(128, 32, tca[3]), which works just fine? Thanks!

displayio is a module implemented in the core using C. adafruit_ssd1306 is a pure Python module.

timchinowsky commented 2 months ago

Are there any native modules which do use the Python function lookup in the way that would be required? And, are there adafruit_* modules which are not pure Python, i.e. use native code?

tannewt commented 2 months ago

Are there any native modules which do use the Python function lookup in the way that would be required?

adafruit_bus_device does this correctly: https://github.com/adafruit/circuitpython/blob/94afcaa48a225bd6655957c192df5524bdf95bad/shared-module/adafruit_bus_device/i2c_device/I2CDevice.c#L73-L77

displayio has the added challenge of running when no VM is present as well.

And, are there adafruit_* modules which are not pure Python, i.e. use native code?

There are only two currently: adafruit_bus_device and adafruit_pypixelbuf. We have both native and Python versions of them.

timchinowsky commented 2 months ago

displayio has the added challenge of running when no VM is present as well.

Why does it need to be able to do that?

tannewt commented 2 months ago

displayio has the added challenge of running when no VM is present as well.

Why does it need to be able to do that?

We automatically show errors on the display after the VM finishes. It shows the CircuitPython terminal. We have plans to make this behavior more explicit but it won't happen soon.

timchinowsky commented 2 months ago

Ah, great background, thanks

johnnohj commented 2 weeks ago

Could this be failing because the arguments passed to create the display bus are incorrect?

display_bus = displayio.I2CDisplay(tca[3], device_address=0x3C)

tca[3] should correctly point to the i2c bus via the mux channel indicated, but device_address=0x3C isn't where the display is connected. Assuming there isn't a deeper issue with the way abstraction gets handled, as discussed, shouldn't the interpreter be directed to the mux address?

display_bus = displayio.I2CDisplay(tca[3], device_address=0x70)

Or, depending on how picky the driver is about satisfying argument positions:

display_bus = displayio.I2CDisplay(tca[3])