init Files
This commit is contained in:
322
libraries/lvgl/docs/porting/display.rst
Normal file
322
libraries/lvgl/docs/porting/display.rst
Normal file
@@ -0,0 +1,322 @@
|
||||
.. _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, uint8_t * 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``.
|
||||
|
||||
:cpp:expr:`lv_display_rotate_area(display, &area)` rotates the rendered area
|
||||
according to the current rotation settings of the display.
|
||||
|
||||
Note that in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` the small changed areas
|
||||
are rendered directly in the frame buffer so they cannot be
|
||||
rotated later. Therefore in direct mode only the whole frame buffer can be rotated.
|
||||
The same is true for :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_FULL`.
|
||||
|
||||
In the case of :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` the small rendered areas
|
||||
can be rotated on their own before flushing to the frame buffer.
|
||||
|
||||
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)
|
||||
- :c:macro:`LV_COLOR_DEPTH` ``1``: I1 (1 bit/pixel) Only support for horizontal mapped buffers. See :refr:`monochrome` for more details:
|
||||
|
||||
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``.
|
||||
|
||||
.. _monochrome:
|
||||
|
||||
Monochrome Displays
|
||||
-------------------
|
||||
|
||||
LVGL supports rendering directly in a 1-bit format for monochrome displays.
|
||||
To enable it, set ``LV_COLOR_DEPTH 1`` or use :cpp:expr:`lv_display_set_color_format(display, LV_COLOR_FORMAT_I1)`.
|
||||
|
||||
The :cpp:expr:`LV_COLOR_FORMAT_I1` format assumes that bytes are mapped to rows (i.e., the bits of a byte are written next to each other).
|
||||
The order of bits is MSB first, which means:
|
||||
|
||||
.. code::
|
||||
|
||||
MSB LSB
|
||||
bits 7 6 5 4 3 2 1 0
|
||||
pixels 0 1 2 3 4 5 6 7
|
||||
Left Right
|
||||
|
||||
Ensure that the LCD controller is configured accordingly.
|
||||
|
||||
Internally, LVGL rounds the redrawn areas to byte boundaries. Therefore, updated areas will:
|
||||
|
||||
- Start on an ``Nx8`` coordinate.
|
||||
- End on an ``Nx8 - 1`` coordinate.
|
||||
|
||||
When setting up the buffers for rendering (:cpp:func:`lv_display_set_buffers`), make the buffer 8 bytes larger.
|
||||
This is necessary because LVGL reserves 2 x 4 bytes in the buffer, as these are assumed to be used as a palette.
|
||||
|
||||
To skip the palette, include the following line in your ``flush_cb`` function: ``px_map += 8``.
|
||||
|
||||
As usual, monochrome displays support partial, full, and direct rendering modes as well.
|
||||
In full and direct modes, the buffer size should be large enough for the whole screen, meaning ``(horizontal_resolution x vertical_resolution / 8) + 8`` bytes.
|
||||
As LVGL can not handle fractional width make sure to round the horizontal resolution to 8-
|
||||
(For example 90 to 96)
|
||||
|
||||
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` cannot 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
|
||||
:c: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
libraries/lvgl/docs/porting/draw.rst
Normal file
77
libraries/lvgl/docs/porting/draw.rst
Normal 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 another 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
|
||||
***
|
||||
|
||||
303
libraries/lvgl/docs/porting/indev.rst
Normal file
303
libraries/lvgl/docs/porting/indev.rst
Normal 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_sensitivity` 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` cannot 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
|
||||
***
|
||||
19
libraries/lvgl/docs/porting/index.rst
Normal file
19
libraries/lvgl/docs/porting/index.rst
Normal file
@@ -0,0 +1,19 @@
|
||||
.. _porting:
|
||||
|
||||
=======
|
||||
Porting
|
||||
=======
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
project
|
||||
display
|
||||
indev
|
||||
tick
|
||||
timer_handler
|
||||
sleep
|
||||
os
|
||||
log
|
||||
draw
|
||||
71
libraries/lvgl/docs/porting/log.rst
Normal file
71
libraries/lvgl/docs/porting/log.rst
Normal 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
|
||||
***
|
||||
75
libraries/lvgl/docs/porting/os.rst
Normal file
75
libraries/lvgl/docs/porting/os.rst
Normal file
@@ -0,0 +1,75 @@
|
||||
.. _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.
|
||||
|
||||
LVGL has a built-in mutex which can be used with:
|
||||
- :cpp:func:`lv_lock()` and :cpp:func:`lv_lock_isr()`
|
||||
- :cpp:func:`lv_unlock()`
|
||||
|
||||
These functions are called internally in :cpp:func:`lv_timer_handler`
|
||||
and the users need to call them only from their own threads.
|
||||
|
||||
To enable ``lv_lock/lv_unlock`` ``LV_USE_OS`` needs to be set to other
|
||||
than ``LV_OS_NONE``.
|
||||
|
||||
|
||||
Here is some pseudocode to illustrate the concept:
|
||||
|
||||
.. code:: c
|
||||
|
||||
void lvgl_thread(void)
|
||||
{
|
||||
while(1) {
|
||||
uint32_t time_till_next;
|
||||
time_till_next = lv_timer_handler(); /*lv_lock/lv_unlock is called internally*/
|
||||
thread_sleep(time_till_next); /* sleep for a while */
|
||||
}
|
||||
}
|
||||
|
||||
void other_thread(void)
|
||||
{
|
||||
/* You must always hold the mutex while using LVGL APIs */
|
||||
lv_lock();
|
||||
lv_obj_t *img = lv_image_create(lv_screen_active());
|
||||
lv_unlock();
|
||||
|
||||
while(1) {
|
||||
lv_lock();
|
||||
/* change to the next image */
|
||||
lv_image_set_src(img, next_image);
|
||||
lv_unlock();
|
||||
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`).
|
||||
139
libraries/lvgl/docs/porting/project.rst
Normal file
139
libraries/lvgl/docs/porting/project.rst
Normal file
@@ -0,0 +1,139 @@
|
||||
================
|
||||
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 </integration/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 this define
|
||||
is set :c:macro:`LV_CONF_SKIP` is assumed to be ``0``.
|
||||
|
||||
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>`__.
|
||||
33
libraries/lvgl/docs/porting/sleep.rst
Normal file
33
libraries/lvgl/docs/porting/sleep.rst
Normal 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_timer_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_timer_handler(); /*Call `lv_timer_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.
|
||||
33
libraries/lvgl/docs/porting/tick.rst
Normal file
33
libraries/lvgl/docs/porting/tick.rst
Normal 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(my_tick_get_cb);``, where ``my_tick_get_cb`` is: ``static uint32_t my_tick_get_cb(void) { return 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
|
||||
---
|
||||
59
libraries/lvgl/docs/porting/timer_handler.rst
Normal file
59
libraries/lvgl/docs/porting/timer_handler.rst
Normal 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
|
||||
***
|
||||
Reference in New Issue
Block a user