arduino下使用LVGL的一些笔记(驱动CST816触摸屏、适配FFat内部文件系统)

此文是我在arduino下使用LVGL的一些随手笔记,网上的资料不全,总踩很多坑,所以记录一些关键点。

TFT为ST7789,触摸芯片使用电容屏芯片CST816D。购自某宝,价格有点小贵,但尺寸适合88*38的小机箱。分辨率170*320。

1.arduino下安装LVGL和TFT_eSPI库。

安装方法很多,百度一下这个没什么坑。

arduino个人习惯2.X版本,但是有时要使用1.8.X,至于原因后面会讲到,所以要装2个版本,下文没特别说明的都是2.X上进行操作。

网络不好的话离线安装效率高很多,百度一下arduino esp32离线安装包就能找到下载。

LVGL是在arduino内安装的8.3.10版本,应该是截止目前(23年10月19日)最新的版本了。

2.TFT_eSPI库的使用

(1)启用配置文件

在Documents\Arduino\libraries\TFT_eSPI下找到User_Setup_Select.h文件,取消#include <User_Setups/Setup24_ST7789.h>  行的注释(如果默认取消掉了#include <User_Setup.h>  行,就把它注释掉。)

(2)修改配置文件

在Documents\Arduino\libraries\TFT_eSPI\User_Setups文件夹中找到Setup24_ST7789.h打开,根据实际情况修改:

#define TFT_MISO         -1
#define TFT_MOSI         13
#define TFT_SCLK         10
#define TFT_CS            12
#define TFT_DC            11
#define TFT_RST           9
#define TFT_BL            14        //Also used as TFT-enable Pin

//INVERSION(反色)和RGB_ORDER(RGB颜色顺序)根据实际情况,下面只是我的屏幕设置

#define TFT_INVERSION_ON

#define TFT_RGB_ORDER TFT_BGR  // Colour order Blue-Green-Red

TOUCH_CS如果不是使用集成的电阻触摸屏驱动,可以不启用。

//#define TOUCH_CS        40

(3)测试TFT_eSPI是否正常

 arduino下TFT_eSPI的测试示例:

#include <TFT_eSPI.h> // Hardware-specific library

#include <SPI.h>



TFT_eSPI tft = TFT_eSPI(170,320);

tft.setRotation(1);

tft.init();

tft.fillScreen(TFT_BLACK);

tft.setTextSize(2);

tft.setTextColor(TFT_YELLOW, TFT_BLACK);

tft.println("1");

此处有坑:

我的1.9寸液晶Rotation=0无旋转的情况下,width=170,height=320是竖着显示,就和前文中照片那样的显示方式,但我的应用场景是横着的,所以要setRotation(1)。

但是旋转之后,长变320正常,竖边170老是不正常,上面缺了几十行。

经过研究是TFT_eSPI.cpp文件中引用了一个旋转头文件:

#elif defined (ST7789_DRIVER)
    #include "TFT_Drivers/ST7789_Rotation.h"

跟踪TFT_Drivers/ST7789_Rotation.h中发现如下代码:

#ifdef CGRAM_OFFSET
      Serial.println( "CGRAM_OFFSET defined" );
      if (_init_width == 135)
      {
        colstart = 40;
        rowstart = 53;
      }
      else if(_init_height == 280)
      {
        colstart = 20;
        rowstart = 0;
      }
      else if(_init_width == 172)
      {
        colstart = 0;
        rowstart = 34;
      }
      else if(_init_width == 170)
      {
        colstart = 0;
        rowstart = 35;
        Serial.println( "set width=170" );
      }
      else
      {
        colstart = 0;
        rowstart = 0;
      }
#endif

Serial.println( "set width=170" );这行是我添加做测试的。测试的结果是该块代码并未被执行!然后跟踪发现CGRAM_OFFSET并没有define,所以colstart和rowstart的偏移并未生效,屏幕上无端少了几十行。

再查原因是ST7789_Defines.h中有如下行:

#if (TFT_HEIGHT == 320) && (TFT_WIDTH == 170)
  #ifndef CGRAM_OFFSET
    #define CGRAM_OFFSET
  #endif
#endif

所以需要在TFT_eSPI.cpp把ST7789_Defines.h文件include进去,但是由于不好测试TFT_HEIGHT和TFT_WIDTH如何从代码传递到该文件的,所以我干脆在ST7789_Rotation.h中直接增加:

#define CGRAM_OFFSET

编译测试TFT_eSPI终于显示正常了,到此TFT_eSPI显示部分总算折腾好了,然后就是LVGL显示了。

有人问为什么要折腾TFT_eSPI,这是因为LVGL使用了TFT_eSPI做屏幕驱动!

3.触摸驱动

没有了触摸功能,显示屏就没有了灵魂。

我显示屏触摸芯片使用的是CST816D芯片,我在arduino库中没有找到CST816D,但是有一个CST816S的(by fbiego),实际测试是可以用的。

要使用该驱动,首先我们得知道TFT_eSPI实际是集成了触摸驱动的,只不过是电阻屏的触摸,而CST816D是电容触摸的,所以不能用集成的驱动。

首先,我们要禁用掉其集成的电阻屏驱动,在TFT_eSPI的Setup24_ST7789.h中有一行:

#define TOUCH_CS

这行我们不使用它集成的驱动的话,我们不需要去启用它,保持被注释掉即可,它会根据该行是否被注释掉来决定是否编译电阻触摸屏的驱动进去。

然后我们把CST816S(by fbiego)这个库安装好,安装好后打开其自带的example,是一个实时串口输出触摸点坐标的例程。修改一下引脚和你ESP32与触摸屏的引脚一致即可开始测试。下面是例程修改后的头部分。

#include <CST816S.h>

CST816S touch(21, 22, 5, 4);  // sda, scl, rst, irq修改成你自己的实际连线

例程会从串口输出触摸点的坐标。用手触摸屏幕,观察串口输出的坐标是不是和你的屏幕坐标定义是否一致,也就是你的屏幕驱动的XY轴原点和正方向是不是和触摸测试的结果一致。这个很重要,不然后面触摸就是乱的。

不一致的话就要修改触摸驱动了。刚好,我的触摸就和屏幕的方向定义不一致,XY轴方向是交换的,Y轴正方向也是反的(因为我的屏幕做了旋转rotation=1)。

所以我做了如下修改,打开libraries\CST816S文件夹中的CST816S.cpp文件,修改void CST816S::read_touch()函数,下面代码中int tmp开始就是我增加的,用于调整触摸的坐标输出。

void CST816S::read_touch() {
  byte data_raw[8];
  i2c_read(CST816S_ADDRESS, 0x01, data_raw, 6);

  data.gestureID = data_raw[0];
  data.points = data_raw[1];
  data.event = data_raw[2] >> 6;
  data.x = ((data_raw[2] & 0xF) << 8) + data_raw[3];
  data.y = ((data_raw[4] & 0xF) << 8) + data_raw[5];
  int tmp;
  tmp = data.x;
  data.x = data.y;
  data.y = tmp;
  data.y = 170 - data.y;
}

至于为什么是修改这个函数?那是因为这个函数是LVGL中调用触摸的驱动就是通过这个函数来的触摸坐标结果,下文会讲到。

到此,触摸驱动正常后就可以开始做LVGL的测试了。

4.LVGL例程测试

我们从LVGL的示例代码开始测试。打开例程修改好screenWidth和screenHeight,注意和TFT_eSPI中定义得要一致,毕竟你再TFT_eSPI中测试是正常的了嘛。但是这里也有坑。

打开例程后特别要注意的是这两行:

    disp_drv.ver_res = screenWidth;

    disp_drv.hor_res = screenHeight;

屏幕进行了旋转,这两行也要跟着更改,width和height与ver_res和hor_res对应关系要实际测试,不行就width和height互换一下。

到此,显示应该基本正常了,然后就是增加触摸驱动。

触摸屏驱动主要是修改触摸屏的事件回调函数:touchpad_read(),该函数在驱动设备注册部分被设定为回调函数。下面给出了例程中涉及触摸驱动的代码。

#include <CST816S.h>



static void ta_event_cb(lv_event_t * e)
//软键盘例程的触摸事件回调函数
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * ta = lv_event_get_target(e);
    lv_obj_t * kb = lv_event_get_user_data(e);
    if(code == LV_EVENT_FOCUSED) {
        lv_keyboard_set_textarea(kb, ta);
        lv_obj_clear_flag(kb, LV_OBJ_FLAG_HIDDEN);
    }

    if(code == LV_EVENT_DEFOCUSED) {
        lv_keyboard_set_textarea(kb, NULL);
        lv_obj_add_flag(kb, LV_OBJ_FLAG_HIDDEN);
    }
}



/*Read the touchpad该函数是修改过的,适配CST816D驱动*/
void my_touchpad_read( lv_indev_drv_t * indev_drv, lv_indev_data_t * data )
{
    if (touch.available())
  {
    data->state = LV_INDEV_STATE_PR;
    /*Set the coordinates*/
    data->point.x = touch.data.x;
    data->point.y = touch.data.y;
    Serial.print("Data x ");
    Serial.println(touch.data.x);
    Serial.print("Data y ");
    Serial.println(touch.data.y);
  }
  else
  {
    data->state = LV_INDEV_STATE_REL;
  }

}




void setup()
{

    touch.begin();

    /*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 a keyboard to use it with an of the text areas*/
    //软键盘例程,和触摸相关的只是lv_obj_add_event_cb句
    lv_obj_t * kb = lv_keyboard_create(lv_scr_act());

    /*Create a text area. The keyboard will write here*/
    lv_obj_t * ta;
    ta = lv_textarea_create(lv_scr_act());
    lv_obj_align(ta, LV_ALIGN_TOP_LEFT, 10, 10);
    lv_obj_add_event_cb(ta, ta_event_cb, LV_EVENT_ALL, kb);
    lv_textarea_set_placeholder_text(ta, "Hello");
    lv_obj_set_size(ta, 140, 80);

    ta = lv_textarea_create(lv_scr_act());
    lv_obj_align(ta, LV_ALIGN_TOP_RIGHT, -10, 10);
    lv_obj_add_event_cb(ta, ta_event_cb, LV_EVENT_ALL, kb);
    lv_obj_set_size(ta, 140, 80);

    lv_keyboard_set_textarea(kb, ta);

}

void loop()
{
    lv_timer_handler(); /* let the GUI do its work */
    delay( 5 );
}

到此,LVGL显示和触摸功能就应该OK了。

运行起来的软键盘例程效果是这样的:

当然,我自己做的开发板是这样的:

这个是适配88*38机壳的前面板,把主要的功能都做上去了,留了扩展IO和电源供电到接口板上。

前面板开孔是自己用小型CNC铣床自己加工的,略显粗糙。

5.关于向esp32上传文件

由于项目需要,我们经常需要在ESP32中存储一些文件,比如webserver、配置文件等,这时我们需要在ESP32上建立一个文件系统,并能够从PC上传文件上去。

我习惯使用arduino2.X,但是arduino 1.8.X才能使用网上广为流传的arduino-esp32fs-plugin插件,通过串口向esp32传输文件。

下载地址:https://github.com/lorol/arduino-esp32fs-plugin

但是要注意:

(1)有2个东西要下载,一个是esp32fs.jar,另一个是mkfatfs.exe,按照链接地址的说明放到相应文件夹中。参考下文:ESP32 Arduino FAT文件系统详细使用教程_esp32 fat_perseverance52的博客-优快云博客

注意esp32fs也有两个版本,一个老的只支持littlefs,新一点的那个增加了支持FATFs和SPIFFS,点开后会弹窗要求选择哪个文件系统。

(2)在arduino的“工具”->partition scheme菜单和flash size菜单两个地方,要根据esp32实际情况,把flash大小和分区方法选正确,否则可能出现刷写后不断重启的情况。partition scheme里分区方法也有littlefs、FATFs、SPIFFS几种选择,不要选错了。

(3)在arduino1.8.X中,使用esp32fs工具上传文件后,可以切换成arduino2.X版本里使用FATFs里listdir()查看到上传的文件。但是要注意例程里有个#define FORMAT_FFAT,如果定义成true,begin代码里有一句if (FORMAT_FFAT)  FFat.format();意思会格式化掉文件系统,所以要把#define FORMAT_FFAT改成false才能保留在arduino1.8.X中上传的文件并浏览到。这也是使用1.8.X上传,在arduino2.X中使用的目的。两个版本里的flash size和partition scheme必须选择一致。

6.LVGL使用ESP32-S3 的内部文件系统

ESP32-S3模块板载的flash空间可以到16M甚至32M,小型应用不用外挂SD卡也够用了。下面是如何用模块内部的flash文件系统存储LVGL所需要的图片文件。

LVGL的版本是8.3.10,老版本的LVGL可能区别比较大,不适用这个方法。

(1)上传图片文件

首先在arduino中测试一下文件系统。我的ESP32-S3是16M的flash。参考前文5中,在arduino1.8.X的“工具”->partition scheme中选择合适的分区方法,我选择的是“16M flash(2M APP,12.5MFATFS)”,然后打开examples的FFat->FFat test例程,另存该文件到自建的文件夹(因为要用前文5中的文件上传工具,所以自建文件夹方便些)。

运行该例程,例程会把ESP32的flash按照所选择的分区进行格式化FATFS文件系统。注意该例程的开头有一句:

#define FORMAT_FFAT true

默认为true,后面begin()部分的代码:

if (FORMAT_FFAT) FFat.format();

会格式化文件系统。

如果改成false,代码就不会在格式化文件系统。

文件系统格式化好后,就使用前文5中的文件上传工具把要使用的jpg、png图片文件上传进去。方法是在自建文件夹中建一个data文件夹,图片文件放进去,上传工具会把该文件夹内的内容整个上传到esp32文件系统的根目录。

(2)flash中的FFat文件系统适配LVGL

找到lv_conf.h注意该文件的路径是在Arduino\libraries下,与lvgl文件夹并列,并不是在lvgl文件夹内部!!我就是被这个疏忽坑了一整天,后来才发现改错了文件(改成了lvgl文件夹内的一个lv_conf.h)。

找到文件的这个部分,修改成这个样子:

/*API for fopen, fread, etc*/
#define LV_USE_FS_STDIO 1
#if LV_USE_FS_STDIO
    #define LV_FS_STDIO_LETTER 'F'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
    #define LV_FS_STDIO_PATH "/ffat"         /*Set the working directory. File/directory paths will be appended to it.*/
    #define LV_FS_STDIO_CACHE_SIZE 0    /*>0 to cache this number of bytes in lv_fs_read()*/
#endif

需要特别注意的是:

LV_FS_STDIO_LETTER是后面LVGL代码中读取文件的“盘符”。

LV_FS_STDIO_PATH则是文件系统的挂载点,注意不是文件系统的根目录!!这个必须和文件系统的初始化代码中的挂载点保持一致!

    if(!FFat.begin(false,"/ffat")){
        Serial.println("FFat Mount Failed");
        return;
    }

你可以看看FFat 文件系统例程文件中include进去的FFat.h中关于该begin函数是怎么定义的。

    bool begin(bool formatOnFail=false, const char * basePath="/ffat", uint8_t maxOpenFiles=10, const char * partitionLabel = (char*)FFAT_PARTITION_LABEL);

默认的FFat文件系统挂载点就是/ffat,所以在调用begin()时,不自己指定的话就是/ffat,也可以自己指定。

如果你要使用LittleFS文件系统,那也是可以的,只是默认挂载点变成/littlefs了:

    bool begin(bool formatOnFail=false, const char * basePath="/littlefs", uint8_t maxOpenFiles=10, const char * partitionLabel="spiffs");

使用LittleFS文件系统在lv_conf.h中也是修改#define LV_USE_FS_STDIO 1部分,因为都是Flash设备,属于STDIO类。

(3)LVGL读取图片文件

先用LVGL的文件读写函数来测试一下文件读取是否正常。主要是看FFat和LVGL对接是否成功、文件的读取路径是否正常。

    lv_fs_file_t f;
    lv_fs_res_t res;
    res = lv_fs_open(&f, "F:/minus.png", LV_FS_MODE_RD);
    if(res != LV_FS_RES_OK) Serial.println("lv fs open error");
    lv_fs_close(&f);

没报错的话,说明LVGL能正常读取FFat文件系统中的文件了。

在lv_vonf.h中使能png解码器:#define LV_USE_PNG 1,然后就可以试试看图片能否正常显示出来:

    lv_obj_t * img;

    img = lv_img_create(lv_scr_act());
    lv_img_set_src(img, "F:/minus.png");
    lv_obj_align(img, LV_ALIGN_TOP_RIGHT, 0, 0);    

如果文件打开没有报错,屏幕上却显示no data,尝试把lv_conf.h中的内存分配改大些,前提是你的esp32模块有那么多内存可用。下面这行默认是(32U * 1024U),我改到了(128U * 1024U)方能正常解码96*96像素的png图片。

#define LV_MEM_SIZE (128U * 1024U) 

(5)这个部分的关键代码

#include "FS.h"
#include "FFat.h"
#define FORMAT_FFAT false //不再格式化文件系统,否则使用arduino 1.8.x中ESP32fs工具上传的文件会丢失


void setup()
{
    Serial.begin( 115200 ); /* prepare for possible serial debug */
    Serial.setDebugOutput(true);

    
    if (FORMAT_FFAT) FFat.format();
    if(!FFat.begin(false,"/ffat")){
        Serial.println("FFat Mount Failed");
        return;
    }

    //测试文件读取,可以不用
    lv_fs_file_t f;
    lv_fs_res_t res;
    res = lv_fs_open(&f, "F:/minus.png", LV_FS_MODE_RD);
    if(res != LV_FS_RES_OK) Serial.println("lv fs open error");
    lv_fs_close(&f);

    img = lv_img_create(lv_scr_act());
    lv_img_set_src(img, "F:/minus.png");
    lv_obj_align(img, LV_ALIGN_TOP_RIGHT, 0, 0);  
}

7.LVGL中使用中文字体

暂时未了解到支持全部中文字库的方法。本方法是arduino IDE下的,vscode下有细节差异。

LVGL提供了一种比较节省flash存储空间的方法,即只把你所需要的中文转换成C字节阵列。

转换工具在:https://lvgl.io/tools/fontconverter

自己在电脑的c:\windows\fonts下面选择一种你中意的ttf字体,设置好字库名(例Font_CN)、大小,输入你想转换的文字,点击convert即可。注意:

(1)转换工具似乎不支持长路径和中文路径,所以最好把ttf字体文件复制到驱动器根目录再选择。

(2)不能使用特殊字符、运算符作为字库名,比如减号-、百分号%、除号/等,但下划线_可以。

转换成功会会下载一个.c文件,拷贝到lvgl/src/font文件夹。打开该文件,修改

#include "../../lvgl/lvgl.h"

行为你实际的lvgl.h文件所在路径即可,LVGL会自动编译你拷贝进去的字库文件。

剩下的就是你需要再arduino的程序文件中声明要使用该字库即可:

LV_FONT_DECLARE(Font_CN);

然后你就可以创建一个带中文的按钮来测试了。

      lv_obj_t* btnStartStop=lv_btn_create(leftContainer);
      //lv_obj_set_pos(btnStartStop, 0, 170-32);
      lv_obj_set_size(btnStartStop, 50, 32);
      lv_obj_align(btnStartStop,LV_ALIGN_BOTTOM_LEFT,-15,15);
      labelStartStop = lv_label_create(btnStartStop);
      lv_label_set_text(labelStartStop, "启动");
      lv_obj_set_style_text_font(labelStartStop,&Font_CN,0);
      lv_obj_set_align(labelStartStop, LV_ALIGN_CENTER);

注意:如果想使用ASCII字符的大号字体,需要再lvgl.h文件中启用该字体。

8.LVGL中回调函数操作多个对象

回调函数一定要定义成静态函数。回调函数中可以操作全局变量,回调函数也有一个user_data参数,可以把一个你想操作的对象传给回调函数进行操作。

所以一般是这样的,这是lvgl文档中的例子:

#include "../lv_examples.h"
#if LV_BUILD_EXAMPLES && LV_USE_BTN
static void btn_event_cb(lv_event_t * e)
{
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t * btn = lv_event_get_target(e);
    if(code == LV_EVENT_CLICKED) {
        static uint8_t cnt = 0;
        cnt++;
        /*Get the first child of the button which is the label and change its text*/
        lv_obj_t * label = lv_obj_get_child(btn, 0);
        lv_label_set_text_fmt(label, "Button: %d", cnt);
    }
}
/**
* Create a button with a label and react on click event.
*/
void lv_example_get_started_1(void)
{
    lv_obj_t * btn = lv_btn_create(lv_scr_act()); /*Add a button the current screen*/
    lv_obj_set_pos(btn, 10, 10); /*Set its position*/
    lv_obj_set_size(btn, 120, 50); /*Set its size*/
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); /*Assign a callback to the button*/
    lv_obj_t * label = lv_label_create(btn); /*Add a label to the button*/
    lv_label_set_text(label, "Button"); /*Set the labels text*/
    lv_obj_center(label);
}
#endif

但是某些时候LVGL的回调函数需要处理多个对象,而这些对象并不一定是全局变量(典型的,有可能是自己定义的类变量),没法直接对其操作,这个时候就得像办法把多个对象传递给回调函数了。

例如:按钮btn1被点击时,你需要隐藏label1、label2。

方法有很多,比如:

(1)使用同一个隐藏的父对象

把label1和label2都定义成一个隐藏对象obj下,作为其child。首先定义一个空对象:

lv_obj_t * obj=lv_obj_create(lv_scr_act());

然后将其作为父对象来创建label1和2:

lv_label_create(label1,obj);
lv_label_create(label2,obj);

在btn1的回调函数cb中直接把obj作为user_data传过去,通过

lv_obj_add_flag(obj,LV_OBJ_FLAG_HIDDEN)

把两个label都隐藏掉。

(2)使用指针数组

把你想操作的对象指针依次放到数组里。注册回调函数时把指针数组作为user_data传给回调函数。然后在回调函数中去依次解出来操作。

首先定义一个指针数组:

lv_obj_t * lvObjPtrArray[5]; 

然后把两个label都加到数组里:

lv_label_create(label1,lv_scr_act());
lv_label_create(label2,lv_scr_act());

lvObjPtrArray[0]=label1;
lvObjPtrArray[1]=label2;

在btn的回调函数里把数组作为user_data注册进去:

lv_obj_add_event_cb(btn1, btn1_event_cb, LV_EVENT_ALL, lvObjPtrArray);

在回调函数里,需要把该指针数组分解成各个对象,就可以对其操作了:

static void btn1_event_cb(lv_event_t * e)
    {
      lv_obj_t ** ar = (lv_obj_t **)e->user_data;
      lv_obj_t * label1=(lv_obj_t *)ar[0];
      lv_obj_t * label2=(lv_obj_t *)ar[1];
    
      lv_obj_add_flag(label1,LV_OBJ_FLAG_HIDDEN);
      lv_obj_add_flag(label2,LV_OBJ_FLAG_HIDDEN);
    }

但是要特别小心的是,你要保证回调函数被调用时,lvObjPtrArray中的所有对象都已经被初始化了,或者是存在着的没有被删除掉。否则回调函数操作该对象会直接导致CPU core重启。最好在回调函数里先判断一下对象是否是NULL,不是NULL再对其操作。

(3)使用结构体

定义一个结构体,把需要操作的对象指针放到结构体里面。NXP的gui guider软件生成GUI就是这个操作方法。

定义结构体lv_gui:

typedef lv_gui{
    lv_obj_t * btn1;
    lv_obj_t * label1;
    lv_obj_t * label2;
};

可以在类里面定义,也可以在外面,没有实质区别。

再定义一个结构体对象:

lv_gui * gui1;

注册回调函数时把结构体对象传递过去:

lv_obj_add_event_cb(btn1, btn1_event_cb, LV_EVENT_ALL, gui1);

回调函数里操作对象:

static void btn1_event_cb(lv_event_t * e)
    {
      lv_gui * gui1 = (lv_gui *)e->user_data;
      lv_obj_t * label1=gui1->label1;
      lv_obj_t * label2=gui1->label2;
    
      lv_obj_add_flag(label1,LV_OBJ_FLAG_HIDDEN);
      lv_obj_add_flag(label2,LV_OBJ_FLAG_HIDDEN);
    }

### LVGL 横屏显示花屏解决方案 对于LVGL框架在横屏模式下出现的花屏问题,可以从多个方面进行排查和调整。具体措施包括但不限于配置文件中的参数设置以及硬件控制器的相关属性。 #### 修改启动文件中的内存分配 如果遇到由于内存不足引起的花屏现象,则可以在`startup_gd32f407_427.s`这样的启动脚本里适当增大堆栈空间或其他形式的工作缓冲区尺寸来解决问题[^1]。这有助于确保有足够的资源供图形库正常运作而不至于因为溢出而导致视觉异常。 #### 屏幕控制器类型与接口匹配 确认使用的显示屏型号及其对应的驱动程序是否正确无误,并且检查Display Controller Type Interface等选项是否按照实际硬件情况进行了恰当的选择。例如,在杰理方案AC79项目中通过宏定义指定了LCD的具体规格如宽度、高度及颜色格式等信息: ```c #if TCFG_LCD_ST7789V_ENABLE // 客户屏放最后 lcd_w = 240; lcd_h = 280; ... #endif ``` 这段代码片段展示了如何针对特定类型的显示器(此处为ST7789V)设定其物理特性参数[^5]。同样地,在其他平台或不同种类的屏幕上也需要做类似的适配处理以保证图像渲染的一致性和稳定性。 #### 配置色彩深度及相关属性 为了防止因色彩表示方式不兼容而引发的画面失真,应当依据目标设备的支持能力合理指定Color Depth(即像素位数),并视具体情况决定是否启用RGB565交换功能(LV_COLOR_16_SWAP)[^3]。这些设置通常位于lv_conf.h或者其他全局配置头文件内,需谨慎对待以免造成不必要的干扰。 #### 调整显示方向 当移植到新的平台上时可能会面临坐标系转换带来的挑战,特别是涉及到旋转角度变化的情况下更要注意保持逻辑坐标的连贯性。ESP32-LVGL移植过程中就遇到了类似的情况,通过对ST7735屏幕的方向控制实现了正确的画面呈现[^4]。可以尝试查阅所用液晶面板的数据手册了解支持哪些方位变换指令集,并据此更新初始化序列使最终输出符合预期布局。 综上所述,要彻底消除LVGL应用下的横向花屏状况,建议综合考虑上述几个方面的因素逐一验证直至找到根本原因所在;同时也要留意官方文档提供的指导说明以便获取更多技术支持资料辅助完成整个调试流程。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值