irpas技术客

ESP32/ESP32-C3 Arduino LVGL移植调试_leida_wt_platformio调试esp32

irpas 4690

本文记录以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 15 // Chip select control pin #define TFT_CS -1 #define TFT_DC 2 // Data Command control pin #define TFT_RST 4 // Reset pin (could connect to RST pin) //字体 #define SMOOTH_FONT //SPI速率 #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 /*Change to your screen resolution*/ const uint16_t screenWidth = 320; const uint16_t screenHeight = 240; void my_disp_init(void); // 挂载lvgl接口,设置buffer #endif

项目路径/src/my_lv_ports.cpp

#include "my_lv_ports.h" // TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight); /* TFT instance */ TFT_eSPI tft = TFT_eSPI(screenHeight,screenWidth); /* TFT instance */ /*Read the touchpad*/ 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; // 4096 = 2 ^ 12 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; /*Set the coordinates*/ 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 } } /* Display flushing */ 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); // tft.startWrite(); // tft.setAddrWindow( area->x1, area->y1, w, h ); // tft.pushColors( ( uint16_t * )&color_p->full, w * h, true ); // tft.endWrite(); 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 * 10]; // lv_disp_draw_buf_init(&draw_buf, buf, NULL, screenWidth * 10); static lv_disp_draw_buf_t draw_buf; static lv_color_t buf_2_1[screenWidth * 30]; /*A buffer for 10 rows*/ static lv_color_t buf_2_2[screenWidth * 30]; /*An other buffer for 10 rows*/ lv_disp_draw_buf_init(&draw_buf, buf_2_1, buf_2_2, screenWidth * 30); /*Initialize the display buffer*/ // TFT驱动初始化 tft.begin(); /* TFT init */ tft.initDMA(); tft.setRotation(3); /* Landscape orientation, flipped */ // 设置LVGL显示设备 static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); /*Change the following line to your display resolution*/ 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); // 设置LVGL输入设备(电阻屏) 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); // 设置LVGL串口输出设备(调试用) #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,使能该配置文件,做如下修改:

// 52行(增大lvgl内存池32k->64k) #define LV_MEM_SIZE (64U * 1024U) // 88行 (使能Arduino tick source) #define LV_TICK_CUSTOM 1 //174行,使能调试日志 #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*,修改:

//11行 #if 1 //41行 #define LV_USE_DEMO_BENCHMARK 1 //47行 #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); /* prepare for possible serial debug */ 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 /* Create simple label */ 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 /* Try an example from the lv_examples Arduino library make sure to include it as written above. lv_example_btn_1(); */ // uncomment one of these demos // lv_demo_widgets(); // OK lv_demo_benchmark(); // OK // lv_demo_keypad_encoder(); // works, but I haven't an encoder // lv_demo_music(); // NOK // lv_demo_printer(); // lv_demo_stress(); // seems to be OK #endif Serial.println("Setup done"); } void loop() { lv_timer_handler(); /* let the GUI do its work */ 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 /*Change to your screen resolution*/ const uint16_t screenWidth = 320; const uint16_t screenHeight = 240; void my_disp_init(void); // 挂载lvgl接口,设置buffer #endif

项目路径/src/my_lv_ports.cpp

#include "my_lv_ports.h" //接口I/O TFT_DC: 2, TFT_RST: 1 TFT_SCK: 4 TFT_MOSI : 6 TFT_CS: 拉高 /* More data bus class: https://github.com/moononournation/Arduino_GFX/wiki/Data-Bus-Class */ // DC pin is optional (-1 means using 9-bit SPI) // MISO pin is optional (Arduino_GFX not yet have any features read from display) // ESP32-C3使用FSPI Arduino_DataBus *bus = new Arduino_ESP32SPI(2 /* DC */, -1 /* CS */, 4 /* SCK */, 6 /* MOSI */, -1 /* MISO */, FSPI /* spi_num */); /* More display class: https://github.com/moononournation/Arduino_GFX/wiki/Display-Class */ Arduino_GFX *gfx = new Arduino_ST7789(bus, 1 /* RST */, 3 /* rotation */); /*Read the touchpad*/ 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; // 4096 = 2 ^ 12 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; /*Set the coordinates*/ 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 } } /* Display flushing */ 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); // TFT驱动初始化 // Init Display gfx->begin(); gfx->fillScreen(BLACK); // 设置LVGL显示设备 static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); /*Change the following line to your display resolution*/ 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); // 设置LVGL输入设备(电阻屏) // 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); // 设置LVGL串口输出设备(调试用) #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,使能该配置文件,做如下修改:

// 30行 (调整色彩格式) #define LV_COLOR_16_SWAP 1 // 52行(增大lvgl内存池32k->64k) #define LV_MEM_SIZE (64U * 1024U) // 88行 (使能Arduino tick source) #define LV_TICK_CUSTOM 1 //174行,使能调试日志 #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-filehttps://docs.lvgl.io/master/intro/index.htmlhttps://squareline.io/discoverhttps://docs.espressif.com/projects/arduino-esp32/en/latest/getting_started.htmlhttps://·/nb/47946852https://github.com/Makerfabs/Project_Touch-Screen-Camera


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #SPI时钟CSX