Concepts First
- LVGL: an open-source embedded graphics library written in C, designed for MCUs; it provides widgets (buttons, sliders, charts, etc.) and drawing capabilities.
- v9 vs v8: LVGL v9 is the latest major version with API differences from v8 (e.g. tick setup via
lv_tick_set_cb, renamed rotation enums); the SDK provides separate examples for both. - Display port (lv_port_disp): LVGL does not touch the screen directly; it pushes drawing buffers to the LCD through a display driver. The example initializes the driver in
lv_port_disp_init(). - Task loop: LVGL needs
lv_task_handler()called periodically to refresh the UI; the example calls it in a FreeRTOS task (or the main loop).
Example Overview
This page is based on the lvgl_v9 example in the official Bouffalo SDK (examples/lvgl/lvgl_v9), which demonstrates LVGL v9 running on Ai-M6x:
- Initializes LVGL (
lv_init), registers the tick callback (lv_tick_set_cb) and a log callback; lv_port_disp_initinitializes the LCD display driver;- Runs the benchmark demo (
lv_demo_benchmark) to evaluate rendering performance; other demos (widgets, music, scroll, stress, etc.) are listed as comments — uncomment to switch; - The main loop calls
lv_task_handler()periodically to keep the screen updated. - Sibling examples (
examples/lvgl/):lvgl_v8(v8),lvgl_v9_with_osd/lvgl_v8_with_osd(with OSD layer).
Note
The LVGL example depends on an LCD display and its driver (lcd.h / lv_port_disp). The screen model, pins, and resolution must match your board in lcd_conf_user.h; without a connected display you will not see any picture.
Operation Steps
This page needs a board with an LCD (the example drives the display through the LCD interface). Open a terminal and enter the SDK LVGL v9 example directory (prerequisite: set up the environment as in Quick Start (Linux) or Windows):
cd examples/lvgl/lvgl_v9Run the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:
make CHIP=bl616 BOARD=bl616dkConnect the board with a USB cable, hold the BOOT button (IO2 on the Ai-M61-32S-Kit), briefly press EN/RST to enter download mode, then flash (replace the serial port with the one on your computer):
make flash CHIP=bl616 COMX=/dev/ttyUSB0Open a serial tool (baud rate 2000000). After printing LVGL V9 case and the LVGL version, the screen runs the benchmark demo (lv_demo_benchmark), drawing different shapes and measuring rendering performance; the serial log ends with lvgl success.
Code Execution Flow
The complete flow from startup to rendering is shown below (loop arrows mean repeated execution):
APIs Used by the Example
lv_init()
Initializes the LVGL core library; must be called before any other LVGL API.
Parameters: none
Return: none
lv_tick_set_cb(cb)
Registers a millisecond tick callback used by LVGL for animations and timing. The example passes lv_get_time_ms_cb (which calls bflb_mtimer_get_time_ms).
Parameters:
cb: a function returning the current time in milliseconds
Return: none
lv_port_disp_init()
Initializes the LVGL display port (LCD driver), providing drawing buffers and a flush function to LVGL.
Parameters: none
Return: none
lv_demo_benchmark()
Starts the benchmark demo, drawing various shapes and measuring rendering time to evaluate graphics performance. Other demos (lv_demo_widgets, lv_demo_music, lv_demo_stress, etc.) can be enabled by uncommenting.
Parameters: none
Return: none
lv_task_handler()
LVGL's task handler: processes input, advances animations, and flushes the display. It must be called periodically from the main loop (or a timer task); the example calls it every 10 microseconds.
Parameters: none
Return: none
Complete Code
The complete source below matches the official example (examples/lvgl/lvgl_v9) verbatim. Collapsed by default, click to expand:
📜 Click to expand lvgl_v9/main.c full code
/**
* @file main.c
* @brief
*
* Copyright (c) 2021 Bouffalolab team
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
*
*/
#include "board.h"
#include "bflb_gpio.h"
#include "bflb_l1c.h"
#include "bflb_mtimer.h"
#include "lcd.h"
#if defined(CONFIG_FREERTOS)
#include <FreeRTOS.h>
#include "task.h"
#endif
#include "lvgl.h"
#include "lv_port_disp.h"
#ifdef CONFIG_LVGL9_PORT_INDEV
#include "lv_port_indev.h"
#endif
#include "demos/lv_demos.h"
#define DBG_TAG "MAIN"
#include "log.h"
#if defined(CONFIG_FREERTOS)
static TaskHandle_t lvgl_handle;
#endif
/* lvgl tick cb */
uint32_t lv_get_time_ms_cb(void)
{
return (uint32_t)bflb_mtimer_get_time_ms();
}
#if (LV_USE_LOG)
/* lvgl log cb */
void lv_log_print_cb(lv_log_level_t level, const char *buf)
{
switch (level) {
case LV_LOG_LEVEL_TRACE:
case LV_LOG_LEVEL_INFO:
LOG_RI("[LVGL] %s", buf);
break;
case LV_LOG_LEVEL_WARN:
case LV_LOG_LEVEL_USER:
LOG_RW("[LVGL] %s", buf);
break;
case LV_LOG_LEVEL_ERROR:
LOG_RE("[LVGL] %s", buf);
break;
default:
LOG_RW("[LVGL] %s", buf);
break;
}
}
#endif
void lvgl_main(void *param)
{
/* lvgl init */
lv_init();
/* tick cb */
lv_tick_set_cb(lv_get_time_ms_cb);
#if (LV_USE_LOG)
/* log cb */
lv_log_register_print_cb(lv_log_print_cb);
#endif
/* lcd init */
lv_port_disp_init();
#ifdef CONFIG_LVGL9_PORT_INDEV
lv_port_indev_init();
#endif
// lv_disp_set_rotation(NULL, LV_DISP_ROTATION_90);
lv_demo_benchmark();
// lv_demo_flex_layout();
// lv_demo_keypad_encoder();
// lv_demo_multilang();
// lv_demo_music();
// lv_demo_render(LV_DEMO_RENDER_SCENE_FILL, LV_OPA_50);
// lv_demo_scroll();
// lv_demo_stress();
// lv_demo_transform();
// lv_demo_widgets();
lv_task_handler();
LOG_I("lvgl success\r\n");
while (1) {
lv_task_handler();
bflb_mtimer_delay_us(10);
}
}
int main(void)
{
board_init();
LOG_I("LVGL V9 case\r\n");
LOG_I("LVGL VER: %d.%d.%d\r\n", LVGL_VERSION_MAJOR, LVGL_VERSION_MINOR, LVGL_VERSION_PATCH);
#if defined(CONFIG_FREERTOS)
xTaskCreate(lvgl_main, (char *)"test_task", 2048, NULL, configMAX_PRIORITIES - 2, &lvgl_handle);
vTaskStartScheduler();
#else
lvgl_main(NULL);
#endif
}FAQ
The screen stays blank
First confirm the board actually has an LCD connected and that the screen model, resolution, and pins in lcd_conf_user.h match your board; also make sure FreeRTOS is enabled (the example runs LVGL in a FreeRTOS task by default).
How do I switch to another demo
Comment out lv_demo_benchmark() in lvgl_main, uncomment the demo you want (e.g. lv_demo_widgets()), then rebuild and flash.
What is different between v9 and v8 APIs
The main differences are naming and initialization: v9 uses lv_tick_set_cb for the tick and LV_DISP_ROTATION_* enums; v8 uses lv_tick_inc and LV_DISP_ROT_*. For new code, prefer v9.
Have questions?
For any other questions, visit the unified Q&A and discussion board: Ai-Thinker Discussions

