MicroPython 的 LVGL 绑定
This repo is a submodule of lv_micropython. Please fork lv_micropython for a quick start with LVGL MicroPython Bindings.
See also Micropython + LittlevGL blog post. (LittlevGL is the previous name of LVGL.) For advanced features, see Pure MicroPython Display Driver blog post. For questions and discussions - please use the forum: https://forum.lvgl.io/c/micropython
MicroPython Binding for LVGL provides an automatically generated MicroPython module with classes and functions that allow the user access much of the LVGL library.
The module is generated automatically by the script gen_mpy.py.
This script reads, preprocesses and parses LVGL header files, and generates a C file lv_mpy.c which defines the MicroPython module (API) for accessing LVGL from MicroPython.
Micopython's build script (Makefile or CMake) should run gen_mpy.py automatically to generate and compile lv_mpy.c.
lv_mpy.c looks like, have a look at lv_mpy_example.c. Note that its only exported (non static) symbol is mp_module_lvgl which should be registered in MicroPython as a module.It's worth noting that the Micropython Bindings module (lv_mpy.c) is dependent on LVGL configuration.
LVGL is configured by lv_conf.h where different objects and features could be enabled or disabled. LVGL bindings are generated only for the enabled objects and features. Changing lv_conf.h requires re running gen_mpy.py, therefore it's useful to run it automatically in the build script, as done by lv_micropython.
When LVGL is built as a MicroPython library, it is configured to allocate memory using MicroPython memory allocation functions and take advantage of MicroPython Garbage Collection ("gc"). This means that structs allocated for LVGL use don't need to be deallocated explicitly, gc takes care of that. For this to work correctly, LVGL is configured to use gc and to use MicroPython's memory allocation functions, and also register all LVGL "root" global variables to MicroPython's gc.
From the user's perspective, structs can be created and will be collected by gc when they are no longer referenced.
However, LVGL screen objects (lv.obj with no parent) are automatically assigned to default display, therefore not collected by gc even when no longer explicitly referenced.
When you want to free a screen and all its descendants so gc could collect their memory, make sure you call screen.delete() when you no longer need it.
Make sure you keep a reference to your display driver and input driver to prevent them from being collected.
This implementation of MicroPython Bindings to LVGL assumes that MicroPython and LVGL are running on a single thread and on the same thread (or alternatively, running without multithreading at all). No synchronization means (locks, mutexes) are taken. However, asynchronous calls to LVGL still take place periodically for screen refresh and other LVGL tasks such as animation.
This is achieved by using the internal MicroPython scheduler (that must be enabled), by calling mp_sched_schedule.
mp_sched_schedule is called when screen needs to be refreshed. LVGL expects the function lv_task_handler to be called periodically (see lvgl/README.md#porting). This is usually handled in the display device driver.
Here is an example of calling lv_task_handler with mp_sched_schedule for refreshing LVGL. mp_lv_task_handler is scheduled to run on the same thread MicroPython is running, and it calls both lv_task_handler for LVGL task handling and monitor_sdl_refr_core for refreshing the display and handling mouse events.
With REPL (interactive console), when waiting for the user input, asynchronous events can also happen. In this example we just call mp_handle_pending periodically when waiting for a keypress. mp_handle_pending takes care of dispatching asynchronous events registered with mp_sched_schedule.
The LVGL binding script parses LVGL headers and provides API to access LVGL classes (such as btn) and structs (such as color_t). All structs and classes are available under lvgl micropython module.
lvgl Class contains:
set_x)STATE of a btn)lvgl struct contains only attributes that can be read or written. For example:
c = lvgl.color_t()
c.ch.red = 0xffstructs can also be initialized from dict. For example, the example above can be written like this:
c = lvgl.color_t({'ch': {'red' : 0xff}})All lvgl globals (functions, enums, types) are available under lvgl module. For example, lvgl.SYMBOL is an "enum" of symbol strings, lvgl.anim_create will create animation etc.
In C a callback is a function pointer. In MicroPython we would also need to register a MicroPython callable object for each callback. Therefore in the MicroPython binding we need to register both a function pointer and a MicroPython object for every callback.
Therefore we defined a callback convention that expects lvgl headers to be defined in a certain way. Callbacks that are declared according to the convention would allow the binding to register a MicroPython object next to the function pointer when registering a callback, and access that object when the callback is called.
The MicroPython callable object is automatically saved in a user_data variable which is provided when registering or calling the callback.
The callback convention assumes the following:
void * user_data.Another option is that the callback function pointer is just a field of a struct, in that case we expect the same struct to contain user_data field as well.
Another option is:
void * user_data is provided to the registration function as the last argument.void * as the last argumentIn this case, the user should provide either None or a dict as the user_data argument of the registration function.
The callback will receive a Blob which can be casted to the dict in the last argument.
(See async_call example below)
As long as the convention above is followed, the lvgl MicroPython binding script would automatically set and use user_data when callbacks are set and used.
From the user perspective, any python callable object (such as python regular function, class function, lambda etc.) can be user as an lvgl callbacks. For example:
lvgl.anim_set_custom_exec_cb(anim, lambda anim, val, obj=obj: obj.set_y(val))In this example an exec callback is registered for an animation anim, which would animate the y coordinate of obj.
An lvgl API function can also be used as a callback directly, so the example above could also be written like this:
lv.anim_set_exec_cb(anim, obj, obj.set_y)lvgl callbacks that do not follow the Callback Convention cannot be used with micropython callable objects. A discussion related to adjusting lvgl callbacks to the convention: https://github.com/lvgl/lvgl/issues/1036
The user_data field must not be used directly by the user, since it is used internally to hold pointers to MicroPython objects.
LVGL can be configured to use different displays and different input devices. More information is available on LVGL documentation.
Registering a driver is essentially calling a registration function (for example disp_drv_register) and passing a function pointer as a parameter (actually a struct that contains function pointers). The function pointer is used to access the actual display / input device.
When implementing a display or input LVGL driver with MicroPython, there are 3 option:
flush function) are in C, and the non-critical part (such as initializing the display) are implemented in Python.An example of Pure/Hybrid driver is the ili9XXX.py.
The driver registration should eventually be performed in the MicroPython script, either in the driver code itself in case of the pure/hybrid driver or in user code in case of C driver (for example, in the case of the SDL driver). Registering the driver on Python and not in C is important to make it easy for the user to select and replace drivers without building the project and changing C files.
When creating a display or input LVGL driver, make sure you let the user configure all parameters on runtime, such as SPI pins, frequency, etc. Eventually the user would want to build the firmware once and use the same driver in different configuration without re-building the C project. This is different from standard LVGL C drivers where you usually use macros to configure parameters and require the user to re-build when any configurations changes.
Example:
# Initialize ILI9341 display
from ili9XXX import ili9341
self.disp = ili9341(dc=32, cs=33, power=-1, backlight=-1)
# Register xpt2046 touch driver
from xpt2046 import xpt2046
self.touch = xpt2046()Example:
# init
import lvgl as lv
lv.init()
from lv_utils import event_loop
WIDTH = 480
HEIGHT = 320
event_loop = event_loop()
disp_drv = lv.sdl_window_create(WIDTH, HEIGHT)
mouse = lv.sdl_mouse_create()
keyboard = lv.sdl_keyboard_create()
keyboard.set_group(self.group)In this example we use LVGL built in LVGL driver.
Currently supported drivers for Micropyton are
/dev/fb0)Driver code is under /driver directory.
Drivers can also be implemented in pure MicroPython, by providing callbacks (disp_drv.flush_cb, indev_drv.read_cb etc.)
Currently the supported ILI9341, FT6X36 and XPT2046 are pure micropython drivers.
LVGL C drivers and MicroPython drivers (either C or Python) are separate and independent from each other. The main reason is configuration:
暂无开放 Issues,或尚未同步最近议题。