Update LVGL to v9.1.0

This commit is contained in:
jacqueline
2024-06-12 17:54:40 +10:00
parent 611176ed66
commit 64bd9053a2
2244 changed files with 278442 additions and 118864 deletions
-243
View File
@@ -1,243 +0,0 @@
# Display interface
To register a display for LVGL, a `lv_disp_draw_buf_t` and a `lv_disp_drv_t` variable have to be initialized.
- `lv_disp_draw_buf_t` contains internal graphic buffer(s) called draw buffer(s).
- `lv_disp_drv_t` contains callback functions to interact with the display and manipulate low level drawing behavior.
## Draw buffer
Draw buffer(s) are simple array(s) that LVGL uses to render the screen content.
Once rendering is ready the content of the draw buffer is sent to the display using the `flush_cb` function set in the display driver (see below).
A draw buffer can be initialized via a `lv_disp_draw_buf_t` variable like this:
```c
/*A static or global variable to store the buffers*/
static lv_disp_draw_buf_t disp_buf;
/*Static or global buffer(s). The second buffer is optional*/
static lv_color_t buf_1[MY_DISP_HOR_RES * 10];
static lv_color_t buf_2[MY_DISP_HOR_RES * 10];
/*Initialize `disp_buf` with the buffer(s). With only one buffer use NULL instead buf_2 */
lv_disp_draw_buf_init(&disp_buf, buf_1, buf_2, MY_DISP_HOR_RES*10);
```
Note that `lv_disp_draw_buf_t` must be a static, global or dynamically allocated variable. It cannot be a local variable as they are destroyed upon end of scope.
As you can see above, the draw buffer may be smaller than the screen. In this case, larger areas are redrawn in smaller segments that fit into the draw buffer(s).
If only a small area changes (e.g. a button is pressed) then only that area will be refreshed.
A larger buffer results in better performance but above 1/10 screen sized buffer(s) there is no significant performance improvement.
Therefore it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized.
## Buffering modes
There are several settings to adjust the number draw buffers and buffering/refreshing modes.
You can measure the performance of different configurations using the [benchmark example](https://github.com/lvgl/lvgl/tree/master/demos/benchmark).
### One buffer
If only one buffer is used LVGL draws the content of the screen into that draw buffer and sends it to the display.
LVGL then needs to wait until the content of the buffer is sent to the display before drawing something new in it.
### Two buffers
If two buffers are used LVGL can draw into one buffer while the content of the other buffer is sent to the display in the background.
DMA or other hardware should be used to transfer data to the display so the MCU can continue drawing.
This way, the rendering and refreshing of the display become parallel operations.
### Full refresh
In the display driver (`lv_disp_drv_t`) enabling the `full_refresh` bit will force LVGL to always redraw the whole screen. This works in both *one buffer* and *two buffers* modes.
If `full_refresh` is enabled and two screen sized draw buffers are provided, LVGL's display handling works like "traditional" double buffering.
This means the `flush_cb` callback only has to update the address of the framebuffer (`color_p` parameter).
This configuration should be used if the MCU has an LCD controller peripheral and not with an external display controller (e.g. ILI9341 or SSD1963) accessed via serial link. The latter will generally be too slow to maintain high frame rates with full screen redraws.
### Direct mode
If the `direct_mode` flag is enabled in the display driver LVGL will draw directly into a **screen sized frame buffer**. That is the draw buffer(s) needs to be screen sized.
It this case `flush_cb` will be called only once when all dirty areas are redrawn.
With `direct_mode` the frame buffer always contains the current frame as it should be displayed on the screen.
If 2 frame buffers are provided as draw buffers LVGL will alter the buffers but always draw only the dirty areas.
Therefore the 2 buffers needs to synchronized in `flush_cb` like this:
1. Display the frame buffer pointed by `color_p`
2. Copy the redrawn areas from `color_p` to the other buffer.
The get the redrawn areas to copy use the following functions
`_lv_refr_get_disp_refreshing()` returns the display being refreshed
`disp->inv_areas[LV_INV_BUF_SIZE]` contains the invalidated areas
`disp->inv_area_joined[LV_INV_BUF_SIZE]` if 1 that area was joined into another one and should be ignored
`disp->inv_p` number of valid elements in `inv_areas`
## Display driver
Once the buffer initialization is ready a `lv_disp_drv_t` display driver needs to be:
1. initialized with `lv_disp_drv_init(&disp_drv)`
2. its fields need to be set
3. it needs to be registered in LVGL with `lv_disp_drv_register(&disp_drv)`
Note that `lv_disp_drv_t` also needs to be a static, global or dynamically allocated variable.
### Mandatory fields
In the most simple case only the following fields of `lv_disp_drv_t` need to be set:
- `draw_buf` pointer to an initialized `lv_disp_draw_buf_t` variable.
- `hor_res` horizontal resolution of the display in pixels.
- `ver_res` vertical resolution of the display in pixels.
- `flush_cb` a callback function to copy a buffer's content to a specific area of the display.
`lv_disp_flush_ready(&disp_drv)` needs to be called when flushing is ready.
LVGL might render the screen in multiple chunks and therefore call `flush_cb` multiple times. To see if the current one is the last chunk of rendering use `lv_disp_flush_is_last(&disp_drv)`.
### Optional fields
There are some optional display driver data fields:
- `physical_hor_res` horizontal resolution of the full / physical display in pixels. Only set this when _not_ using the full screen (defaults to -1 / same as `hor_res`).
- `physical_ver_res` vertical resolution of the full / physical display in pixels. Only set this when _not_ using the full screen (defaults to -1 / same as `ver_res`).
- `offset_x` horizontal offset from the full / physical display in pixels. Only set this when _not_ using the full screen (defaults to 0).
- `offset_y` vertical offset from the full / physical display in pixels. Only set this when _not_ using the full screen (defaults to 0).
- `color_chroma_key` A color which will be drawn as transparent on chrome keyed images. Set to `LV_COLOR_CHROMA_KEY` from `lv_conf.h` by default.
- `anti_aliasing` use anti-aliasing (edge smoothing). Enabled by default if `LV_COLOR_DEPTH` is set to at least 16 in `lv_conf.h`.
- `rotated` and `sw_rotate` See the [Rotation](#rotation) section below.
- `screen_transp` if `1` the screen itself can have transparency as well. `LV_COLOR_SCREEN_TRANSP` must be enabled in `lv_conf.h` and `LV_COLOR_DEPTH` must be 32.
- `user_data` A custom `void` user data for the driver.
- `full_refresh` always redrawn the whole screen (see above)
- `direct_mode` draw directly into the frame buffer (see above)
Some other optional callbacks to make it easier and more optimal to work with monochrome, grayscale or other non-standard RGB displays:
- `rounder_cb` Round the coordinates of areas to redraw. E.g. a 2x2 px can be converted to 2x8.
It can be used if the display controller can refresh only areas with specific height or width (usually 8 px height with monochrome displays).
- `set_px_cb` a custom function to write the draw buffer. It can be used to store the pixels more compactly in the draw buffer if the display has a special color format. (e.g. 1-bit monochrome, 2-bit grayscale etc.)
This way the buffers used in `lv_disp_draw_buf_t` can be smaller to hold only the required number of bits for the given area size. Note that rendering with `set_px_cb` is slower than normal rendering.
- `monitor_cb` A callback function that tells how many pixels were refreshed and in how much time. Called when the last chunk is rendered and sent to the display.
- `clean_dcache_cb` A callback for cleaning any caches related to the display.
- `render_start_cb` A callback function that notifies the display driver that rendering has started. It also could be used to wait for VSYNC to start rendering. It's useful if rendering is faster than a VSYNC period.
LVGL has built-in support to several GPUs (see `lv_conf.h`) but if something else is required these functions can be used to make LVGL use a GPU:
- `gpu_fill_cb` fill an area in the memory with a color.
- `gpu_wait_cb` if any GPU function returns while the GPU is still working, LVGL will use this function when required to make sure GPU rendering is ready.
### Examples
All together it looks like this:
```c
static lv_disp_drv_t disp_drv; /*A variable to hold the drivers. Must be static or global.*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
disp_drv.draw_buf = &disp_buf; /*Set an initialized buffer*/
disp_drv.flush_cb = my_flush_cb; /*Set a flush callback to draw to the display*/
disp_drv.hor_res = 320; /*Set the horizontal resolution in pixels*/
disp_drv.ver_res = 240; /*Set the vertical resolution in pixels*/
lv_disp_t * disp;
disp = lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/
```
Here are some simple examples of the callbacks:
```c
void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one
*`put_px` is just an example, it needs to implemented by you.*/
int32_t x, y;
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
put_px(x, y, *color_p);
color_p++;
}
}
/* IMPORTANT!!!
* Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
void my_gpu_fill_cb(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, const lv_area_t * dest_area, const lv_area_t * fill_area, lv_color_t color);
{
/*It's an example code which should be done by your GPU*/
uint32_t x, y;
dest_buf += dest_width * fill_area->y1; /*Go to the first line*/
for(y = fill_area->y1; y < fill_area->y2; y++) {
for(x = fill_area->x1; x < fill_area->x2; x++) {
dest_buf[x] = color;
}
dest_buf+=dest_width; /*Go to the next line*/
}
}
void my_rounder_cb(lv_disp_drv_t * disp_drv, lv_area_t * area)
{
/* Update the areas as needed.
* For example it makes the area to start only on 8th rows and have Nx8 pixel height.*/
area->y1 = area->y1 & 0x07;
area->y2 = (area->y2 & 0x07) + 8;
}
void my_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa)
{
/* Write to the buffer as required for the display.
* For example it writes only 1-bit for monochrome displays mapped vertically.*/
buf += buf_w * (y >> 3) + x;
if(lv_color_brightness(color) > 128) (*buf) |= (1 << (y % 8));
else (*buf) &= ~(1 << (y % 8));
}
void my_monitor_cb(lv_disp_drv_t * disp_drv, uint32_t time, uint32_t px)
{
printf("%d px refreshed in %d ms\n", time, ms);
}
void my_clean_dcache_cb(lv_disp_drv_t * disp_drv, uint32)
{
/* Example for Cortex-M (CMSIS) */
SCB_CleanInvalidateDCache();
}
```
## Other options
### Rotation
LVGL supports rotation of the display in 90 degree increments. You can select whether you'd like software rotation or hardware rotation.
If you select software rotation (`sw_rotate` flag set to 1), LVGL will perform the rotation for you. Your driver can and should assume that the screen width and height have not changed. Simply flush pixels to the display as normal. Software rotation requires no additional logic in your `flush_cb` callback.
There is a noticeable amount of overhead to performing rotation in software. Hardware rotation is available to avoid unwanted slowdowns. In this mode, LVGL draws into the buffer as if your screen width and height were swapped. You are responsible for rotating the provided pixels yourself.
The default rotation of your display when it is initialized can be set using the `rotated` flag. The available options are `LV_DISP_ROT_NONE`, `LV_DISP_ROT_90`, `LV_DISP_ROT_180`, or `LV_DISP_ROT_270`. The rotation values are relative to how you would rotate the physical display in the clockwise direction. Thus, `LV_DISP_ROT_90` means you rotate the hardware 90 degrees clockwise, and the display rotates 90 degrees counterclockwise to compensate.
(Note for users upgrading from 7.10.0 and older: these new rotation enum values match up with the old 0/1 system for rotating 90 degrees, so legacy code should continue to work as expected. Software rotation is also disabled by default for compatibility.)
Display rotation can also be changed at runtime using the `lv_disp_set_rotation(disp, rot)` API.
Support for software rotation is a new feature, so there may be some glitches/bugs depending on your configuration. If you encounter a problem please open an issue on [GitHub](https://github.com/lvgl/lvgl/issues).
### Decoupling the display refresh timer
Normally the dirty (a.k.a invalid) areas are checked and redrawn in every `LV_DISP_DEF_REFR_PERIOD` milliseconds (set in `lv_conf.h`).
However, in some cases you might need more control on when the display refreshing happen, for example to synchronize rendering with VSYNC or the TE signal.
You can do this in the following way:
```c
/*Delete the original display refresh timer*/
lv_timer_del(disp->refr_timer);
disp->refr_timer = NULL;
/*Call this anywhere you want to refresh the dirty areas*/
_lv_disp_refr_timer(NULL);
```
If you have multiple displays call `lv_disp_set_deafult(disp1);` to select the display to refresh before `_lv_disp_refr_timer(NULL);`.
Note that `lv_timer_handler()` and `_lv_disp_refr_timer()` can not run at the same time.
If the performance monitor is enabled, the value of `LV_DISP_DEF_REFR_PERIOD` needs to be set to be consistent with the refresh period of the display to ensure that the statistical results are correct.
## Further reading
- [lv_port_disp_template.c](https://github.com/lvgl/lvgl/blob/master/examples/porting/lv_port_disp_template.c) for a template for your own driver.
- [Drawing](/overview/drawing) to learn more about how rendering works in LVGL.
- [Display features](/overview/display) to learn more about higher level display features.
## API
```eval_rst
.. doxygenfile:: lv_hal_disp.h
:project: lvgl
```
+277
View File
@@ -0,0 +1,277 @@
.. _display_interface:
=================
Display interface
=================
To create a display for LVGL call
:cpp:expr:`lv_display_t * display = lv_display_create(hor_res, ver_res)`. You can create
a multiple displays and a different driver for each (see below),
Basic setup
***********
Draw buffer(s) are simple array(s) that LVGL uses to render the screen's
content. Once rendering is ready the content of the draw buffer is sent
to the display using the ``flush_cb`` function.
flush_cb
--------
An example ``flush_cb`` looks like this:
.. code:: c
void my_flush_cb(lv_display_t * display, const lv_area_t * area, void * px_map)
{
/*The most simple case (but also the slowest) to put all pixels to the screen one-by-one
*`put_px` is just an example, it needs to be implemented by you.*/
uint16_t * buf16 = (uint16_t *)px_map; /*Let's say it's a 16 bit (RGB565) display*/
int32_t x, y;
for(y = area->y1; y <= area->y2; y++) {
for(x = area->x1; x <= area->x2; x++) {
put_px(x, y, *buf16);
buf16++;
}
}
/* IMPORTANT!!!
* Inform LVGL that you are ready with the flushing and buf is not used anymore*/
lv_display_flush_ready(disp);
}
Use :cpp:expr:`lv_display_set_flush_cb(disp, my_flush_cb)` to set a new ``flush_cb``.
:cpp:expr:`lv_display_flush_ready(disp)` needs to be called when flushing is ready
to inform LVGL the buffer is not used anymore by the driver and it can
render new content into it.
LVGL might render the screen in multiple chunks and therefore call
``flush_cb`` multiple times. To see if the current one is the last chunk
of rendering use :cpp:expr:`lv_display_flush_is_last(display)`.
Draw buffers
------------
The draw buffers can be set with
:cpp:expr:`lv_display_set_buffers(display, buf1, buf2, buf_size_byte, render_mode)`
- ``buf1`` a buffer where LVGL can render
- ``buf2`` a second optional buffer (see more details below)
- ``buf_size_byte`` size of the buffer(s) in bytes
- ``render_mode``
- :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` Use the buffer(s) to render the
screen in smaller parts. This way the buffers can be smaller then
the display to save RAM. At least 1/10 screen size buffer(s) are
recommended. In ``flush_cb`` the rendered images needs to be
copied to the given area of the display. In this mode if a button is pressed
only the button's area will be redrawn.
- :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` The buffer(s) has to be screen
sized and LVGL will render into the correct location of the
buffer. This way the buffer always contain the whole image. If two
buffer are used the rendered areas are automatically copied to the
other buffer after flushing. Due to this in ``flush_cb`` typically
only a frame buffer address needs to be changed. If a button is pressed
only the button's area will be redrawn.
- :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_FULL` The buffer(s) has to be screen
sized and LVGL will always redraw the whole screen even if only 1
pixel has been changed. If two screen sized draw buffers are
provided, LVGL's display handling works like "traditional" double
buffering. This means the ``flush_cb`` callback only has to update
the address of the frame buffer to the ``px_map`` parameter.
Example:
.. code:: c
static uint16_t buf[LCD_HOR_RES * LCD_VER_RES / 10];
lv_display_set_buffers(disp, buf, NULL, sizeof(buf), LV_DISPLAY_RENDER_MODE_PARTIAL);
One buffer
^^^^^^^^^^
If only one buffer is used LVGL draws the content of the screen into
that draw buffer and sends it to the display via the ``flush_cb``. LVGL
then needs to wait until :cpp:expr:`lv_display_flush_ready` is called
(that is the content of the buffer is sent to the
display) before drawing something new into it.
Two buffers
^^^^^^^^^^^
If two buffers are used LVGL can draw into one buffer while the content
of the other buffer is sent to the display in the background. DMA or
other hardware should be used to transfer data to the display so the MCU
can continue drawing. This way, the rendering and refreshing of the
display become parallel operations.
Advanced options
****************
Resolution
----------
To set the resolution of the display after creation use
:cpp:expr:`lv_display_set_resolution(display, hor_res, ver_res)`
It's not mandatory to use the whole display for LVGL, however in some
cases the physical resolution is important. For example the touchpad
still sees the whole resolution and the values needs to be converted to
the active LVGL display area. So the physical resolution and the offset
of the active area can be set with
:cpp:expr:`lv_display_set_physical_resolution(disp, hor_res, ver_res)` and
:cpp:expr:`lv_display_set_offset(disp, x, y)`
Flush wait callback
-------------------
By using :cpp:expr:`lv_display_flush_ready` LVGL will spin in a loop
while waiting for flushing.
However with the help of :cpp:expr:`lv_display_set_flush_wait_cb` a custom
wait callback be set for flushing. This callback can use a semaphore, mutex,
or anything else to optimize while the waiting for flush.
If ``flush_wait_cb`` is not set, LVGL assume that `lv_display_flush_ready`
is used.
Rotation
--------
LVGL supports rotation of the display in 90 degree increments. You can
select whether you would like software rotation or hardware rotation.
The orientation of the display can be changed with
``lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_0/90/180/270)``.
LVGL will swap the horizontal and vertical resolutions internally
according to the set degree. When changing the rotation
:cpp:expr:`LV_EVENT_SIZE_CHANGED` is sent to the display to allow
reconfiguring the hardware. In lack of hardware display rotation support
:cpp:expr:`lv_draw_sw_rotate` can be used to rotate the buffer in the
``flush_cb``.
Color format
------------
The default color format of the display is set according to :c:macro:`LV_COLOR_DEPTH`
(see ``lv_conf.h``)
- :c:macro:`LV_COLOR_DEPTH` ``32``: XRGB8888 (4 bytes/pixel)
- :c:macro:`LV_COLOR_DEPTH` ``24``: RGB888 (3 bytes/pixel)
- :c:macro:`LV_COLOR_DEPTH` ``16``: RGB565 (2 bytes/pixel)
- :c:macro:`LV_COLOR_DEPTH` ``8``: L8 (1 bytes/pixel) Not supported yet
The ``color_format`` can be changed with
:cpp:expr:`lv_display_set_color_depth(display, LV_COLOR_FORMAT_...)`.
Besides the default value :c:macro:`LV_COLOR_FORMAT_ARGB8888` can be
used as a well.
It's very important that draw buffer(s) should be large enough for any
selected color format.
Swap endianness
--------------
In case of RGB565 color format it might be required to swap the 2 bytes
because the SPI, I2C or 8 bit parallel port periphery sends them in the wrong order.
The ideal solution is configure the hardware to handle the 16 bit data with different byte order,
however if it's not possible :cpp:expr:`lv_draw_sw_rgb565_swap(buf, buf_size_in_px)`
can be called in the ``flush_cb`` to swap the bytes.
If you wish you can also write your own function, or use assembly instructions for
the fastest possible byte swapping.
Note that this is not about swapping the Red and Blue channel but converting
``RRRRR GGG | GGG BBBBB``
to
``GGG BBBBB | RRRRR GGG``.
User data
---------
With :cpp:expr:`lv_display_set_user_data(disp, p)` a pointer to a custom data can
be stored in display object.
Decoupling the display refresh timer
------------------------------------
Normally the dirty (a.k.a invalid) areas are checked and redrawn in
every :c:macro:`LV_DEF_REFR_PERIOD` milliseconds (set in ``lv_conf.h``).
However, in some cases you might need more control on when the display
refreshing happen, for example to synchronize rendering with VSYNC or
the TE signal.
You can do this in the following way:
.. code:: c
/*Delete the original display refresh timer*/
lv_display_delete_refr_timer(disp);
/*Call this anywhere you want to refresh the dirty areas*/
_lv_display_refr_timer(NULL);
If you have multiple displays call :cpp:expr:`lv_display_set_default(disp1)` to
select the display to refresh before :cpp:expr:`_lv_display_refr_timer(NULL)`.
.. note:: that :cpp:func:`lv_timer_handler` and :cpp:func:`_lv_display_refr_timer` can not run at the same time.
If the performance monitor is enabled, the value of :c:macro:`LV_DEF_REFR_PERIOD` needs to be set to be
consistent with the refresh period of the display to ensure that the statistical results are correct.
Force refreshing
----------------
Normally the invalidated areas (marked for redraw) are rendered in :cpp:func:`lv_timer_handler` in every
:cpp:macro:`LV_DEF_REFR_PERIOD`milliseconds. However, by using :cpp:func:`lv_refr_now(display)` you can ask LVGL to
redraw the invalid areas immediately. The refreshing will happen in :cpp:func:`lv_refr_now` which might take
longer time.
The parameter of :cpp:func:`lv_refr_now` is a display to refresh. If ``NULL`` is set the default display will be updated.
Events
******
:cpp:expr:`lv_display_add_event_cb(disp, event_cb, LV_EVENT_..., user_data)` adds
an event handler to a display. The following events are sent:
- :cpp:enumerator:`LV_EVENT_INVALIDATE_AREA` An area is invalidated (marked for redraw).
:cpp:expr:`lv_event_get_param(e)` returns a pointer to an :cpp:struct:`lv_area_t`
variable with the coordinates of the area to be invalidated. The area can
be freely modified if needed to adopt it the special requirement of the
display. Usually needed with monochrome displays to invalidate ``N x 8``
rows or columns at once.
- :cpp:enumerator:`LV_EVENT_REFR_REQUEST`: Sent when something happened that requires redraw.
- :cpp:enumerator:`LV_EVENT_REFR_START`: Sent when a refreshing cycle starts. Sent even if there is nothing to redraw.
- :cpp:enumerator:`LV_EVENT_REFR_READY`: Sent when refreshing is ready (after rendering and calling the ``flush_cb``). Sent even if no redraw happened.
- :cpp:enumerator:`LV_EVENT_RENDER_START`: Sent when rendering starts.
- :cpp:enumerator:`LV_EVENT_RENDER_READY`: Sent when rendering is ready (before calling the ``flush_cb``)
- :cpp:enumerator:`LV_EVENT_FLUSH_START`: Sent before the ``flush_cb`` is called.
- :cpp:enumerator:`LV_EVENT_FLUSH_READY`: Sent when the ``flush_cb`` returned.
- :cpp:enumerator:`LV_EVENT_RESOLUTION_CHANGED`: Sent when the resolution changes due
to :cpp:func:`lv_display_set_resolution` or :cpp:func:`lv_display_set_rotation`.
Further reading
***************
- `lv_port_disp_template.c <https://github.com/lvgl/lvgl/blob/master/examples/porting/lv_port_disp_template.c>`__
for a template for your own driver.
- :ref:`Drawing <drawing>` to learn more about how rendering
works in LVGL.
- :ref:`display_features` to learn more about higher
level display features.
API
***
+77
View File
@@ -0,0 +1,77 @@
.. _porting_draw:
==========
Custom GPU
==========
LVGL has a flexible and extendable draw pipeline. You can hook it to do
some rendering with a GPU or even completely replace the built-in
software renderer.
Overview
********
Draw task
---------
When :cpp:expr:`lv_draw_rect`, :cpp:expr:`lv_draw_label` or similar functions are called
LVGL creates a so called draw task.
Draw unit
---------
The draw tasks are collected in a list and periodically dispatched to draw units. A
draw unit can a CPU core, a GPU, just a new rendering library for certain or all draw tasks,
or basically anything that can draw somehow.
Draw task evaluation
--------------------
Different draw units might render slight different output for example for an image transformation or
a gradient. If such a draw task were assigned to a different draw units, the screen might jitter a
little bit. To resolve it each draw unit has an ``evaluate_cb`` which is called when a draw task is created.
Based on the type and parameters of the draw task each draw unit can decide if it want to assign the
draw task to itself. This way a certain type of draw task (e.g. rounded rectangle with horizontal
gradient) will be always assigned to the same draw unit. It avoid the above mentioned issue of
slight difference between draw units.
Dispatching
-----------
While collecting draw tasks LVGL frequently tries to dispatch the collected draw tasks to the draw units.
This handles via the ``dispatch_cb`` of the draw units.
If a draw unit is busy with an other draw task, it just returns. However, it is available it can take a draw task.
:cpp:expr:`lv_draw_get_next_available_task(layer, previous_task, draw_unit_id)` is a useful helper function which
returns an available draw task. "Available draw task" means that, all the draw tasks which should be drawn under a draw task
are ready and it is assigned to the given draw unit.
Layers
------
A layer is a buffer with a given area on which rendering happens. Each display has a "main" layer, but
during rendering additional layers might be created internally to handle for example arbitrary widget transformations.
Hierarchy of modules
--------------------
All these together looks like this
- list of draw units
- display(s)
- layer(s): Each display has its own list of layers
- draw tasks: Each layer has its own list of draw tasks
References
**********
As a reference take a look at `lv_draw_sw.c <https://github.com/lvgl/lvgl/blob/master/src/draw/sw/lv_draw_sw.c>`__
API
***
-198
View File
@@ -1,198 +0,0 @@
# Add custom GPU
LVGL has a flexible and extendable draw pipeline. You can hook it to do some rendering with a GPU or even completely replace the built-in software renderer.
## Draw context
The core structure of drawing is `lv_draw_ctx_t`.
It contains a pointer to a buffer where drawing should happen and a couple of callbacks to draw rectangles, texts, and other primitives.
### Fields
`lv_draw_ctx_t` has the following fields:
- `void * buf` Pointer to a buffer to draw into
- `lv_area_t * buf_area` The position and size of `buf` (absolute coordinates)
- `const lv_area_t * clip_area` The current clip area with absolute coordinates, always the same or smaller than `buf_area`. All drawings should be clipped to this area.
- `void (*draw_rect)()` Draw a rectangle with shadow, gradient, border, etc.
- `void (*draw_arc)()` Draw an arc
- `void (*draw_img_decoded)()` Draw an (A)RGB image that is already decoded by LVGL.
- `lv_res_t (*draw_img)()` Draw an image before decoding it (it bypasses LVGL's internal image decoders)
- `void (*draw_letter)()` Draw a letter
- `void (*draw_line)()` Draw a line
- `void (*draw_polygon)()` Draw a polygon
- `void (*draw_bg)()` Replace the buffer with a rect without decoration like radius or borders.
- `void (*wait_for_finish)()` Wait until all background operation are finished. (E.g. GPU operations)
- `void * user_data` Custom user data for arbitrary purpose
(For the sake of simplicity the parameters of the callbacks are not shown here.)
All `draw_*` callbacks receive a pointer to the current `draw_ctx` as their first parameter. Among the other parameters there is a descriptor that tells what to draw,
e.g. for `draw_rect` it's called [lv_draw_rect_dsc_t](https://github.com/lvgl/lvgl/blob/master/src/draw/lv_draw_rect.h),
for `lv_draw_line` it's called [lv_draw_line_dsc_t](https://github.com/lvgl/lvgl/blob/master/src/draw/lv_draw_line.h), etc.
To correctly render according to a `draw_dsc` you need to be familiar with the [Boxing model](https://docs.lvgl.io/master/overview/coords.html#boxing-model) of LVGL and the meanings of the fields. The name and meaning of the fields are identical to name and meaning of the [Style properties](https://docs.lvgl.io/master/overview/style-props.html).
### Initialization
The `lv_disp_drv_t` has 4 fields related to the draw context:
- `lv_draw_ctx_t * draw_ctx` Pointer to the `draw_ctx` of this display
- `void (*draw_ctx_init)(struct _lv_disp_drv_t * disp_drv, lv_draw_ctx_t * draw_ctx)` Callback to initialize a `draw_ctx`
- `void (*draw_ctx_deinit)(struct _lv_disp_drv_t * disp_drv, lv_draw_ctx_t * draw_ctx)` Callback to de-initialize a `draw_ctx`
- `size_t draw_ctx_size` Size of the draw context structure. E.g. `sizeof(lv_draw_sw_ctx_t)`
When you ignore these fields, LVGL will set default values for callbacks and size in `lv_disp_drv_init()` based on the configuration in `lv_conf.h`.
`lv_disp_drv_register()` will allocate a `draw_ctx` based on `draw_ctx_size` and call `draw_ctx_init()` on it.
However, you can overwrite the callbacks and the size values before calling `lv_disp_drv_register()`.
It makes it possible to use your own `draw_ctx` with your own callbacks.
## Software renderer
LVGL's built in software renderer extends the basic `lv_draw_ctx_t` structure and sets the draw callbacks. It looks like this:
```c
typedef struct {
/** Include the basic draw_ctx type*/
lv_draw_ctx_t base_draw;
/** Blend a color or image to an area*/
void (*blend)(lv_draw_ctx_t * draw_ctx, const lv_draw_sw_blend_dsc_t * dsc);
} lv_draw_sw_ctx_t;
```
Set the draw callbacks in `draw_ctx_init()` like:
```c
draw_sw_ctx->base_draw.draw_rect = lv_draw_sw_rect;
draw_sw_ctx->base_draw.draw_letter = lv_draw_sw_letter;
...
```
### Blend callback
As you saw above the software renderer adds the `blend` callback field. It's a special callback related to how the software renderer works.
All draw operations end up in the `blend` callback which can either fill an area or copy an image to an area by considering an optional mask.
The `lv_draw_sw_blend_dsc_t` parameter describes what and how to blend. It has the following fields:
- `const lv_area_t * blend_area` The area with absolute coordinates to draw on `draw_ctx->buf`. If `src_buf` is set, it's the coordinates of the image to blend.
- `const lv_color_t * src_buf` Pointer to an image to blend. If set, `color` is ignored. If not set fill `blend_area` with `color`
- `lv_color_t color` Fill color. Used only if `src_buf == NULL`
- `lv_opa_t * mask_buf` NULL if ignored, or an alpha mask to apply on `blend_area`
- `lv_draw_mask_res_t mask_res` The result of the previous mask operation. (`LV_DRAW_MASK_RES_...`)
- `const lv_area_t * mask_area` The area of `mask_buf` with absolute coordinates
- `lv_opa_t opa` The overall opacity
- `lv_blend_mode_t blend_mode` E.g. `LV_BLEND_MODE_ADDITIVE`
## Extend the software renderer
### New blend callback
Let's take a practical example: you would like to use your MCUs GPU for color fill operations only.
As all draw callbacks call `blend` callback to fill an area in the end only the `blend` callback needs to be overwritten.
First extend `lv_draw_sw_ctx_t`:
```c
/*We don't add new fields, so just for clarity add new type*/
typedef lv_draw_sw_ctx_t my_draw_ctx_t;
void my_draw_ctx_init(lv_disp_drv_t * drv, lv_draw_ctx_t * draw_ctx)
{
    /*Initialize the parent type first */
    lv_draw_sw_init_ctx(drv, draw_ctx);
    /*Change some callbacks*/
    my_draw_ctx_t * my_draw_ctx = (my_draw_ctx_t *)draw_ctx;
    my_draw_ctx->blend = my_draw_blend;
    my_draw_ctx->base_draw.wait_for_finish = my_gpu_wait;
}
```
After calling `lv_disp_draw_init(&drv)` you can assign the new `draw_ctx_init` callback and set `draw_ctx_size` to overwrite the defaults:
```c
static lv_disp_drv_t drv;
lv_disp_draw_init(&drv);
drv->hor_res = my_hor_res;
drv->ver_res = my_ver_res;
drv->flush_cb = my_flush_cb;
/*New draw ctx settings*/
drv->draw_ctx_init = my_draw_ctx_init;
drv->draw_ctx_size = sizeof(my_draw_ctx_t);
lv_disp_drv_register(&drv);
```
This way when LVGL calls `blend` it will call `my_draw_blend` and we can do custom GPU operations. Here is a complete example:
```c
void my_draw_blend(lv_draw_ctx_t * draw_ctx, const lv_draw_sw_blend_dsc_t * dsc)
{
    /*Let's get the blend area which is the intersection of the area to fill and the clip area.*/
    lv_area_t blend_area;
    if(!_lv_area_intersect(&blend_area, dsc->blend_area, draw_ctx->clip_area)) return;  /*Fully clipped, nothing to do*/
    /*Fill only non masked, fully opaque, normal blended and not too small areas*/
    if(dsc->src_buf == NULL && dsc->mask == NULL && dsc->opa >= LV_OPA_MAX &&
       dsc->blend_mode == LV_BLEND_MODE_NORMAL && lv_area_get_size(&blend_area) > 100) {
        /*Got the first pixel on the buffer*/
        lv_coord_t dest_stride = lv_area_get_width(draw_ctx->buf_area); /*Width of the destination buffer*/
        lv_color_t * dest_buf = draw_ctx->buf;
        dest_buf += dest_stride * (blend_area.y1 - draw_ctx->buf_area->y1) + (blend_area.x1 - draw_ctx->buf_area->x1);
        /*Make the blend area relative to the buffer*/      
        lv_area_move(&blend_area, -draw_ctx->buf_area->x1, -draw_ctx->buf_area->y1);
       
        /*Call your custom gou fill function to fill blend_area, on dest_buf with dsc->color*/  
        my_gpu_fill(dest_buf, dest_stride, &blend_area, dsc->color);
    }
    /*Fallback: the GPU doesn't support these settings. Call the SW renderer.*/
    else {
      lv_draw_sw_blend_basic(draw_ctx, dsc);
    }
}
```
The implementation of wait callback is much simpler:
```c
void my_gpu_wait(lv_draw_ctx_t * draw_ctx)
{
    while(my_gpu_is_working());
   
    /*Call SW renderer's wait callback too*/
    lv_draw_sw_wait_for_finish(draw_ctx);
}
```
### New rectangle drawer
If your MCU has a more powerful GPU that can draw e.g. rounded rectangles you can replace the original software drawer too.
A custom `draw_rect` callback might look like this:
```c
void my_draw_rect(lv_draw_ctx_t * draw_ctx, const lv_draw_rect_dsc_t * dsc, const lv_area_t * coords)
{
  if(lv_draw_mask_is_any(coords) == false && dsc->grad == NULL && dsc->bg_img_src == NULL &&
     dsc->shadow_width == 0 && dsc->blend_mode = LV_BLEND_MODE_NORMAL)
  {
    /*Draw the background*/
    my_bg_drawer(draw_ctx, coords, dsc->bg_color, dsc->radius);
   
    /*Draw the border if any*/
    if(dsc->border_width) {
      my_border_drawer(draw_ctx, coords, dsc->border_width, dsc->border_color, dsc->border_opa)
    }
   
    /*Draw the outline if any*/
    if(dsc->outline_width) {
      my_outline_drawer(draw_ctx, coords, dsc->outline_width, dsc->outline_color, dsc->outline_opa, dsc->outline_pad)
    }
  }
  /*Fallback*/
  else {
    lv_draw_sw_rect(draw_ctx, dsc, coords);
  }
}
```
`my_draw_rect` can fully bypass the use of `blend` callback if needed.
## Fully custom draw engine
For example if your MCU/MPU supports a powerful vector graphics engine you might use only that instead of LVGL's SW renderer.
In this case, you need to base the renderer on the basic `lv_draw_ctx_t` (instead of `lv_draw_sw_ctx_t`) and extend/initialize it as you wish.
-206
View File
@@ -1,206 +0,0 @@
# Input device interface
## Types of input devices
To register an input device an `lv_indev_drv_t` variable has to be initialized. **Be sure to register at least one display before you register any input devices.**
```c
/*Register at least one display before you register any input devices*/
lv_disp_drv_register(&disp_drv);
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv); /*Basic initialization*/
indev_drv.type =... /*See below.*/
indev_drv.read_cb =... /*See below.*/
/*Register the driver in LVGL and save the created input device object*/
lv_indev_t * my_indev = lv_indev_drv_register(&indev_drv);
```
The `type` member can be:
- `LV_INDEV_TYPE_POINTER` touchpad or mouse
- `LV_INDEV_TYPE_KEYPAD` keyboard or keypad
- `LV_INDEV_TYPE_ENCODER` encoder with left/right turn and push options
- `LV_INDEV_TYPE_BUTTON` external buttons virtually pressing the screen
`read_cb` is a function pointer which will be called periodically to report the current state of an input device.
Visit [Input devices](/overview/indev) to learn more about input devices in general.
### Touchpad, mouse or any pointer
Input devices that can click points on the screen belong to this category.
```c
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_input_read;
...
void my_input_read(lv_indev_drv_t * drv, lv_indev_data_t*data)
{
if(touchpad_pressed) {
data->point.x = touchpad_x;
data->point.y = touchpad_y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
```
To set a mouse cursor use `lv_indev_set_cursor(my_indev, &img_cursor)`. (`my_indev` is the return value of `lv_indev_drv_register`)
### Keypad or keyboard
Full keyboards with all the letters or simple keypads with a few navigation buttons belong here.
To use a keyboard/keypad:
- Register a `read_cb` function with `LV_INDEV_TYPE_KEYPAD` type.
- An object group has to be created: `lv_group_t * g = lv_group_create()` and objects have to be added to it with `lv_group_add_obj(g, obj)`
- The created group has to be assigned to an input device: `lv_indev_set_group(my_indev, g)` (`my_indev` is the return value of `lv_indev_drv_register`)
- Use `LV_KEY_...` to navigate among the objects in the group. See `lv_core/lv_group.h` for the available keys.
```c
indev_drv.type = LV_INDEV_TYPE_KEYPAD;
indev_drv.read_cb = keyboard_read;
...
void keyboard_read(lv_indev_drv_t * drv, lv_indev_data_t*data){
data->key = last_key(); /*Get the last pressed or released key*/
if(key_pressed()) data->state = LV_INDEV_STATE_PRESSED;
else data->state = LV_INDEV_STATE_RELEASED;
}
```
### Encoder
With an encoder you can do the following:
1. Press its button
2. Long-press its button
3. Turn left
4. Turn right
In short, the Encoder input devices work like this:
- By turning the encoder you can focus on the next/previous object.
- When you press the encoder on a simple object (like a button), it will be clicked.
- If you press the encoder on a complex object (like a list, message box, etc.) the object will go to edit mode whereby you can navigate inside the object by turning the encoder.
- To leave edit mode, long press the button.
To use an *Encoder* (similarly to the *Keypads*) the objects should be added to groups.
```c
indev_drv.type = LV_INDEV_TYPE_ENCODER;
indev_drv.read_cb = encoder_read;
...
void encoder_read(lv_indev_drv_t * drv, lv_indev_data_t*data){
data->enc_diff = enc_get_new_moves();
if(enc_pressed()) data->state = LV_INDEV_STATE_PRESSED;
else data->state = LV_INDEV_STATE_RELEASED;
}
```
#### Using buttons with Encoder logic
In addition to standard encoder behavior, you can also utilize its logic to navigate(focus) and edit widgets using buttons.
This is especially handy if you have only few buttons available, or you want to use other buttons in addition to encoder wheel.
You need to have 3 buttons available:
- `LV_KEY_ENTER` will simulate press or pushing of the encoder button
- `LV_KEY_LEFT` will simulate turning encoder left
- `LV_KEY_RIGHT` will simulate turning encoder right
- other keys will be passed to the focused widget
If you hold the keys it will simulate an encoder advance with period specified in `indev_drv.long_press_rep_time`.
```c
indev_drv.type = LV_INDEV_TYPE_ENCODER;
indev_drv.read_cb = encoder_with_keys_read;
...
void encoder_with_keys_read(lv_indev_drv_t * drv, lv_indev_data_t*data){
data->key = last_key(); /*Get the last pressed or released key*/
/* use LV_KEY_ENTER for encoder press */
if(key_pressed()) data->state = LV_INDEV_STATE_PRESSED;
else {
data->state = LV_INDEV_STATE_RELEASED;
/* Optionally you can also use enc_diff, if you have encoder*/
data->enc_diff = enc_get_new_moves();
}
}
```
### Button
*Buttons* mean external "hardware" buttons next to the screen which are assigned to specific coordinates of the screen.
If a button is pressed it will simulate the pressing on the assigned coordinate. (Similarly to a touchpad)
To assign buttons to coordinates use `lv_indev_set_button_points(my_indev, points_array)`.
`points_array` should look like `const lv_point_t points_array[] = { {12,30},{60,90}, ...}`
``` important:: The points_array can't go out of scope. Either declare it as a global variable or as a static variable inside a function.
```
```c
indev_drv.type = LV_INDEV_TYPE_BUTTON;
indev_drv.read_cb = button_read;
...
void button_read(lv_indev_drv_t * drv, lv_indev_data_t*data){
static uint32_t last_btn = 0; /*Store the last pressed button*/
int btn_pr = my_btn_read(); /*Get the ID (0,1,2...) of the pressed button*/
if(btn_pr >= 0) { /*Is there a button press? (E.g. -1 indicated no button was pressed)*/
last_btn = btn_pr; /*Save the ID of the pressed button*/
data->state = LV_INDEV_STATE_PRESSED; /*Set the pressed state*/
} else {
data->state = LV_INDEV_STATE_RELEASED; /*Set the released state*/
}
data->btn = last_btn; /*Save the last button*/
}
```
## Other features
### Parameters
The default value of the following parameters can be changed in `lv_indev_drv_t`:
- `scroll_limit` Number of pixels to slide before actually scrolling the object.
- `scroll_throw` Scroll throw (momentum) slow-down in [%]. Greater value means faster slow-down.
- `long_press_time` Press time to send `LV_EVENT_LONG_PRESSED` (in milliseconds)
- `long_press_rep_time` Interval of sending `LV_EVENT_LONG_PRESSED_REPEAT` (in milliseconds)
- `read_timer` pointer to the `lv_timer` which reads the input device. Its parameters can be changed by `lv_timer_...()` functions. `LV_INDEV_DEF_READ_PERIOD` in `lv_conf.h` sets the default read period.
### Feedback
Besides `read_cb` a `feedback_cb` callback can be also specified in `lv_indev_drv_t`.
`feedback_cb` is called when any type of event is sent by the input devices (independently of its type). This allows generating feedback for the user, e.g. to play a sound on `LV_EVENT_CLICKED`.
### Associating with a display
Every input device is associated with a display. By default, a new input device is added to the last display created or explicitly selected (using `lv_disp_set_default()`).
The associated display is stored and can be changed in `disp` field of the driver.
### Buffered reading
By default, LVGL calls `read_cb` periodically. Because of this intermittent polling there is a chance that some user gestures are missed.
To solve this you can write an event driven driver for your input device that buffers measured data. In `read_cb` you can report the buffered data instead of directly reading the input device.
Setting the `data->continue_reading` flag will tell LVGL there is more data to read and it should call `read_cb` again.
## Further reading
- [lv_port_indev_template.c](https://github.com/lvgl/lvgl/blob/master/examples/porting/lv_port_indev_template.c) for a template for your own driver.
- [INdev features](/overview/display) to learn more about higher level input device features.
## API
```eval_rst
.. doxygenfile:: lv_hal_indev.h
:project: lvgl
```
+303
View File
@@ -0,0 +1,303 @@
.. _porting_indev:
======================
Input device interface
======================
Types of input devices
**********************
To create an input device use
.. code:: c
/*Register at least one display before you register any input devices*/
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_...); /*See below.*/
lv_indev_set_read_cb(indev, read_cb); /*See below.*/
The ``type`` member can be:
- :cpp:enumerator:`LV_INDEV_TYPE_POINTER`: touchpad or mouse
- :cpp:enumerator:`LV_INDEV_TYPE_KEYPAD`: keyboard or keypad
- :cpp:enumerator:`LV_INDEV_TYPE_ENCODER`: encoder with left/right turn and push options
- :cpp:enumerator:`LV_INDEV_TYPE_BUTTON`: external buttons virtually pressing the screen
``read_cb`` is a function pointer which will be called periodically to
report the current state of an input device.
Visit :ref:`Input devices <indev>` to learn more about input
devices in general.
Touchpad, mouse or any pointer
------------------------------
Input devices that can click points on the screen belong to this
category.
.. code:: c
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
...
void my_input_read(lv_indev_t * indev, lv_indev_data_t*data)
{
if(touchpad_pressed) {
data->point.x = touchpad_x;
data->point.y = touchpad_y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
Mouse cursor
~~~~~~~~~~~~
To set a mouse cursor use :cpp:expr:`lv_indev_set_cursor(indev, &img_cursor)`.
Crown behavior
~~~~~~~~~~~~~~
The "Crown" is a rotary device typically found on smart watches.
When the user clicks somewhere and after that turns the rotary
the last clicked widget will be either scrolled or it's value will be incremented/decremented
(e.g. in case of a slider).
As this behavior is tightly related to the last clicked widget, the crown support is
an extension of the pointer input device. Just set ``data->diff`` to the number of
turned steps and LVGL will automatically send :cpp:enum:`LV_EVENT_ROTARY` or scroll the widget based on the
``editable`` flag in the widget's class. Non-editable widgets are scrolled and for editable widgets the event is sent.
To get the steps in an event callback use :cpp:func:`int32_t diff = lv_event_get_rotary_diff(e)`
The rotary sensitivity can be adjusted on 2 levels:
1. In the input device by the `indev->rotary_sensitvity` element (1/256 unit)
2. By the `rotary_sensitivity` style property in the widget (1/256 unit)
The final diff is calculated like this:
``diff_final = diff_in * (indev_sensitivity / 256) + (widget_sensitivity / 256); ``
For example, if both the indev and widget sensitivity is set to 128 (0.5), the input diff. will be
multiplied by 0.25 (divided by 4). The value of the widget will be incremented by this value or
the widget will be scrolled this amount of pixels.
Keypad or keyboard
------------------
Full keyboards with all the letters or simple keypads with a few
navigation buttons belong here.
To use a keyboard/keypad:
- Register a ``read_cb`` function and use :cpp:enumerator:`LV_INDEV_TYPE_KEYPAD` type.
- An object group has to be created: ``lv_group_t * g = lv_group_create()`` and objects have to be added to
it with :cpp:expr:`lv_group_add_obj(g, obj)`
- The created group has to be assigned to an input device: :cpp:expr:`lv_indev_set_group(indev, g)`
- Use ``LV_KEY_...`` to navigate among the objects in the group. See
``lv_core/lv_group.h`` for the available keys.
.. code:: c
lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
...
void keyboard_read(lv_indev_t * indev, lv_indev_data_t*data){
data->key = last_key(); /*Get the last pressed or released key*/
if(key_pressed()) data->state = LV_INDEV_STATE_PRESSED;
else data->state = LV_INDEV_STATE_RELEASED;
}
Encoder
-------
With an encoder you can do the following:
1. Press its button
2. Long-press its button
3. Turn left
4. Turn right
In short, the Encoder input devices work like this:
- By turning the encoder you can focus on the next/previous object.
- When you press the encoder on a simple object (like a button), it will be clicked.
- If you press the encoder on a complex object (like a list, message box, etc.)
the object will go to edit mode whereby you can navigate inside the
object by turning the encoder.
- To leave edit mode, long press the button.
To use an *Encoder* (similarly to the *Keypads*) the objects should be
added to groups.
.. code:: c
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
...
void encoder_read(lv_indev_t * indev, lv_indev_data_t*data){
data->enc_diff = enc_get_new_moves();
if(enc_pressed()) data->state = LV_INDEV_STATE_PRESSED;
else data->state = LV_INDEV_STATE_RELEASED;
}
Using buttons with Encoder logic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In addition to standard encoder behavior, you can also utilize its logic
to navigate(focus) and edit widgets using buttons. This is especially
handy if you have only few buttons available, or you want to use other
buttons in addition to encoder wheel.
You need to have 3 buttons available:
- :cpp:enumerator:`LV_KEY_ENTER`: will simulate press or pushing of the encoder button
- :cpp:enumerator:`LV_KEY_LEFT`: will simulate turning encoder left
- :cpp:enumerator:`LV_KEY_RIGHT`: will simulate turning encoder right
- other keys will be passed to the focused widget
If you hold the keys it will simulate an encoder advance with period
specified in ``indev_drv.long_press_repeat_time``.
.. code:: c
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
...
void encoder_with_keys_read(lv_indev_t * indev, lv_indev_data_t*data){
data->key = last_key(); /*Get the last pressed or released key*/
/* use LV_KEY_ENTER for encoder press */
if(key_pressed()) data->state = LV_INDEV_STATE_PRESSED;
else {
data->state = LV_INDEV_STATE_RELEASED;
/* Optionally you can also use enc_diff, if you have encoder*/
data->enc_diff = enc_get_new_moves();
}
}
Button
------
*Buttons* mean external "hardware" buttons next to the screen which are
assigned to specific coordinates of the screen. If a button is pressed
it will simulate the pressing on the assigned coordinate. (Similarly to a touchpad)
To assign buttons to coordinates use ``lv_indev_set_button_points(my_indev, points_array)``. ``points_array``
should look like ``const lv_point_t points_array[] = { {12,30},{60,90}, ...}``
:important: The points_array can't go out of scope. Either declare it as a global variable
or as a static variable inside a function.`
.. code:: c
lv_indev_set_type(indev, LV_INDEV_TYPE_BUTTON);
...
void button_read(lv_indev_t * indev, lv_indev_data_t*data){
static uint32_t last_btn = 0; /*Store the last pressed button*/
int btn_pr = my_btn_read(); /*Get the ID (0,1,2...) of the pressed button*/
if(btn_pr >= 0) { /*Is there a button press? (E.g. -1 indicated no button was pressed)*/
last_btn = btn_pr; /*Save the ID of the pressed button*/
data->state = LV_INDEV_STATE_PRESSED; /*Set the pressed state*/
} else {
data->state = LV_INDEV_STATE_RELEASED; /*Set the released state*/
}
data->btn_id = last_btn; /*Save the last button*/
}
When the ``button_read`` callback in the example above changes the ``data->btn_id`` to ``0``
a press/release action at the first index of the ``points_array`` will be performed (``{12,30}``).
.. _porting_indev_other_features:
Other features
**************
Parameters
----------
The default value of the following parameters can be changed in :cpp:type:`lv_indev_t`:
- ``scroll_limit`` Number of pixels to slide before actually scrolling the object.
- ``scroll_throw`` Scroll throw (momentum) slow-down in [%]. Greater value means faster slow-down.
- ``long_press_time`` Press time to send :cpp:enumerator:`LV_EVENT_LONG_PRESSED` (in milliseconds)
- ``long_press_repeat_time`` Interval of sending :cpp:enumerator:`LV_EVENT_LONG_PRESSED_REPEAT` (in milliseconds)
- ``read_timer`` pointer to the ``lv_timer`` which reads the input device. Its parameters
can be changed by ``lv_timer_...()`` functions. :c:macro:`LV_DEF_REFR_PERIOD`
in ``lv_conf.h`` sets the default read period.
Feedback
--------
Besides ``read_cb`` a ``feedback_cb`` callback can be also specified in
:cpp:type:`lv_indev_t`. ``feedback_cb`` is called when any type of event is sent
by the input devices (independently of its type). This allows generating
feedback for the user, e.g. to play a sound on :cpp:enumerator:`LV_EVENT_CLICKED`.
Associating with a display
--------------------------
Every input device is associated with a display. By default, a new input
device is added to the last display created or explicitly selected
(using :cpp:func:`lv_display_set_default`). The associated display is stored and
can be changed in ``disp`` field of the driver.
Buffered reading
----------------
By default, LVGL calls ``read_cb`` periodically. Because of this
intermittent polling there is a chance that some user gestures are
missed.
To solve this you can write an event driven driver for your input device
that buffers measured data. In ``read_cb`` you can report the buffered
data instead of directly reading the input device. Setting the
``data->continue_reading`` flag will tell LVGL there is more data to
read and it should call ``read_cb`` again.
Switching the input device to event-driven mode
-----------------------------------------------
Normally the input event is read every :c:macro:`LV_DEF_REFR_PERIOD`
milliseconds (set in ``lv_conf.h``). However, in some cases, you might
need more control over when to read the input device. For example, you
might need to read it by polling file descriptor (fd).
You can do this in the following way:
.. code:: c
/*Update the input device's running mode to LV_INDEV_MODE_EVENT*/
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
...
/*Call this anywhere you want to read the input device*/
lv_indev_read(indev);
.. note:: that :cpp:func:`lv_indev_read`, :cpp:func:`lv_timer_handler` and :cpp:func:`_lv_display_refr_timer` can not run at the same time.
.. note:: For devices in event-driven mode, `data->continue_reading` is ignored.
Further reading
***************
- `lv_port_indev_template.c <https://github.com/lvgl/lvgl/blob/master/examples/porting/lv_port_indev_template.c>`__ for a template for your own driver.
- `INdev features <indev>` to learn more about higher level input device features.
API
***
-20
View File
@@ -1,20 +0,0 @@
# Porting
```eval_rst
.. toctree::
:maxdepth: 2
project
display
indev
tick
timer-handler
sleep
os
log
gpu
```
+19
View File
@@ -0,0 +1,19 @@
.. _porting:
=======
Porting
=======
.. toctree::
:maxdepth: 2
project
display
indev
tick
timer_handler
sleep
os
log
draw
-49
View File
@@ -1,49 +0,0 @@
# Logging
LVGL has a built-in *Log* module to inform the user about what is happening in the library.
## Log level
To enable logging, set `LV_USE_LOG 1` in `lv_conf.h` and set `LV_LOG_LEVEL` to one of the following values:
- `LV_LOG_LEVEL_TRACE` A lot of logs to give detailed information
- `LV_LOG_LEVEL_INFO` Log important events
- `LV_LOG_LEVEL_WARN` Log if something unwanted happened but didn't cause a problem
- `LV_LOG_LEVEL_ERROR` Only critical issues, where the system may fail
- `LV_LOG_LEVEL_USER` Only user messages
- `LV_LOG_LEVEL_NONE` Do not log anything
The events which have a higher level than the set log level will be logged too. E.g. if you `LV_LOG_LEVEL_WARN`, errors will be also logged.
## Printing logs
### Logging with printf
If your system supports `printf`, you just need to enable `LV_LOG_PRINTF` in `lv_conf.h` to send the logs with `printf`.
### Custom log function
If you can't use `printf` or want to use a custom function to log, you can register a "logger" callback with `lv_log_register_print_cb()`.
For example:
```c
void my_log_cb(const char * buf)
{
serial_send(buf, strlen(buf));
}
...
lv_log_register_print_cb(my_log_cb);
```
## Add logs
You can also use the log module via the `LV_LOG_TRACE/INFO/WARN/ERROR/USER(text)` or `LV_LOG(text)` functions. Here:
- `LV_LOG_TRACE/INFO/WARN/ERROR/USER(text)` append following information to your `text`
- Log Level
- \_\_FILE\_\_
- \_\_LINE\_\_
- \_\_func\_\_
- `LV_LOG(text)` is similar to `LV_LOG_USER` but has no extra information attached.
+71
View File
@@ -0,0 +1,71 @@
.. _logging:
=======
Logging
=======
LVGL has a built-in *Log* module to inform the user about what is
happening in the library.
Log level
*********
To enable logging, set :c:macro:`LV_USE_LOG` in ``lv_conf.h`` and set
:c:macro:`LV_LOG_LEVEL` to one of the following values:
- :c:macro:`LV_LOG_LEVEL_TRACE`: A lot of logs to give detailed information
- :c:macro:`LV_LOG_LEVEL_INFO`: Log important events
- :c:macro:`LV_LOG_LEVEL_WARN`: Log if something unwanted happened but didn't cause a problem
- :c:macro:`LV_LOG_LEVEL_ERROR`: Only critical issues, where the system may fail
- :c:macro:`LV_LOG_LEVEL_USER`: Only user messages
- :c:macro:`LV_LOG_LEVEL_NONE`: Do not log anything
The events which have a higher level than the set log level will be
logged too. E.g. if you :c:macro:`LV_LOG_LEVEL_WARN`, errors will be also
logged.
Printing logs
*************
Logging with printf
-------------------
If your system supports ``printf``, you just need to enable
:c:macro:`LV_LOG_PRINTF` in ``lv_conf.h`` to send the logs with ``printf``.
Custom log function
-------------------
If you can't use ``printf`` or want to use a custom function to log, you
can register a "logger" callback with :cpp:func:`lv_log_register_print_cb`.
For example:
.. code:: c
void my_log_cb(lv_log_level_t level, const char * buf)
{
serial_send(buf, strlen(buf));
}
...
lv_log_register_print_cb(my_log_cb);
Add logs
********
You can also use the log module via the
``LV_LOG_TRACE/INFO/WARN/ERROR/USER(text)`` or ``LV_LOG(text)``
functions. Here:
- ``LV_LOG_TRACE/INFO/WARN/ERROR/USER(text)`` append following information to your ``text``
- Log Level
- \__FILE\_\_
- \__LINE\_\_
- \__func\_\_
- ``LV_LOG(text)`` is similar to ``LV_LOG_USER`` but has no extra information attached.
API
***
-50
View File
@@ -1,50 +0,0 @@
# Operating system and interrupts
LVGL is **not thread-safe** by default.
However, in the following conditions it's valid to call LVGL related functions:
- In *events*. Learn more in [Events](/overview/event).
- In *lv_timer*. Learn more in [Timers](/overview/timer).
## Tasks and threads
If you need to use real tasks or threads, you need a mutex which should be invoked before the call of `lv_timer_handler` and released after it.
Also, you have to use the same mutex in other tasks and threads around every LVGL (`lv_...`) related function call and code.
This way you can use LVGL in a real multitasking environment. Just make use of a mutex to avoid the concurrent calling of LVGL functions.
Here is some pseudocode to illustrate the concept:
```c
static mutex_t lvgl_mutex;
void lvgl_thread(void)
{
while(1) {
mutex_lock(&lvgl_mutex);
lv_task_handler();
mutex_unlock(&lvgl_mutex);
thread_sleep(10); /* sleep for 10 ms */
}
}
void other_thread(void)
{
/* You must always hold the mutex while using LVGL APIs */
mutex_lock(&lvgl_mutex);
lv_obj_t *img = lv_img_create(lv_scr_act());
mutex_unlock(&lvgl_mutex);
while(1) {
mutex_lock(&lvgl_mutex);
/* change to the next image */
lv_img_set_src(img, next_image);
mutex_unlock(&lvgl_mutex);
thread_sleep(2000);
}
}
```
## Interrupts
Try to avoid calling LVGL functions from interrupt handlers (except `lv_tick_inc()` and `lv_disp_flush_ready()`). But if you need to do this you have to disable the interrupt which uses LVGL functions while `lv_timer_handler` is running.
It's a better approach to simply set a flag or some value in the interrupt, and periodically check it in an LVGL timer (which is run by `lv_timer_handler`).
+66
View File
@@ -0,0 +1,66 @@
.. _os_interrupt:
===============================
Operating system and interrupts
===============================
LVGL is **not thread-safe** by default.
However, in the following conditions it's valid to call LVGL related
functions: - In *events*. Learn more in :ref:`events`. -
In *lv_timer*. Learn more in :ref:`timer`.
Tasks and threads
-----------------
If you need to use real tasks or threads, you need a mutex which should
be invoked before the call of :cpp:func:`lv_timer_handler` and released after
it. Also, you have to use the same mutex in other tasks and threads
around every LVGL (``lv_...``) related function call and code. This way
you can use LVGL in a real multitasking environment. Just make use of a
mutex to avoid the concurrent calling of LVGL functions.
Here is some pseudocode to illustrate the concept:
.. code:: c
static mutex_t lvgl_mutex;
void lvgl_thread(void)
{
while(1) {
uint32_t time_till_next;
mutex_lock(&lvgl_mutex);
time_till_next = lv_task_handler();
mutex_unlock(&lvgl_mutex);
thread_sleep(time_till_next); /* sleep for a while */
}
}
void other_thread(void)
{
/* You must always hold the mutex while using LVGL APIs */
mutex_lock(&lvgl_mutex);
lv_obj_t *img = lv_image_create(lv_screen_active());
mutex_unlock(&lvgl_mutex);
while(1) {
mutex_lock(&lvgl_mutex);
/* change to the next image */
lv_image_set_src(img, next_image);
mutex_unlock(&lvgl_mutex);
thread_sleep(2000);
}
}
Interrupts
----------
Try to avoid calling LVGL functions from interrupt handlers (except
:cpp:func:`lv_tick_inc` and :cpp:func:`lv_display_flush_ready`). But if you need to do
this you have to disable the interrupt which uses LVGL functions while
:cpp:func:`lv_timer_handler` is running.
It's a better approach to simply set a flag or some value in the
interrupt, and periodically check it in an LVGL timer (which is run by
:cpp:func:`lv_timer_handler`).
-66
View File
@@ -1,66 +0,0 @@
# Set up a project
## Get the library
LVGL is available on GitHub: [https://github.com/lvgl/lvgl](https://github.com/lvgl/lvgl).
You can clone it or [Download](https://github.com/lvgl/lvgl/archive/refs/heads/master.zip) the latest version of the library from GitHub.
## Add lvgl to your project
The graphics library itself is the `lvgl` directory. It contains a couple of folders but to use `lvgl` you only need `.c` and `.h` files from the `src` folder.
### Automatically add files
If your IDE automatically adds the files from the folders copied to the project folder (as Eclipse or VSCode does), you can simply copy the `lvgl` folder as it is into your project.
### Make and CMake
LVGL also supports `make` and `CMake` build systems out of the box. To add LVGL to your Makefile based build system add these lines to your main Makefile:
```make
LVGL_DIR_NAME ?= lvgl #The name of the lvgl folder (change this if you have renamed it)
LVGL_DIR ?= ${shell pwd} #The path where the lvgl folder is
include $(LVGL_DIR)/$(LVGL_DIR_NAME)/lvgl.mk
```
For integration with CMake take a look this section of the [Documentation](/get-started/platforms/cmake).
### Other platforms and tools
The [Get started](/get-started/index.html) section contains many platform specific descriptions e.g. for ESP32, Arduino, NXP, RT-Thread, NuttX, etc.
### Demos and Examples
The `lvgl` folder also contains an `examples` and a `demos` folder. If you needed to add the source files manually to your project, you can do the same with the source files of these two folders too. `make` and `CMake` handles the examples and demos, so no extra action required in these cases.
## Configuration file
There is a configuration header file for LVGL called **lv_conf.h**. You modify this header to set the library's basic behavior, disable unused modules and features, adjust the size of memory buffers in compile-time, etc.
To get `lv_conf.h` **copy lvgl/lv_conf_template.h** next to the `lvgl` directory and rename it to *lv_conf.h*. Open the file and change the `#if 0` at the beginning to `#if 1` to enable its content. So the layout of the files should look like this:
```
|-lvgl
|-lv_conf.h
|-other files and folders
```
Comments in the config file explain the meaning of the options. Be sure to set at least `LV_COLOR_DEPTH` according to your display's color depth. Note that, the examples and demos explicitly need to be enabled in `lv_conf.h`.
Alternatively, `lv_conf.h` can be copied to another place but then you should add the `LV_CONF_INCLUDE_SIMPLE` define to your compiler options (e.g. `-DLV_CONF_INCLUDE_SIMPLE` for GCC compiler) and set the include path manually (e.g. `-I../include/gui`).
In this case LVGL will attempt to include `lv_conf.h` simply with `#include "lv_conf.h"`.
You can even use a different name for `lv_conf.h`. The custom path can be set via the `LV_CONF_PATH` define.
For example `-DLV_CONF_PATH="/home/joe/my_project/my_custom_conf.h"`
If `LV_CONF_SKIP` is defined, LVGL will not try to include `lv_conf.h`. Instead you can pass the config defines using build options. For example `"-DLV_COLOR_DEPTH=32 -DLV_USE_BTN=1"`. The unset options will get a default value which is the same as the ones in `lv_conf_template.h`.
LVGL also can be used via `Kconfig` and `menuconfig`.  You can use `lv_conf.h` together with Kconfig, but keep in mind that the value from `lv_conf.h` or build settings (`-D...`) overwrite the values set in Kconfig. To ignore the configs from `lv_conf.h` simply remove its content, or define `LV_CONF_SKIP`. 
## Initialization
To use the graphics library you have to initialize it and setup required components. The order of the initialization is:
1. Call `lv_init()`.
2. Initialize your drivers.
3. Register the display and input devices drivers in LVGL. Learn more about [Display](/porting/display) and [Input device](/porting/indev) registration.
4. Call `lv_tick_inc(x)` every `x` milliseconds in an interrupt to report the elapsed time to LVGL. [Learn more](/porting/tick).
5. Call `lv_timer_handler()` every few milliseconds to handle LVGL related tasks. [Learn more](/porting/timer-handler).
+138
View File
@@ -0,0 +1,138 @@
================
Set up a project
================
Get the library
---------------
LVGL is available on GitHub: https://github.com/lvgl/lvgl.
You can clone it or
`Download <https://github.com/lvgl/lvgl/archive/refs/heads/master.zip>`__
the latest version of the library from GitHub.
Add lvgl to your project
------------------------
The graphics library itself is the ``lvgl`` directory. It contains a
couple of folders but to use ``lvgl`` you only need the ``.c`` and ``.h``
files in the ``src`` folder.
Automatically add files
~~~~~~~~~~~~~~~~~~~~~~~
If your IDE automatically adds the files from the folders copied to the
project folder (as Eclipse or VSCode does), you can simply copy the
``lvgl`` folder as it is into your project.
Make and CMake
~~~~~~~~~~~~~~
LVGL also supports ``make`` and ``CMake`` build systems out of the box.
To add LVGL to your Makefile based build system add these lines to your
main Makefile:
.. code:: make
LVGL_DIR_NAME ?= lvgl #The name of the lvgl folder (change this if you have renamed it)
LVGL_DIR ?= ${shell pwd} #The path where the lvgl folder is
include $(LVGL_DIR)/$(LVGL_DIR_NAME)/lvgl.mk
For integration with CMake take a look this section of the
`Documentation </integration/build/cmake>`__.
Other platforms and tools
~~~~~~~~~~~~~~~~~~~~~~~~~
The `Get started </integartion/index>`__ section contains many platform
specific descriptions e.g. for ESP32, Arduino, NXP, RT-Thread, NuttX,
etc.
Demos and Examples
~~~~~~~~~~~~~~~~~~
The ``lvgl`` folder also contains an ``examples`` and a ``demos``
folder. If you needed to add the source files manually to your project,
you can do the same with the source files of these two folders too.
``make`` and ``CMake`` handles the examples and demos, so no extra
action required in these cases.
Configuration file
------------------
There is a configuration header file for LVGL called **lv_conf.h**. You
can modify this header to set the library's basic behavior, disable unused
modules and features, adjust the size of buffers in compile-time,
etc.
To get ``lv_conf.h`` **copy lvgl/lv_conf_template.h** next to the
``lvgl`` directory and rename it to *lv_conf.h*. Open the file and
change the ``#if 0`` at the beginning to ``#if 1`` to enable its
content. So the layout of the files should look like this:
::
|-lvgl
|-lv_conf.h
|-other files and folders
Comments in the config file explain the meaning of the options. Be sure
to set at least :c:macro:`LV_COLOR_DEPTH` according to your display's color
depth. Note that, the examples and demos explicitly need to be enabled
in ``lv_conf.h``.
Alternatively, ``lv_conf.h`` can be copied to another place but then you
should add the :c:macro:`LV_CONF_INCLUDE_SIMPLE` define to your compiler
options (e.g. ``-DLV_CONF_INCLUDE_SIMPLE`` for GCC compiler) and set the
include path manually (e.g. ``-I../include/gui``). In this case LVGL
will attempt to include ``lv_conf.h`` simply with
``#include "lv_conf.h"``.
You can even use a different name for ``lv_conf.h``. The custom path can
be set via the :c:macro:`LV_CONF_PATH` define. For example
``-DLV_CONF_PATH="/home/joe/my_project/my_custom_conf.h"``
If :c:macro:`LV_CONF_SKIP` is defined, LVGL will not try to include
``lv_conf.h``. Instead you can pass the config defines using build
options. For example ``"-DLV_COLOR_DEPTH=32 -DLV_USE_BUTTON=1"``. The unset
options will get a default value which is the same as the content of
``lv_conf_template.h``.
LVGL also can be used via ``Kconfig`` and ``menuconfig``. You can use
``lv_conf.h`` together with Kconfig too, but keep in mind that the value
from ``lv_conf.h`` or build settings (``-D...``) overwrite the values
set in Kconfig. To ignore the configs from ``lv_conf.h`` simply remove
its content, or define :c:macro:`LV_CONF_SKIP`.
To enable multi-instance feature, set :c:macro:`LV_GLOBAL_CUSTOM` in
``lv_conf.h`` and provide a custom function to
:cpp:func:`lv_global_default` using ``__thread`` or ``pthread_key_t``.
It will allow running multiple LVGL instances by storing the global variables
in TLS (Thread Local Storage).
For example:
.. code:: c
lv_global_t * lv_global_default(void)
{
static __thread lv_global_t lv_global;
return &lv_global;
}
Initialization
--------------
To use the graphics library you have to initialize it and setup required
components. The order of the initialization is:
1. Call :cpp:func:`lv_init`.
2. Initialize your drivers.
3. Register the display and input devices drivers in LVGL. Learn more
about `Display </porting/display>`__ and `Input
device </porting/indev>`__ registration.
4. Call :cpp:expr:`lv_tick_inc(x)` every ``x`` milliseconds in an interrupt to
report the elapsed time to LVGL. `Learn more </porting/tick>`__.
5. Call :cpp:func:`lv_timer_handler` every few milliseconds to handle LVGL
related tasks. `Learn more </porting/timer-handler>`__.
-27
View File
@@ -1,27 +0,0 @@
# Sleep management
The MCU can go to sleep when no user input happens. In this case, the main `while(1)` should look like this:
```c
while(1) {
/*Normal operation (no sleep) in < 1 sec inactivity*/
if(lv_disp_get_inactive_time(NULL) < 1000) {
lv_task_handler();
}
/*Sleep after 1 sec inactivity*/
else {
timer_stop(); /*Stop the timer where lv_tick_inc() is called*/
sleep(); /*Sleep the MCU*/
}
my_delay_ms(5);
}
```
You should also add the following lines to your input device read function to signal a wake-up (press, touch or click etc.) has happened:
```c
lv_tick_inc(LV_DISP_DEF_REFR_PERIOD); /*Force task execution on wake-up*/
timer_start(); /*Restart the timer where lv_tick_inc() is called*/
lv_task_handler(); /*Call `lv_task_handler()` manually to process the wake-up event*/
```
In addition to `lv_disp_get_inactive_time()` you can check `lv_anim_count_running()` to see if all animations have finished.
+33
View File
@@ -0,0 +1,33 @@
================
Sleep management
================
The MCU can go to sleep when no user input happens. In this case, the
main ``while(1)`` should look like this:
.. code:: c
while(1) {
/*Normal operation (no sleep) in < 1 sec inactivity*/
if(lv_display_get_inactive_time(NULL) < 1000) {
lv_task_handler();
}
/*Sleep after 1 sec inactivity*/
else {
timer_stop(); /*Stop the timer where lv_tick_inc() is called*/
sleep(); /*Sleep the MCU*/
}
my_delay_ms(5);
}
You should also add the following lines to your input device read
function to signal a wake-up (press, touch or click etc.) has happened:
.. code:: c
lv_tick_inc(LV_DEF_REFR_PERIOD); /*Force task execution on wake-up*/
timer_start(); /*Restart the timer where lv_tick_inc() is called*/
lv_task_handler(); /*Call `lv_task_handler()` manually to process the wake-up event*/
In addition to :cpp:func:`lv_display_get_inactive_time` you can check
:cpp:func:`lv_anim_count_running` to see if all animations have finished.
-31
View File
@@ -1,31 +0,0 @@
# Tick interface
LVGL needs a system tick to know elapsed time for animations and other tasks.
You need to call the `lv_tick_inc(tick_period)` function periodically and provide the call period in milliseconds. For example, `lv_tick_inc(1)` when calling every millisecond.
`lv_tick_inc` should be called in a higher priority routine than `lv_task_handler()` (e.g. in an interrupt) to precisely know the elapsed milliseconds even if the execution of `lv_task_handler` takes more time.
With FreeRTOS `lv_tick_inc` can be called in `vApplicationTickHook`.
On Linux based operating systems (e.g. on Raspberry Pi) `lv_tick_inc` can be called in a thread like below:
```c
void * tick_thread (void *args)
{
while(1) {
usleep(5*1000); /*Sleep for 5 millisecond*/
lv_tick_inc(5); /*Tell LVGL that 5 milliseconds were elapsed*/
}
}
```
## API
```eval_rst
.. doxygenfile:: lv_hal_tick.h
:project: lvgl
```
+33
View File
@@ -0,0 +1,33 @@
.. _tick:
==============
Tick interface
==============
LVGL needs a system tick to know the elapsed time for animations and other
tasks.
There are two ways to provide the tick to LVGL:
1. Call ``lv_tick_set_cb(my_get_milliseconds_function);``: `my_get_milliseconds_function` needs to tell how many milliseconds have elapsed since start up. Most of the platforms have built-in functions that can be used as they are. For example
- SDL: ``lv_tick_set_cb(SDL_GetTicks);``
- Arduino: ``lv_tick_set_cb(millis);``
- FreeRTOS: ``lv_tick_set_cb(xTaskGetTickCount);``
- STM32: ``lv_tick_set_cb(HAL_GetTick);``
- ESP32: ``lv_tick_set_cb(my_tick_get_cb);``, where ``my_tick_get_cb`` is a wrapper for ``esp_timer_get_time() / 1000;``
2. Call ``lv_tick_inc(x)`` periodically, where ``x`` is the elapsed milliseconds since the last call. ``lv_tick_inc`` should be called from a high priority interrupt.
The ticks (milliseconds) should be independent from any other activities of the MCU.
For example this works, but LVGL's timing will be incorrect as the execution time of ``lv_timer_handler`` is not considered:
.. code:: c
// Bad idea
lv_timer_handler();
lv_tick_inc(5);
my_delay_ms(5);
API
---
-38
View File
@@ -1,38 +0,0 @@
# Timer Handler
To handle the tasks of LVGL you need to call `lv_timer_handler()` periodically in one of the following:
- *while(1)* of *main()* function
- timer interrupt periodically (lower priority than `lv_tick_inc()`)
- an OS task periodically
The timing is not critical but it should be about 5 milliseconds to keep the system responsive.
Example:
```c
while(1) {
lv_timer_handler();
my_delay_ms(5);
}
```
If you want to use `lv_timer_handler()` in a super-loop, a helper function`lv_timer_handler_run_in_period()` is provided to simplify the porting:
```c
while(1) {
...
lv_timer_handler_run_in_period(5); /* run lv_timer_handler() every 5ms */
...
}
```
In an OS environment, you can use it together with the **delay** or **sleep** provided by OS to release CPU whenever possible:
```c
while (1) {
lv_timer_handler_run_in_period(5); /* run lv_timer_handler() every 5ms */
my_delay_ms(5); /* delay 5ms to avoid unnecessary polling */
}
```
To learn more about timers visit the [Timer](/overview/timer) section.
+59
View File
@@ -0,0 +1,59 @@
.. _timer:
=============
Timer Handler
=============
To handle the tasks of LVGL you need to call :cpp:func:`lv_timer_handler`
periodically in one of the following:
- *while(1)* of *main()* function
- timer interrupt periodically (lower priority than :cpp:func:`lv_tick_inc`)
- an OS task periodically
Example:
.. code:: c
while(1) {
uint32_t time_till_next = lv_timer_handler();
my_delay_ms(time_till_next);
}
If you want to use :cpp:func:`lv_timer_handler` in a super-loop, a helper
function :cpp:func:`lv_timer_handler_run_in_period` is provided to simplify
the porting:
.. code:: c
while(1) {
...
lv_timer_handler_run_in_period(5); /* run lv_timer_handler() every 5ms */
...
}
Or use the sleep time automatically calculated by LVGL:
.. code:: c
while(1) {
...
lv_timer_periodic_handler();
...
}
In an OS environment, you can use it together with the **delay** or
**sleep** provided by OS to release CPU whenever possible:
.. code:: c
while (1) {
uint32_t time_till_next = lv_timer_handler();
os_delay_ms(time_till_next); /* delay to avoid unnecessary polling */
}
To learn more about timers visit the :ref:`timer`
section.
API
***