-
Notifications
You must be signed in to change notification settings - Fork 1.3k
displayio: Impossible to have more than one display on the same bus #1760
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
Comments
So we need a separate FourWireDevice then. (To match I2CDevice for example.) |
I think it would be sufficient to be able to pass a DigitalInOut object as the pins during the initialization, at which point it wouldn't try to create new ones. |
This would be super cool but I think it's pretty niche. |
I would like this feature as well. I have not-very-much experience in this type of development, but am happy to open a pull request if I can get a little guidance on where to start. |
I am currently doing the following to drive three displays, but would like to add a fourth. I get a 'too many busses' error if I just allocate four more pins.
|
@tannewt should displays on different busses work? I had an issue a few weeks ago with one SPI and one I2C display. I don't recall the exact error but I can create an issue for it |
@3ach What version are you using? I'm actually very surprised three worked. I think I've knocked it down to 1 in 5.x. All display objects and their busses are statically allocated so they can live outside the vm. The limit is controlled by CIRCUITPY_DISPLAY_LIMIT. I'm unsure if that's how I want it to keep working because the terminal can only show on one screen at a time now. If we stick with that, then we could statically allocate the first display and bus and dynamically allocate the rest. On the other hand, on the monster mask for example, it might be nice to actually split the terminal across the two displays. To do that, we need to allow two displays to show the same Group and statically allocate them both. |
I think it’s version 4. It’s whatever came by default on the Adafruit Metro
M4 Airlift.
Zach
Le jeu. 26 sept. 2019 à 11:31, Scott Shawcroft <notifications@github.com> a
écrit :
… @3ach <https://github.com/3ach> What version are you using? I'm actually
very surprised three worked. I think I've knocked it down to 1 in 5.x. All
display objects and their busses are statically allocated so they can live
outside the vm. The limit is controlled by CIRCUITPY_DISPLAY_LIMIT
<https://github.com/adafruit/circuitpython/blob/master/py/circuitpy_mpconfig.h#L317>
.
I'm unsure if that's how I want it to keep working because the terminal
can only show on one screen at a time now. If we stick with that, then we
could statically allocate the first display and bus and dynamically
allocate the rest. On the other hand, on the monster mask for example, it
might be nice to actually split the terminal across the two displays. To do
that, we need to allow two displays to show the same Group and statically
allocate them both.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1760?email_source=notifications&email_token=AAXTGTLPYSLK5JIO2UXRIYTQLTWWZA5CNFSM4HEA2WFKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7WLUOI#issuecomment-535607865>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAXTGTOI4EIYJXNHB72LT3DQLTWWZANCNFSM4HEA2WFA>
.
|
Would it be possible to make the display limit a user definable value? Defaults to 1 but can be set higher for more capable hardware, use at your own risk. I've got 3 running on a Grand Central with 4.1. |
@pbricmont Can you get multiple running on 5.x? There was a number of changes made to displayio in 5.x for OLED and ePaper support. |
@tannewt I can only create 1 display bus with 5 b1. I'm going to revert to the Arduino code I've been running. I can run 6 128x128 7735Rs on the Metro M4 Express with that code. Still holding out hope for CircuitPython as I really like displayio. |
@pbricmont you can try changing CIRCUITPY_DISPLAY_LIMIT as I mention in #1760 (comment) and then rebuilding 5.x. If it works we can bump it back up for 5.0.0. |
@tannewt I got 5 displays working. 2 on board.SPI, 2 on SERCOM 6 and 1 on SERCOM 2. Not sure why it won't do more than 5, plenty of RAM available. 5 x 128x128 7735Rs uses about 3K. Adding a 6th display bus results in a hard crash. |
@pbricmont that is impressive! Any chance you have a debugger that can snag a backtrace from the hard crash? |
hello. |
@tannewt Unfortunately, I don't have a debugger. |
@Marius-450 please make a PR to bump it to 2. I don't think we want more than that due to the RAM it takes but 2 makes sense for the m4sk. @pbricmont Bummer. How many would you like to support in the end? What are you using them for? |
@tannewt I would need to get six working. Would like to port this project to CP: https://www.hackster.io/paul-bricmont/nearly-nimo-clock-9309ec |
@pbricmont Your url text and link differ for https://www.hackster.io/paul-bricmont/nearly-nimo-clock-9309ec I didn't know about NIMOs. At a glance, I would have assumed your project emulated a vacuum fluorescent display but that's probably based mostly on the colour. |
@tannewt : Is there a way to use ports/atmel-samd/boards/monster_m4sk/mpconfigboard.mk, or I change the value directly in py/circuitpy_mpconfig.h ? |
(Not tested): What you could do is change these lines in #if CIRCUITPY_DISPLAYIO
extern const struct _mp_obj_module_t displayio_module;
extern const struct _mp_obj_module_t fontio_module;
extern const struct _mp_obj_module_t terminalio_module;
#define DISPLAYIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_displayio), (mp_obj_t)&displayio_module },
#define FONTIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_fontio), (mp_obj_t)&fontio_module },
#define TERMINALIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_terminalio), (mp_obj_t)&terminalio_module },
#define CIRCUITPY_DISPLAY_LIMIT (1)
#else
#define DISPLAYIO_MODULE
#define FONTIO_MODULE
#define TERMINALIO_MODULE
#define CIRCUITPY_DISPLAY_LIMIT (0)
#endif to this (see #if CIRCUITPY_DISPLAYIO
extern const struct _mp_obj_module_t displayio_module;
extern const struct _mp_obj_module_t fontio_module;
extern const struct _mp_obj_module_t terminalio_module;
#define DISPLAYIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_displayio), (mp_obj_t)&displayio_module },
#define FONTIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_fontio), (mp_obj_t)&fontio_module },
#define TERMINALIO_MODULE { MP_OBJ_NEW_QSTR(MP_QSTR_terminalio), (mp_obj_t)&terminalio_module },
#ifndef CIRCUITPY_DISPLAY_LIMIT // ***NEW
#define CIRCUITPY_DISPLAY_LIMIT (1)
#endif // ***NEW
#else
#define DISPLAYIO_MODULE
#define FONTIO_MODULE
#define TERMINALIO_MODULE
#define CIRCUITPY_DISPLAY_LIMIT (0)
#endif and then in `mpconfigboard.h, do: #define CIRCUITPY_DISPLAY_LIMIT (6) or whatever you want. |
perfect. I will do that very soon |
my first PR ... sorry if something is wrong... |
@pbricmont Thanks for the link! Are the displays all on the same bus or are they different? Would you be interested in debugging if we got you a JLinkEDU? |
|
@pbricmont Please email me scott@adafruit.com and we'll get you setup. |
You could but it's not implemented that way. Having things behave differently is tricky. |
I only discovered the twin display Adafruit Monster Mask today. It was mentioned previously in the comments but it's also in the very recent A CircuitPython Board Tour with Ladyada #CircuitPythonDay2021 (YouTube). |
I want to drive two 1.28 inch displays on a RPi Pico. The controllers are GCA901 so I need to run two SPI devices with, presumably, device selection using Chip Select. How do I configure disaplyio to work with two displays on a common bus? I see a reference to the configuration definition CIRCUITPY_DISPLAY_LIMIT - what is the current default value? |
I believe the default setting on almost all devices is |
That was my finding as well. |
The current limit is 1 display. I have been told you can re-compile it and
change the value to 2, but because circuitpython is basically running a
virtual machine it doesn't work so great ( atleast that's how I understand
it, I might not be explaining it correctly). You can use multiple displays
using rgb565. I dunno if the particular display driver you mentioned
supports it.
…On Mon, May 9, 2022, 8:53 PM David Goadby ***@***.***> wrote:
I want to drive two 1.28 inch displays on a RPi Pico. The controllers are
GCA901 so I need to run two SPI devices with, presumably, device selection
using Chip Select. How do I configure disaplyio to work with two displays
on a common bus? I see a reference to the configuration definition
CIRCUITPY_DISPLAY_LIMIT - what is the current default value?
—
Reply to this email directly, view it on GitHub
<#1760 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AUDDR4FBRBKZRRW3FYUDVI3VJGXRTANCNFSM4HEA2WFA>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Thanks for the information. I have chosen to use an extra XIAO RP2040 board to drive the second display and use I2C to take the data from the main processor. The RP2040 is a lot cheaper than many hours of my time trying to get dual display to work. |
I've been experimenting with multi-display support on the LILYGO T-Keyboard-S3, and was able to initialize and render a However, with as niche as this feature would be, I wanted to contribute in the hopes that we can push this forward. For the T-Keyboard-S3, the --- a/shared-bindings/fourwire/FourWire.c
+++ b/shared-bindings/fourwire/FourWire.c
@@ -88,9 +88,9 @@ STATIC mp_obj_t fourwire_fourwire_make_new(const mp_obj_type_t *type, size_t n_a
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
- const mcu_pin_obj_t *command = validate_obj_is_free_pin_or_none(args[ARG_command].u_obj, MP_QSTR_command);
+ const mcu_pin_obj_t *command = validate_obj_is_pin_or_none(args[ARG_command].u_obj, MP_QSTR_command);
const mcu_pin_obj_t *chip_select = validate_obj_is_free_pin_or_none(args[ARG_chip_select].u_obj, MP_QSTR_chip_select);
- const mcu_pin_obj_t *reset = validate_obj_is_free_pin_or_none(args[ARG_reset].u_obj, MP_QSTR_reset);
+ const mcu_pin_obj_t *reset = validate_obj_is_pin_or_none(args[ARG_reset].u_obj, MP_QSTR_reset);
mp_obj_t spi = mp_arg_validate_type(args[ARG_spi_bus].u_obj, &busio_spi_type, MP_QSTR_spi_bus);
As for testing, I used the latest version of CircuitPython at the time: I also tried to configure CircuitPython to start all 4 displays at boot, but that didn't pan out. It sort of worked, but one the next display was initialized the previous one was de-initialized. However, the following script does work as I exepcted so, I'm willing to bet I'm missing a crucial detail or two. import board
import busio
import displayio
import time
import adafruit_st7789
from adafruit_display_shapes.rect import Rect
from adafruit_display_shapes.circle import Circle
_WIDTH = 128
_HEIGHT = 128
tft_sck = board.IO47
tft_mosi = board.IO48
tft_blk = board.IO39
tft_dc = board.IO45
tft_rst = board.IO38
tft_cs1 = board.IO12
tft_cs2 = board.IO13
tft_cs3 = board.IO14
tft_cs4 = board.IO21
displayio.release_displays()
tft_spi = busio.SPI(tft_sck, tft_mosi)
tft_bus_1 = displayio.FourWire(spi_bus=tft_spi, command=tft_dc, reset=tft_rst, chip_select=tft_cs1)
tft_bus_2 = displayio.FourWire(spi_bus=tft_spi, command=tft_dc, reset=tft_rst, chip_select=tft_cs2)
tft_bus_3 = displayio.FourWire(spi_bus=tft_spi, command=tft_dc, reset=tft_rst, chip_select=tft_cs3)
tft_bus_4 = displayio.FourWire(spi_bus=tft_spi, command=tft_dc, reset=tft_rst, chip_select=tft_cs4)
display_1 = adafruit_st7789.ST7789(bus=tft_bus_1, backlight_pin=tft_blk, rotation=0, width=_WIDTH, height=_HEIGHT)
display_2 = adafruit_st7789.ST7789(bus=tft_bus_2, backlight_pin=None, rotation=0, width=_WIDTH, height=_HEIGHT)
display_3 = adafruit_st7789.ST7789(bus=tft_bus_3, backlight_pin=None, rotation=0, width=_WIDTH, height=_HEIGHT)
display_4 = adafruit_st7789.ST7789(bus=tft_bus_4, backlight_pin=None, rotation=0, width=_WIDTH, height=_HEIGHT)
red_group = displayio.Group()
red_group.append(Rect(x=0, y=0, width=128, height=128, fill=0xFFFFFF))
red_group.append(Circle(x0=64, y0=64, r=32, fill=0xFF0000))
display_1.root_group = red_group
blue_group = displayio.Group()
blue_group.append(Rect(x=0, y=0, width=128, height=128, fill=0xFFFFFF))
blue_group.append(Circle(x0=64, y0=64, r=32, fill=0x0000FF))
display_2.root_group = blue_group
green_group = displayio.Group()
green_group.append(Rect(x=0, y=0, width=128, height=128, fill=0xFFFFFF))
green_group.append(Circle(x0=64, y0=64, r=32, fill=0x00FF00))
display_3.root_group = green_group
black_group = displayio.Group()
black_group.append(Rect(x=0, y=0, width=128, height=128, fill=0xFFFFFF))
black_group.append(Circle(x0=64, y0=64, r=32, fill=0x000000))
display_4.root_group = black_group
while True:
time.sleep(1) |
Just like many others I I wanted to connect more than 1 display. I will try recompiling CircuitPython tomorrow. I also understand that it's a tradeoff and objects live outside of VM, etc. It still would be easier if we could do it without recompiling. |
I took a quick look at trying to use a settings.toml parameter to define the display limit. Unfortunately the display/bus structures are allocated (dimensioned) at compile time (as earlier messages had explained 😁) so in order to set the display limit without recompiling, the memory for the structures would have to be changed to a dynamically allocated model (also mentioned above, I really should have read the whole thread) which probably makes it over my head.... |
Yes, I know, it's at compile time... now. |
Give it a try! I bet you can do it. We now have
I think |
If it's allocated dynamically, do we even need a display limit in settings or anywhere ? |
Allocating each display individually will be trickier. It is something I'd like to do in CP11. For now, resizing the "static" array will be simpler and probably work. |
primary_display_bus_t display_busesx[CIRCUITPY_DISPLAY_LIMIT];
primary_display_t displaysx[CIRCUITPY_DISPLAY_LIMIT];
primary_display_bus_t *display_buses = &display_busesx[0];
primary_display_t *displays = &displaysx[0];
display_buses = (primary_display_bus_t*)port_malloc(sizeof(primary_display_bus_t) * CIRCUITPY_DISPLAY_LIMIT, false);
displays = (primary_display_t*)port_malloc(sizeof(primary_display_t) * CIRCUITPY_DISPLAY_LIMIT, false);
memcpy(display_buses, &display_busesx[0], sizeof(primary_display_bus_t) * CIRCUITPY_DISPLAY_LIMIT);
memcpy(displays, &displaysx[0], sizeof(primary_display_t) * CIRCUITPY_DISPLAY_LIMIT);
EDIT: I think I've gotten past this stumbling block 😁 onward!!! |
PR #10158 seems to work on the configuration I've been testing. I plan to test more configurations but after several long nights I needed to step back for a couple days. If anyone is anxious to help out and test the artifacts, that would be great 😁 |
This is very cool! I will try some tests on RP2040/RP2350 this afternoon. Thanks! |
Beware, I just hooked up an I2C display and it's having issues 😦 Edit: It turns out the issues maybe hardware related and not the PRs fault.... |
I was able to get a Feather RP2350 displaying on a DVI display, an I2C display and an SPI display using CIRCUITPY_DISPLAY_LIMIT=3 in settings.toml. Tomorrow I'll try setting up multiple displays on an Espressif micro controller . During my testing I've noticed pressing ctrl-D without performing a displayio.release_displays() seems to cause a Safemode Crash which I need to track down (possibly related to #10084) |
I've been keeping an eye on this thread and your PR for the past few weeks. Thanks so much for your efforts! I've dug out the old T-Keyboard-S3, so if you're looking for some more Espressif testing, let me know. |
I had some time to try this today. My results:
My test setup is an RP2040 Pico clone with multiple SSD1306 monochrome I2C OLEDs. (minimizes wiring and memory footprint). The setup looks like this: And my code looked like this: import time
import board, busio, displayio, i2cdisplaybus, terminalio
import adafruit_displayio_ssd1306
from adafruit_display_text import label
displayio.release_displays()
i2c0 = busio.I2C(sda=board.GP16, scl=board.GP17)
i2c1 = busio.I2C(sda=board.GP14, scl=board.GP15)
dw, dh = 128, 64
displays = [
adafruit_displayio_ssd1306.SSD1306(
i2cdisplaybus.I2CDisplayBus(i2c1, device_address=0x3c), width=dw, height=dh),
adafruit_displayio_ssd1306.SSD1306(
i2cdisplaybus.I2CDisplayBus(i2c1, device_address=0x3d), width=dw, height=dh),
adafruit_displayio_ssd1306.SSD1306(
i2cdisplaybus.I2CDisplayBus(i2c0, device_address=0x3c), width=dw, height=dh),
adafruit_displayio_ssd1306.SSD1306(
i2cdisplaybus.I2CDisplayBus(i2c0, device_address=0x3d), width=dw, height=dh),
]
text_areas=[]
for d in displays:
d.root_group = displayio.Group()
text_area = label.Label(terminalio.FONT, text="HI", color=0xFFFFFF, x=10, y=dh//2, scale=2)
d.root_group.append(text_area)
text_areas.append(text_area)
while True:
print("hi")
for i,ta in enumerate(text_areas):
ta.text = "hi %d %2.1f" % (i,time.monotonic())
time.sleep(0.1) |
I was thinking that I would follow this PR up with a settings.toml parameter that allowed the sharing of DC/RST pins, something like CIRCUITPY_FOURWIRE_SHAREPINS. Edit: Oh wait, the T-Keyboard-S3 doesn't have an official CP build yet.... 😦 |
This is another one I need to follow-up on. I vaguely recall that there were some FourWire changes since I last tested.
That was something I was working on initially, but chose not to merge in the board definitions until we could get the all of the displays working. The branch is still hanging around and it builds just fine, but I just need to rebase and open the PR. A generic ESP32-S3 build works in a pinch, but I didn't want to get anyone's hopes that this thing was fully supported yet. |
I had to use the hack from @rgrizzell to allow sharing of the DC pins but here is 6 displays off an ESP32-S3 Feather 😁 |
I haven't seen this in my testing, I'll try and reproduce your setup and see if I can make it happen. *Hopefully the hard faults you were seeing are fixed, I haven't seen any since my last commit..... |
I haven't been able to reproduce this with three displays (all the I2C I have 😁). I'm assuming you have the program in code.py and you are just pressing ctrl-D to restart after Ctrl-C-ing? I actually was able to get the GP17 in use if I ran the code from a shell (PyDOS) and then restarted it, but that made sense since the I2C busses are never deinit'd in that case. Just to be sure, I put last while loop inside a try/except and deinit both I2C busses in the except block and then I could re-run the test without issue. |
Yes, I had it as But I just tried the CI artifact from g4b151d68ef and I can do four displays now, it hasn't hard faulted on me, and I don't get the "in use" error! Very cool! |
In theory, I could have several SPI displays connected to the same bus, and displayio has provisions for that — locking of the SPI bus, toggling of the CS pin, even re-setting of the SPI frequency, polarity and phase. However, it's not possible to create two (or more)
FourWire
objects sharing the same DC and/or reset pins, but with different CS pins — because we will get a "Pin in use" error when the FourWire object tries to create them.The text was updated successfully, but these errors were encountered: