adafruit / circuitpython

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

displayio: Impossible to have more than one display on the same bus #1760

Open deshipu opened 5 years ago

deshipu commented 5 years ago

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.

tannewt commented 5 years ago

So we need a separate FourWireDevice then. (To match I2CDevice for example.)

deshipu commented 5 years ago

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.

tannewt commented 5 years ago

This would be super cool but I think it's pretty niche.

3ach commented 4 years ago

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.

3ach commented 4 years ago

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.

displayio.release_displays()
for d in range(3):
    cs_pin = 13 - (d * 3)
    rs_pin = 13 - (d * 3) - 1
    dc_pin = 13 - (d * 3) - 2

    spi = board.SPI()
    tft_cs = getattr(board, "D" + str(cs_pin))
    tft_rs = getattr(board, "D" + str(rs_pin))
    tft_dc = getattr(board, "D" + str(dc_pin))

    display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=tft_rs)
    display = ST7735R(display_bus, width=128, height=128, colstart=2, rowstart=1)
siddacious commented 4 years ago

@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

tannewt commented 4 years ago

@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.

3ach commented 4 years ago

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 https://github.com/adafruit/circuitpython/issues/1760?email_source=notifications&email_token=AAXTGTLPYSLK5JIO2UXRIYTQLTWWZA5CNFSM4HEA2WFKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7WLUOI#issuecomment-535607865, or mute the thread https://github.com/notifications/unsubscribe-auth/AAXTGTOI4EIYJXNHB72LT3DQLTWWZANCNFSM4HEA2WFA .

pbricmont commented 4 years ago

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.

tannewt commented 4 years ago

@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.

pbricmont commented 4 years ago

@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.

tannewt commented 4 years ago

@pbricmont you can try changing CIRCUITPY_DISPLAY_LIMIT as I mention in https://github.com/adafruit/circuitpython/issues/1760#issuecomment-535607865 and then rebuilding 5.x. If it works we can bump it back up for 5.0.0.

pbricmont commented 4 years ago

@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.

tannewt commented 4 years ago

@pbricmont that is impressive! Any chance you have a debugger that can snag a backtrace from the hard crash?

Marius-450 commented 4 years ago

hello. I managed to build CP with CIRCUITPY_DISPLAY_LIMIT = 2 for the monster m4sk, and made the 2 displays working nicely. It would be a great idea to allow, at least for this board, multiple displays...

pbricmont commented 4 years ago

@tannewt Unfortunately, I don't have a debugger.

tannewt commented 4 years ago

@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?

pbricmont commented 4 years ago

@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

kevinjwalters commented 4 years ago

@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.

Marius-450 commented 4 years ago

@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 ?

dhalbert commented 4 years ago

(Not tested): What you could do is change these lines in circuitpy_mpconfig.h:

#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 // ***NEW lines), to allow overriding the default value:

#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.

Marius-450 commented 4 years ago

perfect. I will do that very soon

Marius-450 commented 4 years ago

my first PR ... sorry if something is wrong...

tannewt commented 4 years ago

@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 commented 4 years ago

@tannewt

@pbricmont Thanks for the link! Are the displays all on the same bus or are they different? In the Arduino version, all six displays are on the same SPI bus. The CP SPI buses seem much faster (24mHz?) but multiple buses are required due to the 2 per bus limit. Also, I had to make all the wiring as short as possible to get it to work at all. Would you be interested in debugging if we got you a JLinkEDU? Yes, I would be happy to investigate further. I have reviewed the CP debugging guide. I just did a build of 5.0b3 for the Grand Central and got the same "Crash into the HardFault_Handler" when adding the 6th display bus.

tannewt commented 4 years ago

@pbricmont Please email me scott@adafruit.com and we'll get you setup.

MiracleToast commented 4 years ago

Hello, was wondering if there was any update on this request for multiple spi displays? 5.3 is showing 'Too many display busses'. Thank you!

tannewt commented 4 years ago

@MiracleToast We don't have any plans to change the default setting. You can recompile CircuitPython to change CIRCUITPY_DISPLAY_LIMIT to whatever you need. What board are you using?

MiracleToast commented 4 years ago

We're using the Feather M4. I'll look at doing the recompile -- will that work w/ the latest cp v5.3? I noticed some folks were using two displays per bus -- do you know if we could have 4 displays on the primary SPI bus?

geekguy-wy commented 4 years ago

We can have more than one device on an SPI bus. Why are SPI displays treated differently? Why should SPI displays be treated differently?

MiracleToast commented 4 years ago

The SPI bus can certainly handle it, and the Feather M4 has native support for multiple displays if you're using the Adruino env. I'd just prefer to use the CP environment. Maybe there is an issue for the kind of system/debug info that CP will render on the display if the there is ambiguity about which one to use, not sure. I can recompile if that's all it takes -- just hoping I can use 4 displays on the m4 hardware spi bus.

tannewt commented 4 years ago

We're using the Feather M4. I'll look at doing the recompile -- will that work w/ the latest cp v5.3? I noticed some folks were using two displays per bus -- do you know if we could have 4 displays on the primary SPI bus?

Ya, that setting should work in 5.3.0. I believe folks tested up to 6 on the same bus so 4 should be ok. Note, you'll drop framerate because you can only compute and send so many pixels.

We can have more than one device on an SPI bus. Why are SPI displays treated differently? Why should SPI displays be treated differently?

Displays are weird because the objects live outside the heap in order for CircuitPython itself to use them. The define we pointed to changes the static allocation size to fit more displays. On M4 we could probably make the default higher.

I'm not a huge fan of how it works though and probably want to rework the API for multi-displays at some point to make mirroring easier.

darianbjohnson commented 3 years ago

Wanted to add a quick comment - I was able to build a modified version of CPy for an ItsyBitsyM4! It took some time to get the build environment working... but I was ultimately able to display data on two 2.4 TFTs (ili9341). Thanks for this thread!!!!

SwannSchilling commented 3 years ago

I would like to use two separate Oleds on I2C, is there any way to do this...is there any build that has a higher Display limit that I could try? Maybe @darianbjohnson could share his build? I am also using the ItsyBitsy M4...

FoamyGuy commented 3 years ago

@SwannSchilling the MONSTER M4SK device has two displays built in. But they use SPI busses I believe instead of I2C. You can see an example of the build flag that allows mulitple displays in that devices port directory here: https://github.com/adafruit/circuitpython/blob/main/ports/atmel-samd/boards/monster_m4sk/mpconfigboard.h

I don't have experience using multiple screens on any other devices, but perhaps looking over the configuration of this one can get you started on what you need to change in your build for the Itsy Bitsy M4

SwannSchilling commented 3 years ago

@FoamyGuy thanks a lot for the Quick Response, and your suggestion! I realized that the MONSTER M4SK has two screens attached, actually I would want to use my to Oleds for kind of the same thing... They are meant to be for robots eyes! I just don't know how to compile Circuit Python for the ItsyBitsy M4, I could give it a try...but it might turn out to be frustrating.

I thought maybe I could use the one @darianbjohnson already compiled! Since he said, it took him a while to get the build environment working, I figured, it might not be a simple task...

FoamyGuy commented 3 years ago

@SwannSchilling There are some great resources to help you along if you do end up wanting to give it a try. This guide covers the process very well: https://learn.adafruit.com/building-circuitpython/build-circuitpython. Also there are plenty of helpful folks in the Adafruit Discord, linked in the readme of this repository, who are willing to help you through issues that come up.

I do understand though it can seem a bit daunting if you've never done it. And like with many things that are this technical can be a bit frustrating if / when you run into issues.

If darianbjohnson can provide their known good build that would be a great place to start from. It will likely be a bit out of date at this point unless they've made new builds recently. But good to start from something known to work.

I made a build for Itsy Bitsy M4 that includes the #define CIRCUITPY_DISPLAY_LIMIT (2) build flag in mpconfigboard.h

I think this is all that is needed to enable multiple displays. But am not certain.

I loaded this firmware on a device and it does seem to boot up and report it's version correctly and allowed me to access the REPL. I can't do any testing beyond that at this time. But you can give this UF2 a try if you'd like it may be set up correctly for allowing 2 displays.

ItsyBitsyM4_2Display_uf2.zip

SwannSchilling commented 3 years ago

@FoamyGuy thank you so much!! This is great!! I will give it a try later tonight!! You just made my day!! :)

SwannSchilling commented 3 years ago

@FoamyGuy thank you so very much...you saved me!! Everything is working out great! I really like displayio, and was a little bummed out, that it did not let me use two displays, but now it all works like a charm!! :)

One thing I noticed, it seems like the allowed memory size of a bitmap is reduced?

Also I can see quiet a bit of performance drop... those screens are I2C at the moment, I will give it a try using SPI later!

Is there any way to reuse the group/sprite on the second screen and just flip it over? I checked the MONSTERM4SK and it seems like best practice is to assign left_group/right_group and left_sprite/right_sprite?

Thanks again for you help, and being so responsive!!! 😻

FoamyGuy commented 3 years ago

Nice. I'm glad that it worked out for you.

I'm not sure about the allowed memory size of a bitmap. But you do probably have less memory to play with overall for your project since you'll need to keep 2 of some objects instead of 1.

On the Monster M4sk I played with it for a bit and figured out that you can use the same Bitmap object, but I think you need different TileGrids and different Groups for each screen.

I didn't find a way within CircuitPython user code to only generate one screen and then "duplicate" it over to the other. I suspect it may be possible somehow, but is likely to involve changing some more significant things within CircuitPython itself and making a custom build with those changes.

Things did slow down noticeably once I started using the second screen on the Monster M4sk. I got simple animations working okay. But definitely nothing as smooth as the Arduino based M4 Eyes project. I have only scratched the surface with it still though. I think there is room to improve some by tuning the right tradeoffs in source image size and technique used for drawing / animating it

For instance: It seemed to me that adafruit_imageload based animation that changed between different colored sprites on a sheet ran smoother visually than OnDiskBitmap based motion animation (moving back and forth).

One trick to try to get a bit of memory back is scale your source assets down lower than "real size" in the bmp files that you include. Then in the code scale them back up with the Group they are inside of.

SwannSchilling commented 3 years ago

I might have also been confused, and saved the bitmap in a bigger version...it does not matter too much anyways, I will figure out which size is good for quality vs performance tradeoff! 😸

I actually did all of the above, I loaded in one bitmap using the adafruit_imageload and one TileGrid/Group for each screen. Also I reduced the bitmap size and scaled the group!

I am kind of thinking the performance drop is rather due to the limitations of I2C speed than bitmap size...but lets see! I really like the Circuit Python approach, but might also take a look into the performance when using the Arduino IDE, good point!

Thanks again for your help and suggestions!! I am really happy to move on, into further testing and building my robot!! 🤖

darianbjohnson commented 3 years ago

@SwannSchilling I'm glad @FoamyGuy was able to help you out. I've been heads down on "day job" stuff, and am just coming up for air. Can't wait to see what you build!

SwannSchilling commented 3 years ago

Thanks for all the help, I will do some further testing and keep you guys posted... Just about to set up the animations for those Oleds! 😄

SwannSchilling commented 3 years ago

One more thing that I am trying to figure out is...do I have to put the whole animation sequence into the initial sprite_sheet, or is it possible to use adafruit_imageload to load another bitmap?

edit I just realized, I have to delete a layer of the group to make room for another bitmap to be loaded in! 😄 group.pop()

todbot commented 3 years ago

Dang it! I just discovered this when trying to put multiple displays on the Pico. I understand the limitation but it's surprising, considering these are just SPI devices and the Pico has so many GPIO for CS/RST/DC pins.

bablokb commented 3 years ago

I'm also trying to connect multiple displays (ST7735) to a pico and fail because the dc-pin is checked if it is in use. I don't understand the rational behind that: as long as the CS pin does not select the device, it should ignore everything anyhow. So is it really necessary to check for dc-pin usage at all?

tannewt commented 3 years ago

@bablokb You almost always want pin in use checking because it catches misuses. This is a very special case where you want to share display busses. The easiest solution by far is to use a separate pins for the second DC and reset. Otherwise, we could check if another display bus objects are using the same reset and DC and ignore the duplication. It's not a priority for us though.

bablokb commented 3 years ago

I want to attach 12 (!) displays to a pico, using two SPI-busses. Although the pico is a pin-monster, this does not work out if I have a dedicated dc-pin for every display. (I'm building an advent Calendar with two picos and 24 displays in total).

"Catching misuse" is fine as long as the check is for real misuse and not for non-standard but valid usage. Since this is no priority on your side, I will see if I fork the library or switch to C.

deshipu commented 3 years ago

Wouldn't it be better to accept a DigitalInOut object instead of a pin object, so you can use the same instance in both places to avoid the error?

kevinjwalters commented 3 years ago

Can displayio drive multiple physical screens as one displayio.Display?