init Files

This commit is contained in:
2024-12-09 19:47:18 +01:00
parent b55a8cd2bc
commit 528e862838
3471 changed files with 1105029 additions and 0 deletions

View 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
***

View File

@@ -0,0 +1,77 @@
.. _porting_draw:
==========
Custom GPU
==========
LVGL has a flexible and extendable draw pipeline. You can hook it to do
some rendering with a GPU or even completely replace the built-in
software renderer.
Overview
********
Draw task
---------
When :cpp:expr:`lv_draw_rect`, :cpp:expr:`lv_draw_label` or similar functions are called
LVGL creates a so called draw task.
Draw unit
---------
The draw tasks are collected in a list and periodically dispatched to draw units. A
draw unit can a CPU core, a GPU, just a new rendering library for certain or all draw tasks,
or basically anything that can draw somehow.
Draw task evaluation
--------------------
Different draw units might render slight different output for example for an image transformation or
a gradient. If such a draw task were assigned to a different draw units, the screen might jitter a
little bit. To resolve it each draw unit has an ``evaluate_cb`` which is called when a draw task is created.
Based on the type and parameters of the draw task each draw unit can decide if it want to assign the
draw task to itself. This way a certain type of draw task (e.g. rounded rectangle with horizontal
gradient) will be always assigned to the same draw unit. It avoid the above mentioned issue of
slight difference between draw units.
Dispatching
-----------
While collecting draw tasks LVGL frequently tries to dispatch the collected draw tasks to the draw units.
This handles via the ``dispatch_cb`` of the draw units.
If a draw unit is busy with 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
***

View File

@@ -0,0 +1,303 @@
.. _porting_indev:
======================
Input device interface
======================
Types of input devices
**********************
To create an input device use
.. code:: c
/*Register at least one display before you register any input devices*/
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_...); /*See below.*/
lv_indev_set_read_cb(indev, read_cb); /*See below.*/
The ``type`` member can be:
- :cpp:enumerator:`LV_INDEV_TYPE_POINTER`: touchpad or mouse
- :cpp:enumerator:`LV_INDEV_TYPE_KEYPAD`: keyboard or keypad
- :cpp:enumerator:`LV_INDEV_TYPE_ENCODER`: encoder with left/right turn and push options
- :cpp:enumerator:`LV_INDEV_TYPE_BUTTON`: external buttons virtually pressing the screen
``read_cb`` is a function pointer which will be called periodically to
report the current state of an input device.
Visit :ref:`Input devices <indev>` to learn more about input
devices in general.
Touchpad, mouse or any pointer
------------------------------
Input devices that can click points on the screen belong to this
category.
.. code:: c
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
...
void my_input_read(lv_indev_t * indev, lv_indev_data_t*data)
{
if(touchpad_pressed) {
data->point.x = touchpad_x;
data->point.y = touchpad_y;
data->state = LV_INDEV_STATE_PRESSED;
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
Mouse cursor
~~~~~~~~~~~~
To set a mouse cursor use :cpp:expr:`lv_indev_set_cursor(indev, &img_cursor)`.
Crown behavior
~~~~~~~~~~~~~~
The "Crown" is a rotary device typically found on smart watches.
When the user clicks somewhere and after that turns the rotary
the last clicked widget will be either scrolled or it's value will be incremented/decremented
(e.g. in case of a slider).
As this behavior is tightly related to the last clicked widget, the crown support is
an extension of the pointer input device. Just set ``data->diff`` to the number of
turned steps and LVGL will automatically send :cpp:enum:`LV_EVENT_ROTARY` or scroll the widget based on the
``editable`` flag in the widget's class. Non-editable widgets are scrolled and for editable widgets the event is sent.
To get the steps in an event callback use :cpp:func:`int32_t diff = lv_event_get_rotary_diff(e)`
The rotary sensitivity can be adjusted on 2 levels:
1. In the input device by the `indev->rotary_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
***

View File

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

View File

@@ -0,0 +1,71 @@
.. _logging:
=======
Logging
=======
LVGL has a built-in *Log* module to inform the user about what is
happening in the library.
Log level
*********
To enable logging, set :c:macro:`LV_USE_LOG` in ``lv_conf.h`` and set
:c:macro:`LV_LOG_LEVEL` to one of the following values:
- :c:macro:`LV_LOG_LEVEL_TRACE`: A lot of logs to give detailed information
- :c:macro:`LV_LOG_LEVEL_INFO`: Log important events
- :c:macro:`LV_LOG_LEVEL_WARN`: Log if something unwanted happened but didn't cause a problem
- :c:macro:`LV_LOG_LEVEL_ERROR`: Only critical issues, where the system may fail
- :c:macro:`LV_LOG_LEVEL_USER`: Only user messages
- :c:macro:`LV_LOG_LEVEL_NONE`: Do not log anything
The events which have a higher level than the set log level will be
logged too. E.g. if you :c:macro:`LV_LOG_LEVEL_WARN`, errors will be also
logged.
Printing logs
*************
Logging with printf
-------------------
If your system supports ``printf``, you just need to enable
:c:macro:`LV_LOG_PRINTF` in ``lv_conf.h`` to send the logs with ``printf``.
Custom log function
-------------------
If you can't use ``printf`` or want to use a custom function to log, you
can register a "logger" callback with :cpp:func:`lv_log_register_print_cb`.
For example:
.. code:: c
void my_log_cb(lv_log_level_t level, const char * buf)
{
serial_send(buf, strlen(buf));
}
...
lv_log_register_print_cb(my_log_cb);
Add logs
********
You can also use the log module via the
``LV_LOG_TRACE/INFO/WARN/ERROR/USER(text)`` or ``LV_LOG(text)``
functions. Here:
- ``LV_LOG_TRACE/INFO/WARN/ERROR/USER(text)`` append following information to your ``text``
- Log Level
- \__FILE\_\_
- \__LINE\_\_
- \__func\_\_
- ``LV_LOG(text)`` is similar to ``LV_LOG_USER`` but has no extra information attached.
API
***

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

View 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>`__.

View File

@@ -0,0 +1,33 @@
================
Sleep management
================
The MCU can go to sleep when no user input happens. In this case, the
main ``while(1)`` should look like this:
.. code:: c
while(1) {
/*Normal operation (no sleep) in < 1 sec inactivity*/
if(lv_display_get_inactive_time(NULL) < 1000) {
lv_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.

View File

@@ -0,0 +1,33 @@
.. _tick:
==============
Tick interface
==============
LVGL needs a system tick to know the elapsed time for animations and other
tasks.
There are two ways to provide the tick to LVGL:
1. Call ``lv_tick_set_cb(my_get_milliseconds_function);``: `my_get_milliseconds_function` needs to tell how many milliseconds have elapsed since start up. Most of the platforms have built-in functions that can be used as they are. For example
- SDL: ``lv_tick_set_cb(SDL_GetTicks);``
- Arduino: ``lv_tick_set_cb(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
---

View File

@@ -0,0 +1,59 @@
.. _timer:
=============
Timer Handler
=============
To handle the tasks of LVGL you need to call :cpp:func:`lv_timer_handler`
periodically in one of the following:
- *while(1)* of *main()* function
- timer interrupt periodically (lower priority than :cpp:func:`lv_tick_inc`)
- an OS task periodically
Example:
.. code:: c
while(1) {
uint32_t time_till_next = lv_timer_handler();
my_delay_ms(time_till_next);
}
If you want to use :cpp:func:`lv_timer_handler` in a super-loop, a helper
function :cpp:func:`lv_timer_handler_run_in_period` is provided to simplify
the porting:
.. code:: c
while(1) {
...
lv_timer_handler_run_in_period(5); /* run lv_timer_handler() every 5ms */
...
}
Or use the sleep time automatically calculated by LVGL:
.. code:: c
while(1) {
...
lv_timer_periodic_handler();
...
}
In an OS environment, you can use it together with the **delay** or
**sleep** provided by OS to release CPU whenever possible:
.. code:: c
while (1) {
uint32_t time_till_next = lv_timer_handler();
os_delay_ms(time_till_next); /* delay to avoid unnecessary polling */
}
To learn more about timers visit the :ref:`timer`
section.
API
***