模板: LCD2 for Pico use

来自Waveshare Wiki
跳转至: 导航搜索

硬件连接

连接Pico/Pico2的时候,请注意对应方向不要接反。可以观察模块上有USB丝印的一端与Pico/Pico2的USB接口一端来判断方向(也可以根据模块上的排母的引脚标号与Pico/Pico2的引脚标号判断)
您可以对照以下表格连线。

Pico连接引脚对应关系
LCD Pico/Pico2 功能
VCC VSYS 电源输入
GND GND 电源地
DIN GP11 SPI通信MOSI引脚,从设备数据输入
CLK GP10 SPI通信SCK引脚,从设备时钟输入
CS GP9 SPI片选引脚(低电平有效)
DC GP8 数据/命令控制引脚(高电平数据,低电平命令)
RST GP12 外部复位引脚(低电平有效)
BL GP13 背光控制
KEY0 GP15 用户按键KEY0
KEY1 GP17 用户按键KEY1
KEY2 GP2 用户按键KEY2
KEY3 GP3 用户按键KEY3

Pico-LCD-2-connect.jpg

基础介绍

MicroPython系列
【MicroPython】Pico的基础介绍与环境安装

【MicroPython】machine.Pin类函数详解
【MicroPython】machine.PWM类函数详解
【MicroPython】machine.ADC类函数详解
【MicroPython】machine.UART类函数详解
【MicroPython】machine.I2C类函数详解
【MicroPython】machine.SPI类函数详解

C/C++系列
【C/C++】 Windows教程1——环境搭建

【C/C++】 Windows教程2——创建工程

教程名3
列表3
教程名4
列表4
教程名5
列表5
教程名6
列表6
教程名7
列表7
教程名8
列表8

程序下载

打开树莓派终端,执行:
方法一:从我们官网下载,推荐使用。

sudo apt-get install p7zip-full
cd ~
sudo wget  https://www.waveshare.net/w/upload/2/28/Pico_code.7z
7z x Pico_code.7z -o./Pico_code
cd ~/Pico_code
cd c/build/

例程使用

C部分

  • 以下教程为在树莓派上操作,但由于cmake的多平台、可移植的特点,在PC上也是能成功编译,但操作略有不同,需要您自行判断。

进行编译,请确保在c目录:

cd ~/Pico_code/c/

创建并进入build目录,并添加SDK: 其中 ../../pico-sdk 是你的SDK的目录。 我们示例程序中是有build的,直接进入即可

cd build
export PICO_SDK_PATH=../../pico-sdk
(注意:务必写对你自己的SDK所在路径)

执行cmake自动生成Makefile文件

#Pico
cmake -DPICO_BOARD=pico -DPICO_PLATFORM=rp2040 ..
#Pico2
cmake -DPICO_BOARD=pico2 -DPICO_PLATFORM=rp2350 ..

打开c文件夹下面的main.c,可以更改你需要的例程,该例程可以驱动本公司Pico系列的显示屏且源码会一直更新,请选择对应的LCD或OLED测试函数,注释掉无关函数
Pico-lcd-0.96-img-main.png
执行make生成可执行文件,第一次编译时间比较久

make -j9

编译完成,会生成uf2文件。 按住Pico/Pico2板上的按键,将Pico/Pico2通过Micro USB线接到树莓派的USB接口,然后松开按键。接入之后,树莓派会自动识别到一个可移动盘,将build文件夹下的main.uf2 文件复制到识别的可移动盘中即可。

#Pico
cp main.uf2 /media/pi/RPI-RP2/
#Pico2
cp main.uf2 /media/pi/RP2350

Python部分

windows环境下的使用

  • 1.按住Pico板上的BOOTSET按键,将Pico/Pico2通过Micro USB线接到电脑的USB接口,待电脑识别出一个可移动硬盘后,松开按键。
  • 2.将python目录中的.uf2 格式文件复制到刚刚识别的可移动盘中
  • 3.打开Thonny IDE(注意:要使用最新版本的Thonny,否则是没有Pico的支持包的,当前Windows下的最新版本为v3.3.3)
  • 4.点击工具->设置->解释器,如图所示选择Pico/Pico2及对应的端口

Pico-lcd-0.96-img-config.png

  • 5.文件->打开->python/Pico-LCD-2/Pico-LCD-2.py,点击运行即可,如下图所示:

Pico-lcd-0.96-img-run.png
本例程提供了一个简单的程序...

树莓派环境下的使用

  • 1.刷固件的过程与Windows上一样,你可以选择在PC或者树莓派上将 .uf2 格式文件拷入Pico/Pico2中。
  • 2.在树莓派上打开Thonny IDE (点击树莓logo -> Programming -> Thonny Python IDE ),你可以在Help->About Thonny查看版本信息

以确保你的版本是有Pico支持包的,同样你可以点击Tools -> Options... -> Interpreter选择MicroPython(Raspberry Pi Pico 和ttyACM0端口
如图所示:
Pico-lcd-0.96-img-config2.png
如果你当前的Thonny版本没有pico支持包,输入以下指令来更新Thonny IDE

sudo apt upgrade thonny
  • 3.点击File->Open...->python/Pico-LCD-2/Pico-LCD-2.py,运行脚本即可

代码简析

如果您以前使用过我们的SPI屏幕应该会对这份例程比较熟悉

C

底层硬件接口

我们进行了底层的封装,由于硬件平台不一样,内部的实现是不一样的,如果需要了解内部实现可以去对应的目录中查看
在DEV_Config.c(.h)可以看到很多定义,在目录:...\c\lib\Config

  • 数据类型:
#define UBYTE   uint8_t
#define UWORD   uint16_t
#define UDOUBLE uint32_t
  • 模块初始化与退出的处理:
void DEV_Module_Init(void);
void DEV_Module_Exit(void);
注意:
1.这里是处理使用液晶屏前与使用完之后一些GPIO的处理。
  • GPIO读写:
void 	DEV_Digital_Write(UWORD Pin, UBYTE Value);
UBYTE 	DEV_Digital_Read(UWORD Pin);
  • SPI写数据
void DEV_SPI_WriteByte(UBYTE Value);

上层应用

对于屏幕而言,如果需要进行画图、显示中英文字符、显示图片等怎么办,这些都是上层应用做的。这有很多小伙伴有问到一些图形的处理,我们这里提供了一些基本的功能 在如下的目录中可以找到GUI,在目录:..\c\lib\GUI\GUI_Paint.c(.h)
LCD PICO GUI 1.png
在如下目录下是GUI依赖的字符字体,在目录:RaspberryPi\c\lib\Fonts
LCD rpi Font.png

  • 新建图像属性:新建一个图像属性,这个属性包括图像缓存的名称、宽度、高度、翻转角度、颜色
void Paint_NewImage(UWORD *image, UWORD Width, UWORD Height, UWORD Rotate, UWORD Color)
参数:
 	image : 图像缓存的名称,实际上是一个指向图像缓存首地址的指针;
 	Width : 图像缓存的宽度;
 	Height: 图像缓存的高度;
 	Rotate:图像的翻转的角度
 	Color :图像的初始颜色;
  • 选择图像缓存:选择图像缓存,选择的目的是你可以创建多个图像属性,图像缓存可以存在多个,你可以选择你所创建的每一张图像
void Paint_SelectImage(UBYTE *image)
参数:
 	image: 图像缓存的名称,实际上是一个指向图像缓存首地址的指针;
  • 图像旋转:设置选择好的图像的旋转角度,最好使用在Paint_SelectImage()后,可以选择旋转0、90、180、270
void Paint_SetRotate(UWORD Rotate)
参数:
 	Rotate: 图像选择角度,可以选择ROTATE_0、ROTATE_90、ROTATE_180、ROTATE_270分别对应0、90、180、270度
【说明】不同选择角度下,坐标对应起始像素点不同,这里以1.14为例,四张图,按顺序为0°, 90°, 180°, 270°。仅做为参考
Pico LCD Rotate.jpg
  • 图像镜像翻转:设置选择好的图像的镜像翻转,可以选择不镜像、关于水平镜像、关于垂直镜像、关于图像中心镜像。
void Paint_SetMirroring(UBYTE mirror)
参数:
 	mirror: 图像的镜像方式,可以选择MIRROR_NONE、MIRROR_HORIZONTAL、MIRROR_VERTICAL、MIRROR_ORIGIN分别对应不镜像、关于水平镜像、关于垂直镜像、关于图像中心镜像
  • 设置点在缓存中显示位置和颜色:这里是GUI最核心的一个函数、处理点在缓存中显示位置和颜色;
void Paint_SetPixel(UWORD Xpoint, UWORD Ypoint, UWORD Color)
参数:
 	Xpoint: 点在图像缓存中X位置
 	Ypoint: 点在图像缓存中Y位置
 	Color : 点显示的颜色
  • 图像缓存填充颜色:把图像缓存填充为某颜色,一般作为屏幕刷白的作用
void Paint_Clear(UWORD Color)
参数:
 	Color: 填充的颜色
  • 图像缓存部分窗口填充颜色:把图像缓存的某部分窗口填充为某颜色,一般作为窗口刷白的作用,常用于时间的显示,刷白上一秒
void Paint_ClearWindows(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color)
参数:
 	Xstart: 窗口的X起点坐标
 	Ystart: 窗口的Y起点坐标
 	Xend: 窗口的X终点坐标
 	Yend: 窗口的Y终点坐标
 	Color: 填充的颜色
  • 画点:在图像缓存中,在(Xpoint, Ypoint)上画点,可以选择颜色,点的大小,点的风格
void Paint_DrawPoint(UWORD Xpoint, UWORD Ypoint, UWORD Color, DOT_PIXEL Dot_Pixel, DOT_STYLE Dot_Style)
参数:
 	Xpoint: 点的X坐标
 	Ypoint: 点的Y坐标
 	Color: 填充的颜色
 	Dot_Pixel: 点的大小,提供默认的8种大小点
 	 	 typedef enum {
 	 	 	 DOT_PIXEL_1X1  = 1,	// 1 x 1
 	 	 	 DOT_PIXEL_2X2  , 		// 2 X 2
 	 	 	 DOT_PIXEL_3X3  , 	 	// 3 X 3
 	 	 	 DOT_PIXEL_4X4  , 	 	// 4 X 4
 	 	 	 DOT_PIXEL_5X5  , 		// 5 X 5
 	 	 	 DOT_PIXEL_6X6  , 		// 6 X 6
 	 	 	 DOT_PIXEL_7X7  , 		// 7 X 7
 	 	 	 DOT_PIXEL_8X8  , 		// 8 X 8
 	 	} DOT_PIXEL;
 	Dot_Style: 点的风格,大小扩充方式是以点为中心扩大还是以点为左下角往右上扩大
 	 	typedef enum {
 	 	   DOT_FILL_AROUND  = 1,		
 	 	   DOT_FILL_RIGHTUP,
 	 	} DOT_STYLE;
  • 画线:在图像缓存中,从 (Xstart, Ystart) 到 (Xend, Yend) 画线,可以选择颜色,线的宽度,线的风格
void Paint_DrawLine(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color, LINE_STYLE Line_Style , LINE_STYLE Line_Style)
参数:
 	Xstart: 线的X起点坐标
 	Ystart: 线的Y起点坐标
 	Xend: 线的X终点坐标
 	Yend: 线的Y终点坐标
 	Color: 填充的颜色
 	Line_width: 线的宽度,提供默认的8种宽度
 	 	typedef enum {
 	 	 	 DOT_PIXEL_1X1  = 1,	// 1 x 1
 	 	 	 DOT_PIXEL_2X2  , 		// 2 X 2
 	 	 	 DOT_PIXEL_3X3  ,		// 3 X 3
 	 	 	 DOT_PIXEL_4X4  ,		// 4 X 4
 	 	 	 DOT_PIXEL_5X5  , 		// 5 X 5
 	 	 	 DOT_PIXEL_6X6  , 		// 6 X 6
 	 	 	 DOT_PIXEL_7X7  , 		// 7 X 7
 	 	 	 DOT_PIXEL_8X8  , 		// 8 X 8
 	 	} DOT_PIXEL;
 	 Line_Style: 线的风格,选择线是以直线连接还是以虚线的方式连接
 	 	typedef enum {
 	 	 	 LINE_STYLE_SOLID = 0,
 	 	 	 LINE_STYLE_DOTTED,
 	 	} LINE_STYLE;
  • 画矩形:在图像缓存中,从 (Xstart, Ystart) 到 (Xend, Yend) 画一个矩形,可以选择颜色,线的宽度,是否填充矩形内部
void Paint_DrawRectangle(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend, UWORD Color, DOT_PIXEL Line_width, DRAW_FILL Draw_Fill)
参数:
 	Xstart: 矩形的X起点坐标
 	Ystart: 矩形的Y起点坐标
 	Xend: 矩形的X终点坐标
 	Yend: 矩形的Y终点坐标
 	Color: 填充的颜色
 	Line_width: 矩形四边的宽度,提供默认的8种宽度
 	 	typedef enum {
 	 	 	 DOT_PIXEL_1X1  = 1,	// 1 x 1
 	 	 	 DOT_PIXEL_2X2  , 		// 2 X 2
 	 	 	 DOT_PIXEL_3X3  ,		// 3 X 3
 	 	 	 DOT_PIXEL_4X4  ,		// 4 X 4
 	 	 	 DOT_PIXEL_5X5  , 		// 5 X 5
 	 	 	 DOT_PIXEL_6X6  , 		// 6 X 6
 	 	 	 DOT_PIXEL_7X7  , 		// 7 X 7
 	 	 	 DOT_PIXEL_8X8  , 		// 8 X 8
 	 	} DOT_PIXEL;
 	Draw_Fill: 填充,是否填充矩形的内部
 	 	typedef enum {
 	 	 	 DRAW_FILL_EMPTY = 0,
 	 	 	 DRAW_FILL_FULL,
 	 	} DRAW_FILL;
  • 画圆:在图像缓存中,以 (X_Center Y_Center) 为圆心,画一个半径为Radius的圆,可以选择颜色,线的宽度,是否填充圆内部
void Paint_DrawCircle(UWORD X_Center, UWORD Y_Center, UWORD Radius, UWORD Color, DOT_PIXEL Line_width, DRAW_FILL Draw_Fill)
参数:
 	X_Center: 圆心的X坐标
 	Y_Center: 圆心的Y坐标
 	Radius:圆的半径
 	Color: 填充的颜色
 	Line_width: 圆弧的宽度,提供默认的8种宽度
 	 	typedef enum {
 	 	 	 DOT_PIXEL_1X1  = 1,	// 1 x 1
 	 	 	 DOT_PIXEL_2X2  , 		// 2 X 2
 	 	 	 DOT_PIXEL_3X3  ,		// 3 X 3
 	 	 	 DOT_PIXEL_4X4  ,		// 4 X 4
 	 	 	 DOT_PIXEL_5X5  , 		// 5 X 5
 	 	 	 DOT_PIXEL_6X6  , 		// 6 X 6
 	 	 	 DOT_PIXEL_7X7  , 		// 7 X 7
 	 	 	 DOT_PIXEL_8X8  , 		// 8 X 8
 	 	} DOT_PIXEL;
 	Draw_Fill: 填充,是否填充圆的内部
 	 	typedef enum {
 	 	 	 DRAW_FILL_EMPTY = 0,
 	 	 	 DRAW_FILL_FULL,
 	 	} DRAW_FILL;
  • 写Ascii字符:在图像缓存中,在 (Xstart Ystart) 为左顶点,写一个Ascii字符,可以选择Ascii码可视字符字库、字体前景色、字体背景色
void Paint_DrawChar(UWORD Xstart, UWORD Ystart, const char Ascii_Char, sFONT* Font, UWORD Color_Foreground, UWORD Color_Background)
参数:
 	Xstart: 字符的左顶点X坐标
 	Ystart: 字体的左顶点Y坐标
 	Ascii_Char:Ascii字符
 	Font: Ascii码可视字符字库,在Fonts文件夹中提供了以下字体:
 	 	font8:5*8的字体
 	 	font12:7*12的字体
 	 	font16:11*16的字体
 	 	font20:14*20的字体
 	 	font24:17*24的字体
 	Color_Foreground: 字体颜色
 	Color_Background: 背景颜色
  • 写英文字符串:在图像缓存中,在 (Xstart Ystart) 为左顶点,写一串英文字符,可以选择Ascii码可视字符字库、字体前景色、字体背景色
void Paint_DrawString_EN(UWORD Xstart, UWORD Ystart, const char * pString, sFONT* Font, UWORD Color_Foreground, UWORD Color_Background)
参数:
 	Xstart: 字符的左顶点X坐标
 	Ystart: 字体的左顶点Y坐标
 	pString:字符串,字符串是一个指针
 	Font: Ascii码可视字符字库,在Fonts文件夹中提供了以下字体:
 	 	font8:5*8的字体
 	 	font12:7*12的字体
 	 	font16:11*16的字体
 	 	font20:14*20的字体
 	 	font24:17*24的字体
 	Color_Foreground: 字体颜色
 	Color_Background: 背景颜色
  • 写中文字符串:在图像缓存中,在 (Xstart Ystart) 为左顶点,写一串中文字符,可以选择GB2312编码字符字库、字体前景色、字体背景色;
void Paint_DrawString_CN(UWORD Xstart, UWORD Ystart, const char * pString, cFONT* font, UWORD Color_Foreground, UWORD Color_Background)
参数:
 	Xstart: 字符的左顶点X坐标
 	Ystart: 字体的左顶点Y坐标
 	pString:字符串,字符串是一个指针
 	Font: GB2312编码字符字库,在Fonts文件夹中提供了以下字体:
 	 	font12CN:ascii字符字体11*21,中文字体16*21
 	 	font24CN:ascii字符字体24*41,中文字体32*41
 	Color_Foreground: 字体颜色
 	Color_Background: 背景颜色
  • 写数字:在图像缓存中,在 (Xstart Ystart) 为左顶点,写一串数字,可以选择Ascii码可视字符字库、字体前景色、字体背景色
void Paint_DrawNum(UWORD Xpoint, UWORD Ypoint, double Nummber, sFONT* Font, UWORD Digit,UWORD Color_Foreground, UWORD Color_Background);
参数:
 	Xstart: 字符的左顶点X坐标
 	Ystart: 字体的左顶点Y坐标
 	Nummber:显示的数字,这里使用的是32位长的int型保存,可以最大显示到2147483647
 	Font: Ascii码可视字符字库,在Fonts文件夹中提供了以下字体:
 	 	font8:5*8的字体
 	 	font12:7*12的字体
 	 	font16:11*16的字体
 	 	font20:14*20的字体
 	 	font24:17*24的字体
        Digit:显示小数点位数
 	Color_Foreground: 字体颜色
 	Color_Background: 背景颜色
  • 显示时间:在图像缓存中,在 (Xstart Ystart) 为左顶点,显示一段时间,可以选择Ascii码可视字符字库、字体前景色、字体背景色;
void Paint_DrawTime(UWORD Xstart, UWORD Ystart, PAINT_TIME *pTime, sFONT* Font, UWORD Color_Background, UWORD Color_Foreground)
参数:
 	Xstart: 字符的左顶点X坐标
 	Ystart: 字体的左顶点Y坐标
 	pTime:显示的时间,这里定义好了一个时间的结构体,只要把时分秒各位数传给参数;
 	Font: Ascii码可视字符字库,在Fonts文件夹中提供了以下字体:
 	 	font8:5*8的字体
 	 	font12:7*12的字体
 	 	font16:11*16的字体
 	 	font20:14*20的字体
 	 	font24:17*24的字体
 	Color_Foreground: 字体颜色
 	Color_Background: 背景颜色


LVGL 示例程序

功能介绍

本例程兼容多款 LCD ,依据设备是否配备摇杆及配备按键数量来定制功能实现。通过摇杆实现虚拟指针移动,通过按键实现界面切换、模拟指针点击、开关状态反转等功能。各 LCD 示例功能概览表格如下:

Pico-LCD LVGL 示例功能介绍
型号 界面1 界面2 界面3
Pico-LCD-0.96 一张图片 一个虚拟的指针、一个按钮、一个标签记录点击按钮次数
Pico-LCD-1.14 一张图片 一个虚拟的指针、一个按钮、一个标签记录点击按钮次数
Pico-LCD-1.3 一张图片 一个虚拟的指针、一个按钮、一个标签记录点击按钮次数 两个开关
Pico-LCD-1.44 一张图片 一个按钮、一个标签记录点击按钮次数 两个开关
Pico-LCD-1.8 一张图片 一张图片
Pico-LCD-2 一张图片 一个按钮、一个标签记录点击按钮次数 两个开关

示例简介

本例用于测试 LVGL 控件交互、样式美化等, LVGL 的具体开发请参考LVGL开发文档

实现功能

  1. 本例使用 DMA 传输颜色数据到 SPI 总线,降低 CPU 的利用率,在进行简单交互时可以控制 CPU 的占用率在 35% 以下,内存占用在 20% 以下。
  2. 本例系统时钟为 150MHz ,设置 SPI 的外围时钟频率与系统时钟一致,同时使用 LVGL 库的双缓冲区机制,在进行一个缓冲区的数据传输时另一个缓冲区进行渲染,保证了动画的流畅度。
  3. 本例通过按键切换界面,通过按下按键实现点击按钮、开关状态切换,展示了 LVGL 控件的简单运用。

编译运行

  • 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/pi/pico/pico-sdk”
      nano ~/.bashrc
      #将以下内容添加到最后一行
      export PICO_SDK_PATH="/home/pi/pico/pico-sdk"
    • 设置完后保存退出,使配置立即生效
      source ~/.bashrc
    • 下载例程,进入源码目录,如果 build 目录已经存在,则可以直接进入。如没有则创建该目录:
      mkdir build
      cd build
    • 执行 cmake ,自动生成 Makefile 文件:
      cmake ..
    • 执行 make 生成可执行文件,然后在终端中输入:
      make
      编译好后会生成一个 .uf2 格式的文件。
    • 按住板上的 boot 按键,通过 Micro USB 线将板子接到电脑的 USB 接口,然后松开按键,电脑会将 pico 识别为一个可移动盘,最后将编译生成的 .uf2 格式的文件复制到 pico 即可

源码解析

源码结构

  • LVGL 库的源码位于工程文件夹的 lib/lvgl ,使用的版本号为8.1,二次开发请参考对应版本的开发文档。
  • LVGL 库的相关设置在工程文件夹的 examples/inc/lv_conf.h 中,可以设置显示刷新频率、系统占用数据等。
    Pico-LCD-LVGL-inc.png
  • LVGL 库的应用代码位于工程文件夹的 examples/src/LVGL_example.cexamples/src/LCD_XinXX_LVGL_test.c,XinXX 表示 LCD 尺寸,如 0in96 对应的文件为 LCD_0in96_LVGL_test.c 。
    Pico-LCD-LVGL-src.png

数据存储

本例使用了一个数据结构体,用于存储和管理与 LVGL 相关的数据。它包含了指向不同界面的指针、各个界面中控件(按钮、标签、开关)的指针,以及用于记录按钮点击次数和按键状态的变量。

  • 数据结构体
    • 代码位置:examples/inc/LVGL_example.h
    • 功能介绍:用于存储和管理所需的数据。
    typedef struct {
        lv_obj_t *scr[4];     // 用于存储不同界面
        lv_obj_t *cur;        // 指针
        lv_obj_t *btn;        // 按钮
        lv_obj_t *label;      // 标签
        lv_obj_t *sw_1;       // 开关 1 
        lv_obj_t *sw_2;       // 开关 2
        uint16_t click_num;   // 按钮点击次数
        uint16_t KEY_now;     // 按键当前状态
        uint16_t KEY_old;     // 按键上一状态
    } lvgl_data_struct;
    

按键读取

对于按键读取,本例使用按键扫描的方法实现,下面我们进行详细的介绍,程序中也提供了相应的注释。

  • 按键初始化
    • 代码位置:examples/src/LCD_XinXX_LVGL_test.c
    • 功能介绍:初始化模块所有物理按键。
    LCD_XINXX_KEY_Init();
    
  • 按键相关宏定义
    • 代码位置:lib/Config/DEV_Config.h
    • 使用方法:本例程兼容多款 LCD ,有摇杆需要将 LCD_HAS_JOY 的值设置为 1,无摇杆则置 0。所有按键的状态用一个 16 进制变量存储,该变量的每一位都可以用来表示一个按键的状态,如用第一位表示 KEY_A 的状态,用第二位表示 KEY_B 状态。每个位有 1 或 0 两种状态,分别表示按键按下或松开,如第一位为 1 则表示 KEY_A 按下,为 0 则表示 KEY_A 松开。该方式支持检测多个按键同时按下。
    #define LCD_HAS_JOY  1
    
    #define KEY_A     0x0001
    #define KEY_B     0x0002
    #define KEY_X     0x0004
    #define KEY_Y     0x0008
    #define KEY_UP    0x0010
    #define KEY_DOWN  0x0020
    #define KEY_LEFT  0x0040
    #define KEY_RIGHT 0x0080
    #define KEY_CTRL  0x0100
    #define KEY_0     0x0200
    #define KEY_1     0x0400
    #define KEY_2     0x0800
    #define KEY_3     0x1000
    
  • 按键读取
    • 代码位置:examples/src/LCD_XinXX_LVGL_test.c
    • 实现方式:定义一个 16 进制变量 KEY_Value,初始化为 0,若检测到按键按下,则按键对应的数据位置 1,读取完所有按键的状态后,返回 KEY_Value。由于每个模块搭载的按键数量存在差异,所以按键读取函数也会有所不同,这里仅以 Pico-LCD-0.96 为例演示,其他型号请参考完整示例。
    static uint16_t LCD_0IN96_Read_KEY(void) 
    {
        uint16_t KEY_Value = 0;
    
        if (DEV_Digital_Read(LCD_KEY_A)  == 0)          KEY_Value |= KEY_A;    
        if (DEV_Digital_Read(LCD_KEY_B)  == 0)          KEY_Value |= KEY_B;    
    
        if (DEV_Digital_Read(LCD_KEY_UP) == 0)          KEY_Value |= KEY_UP;
        else if (DEV_Digital_Read(LCD_KEY_DOWN)  == 0)  KEY_Value |= KEY_DOWN;
        else if (DEV_Digital_Read(LCD_KEY_LEFT)  == 0)  KEY_Value |= KEY_LEFT;
        else if (DEV_Digital_Read(LCD_KEY_RIGHT) == 0)  KEY_Value |= KEY_RIGHT;
        else if (DEV_Digital_Read(LCD_KEY_CTRL)  == 0)  KEY_Value |= KEY_CTRL;
    
        return KEY_Value;
    }
    
  • 键值存储
    • 代码位置:examples/src/LCD_XinXX_LVGL_test.c
    • 实现方式:读取当前按键值并保存到 dat->KEY_now 中。按键处理后,将当前按键值保存到 dat->KEY_old 中。在下一次按键处理时,这个值将作为按键的先前状态,用于判断按键状态的变化。如果某一位上一循环是 0,当前是 1,则表示该按键新按下。如果某一位上一循环是 1,当前是 0,则表示该按键按下后松开。
    while(1)
    {
        dat->KEY_now = LCD_XINXX_Read_KEY();  // 读取当前键值
        handle_key_press(dat);                // 按键处理
        dat->KEY_old = dat->KEY_now;          // 保存为旧键值
    }
    
  • 按键处理
    • 代码位置:examples/src/LVGL_example.c
    • 实现方式:使用 dat->KEY_now 通过与运算判断当前按键是否按下,结合 dat->KEY_old 可判断当前按键是否为新按下。
    void handle_key_press(lvgl_data_struct *dat) 
    {
        // 判断 KEY_UP 按键是否按下
        if(dat->KEY_now & KEY_UP) 
        ....
        // 判断 KEY_A 按键是否新按下
        if((dat->KEY_now & KEY_A) && !(dat->KEY_old & KEY_A)) 
        ....
        // 判断 KEY_A 按键是否松开
        else if(!(dat->KEY_now & KEY_A))
        ....
    }
    

LCD 参数配置

为了适配多款 LCD 显示屏,本例设置了三个关键参数,允许用户根据目标 LCD 的具体特性进行外部配置。

  • LCD 参数定义
    • 代码位置:examples/src/LVGL_example.c
    • 实现功能:DISP_HOR_RES 、DISP_VER_RES 主要用于初始化 LVGL 显示缓冲区;LCD_SetWindows 是一个函数指针,指向 LCD 设置显示位置函数,主要用于设置 LVGL 界面显示位置。
    // LCD 
    LCD_SetWindowsFunc LCD_SetWindows;
    uint16_t DISP_HOR_RES;
    uint16_t DISP_VER_RES;
    
  • LCD 参数配置
    • 代码位置:examples/src/LCD_XinXX_LVGL_test.c
    • 实现功能:根据使用的 LCD 型号配置参数。
    LCD_SetWindows = LCD_XINXX_SetWindows; // 指向LCD显示函数
    DISP_HOR_RES = LCD_XINXX_WIDTH;        // LCD水平分辨率
    DISP_VER_RES = LCD_XINXX_HEIGHT;       // LCD垂直分辨率
    

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。本例实现双缓冲机制,绘制缓冲区由缓冲区 buf0 和 buf1 组成,大小均设置为屏幕显示面积的一半,能够在降低大面积刷屏锯齿的同时有效提高屏幕刷新率;在使用单缓冲区时最好设置为屏幕显示面积的 10%,可以有效降低系统占用但在大面积图像刷新时会出现较明显的锯齿。
    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/2];
    static lv_color_t buf1[DISP_HOR_RES * DISP_VER_RES/2];
    
  • LVGL 显示设备注册
    • 代码位置:examples/src/LVGL_example.c
    • 实现功能:根据设计需求完善 LVGL 库核心结构体变量,初始化显示驱动 disp_drv,并设置绘制缓冲区,该缓冲区是 LVGL 用来渲染屏幕内容的简单数组。一旦渲染准备就绪,绘制缓冲区的内容将使用显示驱动程序中设置的 disp_drv_flush_cb 函数发送到显示器。
    lv_disp_draw_buf_init(&disp_buf, buf0, buf1, DISP_HOR_RES * DISP_VER_RES / 2); 
    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
    • 实现功能:主要完成图像在刷新区的绘制。
    static void disp_flush_cb(lv_disp_drv_t * disp, 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输入读取地址将数据传输到SPI总线,完成图像的绘制
    
  • LVGL 显示回调函数实现
    • 代码位置:examples/src/LVGL_example.c
    • 实现方式:在本例中为了最大化降低处理器的利用率,使用 DMA 来进行颜色数据的传输,设置 color_p 作为读地址,SPI 总线的输出数据寄存器为写地址。
    static void disp_flush_cb(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p)
    {
        LCD_SetWindows(area->x1, area->y1, area->x2+1, area->y2+1);
        DEV_Digital_Write(LCD_DC_PIN, 1);
        DEV_Digital_Write(LCD_CS_PIN, 0);
        dma_channel_configure(dma_tx,
                              &c,
                              &spi_get_hw(LCD_SPI_PORT)->dr, 
                              color_p, // read address
                              ((area->x2 + 1 - area-> x1)*(area->y2 + 1 - area -> y1))*2,
                              true);
    }
    
  • 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);
            DEV_Digital_Write(LCD_CS_PIN, 1);
            lv_disp_flush_ready(&disp_drv);     
        }
    }
    

LVGL 控件布局

在 LVGL 中,我们能够建立各种不同的用户界面。界面的基本组成部分是对象,也称为控件(Widgets),比如按钮(Button)、标签(Label)、图像(Image)、列表(List)、图表或文本区域。在一个界面中,可以同时创建多个控件,并且我们可以设置它们的位置、尺寸、父对象、样式以及事件处理程序等基本属性。

  • LVGL 控件初始化函数
    • 代码位置:examples/src/LCD_XinXX_LVGL_test.c
    • 实现功能:实现风格化控件和布局控件,参数 dat 作为传入传出参数,用于存储初始化后的界面和各个控件。
    static void Widgets_Init(lvgl_data_struct *dat);
  • LVGL 创建界面
    • 代码位置:examples/src/LCD_XinXX_LVGL_test.c
    • 实现功能:创建一个界面。
    //lv_obj_t *scr[4]是一个指针数组,每个元素都可以用来指向一个界面
    dat->scr[0] = lv_obj_create(NULL); 
  • LVGL 创建控件
    • 代码位置:examples/src/LCD_XinXX_LVGL_test.c
    • 实现功能:创建控件,不同控件需要使用不同的函数接口,可以选择父对象进行创建。
    //创建一个控件,其中dat->scr[1]为该按键的父对象,可以替换为list、title等可以有子对象的控件
    dat->btn = lv_btn_create(dat->scr[1]); 
  • LVGL 控件的对齐定位
    • 代码位置:examples/src/LCD_XinXX_LVGL_test.c
    • 实现功能:使控件能够基于参考点进行偏移定位。控件对齐偏移的参考点控件的中心。
    • 对齐标准: LVGL 库具备内部对齐和外部对齐两种方式。默认以左上角作为原点,向左为水平方向的正方向,向下为垂直方向的正方向。
    //将btn控件定位在中心点向左偏移50个像素,向下偏移50个像素的位置
    lv_obj_align(btn, LV_ALIGN_CENTER, -50 , 50);
    RP2040-Touch-LCD-1.28-LVGL-align.png
  • LVGL 控件切换字体大小
    • 代码位置:examples/inc/lv_conf.hexamples/src/LVGL_example.c
    • 实现功能:在实际运用时一个界面可能需要运用多种字体大小,可以在 lv_conf.h 中使能多种字体大小,且可以设置默认字体大小。设置字体大小时需要对控件风格化,使控件能够按照设置的风格进行渲染。利用 lv_obj_add_style 函数可以实现对控件各个部位在不同状态下的渲染。
    #define LV_FONT_MONTSERRAT_14 1                                // 使能14号字体
    #define LV_FONT_MONTSERRAT_16 1                                // 使能16号字体
    #define LV_FONT_DEFAULT &lv_font_montserrat_14                 // 设置默认字体大小为14号
    
    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 接口函数

本例自定义了一些函数,主要用于指针的移动和点击。

  • LVGL 控件相关的自定义接口函数
    • 代码位置:examples/src/LVGL_example.c
    • 实现功能:主要用于移动控件、判断指针点击是否有效,这里仅做简单描述,详情请查看完整示例。
    static void widgets_up(lv_obj_t *widgets);                          // 控件上移
    static void widgets_down(lv_obj_t *widgets, uint16_t DISP_VER_RES); // 控件下移
    static void widgets_left(lv_obj_t *widgets);                        // 控件左移
    static void widgets_right(lv_obj_t *widgets, uint16_t DISP_HOR_RES);// 控件右移
    static bool click_valid(lv_obj_t *cur, lv_obj_t *widgets);          // 判断指针点击是否有效