peterhinch / micropython-micro-gui

A lightweight MicroPython GUI library for display drivers based on framebuf, allows input via pushbuttons. See also micropython-touch.
MIT License
256 stars 37 forks source link

After cross-compilation: "ImportError: can't import name SSD" #28

Closed jeffmakes closed 1 year ago

jeffmakes commented 1 year ago

First off, thanks for your work on this library. I'm sure I'll make use of it in many a project. I have a generic ESP32 module (ESP32D0WDQ5) Firebeetle v4 hooked up to a ILI9341 TFT (random Amazon brand "Dollatek"). I'm running esp32-20220618-v1.19.1.bin

Following the ugui and ESP32 Micropython docs, I adapted a hardware_config.py from ili9341_pyb.py:

from gui.core.ugui import Display
from machine import Pin, SPI
import gc

from drivers.ili93xx.ili9341 import ILI9341 as SSD
# Create and export an SSD instance
pdc = Pin('3', Pin.OUT, value=0)  # Arbitrary pins
prst = Pin('2', Pin.OUT, value=1)
pcs = Pin('9', Pin.OUT, value=1)
spi = SPI(2, baudrate=30_000_000)
gc.collect()  # Precaution before instantiating framebuf
ssd = SSD(spi, cs=pcs, dc=pdc, rst=prst)

# Create and export a Display instance
# Define control buttons
nxt = Pin('4', Pin.IN, Pin.PULL_UP)  # Move to next control
sel = Pin('5', Pin.IN, Pin.PULL_UP)  # Operate current control
prev = Pin('6', Pin.IN, Pin.PULL_UP)  # Move to previous control
increase = Pin('7', Pin.IN, Pin.PULL_UP)  # Increase control's value
decrease = Pin('8', Pin.IN, Pin.PULL_UP)  # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease)

Following 1.6 - Quick hardware check:

>>> from hardware_setup import ssd
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "hardware_setup.py", line 34, in <module>
MemoryError: memory allocation failed, allocating 776 bytes

About a 10-second pause between executing the command and the error.

So I followed Appendix 3. I cross-compiled ugui.py to ugui.mpy, and moved the original to ugui.py.ignore Trying again:

>>> from hardware_setup import ssd
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "hardware_setup.py", line 34, in <module>
  File "ugui.py", line 13, in <module>
  File "gui/core/colors.py", line 5, in <module>
ImportError: can't import name SSD

Same ~10s pause.

And now I'm stumped

peterhinch commented 1 year ago

I'm not familiar with the ESP32D0WDQ5 which appears to be obsolete. How much available RAM has it got? A 3.2 inch ILI9341 requires 37.5KiB of contiguous RAM for its buffer which is quite high. The 10s delay and the inability to import do point to memory issues.

Please run the following after issuing a soft reset:

import gc
gc.collect()
print(gc.mem_free())
jeffmakes commented 1 year ago

Hi Peter.

>>> print(gc.mem_free())
105120
jeffmakes commented 1 year ago

I also have a TTGO T-display but, after erasing and successfully flashing esp32-20220618-v1.19.1.bin, I can't talk to it with mpremote (it just hangs).

Am I using the correct MPY binary? There is a large number of board-specific downloads from the MPY website, but I'm unclear on what actually differs between them.

peterhinch commented 1 year ago

105K free is very tight for such a large display. Options are to use a smaller display or to experiment with frozen bytecode. The entire GUI can be frozen, although I usually leave hardware_setup.py and the display driver (with boolpalette.py) on the physical device because I have had problems with some drivers when frozen.

There are numerous variants of ESP32. For a bog standard ESP32 I use this driver which is for the reference board. I'm 99% certain that's what I used on the T-display. Lack of a REPL suggests a wrong version.

jeffmakes commented 1 year ago

Interesting - after flashing the " LILYGO TTGO LoRa32" firmware (there is no plain TTGO or T-Display firmware) I can get that working with its onboard display with the unmodified st7789_ttgo.py config.

There is still a 10-second delay on import.

I do need to get my ILI9341 going, though. I'll buy a newer module - I'd much rather it be simple and stable than save a few quid. Is it desirable to use one with PSRAM, or should I get one with an ESP32 with more internal RAM?

And yes, I'm currently using the same driver as yours, so I guess my chip is incompatible. Do you know if there's a resource somewhere that lists the actual chips (rather than modules) supported by that driver?

jeffmakes commented 1 year ago

Ok, it's not a RAM issue, and it's not a binary compatibility problem.

The T-Display contains a ESP32D0WDQ6-V3. That works fine. The Firebeetle contains a ESP32-WROOM-32D which contains a ESP32D0WDQ5. These two chips are almost identical, and have identical memory specs (both RAM and flash).

I'm now running the "LILYGO TTGO LoRa32" firmware on both, and I get a REPL on both. Running the stock st7789_ttgo.py config on both, after a 10-20sec import it runs without error (and draws to the screen on the TTGO).

Running my ILI9341 config I get the crash

>>> from hardware_setup import ssd
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "hardware_setup.py", line 34, in <module>
  File "ugui.py", line 13, in <module>
  File "gui/core/colors.py", line 5, in <module>
ImportError: can't import name SSD

So it must be something in my config. Can you please have a look? Here's my latest hardware_setup.py:

from gui.core.ugui import Display
from machine import Pin, SPI, freq
import gc

from drivers.ili93xx.ili9341 import ILI9341 as SSD
# Create and export an SSD instance
pdc = Pin('3', Pin.OUT, value=0)  # Arbitrary pins
prst = Pin('2', Pin.OUT, value=1)
pcs = Pin('9', Pin.OUT, value=1)
gc.collect()  # Precaution before instantiating framebuf
spi = SPI(2, baudrate=30_000_000, sck=Pin(18), mosi=Pin(23))
freq(160_000_000)
ssd = SSD(spi, cs=pcs, dc=pdc, rst=prst)

# Create and export a Display instance
# Define control buttons
nxt = Pin('4', Pin.IN, Pin.PULL_UP)  # Move to next control
sel = Pin('5', Pin.IN, Pin.PULL_UP)  # Operate current control
prev = Pin('6', Pin.IN, Pin.PULL_UP)  # Move to previous control
increase = Pin('7', Pin.IN, Pin.PULL_UP)  # Increase control's value
decrease = Pin('8', Pin.IN, Pin.PULL_UP)  # Decrease control's value
display = Display(ssd, nxt, sel, prev, increase, decrease)
jeffmakes commented 1 year ago

Actually, you know what? Just tell me what ESP32 module to buy!

I have no system constraints other than what it takes to get ugui to work in a simple, stable way. Doesn't matter if the module is £50.

peterhinch commented 1 year ago

I can't see anything wrong there. Please run this check from the docs:

from hardware_setup import ssd  # Create a display instance
from gui.core.colors import *
ssd.fill(0)
ssd.line(0, 0, ssd.width - 1, ssd.height - 1, GREEN)  # Green diagonal corner-to-corner
ssd.rect(0, 0, 15, 15, RED)  # Red square at top left
ssd.rect(ssd.width -15, ssd.height -15, 15, 15, BLUE)  # Blue square at bottom right
ssd.show()

If that works your hardware and hardware_setup.py are good and the fault lies elsewhere.

Running this check is crucial on any new setup.

jeffmakes commented 1 year ago

Thanks Peter. I'm following the docs scrupulously. The crash occurs on the first line of the check, as you can see in my post above.

Has the ILI1341 driver been tested on ESP32? That seems to be the problem.

peterhinch commented 1 year ago

What about the TTGO display? I have just installed the latest ESP32 Generic nightly build from this page and it works fine. I would recommend this firmware (or the 1.19.1 release build) for units based on a standard ESP32 without SPIRAM. Time to load the aclock.py demo is about 3s. All files are stored on the device in .py form.

Re the ILI9341 testing was on the RPI Pico: these displays require large framebuffers and I'm not surprised they don't work on a device with only 100K of free RAM. The drivers for smaller displays have been tested on multiple platforms including ESP32. All supported displays have a driver written to be cross-platform so I'd be surprised if there were any ESP32 specific issues.

To gain RAM you could try an ESP32 with SPIRAM (which will need suitable firmware). Another possibility is a Pico W which has 161K of free RAM. If you don't need WiFi the Pico has even more.

For comparison, the driver for the even larger ILI9486 needs a 76,800byte buffer. Even on a Pico I needed to use frozen bytecode to enable it to work, when it worked well with 75K free.

jeffmakes commented 1 year ago

Yes, as you can see above the TTGO display works fine. It still takes 10-20s to load, but perhaps that's because I'm using mpremote mount.

It's strange because the RPI Pico has much less RAM than the ESP32. The RP2040 has 264kB whereas all ESP32's (excluding the -S, -H, -C series) have 520kB. I guess the WiFi stack on the ESP32 uses a load...

I'll order some WROVER-based dev boards which have PSRAM (SPI RAM). I'm committed to ESP32 rather than RPI. I still have the feeling that something else is at fault.

Thanks for all your help.

peterhinch commented 1 year ago

mpremote mount does slow things down because the files have to be sent down USB then compiled on the target. Local .py files are faster. Frozen bytecode is very fast as the compiler doesn't have to run: I have a Pico running a substantial app on a large ILI9486 which boots from power up to full display in 2s. With about 70K of free RAM.

The ESP32 runs an entire OS on one core which is why you see only 105K (your measurement). As you may have gathered from my comments, the reason I didn't test on ESP32 was that I didn't think it had much chance of working. Also I've struggled to build for ESP32. I'm pretty sure that frozen bytecode would work with this display.

FWIW I much prefer the Pico W to ESP32 because it is a bare metal port. WiFi is delegated to a separate chip (like Pyboard D). It therefore has a much better realtime response with hard IRQ's.

On ESP32 SPIRAM fixes the memory space issue, but does not help with realtime response with GC times of 100ms. I have made suggestions as to how this could be improved, but to date the maintainers have not implemented this (or other ideas that have been floated).

But each to their own: the joy of MP is that we have a lot of choice :)

I'll close this, but if you have problems on a platform with adequate RAM please raise a new issue.

jeffmakes commented 1 year ago

Thanks Peter. I have now tested again with an ESP32 with SPIRAM, and the problem remains. I'll open a new issue.