WIP - lv_circuitpython

This probably could have been posted in the projects group as well but seeing how it’s a modification of micropython figured I’d post here.

I noticed a few different questions and posts about getting LVGL to work with circuitpython so I thought I’d share a project I have been working on to modify the bindings to work with circuitpython. A test repo can be found here if anyone is interested: http://github.com/desterly/lv_circuitpython

This is based on:
HEAD of circuitpython (6.0 RC2)
HEAD of lv_bindings
HEAD of lvgl.

Ultimately, the main differences between the main lv_bindings are below:

  • created gen_cp.py file to generate bindings source compatible with CP - this is the same as gen_mp.py with a few modifications for CP.
  • renamed the “decompress” method in lv_font_fmt_txt.c to lv_decompress due to CP having it’s own decompress
  • created a LVGlue C module which acts as the core display driver for LVGL by hooking into CP’s displayio

LVGlue more or less works like this:

import lvgl as lv
import lvglue
display = lvglue.display()
display.setActive()
… normal LV code

During init, LVGlue’s display:

  • creates a dummy displayio group and adds to CP’s displayio
  • init’s lvgl and sets the displaydriver hooks

setActive sets the dummy group as active in displayio

LVGL’s tick and refresh is handled by CP’s supervisor tick module.

flush calls flush LVGL’s buffer via displayio core’s flush methods.
during flush if the dummy bitmap isn’t active (IE: lvgl isn’t current display on CP), the flush simply returns.

This process allows LVGL to act as another CP display “group” allowing use of both LVGL and CP’s display processes.

Touch is handled currently in a py module utilizing the adafruit_touchscreen module.
File storage is handled by the py storage module posted here in another topic.

Currently everything is hooked into the atmel port (specifically the pyportal board) however it should work for all CP ports with displayio.

1 Like

Nice work @Desterly !
Thank you for sharing this with us.

I’m curious though, what are the differences between gen_cp.py and gen_mpy.py? gen_mpy.py is quite generic, it is also used with other libraries. What is different in circuitpython that required these changes?

Looking forward, wouldn’t it be better to have a single lv_binding_micropython repo that works with both Micropython and Circuitpython?
There is active development around the next version of LVGL (v8) and I expect that lv_binding_micropython would also need to evolve. Having a single lv_bindings could lower the porting and maintanance effort for Circuitpython in the future, don’t you think?

Totally agree.

There are two main changes that had to be made

  1. two commits rolled back that deal with upstream micropython changes. Seeing how CP only merges micropython at major releases and then only after a delay, those two commits cause issues.

Specifically: Fixes required when updating to Micropython upstream (master) · lvgl/lv_binding_micropython@a2b13da (github.com)

bugfix: define static/non-static function objects · lvgl/lv_binding_micropython@ddb4bda (github.com)

  1. Couple minor changes to remove compile warnings… mostly () to (void) (not a prototype warnings)

The second one probably should be made upstream but I haven’t tested it w/ micropython yet to create a pull request.

The first one could also probably be done with an argument… something like -CP and than just have the script format the string differently.

I’ll probably create PR’s for both once I’m sure I’m not making anymore changes to it.

I’ll probably also put up a PR for the change i had to make to LVGL itself (decompress to lv_decompress) - that one is only used in the font source, just a naming change, and only applies (now) when compression is enabled. With that, i won’t need a fork of LVGL

Ultimately yes, the goal will be that there’s no need for a fork

If I understand correctly, CircuitPython merges Micropython’s major releases.
I’m not sure which release CircuitPython is aligned to now, but latest Micropython release (1.13) requires this .flags = MP_TYPE_FLAG_BINDS_SELF | MP_TYPE_FLAG_BUILTIN_FUN change and subsequent static/non-static function distinction.
We could probably make this behavior conditional to accommodate both Micropython and CircuitPython, as you suggested with the -CP argument.

Such fixes are welcome.

That would be great!

Could you explain how decompress affects CircuitPython?
decompress is defined as a static function in lv_font_fmt_txt.c, so it does not link outside this compilation unit. There should be no problem with other appearances of different decompress functions on other C files.

They have not updated to 1.13 yet: https://github.com/adafruit/circuitpython/issues/2999

You are correct that they track major releases but unfortunately they haven’t updated to 1.13 yet. They will eventually but with Circuitpython 6.0 in Beta and now RC stage my guess is it won’t happen till after 6.0 release.

It’s the way they have the files structured. The cascading includes cause a reference to both:

…/…/lib/lv_bindings/lvgl/src/lv_font/lv_font_fmt_txt.c:46:13: error: conflicting types for ‘decompress’
46 | static void decompress(const uint8_t * in, uint8_t * out, lv_coord_t w, lv_coord_t h, uint8_t bpp, bool prefilter);
| ^~~~~~~~~~
In file included from …/…/py/obj.h:37,
from …/…/py/mpstate.h:35,
from …/…/lib/lv_bindings/lvgl/src/lv_font/lv_font_fmt_txt.c:21:
…/…/supervisor/shared/translate.h:79:7: note: previous declaration of ‘decompress’ was here
79 | char* decompress(const compressed_string_t* compressed, char* decompressed);
| ^~~~~~~~~~

I’m looking to see if there’s another way to handle it. If it’s possible to make the change in CP instead, that works too… makes more sense in a way as you have to add the bindings to CP anyway… one more change won’t hurt.

It’s also worth noting that decompress is not the best naming choice on their end for a global function. It would be better to prefix it like cp_decompress (the way MicroPython does with mp_<function>).

Heh… well it’s not the first oddity I ran across w/ it. What’s worse is this decompress is used everywhere

I did find an alternate method… If I create a new header with this:

#define decompress decompress_orig
#include “py/mpstate.h”
#undef decompress

and then reference that header for LV_GC_INCLUDE in lv.conf it solves the problem. That makes my skin crawl though.

It could be worth opening an issue about that… might be hard to get them to change it though.