feat(docs): migrate from .md to .rst (#4129)

This commit is contained in:
Kevin Schlosser
2023-04-27 06:42:02 -06:00
committed by GitHub
parent e7f88efa58
commit e485dd8bb4
366 changed files with 27657 additions and 15146 deletions

261
docs/porting/disp.rst Normal file
View File

@@ -0,0 +1,261 @@
.. _display_interface:
=================
Display interface
=================
To create a display for LVGL call
:cpp:expr:`lv_disp_t * disp = lv_disp_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 screens
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_disp_t * disp, const lv_area_t * area, lv_color_t * buf)
{
/*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.*/
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 LVGL that you are ready with the flushing and buf is not used anymore*/
lv_disp_flush_ready(disp);
}
Use :cpp:expr:`lv_disp_set_flush_cb(disp, my_flush_cb)` to set a new ``flush_cb``.
:cpp:expr:`lv_disp_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_disp_flush_is_last(disp)`.
Draw buffers
------------
The draw buffers can be set with
:cpp:expr:`lv_disp_set_draw_buffers(disp, buf1, buf2, buf_size_px, render_mode)`
- ``buf1`` a bufer 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_DISP_RENDER_MODE_PARTIAL` Use the buffer(s) to render the
screen is smaller parts. This way the buffers can be smaller then
the display to save RAM. At least 1/10 sceen size buffer(s) are
recommended. In ``flush_cb`` the rendered images needs to be
copied to the given area of the display.
- :cpp:enumerator:`LV_DISP_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 ares 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 and always the
changed areas will be redrawn.
- :cpp:enumerator:`LV_DISP_RENDER_MODE_FULL` The buffer can smaller or screen
sized but LVGL will always redraw the whole screen even is only 1
pixel has been changed. If two screen sized draw buffers are
provided, LVGLs 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).
Example:
.. code:: c
static lv_color_t buf[LCD_HOR_RES * LCD_VER_RES / 10];
lv_disp_set_draw_buffers(disp, buf, NULL, sizeof(buf), LV_DISP_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 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.
Advnaced options
****************
Resolution
----------
To set the resolution of the display after creation use
:cpp:expr:`lv_disp_set_res(disp, hor_res, ver_res)`
Its 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 resoltution and the offset
of the active area can be set with
:cpp:expr:`lv_disp_set_physical_res(disp, hor_res, ver_res)` and
:cpp:expr:`lv_disp_set_offset(disp, x, y)`
Rotation
--------
LVGL supports rotation of the display in 90 degree increments. You can
select whether youd like software rotation or hardware rotation.
The orientation of the display can be changed with
``lv_disp_set_rotation(disp, LV_DISP_ROTATION_0/90/180/270, true/false)``.
LVGL will swap the horizontal and vertical resolutions internally
according to the set degree. IF the last paramter is ``true`` LVGL will
rotate the rendered image. If its ``false`` the display driver should
rotate the rendered image.
Color format
------------
Set the color format of the display. The default is
:cpp:enumerator:`LV_COLOR_FORMAT_NATIVE` which means LVGL render with the follow
formats dpeneding on :c:macro:`LV_COLOR_DEPTH`:
- :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)
The ``color_format`` can be changed with
:cpp:expr:`lv_disp_set_color_depth(disp, LV_COLOR_FORMAT_...)` to the following
values:
- :cpp:enumerator:`LV_COLOR_FORMAT_NATIVE_ALPHA`: Append an alpha byte to the native format resulting
in A8L8, ARGB8565, ARGB8888 formats.
- :cpp:enumerator:`LV_COLOR_FORMAT_NATIVE_REVERSE`: Reverse the byte order of the native format. Useful if the
rendered image is sent to the disply via SPI and
the display needs the bytes in the opposite order.
- :cpp:enumerator:`LV_COLOR_FORMAT_L8`: Lightness only on 8 bit
- :cpp:enumerator:`LV_COLOR_FORMAT_A8`: Alpha only on 8 bit
- :cpp:enumerator:`LV_COLOR_FORMAT_I8`: Indexed (palette) 8 bit
- :cpp:enumerator:`LV_COLOR_FORMAT_A8L8`: Lightness on 8 bit with 8 bit alpha
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB2222`: ARGB with 2 bit for each channel
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565`: 16 bit RGB565 format without alpha channel
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8565`: 16 bit RGB565 format and 8 bit alpha channel
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB1555`: 5 bit for each color channel and 1 bit for alpha
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB4444`: 4 bit for each channel
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB888`: 8 bit for each color channel with out alpha channel
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888`: 8 bit for each channel
- :cpp:enumerator:`LV_COLOR_FORMAT_XRGB8888`: 8 bit for each color channel and 8 bit placholder for the alpha cannel
If the color fotmat is set to non-native ``draw_ctx->buffer_convert``
function will be called before calling ``flush_cb`` to convert the
native color format to the desired, therfore rendering in non-native
formats has a negative effect on peroformance. Learn more about
``draw_ctx`` `here </porting/gpu>`__.
Its very important that draw buffer(s) should be large enough for both
the native format and the target color format. For example if
``LV_COLOR_DEPTH == 16`` and :cpp:enumerator:`LV_COLOR_FORMAT_XRGB8888` is selected
LVGL will choosoe the larger to figure out how many pixel can be
rendered at once. Therefore with :cpp:enumerator:`LV_DISP_RENDER_MODE_FULL` and the
larger pixel size needs to choosen.
:cpp:enumerator:`LV_DISP_RENDER_MODE_DIRECT` supports only the
:cpp:enumerator:`LV_COLOR_FORMAT_NATIVE` format.
Antialiasing
------------
:cpp:expr:`lv_disp_set_antialiasing(disp, true/false)` enables/disables the
antialiasing (edge smoothing) on the given display.
User data
---------
With :cpp:expr:`lv_disp_set_user_data(disp, p)` a pointer to a custom data can
be stored in display object.
Events
******
:cpp:expr:`lv_disp_add_event(disp, event_cb, LV_DISP_EVENT_..., user_data)` adds
an event handler to a display. The following events are sent:
- :cpp:enumerator:`LV_DISP_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`
varaible with the coordinates of the area to be invalidated. The ara can
be freely modified is needed to adopt it the specialrequirement of the
display. Usually needed with monoschrome displays to invalidate Nx8
lines at once.
- :cpp:enumerator:`LV_DISP_EVENT_RENDER_START`: Called when rendering starts.
- :cpp:enumerator:`LV_DISP_EVENT_RENDER_READY`: Called when rendering is ready
- :cpp:enumerator:`LV_DISP_EVENT_RESOLUTION_CHANGED`: Called when the resolution changes due
to :cpp:func:`lv_disp_set_resolution` or :cpp:func:`lv_disp_set_rotation`.
Other options
*************
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_hal_disp.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_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 :cpp:expr:`lv_disp_set_deafult(disp1)` to
select the display to refresh before :cpp:expr:`_lv_disp_refr_timer(NULL)`.
.. note:: that :cpp:func:`lv_timer_handler` and :cpp:func:`_lv_disp_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.
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
***

View File

@@ -1,164 +0,0 @@
# Display interface
To create a display for LVGL call `lv_disp_t * disp = lv_disp_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:
```c
void my_flush_cb(lv_disp_t * disp, const lv_area_t * area, lv_color_t * buf)
{
/*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.*/
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 LVGL that you are ready with the flushing and buf is not used anymore*/
lv_disp_flush_ready(disp);
}
```
Use `lv_disp_set_flush_cb(disp, my_flush_cb)` to set a new `flush_cb`.
`lv_disp_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 `lv_disp_flush_is_last(disp)`.
### Draw buffers
The draw buffers can be set with
`lv_disp_set_draw_buffers(disp, buf1, buf2, buf_size_px, render_mode);`
- `buf1` a bufer 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`
- `LV_DISP_RENDER_MODE_PARTIAL` Use the buffer(s) to render the screen is smaller parts. This way the buffers can be smaller then the display to save RAM. At least 1/10 sceen size buffer(s) are recommended. In `flush_cb` the rendered images needs to be copied to the given area of the display.
- `LV_DISP_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 ares 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 and always the changed areas will be redrawn.
- `LV_DISP_RENDER_MODE_FULL` The buffer can smaller or screen sized but LVGL will always redraw the whole screen even is 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 framebuffer (`color_p` parameter).
Example:
```c
static lv_color_t buf[LCD_HOR_RES * LCD_VER_RES / 10];
lv_disp_set_draw_buffers(disp, buf, NULL, sizeof(buf), LV_DISP_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 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 `lv_disp_set_res(disp, 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 resoltution and the offset of the active area can be set with `lv_disp_set_physical_res(disp, hor_res, ver_res);`and `lv_disp_set_offset(disp, x, y);`
### Rotation
LVGL supports rotation of the display in 90 degree increments. You can select whether you'd like software rotation or hardware rotation.
The orientation of the display can be changed with
`lv_disp_set_rotation(disp, LV_DISP_ROTATION_0/90/180/270, true/false)`.
LVGL will swap the horizontal and vertical resolutions internally according to the set degree. IF the last paramter is `true` LVGL will rotate the rendered image. If it's `false` the display driver should rotate the rendered image.
### Color format
Set the color format of the display. The default is `LV_COLOR_FORMAT_NATIVE` which means LVGL render with the follow formats dpeneding on `LV_COLOR_DEPTH`:
- `LV_COLOR_DEPTH 32` XRGB8888 (4 bytes/pixel)
- `LV_COLOR_DEPTH 24` RGB888 (3 bytes/pixel)
- `LV_COLOR_DEPTH 16` RGB565 (2 bytes/pixel)
- `LV_COLOR_DEPTH 8` L8 (1 bytes/pixel)
The `color_format` can be changed with `lv_disp_set_color_depth(disp, LV_COLOR_FORMAT_...)` to the following values:
- `LV_COLOR_FORMAT_NATIVE_ALPHA` Append an alpha byte to the native format resulting in A8L8, ARGB8565, ARGB8888 formats.
- `LV_COLOR_FORMAT_NATIVE_REVERSE` Reverse the byte order of the native format. Useful if the rendered image is sent to the disply via SPI and the display needs the bytes in the opposite order.
- `LV_COLOR_FORMAT_L8` Lightness only on 8 bit
- `LV_COLOR_FORMAT_A8` Alpha only on 8 bit
- `LV_COLOR_FORMAT_I8` Indexed (palette) 8 bit
- `LV_COLOR_FORMAT_A8L8` Lightness on 8 bit with 8 bit alpha
- `LV_COLOR_FORMAT_ARGB2222` ARGB with 2 bit for each channel
- `LV_COLOR_FORMAT_RGB565` 16 bit RGB565 format without alpha channel
- `LV_COLOR_FORMAT_ARGB8565` 16 bit RGB565 format and 8 bit alpha channel
- `LV_COLOR_FORMAT_ARGB1555` 5 bit for each color channel and 1 bit for alpha
- `LV_COLOR_FORMAT_ARGB4444` 4 bit for each channel
- `LV_COLOR_FORMAT_RGB888` 8 bit for each color channel with out alpha channel
- `LV_COLOR_FORMAT_ARGB8888` 8 bit for each channel
- `LV_COLOR_FORMAT_XRGB8888` 8 bit for each color channel and 8 bit placholder for the alpha cannel
If the color fotmat is set to non-native `draw_ctx->buffer_convert` function will be called before calling `flush_cb` to convert the native color format to the desired, therfore rendering in non-native formats has a negative effect on peroformance. Learn more about `draw_ctx` [here](/porting/gpu).
It's very important that draw buffer(s) should be large enough for both the native format and the target color format. For example if `LV_COLOR_DEPTH == 16` and `LV_COLOR_FORMAT_XRGB8888` is selected LVGL will choosoe the larger to figure out how many pixel can be rendered at once. Therefore with `LV_DISP_RENDER_MODE_FULL` and the larger pixel size needs to choosen.
`LV_DISP_RENDER_MODE_DIRECT` supports only the `LV_COLOR_FORMAT_NATIVE` format.
### Antialiasing
`lv_disp_set_antialiasing(disp, true/false)` enables/disables the antialiasing (edge smoothing) on the given display.
### User data
With `lv_disp_set_user_data(disp, p)` a pointer to a custom data can be stored in display object.
## Events
`lv_disp_add_event(disp, event_cb, LV_DISP_EVENT_..., user_data)` adds an event handler to a display.
The following events are sent:
- `LV_DISP_EVENT_INVALIDATE_AREA` An area is invalidated (marked for redraw). `lv_event_get_param(e)` returns a pointer to an `lv_area_t` varaible with the coordinates of the area to be invalidated. The ara can be freely modified is needed to adopt it the specialrequirement of the display. Usually needed with monoschrome displays to invalidate Nx8 lines at once.
- `LV_DISP_EVENT_RENDER_START` Called when rendering starts.
- `LV_DISP_EVENT_RENDER_READY` Called when rendering is ready
- `LV_DISP_EVENT_RESOLUTION_CHANGED` CAlled when the resolution changes due to `lv_disp_set_resolution()` or `lv_disp_set_rotation()`.
## Other options
### Decoupling the display refresh timer
Normally the dirty (a.k.a invalid) areas are checked and redrawn in every `LV_DEF_REFR_PERIOD` milliseconds (set in `lv_hal_disp.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_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_disp.h
:project: lvgl
```

252
docs/porting/draw.rst Normal file
View File

@@ -0,0 +1,252 @@
==============
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 :cpp:type:`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
------
:cpp:type:`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 LVGLs 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`` its called
:cpp:struct:`lv_draw_rect_dsc_t`,
for :cpp:func:`lv_draw_line` its called :cpp:struct:`lv_draw_line_dsc_t`,
etc.
To correctly render according to a ``draw_dsc`` you need to be familiar
with the `Boxing model </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 </overview/style-props.html>`__.
Initialization
--------------
The :cpp:type:`lv_disp_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_t * disp_drv, lv_draw_ctx_t * draw_ctx)`` Callback to initialize a ``draw_ctx``
- ``void (*draw_ctx_deinit)(struct _lv_disp_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. :cpp:expr:`sizeof(lv_draw_sw_ctx_t)`
When you ignore these fields, LVGL will set default values for callbacks
and size in :cpp:func:`lv_disp_drv_init` based on the configuration in
``lv_conf.h``. :cpp:func:`lv_disp_drv_register` will allocate a ``draw_ctx``
based on ``draw_ctx_size`` and call :cpp:func:`draw_ctx_init` on it.
However, you can overwrite the callbacks and the size values before
calling :cpp:func:`lv_disp_drv_register`. It makes it possible to use your own
``draw_ctx`` with your own callbacks.
Software renderer
*****************
LVGLs built in software renderer extends the basic :cpp:type:`lv_draw_ctx_t`
structure and sets the draw callbacks. It looks like this:
.. code:: 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 :cpp:func:`draw_ctx_init` like:
.. code:: 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. Its 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 :cpp:struct:`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, its 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. :cpp:enumerator:`LV_BLEND_MODE_ADDITIVE`
Extend the software renderer
****************************
New blend callback
------------------
Lets 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 :cpp:struct:`lv_draw_sw_ctx_t`:
.. code:: 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_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 :cpp:expr:`lv_disp_draw_init(&drv)` you can assign the new
:cpp:func:`draw_ctx_init` callback and set ``draw_ctx_size`` to overwrite the
defaults:
.. code:: c
static lv_disp_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:
.. code:: 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:
.. code:: 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:
.. code:: 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 LVGLs SW renderer. In this case, you
need to base the renderer on the basic :cpp:type:`lv_draw_ctx_t` (instead of
:cpp:struct:`lv_draw_sw_ctx_t`) and extend/initialize it as you wish.
API
***

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_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_t * disp_drv, lv_draw_ctx_t * draw_ctx)` Callback to initialize a `draw_ctx`
- `void (*draw_ctx_deinit)(struct _lv_disp_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_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_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.

View File

@@ -1,199 +0,0 @@
# Input device interface
## Types of input devices
To create an input device use
```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:
- `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
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;
}
}
```
To set a mouse cursor use `lv_indev_set_cursor(indev, &img_cursor)`.
### 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 `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(indev, g)`
- Use `LV_KEY_...` to navigate among the objects in the group. See `lv_core/lv_group.h` for the available keys.
```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.
```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:
- `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_repeat_time`.
```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.
```
```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 = last_btn; /*Save the last button*/
}
```
## Other features
### Parameters
The default value of the following parameters can be changed in `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 `LV_EVENT_LONG_PRESSED` (in milliseconds)
- `long_press_repeat_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_DEF_REFR_PERIOD` in `lv_hal_disp.h` sets the default read period.
### Feedback
Besides `read_cb` a `feedback_cb` callback can be also specified in `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 `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_indev.h
:project: lvgl
```

239
docs/porting/indev.rst Normal file
View File

@@ -0,0 +1,239 @@
======================
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 `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.
.. 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;
}
}
To set a mouse cursor use :cpp:expr:`lv_indev_set_cursor(indev, &img_cursor)`.
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 = last_btn; /*Save the last button*/
}
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_hal_disp.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_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
***

View File

@@ -1,20 +0,0 @@
# Porting
```eval_rst
.. toctree::
:maxdepth: 2
project
display
indev
tick
timer-handler
sleep
os
log
gpu
```

19
docs/porting/index.rst Normal file
View File

@@ -0,0 +1,19 @@
.. _porting:
=======
Porting
=======
.. toctree::
:maxdepth: 2
project
disp
indev
tick
timer_handler
sleep
os
log
draw

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(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.

71
docs/porting/log.rst Normal file
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 didnt 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 cant 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
***

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`).

65
docs/porting/os.rst Normal file
View File

@@ -0,0 +1,65 @@
.. _os_interrupt:
===============================
Operating system and interrupts
===============================
LVGL is **not thread-safe** by default.
However, in the following conditions its valid to call LVGL related
functions: - In *events*. Learn more in :ref:`events`. -
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 :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) {
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
:cpp:func:`lv_tick_inc` and :cpp:func:`lv_disp_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.
Its 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`).

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) 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).

121
docs/porting/project.rst Normal file
View File

@@ -0,0 +1,121 @@
================
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 ``.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:
.. 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 </get-started/platforms/cmake>`__.
Other platforms and tools
~~~~~~~~~~~~~~~~~~~~~~~~~
The `Get started </get-started/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
modify this header to set the librarys 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 :c:macro:`LV_COLOR_DEPTH` according to your displays 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_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 :c:macro:`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 :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>`__.

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_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
docs/porting/sleep.rst Normal file
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_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:
.. 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_disp_get_inactive_time` you can check
:cpp:func:`lv_anim_count_running` to see if all animations have finished.

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
```

35
docs/porting/tick.rst Normal file
View File

@@ -0,0 +1,35 @@
.. _tick:
==============
Tick interface
==============
LVGL needs a system tick to know elapsed time for animations and other
tasks.
You need to call the :cpp:expr:`lv_tick_inc(tick_period)` function periodically
and provide the call period in milliseconds. For example,
:cpp:expr:`lv_tick_inc(1)` when calling every millisecond.
:cpp:func:`lv_tick_inc` should be called in a higher priority routine than
:cpp:func:`lv_task_handler` (e.g. in an interrupt) to precisely know the
elapsed milliseconds even if the execution of :cpp:func:`lv_task_handler` takes
more time.
With FreeRTOS :cpp:func:`lv_tick_inc` can be called in ``vApplicationTickHook``.
On Linux based operating systems (e.g. on Raspberry Pi) :cpp:func:`lv_tick_inc`
can be called in a thread like below:
.. code:: 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
---

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.

View File

@@ -0,0 +1,52 @@
.. _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
The timing is not critical but it should be about 5 milliseconds to keep
the system responsive.
Example:
.. code:: c
while(1) {
lv_timer_handler();
my_delay_ms(5);
}
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 */
...
}
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) {
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.
API
***