feat(docs): reorganize docs (#7136)

This commit is contained in:
Victor Wheeler
2024-10-23 12:53:33 -06:00
committed by GitHub
parent c61ca42a2a
commit 9b6f6d23f1
212 changed files with 6314 additions and 5806 deletions

View File

@@ -0,0 +1,28 @@
.. _building_lvgl:
=============
Building LVGL
=============
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-block:: 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
:ref:`Documentation <build_cmake>`.
Managed builds
**************
TODO

View File

@@ -0,0 +1,93 @@
.. _configuration:
=============
Configuration
=============
.. _lv_conf:
lv_conf.h
*********
Creating lv_conf.h
------------------
When setting up your project for the first time, copy ``lvgl/lv_conf_template.h`` to
``lv_conf.h`` next to the ``lvgl`` folder. Change the first ``#if 0`` to ``1`` to
enable the file's content and set the :c:macro:`LV_COLOR_DEPTH` define to align with
the color depth used by your display panel. See comments in ``lv_conf.h`` for
details.
The layout of the files should look like this::
lvgl/
lv_conf.h
other files and folders in your project
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"``. 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 as well, but keep in mind that the values
from ``lv_conf.h`` or build settings (``-D...``) override the values
set in Kconfig. To ignore the configs from ``lv_conf.h`` simply remove
its content, or define :c:macro:`LV_CONF_SKIP`.
.. _configuration_settings:
Configuration Settings
----------------------
Once the ``lv_conf.h`` file is in place, you can modify this header to configure
LVGL's behavior, disable unused modules and features, adjust the size of buffers, etc.
The comments in ``lv_conf.h`` explain the meaning of each setting. Be sure
to at least set :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`` if you need them.
TODO: Add all things related to ``lv_conf.h`` file and its contents.
Multiple Instances of LVGL
~~~~~~~~~~~~~~~~~~~~~~~~~~
It is possible to run multiple, independent isntances of LVGL. To enable its
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 LVGL's
global variables in TLS (Thread-Local Storage).
For example:
.. code-block:: c
lv_global_t * lv_global_default(void)
{
static __thread lv_global_t lv_global;
return &lv_global;
}
Kconfig
*******
TODO: Add how to use LVGL with Kconfig.

View File

@@ -0,0 +1,131 @@
.. _connecting_lvgl:
================================
Connecting LVGL to Your Hardware
================================
.. _initializing_lvgl:
Initializing LVGL
*****************
After you have:
- :ref:`acquired LVGL <getting_lvgl>`,
- added the appropriate LVGL files to your project, and
- :ref:`created a lv_conf.h file <lv_conf>` for your project,
you will need to complete a few more steps to get your project up and running with LVGL.
1. Initialize LVGL once early during system execution by calling :cpp:func:`lv_init`.
This needs to be done before making any other LVGL calls.
2. Initialize your drivers.
3. Connect the :ref:`tick_interface`.
4. Connect the :ref:`display_interface`.
5. Connect the :ref:`indev_interface`.
6. Drive LVGL time-related tasks by calling :cpp:func:`lv_timer_handler` every few
milliseconds to manage LVGL timers. See :ref:`timer_handler` for different ways
to do this.
7. Optionally set a theme with :cpp:func:`lv_display_set_theme`.
8. Thereafter #include "lvgl/lvgl.h" in source files wherever you need to use LVGL
functions.
.. _tick_interface:
Tick Interface
**************
LVGL needs awareness of what time it is (i.e. elapsed time in milliseconds) for
all of its tasks for which time is a factor: refreshing displays, reading user
input, firing events, animations, etc.
.. image:: /misc/intro_data_flow.png
:scale: 75 %
:alt: LVGL Data Flow
:align: center
There are two ways to provide this information to LVGL:
1. Supply LVGL with a callback function to retrieve elapsed system milliseconds by
calling :cpp:expr:`lv_tick_set_cb(my_get_milliseconds_function)`.
:cpp:expr:`my_get_milliseconds_function()` needs to return the number of
milliseconds elapsed since system start up. Many 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 :cpp:expr:`lv_tick_inc(x)` periodically, where ``x`` is the elapsed
milliseconds since the last call. If :cpp:func:`lv_tick_inc` is called from an
ISR, it should be from either a high priority interrupt or an interrupt that
cannot be missed when the system is under high load.
.. note:: :cpp:func:`lv_tick_inc` is only one of two LVGL functions that may be
called from an interrupt. See the :ref:`threading` section to learn more.
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
:c:func:`lv_timer_handler` is not considered:
.. code-block:: c
// Bad idea
lv_timer_handler();
lv_tick_inc(5);
my_delay_ms(5);
.. _display_interface:
Display Interface
*****************
LVGL needs to be supplied with knowledge about each display panel you want it to use.
Specificially:
- its pixel format and size (:ref:`creating_a_display`),
- where to render pixels for it (:ref:`draw_buffers`), and
- how to send those rendered pixels to it (:ref:`flush_callback`).
See the respective links for how to supply LVGL with this knowledge.
.. _indev_interface:
Input-Device Interface
**********************
LVGL needs to know how to get input from all user-input devices that will be used in
your project. LVGL supports a wide variety of user-input devices:
- touch-screens,
- touch-pads,
- mice,
- crowns,
- encoders,
- keypads,
- keyboards,
- etc.
See :ref:`indev_creation` to see how to do this.
API
***
:ref:`lv_tick_h`

View File

@@ -0,0 +1,25 @@
.. _getting_lvgl:
============
Getting LVGL
============
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.
The graphics library itself is the ``lvgl`` directory. It contains several
directories but to use LVGL you only need the ``.c`` and ``.h`` files under
the ``src`` directory, plus ``lvgl/lvgl.h``, and ``lvgl/lv_version.h``.
Demos and Examples
------------------
The ``lvgl`` directory also contains an ``examples`` and a ``demos``
directory. If your project needs examples and/or demos, add the these
directories to your project. If ``make`` or :ref:`build_cmake` handle the
examples and demos directories, no extra action is required.

View File

@@ -0,0 +1,18 @@
.. _add_lvgl_to_your_project:
========================
Add LVGL to Your Project
========================
.. toctree::
:maxdepth: 2
getting_lvgl
building_lvgl
configuration
connecting_lvgl
timer_handler
threading
other_platforms

View File

@@ -0,0 +1,11 @@
.. _other_platforms:
=========================
Other Platforms and Tools
=========================
See :ref:`Integration <integration_index>` to see how to use LVGL on different
platforms. There, you will find many platform-specific descriptions e.g. for ESP32,
Arduino, NXP, RT-Thread, NuttX, etc.

View File

@@ -0,0 +1,277 @@
.. _threading:
========================
Threading Considerations
========================
.. _threading_definitions:
Definitions
***********
.. _thread:
Thread
In the context of this document, a thread is any sequence of CPU instructions.
In "bare-metal" implementations (i.e. no OS), threads include:
- the main thread executing a while(1) loop that runs the system, and
- interrupts.
When running under an OS, threads include:
- each task (or process),
- interrupts, and
- advanced OSes can have multiple "execution threads" within a processes.
.. _atomic operation:
Atomic Operation
If operation X is atomic, that means that any thread observing the operation will
see it either as not yet started, or as completed, and not in any state that is
partially completed.
If other threads can see the operation in a partially performed state, or
interfere with it, then operation X is not atomic.
If an atomic operation can fail, its implementation must return the the resource
back to the state before the operation was started. To other threads it must
appear as though the operation had not yet started.
.. _atomic data:
.. _atomic:
.. _non-atomic data:
Atomic Data
A datum (i.e. contents of a variable or data structure) is atomic if any thread
observing it will always see it in a consistent state, as if operations on it
have either not yet started, or have been successfully completed, and not in a
state that is partially changed or otherwise inconsistent.
When reading or writing a value is started and completed with 1 CPU instruction,
it is automatically atomic, since it can never been seen in an inconsistent
(partially-changed) state, even from a CPU interrupt or exception. With such
values, no special protection is required by programmers to ensure all threads
see it in a consistent state.
.. _lvgl_and_threads:
LVGL and Threads
****************
LVGL is **not thread-safe**.
That means it is the programmer's responsibility to see that no LVGL function is
called while another LVGL call is in progress in another thread. This includes calls
to :cpp:func:`lv_timer_handler`.
.. note::
Assuming the above is the case, it is safe to call LVGL functions in
- :ref:`event callbacks <events>`, and in
- :ref:`timer callbacks <timer>`
because the thread that drives both of these is the thread that calls
:cpp:func:`lv_timer_handler`.
Reason:
LVGL manages many complex data structures, and those structures are "system
resources" that must be protected from being "seen" by other threads in an
inconsistent state. A high percentage LVGL functions (functions that start with
``lv_``) either read from or change those data structures. Those that change them
place the data in an inconsistent state during execution (because such changes are
multi-step sequences), but return them to a consistent state before those functions
return. For this reason, execution of each LVGL function must be allowed to complete
before any other LVGL function is started.
.. _os_exception:
.. admonition:: Exceptions to the Above:
These two LVGL functions may be called from any thread:
- :cpp:func:`lv_tick_inc` (see :ref:`tick_interface` for more information) and
- :cpp:func:`lv_display_flush_ready` (see :ref:`flush_callback` for more information)
The reason this is okay is that the LVGL data changed by them is :ref:`atomic <atomic>`.
If an interrupt MUST convey information to part of your application that calls
LVGL functions, set a flag or other atomic value that your LVGL-calling thread
(or an :ref:`LVGL Timer <timer>` you create) can read from and take action.
If you are using an OS, there are a few other options. See below.
.. _tasks:
Tasks
*****
Under an OS, it is common to have many threads of execution ("tasks" in some OSes)
performing services for the application. In some cases, such threads can acquire
data that should be shown (or otherwise reflected) in the user interface, and doing
so requires making LVGL calls to get that data (or change) shown.
Yet it still remains the programmer's responsibility to see that no LVGL function is
called while another LVGL call is in progress.
How do you do this?
.. _gateway thread:
Method 1: Use a Gateway Thread
-------------------------------
A "Gateway Thread" (or "Gateway Task" in some OSes) is a thread (task) that the
system designer designates to *exclusively* manage a system resource. An example is
management of a remote chip, such as an EEPROM or other device that always needs to
be brought into a consistent state before something new is started. Another example
is management of multiple devices on an I2C bus (or any data bus). In this case the
I2C bus is the "exclusively-managed resource", and having only one thread managing it
guarantees that each action started is allowed to complete before another action with
it is started.
LVGL's data structures are a system resource that requires such protection.
Using this method, creation, modification and deletion of all Widgets and other
LVGL resources (i.e. all LVGL function calls excluding the :ref:`exceptions
<os_exception>` mentioned above) are called by that thread. That means
that thread is also the ONLY caller of :cpp:func:`lv_timer_handler`. (See
:ref:`add_lvgl_to_your_project` for more information.)
This ensures LVGL's data structures "appear" atomic_ (all threads using this data
"see" it in a consistent state) by the fact that no other threads are "viewing" those
data structures. This is enforced by programmer discipline that ensures the `Gateway
Thread`_ is the only thread making LVGL calls (excluding the :ref:`exceptions
<os_exception>` mentioned above).
If `atomic data`_ relevant to the user interface is updated in another thread (i.e.
by another task or in an interrupt), the thread calling LVGL functions can read that
data directly without worry that it is in an inconsistent state. (To avoid
unnecessary CPU overhead, a mechanism can be provided [such as a flag raised by the
updating thread] so that the user interface is only updated when it will result in a
change visible to the end user.)
If `non-atomic data`_ relevant to the user interface is updated in another thread
(i.e. by another task or in an interrupt), an alternate (and safe) way of convey that
data to the thread calling LVGL functions is to pass a private copy of that data to
that thread via a QUEUE or other OS mechanism that protects that data from being seen
in an inconsistent state.
Use of a `Gateway Thread`_ avoids the CPU-overhead (and coding overhead) of using a
MUTEX to protect LVGL data structures.
Method 2: Use a MUTEX
----------------------
A MUTEX stands for "MUTually EXclusive" and is a synchronization primative that
protects the state of a system resource from being modified or accessed by multiple
threads of execution at once. In other words, it makes data so protected "appear"
atomic (all threads using this data "see" it in a consistent state). Most OSes
provide MUTEXes.
The system designer assigns a single MUTEX to product a single system resource. Once
assigned, that MUTEX performs such protection by programmers:
1. acquiring the MUTEX (a.k.a. locking it) before accessing or modifying that
resource, and
2. releasing the MUTEX (a.k.a. unlocking it) after that access or modification
is complete.
If a thread attempts to acquire (lock) the MUTEX while another thread "owns" it,
that thread waits on the other thread to release (unlock) it before it is allowed
to continue execution.
To be clear: this must be done *both* by threads that READ from that resource, and
threads that MODIFY that resource.
If a MUTEX is used to protect LVGL data structures, that means *every* LVGL function
call (or group of function calls) must be preceeded by #1, and followed by #2,
including calls to :cpp:func:`lv_timer_handler`.
.. note::
If your OS is integrated with LVGL (the macro :c:macro:`LV_USE_OS` has a value
other than ``LV_OS_NONE`` in ``lv_conf.h``) you can use :cpp:func:`lv_lock()` and
:cpp:func:`lv_unlock()` to perform #1 and #2.
When this is the case, :cpp:func:`lv_timer_handler` calls :cpp:func:`lv_lock()`
and :cpp:func:`lv_unlock()` internally, so you do not have to bracket your
calls to :cpp:func:`lv_timer_handler` with them.
If your OS is NOT integrated with LVGL, then these calls either return
immediately with no effect, or are optimized away by the linker.
To enable :cpp:func:`lv_lock()` and :cpp:func:`lv_unlock()`, set ``LV_USE_OS``
to a value other than ``LV_OS_NONE``.
This pseudocode illustrates the concept of using a MUTEX:
.. code-block:: 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 (lock) the MUTEX while calling LVGL functions. */
lv_lock();
lv_obj_t *img = lv_image_create(lv_screen_active());
lv_unlock();
while(1) {
lv_lock();
/* Change to next image. */
lv_image_set_src(img, next_image);
lv_unlock();
thread_sleep(2000);
}
}
.. _sleep_management:
Sleep Management
****************
The MCU can go to sleep when no user input has been received for a certain period.
In this case, the main ``while(1)`` could look like this:
.. code-block:: 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, click, etc.) has happened:
.. code-block:: c
lv_tick_inc(LV_DEF_REFR_PERIOD); /* Force task execution on wake-up */
timer_start(); /* Restart 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.

View File

@@ -0,0 +1,63 @@
.. _timer_handler:
=============
Timer Handler
=============
To drive the timers of LVGL you need to call :cpp:func:`lv_timer_handler`
periodically in one of the following:
- *while(1)* of *main()* function, or
- an OS task periodically. (See :ref:`lvgl_and_threads`.)
.. image:: /misc/intro_data_flow.png
:scale: 75 %
:alt: LVGL Data Flow
:align: center
Example:
.. code-block:: 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
supplying LVGL with time awareness:
.. code-block:: 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-block:: 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-block:: c
while (1) {
uint32_t time_till_next = lv_timer_handler();
os_delay_ms(time_till_next); /* delay to avoid unnecessary polling */
}
See :ref:`timer` section to learn more about timers.
API
***