基于ESP-IDF在ESP32S3上移植LVGL和显示触摸驱动
ESP32很久没搞了,这篇文章是基于VSCode写的,写的自认为很一般,不过对于初学者应该是有点帮助的,近期可能会用CLion再试试配置ESP-IDF环境 - 20241028更新
前言:之前基本一直在搞STM32的开发,想换到别的平台(比如ESP32)试试,也算对自己的一种锻炼和提升,注意到网上关于LVGL移植的教程非常多,但是大部分是基于Arduino平台的,基本没有基于ESP-IDF框架的(即使有也已经过时)。最近入手了几块ESP32的开发板和一块带CTP(电容触摸)的3.5寸IPS屏幕,可视角度和分辨率都不错,作为一个对GUI界面有执着追求的人,我想把当下很火的嵌入式GUI项目LVGL - Light and Versatile Embedded Graphics Library移植到我的项目里,并实现一部分优化,让它即使在资源极端有限的情况下也能较为流畅地运行~
进度:
- (已完成)实现正确显示 - 20240412
- (已完成)跑demo、优化帧率 - 20240414
- (已完成)基于SquareLine平台设计UI - 很简单,可以看一下CLion实现的LVGL模拟器 20241028
- (已完成)接入各种传感器 - 很简单
物料选择
ESP32S3开发板一块,比如我使用立创的ESP32-S3开发板(集成串口芯片)
3.5寸电容触摸屏(显示驱动为ST7796U,电容触摸驱动为gt911)
杜邦线若干
Type-C数据线
环境配置
- 电脑系统:
Windows 11
- 开发环境:
Visual Studio Code
(至于为什么不用CLion,因为CLion上没配起来IDF框架(抽象))、ESP-IDF_5.1.2
2024/10/29更新,CLion官网出配置ESP-IDF教程了,好耶:https://www.jetbrains.com/help/clion/esp-idf.html
这里具体配置细节暂时略去,因为最近要重新装系统,到时候刚好重新配置一遍,写个教程补上 - 20240412
开工!
文件准备
先找到示例文件夹,在IDF的安装目录下
打开进入
./get-started/
,找到hello_world
程序并复制出来再在
./peripherals/i2c
中找到i2c_tools
,也和刚才的hello_world
放在一起打开
Visual Studio Code
,选到hello_world目录这里配置一下开发板的信息:
1. 插上开发板,点一下左下角的COM1
,根据实际选择正确的COM
1. 点左下角esp32
,选择合适的esp芯片型号,比如我就选esp32s3
1. 选择OpenOCD
的配置文件,一般选择via ESP-PROG
就好
1. 点左下角的五角星,选择UART
(我的开发板上带了TTL芯片)
最终配置好应该是这样的:在main文件夹里新建一个
idf_component.yml
文件,并复制以下内容这个网站有
idf_component.yml
的编写规则,这里我就直接用里面的框架了
1 | dependencies: |
浏览器打开乐鑫官网,在
SDKs
下找到IDF Component Manager
包管理器在
IDF Component Manager
中搜索LVGL
,这次用的是lvgl
和esp_lvgl_port
,至于驱动为什么不用lvgl_esp32_drivers
,是因为这玩意的代码已经很久没有维护了,不支持IDF5.x
版本和较新的LVGL
这次用的是
1.4.0
版本的esp_lvgl_port
,在idf_component.yml
中把pkg_name
换成esp_lvgl_port
,因为我们这次移植的是8.4.0
的LVGL
,所以pkg_version
改成1.4.0
,把LVGL
和gt911触摸芯片
和ST7796显示芯片
的驱动也加上,像下面这样
1 | dependencies: |
然后编译一次就会自动下载相关依赖(这里即使最后编译报错了也没关系,因为依赖已经下好了,报错稍后再去解决)
待依赖下载完成之后,工作空间的根目录会出现managed_components
文件夹,把这个文件夹改为components
(这步是因为可能要修改某些依赖的源码,如果不这样的话修改后会报哈希不匹配
的错),把里面的依赖整理一下,像这样
检查每个依赖中的其他关联依赖(idf_component.yml
),把所有除了IDF以外的其他依赖全部删掉
把
./esp_lvgl_port/examples/touchscreen/main/
下的main.c
文件的全部内容复制到hello_world_main.c里顺便说一下,如果VScode没法代码补全和代码跳转的话,按下
Ctrl+Shift+P
,搜索IntelliSense
,选择第一个不带bootloader的,选好之后Full Clean,再点一下编译,就可以正常使用了编译一下,看看报错
可以看到找不到对应的头文件,这里示例文件默认用的触摸驱动是tt21100,与我用的gt911不一样,所以我们要包含gt911的驱动进去,故要做的就是修改头文件,把tt21100的驱动换成gt911的,并且把程序里调用的函数进行对应的更改
1
更改为
1
对应的函数改为gt911
再次编译,看看报错
注意到这里找不到
lvgl.h
的错误是esp_lvgl_port
这个依赖发出的,所以进入该依赖的CmakeLists.txt
,在PRIV_REQUIRES
里加入lvgl
再次编译,看看报错
这里gt911的驱动报错找不到
driver/gpio.h
,与上面类似地,修改gt911的驱动里的CmakeLists.txt
,在PRIV_REQUIRES
里加入driver
再编译,看看报错
这里gt911的驱动报错找不到
esp_lcd_touch.h
,与上面类似地,修改gt911的驱动里的CmakeLists.txt
,在PRIV_REQUIRES
里加入esp_lcd_touch
再次编译,发现可以编译通过了!(好吧,其实离能用还有很远)
硬件连线
查看开发板的原理图和乐鑫ESP32S3技术文档,应该按照下表接线
屏幕+触摸 ESP32S3开发板 VDD 3V3 GND GND SCLK GPIO_NUM_12 MOSI GPIO_NUM_11 RST GPIO_NUM_3 DC GPIO_NUM_9 CS GPIO_NUM_10 BL GPIO_NUM_46 I2C_SCL GPIO_NUM_2 I2C_SDA GPIO_NUM_1 CTP_INT GPIO_NUM_45
注意:VDD和GND千万不能接错,否则烧毁!
接好线后烧录刚才的程序试一下,然后…咦?屏幕怎么没反应呢???
原来是接线没有在程序里映射出来,这里修改hello_world_main.c
的第24-50行为,在不同硬件上需要做相应调整
1 | /* LCD settings */ |
改完之后再次烧录,发现屏幕不断滚动报错
看红字知道报错是因为I2C总线上没有找到gt911,这就比较奇怪了,难道是因为I2C地址错了吗?幸好乐鑫提供了一个专门的I2C检测工具,可以遍历所有I2C地址,还记得和hello_world工程一起复制过来的i2c_tools工程吗,现在需要用到它了
在VScode中打开i2c_tools
工程,编译烧录,在终端中可以看到进入了i2c_tools
输入i2cdetect
即可遍历总线上的地址
这里出现了0x14
,当前挂载在I2C总线上的只有一个设备,所以gt911的地址是0x14
,然后我们回到hello_world程序,打开/components/esp_lcd_touch_gt911/include/esp_lcd_touch_gt911.h
可以看到定义了两个I2C地址,在结构体中调用了一个,这里看到调用的是ESP_LCD_TOUCH_IO_I2C_gt911_ADDRESS
,值为0x5D
,与实际地址0x14
不符合,而ESP_LCD_TOUCH_IO_I2C_gt911_ADDRESS_BACKUP
的值为0x14
,所以结构体里改为ESP_LCD_TOUCH_IO_I2C_gt911_ADDRESS_BACKUP
改代码
再次烧录,看看效果__结果还是不行,还是报I2C的错误,这里我们查看原理图,发现屏幕的I2C没有上拉__,所以我们需要打开芯片的内部上拉
然后再次烧录,屏幕成功点亮,但是显示有点奇怪
现在发现:
- 长宽不对
- 字显示不清晰
- 屏幕镜像颠倒
- 触摸X、Y轴反向
现在开始逐一解决:
- 设置长宽,更改宏定义里的
EXAMPLE_LCD_H_RES
为320,EXAMPLE_LCD_V_RES
为480 - 点左下角小齿轮,进入
SDK Configuration editor
,搜索Swap
,找到这个选项,打上勾(改完记得保存)
- 这两个
true
随便改一个为false
- 把结构体里的
mirror_x
的1改为0,mirror_y
的0改为1
然后再次烧录测试,发现颜色还是不对,背景是黑色的,但是理论上应该是白色
考虑到屏幕的面板是IPS的,而默认显示驱动可能支持的是TN屏,我这里选择魔改另外一款默认为IPS的显示驱动芯片(ili9341)的驱动程序,具体操作如下:
通过
idf-component
下载esp_lcd_ili9341
并移动到components
目录下,并修改esp_lcd_ili9341
的idf-component.yml
修改
/esp_lcd_ili9341/esp_lcd_ili9341.c
的vendor_specific_init_default
数组为(这里是参照屏幕厂商的驱动改的)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const ili9341_lcd_init_cmd_t vendor_specific_init_default[] = {
// {cmd, { data }, data_size, delay_ms}
/* Power contorl B, power control = 0, DC_ENA = 1 */
// {0x11, (uint8_t []){0x00}, 0, 120},
{0xB2, (uint8_t []){0x0C, 0x0C, 0x00, 0x33, 0x33}, 5, 0},
{0xB0, (uint8_t []){0x00, 0xE0}, 2, 0},
// {0x36, (uint8_t []){0x00}, 1, 0},
// {0x3A, (uint8_t []){0x05}, 1, 0},
{0xB7, (uint8_t []){0x56}, 1, 0},
{0xBB, (uint8_t []){0x14}, 1, 0},
{0xC0, (uint8_t []){0x2C}, 1, 0},
{0xC2, (uint8_t []){0x01}, 1, 0},
{0xC3, (uint8_t []){0x0B}, 1, 0},
{0xC4, (uint8_t []){0x10}, 1, 0},
{0xC6, (uint8_t []){0x0F}, 1, 0},
{0xD0, (uint8_t []){0xA4, 0xA1}, 2, 0},
{0xD6, (uint8_t []){0xA1}, 1, 0},
{0xE0, (uint8_t []){0xD0, 0x08, 0x0A, 0x0D, 0x0B, 0x07, 0x21, 0x33, 0x39, 0x39, 0x16, 0x16, 0x1F, 0x3C}, 14, 0},
{0xE1, (uint8_t []){0xD0, 0x00, 0x03, 0x01, 0x00, 0x10, 0x21, 0x32, 0x38, 0x16, 0x14, 0x14, 0x20, 0x3D}, 14, 0},
{0x21, (uint8_t []){0x00}, 0, 120},
{0x29, (uint8_t []){0x00}, 0, 1},
{0x2C, (uint8_t []){0x00}, 0, 1},
};至此驱动修改完毕,在
hello_world_main.c
中include一下esp_lcd_ili9341.h
,把esp_lcd_new_panel_st7796
函数改为esp_lcd_new_panel_ili9341
,烧录看看效果
正常显示 - 20240412
可以看到,已经正确显示了(但其实旋转方向之后显示还是不太对,屏幕会镜像,不知道为什么,应该还是驱动的问题)
跑demo - 20240414
Music音乐播放器demo
点左下角小齿轮,进入SDK Configuration editor
,找到LVGL相关的设置
在Demos
里面勾选Music player demo
,别忘了保存
然后回到hello_world_main.c里,找到函数app_main_display()
,将里面的Label
和Button
注释掉,并在后面加上lv_demo_music函数的声明和调用
这里补充一点,C语言可以在函数里面声明函数,格式与在函数之外相同。 声明后的函数只在本函数内调用有效。其他函数需要重新声明
修改好后的app_main_display()函数如下
1 | static void app_main_display(void) |
我们编译试试,看会不会报错
还真报错了,这里说的是找不到需要的字体,点左下角小齿轮进入LVGL的配置界面,在Font usage里勾选上12和16号字体,记得保存
再编译试试,可以看到已经编译过了
那就烧录看看效果吧(这里有可能触摸的X或者Y还会反,再去改tp_cfg结构体里的mirror_x或者mirror_y就行)
可以看到显示和触摸都正常了
跑组件(Widget)demo
优化帧率 - 20240414
打开乐鑫官网,点顶部的Support,在找到ESP-FAQ,搜索LVGL,即可找到如下内容
Taking ESP32-S3R8 as an example, the following ESP configuration items can improve the frame rate (ESP-IDF release/v5.1):
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
[should be consistent with PSRAM]CONFIG_SPIRAM_MODE_OCT=y
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
andCONFIG_SPIRAM_SPEED_120M=y
[should be consistent with FLASH]CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
CONFIG_SPIRAM_RODATA=y
CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
CONFIG_COMPILER_OPTIMIZATION_PERF=y
The following LVGL configuration items can improve the frame rate (LVGL v8.3):
#define LV_MEM_CUSTOM 1
orCONFIG_LV_MEM_CUSTOM=y
#define LV_MEMCPY_MEMSET_STD 1
orCONFIG_LV_MEMCPY_MEMSET_STD=y
#define LV_ATTRIBUTE_FAST_MEM IRAM_ATTR
orCONFIG_LV_ATTRIBUTE_FAST_MEM=y
再加一个LV_DISP_DEF_REFR_PERIOD
由30改为10
注:如果要显示帧率和CPU占用,可以吧Show CPU usage and FPS count
勾上
这里就按照官网的推荐来进行修改,点左下角小齿轮,分别修改上面的这些参数(修改完记得保存),其中某些参数可能找不到,这是由于CSDK的版本和使用的MCU型号不同导致的,不用深究
修改hello_world_main.c里的SPI频率到80M(实测这个影响帧率最为明显)
改完以后再来编译烧录,发现帧率提升了很多,滑动较为丝滑了