feat(docs): upgrade presentation of display.rst (#7478)
This commit is contained in:
@@ -72,7 +72,7 @@ have a choice of several different types of units you can use.
|
||||
| 80 | 130 | 66 pixels to make 1/2 inch |
|
||||
+----+-----+----------------------------+
|
||||
|
||||
See DPI under :ref:`display_features`.
|
||||
See DPI under :ref:`display_attributes`.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -193,16 +193,7 @@ Other Events
|
||||
Display Events
|
||||
--------------
|
||||
|
||||
- :cpp:enumerator:`LV_EVENT_INVALIDATE_AREA`
|
||||
- :cpp:enumerator:`LV_EVENT_RESOLUTION_CHANGED`
|
||||
- :cpp:enumerator:`LV_EVENT_COLOR_FORMAT_CHANGED`
|
||||
- :cpp:enumerator:`LV_EVENT_REFR_REQUEST`
|
||||
- :cpp:enumerator:`LV_EVENT_REFR_START`
|
||||
- :cpp:enumerator:`LV_EVENT_REFR_READY`
|
||||
- :cpp:enumerator:`LV_EVENT_RENDER_START`
|
||||
- :cpp:enumerator:`LV_EVENT_RENDER_READY`
|
||||
- :cpp:enumerator:`LV_EVENT_FLUSH_START`
|
||||
- :cpp:enumerator:`LV_EVENT_FLUSH_FINISH`
|
||||
.. include:: ../main-components/display/display_events_list.txt
|
||||
|
||||
|
||||
Custom Events
|
||||
|
||||
@@ -9,10 +9,9 @@ things:
|
||||
|
||||
1. for Widgets, the :ref:`layers_creation` creates a natural layering of Widgets;
|
||||
2. in the context of pixel rendering (drawing), there are :ref:`draw_layers`;
|
||||
3. permanent :ref:`screen_layers` are part of each :ref:`display` object, and
|
||||
are covered :ref:`here <screen_layers>`
|
||||
3. permanent :ref:`display_screen_layers` are part of each :ref:`display` object.
|
||||
|
||||
#1 and #2 are covered below.
|
||||
#1 and #2 are covered below. #3 is covered in :ref:`display_screen_layers`.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -386,36 +386,8 @@ attached to it.
|
||||
4. System Layer
|
||||
|
||||
1, 3 and 4 are independent of the :ref:`active_screen` and they will be shown (if
|
||||
they contain anything that is visible) regardless of which screen is the Active
|
||||
Screen. See :ref:`screen_layers` for more information.
|
||||
|
||||
|
||||
.. _transparent_screens:
|
||||
|
||||
Transparent Screens
|
||||
-------------------
|
||||
|
||||
Usually, the opacity of the Screen is :cpp:enumerator:`LV_OPA_COVER` to provide a
|
||||
solid background for its children. If this is not the case (opacity <
|
||||
100%) the display's ``bottom_layer`` will be visible. If the bottom layer's
|
||||
opacity is also not :cpp:enumerator:`LV_OPA_COVER` LVGL will have no solid background
|
||||
to draw.
|
||||
|
||||
This configuration (transparent Screen) could be useful to create, for example,
|
||||
on-screen display (OSD) menus where a video is played on a different hardware layer
|
||||
of the display panel, and a menu is overlaid on a higher layer.
|
||||
|
||||
To properly render a UI on a transparent Screen the Display's color format needs to
|
||||
be set to one with an alpha channel (for example LV_COLOR_FORMAT_ARGB8888).
|
||||
|
||||
In summary, to enable transparent screens and displays for OSD menu-like UIs:
|
||||
|
||||
- Set the screen's ``bg_opa`` to transparent:
|
||||
:cpp:expr:`lv_obj_set_style_bg_opa(lv_screen_active(), LV_OPA_TRANSP, LV_PART_MAIN)`
|
||||
- Set the bottom layer's ``bg_opa`` to transparent:
|
||||
:cpp:expr:`lv_obj_set_style_bg_opa(lv_layer_bottom(), LV_OPA_TRANSP, LV_PART_MAIN)`
|
||||
- Set a color format with alpha channel. E.g.
|
||||
:cpp:expr:`lv_display_set_color_format(disp, LV_COLOR_FORMAT_ARGB8888)`
|
||||
they contain anything that is visible) regardless of which screen is the Active Screen.
|
||||
See :ref:`display_screen_layers` and :ref:`transparent_screens` for more information.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,753 +0,0 @@
|
||||
.. _display:
|
||||
|
||||
====================
|
||||
Display (lv_display)
|
||||
====================
|
||||
|
||||
|
||||
What is a Display?
|
||||
******************
|
||||
In LVGL, an *lv_display_t* (not to be confused with a :ref:`Screen <screens>`) is a
|
||||
data type that represents a single display panel --- the hardware that displays
|
||||
LVGL-rendered pixels on your device. During system initialization, you must do the
|
||||
following for each display panel you want LVGL to use:
|
||||
|
||||
- :ref:`create an lv_display_t <creating_a_display>` object for it,
|
||||
- assign a :ref:`flush_callback` for it, and
|
||||
- assign its :ref:`draw_buffers`.
|
||||
|
||||
|
||||
.. _multiple_displays:
|
||||
|
||||
How Many Displays Can LVGL Use?
|
||||
*******************************
|
||||
LVGL can use any number of displays. It is only limited by available RAM and MCU time.
|
||||
|
||||
Why would you want multi-display support? Here are some examples:
|
||||
|
||||
- Have a "normal" TFT display with local UI and create "virtual" screens on VNC
|
||||
on demand. (You need to add your VNC driver.)
|
||||
- Have a large TFT display and a small monochrome display.
|
||||
- Have some smaller and simple displays in a large instrument or technology.
|
||||
- Have two large TFT displays: one for a customer and one for the shop assistant.
|
||||
|
||||
If you set up LVGL to use more than one display, be aware that some functions use the
|
||||
:ref:`default_display` during their execution, such as creating :ref:`screens`.
|
||||
|
||||
|
||||
|
||||
.. _display_features:
|
||||
|
||||
Display Features
|
||||
****************
|
||||
|
||||
|
||||
.. _display_attributes:
|
||||
|
||||
Attributes
|
||||
----------
|
||||
Once created, a Display object remembers the characteristics of the display hardware
|
||||
it is representing, as well as other things relevant to its lifetime:
|
||||
|
||||
- Resolution (width and height in pixels)
|
||||
- Color Depth (bits per pixel)
|
||||
- Color Format (how colors in pixels are laid out)
|
||||
- DPI (default is configured :c:macro:`LV_DPI_DEF` in ``lv_conf.h``, but can be
|
||||
modified with :cpp:expr:`lv_display_set_dpi(disp, new_dpi)`).
|
||||
- 4 :ref:`screen_layers` automatically created with each display
|
||||
- All :ref:`screens` created in association with this display (and not yet deleted---only
|
||||
one is displayed at any given time)
|
||||
- The :ref:`draw_buffers` assigned to it
|
||||
- The :ref:`flush_callback` function that moves pixels from :ref:`draw_buffers` to Display hardware
|
||||
- What areas of the display have been updated (made "dirty") so rendering logic can
|
||||
compute what to render during a :ref:`display refresh <basic_data_flow>`
|
||||
- Optional custom pointer as :ref:`display_user_data`
|
||||
|
||||
|
||||
.. _screen_layers:
|
||||
|
||||
Screen Layers
|
||||
-------------
|
||||
|
||||
When an ``lv_display_t`` object is created, 4 permanent :ref:`screens` that
|
||||
facilitate layering are created and attached to it.
|
||||
|
||||
1. Bottom Layer (below Active Screen, transparent, not scroll-able, but click-able)
|
||||
2. :ref:`active_screen`
|
||||
3. Top Layer (above Active Screen, transparent and neither scroll-able nor click-able)
|
||||
4. System Layer (above Top Layer, transparent and neither scroll-able nor click-able)
|
||||
|
||||
1, 3 and 4 are independent of the :ref:`active_screen` and they will be shown (if
|
||||
they contain anything that is visible) regardless of which screen is the
|
||||
:ref:`active_screen`.
|
||||
|
||||
.. note::
|
||||
|
||||
For the bottom layer to be visible, the Active Screen's background has to be
|
||||
at least partially, if not fully, transparent.
|
||||
|
||||
You can get pointers to each of these screens on the :ref:`default_display` by using
|
||||
(respectively):
|
||||
|
||||
- :cpp:func:`lv_screen_active`,
|
||||
- :cpp:func:`lv_layer_top`,
|
||||
- :cpp:func:`lv_layer_sys`, and
|
||||
- :cpp:func:`lv_layer_bottom`.
|
||||
|
||||
You can get pointers to each of these screens on a specified display by using
|
||||
(respectively):
|
||||
|
||||
- :cpp:expr:`lv_display_get_screen_active(disp)`,
|
||||
- :cpp:expr:`lv_display_get_layer_top(disp)`,
|
||||
- :cpp:expr:`lv_display_get_layer_sys(disp)`, and
|
||||
- :cpp:expr:`lv_display_get_layer_bottom(disp)`.
|
||||
|
||||
To set a Screen you create to be the :ref:`active_screen`, call
|
||||
:cpp:func:`lv_screen_load` or :cpp:func:`lv_screen_load_anim`.
|
||||
|
||||
.. _layers_top_and_sys:
|
||||
|
||||
Top and System Layers
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
LVGL uses the Top Layer and System Layer two empower you to ensure that certain
|
||||
:ref:`widgets` are *always* on top of other layers.
|
||||
|
||||
You can add "pop-up windows" to the *Top Layer* freely. The Top Layer was meant to
|
||||
be used to create Widgets that are visible on all Screens shown on a Display. But,
|
||||
the *System Layer* is intended for system-level things (e.g. mouse cursor will be
|
||||
placed there with :cpp:func:`lv_indev_set_cursor`).
|
||||
|
||||
These layers work like any other Widget, meaning they have styles, and any kind of
|
||||
Widgets can be created in them.
|
||||
|
||||
.. note::
|
||||
While the Top Layer and System Layer are created by their owning :ref:`display`
|
||||
as not scroll-able and not click-able, these behaviors can be overridden the same
|
||||
as any other Widget by using :cpp:expr:`lv_obj_set_scrollbar_mode(scr1, LV_SCROLLBAR_MODE_xxx)`
|
||||
and :cpp:expr:`lv_obj_add_flag(scr1, LV_OBJ_FLAG_CLICKABLE)` respectively.
|
||||
|
||||
If the :cpp:enumerator:`LV_OBJ_FLAG_CLICKABLE` flag is set on the Top Layer, then it will
|
||||
absorb all user clicks and acts as a modal Widget.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_obj_add_flag(lv_layer_top(), LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
.. _layers_bottom:
|
||||
|
||||
Bottom Layer
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Similar to the Top- and System Layers, the Bottom Layer is also the full size of the
|
||||
Display, but it is located below the :ref:`active_screen`. It's visible only if the
|
||||
Active Screen's background opacity is < 255.
|
||||
|
||||
|
||||
|
||||
.. _display_events:
|
||||
|
||||
Display Events
|
||||
**************
|
||||
|
||||
:cpp:expr:`lv_display_add_event_cb(disp, event_cb, LV_EVENT_..., user_data)` adds
|
||||
an event handler to a display.
|
||||
|
||||
If you added ``user_data`` to the Display, you can retrieve it in an event like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_display_t *display1;
|
||||
my_type_t *my_user_data;
|
||||
display1 = (lv_display_t *)lv_event_get_current_target(e);
|
||||
my_user_data = lv_display_get_user_data(display1);
|
||||
|
||||
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 :ref:`flush_callback`). 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 :ref:`flush_callback`)
|
||||
- :cpp:enumerator:`LV_EVENT_FLUSH_START`: Sent before the :ref:`flush_callback` is called.
|
||||
- :cpp:enumerator:`LV_EVENT_FLUSH_READY`: Sent when the :ref:`flush_callback` 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`.
|
||||
|
||||
|
||||
|
||||
.. _display_setup:
|
||||
|
||||
Display Setup
|
||||
*************
|
||||
|
||||
|
||||
.. _creating_a_display:
|
||||
|
||||
Creating a Display
|
||||
------------------
|
||||
|
||||
To create a display for LVGL:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_display_t * display1 = lv_display_create(hor_res, ver_res)
|
||||
|
||||
You can create :ref:`multiple displays <multiple_displays>` with a different driver for
|
||||
each (see below).
|
||||
|
||||
When an ``lv_display_t`` object is created, with it are created 4 Screens set up
|
||||
to help you manage layering of displayed Widgets. See :ref:`transparent_screens` and
|
||||
:ref:`screen_layers` for more information.
|
||||
|
||||
|
||||
.. _default_display:
|
||||
|
||||
Default Display
|
||||
~~~~~~~~~~~~~~~
|
||||
When the first :ref:`display` object is created, it becomes the Default Display. If
|
||||
other Display Objects are created (to service additional Display Panels), the Default
|
||||
Display remains the first one created.
|
||||
|
||||
To set another :ref:`display` as the Default Display, call :cpp:func:`lv_display_set_default`.
|
||||
|
||||
See :ref:`multiple_displays` for more information about using multiple displays.
|
||||
|
||||
For many ``lv_display_...()`` functions, passing NULL for the ``disp`` argument will
|
||||
cause the function to target the Default Display. Check the API documentation for
|
||||
the function you are calling to be sure.
|
||||
|
||||
|
||||
.. _draw_buffers:
|
||||
|
||||
Draw Buffer(s)
|
||||
--------------
|
||||
|
||||
During system initialization, you must set drawing buffers for LVGL to use for each
|
||||
display. Do so by calling:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_display_set_buffers(display1, buf1, buf2, buf_size_in_bytes, render_mode)
|
||||
|
||||
- ``buf1`` a buffer to which LVGL can render pixels
|
||||
- ``buf2`` a second optional buffer (see below)
|
||||
- ``buf_size_in_bytes`` size of buffer(s) in bytes
|
||||
- ``render_mode`` is one of the following:
|
||||
|
||||
- :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` Use the buffer(s) to render
|
||||
to the display using buffers smaller than the size of the display. Use of
|
||||
buffers at least 1/10 display size is recommended. In :ref:`flush_callback` 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 size(es) must match
|
||||
the size of the display. LVGL will render into the correct location of the
|
||||
buffer. Using this method the buffer(s) always contain the whole display image.
|
||||
If two buffer are used, the rendered areas are automatically copied to the
|
||||
other buffer after flushing. Due to this in :ref:`flush_callback` 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 size(es) must match
|
||||
the size of the display. LVGL will always redraw the whole screen even if only
|
||||
1 pixel has been changed. If two display-sized draw buffers are provided,
|
||||
LVGL's display handling works like "traditional" double buffering. This means
|
||||
the :ref:`flush_callback` callback only has to update the address of the frame buffer to
|
||||
the ``px_map`` parameter.
|
||||
|
||||
|
||||
Simple Example
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* Declare buffer for 1/10 screen size; BYTES_PER_PIXEL will be 2 for RGB565. */
|
||||
#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))
|
||||
static uint8_t buf1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 10 * BYTES_PER_PIXEL];
|
||||
/* Set display buffer for display `display1`. */
|
||||
lv_display_set_buffers(display1, buf1, NULL, sizeof(buf1), 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 :ref:`flush_callback`. LVGL
|
||||
then waits until :cpp:func:`lv_display_flush_ready` is called
|
||||
(that is, the content of the buffer has been 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. Doing so allows *rendering* and *refreshing* the
|
||||
display to become parallel operations.
|
||||
|
||||
|
||||
.. _flush_callback:
|
||||
|
||||
Flush Callback
|
||||
--------------
|
||||
|
||||
Draw buffer(s) are simple array(s) that LVGL uses to render the display's
|
||||
content. Once rendering is has been completed, the content of the draw buffer is
|
||||
sent to the display using a Flush Callback function.
|
||||
|
||||
An example looks like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)
|
||||
{
|
||||
/* The most simple case (also the slowest) to send all rendered 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 flushing is complete so buffer can be modified again. */
|
||||
lv_display_flush_ready(display);
|
||||
}
|
||||
|
||||
During system initialization, tell LVGL you want that function to copy pixels from
|
||||
rendered pixel-buffers to a particular display by doing the following:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_display_set_flush_cb(display1, my_flush_cb)
|
||||
|
||||
Note that which display is targeted is passed to the function, so you can use the
|
||||
same function for multiple displays, or use different functions for multiple
|
||||
displays. It's up to you.
|
||||
|
||||
.. note::
|
||||
|
||||
:cpp:expr:`lv_display_flush_ready(display1)` needs to be called when flushing is
|
||||
complete to inform LVGL that the buffer is available again to render new content
|
||||
into it.
|
||||
|
||||
LVGL might render the screen in multiple chunks and therefore call your Flush
|
||||
Callback multiple times. To see whether the current call is for the last chunk being
|
||||
rendered, use :cpp:expr:`lv_display_flush_is_last(display1)`.
|
||||
|
||||
|
||||
|
||||
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:func:`lv_display_flush_ready` LVGL will spin in a loop
|
||||
while waiting for flushing.
|
||||
|
||||
However with the help of :cpp:func:`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 waiting for the flush to be completed.
|
||||
|
||||
If a Flush-Wait Callback is not set, LVGL assumes that
|
||||
:cpp:func:`lv_display_flush_ready` is used.
|
||||
|
||||
|
||||
Rotation
|
||||
--------
|
||||
|
||||
LVGL supports rotation of the display in 90 degree increments.
|
||||
|
||||
The orientation of the display can be changed with
|
||||
:cpp:expr:`lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_xxx)` where ``xxx`` is
|
||||
0, 90, 180 or 270. This will swap the horizontal and vertical resolutions internally
|
||||
according to the set degree, however it will not perform the actual rotation.
|
||||
When changing the rotation, the :cpp:enumerator:`LV_EVENT_SIZE_CHANGED` event is
|
||||
emitted to allow for hardware reconfiguration. If your display panel and/or its
|
||||
driver chip(s) do not support rotation, :cpp:func:`lv_draw_sw_rotate` can be used to
|
||||
rotate the buffer in the :ref:`flush_callback` function.
|
||||
|
||||
: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.
|
||||
|
||||
Below is an example for rotating when the rendering mode is
|
||||
:cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` and the rotated image should be sent to a
|
||||
**display controller**.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/*Rotate a partially rendered area to another buffer and send it*/
|
||||
void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
|
||||
{
|
||||
lv_display_rotation_t rotation = lv_display_get_rotation(disp);
|
||||
lv_area_t rotated_area;
|
||||
if(rotation != LV_DISPLAY_ROTATION_0) {
|
||||
lv_color_format_t cf = lv_display_get_color_format(disp);
|
||||
/*Calculate the position of the rotated area*/
|
||||
rotated_area = *area;
|
||||
lv_display_rotate_area(disp, &rotated_area);
|
||||
/*Calculate the source stride (bytes in a line) from the width of the area*/
|
||||
uint32_t src_stride = lv_draw_buf_width_to_stride(lv_area_get_width(area), cf);
|
||||
/*Calculate the stride of the destination (rotated) area too*/
|
||||
uint32_t dest_stride = lv_draw_buf_width_to_stride(lv_area_get_width(&rotated_area), cf);
|
||||
/*Have a buffer to store the rotated area and perform the rotation*/
|
||||
static uint8_t rotated_buf[500*1014];
|
||||
int32_t src_w = lv_area_get_width(area);
|
||||
int32_t src_h = lv_area_get_height(area);
|
||||
lv_draw_sw_rotate(px_map, rotated_buf, src_w, src_h, src_stride, dest_stride, rotation, cf);
|
||||
/*Use the rotated area and rotated buffer from now on*/
|
||||
area = &rotated_area;
|
||||
px_map = rotated_buf;
|
||||
}
|
||||
my_set_window(area->x1, area->y1, area->x2, area->y2);
|
||||
my_send_colors(px_map);
|
||||
}
|
||||
|
||||
Below is an example for rotating when the rendering mode is
|
||||
:cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` and the image can be rotated directly
|
||||
into a **frame buffer of the LCD peripheral**.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/*Rotate a partially rendered area to the frame buffer*/
|
||||
void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
|
||||
{
|
||||
lv_color_format_t cf = lv_display_get_color_format(disp);
|
||||
uint32_t px_size = lv_color_format_get_size(cf);
|
||||
/*Calculate the position of the rotated area*/
|
||||
lv_area_t rotated_area = *area;
|
||||
lv_display_rotate_area(disp, &rotated_area);
|
||||
/*Calculate the properties of the source buffer*/
|
||||
int32_t src_w = lv_area_get_width(area);
|
||||
int32_t src_h = lv_area_get_height(area);
|
||||
uint32_t src_stride = lv_draw_buf_width_to_stride(src_w, cf);
|
||||
/*Calculate the properties of the frame buffer*/
|
||||
int32_t fb_stride = lv_draw_buf_width_to_stride(disp->hor_res, cf);
|
||||
uint8_t * fb_start = my_fb_address;
|
||||
fb_start += rotated_area.y1 * fb_stride + rotated_area.x1 * px_size;
|
||||
lv_display_rotation_t rotation = lv_display_get_rotation(disp);
|
||||
if(rotation == LV_DISPLAY_ROTATION_0) {
|
||||
int32_t y;
|
||||
for(y = area->y1; y <= area->y2; y++) {
|
||||
lv_memcpy(fb_start, px_map, src_stride);
|
||||
px_map += src_stride;
|
||||
fb_start += fb_stride;
|
||||
}
|
||||
}
|
||||
else {
|
||||
lv_draw_sw_rotate(px_map, fb_start, src_w, src_h, src_stride, fb_stride, rotation, cf);
|
||||
}
|
||||
}
|
||||
|
||||
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 :ref:`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 the
|
||||
selected color format.
|
||||
|
||||
|
||||
Swapping Endian-ness
|
||||
--------------------
|
||||
|
||||
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 this is not possible :cpp:expr:`lv_draw_sw_rgb565_swap(buf, buf_size_in_px)`
|
||||
can be called in the :ref:`flush_callback` 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-block::
|
||||
|
||||
MSB LSB
|
||||
bits 7 6 5 4 3 2 1 0
|
||||
|
||||
are represented on the display as:
|
||||
|
||||
.. code-block::
|
||||
|
||||
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, and
|
||||
- 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 :ref:`flush_callback` 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 bits (for example 90 to 96).
|
||||
|
||||
The :cpp:func:`lv_draw_sw_i1_convert_to_vtiled` function is used to convert a draw
|
||||
buffer in I1 color format from a row-wise (htiled) to a column-wise (vtiled) buffer
|
||||
layout. This conversion is necessary for certain display controllers that require a
|
||||
different draw buffer mapping. The function assumes that the buffer width and height
|
||||
are rounded to a multiple of 8. The bit order of the resulting vtiled buffer can be
|
||||
specified using the `bit_order_lsb` parameter.
|
||||
|
||||
For more details, refer to the implementation in
|
||||
:cpp:func:`lv_draw_sw_i1_convert_to_vtiled` in :file:`src/draw/sw/lv_draw_sw.c`.
|
||||
|
||||
To ensure that the redrawn areas start and end on byte boundaries, you can add a
|
||||
rounder callback to your display driver. This callback will round the width and
|
||||
height to the nearest multiple of 8.
|
||||
|
||||
Here is an example of how to implement and set a rounder callback:
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void my_rounder_cb(lv_event_t *e)
|
||||
{
|
||||
lv_area_t *area = lv_event_get_param(e);
|
||||
|
||||
/* Round the height to the nearest multiple of 8 */
|
||||
area->y1 = (area->y1 & ~0x7);
|
||||
area->y2 = (area->y2 | 0x7);
|
||||
}
|
||||
|
||||
lv_display_add_event_cb(display, my_rounder_cb, LV_EVENT_INVALIDATE_AREA, display);
|
||||
|
||||
In this example, the `my_rounder_cb` function rounds the coordinates of the redrawn
|
||||
area to the nearest multiple of 8. The `x1` and `y1` coordinates are rounded down,
|
||||
while the `x2` and `y2` coordinates are rounded up. This ensures that the width and
|
||||
height of the redrawn area are always multiples of 8.
|
||||
|
||||
Constraints on Redrawn Area
|
||||
---------------------------
|
||||
|
||||
Some display controllers have specific requirements for the window area where the rendered image can be sent
|
||||
(e.g., `x1` must be even, and `x2` must be odd).
|
||||
|
||||
In the case of monochrome displays, `x1` must be `Nx8`, and `x2` must be `Nx8 - 1`.
|
||||
(If the display uses `LV_COLOR_FORMAT_I1`, LVGL automatically applies this rounding. See :ref:`monochrome`.)
|
||||
|
||||
The size of the invalidated (redrawn) area can be controlled as follows:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void rounder_event_cb(lv_event_t * e)
|
||||
{
|
||||
lv_area_t * a = lv_event_get_invalidated_area(e);
|
||||
|
||||
a->x1 = a->x1 & (~0x1); /* Ensure x1 is even */
|
||||
a->x2 = a->x2 | 0x1; /* Ensure x2 is odd */
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
lv_display_add_event_cb(disp, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
|
||||
|
||||
|
||||
Tiled Rendering
|
||||
---------------
|
||||
|
||||
When multiple CPU cores are available and a large area needs to be redrawn, LVGL must identify independent areas that can be rendered in parallel.
|
||||
|
||||
For example, if there are 4 CPU cores, one core can draw the screen's background while the other 3 must wait until it is finished. If there are 2 buttons on the screen, those 2 buttons can be rendered in parallel, but 2 cores will still remain idle.
|
||||
|
||||
Due to dependencies among different areas, CPU cores cannot always be fully utilized.
|
||||
|
||||
To address this, LVGL can divide large areas that need to be updated into smaller tiles. These tiles are independent, making it easier to find areas that can be rendered concurrently.
|
||||
|
||||
Specifically, if there are 4 tiles and 4 cores, there will always be an independent area for each core within one of the tiles.
|
||||
|
||||
The maximum number of tiles can be set using the function :cpp:expr:`lv_display_set_tile_cnt(disp, cnt)`. The default value is :cpp:expr:`LV_DRAW_SW_DRAW_UNIT_CNT` (or 1 if software rendering is not enabled).
|
||||
|
||||
Small areas are not further divided into smaller tiles because the overhead of spinning up 4 cores would outweigh the benefits.
|
||||
|
||||
The ideal tile size is calculated as ``ideal_tile_size = draw_buf_size / tile_cnt``. For example, in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` mode on an 800x480 screen, the display buffer is 800x480 = 375k pixels. If there are 4 tiles, the ideal tile size is approximately 93k pixels. Based on this, core utilization is as follows:
|
||||
|
||||
- 30k pixels: 1 core
|
||||
- 90k pixels: 1 core
|
||||
- 95k pixels: 2 cores (above 93k pixels, 2 cores are used)
|
||||
- 150k pixels: 2 cores
|
||||
- 200k pixels: 3 cores (above 186k pixels, 3 cores are used)
|
||||
- 300k pixels: 4 cores (above 279k pixels, 4 cores are used)
|
||||
- 375k pixels: 4 cores
|
||||
|
||||
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT`, the screen-sized draw buffer is divided by the tile count to determine the ideal tile sizes. If smaller areas are refreshed, it may result in fewer cores being used.
|
||||
|
||||
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_FULL`, the maximum number of tiles is always created when the entire screen is refreshed.
|
||||
|
||||
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL`, the partial buffer is divided into tiles. For example, if the draw buffer is 1/10th the size of the screen and there are 2 tiles, then 1/20th + 1/20th of the screen area will be rendered at once.
|
||||
|
||||
Tiled rendering only affects the rendering process, and the :ref:`flush_callback` is called once for each invalidated area. Therefore, tiling is not visible from the flushing point of view.
|
||||
|
||||
|
||||
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-block:: c
|
||||
|
||||
/* Delete original display refresh timer */
|
||||
lv_display_delete_refr_timer(display1);
|
||||
|
||||
/* Call this to refresh dirty (changed) areas of the display. */
|
||||
_lv_display_refr_timer(NULL);
|
||||
|
||||
If you have multiple displays call :cpp:expr:`lv_display_set_default(display1)` to
|
||||
select the display to refresh before :cpp:expr:`_lv_display_refr_timer(NULL)`.
|
||||
|
||||
|
||||
.. note:: :cpp:func:`lv_timer_handler` and :cpp:func:`_lv_display_refr_timer` must not run at the same time.
|
||||
|
||||
|
||||
If the performance monitor is enabled, the value of :c:macro:`LV_DEF_REFR_PERIOD` needs to be set to be
|
||||
consistent with the refresh period of the display to ensure that the statistical results are correct.
|
||||
|
||||
|
||||
Force Refreshing
|
||||
----------------
|
||||
|
||||
Normally the invalidated areas (marked for redrawing) are rendered in
|
||||
:cpp:func:`lv_timer_handler` in every :c:macro:`LV_DEF_REFR_PERIOD` milliseconds.
|
||||
However, by using :cpp:expr:`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.
|
||||
|
||||
The parameter of :cpp:func:`lv_refr_now` is a display to refresh. If ``NULL`` is set
|
||||
the :ref:`default_display` will be updated.
|
||||
|
||||
|
||||
Mirroring a Display
|
||||
-------------------
|
||||
|
||||
To mirror the image of a display to another display, you don't need to use
|
||||
multi-display support. Just transfer the buffer received in the first display's
|
||||
:ref:`flush_callback` to the other display as well.
|
||||
|
||||
|
||||
Split Image
|
||||
-----------
|
||||
|
||||
You can create a larger virtual display from an array of smaller ones.
|
||||
You can create it by:
|
||||
|
||||
1. setting the resolution of the displays to the large display's resolution;
|
||||
2. in :ref:`flush_callback`, truncate and modify the ``area`` parameter for each display; and
|
||||
3. send the buffer's content to each real display with the truncated area.
|
||||
|
||||
|
||||
.. _display_user_data:
|
||||
|
||||
User Data
|
||||
---------
|
||||
|
||||
With :cpp:expr:`lv_display_set_user_data(display1, p)` a custom pointer can be stored
|
||||
with ``lv_display_t`` object. This pointer can be used later, e.g. in
|
||||
:ref:`display_events`.
|
||||
|
||||
|
||||
.. _display_inactivity:
|
||||
|
||||
Inactivity Measurement
|
||||
----------------------
|
||||
|
||||
A user's inactivity time is measured and stored with each ``lv_display_t`` object.
|
||||
Every use of an :ref:`Input Device <indev>` (if :ref:`associated with the display
|
||||
<indev_other_features>`) counts as an activity. To get time elapsed since the last
|
||||
activity, use :cpp:expr:`lv_display_get_inactive_time(display1)`. If ``NULL`` is
|
||||
passed, the lowest inactivity time among all displays will be returned (in this case
|
||||
NULL does *not* mean the :ref:`default_display`).
|
||||
|
||||
You can manually trigger an activity using
|
||||
:cpp:expr:`lv_display_trigger_activity(display1)`. If ``display1`` is ``NULL``, the
|
||||
:ref:`default_display` will be used (**not all displays**).
|
||||
|
||||
|
||||
.. admonition:: 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 <draw>` to learn more about how rendering works in LVGL.
|
||||
|
||||
|
||||
|
||||
API
|
||||
***
|
||||
153
docs/details/main-components/display/color_format.rst
Normal file
153
docs/details/main-components/display/color_format.rst
Normal file
@@ -0,0 +1,153 @@
|
||||
.. _display_color_format:
|
||||
|
||||
============
|
||||
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 :ref:`display_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 the
|
||||
selected color format.
|
||||
|
||||
|
||||
|
||||
.. _display_endianness:
|
||||
|
||||
Swapping Endian-ness
|
||||
********************
|
||||
|
||||
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 this is not possible :cpp:expr:`lv_draw_sw_rgb565_swap(buf, buf_size_in_px)`
|
||||
can be called in the :ref:`flush_callback` 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``.
|
||||
|
||||
|
||||
|
||||
.. _display_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-block::
|
||||
|
||||
MSB LSB
|
||||
bits 7 6 5 4 3 2 1 0
|
||||
|
||||
are represented on the display as:
|
||||
|
||||
.. code-block::
|
||||
|
||||
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, and
|
||||
- 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 :ref:`flush_callback` 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 bits (for example 90 to 96).
|
||||
|
||||
The :cpp:func:`lv_draw_sw_i1_convert_to_vtiled` function is used to convert a draw
|
||||
buffer in I1 color format from a row-wise (htiled) to a column-wise (vtiled) buffer
|
||||
layout. This conversion is necessary for certain display controllers that require a
|
||||
different draw buffer mapping. The function assumes that the buffer width and height
|
||||
are rounded to a multiple of 8. The bit order of the resulting vtiled buffer can be
|
||||
specified using the `bit_order_lsb` parameter.
|
||||
|
||||
For more details, refer to the implementation in
|
||||
:cpp:func:`lv_draw_sw_i1_convert_to_vtiled` in :file:`src/draw/sw/lv_draw_sw.c`.
|
||||
|
||||
To ensure that the redrawn areas start and end on byte boundaries, you can add a
|
||||
rounder callback to your display driver. This callback will round the width and
|
||||
height to the nearest multiple of 8.
|
||||
|
||||
Here is an example of how to implement and set a rounder callback:
|
||||
|
||||
.. code:: c
|
||||
|
||||
static void my_rounder_cb(lv_event_t *e)
|
||||
{
|
||||
lv_area_t *area = lv_event_get_param(e);
|
||||
|
||||
/* Round the height to the nearest multiple of 8 */
|
||||
area->y1 = (area->y1 & ~0x7);
|
||||
area->y2 = (area->y2 | 0x7);
|
||||
}
|
||||
|
||||
lv_display_add_event_cb(display, my_rounder_cb, LV_EVENT_INVALIDATE_AREA, display);
|
||||
|
||||
In this example, the `my_rounder_cb` function rounds the coordinates of the redrawn
|
||||
area to the nearest multiple of 8. The `x1` and `y1` coordinates are rounded down,
|
||||
while the `x2` and `y2` coordinates are rounded up. This ensures that the width and
|
||||
height of the redrawn area are always multiples of 8.
|
||||
|
||||
|
||||
|
||||
.. _transparent_screens:
|
||||
|
||||
Transparent Screens
|
||||
*******************
|
||||
|
||||
Usually, the opacity of the Screen is :cpp:enumerator:`LV_OPA_COVER` to provide a
|
||||
solid background for its children. If this is not the case (opacity <
|
||||
100%) the display's ``bottom_layer`` will be visible. If the bottom layer's
|
||||
opacity is also not :cpp:enumerator:`LV_OPA_COVER` LVGL will have no solid background
|
||||
to draw.
|
||||
|
||||
This configuration (transparent Screen) could be useful to create, for example,
|
||||
on-screen display (OSD) menus where a video is played on a different hardware layer
|
||||
of the display panel, and a menu is overlaid on a higher layer.
|
||||
|
||||
To properly render a UI on a transparent Screen the Display's color format needs to
|
||||
be set to one with an alpha channel (for example LV_COLOR_FORMAT_ARGB8888).
|
||||
|
||||
In summary, to enable transparent screens and displays for OSD menu-like UIs:
|
||||
|
||||
- Set the screen's ``bg_opa`` to transparent:
|
||||
:cpp:expr:`lv_obj_set_style_bg_opa(lv_screen_active(), LV_OPA_TRANSP, LV_PART_MAIN)`
|
||||
- Set the bottom layer's ``bg_opa`` to transparent:
|
||||
:cpp:expr:`lv_obj_set_style_bg_opa(lv_layer_bottom(), LV_OPA_TRANSP, LV_PART_MAIN)`
|
||||
- Set a color format with alpha channel. E.g.
|
||||
:cpp:expr:`lv_display_set_color_format(disp, LV_COLOR_FORMAT_ARGB8888)`
|
||||
20
docs/details/main-components/display/display_events.rst
Normal file
20
docs/details/main-components/display/display_events.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
.. _display_events:
|
||||
|
||||
Events
|
||||
******
|
||||
|
||||
:cpp:expr:`lv_display_add_event_cb(disp, event_cb, LV_EVENT_..., user_data)` adds
|
||||
an event handler to a display.
|
||||
|
||||
If you added ``user_data`` to the Display, you can retrieve it in an event like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_display_t * display1;
|
||||
my_type_t * my_user_data;
|
||||
display1 = (lv_display_t *)lv_event_get_current_target(e);
|
||||
my_user_data = lv_display_get_user_data(display1);
|
||||
|
||||
The following events are sent for Display (lv_display_t) objects:
|
||||
|
||||
.. include:: display_events_list.txt
|
||||
32
docs/details/main-components/display/display_events_list.txt
Normal file
32
docs/details/main-components/display/display_events_list.txt
Normal file
@@ -0,0 +1,32 @@
|
||||
..
|
||||
display_events_list.txt
|
||||
Note: this is used in full in 2 places currently. Thus, it is placed
|
||||
here so its source is only in one place and will be fully duplicated
|
||||
where it is used:
|
||||
- display_events.rst
|
||||
- event.rst
|
||||
|
||||
- :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`
|
||||
object with the coordinates of the area to be invalidated. The area can
|
||||
be freely modified if needed to adapt it a special requirement of the
|
||||
display. Usually needed with monochrome displays to invalidate ``N x 8``
|
||||
rows or columns in one pass.
|
||||
- :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`.
|
||||
- :cpp:enumerator:`LV_EVENT_COLOR_FORMAT_CHANGED`: Sent as a result of any call to `lv_display_set_color_format()`.
|
||||
- :cpp:enumerator:`LV_EVENT_REFR_REQUEST`: Sent when something happened that requires redraw.
|
||||
- :cpp:enumerator:`LV_EVENT_REFR_START`: Sent before a refreshing cycle starts. Sent even
|
||||
if there is nothing to redraw.
|
||||
- :cpp:enumerator:`LV_EVENT_REFR_READY`: Sent when refreshing has been completed (after
|
||||
rendering and calling :ref:`flush_callback`). Sent even if no redraw happened.
|
||||
- :cpp:enumerator:`LV_EVENT_RENDER_START`: Sent just before rendering begins.
|
||||
- :cpp:enumerator:`LV_EVENT_RENDER_READY`: Sent after rendering has been completed (before
|
||||
calling :ref:`flush_callback`)
|
||||
- :cpp:enumerator:`LV_EVENT_FLUSH_START`: Sent before :ref:`flush_callback` is called.
|
||||
- :cpp:enumerator:`LV_EVENT_FLUSH_FINISH`: Sent after :ref:`flush_callback` call has returned.
|
||||
- :cpp:enumerator:`LV_EVENT_FLUSH_WAIT_START`: Sent at the beginning of internal call to
|
||||
`wait_for_flushing()` -- happens whether or not any waiting will actually occur.
|
||||
Call returns immediately if `disp->flushing == 0`.
|
||||
- :cpp:enumerator:`LV_EVENT_FLUSH_WAIT_FINISH`: Sent when the call to `wait_for_flushing()`
|
||||
is about to return, regardless whether any actual waiting occurred.
|
||||
28
docs/details/main-components/display/extending_combining.rst
Normal file
28
docs/details/main-components/display/extending_combining.rst
Normal file
@@ -0,0 +1,28 @@
|
||||
.. _extending_combining_displays:
|
||||
|
||||
============================
|
||||
Extending/Combining Displays
|
||||
============================
|
||||
|
||||
.. _display_mirroring:
|
||||
|
||||
Mirroring a Display
|
||||
*******************
|
||||
|
||||
To mirror the image of a display to another display, you don't need to use
|
||||
multi-display support. Just transfer the buffer received in the first display's
|
||||
:ref:`flush_callback` to the other display as well.
|
||||
|
||||
|
||||
|
||||
.. _display_split_image:
|
||||
|
||||
Splitting an Image
|
||||
******************
|
||||
|
||||
You can create a larger virtual display from an array of smaller ones.
|
||||
You can create it by:
|
||||
|
||||
1. setting the resolution of the displays to the large display's resolution;
|
||||
2. in :ref:`flush_callback`, truncate and modify the ``area`` parameter for each display; and
|
||||
3. send the buffer's content to each real display with the truncated area.
|
||||
23
docs/details/main-components/display/inactivity.rst
Normal file
23
docs/details/main-components/display/inactivity.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
.. _display_inactivity:
|
||||
|
||||
======================
|
||||
Inactivity Measurement
|
||||
======================
|
||||
|
||||
A user's inactivity time is measured and stored with each ``lv_display_t`` object.
|
||||
Every use of an :ref:`Input Device <indev>` (if :ref:`associated with the display
|
||||
<indev_other_features>`) counts as an activity. To get time elapsed since the last
|
||||
activity, use :cpp:expr:`lv_display_get_inactive_time(display1)`. If ``NULL`` is
|
||||
passed, the lowest inactivity time among all displays will be returned (in this case
|
||||
NULL does *not* mean the :ref:`default_display`).
|
||||
|
||||
You can manually trigger an activity using
|
||||
:cpp:expr:`lv_display_trigger_activity(display1)`. If ``display1`` is ``NULL``, the
|
||||
:ref:`default_display` will be used (**not all displays**).
|
||||
|
||||
|
||||
.. admonition:: 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 <draw>` to learn more about how rendering works in LVGL.
|
||||
29
docs/details/main-components/display/index.rst
Normal file
29
docs/details/main-components/display/index.rst
Normal file
@@ -0,0 +1,29 @@
|
||||
.. _display:
|
||||
|
||||
====================
|
||||
Display (lv_display)
|
||||
====================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
setup
|
||||
screen_layers
|
||||
color_format
|
||||
refreshing
|
||||
display_events
|
||||
resolution
|
||||
inactivity
|
||||
rotation
|
||||
redraw_area
|
||||
tiling
|
||||
extending_combining
|
||||
|
||||
|
||||
.. _display_api:
|
||||
|
||||
API
|
||||
***
|
||||
|
||||
:ref:`lv_display_h`
|
||||
69
docs/details/main-components/display/overview.rst
Normal file
69
docs/details/main-components/display/overview.rst
Normal file
@@ -0,0 +1,69 @@
|
||||
.. _display_overview:
|
||||
|
||||
========
|
||||
Overview
|
||||
========
|
||||
|
||||
What is a Display?
|
||||
******************
|
||||
|
||||
In LVGL, an *lv_display_t* (not to be confused with a :ref:`Screen <screens>`) is a
|
||||
data type that represents a single display panel --- the hardware that displays
|
||||
LVGL-rendered pixels on your device.
|
||||
|
||||
|
||||
|
||||
.. _multiple_displays:
|
||||
|
||||
How Many Displays Can LVGL Use?
|
||||
*******************************
|
||||
|
||||
LVGL can use any number of displays. It is only limited by available RAM and MCU time.
|
||||
|
||||
Why would you want multi-display support? Here are some examples:
|
||||
|
||||
- Have a "normal" TFT display with local UI and create "virtual" screens on VNC
|
||||
on demand. (You need to add your VNC driver.)
|
||||
- Have a large TFT display and a small monochrome display.
|
||||
- Have some smaller and simple displays in a large instrument or technology.
|
||||
- Have two large TFT displays: one for a customer and one for the shop assistant.
|
||||
|
||||
If you set up LVGL to use more than one display, be aware that some functions use the
|
||||
:ref:`default_display` during their execution, such as creating :ref:`screens`.
|
||||
|
||||
|
||||
|
||||
.. _display_attributes:
|
||||
|
||||
Attributes
|
||||
**********
|
||||
|
||||
Once created, a Display object remembers the characteristics of the display hardware
|
||||
it is representing, as well as other things relevant to its lifetime:
|
||||
|
||||
- Resolution (width and height in pixels)
|
||||
- Color Depth (bits per pixel)
|
||||
- Color Format (how colors in pixels are laid out)
|
||||
- DPI (default is configured :c:macro:`LV_DPI_DEF` in ``lv_conf.h``, but can be
|
||||
modified with :cpp:expr:`lv_display_set_dpi(disp, new_dpi)`).
|
||||
- 4 :ref:`display_screen_layers` automatically created with each display
|
||||
- All :ref:`screens` created in association with this display (and not yet deleted---only
|
||||
one is displayed at any given time)
|
||||
- The :ref:`draw_buffers` assigned to it
|
||||
- The :ref:`flush_callback` function that moves pixels from :ref:`draw_buffers` to Display hardware
|
||||
- What areas of the display have been updated (made "dirty") so rendering logic can
|
||||
compute what to render during a :ref:`display refresh <basic_data_flow>`
|
||||
- Optional custom pointer as :ref:`display_user_data`
|
||||
|
||||
|
||||
|
||||
.. _display_user_data:
|
||||
|
||||
User Data
|
||||
*********
|
||||
|
||||
With :cpp:expr:`lv_display_set_user_data(display1, p)` a custom pointer can be stored
|
||||
with ``lv_display_t`` object. This pointer can be used later, e.g. in
|
||||
:ref:`display_events`. See code example for how to do this in :ref:`display_events`.
|
||||
|
||||
|
||||
28
docs/details/main-components/display/redraw_area.rst
Normal file
28
docs/details/main-components/display/redraw_area.rst
Normal file
@@ -0,0 +1,28 @@
|
||||
.. _display_redraw_area:
|
||||
|
||||
===========================
|
||||
Constraints on Redrawn Area
|
||||
===========================
|
||||
|
||||
Some display controllers have specific requirements for the window area where the
|
||||
rendered image can be sent (e.g., `x1` must be even, and `x2` must be odd).
|
||||
|
||||
In the case of monochrome displays, `x1` must be `Nx8`, and `x2` must be `Nx8 - 1`.
|
||||
(If the display uses `LV_COLOR_FORMAT_I1`, LVGL automatically applies this rounding.
|
||||
See :ref:`display_monochrome`.)
|
||||
|
||||
The size of the invalidated (redrawn) area can be controlled as follows:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void rounder_event_cb(lv_event_t * e)
|
||||
{
|
||||
lv_area_t * a = lv_event_get_invalidated_area(e);
|
||||
|
||||
a->x1 = a->x1 & (~0x1); /* Ensure x1 is even */
|
||||
a->x2 = a->x2 | 0x1; /* Ensure x2 is odd */
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
lv_display_add_event_cb(disp, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
|
||||
68
docs/details/main-components/display/refreshing.rst
Normal file
68
docs/details/main-components/display/refreshing.rst
Normal file
@@ -0,0 +1,68 @@
|
||||
.. _display_refreshing:
|
||||
|
||||
==========
|
||||
Refreshing
|
||||
==========
|
||||
|
||||
Default Refresh Behavior
|
||||
************************
|
||||
|
||||
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``).
|
||||
This happens as a result of a refresh :ref:`timer` created that gets created when
|
||||
the display is created
|
||||
executed
|
||||
at that
|
||||
interval
|
||||
|
||||
|
||||
|
||||
.. _display_decoupling_refresh_timer:
|
||||
|
||||
Decoupling the Display Refresh Timer
|
||||
************************************
|
||||
|
||||
However, in some cases you might need more control on when display
|
||||
refreshing happens, for example:
|
||||
|
||||
- to synchronize rendering with VSYNC or the TE signal;
|
||||
|
||||
- to time display refreshes immediately after a single screen update of all widgets
|
||||
that needed to have their display data updated (i.e. only updated once immediately
|
||||
before display refresh to reduce CPU overhead).
|
||||
|
||||
You can do this in the following way:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* Delete original display refresh timer */
|
||||
lv_display_delete_refr_timer(display1);
|
||||
|
||||
/* Call this to refresh dirty (changed) areas of the display. */
|
||||
lv_display_refr_timer(NULL);
|
||||
|
||||
If you have multiple displays call :cpp:expr:`lv_display_set_default(display1)` to
|
||||
select the display to refresh before :cpp:expr:`_lv_display_refr_timer(NULL)`.
|
||||
|
||||
|
||||
.. note:: :cpp:func:`lv_timer_handler` and :cpp:func:`_lv_display_refr_timer` must not run at the same time.
|
||||
|
||||
|
||||
If the performance monitor is enabled, the value of :c:macro:`LV_DEF_REFR_PERIOD` needs to be set to be
|
||||
consistent with the refresh period of the display to ensure that the statistical results are correct.
|
||||
|
||||
|
||||
|
||||
.. _display_force_refresh:
|
||||
|
||||
Forcing a Refresh
|
||||
*****************
|
||||
|
||||
Normally the invalidated areas (marked for redrawing) are rendered in
|
||||
:cpp:func:`lv_timer_handler` in every :c:macro:`LV_DEF_REFR_PERIOD` milliseconds.
|
||||
However, by using :cpp:expr:`lv_refr_now(display)` you can tell LVGL to redraw the
|
||||
invalid areas immediately. The refreshing will happen in :cpp:func:`lv_refr_now`
|
||||
which might take longer.
|
||||
|
||||
The parameter of :cpp:func:`lv_refr_now` is a pointer to the display to refresh. If
|
||||
``NULL`` is passed, all displays that have active refresh timers will be refreshed.
|
||||
16
docs/details/main-components/display/resolution.rst
Normal file
16
docs/details/main-components/display/resolution.rst
Normal file
@@ -0,0 +1,16 @@
|
||||
.. _display_resolution:
|
||||
|
||||
===================
|
||||
Changing 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)`
|
||||
96
docs/details/main-components/display/rotation.rst
Normal file
96
docs/details/main-components/display/rotation.rst
Normal file
@@ -0,0 +1,96 @@
|
||||
.. _display_rotation:
|
||||
|
||||
========
|
||||
Rotation
|
||||
========
|
||||
|
||||
LVGL supports rotation of the display in 90 degree increments.
|
||||
|
||||
The orientation of the display can be changed with
|
||||
:cpp:expr:`lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_xxx)` where ``xxx`` is
|
||||
0, 90, 180 or 270. This will swap the horizontal and vertical resolutions internally
|
||||
according to the set degree, however it will not perform the actual rotation.
|
||||
When changing the rotation, the :cpp:enumerator:`LV_EVENT_SIZE_CHANGED` event is
|
||||
emitted to allow for hardware reconfiguration. If your display panel and/or its
|
||||
driver chip(s) do not support rotation, :cpp:func:`lv_draw_sw_rotate` can be used to
|
||||
rotate the buffer in the :ref:`flush_callback` function.
|
||||
|
||||
: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.
|
||||
|
||||
Below is an example for rotating when the rendering mode is
|
||||
:cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` and the rotated image should be sent to a
|
||||
**display controller**.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/*Rotate a partially rendered area to another buffer and send it*/
|
||||
void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
|
||||
{
|
||||
lv_display_rotation_t rotation = lv_display_get_rotation(disp);
|
||||
lv_area_t rotated_area;
|
||||
if(rotation != LV_DISPLAY_ROTATION_0) {
|
||||
lv_color_format_t cf = lv_display_get_color_format(disp);
|
||||
/*Calculate the position of the rotated area*/
|
||||
rotated_area = *area;
|
||||
lv_display_rotate_area(disp, &rotated_area);
|
||||
/*Calculate the source stride (bytes in a line) from the width of the area*/
|
||||
uint32_t src_stride = lv_draw_buf_width_to_stride(lv_area_get_width(area), cf);
|
||||
/*Calculate the stride of the destination (rotated) area too*/
|
||||
uint32_t dest_stride = lv_draw_buf_width_to_stride(lv_area_get_width(&rotated_area), cf);
|
||||
/*Have a buffer to store the rotated area and perform the rotation*/
|
||||
static uint8_t rotated_buf[500*1014];
|
||||
int32_t src_w = lv_area_get_width(area);
|
||||
int32_t src_h = lv_area_get_height(area);
|
||||
lv_draw_sw_rotate(px_map, rotated_buf, src_w, src_h, src_stride, dest_stride, rotation, cf);
|
||||
/*Use the rotated area and rotated buffer from now on*/
|
||||
area = &rotated_area;
|
||||
px_map = rotated_buf;
|
||||
}
|
||||
my_set_window(area->x1, area->y1, area->x2, area->y2);
|
||||
my_send_colors(px_map);
|
||||
}
|
||||
|
||||
Below is an example for rotating when the rendering mode is
|
||||
:cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` and the image can be rotated directly
|
||||
into a **frame buffer of the LCD peripheral**.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/*Rotate a partially rendered area to the frame buffer*/
|
||||
void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map)
|
||||
{
|
||||
lv_color_format_t cf = lv_display_get_color_format(disp);
|
||||
uint32_t px_size = lv_color_format_get_size(cf);
|
||||
/*Calculate the position of the rotated area*/
|
||||
lv_area_t rotated_area = *area;
|
||||
lv_display_rotate_area(disp, &rotated_area);
|
||||
/*Calculate the properties of the source buffer*/
|
||||
int32_t src_w = lv_area_get_width(area);
|
||||
int32_t src_h = lv_area_get_height(area);
|
||||
uint32_t src_stride = lv_draw_buf_width_to_stride(src_w, cf);
|
||||
/*Calculate the properties of the frame buffer*/
|
||||
int32_t fb_stride = lv_draw_buf_width_to_stride(disp->hor_res, cf);
|
||||
uint8_t * fb_start = my_fb_address;
|
||||
fb_start += rotated_area.y1 * fb_stride + rotated_area.x1 * px_size;
|
||||
lv_display_rotation_t rotation = lv_display_get_rotation(disp);
|
||||
if(rotation == LV_DISPLAY_ROTATION_0) {
|
||||
int32_t y;
|
||||
for(y = area->y1; y <= area->y2; y++) {
|
||||
lv_memcpy(fb_start, px_map, src_stride);
|
||||
px_map += src_stride;
|
||||
fb_start += fb_stride;
|
||||
}
|
||||
}
|
||||
else {
|
||||
lv_draw_sw_rotate(px_map, fb_start, src_w, src_h, src_stride, fb_stride, rotation, cf);
|
||||
}
|
||||
}
|
||||
89
docs/details/main-components/display/screen_layers.rst
Normal file
89
docs/details/main-components/display/screen_layers.rst
Normal file
@@ -0,0 +1,89 @@
|
||||
.. _display_screen_layers:
|
||||
|
||||
=============
|
||||
Screen Layers
|
||||
=============
|
||||
|
||||
When an ``lv_display_t`` object is created, 4 permanent :ref:`screens` that
|
||||
facilitate layering are created and attached to it.
|
||||
|
||||
1. Bottom Layer (below Active Screen, transparent, not scroll-able, but click-able)
|
||||
2. :ref:`active_screen`
|
||||
3. Top Layer (above Active Screen, transparent and neither scroll-able nor click-able)
|
||||
4. System Layer (above Top Layer, transparent and neither scroll-able nor click-able)
|
||||
|
||||
1, 3 and 4 are independent of the :ref:`active_screen` and they will be shown (if
|
||||
they contain anything that is visible) regardless of which screen is the
|
||||
:ref:`active_screen`.
|
||||
|
||||
.. note::
|
||||
|
||||
For the bottom layer to be visible, the Active Screen's background has to be
|
||||
at least partially, if not fully, transparent.
|
||||
|
||||
You can get pointers to each of these screens on the :ref:`default_display` by using
|
||||
(respectively):
|
||||
|
||||
- :cpp:func:`lv_screen_active`,
|
||||
- :cpp:func:`lv_layer_top`,
|
||||
- :cpp:func:`lv_layer_sys`, and
|
||||
- :cpp:func:`lv_layer_bottom`.
|
||||
|
||||
You can get pointers to each of these screens on a specified display by using
|
||||
(respectively):
|
||||
|
||||
- :cpp:expr:`lv_display_get_screen_active(disp)`,
|
||||
- :cpp:expr:`lv_display_get_layer_top(disp)`,
|
||||
- :cpp:expr:`lv_display_get_layer_sys(disp)`, and
|
||||
- :cpp:expr:`lv_display_get_layer_bottom(disp)`.
|
||||
|
||||
To set a Screen you create to be the :ref:`active_screen`, call
|
||||
:cpp:func:`lv_screen_load` or :cpp:func:`lv_screen_load_anim`.
|
||||
|
||||
|
||||
|
||||
.. _layers_top_and_sys:
|
||||
|
||||
Top and System Layers
|
||||
*********************
|
||||
|
||||
LVGL uses the Top Layer and System Layer two empower you to ensure that certain
|
||||
:ref:`widgets` are *always* on top of other layers.
|
||||
|
||||
You can add "pop-up windows" to the *Top Layer* freely. The Top Layer was meant to
|
||||
be used to create Widgets that are visible on all Screens shown on a Display. But,
|
||||
the *System Layer* is intended for system-level things (e.g. mouse cursor will be
|
||||
placed there with :cpp:func:`lv_indev_set_cursor`).
|
||||
|
||||
These layers work like any other Widget, meaning they have styles, and any kind of
|
||||
Widgets can be created in them.
|
||||
|
||||
.. note::
|
||||
While the Top Layer and System Layer are created by their owning :ref:`display`
|
||||
as not scroll-able and not click-able, these behaviors can be overridden the same
|
||||
as any other Widget by using :cpp:expr:`lv_obj_set_scrollbar_mode(scr1, LV_SCROLLBAR_MODE_xxx)`
|
||||
and :cpp:expr:`lv_obj_add_flag(scr1, LV_OBJ_FLAG_CLICKABLE)` respectively.
|
||||
|
||||
If the :cpp:enumerator:`LV_OBJ_FLAG_CLICKABLE` flag is set on the Top Layer, then it will
|
||||
absorb all user clicks and acts as a modal Widget.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_obj_add_flag(lv_layer_top(), LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
|
||||
|
||||
.. _layers_bottom:
|
||||
|
||||
Bottom Layer
|
||||
************
|
||||
|
||||
Similar to the Top- and System Layers, the Bottom Layer is also the full size of the
|
||||
Display, but it is located below the :ref:`active_screen`. It's visible only if the
|
||||
Active Screen's background opacity is < 255.
|
||||
|
||||
|
||||
.. admonition:: Further Reading
|
||||
|
||||
:ref:`transparent_screens`.
|
||||
|
||||
193
docs/details/main-components/display/setup.rst
Normal file
193
docs/details/main-components/display/setup.rst
Normal file
@@ -0,0 +1,193 @@
|
||||
.. _display_setup:
|
||||
|
||||
==========================
|
||||
Setting Up Your Display(s)
|
||||
==========================
|
||||
|
||||
During system initialization, you must do the following for each physical display
|
||||
panel you want LVGL to use:
|
||||
|
||||
- :ref:`create an lv_display_t <creating_a_display>` object for it,
|
||||
- assign its :ref:`draw_buffers`, and
|
||||
- assign a :ref:`flush_callback` for it.
|
||||
|
||||
|
||||
|
||||
.. _creating_a_display:
|
||||
|
||||
Creating a Display
|
||||
******************
|
||||
|
||||
To create a display for LVGL:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_display_t * display1 = lv_display_create(hor_res, ver_res)
|
||||
|
||||
You can create :ref:`multiple displays <multiple_displays>` with a different driver for
|
||||
each (see below).
|
||||
|
||||
When an ``lv_display_t`` object is created, with it are created 4 Screens set up
|
||||
to help you manage layering of displayed Widgets. See :ref:`transparent_screens` and
|
||||
:ref:`display_screen_layers` for more information.
|
||||
|
||||
|
||||
.. _default_display:
|
||||
|
||||
Default Display
|
||||
---------------
|
||||
When the first :ref:`display` object is created, it becomes the Default Display. If
|
||||
other Display Objects are created (to service additional Display Panels), the Default
|
||||
Display remains the first one created.
|
||||
|
||||
To set another :ref:`display` as the Default Display, call :cpp:func:`lv_display_set_default`.
|
||||
|
||||
See :ref:`multiple_displays` for more information about using multiple displays.
|
||||
|
||||
For many ``lv_display_...()`` functions, passing NULL for the ``disp`` argument will
|
||||
cause the function to target the Default Display. Check the API documentation for
|
||||
the function you are calling to be sure.
|
||||
|
||||
|
||||
|
||||
.. _draw_buffers:
|
||||
|
||||
Draw Buffer(s)
|
||||
**************
|
||||
|
||||
During system initialization, you must set drawing buffers for LVGL to use for each
|
||||
display. Do so by calling:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_display_set_buffers(display1, buf1, buf2, buf_size_in_bytes, render_mode)
|
||||
|
||||
- ``buf1`` a buffer to which LVGL can render pixels
|
||||
- ``buf2`` a second optional buffer (see below)
|
||||
- ``buf_size_in_bytes`` size of buffer(s) in bytes
|
||||
- ``render_mode`` is one of the following:
|
||||
|
||||
- :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL` Use the buffer(s) to render
|
||||
to the display using buffers smaller than the size of the display. Use of
|
||||
buffers at least 1/10 display size is recommended. In :ref:`flush_callback` 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 size(es) must match
|
||||
the size of the display. LVGL will render into the correct location of the
|
||||
buffer. Using this method the buffer(s) always contain the whole display image.
|
||||
If two buffer are used, the rendered areas are automatically copied to the
|
||||
other buffer after flushing. Due to this in :ref:`flush_callback` 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 size(es) must match
|
||||
the size of the display. LVGL will always redraw the whole screen even if only
|
||||
1 pixel has been changed. If two display-sized draw buffers are provided,
|
||||
LVGL's display handling works like "traditional" double buffering. This means
|
||||
the :ref:`flush_callback` callback only has to update the address of the frame buffer to
|
||||
the ``px_map`` parameter.
|
||||
|
||||
|
||||
Simple Example
|
||||
--------------
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* Declare buffer for 1/10 screen size; BYTES_PER_PIXEL will be 2 for RGB565. */
|
||||
#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))
|
||||
static uint8_t buf1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 10 * BYTES_PER_PIXEL];
|
||||
/* Set display buffer for display `display1`. */
|
||||
lv_display_set_buffers(display1, buf1, NULL, sizeof(buf1), 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 :ref:`flush_callback`. LVGL
|
||||
then waits until :cpp:func:`lv_display_flush_ready` is called
|
||||
(that is, the content of the buffer has been 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. Doing so allows *rendering* and *refreshing* the
|
||||
display to become parallel operations.
|
||||
|
||||
|
||||
|
||||
.. _flush_callback:
|
||||
|
||||
Flush Callback
|
||||
**************
|
||||
|
||||
Draw buffer(s) are simple array(s) that LVGL uses to render the display's
|
||||
content. Once rendering is has been completed, the content of the draw buffer is
|
||||
sent to the display using a Flush Callback function.
|
||||
|
||||
An example looks like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)
|
||||
{
|
||||
/* The most simple case (also the slowest) to send all rendered 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 flushing is complete so buffer can be modified again. */
|
||||
lv_display_flush_ready(display);
|
||||
}
|
||||
|
||||
During system initialization, tell LVGL you want that function to copy pixels from
|
||||
rendered pixel-buffers to a particular display by doing the following:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
lv_display_set_flush_cb(display1, my_flush_cb)
|
||||
|
||||
Note that which display is targeted is passed to the function, so you can use the
|
||||
same function for multiple displays, or use different functions for multiple
|
||||
displays. It's up to you.
|
||||
|
||||
.. note::
|
||||
|
||||
:cpp:expr:`lv_display_flush_ready(display1)` needs to be called when flushing is
|
||||
complete to inform LVGL that the buffer is available again to render new content
|
||||
into it.
|
||||
|
||||
LVGL might render the screen in multiple chunks and therefore call your Flush
|
||||
Callback multiple times. To see whether the current call is for the last chunk being
|
||||
rendered, use :cpp:expr:`lv_display_flush_is_last(display1)`.
|
||||
|
||||
|
||||
|
||||
.. _display_flush-wait_callback:
|
||||
|
||||
Flush-Wait Callback
|
||||
*******************
|
||||
|
||||
By using :cpp:func:`lv_display_flush_ready` LVGL will normally spin in a loop
|
||||
while waiting for flushing.
|
||||
|
||||
However with the help of :cpp:func:`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 waiting for the flush to be completed. The callback
|
||||
need not call :cpp:func:`lv_display_flush_ready` since the caller takes care of
|
||||
that (clearing the display's ``flushing`` flag) when your callback returns.
|
||||
|
||||
However, if a Flush-Wait Callback is not set, LVGL assumes that
|
||||
:cpp:func:`lv_display_flush_ready` is called after the flush has completed.
|
||||
|
||||
|
||||
|
||||
57
docs/details/main-components/display/tiling.rst
Normal file
57
docs/details/main-components/display/tiling.rst
Normal file
@@ -0,0 +1,57 @@
|
||||
.. _display_tiling:
|
||||
|
||||
===============
|
||||
Tiled Rendering
|
||||
===============
|
||||
|
||||
When multiple CPU cores are available and a large area needs to be redrawn, LVGL must
|
||||
identify independent areas that can be rendered in parallel.
|
||||
|
||||
For example, if there are 4 CPU cores, one core can draw the screen's background
|
||||
while the other 3 must wait until it is finished. If there are 2 buttons on the
|
||||
screen, those 2 buttons can be rendered in parallel, but 2 cores will still remain
|
||||
idle.
|
||||
|
||||
Due to dependencies among different areas, CPU cores cannot always be fully utilized.
|
||||
|
||||
To address this, LVGL can divide large areas that need to be updated into smaller
|
||||
tiles. These tiles are independent, making it easier to find areas that can be
|
||||
rendered concurrently.
|
||||
|
||||
Specifically, if there are 4 tiles and 4 cores, there will always be an independent
|
||||
area for each core within one of the tiles.
|
||||
|
||||
The maximum number of tiles can be set using the function
|
||||
:cpp:expr:`lv_display_set_tile_cnt(disp, cnt)`. The default value is
|
||||
:cpp:expr:`LV_DRAW_SW_DRAW_UNIT_CNT` (or 1 if software rendering is not enabled).
|
||||
|
||||
Small areas are not further divided into smaller tiles because the overhead of
|
||||
spinning up 4 cores would outweigh the benefits.
|
||||
|
||||
The ideal tile size is calculated as ``ideal_tile_size = draw_buf_size / tile_cnt``.
|
||||
For example, in :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT` mode on an 800x480
|
||||
screen, the display buffer is 800x480 = 375k pixels. If there are 4 tiles, the ideal
|
||||
tile size is approximately 93k pixels. Based on this, core utilization is as follows:
|
||||
|
||||
- 30k pixels: 1 core
|
||||
- 90k pixels: 1 core
|
||||
- 95k pixels: 2 cores (above 93k pixels, 2 cores are used)
|
||||
- 150k pixels: 2 cores
|
||||
- 200k pixels: 3 cores (above 186k pixels, 3 cores are used)
|
||||
- 300k pixels: 4 cores (above 279k pixels, 4 cores are used)
|
||||
- 375k pixels: 4 cores
|
||||
|
||||
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_DIRECT`, the screen-sized draw buffer is
|
||||
divided by the tile count to determine the ideal tile sizes. If smaller areas are
|
||||
refreshed, it may result in fewer cores being used.
|
||||
|
||||
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_FULL`, the maximum number of tiles is
|
||||
always created when the entire screen is refreshed.
|
||||
|
||||
In :cpp:enumerator:`LV_DISPLAY_RENDER_MODE_PARTIAL`, the partial buffer is divided
|
||||
into tiles. For example, if the draw buffer is 1/10th the size of the screen and
|
||||
there are 2 tiles, then 1/20th + 1/20th of the screen area will be rendered at once.
|
||||
|
||||
Tiled rendering only affects the rendering process, and the :ref:`flush_callback` is
|
||||
called once for each invalidated area. Therefore, tiling is not visible from the
|
||||
flushing point of view.
|
||||
@@ -7,7 +7,7 @@ Main Components
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
display
|
||||
display/index
|
||||
indev
|
||||
color
|
||||
font
|
||||
|
||||
@@ -47,7 +47,7 @@ Create a message box
|
||||
|
||||
:cpp:expr:`lv_msgbox_create(parent)` creates a message box.
|
||||
If ``parent`` is ``NULL`` the message box will be modal, and will use the
|
||||
:ref:`default_display`'s Top :ref:`Layer <screen_layers>` as a parent.
|
||||
:ref:`default_display`'s Top :ref:`Layer <display_screen_layers>` as a parent.
|
||||
|
||||
|
||||
Adding buttons
|
||||
|
||||
Reference in New Issue
Block a user