模板: RP2350-Touch-AMOLED-1.64-LVGL
来自Waveshare Wiki
C
示例效果
本例显示的效果为四个可以通过触摸屏滑动切换的界面。
- 第一个界面
- 第二个界面
- 第三个界面
示例简介
本例用于测试 LVGL 控件交互、样式美化等, LVGL 的具体开发请参考LVGL开发文档。
实现功能
- 本例使用 DMA 传输颜色数据到 QSPI 总线,降低 CPU 的利用率,在进行简单交互时可以控制 CPU 的占用率在 50% 以下,内存占用在 35% 以下。
- 本例使用触摸屏切换界面,实现实时展示六轴传感器、电池电压数据,展示了 LVGL 控件的简单运用。
编译运行
- Pico VSCode
- Windows
- 参考Windows环境搭建教程完成环境搭建
- 打开 VS 2022->工具栏->命令行->开发者 Powershell
- 将你的 pico-sdk 所在绝对地址设置为 PICO_SDK_PATH,如我的 pico-sdk 地址为“D:\pico\pico-sdk”
setx PICO_SDK_PATH "D:\pico\pico-sdk"
- 下载例程,进入源码目录,如果 build 目录已经存在,则可以直接进入。如没有则创建该目录:
mkdir build cd build
- 执行 cmake ,自动生成 Makefile 文件:
cmake -G "NMake Makefiles" ..
- 执行 nmake 生成可执行文件,然后在终端中输入:
nmake
编译好后会生成一个 .uf2 格式的文件。 - 按住板上的 boot 按键,通过 Micro USB 线将板子接到电脑的 USB 接口,然后松开按键,电脑会将 pico 识别为一个可移动盘,最后将编译生成的 .uf2 格式的文件复制到 pico 即可
- Ubuntu
- 参考Pico入门使用手册的 Chapter 2. The SDK 完成环境搭建
- 打开一个 terminal,设置环境变量 PICO_SDK_PATH 的值为 pico-sdk 所在绝对地址,如我的 pico-sdk 地址为“/home/pico/pico-sdk”
nano ~/.bashrc #将以下内容添加到最后一行 export PICO_SDK_PATH="/home/pico/pico-sdk"
- 设置完后保存退出,使配置立即生效
source ~/.bashrc
- 下载例程,进入源码目录,如果 build 目录已经存在,则可以直接进入。如没有则创建该目录:
mkdir build cd build
- 执行 cmake ,自动生成 Makefile 文件:
cmake ..
- 执行 nmake 生成可执行文件,然后在终端中输入:
nmake
编译好后会生成一个 .uf2 格式的文件。 - 按住板上的 boot 按键,通过 Micro USB 线将板子接到电脑的 USB 接口,然后松开按键,电脑会将 pico 识别为一个可移动盘,最后将编译生成的 .uf2 格式的文件复制到 pico 即可
源码解析
源码结构
- LVGL 库的源码位于工程文件夹的 lib/lvgl ,使用的版本号为8.1,二次开发请参考对应版本的开发文档。
- LVGL 库的相关设置在工程文件夹的 examples/inc/lv_conf.h 中,可以设置显示刷新频率、系统占用数据等。

- LVGL 库的应用代码位于工程文件夹的 examples/src/LVGL_example.c 。

LVGL 初始化
在使用 LVGL 图像库之前,您需要先初始化 LVGL。
- LVGL 库的初始化函数
- 代码位置:examples/src/LVGL_example.c
- 实现功能:主要用于初始化 LVGL 所需的硬件和结构体变量。
LVGL_Init();
- LVGL 库核心初始化
- 代码位置:examples/src/LVGL_example.c
/*2.Init LVGL core*/ lv_init();
LVGL 运行
LVGL 库定时调用心跳接口函数 lv_tick_inc 来通知LVGL过去的时间,以便 LVGL 能够更新其内部的时间状态,处理与时间相关的任务,例如动画、定时器等。在主函数的循环中还需要调用 lv_task_handler 函数,以便 LVGL 及时处理事件和任务,保证用户界面的响应和刷新。
- LVGL 心跳接口
- 代码位置:examples/src/LVGL_example.c
- 实现方式:需要确保 lv_task_handler 的优先级低于 lv_tick_inc 的优先级,所以在本例中 lv_tick_inc 在定时器回调函数中调用。
//每5ms调用一次定时器回调函数
add_repeating_timer_ms(5, repeating_lvgl_timer_callback, NULL, &lvgl_timer);
static bool repeating_lvgl_timer_callback(struct repeating_timer *t)
{
lv_tick_inc(5);
return true;
}
- LVGL 任务处理器
- 代码位置:examples/src/LCD_XinXX_LVGL_test.c
- 实现方式:要处理 LVGL 的任务,需要定期调用 lv_timer_handler(),本例中在主函数的循环中进行调用。
int main()
{
...
while(1)
{
lv_task_handler();
DEV_Delay_ms(5);
...
}
}
LVGL 显示
要实现 LVGL 显示,必须初始化一个显示驱动,并设置显示驱动的各个属性,例如,颜色格式、绘制缓冲区、渲染模式以及显示回调函数。在每个 LV_DISP_DEF_REFR_PERIOD(在 lv_conf.h 中设置),LVGL 会检测 UI 上是否发生了一些需要重绘的事情。例如,按下按钮、更改图表、发生动画等。需要重新绘制时,LVGL 调用显示回调函数完成图像在刷新区的绘制。
- LVGL 显示刷新率设置
- 代码位置:examples/inc/lv_conf.h
- 设置方式:在 lv_conf.h 中还能设置显示缓冲区刷新频率的时间,可以修改这个定义来改变屏幕的刷新时间。
#define LV_DISP_DEF_REFR_PERIOD 10 // 单位:ms,这里为10ms
- LVGL 显示颜色设置
- 代码位置:examples/inc/lv_conf.h
- 设置目的:由于 lv_color_t 结构体在默认状态下所构建的像素颜色储存方式与本例需要传输的数据不一致,直接进行传输会导致显示的图像出现色差。
#define LV_COLOR_16_SWAP 1
- LVGL 显示相关变量定义
- 代码位置:examples/src/LVGL_example.c
- 实现功能:定义显示驱动 disp_drv,绘制缓冲区 disp_buf。本例绘制缓冲区设置为整个屏幕显示面积,能够在降低大面积刷屏锯齿的同时有效提高屏幕刷新率。
static lv_disp_drv_t disp_drv; static lv_disp_draw_buf_t disp_buf; static lv_color_t buf0[DISP_HOR_RES * DISP_VER_RES];
- LVGL 显示设备注册
- 代码位置:examples/src/LVGL_example.c
- 实现功能:根据设计需求完善 LVGL 库核心结构体变量,初始化显示驱动 disp_drv,并设置绘制缓冲区,该缓冲区是 LVGL 用来渲染屏幕内容的简单数组。一旦渲染准备就绪,绘制缓冲区的内容将使用显示驱动程序中设置的 disp_drv_flush_cb 函数发送到显示器。
/*3.Init LVGL display*/ lv_disp_draw_buf_init(&disp_buf, buf0, NULL, DISP_HOR_RES * DISP_VER_RES); lv_disp_drv_init(&disp_drv); disp_drv.flush_cb = disp_flush_cb; disp_drv.draw_buf = &disp_buf; disp_drv.hor_res = DISP_HOR_RES; disp_drv.ver_res = DISP_VER_RES; lv_disp_t *disp= lv_disp_drv_register(&disp_drv);
- LVGL 显示回调函数
- 代码位置:examples/src/LVGL_example.c
- 实现功能:主要完成图像在刷新区的绘制。
void disp_flush( lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p )
参数:
lv_disp_drv_t *disp_drv: 显示驱动结构体指针,包含了与显示相关的信息和函数指针。该参数常用于通知刷新完成
const lv_area_t *area : 区域结构体指针,包含待刷新区域的位置信息。在本例中,用于创建 TFT 显示的窗口
lv_color_t *color_p : 颜色结构体指针,表示要在刷新区域内显示的颜色数据。在本例中,作为DMA输入读取地址将数据传输到QSPI总线,完成图像的绘制
- LVGL 显示回调函数实现
- 代码位置:examples/src/LVGL_example.c
- 实现方式:在本例中为了最大化降低处理器的利用率,使用 DMA 来进行颜色数据的传输,设置 color_p 作为读地址,QSPI 接口的 TX FIFO 寄存器为写地址。
static void disp_flush_cb(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
{
AMOLED_1IN64_SetWindows(area->x1, area->y1, area->x2+1 , area->y2+1); // Set the LVGL interface display position
QSPI_Select(qspi);
QSPI_Pixel_Write(qspi, 0x2c);
channel_config_set_dreq(&c, pio_get_dreq(qspi.pio, qspi.sm, true));
dma_channel_configure(dma_tx,
&c,
&qspi.pio->txf[qspi.sm],
color_p, // read address
((area->x2 + 1 - area->x1) * (area->y2 + 1 - area->y1))*2,
true);// Start DMA transfer
}
- LVGL 刷新完成通知实现
- 代码位置:examples/src/LVGL_example.c
- 实现功能:每一次图像刷新完成后都需要通知 LVGL 核心,以便 LVGL 准备下个刷新图像的渲染。
- 实现方式:本例在 DMA 传输完成中断服务函数中通知 LVGL 图像刷新完成
static void dma_handler(void)
{
if (dma_channel_get_irq0_status(dma_tx))
{
dma_channel_acknowledge_irq0(dma_tx);
QSPI_Deselect(qspi);
lv_disp_flush_ready(&disp_drv); // Indicate you are ready with the flushing
}
}
LVGL 输入
在 LVGL 中,允许用户注册输入设备,如触摸板、鼠标、键盘或编码器等设备。用户可以通过这些输入设备控制用户界面,实现更好的交互。
- LVGL 调用输入设备回调函数的频率设置
- 代码位置:examples/inc/lv_conf.h
- 设置方式: LVGL 默认每 30ms 调用一次输入设备回调函数来更新输入设备所触发的事件,可以在 lv_conf.h 中进行设置。
#define LV_INDEV_DEF_READ_PERIOD 30 // 单位:ms,这里为30ms
- LVGL 输入设备注册
- 代码位置:examples/src/LVGL_example.c
- 设置方式:注册触摸屏设备 indev_ts 并初始化
/*4.Init touch screen as input device*/ lv_indev_drv_init(&indev_ts); // 设备初始化 indev_ts.type = LV_INDEV_TYPE_POINTER; // 注册为触摸屏设备 indev_ts.read_cb = ts_read_cb; // 设置回调函数 lv_indev_t * ts_indev = lv_indev_drv_register(&indev_ts); // 注册设备 multicore_launch_core1(core1_entry); //用core1轮询触摸屏状态
- LVGL 的输入设备回调函数
- 代码位置:examples/src/LVGL_example.c
- 实现功能:主要用于更新输入事件。
static void ts_read_cb(lv_indev_drv_t * drv, lv_indev_data_t*data); 参数: lv_indev_drv_t *indev_drv: LVGL 中的输入设备驱动结构体指针。在本例中,该结构体为触摸屏输入设备驱动 lv_indev_data_t *data : LVGL 中的输入设备数据结构体指针。在本例中,该结构体用于存储输入设备的状态和数据,包括当前的触摸状态(按下或释放)以及触摸点的坐标
- 触摸屏输入设备的回调函数实现
- 代码位置:examples/src/LVGL_example.c
- 实现方式:主要是通过core1轮询更新触摸屏的触摸状态和触摸点坐标。i2c_lock作为I2C通讯互斥锁,确保I2C通讯完整性。
void core1_entry()
{
while(true) {
while(i2c_lock)__asm__ volatile("nop");
I2C_LOCK();
bool press = FT3168_Get_Point();
I2C_UNLOCK();
ts_x = FT3168.x_point;
ts_y = FT3168.y_point;
if(press)
{
ts_act = LV_INDEV_STATE_PRESSED;
}
sleep_ms(15);
}
}
static void ts_read_cb(lv_indev_drv_t * drv, lv_indev_data_t*data)
{
data->point.x = ts_x;
data->point.y = ts_y;
data->state = ts_act;
ts_act = LV_INDEV_STATE_RELEASED;
}
LVGL 控件布局
在 LVGL 中,我们能够建立各种不同的用户界面。界面的基本组成部分是对象,也称为控件(Widgets),比如按钮(Button)、标签(Label)、图像(Image)、列表(List)、图表或文本区域。在一个界面中,可以同时创建多个控件,并且我们可以设置它们的位置、尺寸、父对象、样式以及事件处理程序等基本属性。
- LVGL 控件初始化
- 代码位置:examples/src/LVGL_example.c
- 实现功能:主要用于风格化控件和布局控件。
void Widgets_Init(void);
- LVGL 创建图块
- 代码位置:examples/src/LVGL_example.c
- 实现功能:Tile 视图是一个容器对象,其元素(称为图块)可以按网格形式排列。用户可以通过滑动来在各个图块之间导航。调用 lv_tileview_add_tile(tileview, row_id, col_id, dir) 在第 row_id 行和第 col_id 列上创建一个新图块。dir可以是 LV_DIR_LEFT/RIGHT/TOP/BOTTOM/HOR/VER/ALL 或值,以便通过滑动移动到给定方向的相邻图块。
//在 (0,0) 创建一个图块,支持向下滑动到 (0,1) tile1 = lv_tileview_add_tile(tv, 0, 0, LV_DIR_BOTTOM);
- LVGL 创建控件
- 代码位置:examples/src/LVGL_example.c
- 实现功能:创建控件,不同控件需要使用不同的函数接口,可以选择父对象进行创建。
//创建一个控件,其中 tab1 为该按键的父对象,可以替换为list、title等可以有子对象的控件 sw = lv_switch_create(tab1);
- LVGL 控件的对齐定位
- 代码位置:examples/src/LVGL_example.c
- 实现功能:使控件能够基于参考点进行偏移定位。控件对齐偏移的参考点控件的中心。
- 对齐标准: LVGL 库具备内部对齐和外部对齐两种方式。默认以左上角作为原点,向左为水平方向的正方向,向下为垂直方向的正方向。
- LVGL 控件切换字体大小
- 代码位置:examples/inc/lv_conf.h、examples/src/LVGL_example.c
- 实现功能:在实际运用时一个界面可能需要运用多种字体大小,可以在 lv_conf.h 中使能多种字体大小,且可以设置默认字体大小。设置字体大小时需要对控件风格化,使控件能够按照设置的风格进行渲染。利用 lv_obj_add_style 函数可以实现对控件各个部位在不同状态下的渲染。
#define LV_FONT_MONTSERRAT_16 1 // 使能16号字体 #define LV_FONT_MONTSERRAT_18 1 // 使能18号字体 #define LV_FONT_DEFAULT &lv_font_montserrat_18 // 设置默认字体大小为18号 static lv_style_t style_label; lv_style_init(&style_label); // 初始化风格 lv_style_set_text_font(&style_label, &lv_font_montserrat_16); // 设置字体大小为16号 lv_obj_add_style(label,&style_label,0); // 设置label主题的风格
- LVGL 控件事件处理
- 代码位置:examples/src/LVGL_example.c
- 实现功能:在 LVGL 中,可以给控件添加事件处理回调函数,使控件发生被点击、滚动、重绘等事件时,触发事件从而进入事件处理回调函数。在程序中调用 lv_obj_add_event_cb(obj, event_cb, filter, user_data)函数,为控件 obj 添加事件 filter 的处理函数 event_cb,当控件 obj 触发 filter 事件时,系统会自动调用 event_cb 函数。最后一个参数是指向事件中可用的任何自定义数据的指针。
//为控件 sw 添加事件 LV_EVENT_VALUE_CHANGED 的处理函数 sw_event_cb lv_obj_add_event_cb(sw, sw_event_cb,LV_EVENT_VALUE_CHANGED,NULL);

