Skip to content

概念先知道

  • LVGL:一个开源的嵌入式图形库(LittlevGL),用 C 语言编写,专为 MCU 设计,提供控件(按钮、滑块、图表等)和绘图能力。
  • v9 与 v8:LVGL v9 是最新大版本,API 与 v8 有差异(例如 tick 设置改用 lv_tick_set_cb、旋转枚举改名);两者在 SDK 中各有独立例程。
  • 显示驱动(lv_port_disp):LVGL 不直接操作屏幕,而是通过显示驱动接口把绘图缓冲送到 LCD。SDK 例程在 lv_port_disp_init() 里完成屏幕初始化。
  • 任务循环:LVGL 需要周期性调用 lv_task_handler() 来刷新界面;例程在 FreeRTOS 任务或主循环中不断调用它。

例程功能简介

本页对应博流官方 SDK 的 lvgl_v9 例程(examples/lvgl/lvgl_v9),演示 LVGL v9 在 Ai-M6x 上运行:

  • 初始化 LVGL(lv_init),注册 tick 回调(lv_tick_set_cb)与日志回调;
  • lv_port_disp_init 初始化 LCD 显示驱动;
  • 运行 benchmark 演示lv_demo_benchmark),评估 MCU 渲染性能;其余演示(widgets、music、scroll、stress 等)以注释形式列出,取消注释即可切换;
  • 主循环周期调用 lv_task_handler() 持续刷新画面。
  • 同族例程examples/lvgl/):lvgl_v8(v8 版本)、lvgl_v9_with_osd / lvgl_v8_with_osd(带 OSD 图层版本)。

注意

LVGL 例程依赖 LCD 显示屏与对应驱动lcd.h / lv_port_disp)。不同开发板屏幕型号、引脚和分辨率可能不同,需要按板子的 lcd_conf_user.h 配置核对;没有接屏的板子无法看到画面。

操作步骤

1
进入例程目录

本页需要一块带屏幕的开发板(例程通过 LCD 接口驱动显示屏)。在终端进入 SDK 的 LVGL v9 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/lvgl/lvgl_v9
2
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
运行验证

打开串口助手(波特率 2000000)。程序打印 LVGL V9 case 和 LVGL 版本号后,屏幕开始运行 benchmark 性能测试lv_demo_benchmark),不断绘制不同图形并统计渲染帧率/耗时;串口打印 lvgl success

代码执行流程

例程从启动到画面刷新的完整流程如下(图中的循环箭头表示反复执行):

例程调用的 API 介绍

lv_init()

初始化 LVGL 核心库,必须在任何其他 LVGL API 之前调用。

参数:无

返回值:无

lv_tick_set_cb(cb)

注册毫秒 tick 回调,LVGL 用它计时动画与刷新。例程传入 lv_get_time_ms_cb(内部调用 bflb_mtimer_get_time_ms)。

参数

  • cb:返回当前毫秒数的函数

返回值:无

lv_port_disp_init()

初始化 LVGL 的显示端口(LCD 驱动),为 LVGL 提供绘图缓冲区与刷新函数。

参数:无

返回值:无

lv_demo_benchmark()

启动 benchmark 演示,循环绘制各类图形并统计渲染耗时,用于评估设备图形性能。其他演示(lv_demo_widgetslv_demo_musiclv_demo_stress 等)取消注释即可切换。

参数:无

返回值:无

lv_task_handler()

LVGL 的任务处理函数:处理输入、推进动画并刷新显示。必须在主循环(或定时任务)中周期调用,例程每 10 微秒调用一次。

参数:无

返回值:无

完整代码

以下为 lvgl_v9/main.c 完整源码,与官方示例(examples/lvgl/lvgl_v9)一致,默认折叠,点击展开:

📜 点击展开 lvgl_v9/main.c 完整代码
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

屏幕没有画面

先确认开发板确实接了 LCD 屏,且 lcd_conf_user.h 中的屏幕型号、分辨率、引脚与板子一致;再确认编译时 CONFIG_FREERTOS 相关配置正常(例程默认 FreeRTOS 任务方式运行 LVGL)。

如何切换成其他演示

lvgl_main 中的 lv_demo_benchmark() 注释掉,取消注释想要的演示(如 lv_demo_widgets()),重新编译烧录即可。

v9 与 v8 的 API 有什么区别

主要差异在命名与部分初始化方式:v9 用 lv_tick_set_cb 设置 tick、旋转枚举为 LV_DISP_ROTATION_*;v8 用 lv_tick_inc 驱动 tick、旋转枚举为 LV_DISP_ROT_*。写新代码建议直接用 v9。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

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