Using lv_micropython with tileview status

I am thinking to use lv_micropython for an ESP32 project. I have an 80x160 display vertically mounted and “up/select/down” buttons. What I would like to do is have some vertically stacked tiles that I can use the buttons to interact with. I see that tileview is only partially implemented? I can create the tiles, but I cannot scroll between them in any meaningful fashion. Wondering if there is anyone who has successfully used tileview that might could give me some advice, or perhaps a branch where it’s being developed?

Thank you,
Daniel McShan

Hello!

Tileview is fully implemented, and is working without problems even in MicroPython.
My code in which I use is not public (yet), but I can help you if you ask questions about specific problems.

Here is a simple example for your project:

// Create tileview:
tileview = lv.tileview(lv.screen_active())
tileview.remove_style_all()
tileview.set_pos(0, 0)
tileview.set_size(80, 160)
tileview.add_event_cb(tileview_slide_cb, lv.EVENT.VALUE_CHANGED, None)

// Variables to store tiles count, and active tile index
tiles_count = 0
tile_index = 0

// Add tiles:
def add_tile():
	global tiles_count
	tiles_count += 1
	return tileview.add_tile(0, tiles_count - 1, lv.DIR.BOTTOM)

// Functions to slide up/down the tileview:
def slide_up():
	global tile_index
	tile_index = max(0, tile_index - 1)
	tileview.set_tile_by_index(0, tile_index, lv.ANIM.ON)

def slide_down():
	global tile_index
	tile_index = min(tiles_count - 1, tile_index + 1)
	tileview.set_tile_by_index(0, tile_index, lv.ANIM.ON)

// Event handler when tileview is slid:
def tileview_slide_cb(event):
	pass
	// You can refresh objects in the tile, start animations, etc...

Cool. I am using lv_micropython fork of micropython (GitHub - lvgl/lv_micropython: Micropython bindings to LVGL for Embedded devices, Unix and JavaScript), and much of that is not implemented,

help(lv.tileview)
object <class ‘tileview’> is of type type
add_tile –
get_tile_act –

Like, there is no set_tile_by_index nor add_event_cb.

Not sure where the disconnect is?

Hi.
I forgot to mention, that the main branch contains an old and unstable LVGL 9.0 “beta version”. But the documentation reflect the latest LVGL 9.0 changes, so that’s why it seems some functions don’t exist.
You should checkout lv_micropython feat/multi-instance branch, which has the latest LVGL changes, and matches the documentation (mostly).
That should work.
There is also an other PR (Update LVGL and fix pixel size detection by PGNetHun · Pull Request #298 · lvgl/lv_binding_micropython · GitHub) which contains display driver fixes, so when that is merged, pull again multi-instance branch.
The stabilization of LVGL v9, MicroPython binding and documentation is in progress, and it will be finished in the coming weeks.

Does this branch handle external fonts differently? I am using ones produced by the lvgl tool and getting these errors. Is there an easy fix?

/Users/danielmcshan/GitHub/mct_micropython/lib/lv_bindings/lvgl/src/font/conseil_40.c:3328:9: error: unknown type name ‘lv_font_fmt_txt_glyph_cache_t’
static lv_font_fmt_txt_glyph_cache_t cache;
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Users/danielmcshan/GitHub/mct_micropython/lib/lv_bindings/lvgl/src/font/conseil_40.c:3343:6: error: ‘lv_font_fmt_txt_dsc_t’ {aka ‘const struct ’} has no member named ‘cache’
.cache = &cache
^~~~~

Also, FYI, I get this error:

Error: app partition is too small for binary micropython.bin size 0x243e60:

  • Part ‘factory’ 0/0 @ 0x10000 size 0x240000 (overflow 0x3e60)

Seems easy enough to fix in esp32/partitions.csv

factory, app, factory, 0x10000, 0x250000,
vfs, data, fat, 0x260000, 0x1a0000,

Also (at some point, I should just raise issues, but since I have you here…)

MPY: soft reboot
Traceback (most recent call last):
File “”, line 4, in
File “ili9XXX.py”, line 539, in
File “ili9XXX.py”, line 545, in ili9341
AttributeError: type object ‘LV_COLOR_FORMAT’ has no attribute ‘NATIVE_REVERSED’

The driver I am using defaults to NATIVE_REVERSED, which I was using, and no longer exists?

Don’t hate me…

Traceback (most recent call last):
File “”, line 13, in
File “ili9XXX.py”, line 822, in init
RuntimeError: st7735 micropython driver requires defining LV_COLOR_DEPTH=16

It is defined as such in both lv_conf.h and in my make line:
% make -C ports/esp32 LV_CFLAGS=“-DLV_COLOR_DEPTH=16” BOARD=GENERIC_SPIRAM PORT=/dev/cu.SLAB_USBtoUART clean erase deploy

Would you prefer I file these as issues? The previous are not show stoppers, but this one I’m a bit lost on…

No problem, you can ask me, I just reply a bit delayed.

So:

  1. Fonts: yes, loading has changed, and it is working. But you have to init FS first, and assign a letter to FS driver. In my example letter “S” is assigned to FS driver.

My font_helper.py:

import lvgl as lv
FONTS_LVGL_PATH = "S:fonts"
def load(name, lvgl_path: str = FONTS_LVGL_PATH):
    font = lv.font_t()
    if lv.binfont_load(font, f"{lvgl_path}/{name}"):
        return font
    return None
  1. Partition issue: you can change the size, or just copy-paste the partitions file from other board (I use GENERIC_SPIRAM board)

  2. LV_COLOR_FORMAT - NATIVE_RESERVED: at the moment display drivers don’t work on multi-instance branch, I have a PR for it (Update LVGL and fix pixel size detection by PGNetHun · Pull Request #298 · lvgl/lv_binding_micropython · GitHub), which contains the fix, it just needs to be merged to multi-instance branch (or you can copy-paste the changes to your repo). I have not tested ST7735, due to I have only ILI9341 and ST7789.
    There is also a separate discussion about replacing these display drivers with a new generic way, but that will be finished later: MicroPython Display Drivers part 2 - #3 by bdbarnett

For building and deploying things to device, I have some bash scripts created:

env-variables.sh

ESPIDF=~/esp/esp-idf-4-4-5

MICROPYTHON=~/src/lv_micropython

#BOARD=GENERIC
BOARD=GENERIC_SPIRAM

source $ESPIDF/export.sh

clean.sh

. ./env-variables.sh

# Clean mpy-cross
cd $MICROPYTHON/mpy-cross
make clean

# Clean Unix port
cd $MICROPYTHON/ports/unix
make clean
make clean VARIANT=dev

# Clean ESP32 port boards
cd $MICROPYTHON/ports/esp32

BOARD=GENERIC
make clean BOARD=$BOARD

BOARD=GENERIC_SPIRAM
make clean BOARD=$BOARD

build-generic-spiram.sh

. ./env-variables.sh

BOARD=GENERIC_SPIRAM

cd $MICROPYTHON
make -C mpy-cross

cd $MICROPYTHON/ports/esp32
make submodules
make BOARD=$BOARD V=1

deploy.sh

. ./env-variables.sh

cd $MICROPYTHON/ports/esp32
make deploy BOARD=$BOARD

Okay, I got it compiled and more or less working.

If I can bother you for one more review to get this proof of concept working, this is a variation of your test code. I expect to have three tiles, different colors, and to switch between them.

The background on the screen is yellow, but I do see a flash of a blue screen when it loads… so I am suspecting I just have something slightly off. Can you take a peek?

Thank you for your work on this! It’s very cool!

import lvgl as lv
import time
import machine
from ili9XXX import st7735, COLOR_MODE_BGR

# Initialize LVGL
lv.init()

# Initialize the display driver
disp = st7735(
    mhz=3, mosi=14, clk=2, cs=5, dc=26, rst=27, power=-1, backlight=15, backlight_on=1,
    width=80, height=160, start_x=26, start_y=1, rot=0,
    color_format=lv.COLOR_FORMAT.RGB565, colormode=COLOR_MODE_BGR,
    invert=True
)

lv.screen_active().set_style_bg_color(lv.color_make(255,255,0),0)

#Create tileview:
tileview = lv.tileview(lv.screen_active())
tileview.remove_style_all()
tileview.set_pos(0, 0)
tileview.set_size(80, 160)
#tileview.add_event_cb(tileview_slide_cb, lv.EVENT.VALUE_CHANGED, None)

#Variables to store tiles count, and active tile index
tiles_count = 0
tile_index = 0

# Add tiles with different colors
def add_tile(color):
    global tiles_count
    tile = tileview.add_tile(0, tiles_count, lv.DIR.BOTTOM)
    tile.set_style_bg_color(color, 0)
    tiles_count += 1
    return tile

# Functions to slide up/down the tileview
def slide_up():
    global tile_index
    tile_index = max(0, tile_index - 1)
    tileview.set_tile_by_index(0, tile_index, lv.ANIM.ON)

def slide_down():
    global tile_index
    tile_index = min(tiles_count - 1, tile_index + 1)
    tileview.set_tile_by_index(0, tile_index, lv.ANIM.ON)

# Debounce time in milliseconds
debounce_ms = 200
last_press_time = time.ticks_ms()

# Button press event handlers
def handle_SMU(pin):
    global last_press_time
    print("SMU")
    if time.ticks_diff(time.ticks_ms(), last_press_time) > debounce_ms:
        last_press_time = time.ticks_ms()
        slide_up()

def handle_SMD(pin):
    print("SMD")
    global last_press_time
    if time.ticks_diff(time.ticks_ms(), last_press_time) > debounce_ms:
        last_press_time = time.ticks_ms()
        slide_down()

# Setup button interrupts
SMU = machine.Pin(36, machine.Pin.IN, machine.Pin.PULL_UP)
SMD = machine.Pin(34, machine.Pin.IN, machine.Pin.PULL_UP)
SMU.irq(trigger=machine.Pin.IRQ_FALLING, handler=handle_SMU)
SMD.irq(trigger=machine.Pin.IRQ_FALLING, handler=handle_SMD)

# Add three tiles
add_tile(lv.color_make(255, 0, 0))   # Red tile
add_tile(lv.color_make(0, 255, 0))   # Green tile
add_tile(lv.color_make(0, 0, 255))   # Blue tile

while True:
    lv.task_handler()


I added a label with a count to the tileview, and it seems to be working properly (I can see the tile sliding), but the background color is still not set? I can figure something out. Thank you again!

Is there a way to modify the .c files to have the fonts compiled in? Or does this branch only support binfont files?

You should play around the ST7735 display init:
if you have built MicroPython using my changes (from PR Fix display drivers and color mode, update LVGL config file, and fix pixel size detection by PGNetHun · Pull Request #298 · lvgl/lv_binding_micropython · GitHub ), then:
change parameter invert to False, add parameter swap_rgb565_bytes, and change to False / True.
As I see ST7735 was also a “special” display, meaning that it needs RGB565 byte swap:
Fix display drivers and color mode, update LVGL config file, and fix pixel size detection by PGNetHun · Pull Request #298 · lvgl/lv_binding_micropython · GitHub

Fonts:
the built-in fonts are enabled in lv_conf.h:

I think loading fonts dynamically is also enough.
All other solutions require to change C files and build scripts, so it requires to build a custom firmware.

I ask about the font because I am not able to make the binfont work properly with your branch for ESP32. Static loading used to work, but now that is broken as well. Here is my latest attempt. I tried without fs_driver first, but that also just core dumps, so I thought it was worth giving a try.

I can use static or dynamic loading, but I need for one of them to work…

import lvgl as lv
import sys
import fs_driver

fs_drv = lv.fs_drv_t()
fs_driver.fs_register(fs_drv, 'S')

lv.init()

from ili9XXX import st7735, COLOR_MODE_RGB, COLOR_MODE_BGR

# Initialize LVGL and Display
disp = st7735(
    mhz=3, mosi=14, clk=2, cs=5, dc=26, rst=27, power=-1, backlight=15, backlight_on=1,
    width=80, height=160, start_x=26, start_y=1, rot=0,
    color_format=lv.COLOR_FORMAT.NATIVE, colormode=COLOR_MODE_BGR,
    invert=True
)

try:
    
    conseil10 = lv.font_t()
    lv.binfont_load(conseil10, "S:/app/mct/font/conseil10.font")
    
    ltr_label = lv.label(lv.screen_active())
    ltr_label.set_text("test")
    ltr_label.set_style_text_font(conseil10, 0);

except Exception as e:
    sys.print_exception(e)


I get:

MPY: soft reboot
Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Have you seen this work on ESP32, by chance? The example seems to be for unix…

The font loading is working for me.

You can also print out LVGL log messages, for example if font is not found, LVGL logs that.
Just add after lv.init() this line:
lv.log_register_print_cb(print)

Please note, that when you run your MicroPython script, then the LVGL path root will be the current OS folder, and when your device runs the script, then the root will be “/” on your device FS, so please use always relative path when using LVGL, and check whether loading was success.

For example:

conseil10 = lv.font_t()
if lv.binfont_load(conseil10, "S:conseil10.font"):
      print("Font load: SUCCESS")
      ltr_label.set_style_text_font(conseil10, 0)
else:
      print("Font load: FAILED")

Please check also, that the font is in correct type.

I use this Windows batch script to convert from TTF to LVGL’s binary font file:

ttf2bin.bat

@ECHO OFF

SET FONT_NAME=%1
SET FONT_NAME=%FONT_NAME:.ttf=%

FOR /L %%G IN (10, 2, 50) DO @(
	ECHO Convert %FONT_NAME%.ttf to %FONT_NAME%-%%G.font
	
	REM ASCII characters
	lv_font_conv --font %FONT_NAME%.ttf --output %FONT_NAME%-%%G.font --bpp 1 --size %%G --format bin --no-kerning --range 0x20-0x7E
	
	REM Only letters:
	REM lv_font_conv --font %FONT_NAME%.ttf --output %FONT_NAME%-%%G.font --bpp 1 --size %%G --format bin --no-kerning --symbols abcdefghijklmnopqrstuvwxyz
	
	REM Unicode (Basic Latin, Latin-1 Supplement, Latin Extended-A, Greek and Coptic, Cyrillic):
	REM lv_font_conv --font %FONT_NAME%.ttf --output %FONT_NAME%-%%G.font --bpp 1 --size %%G --format bin --no-kerning --range 0x0020-0x007E --range 0x00A0-0x00FF --range 0x0100-0x017F --range 0x0370-0x03FF --range 0x0400-0x04FF
)

Usage: ttf2bin.bat test-font.ttf

Did binfont_load go away? I just rebased to feat/multi-instance, and it seems to no longer be there?

No, just renamed:
lv.binfont_create(full_path)

It returns the font itself if loaded, and exception if something is wrong.

I did figure that out. Thank you. Now I have new issue:

charge = lv.label(page)
charge.set_text(lv.SYMBOL.CHARGE)
charge.set_pos(68, 0)
style = lv.style_t()
style.init()
style.set_text_font(conseil10)
style.set_text_color(lv.color_make(50, 200, 50))
charge.add_style(style, 0)

This code core dumps. Should I just replace it with charge.set_style_text_font… etc?

Many problems could cause the error.

  1. Check, that font is really loaded, and conseil10 != None , and there is no exception
  2. I usually add some print("step xx") lines at specific points to see where the execution stops and error occurs
  3. Set LVGL logging print_cb(): after lv.init(), add line: lv.log_register_print_cb(print) . But before it make sure, that you compile the firmware with enabled logging: in lv_conf.h change MICROPY_LV_USE_LOG or LV_USE_LOG to 1