Skip to content

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_init initializes 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

1
Enter the Example Directory

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_v9
2
Build the Project

Run the build command. The Ai-M62 (BL616) and Ai-M61 (BL618) belong to the same series, so both use bl616:

make CHIP=bl616 BOARD=bl616dk
3
Flash the Firmware

Connect 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/ttyUSB0
4
Run and Verify

Open 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
c
/**
 * @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

Released under the MIT License. Build Time 2026-09-11 14:52:23