2024-9-14 01:59 /
为了后续的写UI,我花了大概整整4天时间查各种资料,最后不断debug/upload不断优化,故写本日志记录并存档,因此本日志不是教程,也不是面向新手,软件安装 / 工程创建等部分还请另找教程

本文撰写花了2小时,并且是深夜写的所以并不影响第二天的效率(

使用时注意,此优化针对ESP32-S3-WROON-1-N16R8模组和屏幕P169H002-CTP,以及为了后续添加其他功能,这不是极限优化 而且我用不了,因为他们用的ESP-IDF写的,太底层了搓不出来(bgm38)

本文使用DMA + 二分屏刷新 二缓冲,从最初没有优化的69 FPS到单缓冲的82 FPS到最终的120 FPS,可保证其流畅性的同时不增加更多的占用 (加入其他功能后RAM 62.6% / Flash 25.8%)

为什么使用二分屏,因为对比全屏刷新带来的优化并不大,并且会占用更多内存
为什么不使用全屏幕双缓冲,因为太吃内存了 (实际运行会内存溢出并无限重启)



目录 (CTRL+F 快捷搜索)
     -   使用器件与软件
     -   PlatformIO配置
     -   TFT_eSPI 库配置
     -   LVGL 图形库配置
     -   程序编写

使用器件与软件
乐鑫ESP32-S3-WROON-1-N16R8开发板
浦洋液晶 1.69寸 IPS全视角TFT彩屏 (P169H002-CTP [带电容触摸])
18P FPC转接板
VScode + PlatformIO 插件


(线路连接,其中屏幕SPI部分必须使用有FSPI的GPIO口 Github

PlatformIO配置
PlatformIO目前仍未适配N16R8,因此你可以直接将配置文件复制粘贴到项目的platformio.ini文件
[env:esp32s3]
; ESP 32 6.6.0 use Arduino Framework 2.0.14, TFT_eSPI 2.5.43 library can work normally
; Higher Arduino Framework version will cause compatibility issue: Guru Meditation Error
; More details: https://github.com/Bodmer/TFT_eSPI/issues/3329
; Solution: https://blog.csdn.net/qq_42757207/article/details/139062034
platform = espressif32 @ 6.6.0
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.partitions = default_16MB.csv
board_build.arduino.memory_type = qio_opi
board_build.flash_mode = qio
build_flags =
        -O2
        -DBOARD_HAS_PSRAM
build_unflags = -Os
board_upload.flash_size = 16MB
monitor_speed = 115200
upload_speed = 921600
board_build.f_cpu = 240000000L
board_build.f_flash = 80000000L
lib_deps =
        bodmer/TFT_eSPI@^2.5.43
        lewisxhe/SensorLib@^0.2.0
        lvgl/lvgl@8.4.0

用于显示屏驱动的TFT_eSPI库对新版本Arduino库不兼容,因此改为6.6.0版本 [CSDN]
使用 -O2 编译代码以获得最佳性能,且不使用 -Os [Bilibili]
添加 DBOARD_HAS_PSRAM 用于启用模组的PSRAM
monitor_speedupload_speed 为串口通信波特率和程序上传速度
board_build.f_cpuboard_build.f_flash 运行速度,直接拉满
lib_deps 是使用的库,你需要在PlatformIO主页 - Libraries 搜索并添加到项目
TFT_eSPI (显示屏驱动) / SensorLib (触控驱动) / LVGL 图形库

TFT_eSPI 库配置
项目文件\.pio\libdeps\esp32s3\TFT_eSPI\User_Setup_Select.h
#include <User_Setups/Setup203_ST7789.h> 一栏取消注释
并进入该头文件,修改其配置,代码如下
#define USER_SETUP_ID 203

// 定义驱动,使能DMA,分辨率设置
#define ST7789_DRIVER     // Configure all registers
#define ESP32_DMA
#define TFT_WIDTH  240
#define TFT_HEIGHT 280
#define CGRAM_OFFSET      // Library will add offsets required

// RGB显示并反转颜色显示
#define TFT_RGB_ORDER TFT_RGB  // Colour order Red-Green-Blue
#define TFT_INVERSION_ON

// GPIO口定义
#define TFT_MISO 10    //P169H002-CTP没有MISO,因此设置为CS Pin
#define TFT_MOSI 12
#define TFT_SCLK 11
#define TFT_CS   10    // Chip select control pin
#define TFT_DC    9    // Data Command control pin
#define TFT_RST  13    // Reset pin (could connect to RST pin)
...... (字体部分省略)
// 运行速率,ESP32-S3 SPI速度可拉到80000000
#define SPI_FREQUENCY  80000000
#define SPI_READ_FREQUENCY  20000000
#define SPI_TOUCH_FREQUENCY  2500000 // 触控屏频率

没有MISO口可用CS口代替
使用DMA必须配置MISO!

LVGL 图形库配置
(优化RAM的使用可去 【正点原子】手把手教你学LVGL图形界面编程
项目文件\.pio\libdeps\esp32-s3-devkitm-1\lvgl
将demos文件移进 LVGL库的src文件夹
lv_conf_template.h修改为lv_conf.c,打开文件,并将#if 0改 1
使用ctrl+f查找以下内容,并修改其值
#define LV_MEM_CUSTOM 1 // 调用PSRAM
#define LV_DISP_DEF_REFR_PERIOD 8
#define LV_INDEV_DEF_READ_PERIOD 10
#define LV_TICK_CUSTOM 1
#define LV_DPI_DEF 219 // 1.69寸屏幕 DPI
#define LV_USE_PERF_MONITOR 1 // 右下角显示FPS / CPU Usage
#define LV_BUILD_EXAMPLES 1 // 使用LVGL示例必须打开
#define LV_USE_DEMO_BENCHMARK 1 // 启用Benchmark性能测试


程序编写
#include <Arduino.h>
#include <Wire.h>

// OLED+Touch+GUI Library
#include <lvgl.h>
#include <TFT_eSPI.h>
#include "TouchDrvCSTXXX.hpp"
#include <demos/lv_demos.h>
#include <esp_adc_cal.h>
#include <driver/adc.h>

TouchDrvCSTXXX touch;
int16_t x[5], y[5];

// 以下为触控屏的IIC接口定义
#ifndef SENSOR_SDA
#define SENSOR_SDA  5
#endif

#ifndef SENSOR_SCL
#define SENSOR_SCL  4
#endif

#ifndef SENSOR_IRQ
#define SENSOR_IRQ  7
#endif

#ifndef SENSOR_RST
#define SENSOR_RST  6
#endif

// 用于启用全屏单缓冲和全屏幕双缓冲
#define LVGL_ONE_BUFFER 0
#define LVGL_DOUBLE_BUFFERING 0

void scanDevices(void) // 搜索设备
{
    byte error, address;
    int nDevices = 0;
    Serial.println("Scanning for I2C devices ...");
    for (address = 0x01; address < 0x7f; address++) {
        Wire.beginTransmission(address);
        error = Wire.endTransmission();
        if (error == 0) {
            Serial.printf("I2C device found at address 0x%02X\n", address);
            nDevices++;
        } else if (error != 2) {
            Serial.printf("Error %d at address 0x%02X\n", error, address);
        }
    }
    if (nDevices == 0) {
        Serial.println("No I2C devices found");
    }
}

static const uint16_t screenWidth  = 240;
static const uint16_t screenHeight = 280;

static lv_disp_draw_buf_t draw_buf_dsc; //Buffer Mode define

#if LVGL_ONE_BUFFER == 1
static lv_color_t buf[ screenWidth * screenHeight ]; //ONE Buffer Mode
#else
static lv_color_t buf_1[screenWidth * screenHeight / 2];                        /*A buffer for 2 rows*/
static lv_color_t buf_2[screenWidth * screenHeight / 2];                        /*An other buffer for 2 rows*/
#endif

TFT_eSPI tft = TFT_eSPI(screenWidth, screenHeight); /* TFT instance */

/* Display flushing */
void my_disp_flush( lv_disp_drv_t *disp_drv, 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);
   
    // 此处不使用自带的显示刷新,改为DMA
    // 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_drv );
}

/*Read the touchpad*/
void my_touchpad_read( lv_indev_drv_t * indev_drv, lv_indev_data_t * data )
{
    uint16_t touchX, touchY;
    uint8_t touched = touch.getPoint(x, y, touch.getSupportTouchPoint());
    // bool touched = tft.getTouch( &touchX, &touchY, 600 );

    if( touched )
    {
        data->state = LV_INDEV_STATE_PR;

        /*Set the coordinates*/
        data->point.x = x[0];
        data->point.y = y[0];

        Serial.print( "Data x " );
        Serial.println( x[0] );

        Serial.print( "Data y " );
        Serial.println( y[0] );
    }
    else
    {
        data->state = LV_INDEV_STATE_REL;
    }
}

void setup() {
  Serial.begin(115200);
  while (!Serial);
  Wire.setPins(SENSOR_SDA, SENSOR_SCL);
  Wire.begin();
  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();

  tft.begin();          /* TFT init */
  tft.initDMA(true); // 使能DMA并初始化
  tft.setRotation( 0 ); /* Landscape orientation, flipped */

  /*Set the touchscreen calibration data,
   the actual data for your display can be acquired using
   the Generic -> Touch_calibrate example from the TFT_eSPI library*/
  uint16_t calData[5] = { 275, 3620, 264, 3532, 1 };
  // tft.setTouch( calData );

  //Graphic Data Buffer Mode
  #if LVGL_ONE_BUFFER == 1
  // lv_disp_draw_buf_init( &draw_buf_dsc, buf, NULL, screenWidth * screenHeight / 10 ); // ONE Buffer Mode, 10分屏
  lv_disp_draw_buf_init( &draw_buf_dsc, buf, NULL, screenWidth * screenHeight ); // ONE Buffer Mode
  #else
  lv_disp_draw_buf_init ( &draw_buf_dsc, buf_1, buf_2, screenWidth * screenHeight / 2); // TWO Buffer Mode, 2分屏
  #endif
   

  /*Initialize the display*/
  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_dsc;

    #if LVGL_DOUBLE_BUFFERING == 1
      disp_drv.full_refresh = 1;
    #endif
    lv_disp_drv_register( &disp_drv );

    /*Initialize the (dummy) input device driver*/
    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 );
  
    /* Create simple label */
    lv_obj_t *label = lv_label_create( lv_scr_act() );
    lv_label_set_text( label, "Hello Ardino and LVGL!");
    lv_obj_align( label, LV_ALIGN_CENTER, 0, 0 );

    /* Try an example. See all the examples
     * online: https://docs.lvgl.io/master/examples.html
     * source codes: https://github.com/lvgl/lvgl/tree/e7f88efa5853128bf871dde335c0ca8da9eb7731/examples */
     //lv_example_btn_1();
   
     /*Or try out a demo. Don't forget to enable the demos in lv_conf.h. E.g. LV_USE_DEMOS_WIDGETS*/
    //lv_demo_widgets();               
    lv_demo_benchmark();
    //lv_demo_benchmark_set_max_speed(true);         
    // lv_demo_keypad_encoder();     
    // lv_demo_music();              
    // lv_demo_printer();
    // lv_demo_stress();
   
    Serial.println( "LVGL Setup done" );

   
#if SENSOR_RST != -1
    pinMode(SENSOR_RST, OUTPUT);
    digitalWrite(SENSOR_RST, LOW);
    delay(30);
    digitalWrite(SENSOR_RST, HIGH);
    delay(50);
    // delay(1000);
#endif

    // Search for known CSTxxx device addresses
    uint8_t address = 0xFF;

// #ifdef ARDUINO_ARCH_RP2040
//     Wire.setSCL(SENSOR_SCL);
//     Wire.setSDA(SENSOR_SDA);
// #else
//     Wire.begin(SENSOR_SDA, SENSOR_SCL);
// #endif

    // Scan I2C devices
    scanDevices();

    Wire.beginTransmission(CST816_SLAVE_ADDRESS);
    if (Wire.endTransmission() == 0) {
        address = CST816_SLAVE_ADDRESS;
    }
    Wire.beginTransmission(CST226SE_SLAVE_ADDRESS);
    if (Wire.endTransmission() == 0) {
        address = CST226SE_SLAVE_ADDRESS;
    }
    Wire.beginTransmission(CST328_SLAVE_ADDRESS);
    if (Wire.endTransmission() == 0) {
        address = CST328_SLAVE_ADDRESS;
    }
    while (address == 0xFF) {
        Serial.println("Could't find touch chip!"); delay(1000);
    }

    touch.setPins(SENSOR_RST, SENSOR_IRQ);
    touch.begin(Wire, address, SENSOR_SDA, SENSOR_SCL);


    Serial.print("Model :"); Serial.println(touch.getModelName());

    // T-Display-S3 CST816 touch panel, touch button coordinates are is 85 , 160
    touch.setCenterButtonCoordinate(85, 360);

    // T-Display-AMOLED 1.91 Inch CST816T touch panel, touch button coordinates is 600, 120.
    // touch.setCenterButtonCoordinate(600, 120);  // Only suitable for AMOLED 1.91 inch


    // Depending on the touch panel, not all touch panels have touch buttons.
    touch.setHomeButtonCallback([](void *user_data) {
        Serial.println("Home key pressed!");
    }, NULL);


    // Unable to obtain coordinates after turning on sleep
    // CST816T sleep current = 1.1 uA
    // CST226SE sleep current = 60 uA
    // touch.sleep();

    // Set touch max xy
    // touch.setMaxCoordinates(536, 240);

    // Set swap xy
    // touch.setSwapXY(true);

    // Set mirror xy
    // touch.setMirrorXY(true, true);
}

void loop() {
  ATimer.handle();
  lv_timer_handler(); /* let the GUI do its work */
  delay( 3 );
}
#1 - 2024-9-14 03:02
(のヮの)
初始化函数最下面的注释我不知道后续会不会用上所以先注释了(
反正触控屏是能用的,要是有不懂的,我自己也没法确保能解释清楚
写出来主要是为了方便自己答辩,也为了后面可能有需要的人,写的非常烂,欢迎鞭策
#1-1 - 2024-9-14 03:04
SilenceAkarin
为什么要写这篇日志,主要是现有网上所有能找到的资料实在是太少了,而且参考程度很低,有些文章不通透的都会在这贴原链接+补充