Learn the Basics¶
Overview¶
LVGL (Light and Versatile Graphics Library) is a free and open-source graphics library providing everything you need to create an embedded GUI with easy-to-use mobile-phone-like graphical elements, beautiful visual effects, and a low memory footprint.
You can think of LVGL as a collection of C and H files that can be dropped into any project to add UI capabilities to the product.
With the help of consistent and easy-to-learn API functions you can create widgets (buttons, sliders, charts, etc), style them, add events, layouts, or animations.
Based on these settings LVGL will render an image (either by using its built-in software rendering engine or a GPU) and will call a callback function to show the rendered image on the display. This callback function is the main interface between LVGL and the display. Most of the porting-related work is focused on writing such a callback in an effective way.
This chapter will show the basics to give an idea about how LVGL works and how it can be used. For more details about each feature visit that feature's dedicated documentation page.
Basic Data Flow¶
Driver Initialization: It's the user's responsibility to set up the clock, timers, peripherals, etc.
Call lv_init(): It initializes LVGL itself
Create display and input devices: Create display(s) (
lv_display_t
) and input device(s) (lv_indev_t
) and set up their callbacksCreate the UI: Call LVGL functions to create screens, widgets, styles, animations, events, etc.
- Call lv_timer_handler() in a loop: It handles all the LVGL-related tasks:
refresh display(s),
read input devices,
fire events based on user input (and other things),
run any animations, and
run user-created timers.
Integration example¶
This is just a brief example of how to add LVGL to a new project. For more details check out Adding LVGL to Your Project.
void main(void)
{
your_driver_init();
lv_init();
lv_tick_set_cb(my_get_millis);
lv_display_t * display = lv_display_create(320, 240);
/*LVGL will render to this 1/10 screen sized buffer for 2 bytes/pixel*/
static uint8_t buf[320 * 240 / 10 * 2];
lv_display_set_buffers(display, buf, NULL, LV_DISPLAY_RENDER_MODE_PARTIAL);
/*This callback will display the rendered image*/
lv_display_set_flush_cb(display, my_flush_cb);
/*Create widgets*/
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_label_set_text(label, "Hello LVGL!");
/*Make LVGL periodically execute its tasks*/
while(1) {
lv_timer_handler();
my_sleep(5); /*Wait 5 milliseconds before processing LVGL timer again*/
}
}
/*Return the elapsed milliseconds since startup.
*It needs to be implemented by the user*/
uint32_t my_get_millis(void)
{
return my_tick_ms;
}
/*Copy the rendered image to the screen.
*It needs to be implemented by the user*/
void my_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_buf)
{
/*Show the rendered image on the display*/
my_display_update(area, px_buf);
/*Indicate that the buffer is available.
*If DMA were used, call in the DMA complete interrupt*/
lv_display_flush_ready();
}
Displays¶
Display refers to the actual hardware. In order to connect LVGL to the hardware an lv_display_t object needs to be created and initialized.
LVGL has built-in support for many built-in drivers, but it's easy to initialize a display from scratch as well (as shown above).
LVGL also handles multiple displays at once.
Screens¶
A Screen is an LVGL widget created on a Display. It's a logical container for other widgets. A display can
have multiple screens, but there is always one active screen, which can be retrieved by using lv_screen_active().
It returns an lv_obj_t *
pointer. See Active Screen for more information.
The most common way to create a screen is by creating a Base widget with a NULL
parent. E.g.
lv_obj_t * my_screen = lv_obj_create(NULL);
A screen can be loaded like this: lv_screen_load(my_screen)
Widgets¶
Widgets are the basic building blocks of the UI. For example: Button (lv_button), Slider (lv_slider), Drop-Down List (lv_dropdown), Chart (lv_chart), etc.
Widgets can be created dynamically by calling their respective create functions. The
create function returns an lv_obj_t *
pointer which can be used to configure the widget later.
Each create function has a single parent
argument that defines which widget the new one will be added to.
For example:
lv_obj_t * my_button1 = lv_button_create(lv_screen_active());
lv_obj_t * my_label1 = lv_label_create(my_button1);
If a widget or screen is no longer needed, it can be removed by calling lv_obj_delete(my_button1)
To change the properties of widgets, two sets of functions can be used:
lv_obj_...()
functions for common properties, e.g. lv_obj_set_width(), lv_obj_add_style(), etc. These are covered in Common Widget Features.lv_<widget_type>_...()
functions for type-specific properties, e.g. lv_label_set_text(), lv_slider_set_value(), etc.
Here is an example that also shows some non-pixel units for sizes:
lv_obj_t * my_button1 = lv_button_create(lv_screen_active());
/*Set parent-sized width, and content-sized height*/
lv_obj_set_size(my_button1, lv_pct(100), LV_SIZE_CONTENT);
/*Align to the right center with 20px offset horizontally*/
lv_obj_align(my_button1, LV_ALIGN_RIGHT_MID, -20, 0);
lv_obj_t * my_label1 = lv_label_create(my_button1);
lv_label_set_text_fmt(my_label1, "Click me!");
lv_obj_set_style_text_color(my_label1, lv_color_hex(0xff0000), 0);
/*Make the text red*/
To see the full API for any widget, see its documentation at All Widgets, or check its related header file in the source code.
Events¶
Events are used to inform the application that something has happened with a Widget. You can assign one or more callbacks to a Widget which will be called when the Widget is clicked, released, dragged, being deleted, etc.
A callback is assigned like this:
lv_obj_add_event_cb(btn, my_btn_event_cb, LV_EVENT_CLICKED, NULL);
...
void my_btn_event_cb(lv_event_t * e)
{
printf("Clicked\n");
}
LV_EVENT_ALL
can be used instead of LV_EVENT_CLICKED
to invoke the callback for all events.
Event callbacks receive the argument lv_event_t * e containing the current event code and other event-related information. The current event code can be retrieved with:
lv_event_code_t code = lv_event_get_code(e);
The Widget that triggered the event can be retrieved with:
lv_obj_t * widget = lv_event_get_target_obj(e);
Learn all about Events in the Events section.
Parts and States¶
Parts¶
Widgets are built from one or more parts. For example, a button
has only one part called LV_PART_MAIN
. However, a
Slider (lv_slider) has LV_PART_MAIN
, LV_PART_INDICATOR
and LV_PART_KNOB
.
By using parts you can apply different styles to the parts of a widget.
Read the Widget's documentation to learn which parts it uses.
States¶
Widgets can be in a combination of the following states:
LV_STATE_DEFAULT
: Normal, released stateLV_STATE_CHECKED
: Toggled or checked stateLV_STATE_FOCUSED
: Focused via keypad or encoder or clicked via touchpad/mouseLV_STATE_FOCUS_KEY
: Focused via keypad or encoder but not via touchpad/mouseLV_STATE_EDITED
: Edited by an encoderLV_STATE_HOVERED
: Hovered by mouseLV_STATE_PRESSED
: Being pressedLV_STATE_SCROLLED
: Being scrolledLV_STATE_DISABLED
: Disabled
For example, if you press a Widget it will automatically go to the
LV_STATE_FOCUSED
and LV_STATE_PRESSED
states. When you
release it, the LV_STATE_PRESSED
state will be removed while the
LV_STATE_FOCUSED
state remains active.
To check if a Widget is in a given state use
lv_obj_has_state(widget, LV_STATE_...). It will return true
if the
Widget is currently in that state.
To programmatically add or remove states use:
lv_obj_add_state(widget, LV_STATE_...);
lv_obj_remove_state(widget, LV_STATE_...);
Styles¶
Initializing styles¶
Styles are carried in lv_style_t
objects. They contain properties such as
background color, border width, font, etc.
The styles can be added to a widget's given Part and State. Only their pointer is saved in the Widgets so they need to be defined as static or global variables.
Before using a style it needs to be initialized with lv_style_init(&style1). After that, properties can be added to configure the style. For example:
static lv_style_t style1;
lv_style_init(&style1);
lv_style_set_bg_color(&style1, lv_color_hex(0xa03080));
lv_style_set_border_width(&style1, 2);
See Style Properties Overview for more details.
See Style Properties to see the full list.
Adding styles to the widgets¶
After that it can be added to widgets:
lv_obj_add_style(my_button1, &style1, 0); /*0 means add to the main part and default state*/
lv_obj_add_style(my_checkbox1, &style1, LV_STATE_DISABLED); /*Add to checkbox's disabled state*/
lv_obj_add_style(my_slider1, &style1, LV_PART_KNOB | LV_STATE_PRESSED); /*Add to the slider's knob pressed state*/
Inheritance¶
Some properties (particularly the text-related ones) can be inherited. This means if a property is not set in a Widget it will be searched for in its parents. For example, you can set the font once in the screen's style and all text on that screen will inherit it by default, unless the font is specified on the widget or one of its parents.
Local styles¶
Local style properties also can be added to Widgets. This creates a style which resides inside the Widget and is used only by that Widget:
lv_obj_set_style_bg_color(slider1, lv_color_hex(0x2080bb), LV_PART_INDICATOR | LV_STATE_PRESSED);
See Styles for full details.
Subjects and Observers¶
Subjects and Observers are powerful tools to easily create data bindings.
Subjects are global lv_subject_t variables that store integer, color, string, etc. values.
Either the UI or the application can subscribe to these subjects by creating observer callbacks that are notified when the subject changes.
A widget can also subscribe to a subject. This way, when the widget is deleted, it will be automatically unsubscribed.
For some widgets, helper functions make it simple to connect them to subjects. E.g.: lv_slider_bind_value(), lv_label_bind_text().
In general, using subjects and observers is a way to connect various parts of the UI and make them dynamically react to application data changes—or allow the application to react to UI changes.
static void label_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
lv_obj_t * label = lv_observer_get_target_obj(observer);
lv_label_set_text_fmt(label, "Progress: %d", lv_subject_get_int(subject));
}
...
static lv_subject_t subject1;
lv_subject_init_int(&subject1, 10);
lv_obj_t * label1 = lv_label_create(lv_screen_active());
/*lv_label_bind_text could have been used too*/
lv_subject_add_observer_obj(&subject1, label_observer_cb, label1, NULL);
lv_obj_t * slider1 = lv_slider_create(lv_screen_active());
lv_slider_bind_value(slider1, &subject1);
lv_obj_set_y(slider1, 30);
lv_subject_set_int(&subject1, 30);
Learn more on the documentation page of Observers.