本文记录以platformio为开发环境,esp32-arduino为框架下ESP32及ESP32-C3上LVGL的移植、调试与帧率优化。
硬件说明
ESP32-S核心板 ESP32-C3核心板 SPI接口TFT屏幕(2.8寸320*240,带电阻屏,ST7789驱动) 电阻屏驱动(NS2009)
屏幕显示部分所需信号: SDA(SPI输入) SDO(SPI输出,可选) SCL (SPI时钟) CSX (SPI片选,可直接拉高) DC(数据/指令选择) RST(复位) 屏幕触控部分所需信号: XL、YU、XR、YD,构成4线电阻屏,原理见ref
ESP32
0.开发环境
platformio + arduino框架 新建ESP32-Arduino工程: 添加三个包: lvgl-8.1.0 lvgl_examples(demo合集) tft_eSPI(提供SPI屏驱动)
1.配置SPI屏驱动
编辑TFT_eSPI的配置文件: 项目路径/.pio/libdeps/esp32dev/TFT_eSPI/User_Setup.h: 根据注释的提示修改,对于本文的ST7789-TFT屏幕,修改项包括:
#define ST7789_DRIVER
#define ESP32_DMA
#define TFT_RGB_ORDER TFT_BGR
#define TFT_INVERSION_OFF
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
#define TFT_MISO 19
#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS -1
#define TFT_DC 2
#define TFT_RST 4
#define SMOOTH_FONT
#define SPI_FREQUENCY 40000000
#define SPI_READ_FREQUENCY 6000000
注:
- SPI读/写速率被限制在6MHz/40MHz,为ST7789驱动芯片的时序限制,ESP32硬件SPI接口可支持到80MHz。
- 关于I/O口的选择问题,高速SPI不可使用I/O映射,见Justice_Gao的博客。
- 由于不使用tft_eSPI的绘图函数,故关闭所有字库以节约ROM。
2.配置LVGL接口函数
ref: 项目路径.pio/libdeps/esp32dev/lvgl/examples/arduino/LVGL_Arduino/LVGL_Arduino.ino 核心是实现my_disp_flush,my_print和my_touchpad_read(可选) 项目路径/src/my_lv_ports.h
#ifndef _MY_LV_PORTS
#define _MY_LV_PORTS
#include <TFT_eSPI.h>
#include <lvgl.h>
#include "NS2009.h"
#define ESP32_I2C_SDA 33
#define ESP32_I2C_SCL 25
const uint16_t screenWidth = 320;
const uint16_t screenHeight = 240;
void my_disp_init(void);
#endif
项目路径/src/my_lv_ports.cpp
#include "my_lv_ports.h"
TFT_eSPI tft = TFT_eSPI(screenHeight,screenWidth);
void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
int16_t touchX, touchY, touchZ;
touchZ = ns2009_read(NS2009_LOW_POWER_READ_Z1);
touchX = ns2009_read(NS2009_LOW_POWER_READ_X);
touchY = ns2009_read(NS2009_LOW_POWER_READ_Y);
touchX = touchX * SCREEN_X_PIXEL / 4096;
touchY = SCREEN_Y_PIXEL - touchY * SCREEN_Y_PIXEL / 4096;
if (touchZ < 30) {
data->state = LV_INDEV_STATE_REL;
} else {
data->state = LV_INDEV_STATE_PR;
data->point.x = touchY;
data->point.y = touchX;
#if LV_USE_LOG != 0
Serial.printf("Touch: x=%d y=%d\r\n", touchX, touchY);
#endif
}
}
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area,
lv_color_t *color_p) {
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
tft.setSwapBytes(true);
tft.pushImageDMA(area->x1, area->y1, w, h,(uint16_t *)&color_p->full);
lv_disp_flush_ready(disp);
}
#if LV_USE_LOG != 0
void my_print(const char *buf) { Serial.printf("%s \r\n", buf); }
#endif
void my_disp_init(void) {
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf_2_1[screenWidth * 30];
static lv_color_t buf_2_2[screenWidth * 30];
lv_disp_draw_buf_init(&draw_buf, buf_2_1, buf_2_2,
screenWidth * 30);
tft.begin();
tft.initDMA();
tft.setRotation(3);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_touchpad_read;
lv_indev_drv_register(&indev_drv);
#if LV_USE_LOG != 0
lv_log_register_print_cb(my_print);
#endif
}
注:
- my_disp_flush的实现中,使用pushImageDMA函数替换LVGL原例程中的pushColors函数,以实现非阻塞DMA传输,同时,应开启双绘图缓冲。此修改可带来20%~40%的帧率提升。
- my_print函数用于支持LVGL-Debug模块向串口输出调试信息,LVGL v8修改了该回调函数接口,LVGL原例程中的实现已不再适用,此处做了修改。
- 在my_disp_init的开始,开辟了两块绘图缓冲,以配合非阻塞DMA传输。缓冲区容量设为30行,大于30行时帧率提升较小。
3.LVGL设置
将 项目路径/.pio/libdeps/esp32dev/lvgl/lv_conf_template.h复制一份到 项目路径/.pio/libdeps/esp32dev/lvgl/src,更名为lv_conf.h,将第15行修改为1,使能该配置文件,做如下修改:
#define LV_MEM_SIZE (64U * 1024U)
#define LV_TICK_CUSTOM 1
#define LV_USE_LOG 1
4.运行benchmark测试
首先,开启lv_examples库的bench_mark部分: 将*/项目路径.pio/libdeps/esp32dev/lv_examples/lv_demo_conf_template.h复制为/项目路径.pio/libdeps/esp32dev/lv_examples/lv_demo_conf.h*,修改:
#if 1
#define LV_USE_DEMO_BENCHMARK 1
#define LV_USE_DEMO_MUSIC 0
最后,编写main函数:项目路径/src/main.cpp:
#include <Arduino.h>
#include "my_lv_ports.h"
#include <TFT_eSPI.h>
#include <lv_demo.h>
#include <lvgl.h>
void setup() {
Serial.begin(115200);
Wire.begin(ESP32_I2C_SDA, ESP32_I2C_SCL);
String LVGL_Arduino = "Hello Arduino! ";
LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() +
"." + lv_version_patch();
Serial.println(LVGL_Arduino);
Serial.println("I am LVGL_Arduino");
lv_init();
my_disp_init();
#if 0
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_text(label, LVGL_Arduino.c_str());
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
#else
lv_demo_benchmark();
#endif
Serial.println("Setup done");
}
void loop() {
lv_timer_handler();
delay(5);
}
Benchmark加权FPS可达50。
ESP32-C3
0.开发环境
2022年最新版本的Arduino-ESP32 2.0.2框架已经添加了对C3芯片的支持,但platformio平台尚未完全适配,故在新建项目时需要手动配置ref。 首先新建ESP32-Arduino工程,将platformio.ini文件替换为如下内容:
[env:arduino-esp32c3]
platform = https://github.com/platformio/platform-espressif32.git#feature/arduino-upstream
board = esp32-c3-devkitm-1
framework = arduino
platform_packages =
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32#master
lib_ldf_mode = deep
lib_deps =
lvgl/lvgl@^8.1.0
moononournation/GFX Library for Arduino@^1.2.0
lvgl/lv_examples@^8.1.1-dev
monitor_speed = 115200
board_build.flash_mode = dio
注:
- 添加lib_ldf_mode=deep解决编译时找不到库文件的问题
- 添加board_build.flash_mode=dio解决boot失败的问题
- 由于tft_eSPI库尚未完成对C3芯片适配,使用GFX Library for Arduino库代替tft_eSPI库作为SPI屏幕的驱动
1.配置SPI屏驱动
无。
2.配置LVGL接口函数
由于更换了SPI驱动库,需将上面的第二部分做如下修改: ref: 项目路径.pio/libdeps/esp32dev/lvgl/examples/arduino/LVGL_Arduino/LVGL_Arduino.ino 核心是实现my_disp_flush,my_print和my_touchpad_read(可选) 项目路径/src/my_lv_ports.h
#ifndef _MY_LV_PORTS
#define _MY_LV_PORTS
#include <Arduino.h>
#include <Arduino_GFX_Library.h>
#include <lvgl.h>
#include "NS2009.h"
#define ESP32_I2C_SDA 33
#define ESP32_I2C_SCL 25
const uint16_t screenWidth = 320;
const uint16_t screenHeight = 240;
void my_disp_init(void);
#endif
项目路径/src/my_lv_ports.cpp
#include "my_lv_ports.h"
Arduino_DataBus *bus = new Arduino_ESP32SPI(2 , -1 , 4 , 6 , -1 , FSPI );
Arduino_GFX *gfx = new Arduino_ST7789(bus, 1 , 3 );
void my_touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data) {
int16_t touchX, touchY, touchZ;
touchZ = ns2009_read(NS2009_LOW_POWER_READ_Z1);
touchX = ns2009_read(NS2009_LOW_POWER_READ_X);
touchY = ns2009_read(NS2009_LOW_POWER_READ_Y);
touchX = touchX * SCREEN_X_PIXEL / 4096;
touchY = SCREEN_Y_PIXEL - touchY * SCREEN_Y_PIXEL / 4096;
if (touchZ < 30) {
data->state = LV_INDEV_STATE_REL;
} else {
data->state = LV_INDEV_STATE_PR;
data->point.x = touchY;
data->point.y = touchX;
#if LV_USE_LOG != 0
Serial.printf("Touch: x=%d y=%d\r\n", touchX, touchY);
#endif
}
}
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area,
lv_color_t *color_p) {
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
lv_disp_flush_ready(disp);
}
#if LV_USE_LOG != 0
void my_print(const char *buf) { Serial.printf("%s \r\n", buf); }
#endif
void my_disp_init(void) {
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[screenWidth * 30];
lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * 30);
gfx->begin();
gfx->fillScreen(BLACK);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
#if LV_USE_LOG != 0
lv_log_register_print_cb(my_print);
#endif
}
注:
- Arduino_GFX库不支持DMA传输,故使用单显示缓存即可。
3.LVGL设置
将 项目路径/.pio/libdeps/esp32dev/lvgl/lv_conf_template.h复制一份到 项目路径/.pio/libdeps/esp32dev/lvgl/src,更名为lv_conf.h,将第15行修改为1,使能该配置文件,做如下修改:
#define LV_COLOR_16_SWAP 1
#define LV_MEM_SIZE (64U * 1024U)
#define LV_TICK_CUSTOM 1
#define LV_USE_LOG 1
4.运行benchmark测试
同ESP32部分 Benchmark加权FPS可达39。
参考资料
- https://daumemo.com/how-to-use-lvgl-library-on-arduino-with-an-esp-32-and-spi-lcd/#config-file
- https://docs.lvgl.io/master/intro/index.html
- https://squareline.io/discover
- https://docs.espressif.com/projects/arduino-esp32/en/latest/getting_started.html
- https://www.jianshu.com/nb/47946852
- https://github.com/Makerfabs/Project_Touch-Screen-Camera
|