mirror of
https://github.com/lvgl/lvgl.git
synced 2024-11-27 11:43:43 +08:00
docs(micropython): add coding conventions section(#3774)
Co-authored-by: Gabor Kiss-Vamosi <kisvegabor@gmail.com>
This commit is contained in:
parent
be1e1fca3a
commit
35e1402f1e
@ -18,7 +18,7 @@ Using Micropython, you can write Python3 code and run it even on a bare metal ar
|
||||
|
||||
## Why Micropython + LVGL?
|
||||
|
||||
Currently, Micropython [does not have a good high-level GUI library](https://forum.micropython.org/viewtopic.php?f=18&t=5543) by default. LVGL is an [Object-Oriented Component Based](https://blog.lvgl.io/2018-12-13/extend-lvgl-objects) high-level GUI library, which seems to be a natural candidate to map into a higher level language, such as Python. LVGL is implemented in C and its APIs are in C.
|
||||
Micropython [does not have a good native high-level GUI library](https://forum.micropython.org/viewtopic.php?f=18&t=5543). LVGL is an [Object-Oriented Component Based](https://blog.lvgl.io/2018-12-13/extend-lvgl-objects) high-level GUI library, which seems to be a natural candidate to map into a higher level language, such as Python. LVGL is implemented in C and its APIs are in C.
|
||||
|
||||
### Here are some advantages of using LVGL in Micropython:
|
||||
|
||||
@ -39,8 +39,7 @@ This goes well with [CircuitPython vision](https://learn.adafruit.com/welcome-to
|
||||
|
||||
## So what does it look like?
|
||||
|
||||
> TL;DR:
|
||||
> It's very much like the C API, but Object-Oriented for LVGL components.
|
||||
**TL;DR:** It's very much like the C API, but Object-Oriented for LVGL components.
|
||||
|
||||
Let's dive right into an example!
|
||||
|
||||
@ -51,9 +50,9 @@ import lvgl as lv
|
||||
lv.init()
|
||||
scr = lv.obj()
|
||||
btn = lv.btn(scr)
|
||||
btn.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
|
||||
btn.align(lv.ALIGN.CENTER, 0, 0)
|
||||
label = lv.label(btn)
|
||||
label.set_text("Button")
|
||||
label.set_text('Hello World!')
|
||||
lv.scr_load(scr)
|
||||
```
|
||||
|
||||
@ -66,9 +65,9 @@ It's a fully functional LVGL + Micropython that runs entirely in the browser and
|
||||
|
||||
[Click here to experiment on the online simulator](https://sim.lvgl.io/)
|
||||
|
||||
[Hello World](https://sim.lvgl.io/v7/micropython/ports/javascript/bundle_out/index.html?script=https://gist.githubusercontent.com/amirgon/51299ce9b6448328a855826149482ae6/raw/0f235c6d40462fd2f0e55364b874f14fe3fd613c/lvgl_hello_world.py&script_startup=https://gist.githubusercontent.com/amirgon/7bf15a66ba6d959bbf90d10f3da571be/raw/8684b5fa55318c184b1310663b187aaab5c65be6/init_lv_mp_js.py)
|
||||
|
||||
Note: the online simulator is available for lvgl v6 and v7.
|
||||
Many [LVGL examples](https://docs.lvgl.io/master/examples.html) are available also for Micropython.
|
||||
Just click the
|
||||
<img src="https://user-images.githubusercontent.com/11742638/198729010-35a12e49-4945-414a-8c3e-d32bc95da940.png" align=middle /> link!
|
||||
|
||||
### PC Simulator
|
||||
|
||||
@ -76,17 +75,119 @@ Micropython is ported to many platforms. One notable port is "unix", which allow
|
||||
|
||||
[Click here to know more information about building and running the unix port](https://github.com/lvgl/lv_micropython)
|
||||
|
||||
### Embedded platform
|
||||
### Embedded Platforms
|
||||
|
||||
In the end, the goal is to run it all on an embedded platform.
|
||||
Both Micropython and LVGL can be used on many embedded architectures, such as stm32, ESP32 etc.
|
||||
You would also need display and input drivers. We have some sample drivers (ESP32+ILI9341, as well as some other examples), but chances are you would want to create your own input/display drivers for your specific hardware.
|
||||
Drivers can be implemented either in C as a Micropython module, or in pure Micropython!
|
||||
Both Micropython and LVGL can be used on many embedded architectures. [lv_micropython](https://github.com/lvgl/lv_micropython) is a fork of Micropython+LVGL and currently supports Linux, ESP32, STM32 and RP2. It can be ported to any other platform supported by Micropython.
|
||||
|
||||
You would also need display and input drivers. You can either use one of the existing drivers provided with lv_micropython, or you can create your own input/display drivers for your specific hardware.
|
||||
Drivers can be implemented either in C as a Micropython module, or in pure Python!
|
||||
|
||||
lv_micropython already contains these drivers:
|
||||
- Display drivers:
|
||||
- SDL on Linux
|
||||
- ESP32 specific: ILI9341, ILI9488, GC9A01, ST7789, ST7735
|
||||
- Generic (pure Python): ILI9341, ST7789, ST7735
|
||||
- Input drivers:
|
||||
- SDL, XPT2046, FT6X36, ESP32 ADC with resistive touch
|
||||
|
||||
## Where can I find more information?
|
||||
|
||||
- In this [Blog Post](https://blog.lvgl.io/2019-02-20/micropython-bindings)
|
||||
- `lv_micropython` [README](https://github.com/lvgl/lv_micropython)
|
||||
- `lv_binding_micropython` [README](https://github.com/lvgl/lv_binding_micropython)
|
||||
- The [LVGL micropython forum](https://forum.lvgl.io/c/micropython) (Feel free to ask anything!)
|
||||
- At Micropython: [docs](http://docs.micropython.org/en/latest/) and [forum](https://forum.micropython.org/)
|
||||
- [Blog Post](https://blog.lvgl.io/2019-02-20/micropython-bindings), a little outdated.
|
||||
|
||||
# The Micropython Binding is auto generated!
|
||||
|
||||
LVGL is a git submodule inside [lv_micropython](https://github.com/lvgl/lv_micropython) (LVGL is a git submodule of [lv_binding_micropython](https://github.com/lvgl/lv_binding_micropython) which is itself a submodule of [lv_micropython](https://github.com/lvgl/lv_micropython)).
|
||||
When building lv_micropython, the public LVGL C API is scanned and Micropython API is auto-generated. That means that lv_micropython provides LVGL API for **any** LVGL version, and generally does not require code changes as LVGL evolves.
|
||||
|
||||
## LVGL C API Coding Conventions
|
||||
|
||||
To support the auto-generation of the Python API, the LVGL C API must follow some coding conventions:
|
||||
|
||||
- Use `enum`s instead of macros. If inevitable to use `define`s export them with `LV_EXPORT_CONST_INT(defined_value)` right after the `define`.
|
||||
- In function arguments use `type name[]` declaration for array parameters instead of `type * name`
|
||||
- Use typed pointers instead of `void *` pointers
|
||||
- Widget constructor must follow the `lv_<widget_name>_create(lv_obj_t * parent)` pattern.
|
||||
- Widget members function must start with `lv_<modul_name>` and should receive `lv_obj_t *` as first argument which is a pointer to widget object itself.
|
||||
- `struct` APIs should follow the widgets' conventions. That is to receive a pointer to the `struct` as the first argument, and the prefix of the `struct` name should be used as the prefix of the function name too (e.g. `lv_disp_set_default(lv_disp_t * disp)`)
|
||||
- Functions and `struct`s which are not part of the public API must begin with underscore in order to mark them as "private".
|
||||
- Argument must be named in H files too.
|
||||
- Do not `malloc` into a static or global variables. Instead declare the variable in `LV_ITERATE_ROOTS` list in `lv_gc.h` and mark the variable with `GC_ROOT(variable)` when it's used.
|
||||
**See [Memory Management](#memory-management)**
|
||||
- To register and use callbacks one of the followings needs to be followed. **See [Callbacks](#callbacks)**
|
||||
- Pass a pointer to a `struct` as the first argument of both the registration function and the callback. That `struct` must contain `void * user_data` field.
|
||||
- The last argument of the registration function must be `void * user_data` and the same `user_data` needs to be passed as the last argument of the callback.
|
||||
|
||||
Most of these rules are simple and straightforward but there are two related concepts that worth a deeper look: **Memory Management** and **Callbacks**.
|
||||
|
||||
## Memory Management
|
||||
|
||||
When LVGL runs in Micropython, all dynamic memory allocations (`lv_malloc`) are handled by Micropython's memory manager which is [garbage-collected](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) (GC).
|
||||
To prevent GC from collecting memory prematurely, all dynamic allocated RAM must be reachable by GC.
|
||||
GC is aware of most allocations, except from pointers on the [Data Segment](https://en.wikipedia.org/wiki/Data_segment):
|
||||
- Pointers which are global variables
|
||||
- Pointers which are static global variables
|
||||
- Pointers which are static local variables
|
||||
|
||||
Such pointers need to be defined in a special way to make them reachable by GC
|
||||
|
||||
### Identify The Problem
|
||||
|
||||
Problem happens when an allocated memory's pointer (return value of `lv_malloc`) is stored only in either **global**, **static global** or **static local** pointer variable and not as part of a previously allocated `struct` or other variable.
|
||||
|
||||
### Solve The Problem
|
||||
|
||||
- Replace the global/static local var with `LV_GC_ROOT(_var)`
|
||||
- Include `lv_gc.h` on files that use `LV_GC_ROOT`
|
||||
- Add `_var` to `LV_ITERATE_ROOTS` on `lv_gc.h`
|
||||
|
||||
### Example
|
||||
|
||||
https://github.com/lvgl/lvgl/commit/adced46eccfa0437f84aa51aedca4895cc3c679c
|
||||
|
||||
### More Information
|
||||
|
||||
- [In the README](https://github.com/lvgl/lv_binding_micropython#memory-management)
|
||||
- [In the Blog](https://blog.lvgl.io/2019-02-20/micropython-bindings#i-need-to-allocate-a-littlevgl-struct-such-as-style-color-etc-how-can-i-do-that-how-do-i-allocatedeallocate-memory-for-it)
|
||||
|
||||
## Callbacks
|
||||
|
||||
In C a callback is just a function pointer.
|
||||
But in Micropython we 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** for the LVGL C API 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 basic idea is that we have `void * user_data` field that is used automatically by the Micropython Binding to save the *Micropython callable object* for a callback. This field must be provided when registering the function pointer, and provided to the callback function itself.
|
||||
Although called "user_data", the user is not expectd to read/write that field. Instead, the Micropython glue code uses `user_data` to automatically keep track of the Micropython callable object. The glue code updates it when the callback is registered, and uses it when the callback is called in order to invoke a call to the original callable object.
|
||||
|
||||
There are a few options for defining a callback in LVGL C API:
|
||||
- Option 1: `user_data` in a struct
|
||||
- There's a struct that contains a field called `void * user_data`
|
||||
- A pointer to that struct is provided as the **first** argument of a callback registration function
|
||||
- A pointer to that struct is provided as the **first** argument of the callback itself
|
||||
- Option 2: `user_data` as a function argument
|
||||
- A parameter called `void * user_data` is provided to the registration function as the **last** argument
|
||||
- The callback itself recieves `void *` as the **last** argument
|
||||
- Option 3: both callback and `user_data` are struct fields
|
||||
- The API exposes a struct with both function pointer member and `user_data` member
|
||||
- The function pointer member receives the same struct as its **first** argument
|
||||
|
||||
In practice it's also possible to mix these options, for example provide a struct pointer when registering a callback (option 1) and provide `user_data` argument when calling the callback (options 2), **as long as the same `user_data` that was registered is passed to the callback when it's called**.
|
||||
|
||||
### Examples
|
||||
|
||||
- [lv_anim_t](https://github.com/lvgl/lvgl/blob/5d50fbc066938d1a4eb43a8366cf83fbd4ce29f2/src/misc/lv_anim.h#L73-L100) contains `user_data` field.
|
||||
[lv_anim_set_path_cb](https://github.com/lvgl/lvgl/blob/5d50fbc066938d1a4eb43a8366cf83fbd4ce29f2/src/misc/lv_anim.h#L197) registers `path_cb` callback. Both `lv_anim_set_path_cb` and [`lv_anim_path_cb_t`](https://github.com/lvgl/lvgl/blob/5d50fbc066938d1a4eb43a8366cf83fbd4ce29f2/src/misc/lv_anim.h#L46) recieve `lv_anim_t` as their first argument
|
||||
- [`path_cb` field](https://github.com/lvgl/lvgl/blob/5d50fbc066938d1a4eb43a8366cf83fbd4ce29f2/src/misc/lv_anim.h#L83) can also be assigned directly in the Python code because it's a member of `lv_anim_t` which contains `user_data` field, and [`lv_anim_path_cb_t`](https://github.com/lvgl/lvgl/blob/5d50fbc066938d1a4eb43a8366cf83fbd4ce29f2/src/misc/lv_anim.h#L46) recieve `lv_anim_t` as its first argument.
|
||||
- [`lv_imgfont_create`](https://github.com/lvgl/lvgl/blob/5d50fbc066938d1a4eb43a8366cf83fbd4ce29f2/src/others/imgfont/lv_imgfont.h#L43) registers `path_cb` and recieves `user_data` as the last argument. The callback [`lv_imgfont_get_path_cb_t`](https://github.com/lvgl/lvgl/blob/5d50fbc066938d1a4eb43a8366cf83fbd4ce29f2/src/others/imgfont/lv_imgfont.h#L29-L31) also receieves the `user_data` as the last argument.
|
||||
|
||||
### More Information
|
||||
|
||||
- In the [Blog](https://blog.lvgl.io/2019-08-05/micropython-pure-display-driver#using-callbacks) and in the [README](https://github.com/lvgl/lv_binding_micropython#callbacks)
|
||||
- [[v6.0] Callback conventions #1036](https://github.com/lvgl/lvgl/issues/1036)
|
||||
- Various discussions: [here](https://github.com/lvgl/lvgl/pull/3294#issuecomment-1184895335) and [here](https://github.com/lvgl/lvgl/issues/1763#issuecomment-762247629) and [here](https://github.com/lvgl/lvgl/issues/316#issuecomment-467221587)
|
||||
|
Loading…
Reference in New Issue
Block a user