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
247 stars 37 forks source link

ESP32 with ILI9341 #30

Closed jeffmakes closed 1 year ago

jeffmakes commented 1 year ago

Following on from a previous issue, it seems that the ILI9341 driver does not run on the ESP32.

I fetched the esp32spiram-20220618-v1.19.1.bin Micropython binary and flashed it to an ESP-WROVER-E (ESP32D0WDQ5 revision 3 with SPIRAM).

Mounted with mpremote.

Checking free RAM:

>>> import gc
>>> gc.collect()
>>> print(gc.mem_free())
4092176

Approximately 4MB free.

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)
gc.collect()  # Precaution before instantiating framebuf
ssd = SSD(spi, cs=pcs, dc=pdc, rst=prst)
gc.collect()  # Precaution before instantiating framebuf

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

Running the 1.6 Hardware Quick Check from the ugui docs:

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

Behaviour is identical on ESP32 without SPIRAM. Behaviour is identical with ugui.py as-is, or disabled and compiled into ugui.mpy.

It would be great if you could have a look at this because micro-gui on the big, ubiquitous ILI9341 displays with ESP32 would be fantasticically useful, even if performance isn't as good as on STM32/Pico. I'm happy to sponsor some hardware if you'd like something to test on.

Cheers, jeffmakes

peterhinch commented 1 year ago

I've now spotted the bug. Sorry it's taken so long. It's a failing of old age that you forget things you knew well. This hardware_setup.py can be imported without error:

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)
gc.collect()  # Precaution before instantiating framebuf
ssd = SSD(spi, cs=pcs, dc=pdc, rst=prst)
from gui.core.ugui import Display  # Must perform this import after instantiating SSD (see other examples)
gc.collect()  # Precaution before instantiating framebuf

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

The problem is that importing Display requires SSD already to have been instantiated. It is a little incestuous.

I also changed string pin numbers to integers - I don't know if strings work, but integers do.

However I can't actually claim success. The only ESP32's with SPIRAM I possess are ancient boards designed and built by an amateur. I upgraded the firmware and got a repl, but the board crashed when I tried to run the hardware test script.

On an ESP32 reference board the board hung, presumably due to lack of RAM.

However there is a definite bugfix and you may have success with good SPIRAM hardware. Please let me know the outcome.

jeffmakes commented 1 year ago

Thanks very much for looking at this again, Peter.

I get the same result as you - pasting your setup and running both with and without ugui.py precompiled, I now get a panic:

>>> from hardware_setup import ssd 
Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Core  1 register dump:
PC      : 0x400e16a4  PS      : 0x00060730  A0      : 0x800d826f  A1      : 0x3ffccc80  
A2      : 0x3f817a40  A3      : 0x000000b1  A4      : 0x3ffccca0  A5      : 0xbbbfeedf  
A6      : 0x00000a4b  A7      : 0x0000000c  A8      : 0x00000019  A9      : 0x3ffccc60  
A10     : 0xbbbfeedf  A11     : 0x3ffc40ec  A12     : 0x3ffc4150  A13     : 0x00000001  
A14     : 0x3f81a341  A15     : 0x3f81a343  SAR     : 0x00000000  EXCCAUSE: 0x0000001c  
EXCVADDR: 0xbbbfeefb  LBEG    : 0x40094706  LEND    : 0x40094711  LCOUNT  : 0x00000000  

Backtrace:0x400e16a1:0x3ffccc80 0x400d826c:0x3ffccca0 0x400f4818:0x3ffccce0 0x40104eaf:0x3ffccd00 0x400db7c2:0x3ffccd20 0x400e18e1:0x3ffccd40 0x400e1a32:0x3ffccd60 0x40085be5:0x3ffccd80 0x400db8f8:0x3ffcce20 0x400e18e1:0x3ffcce90 0x400e190a:0x3ffcceb0 0x400e198e:0x3ffcced0 0x400fc825:0x3ffccf60 0x400fcafb:0x3ffccf90 0x400e1ab2:0x3ffcd070 0x40085e69:0x3ffcd0b0 0x400db8f8:0x3ffcd150 0x400e18e1:0x3ffcd1a0 0x400e190a:0x3ffcd1c0 0x400edffa:0x3ffcd1e0 0x400ee369:0x3ffcd270 0x400d5214:0x3ffcd2b0

ELF file SHA256: 46bca36b7d6020a6

Rebooting...

Any ideas?

peterhinch commented 1 year ago

I have no experience with SPIRAM devices. I bought a couple when they were very new and was so appalled at the GC time that I put them in a drawer and ignored them. You might make enquiries to see if other people have had problems running large applications.

As a general point, prior to the RP2 an ESP32 was my primary host for testing small displays. This was on grounds of cost and availability. I have a PCB which lets me run a variety of smaller LCD and OLED displays: the host is the ESP32 reference board. I therefore think that ESP32-specific issues are unlikely.

The other approach is to grasp the nettle and get the toolchain for building ESP32.

However experimenting with your hardware_setup.py indicates that there is a problem with some of your pin allocations, which causes it to hang - changing pins 2, 3, and 9 gets rid of the hang, but leaves a memory allocation error. This may be fixable by cross-compiling. I'll look at this tomorrow.

peterhinch commented 1 year ago

I suggest you study this diagram which shows which pins are legal to use.

I changed hardware_setup.py to use legal pin numbers and it now appears to run. I haven't yet hooked up an ILI9341 but the aclock demo runs and emits free RAM values so it's running. Here is the hardware_setup.py. Adaptations are also to match my PCB so I can easily attach a 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(27, Pin.OUT, value=0)  # Arbitrary pins borrowed from st7735r_esp32.py
prst = Pin(26, Pin.OUT, value=1)
pcs = Pin(25, Pin.OUT, value=1)
gc.collect()  # Precaution before instantiating framebuf
spi = SPI(1, 20_000_000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
freq(160_000_000)
gc.collect()  # Precaution before instantiating framebuf
ssd = SSD(spi, cs=pcs, dc=pdc, rst=prst)
from gui.core.ugui import Display  # Must perform this import after instantiating SSD (see other examples)
gc.collect()  # Precaution before instantiating framebuf

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

I have now cross-compiled ugui.py. I copied the resultant ugui.mpy to the non-SPIRAM ESP32, deleted ugui.py from that device, copied the new hardware_setup.py to that device, and ran the aclock demo. It runs, but reports free RAM of only 17K.

In my view running on a non-SPIRAM device is marginal. You will need to use frozen bytecode to do anything useful with this configuration.

peterhinch commented 1 year ago

I can now confirm that the SPIRAM device runs the demo scripts on an ILI9341. I haven't yet quantified its responsiveness to pushbutton events but I don't anticipate serious problems. I recommend copying the GUI files to the device as loading via mpremote mount is very slow.

In conclusion there is no problem with the device driver so I'll close this issue. [EDIT] Response to button presses is fine.

jeffmakes commented 1 year ago

It works! Thanks for figuring this out, Peter. I'm excited to start developing my project :D