mirror of
https://github.com/lvgl/lvgl.git
synced 2026-05-20 12:32:18 +08:00
docs(directory structure): simplify by removing details/ and intro/ dirs (#9313)
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
.. _gdb_plugin:
|
||||
|
||||
===========
|
||||
GDB Plug-In
|
||||
===========
|
||||
|
||||
Debugging LVGL with GDB
|
||||
***********************
|
||||
|
||||
To facilitate debugging LVGL with GDB, a GDB plugin is provided. This plugin
|
||||
can be found in the ``lvgl/scripts/gdb`` directory. The GDB plugin can be used
|
||||
with any target where GDB is available. For example, you can use it to debug a
|
||||
device connected to a PC via JLink, which provides a GDB server. Additionally,
|
||||
if your device crashes and you have a core dump, you can use GDB to analyze the
|
||||
core dump. To load the LVGL GDB plugin within GDB's command line, type the
|
||||
following command:
|
||||
|
||||
``source lvgl/scripts/gdb/gdbinit.py``
|
||||
|
||||
|
||||
Example of usage:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
(gdb) source lvgl/scripts/gdb/gdbinit.py
|
||||
|
||||
(gdb) dump obj -L 2
|
||||
obj@0x60700000dd10 (0,0,799,599)
|
||||
tabview@0x608000204ca0 (0,0,799,599)
|
||||
obj@0x607000025da0 (0,0,799,69)
|
||||
obj@0x607000025e80 (0,70,799,599)
|
||||
obj@0x60700002bd70 (743,543,791,591)
|
||||
btn@0x60700002c7f0 (747,547,787,587)
|
||||
keyboard@0x60d0000f7040 (0,300,799,599)
|
||||
dropdown-list@0x608000205420 (0,0,129,129)
|
||||
label@0x60d0000f7ba0 (22,22,56,39)
|
||||
(gdb)
|
||||
|
||||
The plugin provides the following commands.
|
||||
|
||||
- ``dump obj``: Dump the object tree.
|
||||
- ``info style``: Show the object's style.
|
||||
- ``info draw_unit``: Display all current drawing unit information.
|
||||
|
||||
.. note::
|
||||
|
||||
Some versions of ``gdb`` on Windows (e.g. those delivered with various versions
|
||||
of Perl) are compiled without Python support, so the ``source`` command will not
|
||||
be supported.
|
||||
|
||||
|
||||
|
||||
Dump Obj Tree
|
||||
*************
|
||||
|
||||
``dump obj``: Dump the object tree.
|
||||
|
||||
``dump obj -L 2``: Dump the object tree with a depth of 2.
|
||||
|
||||
``dump obj -a 0x60700000dd10``: Dump the object tree starting from the specified address.
|
||||
|
||||
|
||||
|
||||
Show Obj's Style
|
||||
****************
|
||||
|
||||
This command can dump the object's local style, since style value is a union, it's
|
||||
displayed in all possible formats.
|
||||
|
||||
``info style address_of_obj``: Show the object's style.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
(gdb) info style 0x60700000dd10
|
||||
32 = {num = 90, ptr = 0x5a, color = {blue = 90 'Z', green = 0 '\000', red = 0 '\000'}}
|
||||
158 = {num = 32767, ptr = 0x7fff, color = {blue = 255 '\377', green = 127 '\177', red = 0 '\000'}}
|
||||
(gdb) p lv_global->disp_default->act_scr
|
||||
$4 = (lv_obj_t *) 0x60700000dd10
|
||||
(gdb) info style $4
|
||||
32 = {num = 90, ptr = 0x5a, color = {blue = 90 'Z', green = 0 '\000', red = 0 '\000'}}
|
||||
158 = {num = 32767, ptr = 0x7fff, color = {blue = 255 '\377', green = 127 '\177', red = 0 '\000'}}
|
||||
(gdb)
|
||||
|
||||
|
||||
|
||||
Connecting to a Debugger
|
||||
************************
|
||||
|
||||
This command provides the ability to connect and debug GDB Python Script using IDE.
|
||||
|
||||
Connect to ``PyCharm`` / ``VSCode`` / ``Eclipse (not supported yet)``
|
||||
|
||||
``debugger -t pycharm``
|
||||
|
||||
``debugger -t vscode``
|
||||
|
||||
``debugger -t eclipse``
|
||||
|
||||
Perform a web search for ``pydevd_pycharm`` or ``debugpy`` for details about how to
|
||||
use the debugger.
|
||||
@@ -0,0 +1,20 @@
|
||||
.. _debugging:
|
||||
|
||||
=========
|
||||
Debugging
|
||||
=========
|
||||
|
||||
LVGL comes packaged with various tools that can help you debug problems you may
|
||||
encounter during development and testing.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
gdb_plugin
|
||||
log
|
||||
monkey
|
||||
obj_id
|
||||
profiler
|
||||
sysmon
|
||||
test
|
||||
vg_lite_tvg
|
||||
@@ -0,0 +1,89 @@
|
||||
.. _logging:
|
||||
|
||||
=======
|
||||
Logging
|
||||
=======
|
||||
|
||||
LVGL has a built-in *Logging* module to inform the user about what is
|
||||
happening in the library.
|
||||
|
||||
|
||||
|
||||
Configuring Logging
|
||||
*******************
|
||||
|
||||
|
||||
Log Level
|
||||
---------
|
||||
|
||||
To enable logging, set :c:macro:`LV_USE_LOG` in ``lv_conf.h`` to a non-zero value and
|
||||
set :c:macro:`LV_LOG_LEVEL` to one of the following values. They are prioritized as
|
||||
follows (from most to least verbose):
|
||||
|
||||
- :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`: Log only critical issues, where the system may fail.
|
||||
- :c:macro:`LV_LOG_LEVEL_USER`: Log only custom log messages added by the user.
|
||||
- :c:macro:`LV_LOG_LEVEL_NONE`: Do not log anything.
|
||||
|
||||
When you set :c:macro:`LV_LOG_LEVEL` to a certain level, only messages with that level
|
||||
or higher priority (less verbose) will be logged.
|
||||
|
||||
Example: you set :c:macro:`LV_LOG_LEVEL` to :c:macro:`LV_LOG_LEVEL_WARN`, then
|
||||
:c:macro:`LV_LOG_LEVEL_WARN`, :c:macro:`LV_LOG_LEVEL_ERROR` and
|
||||
:c:macro:`LV_LOG_LEVEL_USER` messages will be logged.
|
||||
|
||||
|
||||
Log Output
|
||||
----------
|
||||
|
||||
If your system supports ``printf``, you just need to enable
|
||||
:c:macro:`LV_LOG_PRINTF` in ``lv_conf.h`` to output log messages with ``printf``.
|
||||
|
||||
If you can't use ``printf`` or want to use a custom function to log, you
|
||||
can register a "logging" function with :cpp:func:`lv_log_register_print_cb`.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: 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);
|
||||
|
||||
|
||||
|
||||
Using Logging
|
||||
*************
|
||||
|
||||
You use the log module via the following macros:
|
||||
|
||||
- ``LV_LOG_TRACE(text)``
|
||||
- ``LV_LOG_INFO(text)``
|
||||
- ``LV_LOG_WARN(text)``
|
||||
- ``LV_LOG_ERROR(text)``
|
||||
- ``LV_LOG_USER(text)``
|
||||
- ``LV_LOG(text)``
|
||||
|
||||
The first 5 macros append the following information to your ``text``:
|
||||
|
||||
- Log Level name ("Trace", "Info", "Warn", "Error", "User")
|
||||
- \__FILE\_\_
|
||||
- \__LINE\_\_
|
||||
- \__func\_\_
|
||||
|
||||
``LV_LOG(text)`` is similar to ``LV_LOG_USER`` but has no extra information added.
|
||||
|
||||
|
||||
|
||||
API
|
||||
***
|
||||
|
||||
.. API equals: lv_log_register_print_cb
|
||||
@@ -0,0 +1,63 @@
|
||||
.. _monkey:
|
||||
|
||||
======
|
||||
Monkey
|
||||
======
|
||||
|
||||
The Monkey module provides LVGL applications with a simple monkey test. Monkey
|
||||
Testing is a technique where the user tests the application or system by providing
|
||||
random inputs and checking the behavior or seeing whether the application or system
|
||||
will crash. This module provides this service as simulated random input to stress
|
||||
test an LVGL application.
|
||||
|
||||
|
||||
|
||||
.. _monkey_usage:
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
First, enable :c:macro:`LV_USE_MONKEY` in ``lv_conf.h``.
|
||||
|
||||
Next, declare a variable (it can be local) of type :cpp:type:`lv_monkey_config_t` to
|
||||
define the configuration structure, initialize it using
|
||||
:cpp:expr:`lv_monkey_config_init(cfg)` then set its ``type`` member to the desired
|
||||
type of :ref:`input device <indev>`, and set the ``min`` and ``max`` values for its
|
||||
``period_range`` and ``input_range`` members to set the time ranges (in milliseconds)
|
||||
and input ranges the Monkey module will use to generate random input at random times.
|
||||
|
||||
Next, call :cpp:expr:`lv_monkey_create(cfg)` to create the Monkey. It returns a
|
||||
pointer to the ``lv_monkey_t`` created.
|
||||
|
||||
Finally call :cpp:expr:`lv_monkey_set_enable(monkey, true)` to enable Monkey.
|
||||
|
||||
If you want to pause the monkey, call
|
||||
:cpp:expr:`lv_monkey_set_enable(monkey, false)`. To delete it, call
|
||||
:cpp:expr:`lv_monkey_delete(monkey)`.
|
||||
|
||||
Note that ``input_range`` has different meanings depending on the ``type`` input device:
|
||||
|
||||
- :cpp:enumerator:`LV_INDEV_TYPE_POINTER`: No effect, click randomly within the pixels of the screen resolution.
|
||||
- :cpp:enumerator:`LV_INDEV_TYPE_ENCODER`: The minimum and maximum values of ``enc_diff``.
|
||||
- :cpp:enumerator:`LV_INDEV_TYPE_BUTTON`: The minimum and maximum values of ``btn_id``.
|
||||
Use :cpp:func:`lv_monkey_get_indev` to get the input device, and use
|
||||
:cpp:func:`lv_indev_set_button_points` to map the key ID to the coordinates.
|
||||
- :cpp:enumerator:`LV_INDEV_TYPE_KEYPAD`: No effect, Send random :ref:`indev_keys`.
|
||||
|
||||
|
||||
|
||||
.. _monkey_example:
|
||||
|
||||
Example
|
||||
*******
|
||||
|
||||
.. include:: /examples/others/monkey/index.rst
|
||||
|
||||
|
||||
|
||||
.. _monkey_api:
|
||||
|
||||
API
|
||||
***
|
||||
|
||||
.. API equals: lv_monkey_create
|
||||
@@ -0,0 +1,197 @@
|
||||
.. _widget_id:
|
||||
|
||||
=========
|
||||
Widget ID
|
||||
=========
|
||||
|
||||
Widgets can optionally have identifiers added to their functionality if needed for
|
||||
the application. Exactly how that happens is designed to be flexible, and can morph
|
||||
with the needs of the application. It can even be a timestamp or other data current
|
||||
at the time the Widget was created.
|
||||
|
||||
|
||||
|
||||
.. _widget_id_usage:
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
Enable Widget ID functionality by setting :c:macro:`LV_USE_OBJ_ID` to ``1`` in ``lv_conf.h``.
|
||||
|
||||
Once enabled, several things change:
|
||||
|
||||
- each Widget will now have a ``void *`` field called ``id``;
|
||||
- these two API functions become available:
|
||||
|
||||
- :cpp:expr:`lv_obj_get_id(widget)`,
|
||||
- :cpp:expr:`lv_obj_find_by_id(widget, id)`;
|
||||
|
||||
- several more Widget-ID-related API functions become available if
|
||||
:c:macro:`LV_USE_OBJ_ID_BUILTIN` is non-zero (more on this below);
|
||||
- two additional configuration macros both :c:macro:`LV_OBJ_ID_AUTO_ASSIGN` and
|
||||
:c:macro:`LV_USE_OBJ_ID_BUILTIN` now have meaning.
|
||||
|
||||
|
||||
:c:macro:`LV_OBJ_ID_AUTO_ASSIGN`
|
||||
--------------------------------
|
||||
|
||||
This macro in ``lv_conf.h`` defaults to whatever value :c:macro:`LV_USE_OBJ_ID`
|
||||
equates to. You can change this if you wish. Either way, if it equates to a
|
||||
non-zero value, it causes two things to happen:
|
||||
|
||||
- :cpp:expr:`lv_obj_assign_id(class_p, widget)` will be called at the end of each
|
||||
Widget's creation, and
|
||||
- :cpp:expr:`lv_obj_free_id(widget)` will be called at the end of the sequence when
|
||||
each Widget is deleted.
|
||||
|
||||
Because of this timing, custom versions of these functions can be used according to
|
||||
the below, and they can even be used like "event hooks" to implement a trace
|
||||
operation that occurs when each Widget is created and deleted.
|
||||
|
||||
:cpp:expr:`lv_obj_assign_id(class_p, widget)`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This function (whether provided by LVGL or by you --- more on this below) is
|
||||
responsible for assigning a value to the Widget's ``id`` field, and possibly do
|
||||
other things, depending on the implementation.
|
||||
|
||||
:cpp:expr:`lv_obj_free_id(widget)`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This function (whether provided by LVGL or by you --- more on this below) is
|
||||
responsible for doing the clean-up of any resources allocated by
|
||||
:cpp:func:`lv_obj_assign_id`
|
||||
|
||||
|
||||
:c:macro:`LV_USE_OBJ_ID_BUILTIN`
|
||||
--------------------------------
|
||||
|
||||
This macro in ``lv_conf.h`` equates to ``1`` by default. You can change this if you
|
||||
wish. When it equates to a non-zero value the following function implementations are
|
||||
provided by LVGL:
|
||||
|
||||
- :cpp:expr:`lv_obj_assign_id(class_p, widget)`
|
||||
- :cpp:expr:`lv_obj_free_id(widget)`
|
||||
- :cpp:expr:`lv_obj_set_id(widget, id)`
|
||||
- :cpp:expr:`lv_obj_stringify_id(widget, buf, len)`
|
||||
- :cpp:expr:`lv_obj_id_compare(id1, id2)`
|
||||
|
||||
These supply the default implementation for Widget IDs, namely that for each Widget
|
||||
created, :cpp:expr:`lv_obj_stringify_id(widget, buf, len)` will produce a unique
|
||||
string for it. Example: if the following 6 Widgets are created in this sequence:
|
||||
|
||||
- Screen
|
||||
- Label
|
||||
- Button
|
||||
- Label
|
||||
- Label
|
||||
- Image
|
||||
|
||||
the strings produced by :cpp:expr:`lv_obj_stringify_id(widget, buf, len)` would be
|
||||
|
||||
- obj1
|
||||
- label1
|
||||
- btn1
|
||||
- label2
|
||||
- label3
|
||||
- image1
|
||||
|
||||
respectively.
|
||||
|
||||
|
||||
.. _widget_id_custom_generator:
|
||||
|
||||
Using a custom ID generator
|
||||
---------------------------
|
||||
|
||||
If you wish, you can provide custom implementations for several Widget-ID related
|
||||
functions. You do this by first setting :c:macro:`LV_USE_OBJ_ID_BUILTIN` to `0` in
|
||||
``lv_conf.h``.
|
||||
|
||||
You will then need to provide implementations for the following functions (and link
|
||||
them with LVGL):
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
const char * lv_obj_stringify_id(lv_obj_t * widget, char * buf, uint32_t len);
|
||||
int lv_obj_id_compare(const void * id1, const void * id2);
|
||||
|
||||
If :c:macro:`LV_OBJ_ID_AUTO_ASSIGN` equates to a non-zero value (or if you otherwise
|
||||
simply need to use them), you will also need to provide implementations for:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void lv_obj_assign_id(const lv_obj_class_t * class_p, lv_obj_t * widget);
|
||||
void lv_obj_free_id(lv_obj_t * widget);
|
||||
|
||||
If :c:macro:`LV_BUILD_TEST` equates to a non-zero value and you are including LVGL
|
||||
test code in your compile (or if you otherwise simply need to use them), you
|
||||
will also need to provide an implementation for:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void lv_obj_set_id(lv_obj_t * widget, void * id);
|
||||
|
||||
|
||||
Examples of implementations of these functions exist in ``lv_obj_id_builtin.c``, but
|
||||
you are free to use a different design if needed.
|
||||
|
||||
:cpp:func:`lv_obj_stringify_id` converts the passed ``widget`` to a string
|
||||
representation (typically incorporating the ``id`` field) and writes it into the
|
||||
buffer provided in its ``buf`` argument.
|
||||
|
||||
:cpp:func:`lv_obj_id_compare` compares 2 ``void * id`` values and returns ``0`` when
|
||||
they are considered equal, and non-zero otherwise.
|
||||
|
||||
If :c:macro:`LV_OBJ_ID_AUTO_ASSIGN` equates to a non-zero value,
|
||||
:cpp:func:`lv_obj_assign_id` is called when a Widget is created. It is responsible
|
||||
for assigning a value to the Widget's ``id`` field. A pointer to the Widget's final
|
||||
class is passed in its ``class_p`` argument in case it is needed for determining the
|
||||
value for the ``id`` field, or for other possible needs related to your design for
|
||||
Widget IDs. Note that this pointer may be different than :cpp:expr:`widget->class_p`
|
||||
which is the class of the Widget currently being created.
|
||||
|
||||
If :c:macro:`LV_OBJ_ID_AUTO_ASSIGN` equates to a non-zero value,
|
||||
:cpp:func:`lv_obj_free_id` is called when a Widget is deleted. It needs to perform
|
||||
the clean-up for any resources allocated by :cpp:func:`lv_obj_assign_id`.
|
||||
|
||||
|
||||
Dumping a Widget Tree
|
||||
---------------------
|
||||
|
||||
Regardless of the state of any of the above macros, the function
|
||||
:cpp:expr:`lv_obj_dump_tree(widget)` provides a "dump" of the Widget Tree for the
|
||||
specified Widget (that Widget plus all its children recursively) using the
|
||||
currently-configured method used by the :c:macro:`LV_LOG_USER` macro. If NULL is
|
||||
passed instead of a pointer to a "root" Widget, the dump will include the Widget Tree
|
||||
for all :ref:`Screens`, for all :ref:`Displays <display>` in the system.
|
||||
|
||||
For :c:macro:`LV_LOG_USER` to produce output, the following needs to be true in
|
||||
``lv_conf.h``:
|
||||
|
||||
- :c:macro:`LV_USE_LOG` must equate to a non-zero value
|
||||
- :c:macro:`LV_LOG_LEVEL` <= :c:macro:`LV_LOG_LEVEL_USER`
|
||||
|
||||
It will recursively walk through all that Widget's children (starting with the Widget
|
||||
itself) and print the Widget's parent's address, the Widget's address, and if
|
||||
:c:macro:`LV_USE_OBJ_ID` equates to a non-zero value, will also print the output of
|
||||
:cpp:func:`lv_obj_stringify_id` for that Widget.
|
||||
|
||||
This can be useful in the event of a UI crash. From that log you can examine the
|
||||
state of the Widget Tree when :cpp:expr:`lv_obj_dump_tree(widget)` was called.
|
||||
|
||||
For example, if a pointer to a deleted Widget is stored in a Timer's
|
||||
:cpp:expr:`timer->user_data` field when the timer event callback is called, attempted
|
||||
use of that pointer will likely cause a crash because the pointer is not valid any
|
||||
more. However, a timely dump of the Widget Tree right before that point will show
|
||||
that the Widget no longer exists.
|
||||
|
||||
|
||||
Find child by ID
|
||||
----------------
|
||||
|
||||
.. warning::
|
||||
``lv_obj_find_by_id(widget, id)`` is deprecated. To find a widget use ``obj_name``.
|
||||
|
||||
:cpp:expr:`lv_obj_find_by_id(widget, id)` will perform a recursive walk through
|
||||
``widget``\ 's children and return the first child encountered having the given ID.
|
||||
@@ -0,0 +1,230 @@
|
||||
.. _profiler:
|
||||
|
||||
========
|
||||
Profiler
|
||||
========
|
||||
|
||||
As the complexity of the application increases, performance issues such as low FPS and frequent cache misses
|
||||
causing lag may arise. LVGL has internally set up some hooks for performance measurement to help developers
|
||||
analyze and locate performance issues.
|
||||
|
||||
|
||||
|
||||
.. _profiler_introduction:
|
||||
|
||||
Introduction
|
||||
************
|
||||
|
||||
LVGL has a built-in trace system to track and record the timestamps of important events that occur during runtime,
|
||||
such as rendering events and user input events. These event timestamps serve in measurements taken in performance analysis.
|
||||
|
||||
The trace system has a configurable record buffer that stores the names of event functions and their timestamps.
|
||||
When the buffer is full, the trace system prints the log information through the provided user interface.
|
||||
|
||||
The output trace logs are formatted according to Android's `systrace <https://developer.android.com/topic/performance/tracing>`_
|
||||
format and can be visualized using `Perfetto <https://ui.perfetto.dev>`_.
|
||||
|
||||
|
||||
|
||||
.. _profiler_usage:
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
To enable the profiler, set :c:macro:`LV_USE_PROFILER` in ``lv_conf.h`` and configure the following options:
|
||||
|
||||
1. Enable the built-in profiler functionality by setting :c:macro:`LV_USE_PROFILER_BUILTIN`. If you have POSIX environment support, you can enable :c:macro:`LV_USE_PROFILER_BUILTIN_POSIX`.
|
||||
|
||||
2. Buffer configuration: Set the value of :c:macro:`LV_PROFILER_BUILTIN_BUF_SIZE` to configure the buffer size. A larger buffer can store more trace event information, reducing interference with rendering. However, it also results in higher memory consumption.
|
||||
|
||||
3. Timestamp configuration: LVGL uses the :cpp:func:`lv_tick_get` function with a precision of 1ms by default to obtain timestamps when events occur. Therefore, it cannot accurately measure intervals below 1ms. If your system environment can provide higher precision (e.g., 1us), you can configure the profiler as follows:
|
||||
|
||||
- Recommended configuration in **Arduino** environments:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static uint64_t my_get_tick_cb(void)
|
||||
{
|
||||
/* Use the microsecond time stamp provided by Arduino */
|
||||
return micros();
|
||||
}
|
||||
|
||||
void my_profiler_init(void)
|
||||
{
|
||||
lv_profiler_builtin_config_t config;
|
||||
lv_profiler_builtin_config_init(&config);
|
||||
config.tick_per_sec = 1000000; /* One second is equal to 1000000 microseconds */
|
||||
config.tick_get_cb = my_get_tick_cb;
|
||||
lv_profiler_builtin_init(&config);
|
||||
}
|
||||
|
||||
4. Log output configuration: LVGL uses the :cpp:func:`LV_LOG` interface by default to output trace information. If you want to use another interface to output log information (e.g. stdout), you can redirect the log output using the following code:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static void my_log_print_cb(const char * buf)
|
||||
{
|
||||
printf("%s", buf);
|
||||
}
|
||||
|
||||
void my_profiler_init(void)
|
||||
{
|
||||
lv_profiler_builtin_config_t config;
|
||||
lv_profiler_builtin_config_init(&config);
|
||||
... /* other configurations */
|
||||
config.flush_cb = my_log_print_cb;
|
||||
lv_profiler_builtin_init(&config);
|
||||
}
|
||||
|
||||
|
||||
Run the test scenario
|
||||
---------------------
|
||||
|
||||
Run the UI scenario that you want to measure, such as scrolling a scrollable page up and down or entering/exiting an application.
|
||||
|
||||
|
||||
Process the logs
|
||||
----------------
|
||||
|
||||
Save the output log as `my_trace.txt`, use `trace_filter.py` for filtering and preprocessing:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./lvgl/scripts/trace_filter.py my_trace.txt
|
||||
|
||||
or
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python3 ./lvgl/scripts/trace_filter.py my_trace.txt
|
||||
|
||||
You will obtain a processed text file named `trace.systrace`, which roughly contains the following content:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
# tracer: nop
|
||||
#
|
||||
LVGL-1 [0] 2892.002993: tracing_mark_write: B|1|lv_timer_handler
|
||||
LVGL-1 [0] 2892.002993: tracing_mark_write: B|1|_lv_display_refr_timer
|
||||
LVGL-1 [0] 2892.003459: tracing_mark_write: B|1|refr_invalid_areas
|
||||
LVGL-1 [0] 2892.003461: tracing_mark_write: B|1|lv_draw_rect
|
||||
LVGL-1 [0] 2892.003550: tracing_mark_write: E|1|lv_draw_rect
|
||||
LVGL-1 [0] 2892.003552: tracing_mark_write: B|1|lv_draw_rect
|
||||
LVGL-1 [0] 2892.003556: tracing_mark_write: E|1|lv_draw_rect
|
||||
LVGL-1 [0] 2892.003560: tracing_mark_write: B|1|lv_draw_rect
|
||||
LVGL-1 [0] 2892.003573: tracing_mark_write: E|1|lv_draw_rect
|
||||
...
|
||||
|
||||
Import the processed `trace.systrace` file into `Perfetto <https://ui.perfetto.dev>`_ and wait for it to be parsed.
|
||||
|
||||
|
||||
Performance analysis
|
||||
--------------------
|
||||
|
||||
If the log parsing is successful, you will see a screen similar to the following:
|
||||
|
||||
.. image:: /_static/images/perfetto_ui.png
|
||||
|
||||
In the Perfetto UI, use the :kbd:`A` or :kbd:`D` keys to pan the timeline horizontally
|
||||
and the :kbd:`W` or :kbd:`S` keys to zoom in or out on the timeline.
|
||||
Use the mouse to move the focus and click on functions on the timeline to observe their execution time.
|
||||
|
||||
|
||||
|
||||
Adding Measurement Points
|
||||
*************************
|
||||
|
||||
Users can add their own measured functions:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void my_function_1(void)
|
||||
{
|
||||
LV_PROFILER_BEGIN;
|
||||
do_something();
|
||||
LV_PROFILER_END;
|
||||
}
|
||||
|
||||
void my_function_2(void)
|
||||
{
|
||||
LV_PROFILER_BEGIN_TAG("do_something_1");
|
||||
do_something_1();
|
||||
LV_PROFILER_END_TAG("do_something_1");
|
||||
|
||||
LV_PROFILER_BEGIN_TAG("do_something_2");
|
||||
do_something_2();
|
||||
LV_PROFILER_END_TAG("do_something_2");
|
||||
}
|
||||
|
||||
|
||||
|
||||
.. _profiler_custom_implementation:
|
||||
|
||||
Custom Profiler Implementation
|
||||
******************************
|
||||
|
||||
If you wish to use a profiler method provided by your operating system, you can modify the following macros in ``lv_conf.h``:
|
||||
|
||||
- :c:macro:`LV_PROFILER_INCLUDE`: Provides a header file for the profiler function.
|
||||
- :c:macro:`LV_PROFILER_BEGIN`: Profiler start point function.
|
||||
- :c:macro:`LV_PROFILER_END`: Profiler end point function.
|
||||
- :c:macro:`LV_PROFILER_BEGIN_TAG`: Profiler start point function with custom tag.
|
||||
- :c:macro:`LV_PROFILER_END_TAG`: Profiler end point function with custom tag.
|
||||
|
||||
|
||||
Taking `NuttX <https://github.com/apache/nuttx>`_ RTOS as an example:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#define LV_PROFILER_INCLUDE "nuttx/sched_note.h"
|
||||
#define LV_PROFILER_BEGIN sched_note_begin(NOTE_TAG_ALWAYS)
|
||||
#define LV_PROFILER_END sched_note_end(NOTE_TAG_ALWAYS)
|
||||
#define LV_PROFILER_BEGIN_TAG(str) sched_note_beginex(NOTE_TAG_ALWAYS, str)
|
||||
#define LV_PROFILER_END_TAG(str) sched_note_endex(NOTE_TAG_ALWAYS, str)
|
||||
|
||||
|
||||
|
||||
.. _profiler_faq:
|
||||
|
||||
FAQ
|
||||
***
|
||||
|
||||
|
||||
Perfetto log parsing fails
|
||||
--------------------------
|
||||
|
||||
Please check the completeness of the logs. If the logs are incomplete, it may be due to the following reasons:
|
||||
|
||||
1. Serial port reception errors caused by a high baud rate. You need to reduce the baud rate.
|
||||
2. Data corruption caused by other thread logs inserted during the printing of trace logs. You need to disable the log output of other threads or refer to the configuration above to use a separate log output interface.
|
||||
3. Make sure that the string passed in by :c:macro:`LV_PROFILER_BEGIN_TAG` or :c:macro:`LV_PROFILER_END_TAG` is not a local variable on the stack or a string in shared memory, because currently only the string address is recorded and the content is not copied.
|
||||
|
||||
|
||||
Function execution time displayed as 0s in Perfetto
|
||||
---------------------------------------------------
|
||||
|
||||
If the function execution time is lower than the precision of the timestamps, this situation can occur. You can refer to the configuration instructions above to use a higher precision timestamp.
|
||||
See Configuration_ for an example of this.
|
||||
|
||||
|
||||
Significant stuttering occurs during profiling
|
||||
----------------------------------------------
|
||||
|
||||
When the buffer used to store trace events becomes full, the profiler will output all the data in the buffer, which can cause UI blocking and stuttering during the output. You can optimize this by taking the following measures:
|
||||
|
||||
1. Increase the value of :c:macro:`LV_PROFILER_BUILTIN_BUF_SIZE`. A larger buffer can reduce the frequency of log flushing, but it also consumes more memory.
|
||||
2. Optimize the execution time of log flushing functions, such as increasing the serial port baud rate or improving file writing speed.
|
||||
|
||||
|
||||
Trace logs are not being output
|
||||
-------------------------------
|
||||
|
||||
If the trace logs are not automatically printed when the buffer is not full, you can try the following methods to force the log output:
|
||||
|
||||
1. Reduce the value of :c:macro:`LV_PROFILER_BUILTIN_BUF_SIZE` to fill the buffer more quickly and trigger automatic printing.
|
||||
2. Manually call or use a timer to call the :cpp:func:`lv_profiler_builtin_flush` function to force the log to be flushed.
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
.. _sysmon:
|
||||
|
||||
=======================
|
||||
System Monitor (sysmon)
|
||||
=======================
|
||||
|
||||
The System Monitor module provides real-time monitoring of system performance
|
||||
metrics directly on your display. It supports both performance monitoring
|
||||
(CPU usage and FPS) and memory monitoring (used memory and fragmentation).
|
||||
|
||||
|
||||
|
||||
Dependencies
|
||||
************
|
||||
|
||||
- Requires ``LV_USE_LABEL = 1`` in lv_conf.h
|
||||
- Requires ``LV_USE_OBSERVER = 1`` in lv_conf.h
|
||||
- Requires ``LV_USE_SYSMON = 1`` in lv_conf.h
|
||||
|
||||
|
||||
|
||||
.. _sysmon_usage:
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
|
||||
Configuration
|
||||
--------------
|
||||
|
||||
Enable in ``lv_conf.h``:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* Main sysmon enable */
|
||||
#define LV_USE_SYSMON 1
|
||||
|
||||
/* Performance monitor (CPU% and FPS) */
|
||||
#define LV_USE_PERF_MONITOR 1
|
||||
|
||||
/* Memory monitor (used + fragmentation) */
|
||||
#define LV_USE_MEM_MONITOR 1
|
||||
|
||||
/* Optional: refresh period in ms */
|
||||
#define LV_SYSMON_REFR_PERIOD_DEF 300
|
||||
|
||||
/* Optional: log to console instead of screen */
|
||||
#define LV_USE_PERF_MONITOR_LOG_MODE 0
|
||||
|
||||
|
||||
Creating Monitors
|
||||
-----------------
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* Create generic monitor */
|
||||
lv_obj_t * sysmon = lv_sysmon_create(lv_display_get_default());
|
||||
|
||||
/* Create performance monitor */
|
||||
lv_sysmon_show_performance(NULL); /* NULL = default display */
|
||||
|
||||
/* Create memory monitor */
|
||||
lv_sysmon_show_memory(NULL);
|
||||
|
||||
|
||||
Performance Monitor
|
||||
-------------------
|
||||
|
||||
Tracks:
|
||||
|
||||
- FPS (Frames Per Second)
|
||||
- CPU usage (%)
|
||||
- Render time (ms)
|
||||
- Flush time (ms)
|
||||
- Self CPU usage (%) if enabled
|
||||
|
||||
Display format:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
32 FPS, 45% CPU
|
||||
8 ms
|
||||
|
||||
Where:
|
||||
|
||||
- Line 1: FPS, Total CPU%
|
||||
- Line 2: Total time (Render | Flush)
|
||||
|
||||
|
||||
|
||||
Pause and Resume
|
||||
****************
|
||||
|
||||
:cpp:expr:`lv_sysmon_performance_pause(disp)` pauses the perf monitor.
|
||||
|
||||
:cpp:expr:`lv_sysmon_performance_resume(disp)` resumes the perf monitor.
|
||||
|
||||
|
||||
Memory Monitor
|
||||
--------------
|
||||
|
||||
Displays:
|
||||
|
||||
- Current memory usage (kB and %)
|
||||
- Peak memory usage (kB)
|
||||
- Fragmentation (%)
|
||||
|
||||
Display format:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
24.8 kB (76%)
|
||||
32.4 kB max, 18% frag.
|
||||
|
||||
|
||||
Positioning
|
||||
-----------
|
||||
|
||||
Configure positions in lv_conf.h:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* Top-right corner */
|
||||
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_TOP_RIGHT
|
||||
|
||||
/* Bottom-right corner */
|
||||
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
|
||||
|
||||
|
||||
|
||||
Implementation Details
|
||||
**********************
|
||||
|
||||
|
||||
Initialization
|
||||
--------------
|
||||
|
||||
Maintains:
|
||||
|
||||
- Global memory monitor (``sysmon_mem``)
|
||||
- Per-display performance structures
|
||||
|
||||
|
||||
Performance Measurement
|
||||
-----------------------
|
||||
|
||||
Event-based collection:
|
||||
|
||||
.. container:: tighter-table-3
|
||||
|
||||
+----------------------+--------------------------------+
|
||||
| Event | Measurement |
|
||||
+======================+================================+
|
||||
| LV_EVENT_REFR_START | Refresh interval start |
|
||||
+----------------------+--------------------------------+
|
||||
| LV_EVENT_REFR_READY | Record refresh duration |
|
||||
+----------------------+--------------------------------+
|
||||
| LV_EVENT_RENDER_START| Render time start |
|
||||
+----------------------+--------------------------------+
|
||||
| LV_EVENT_RENDER_READY| Record render duration |
|
||||
+----------------------+--------------------------------+
|
||||
| LV_EVENT_FLUSH_* | Measure flush operations |
|
||||
+----------------------+--------------------------------+
|
||||
|
||||
|
||||
Timers
|
||||
------
|
||||
|
||||
- Performance: ``perf_update_timer_cb``
|
||||
- Memory: ``mem_update_timer_cb``
|
||||
- Default period: 300ms (``LV_SYSMON_REFR_PERIOD_DEF``)
|
||||
@@ -0,0 +1,176 @@
|
||||
.. _test:
|
||||
|
||||
==========
|
||||
UI Testing
|
||||
==========
|
||||
|
||||
Overview
|
||||
********
|
||||
|
||||
The Test module provides functions to emulate clicks, key presses, encoder turns, time passing, and
|
||||
compare the UI with reference images.
|
||||
|
||||
These functions can be easily used in any test framework (such as Unity, GoogleTest, etc.), and
|
||||
assertions can be performed to check if, for example:
|
||||
|
||||
- A widget's value is different from the expected value after emulating user inputs.
|
||||
- The values are incorrect after some time has passed.
|
||||
- The screen's content is different from the reference image.
|
||||
- Some event functions are not triggered.
|
||||
- Etc.
|
||||
|
||||
Note that it is assumed the tests are performed on a desktop or server environment,
|
||||
where there are no memory constraints.
|
||||
|
||||
|
||||
|
||||
Usage
|
||||
*****
|
||||
|
||||
The Test module can be enabled by configuring ``LV_USE_TEST`` to a non-zero value,
|
||||
and it consists of the following components:
|
||||
|
||||
- Helpers
|
||||
- Display emulation
|
||||
- Input device emulation
|
||||
- Screenshot comparison
|
||||
|
||||
|
||||
Helpers
|
||||
-------
|
||||
|
||||
Time
|
||||
~~~~
|
||||
|
||||
To emulate elapsed time, two functions can be used:
|
||||
|
||||
1. :cpp:expr:`lv_test_wait(ms)`: Emulates that ``ms`` milliseconds have elapsed, but it also calls ``lv_timer_handler`` after each millisecond.
|
||||
This is useful to check if events (e.g., long press, long press repeat) and timers were triggered correctly over time.
|
||||
2. :cpp:expr:`lv_test_fast_forward(ms)`: Jumps ``ms`` milliseconds ahead and calls ``lv_timer_handler`` only once at the end.
|
||||
|
||||
:cpp:expr:`lv_refr_now(NULL)` is called at the end of both functions to ensure that animations and
|
||||
widget coordinates are recalculated.
|
||||
|
||||
:cpp:expr:`lv_refr_now(NULL)` can also be called manually to force LVGL to refresh the emulated display.
|
||||
|
||||
Memory Usage
|
||||
~~~~~~~~~~~~
|
||||
|
||||
If ``LV_USE_STDLIB_MALLOC`` is set to ``LV_STDLIB_BUILTIN``, memory usage and memory leaks can be monitored.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
size_t mem1 = lv_test_get_free_mem();
|
||||
<create and delete items>
|
||||
size_t mem2 = lv_test_get_free_mem();
|
||||
if(mem1 != mem2) fail();
|
||||
|
||||
It might make sense to create and delete items in a loop many times and add a small tolerance
|
||||
to the memory leakage test. This might be needed due to potential memory fragmentation. Empirically,
|
||||
a tolerance of 32 bytes is recommended.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
if(LV_ABS((int64_t)mem2 - (int64_t)mem1) > 32) fail();
|
||||
|
||||
|
||||
Display Emulation
|
||||
-----------------
|
||||
|
||||
By calling :cpp:expr:`lv_test_display_create(hor_res, ver_res)`, a dummy display can be created.
|
||||
|
||||
It functions like any other normal display, but its content exists only in memory.
|
||||
|
||||
When creating this display, the horizontal and vertical resolutions must be passed. Internally,
|
||||
a framebuffer will be allocated for this size, and ``XRGB8888`` color format will be set.
|
||||
|
||||
The resolution and color format can be changed at any time by calling :cpp:func:`lv_display_set_resolution` and
|
||||
:cpp:func:`lv_display_set_color_format`.
|
||||
|
||||
|
||||
Input Device Emulation
|
||||
----------------------
|
||||
|
||||
By calling :cpp:func:`lv_test_indev_create_all`, three test input devices will be created:
|
||||
|
||||
1. A pointer (for touch or mouse)
|
||||
2. A keypad
|
||||
3. An encoder
|
||||
|
||||
For example, this is how a scroll gesture can be emulated:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_test_mouse_move_to(20, 30);
|
||||
lv_test_mouse_press();
|
||||
lv_test_wait(20);
|
||||
lv_test_mouse_move_by(0, 100);
|
||||
lv_test_wait(20);
|
||||
lv_test_mouse_release();
|
||||
lv_test_wait(20);
|
||||
|
||||
It is recommended to add :cpp:func:`lv_test_wait` after user actions to ensure that
|
||||
the new state and coordinates are read and applied from the input device.
|
||||
|
||||
After that, the user can check if the given widget was really scrolled
|
||||
by getting the Y coordinate of a child.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int32_t y_start = lv_obj_get_y(child);
|
||||
<scroll emulation>
|
||||
int32_t y_end = lv_obj_get_y(child);
|
||||
if(y_start + 100 != y_end) fail();
|
||||
|
||||
Please refer to the ``lv_test_indev.h`` link below (in the :ref:`test_api` section)
|
||||
for the list of supported input device emulation functions.
|
||||
|
||||
|
||||
Screenshot Comparison
|
||||
---------------------
|
||||
|
||||
``bool lv_test_screenshot_compare(const char * fn_ref)`` is a useful function
|
||||
to compare the content of the emulated display with reference PNG images.
|
||||
|
||||
|
||||
This function works in a practical way:
|
||||
|
||||
- If the folder(s) referenced in ``fn_ref`` do not exist, they will be created automatically.
|
||||
- If the reference image is not found, it will be created automatically from the rendered screen.
|
||||
- If the comparison fails, an ``<image_name>_err.png`` file will be created with the rendered content next to the reference image.
|
||||
- If the comparison fails, the X and Y coordinates of the first divergent pixel, along with the actual and expected colors, will also be printed.
|
||||
|
||||
The reference PNG images should have a **32-bit color format** and match the display size.
|
||||
|
||||
The test display's content will be converted to ``XRGB8888`` to simplify comparison with the reference images.
|
||||
The conversion is supported from the following formats (i.e., the test display should have a color
|
||||
format in this list):
|
||||
|
||||
- :cpp:enumerator:`LV_COLOR_FORMAT_XRGB8888`
|
||||
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888`
|
||||
- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888_PREMULTIPLIED`
|
||||
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB888`
|
||||
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565`
|
||||
- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565_SWAPPED`
|
||||
- :cpp:enumerator:`LV_COLOR_FORMAT_L8`
|
||||
- :cpp:enumerator:`LV_COLOR_FORMAT_AL88`
|
||||
- :cpp:enumerator:`LV_COLOR_FORMAT_I1`
|
||||
|
||||
To read and decode PNG images and to store the converted rendered image, a few MBs of RAM are dynamically allocated using the standard ``malloc``
|
||||
(not :cpp:expr:`lv_malloc`).
|
||||
|
||||
|
||||
The screenshot comparison uses ``lodepng`` which is built-in to LVGL and just needs to be ebnabled with
|
||||
``LV_USE_LODEPNG``.
|
||||
|
||||
To avoid making the entire Test module dependent on ``lodepng``, screenshot comparison can be individually enabled by
|
||||
``LV_USE_TEST_SCREENSHOT_COMPARE``.
|
||||
|
||||
|
||||
|
||||
.. _test_api:
|
||||
|
||||
API
|
||||
***
|
||||
|
||||
.. API startswith: lv_test_
|
||||
@@ -0,0 +1,34 @@
|
||||
.. _vg_lite_tvg:
|
||||
|
||||
=====================
|
||||
VG-Lite GPU Simulator
|
||||
=====================
|
||||
|
||||
LVGL integrates a VG-Lite GPU simulator based on ThorVG. Its purpose is to simplify
|
||||
the debugging of VG-Lite GPU adaptation and reduce the time spent debugging and
|
||||
locating problems on hardware devices.
|
||||
|
||||
It has been integrated into the CI automated compilation and testing process to ensure
|
||||
that the VG-Lite rendering backend can be fully tested after each Pull Request (PR) is
|
||||
merged into the repository.
|
||||
|
||||
|
||||
|
||||
How It Works
|
||||
************
|
||||
|
||||
Using the ``vg_lite.h`` header file, ThorVG re-implements the VG-Lite API, generating
|
||||
the same rendered images as the real VG-Lite GPU hardware.
|
||||
|
||||
|
||||
|
||||
Configuration
|
||||
*************
|
||||
|
||||
1. Enable VG-Lite rendering backend, see :ref:`vg_lite`.
|
||||
|
||||
2. Enable ThorVG and turn on the configuration :c:macro:`LV_USE_THORVG_INTERNAL` or :c:macro:`LV_USE_THORVG_EXTERNAL`.
|
||||
It is recommended to use the internal ThorVG library to ensure uniform rendering results.
|
||||
|
||||
3. Enable :c:macro:`LV_USE_VG_LITE_THORVG` and set :c:macro:`LV_DRAW_BUF_ALIGN` to 64. The rest of the options can remain default.
|
||||
Make sure :c:macro:`LV_VG_LITE_USE_GPU_INIT` is enabled, because the thorvg drawing context needs to be initialized before it can be used.
|
||||
Reference in New Issue
Block a user