ESP32/ESP32-C3 Arduino LVGL移植调试

本文记录以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,使能该配置文件,做如下修改:

// 30行 调整RGB565数据格式
#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测试

首先,开启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。

通过platformio.ini文件进行库配置

在上面两节中,对LVGL库和TFT_sSPI库的配置是通过直接修改源文件实现的,这种方法的缺点是当我们重新拉取(更新)依赖库时,这些修改会被覆盖掉。为解决这一问题,可通过在platformio.ini文件中配置编译标志实现对库的配置。platformio.ini文件描述了一个platformio工程的所有依赖,只需在pio CLI中执行pio init --ide=vscode便可令platformio系统按照platformio.ini文件的描述自动下载所需的编译套件、SDK,和依赖库文件。

通过platformio.ini文件对LVGL库和TFT_sSPI库进行配置的具体方法可见:

参考资料

  • 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
  • 7
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值