使用esp32 idf 移植LVGL驱动ST7796和GT911触摸屏的详尽攻略

1. 开发环境说明

开发板:esp32
显示器驱动:ST7796(SPI接口)
触摸屏驱动:GT911

esp-idf:4.4.7(vscode集成)
LVGL:8.3.11

2. 配置项目

2.1 创建项目
  1. 新建项目,选择事例hello world 作为模板,编译无问题
2.2 置入LVGL库
  1. 新建component文件夹

  2. 下载lvgl 8.3.11和lvgl esp32 drivers文件,并解压至component文件夹下,改名去掉版本号。

下载地址
LVGL:
	https://github.com/lvgl/lvgl/releases/tag/v8.3.11
LVGL esp32 drivers:
	https://github.com/lvgl/lvgl_esp32_drivers

3. 编辑项目

3.1 初始配置
  1. main文件夹下将主文件改为main.c,Cmakelists文件中有关变量同时更改,否则编译检查会报错
    在这里插入图片描述
    在这里插入图片描述

  2. 插入esp32开发板,选择com口以及开发板型号
    在这里插入图片描述

3.2 在menuconfig中配置lvgl

打开menuconfig查看lvgl设置是否同步,若没同步则重启vscode。调整设置如下:
在这里插入图片描述

3.2.1 Serial flasher config
- Flash SPI speed:80MHz
- Flash size:8M
3.2.2 Color settings
- Color depth:16:RGB565
- √ Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI).

“交换RGB565颜色的2个字节。如果显示器具有8位接口,则很有用”。该处2位RGB565设置原因详见[[2. 案例2:esp32移植LVGL#^3313c5]]

3.2.3 Memory settings
- Size of the memory used by `lv_mem_alloc` in kilobytes (>= 2kB):48
3.2.4 LVGL ESP Drivers
LVGL TFT Display controller:
	- Display orientation:Portrait inverted(竖屏显示,根据显示情况调整)
	- Select a display controller model:ST7796S(依据显示屏驱动选择型号,若无型号则需要自己手动移植)
	- TFT SPI Bus:SPI2_HOST(选择TFT显示器连接到的SPI总线)
	- TFT Data Transfer Mode:DIO (为TFT显示选择SPI SIO/DIO/QIO传输模式)
	- √ Use custom SPI clock frequency.
	- Select a custom frequency:40MHz

按照接线设置显示屏GPIO接口信息:

在这里插入图片描述

Display Pin Assignments:
	- GPIO for MOSI (Master Out Slave In):19
	- 取消勾选 GPIO for MISO (Master In Slave Out)
	- GPIO for CLK (SCK / Serial Clock):23
	- GPIO for CS (Slave Select):22
	- GPIO for DC (Data / Command):14
	- GPIO for Reset:12
	- √ Is backlight turn on with a HIGH (1) logic level?(重要)
	- GPIO for Backlight Control:2
	- 

设置触摸板信息:

LVGL Touch controller:
	- Select a touch panel controller model.:GT911
	- Touchpanel Configuration (GT911)
		- 取消勾选 Swap X with Y coordinate.
		- √ Invert X coordinate value.
		- √ Invert Y coordinate value.
	- Select an I2C port for the touch panel:I2C port0
	- I2C Port 0
		- √ Enable I2C port 0
		- SDA (GPIO pin):18
		- SCL (GPIO pin):16
		- Frequency (Hz):100000
		- 

编译后报错,下一步进行调试报错。

3.3 调试报错
3.3.1 在lvgl_helpers.h中增加显示屏分辨率参数

^879332

  1. 报错信息:
H:/ESP32/ESP_IDF_develop_env/3_Fit_LVGL/components/lvgl_esp32_drivers/lvgl_helpers.h:57:25: error: 'LV_HOR_RES_MAX' undeclared (first use in this function); did you mean 'LV_HOR_RES'?
 #define DISP_BUF_SIZE  (LV_HOR_RES_MAX * 40)
  1. 打开lvgl_helpers.h查看
#if defined (CONFIG_CUSTOM_DISPLAY_BUFFER_SIZE)
#define DISP_BUF_SIZE   CONFIG_CUSTOM_DISPLAY_BUFFER_BYTES
#else
#if defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_ST7789)
#define DISP_BUF_SIZE  (LV_HOR_RES_MAX * 40)
#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_ST7735S
#define DISP_BUF_SIZE  (LV_HOR_RES_MAX * 40)
#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_ST7796S
#define DISP_BUF_SIZE  (LV_HOR_RES_MAX * 40)
#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_HX8357
#define DISP_BUF_SIZE  (LV_HOR_RES_MAX * 40)
...

其上部说明文档:

/* DISP_BUF_SIZE value doesn't have an special meaning, but it's the size
 * of the buffer(s) passed to LVGL as display buffers. The default values used
 * were the values working for the contributor of the display controller.
 *
 * As LVGL supports partial display updates the DISP_BUF_SIZE doesn't
 * necessarily need to be equal to the display size.
 *
 * When using RGB displays the display buffer size will also depends on the
 * color format being used, for RGB565 each pixel needs 2 bytes.
 * When using the mono theme, the display pixels can be represented in one bit,
 * so the buffer size can be divided by 8, e.g. see SSD1306 display size.
 *
 * DISP_BUF_SIZE值没有特殊的含义,但它是作为显示缓冲区传递给LVGL的缓冲区的大小。使用的默认值为显示控制器的贡献者工作的值。
 * 由于LVGL支持部分显示更新,所以DISP_BUF_SIZE不一定需要等于显示大小。
 * 当使用RGB显示时,显示缓冲区的大小也取决于所使用的颜色格式,对于RGB565,每个像素需要2字节。
 * 当使用单声道主题时,显示像素可以用一位表示,因此缓冲区大小可以除以8,例如参见SSD1306显示大小。

^3313c5

这说明此段代码定义了一个名为缓冲区大小DISP_BUF_SIZE的值,该值因不同显示驱动取自不同值,但都与液晶屏水平长度LV_HOR_RES_MAX有关。因该值未设置导致报错。

解决方法:增加显示屏分辨率变量数值到此段代码前,代码如下:

//配置显示屏分辨率:
#ifndef LV_HOR_RES_MAX
#define LV_HOR_RES_MAX (320)
#endif
#ifndef LV_VER_RES_MAX
#define LV_VER_RES_MAX (480)
#endif

继续编译报错

3.3.2 在lvgl_helpers.c中明确SPI_Host

报错信息

H:/ESP32/ESP_IDF_develop_env/3_Fit_LVGL/components/lvgl_esp32_drivers/lvgl_helpers.h:57:25: error: 'LV_HOR_RES_MAX' undeclared (first use in this function); did you mean 'LV_HOR_RES'?
 #define DISP_BUF_SIZE  (LV_HOR_RES_MAX * 40)

H:/ESP32/ESP_IDF_develop_env/3_Fit_LVGL/components/lvgl_esp32_drivers/lvgl_helpers.c:157:28: error: 'SPI_HOST_MAX' undeclared (first use in this function); did you mean 'GPIO_PORT_MAX'?
     assert((0 <= host) && (SPI_HOST_MAX > host));

段代码所在位置如下:

bool lvgl_spi_driver_init(int host,
    int miso_pin, int mosi_pin, int sclk_pin,
    int max_transfer_sz,
    int dma_channel,
    int quadwp_pin, int quadhd_pin)
{
    assert((0 <= host) && (SPI_HOST_MAX > host));
    const char *spi_names[] = {
        "SPI1_HOST", "SPI2_HOST", "SPI3_HOST"
    };

    ESP_LOGI(TAG, "Configuring SPI host %s", spi_names[host]);
    ESP_LOGI(TAG, "MISO pin: %d, MOSI pin: %d, SCLK pin: %d, IO2/WP pin: %d, IO3/HD pin: %d",
        miso_pin, mosi_pin, sclk_pin, quadwp_pin, quadhd_pin);

    ESP_LOGI(TAG, "Max transfer size: %d (bytes)", max_transfer_sz);

    spi_bus_config_t buscfg = {
        .miso_io_num = miso_pin,
        .mosi_io_num = mosi_pin,
        .sclk_io_num = sclk_pin,
        .quadwp_io_num = quadwp_pin,
        .quadhd_io_num = quadhd_pin,
        .max_transfer_sz = max_transfer_sz
    };

    ESP_LOGI(TAG, "Initializing SPI bus...");
    #if defined (CONFIG_IDF_TARGET_ESP32C3)
    dma_channel = SPI_DMA_CH_AUTO;
    #endif
    esp_err_t ret = spi_bus_initialize(host, &buscfg, (spi_dma_chan_t)dma_channel);
    assert(ret == ESP_OK);
    
    return ESP_OK != ret;
}

该段代码定义了一个bool类型的lvgl_spi_driver_init()函数用于初始化一个ESP32上的SPI总线。其中assert((0 <= host) && (SPI_HOST_MAX > host));用于检查host是否在有效范围内。

  • 以esp32为例查阅芯片技术手册:
esp32内部设计了4个SPI控制器:
	SPI0:cache访问外部存储单元接口
	SPI1:主机使用
	SPI2:主从两用(使用带前缀HSPI的信号总线)
	SPI3:主从两用(使用带前缀VSPI的信号总线)

在这里插入图片描述

这篇博文对GPIO引脚进行了较好的解释说明:
https://blog.csdn.net/chentuo2000/article/details/128269394?spm=1001.2014.3001.5506

在这里插入图片描述

这些引脚是可以重新映射的,所以我购买的开发板进行了混用,因此该处报错的解决方法比较简单。

简单粗暴的可将该数值定义为 3 :

#define SPI_HOST_MAX (3)

实际上,不同esp32芯片初始化总线方式不同,该段代码

assert((0 <= host) && (SPI_HOST_MAX > host));
const char *spi_names[] = {
    "SPI1_HOST", "SPI2_HOST", "SPI3_HOST"
};

可被修改为:

#if defined(CONFIG_IDF_TARGET_ESP32)
    assert((SPI_HOST <= host) && (VSPI_HOST >= host));
    const char *spi_names[] = {
        "SPI_HOST", "HSPI_HOST", "VSPI_HOST"};
 
    dma_channel = SPI_DMA_CH_AUTO;
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
    assert((SPI_HOST <= host) && (HSPI_HOST >= host));
    const char *spi_names[] = {
        "SPI_HOST", "", ""};
 
    dma_channel = SPI_DMA_CH_AUTO;
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
    assert((SPI1_HOST <= host) && (SPI3_HOST >= host));
    const char *spi_names[] = {
        "SPI1_HOST", "SPI2_HOST", "SPI3_HOST"};
 
    dma_channel = SPI_DMA_CH_AUTO; /* SPI_DMA_CH_AUTO */;
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
    assert((SPI1_HOST <= host) && (SPI3_HOST >= host));
    const char *spi_names[] = {
        "SPI_HOST", "HSPI_HOST", "VSPI_HOST"};
    dma_channel = SPI_DMA_CH_AUTO;
#else
#error "Target chip not selected"
#endif
3.4 修改LVGL配置文件

按照lvgl官方使用说明修改配置文件,移植示例项目。

官方文档说明:
https://lvgl.100ask.net/8.3/porting/project.html

在这里插入图片描述

3.4.1 修改lv_conf.h以设置LVGL项目
  • lv_conf.h文件说明:
该文件为lvgl做了如下配置:
	- 颜色设置:如1位、8位RGB332、16位RGB565、32位ARGB8888
	- 内存设置:使用内外置内存及大小等配置
	- 其他设置:屏幕读写频率、心跳频率
	- 功能配置:绘图、GPU、日志、断点等
	- 编译器设置
	- 字体的使用
	- 文本设置
	- 小部件使用
	- 额外的组件:部件、主题、布局等
	- 例子
	- 演示使用
  1. 上述配置可以在menuconfig进行设置。这里仅将lv_conf_template.h复制并重命名为lv_conf.h文件,打开后查看说明:
Copy this file as `lv_conf.h`
 * 1. simply next to the `lvgl` folder
 * 2. or any other places and
 *    - define `LV_CONF_INCLUDE_SIMPLE`
 *    - add the path as include path

将此文件复制为' lv_conf.h '
	1. 复制在“lvgl”文件夹旁边
	2. 复制到其他地方时要进行如下调整:
		- 定义“LV_CONF_INCLUDE_SIMPLE”
		- 添加路径作为包含路径

修改后文件夹布局如下:
	|-lvgl
	|-lv_conf.h
	|-other files and folders

修改0为1以启用其内容。

#if 1 /*Set it to "1" to enable content*/
3.4.2 确认lvgl_helpers.h以适配显示驱动

打开lvgl_esp32_drivers文件夹下的lvgl_spi_conf.h文件,查看驱动文件有关配置。

  • lvgl_spi_conf.h文件说明:
该文件为lvgl做了如下驱动配置:
	- 定义:显示引脚、触摸引脚、检测共享SPI总线
	- 显示屏驱动型号(不同型号的if配置)
	- 触摸驱动配置:触摸频率及SPI模式
	- 其他宏等

上述配置可以在menuconfig进行设置。

该处仅确认显示器分辨率有关参数是否设置该处设置体现在了lvgl_helpers.h中。详见[[2. 案例2:esp32移植LVGL#^879332]]

3.4.3 修改lv_port_disp.c以移植显示屏
a. 原理分析

要为LVGL注册显示,必须初始化lv_disp_draw_buf_tlv_disp_drv_t变量。

- `lv_disp_draw_buf_t`包含称为绘制缓冲区的内部图形缓冲区。 
- `lv_disp_drv_t`包含与显示器交互和操作低级绘图行为的回调函数。

该变量全局搜索后发现在lvgl-examples-porting文件夹下的lv_port_disp_template.c文件进行了上述变量定义,同时使用说明要求使能。

另外,也可以按照官方说明自行编写显示代码。总体来讲,需要如下步骤:

1. lv_disp_draw_buf_t变量初始化:
	1. 定义用于存储缓冲区的静态或全局变量
		static lv_disp_draw_buf_t disp_buf;
	1. 静态或全局缓冲区。第二个缓冲区是可选的
		static lv_color_t buf_1[MY_DISP_HOR_RES * 10];
		static lv_color_t buf_2[MY_DISP_HOR_RES * 10];
	2. 使用缓冲区初始化“disp_buf”
		lv_disp_draw_buf_init(&disp_buf, buf_1, buf_2, MY_DISP_HOR_RES*10);
		
2. `lv_disp_drv_t`显示驱动程序(该部分在drivers已经配置完成,可以直接调用):
	1. 用`lv_disp_drv_init(&disp_drv)`
	2. 其字段需要设置(详官方文档)
    3. 它需要在LVGL中注册`lv_disp_drv_register(&disp_drv)`

当然也可以使用lv_port_disp移植。

  • lv_port_disp.c文件说明:
该文件进行了显示移植:
	- void lv_port_disp_init(void)
		- 初始化显示:disp_init();
		- 创建一个用于绘图的缓冲区:分3类,解释如下
		- 在LVGL中注册显示:
			- 设置显示器的分辨率(已在drivers配置)
			- 设置显示缓冲区模式
			- 将缓冲区内容复制到显示器(屏幕刷新,drivers已配置)
			- 注册驱动程序(已在drivers配置)
缓冲区解释如下(管网使用说明)https://lvgl.100ask.net/8.3/porting/display.html:

- 一个缓冲区
	如果只使用一个缓冲区,LVGL将屏幕的内容绘制到该绘制缓冲区并将其发送到显示器。LVGL需要等到缓冲区的内容发送到显示器后再在其中绘制新内容。

- 两个缓冲区
	如果使用两个缓冲区,LVGL可以绘制到一个缓冲区中,而另一个缓冲区的内容在后台发送到显示器。应该使用DMA或其他硬件将数据传输到显示器,以便MCU可以继续绘制。这样,显示器的渲染和刷新就变成了并行操作。

- 完全刷新
	在显示驱动程序(`lv_disp_drv_t`)中,启用`full_refresh`位将迫使LVGL始终重绘整个屏幕。这适用于_一个缓冲区_和_两个缓冲区_模式。 如果启用`full_refresh`并提供两个屏幕大小的绘制缓冲区,LVGL的显示处理工作方式类似于“传统”双缓冲。 这意味着`flush_cb`回调只需要更新帧缓冲区的地址(`color_p`参数)。 如果MCU有LCD控制器外设,而不是通过串行链路访问的外部显示控制器(例如ILI9341或SSD1963),则应使用此配置。后者通常太慢,无法在全屏重绘时保持高帧速率。

- 另外,还有直接模式(略)

因此,该文件提供了一个实际可用的显示移植代码。仅需将绘图缓存部分进行适配使用,同时将drivers的部分变量引用在该文件中进行驱动初始化、明确分辨率、屏幕刷新等替代文件功能,即可实现显示屏的移植。具体操作如下:

b. 使能及包含更名
  1. 在components文件夹下新建lv_porting文件夹,将.c和.h文件复制其中并改名为lv_port_disp.clv_port_disp.h
#if 1
	将0改为1
#include "lv_port_disp.h"
	将#include "lv_port_disp_template.h"改名
c. 屏幕分辨率引用
#ifndef MY_DISP_HOR_RES
    #define MY_DISP_HOR_RES    LV_HOR_RES_MAX
#endif

#ifndef MY_DISP_VER_RES
    #define MY_DISP_VER_RES    LV_VER_RES_MAX
#endif

注意到,该变量在文件中有两处引用,一处为缓冲区大小,一处为设置屏幕旋转。这里保留是在该处设置屏幕旋转。

这里要包含lvgl_helpers.h头文件。Cmakelists文件在后面编辑

#include "lv_port_disp.h"
#include <stdbool.h>

#include "lvgl_helpers.h"   //新增
d. 定义缓冲区

缓存方式选择:双缓存

代码分析:

/* Example for 2) */
static lv_disp_draw_buf_t draw_buf_dsc_2;
static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];     
static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10];     
lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10);

这段代码定义了两个用于双缓冲绘图的缓冲区buf_2_1buf_2_2,并初始化lv_disp_draw_buf_init()了一个绘图缓冲区描述符draw_buf_dsc_2来描述这两个缓冲区。

同时,将一个屏幕水平变量赋值给缓冲区buf_2_*以定义缓冲区的大小。该处代码使用的尺寸为屏幕宽度*10,而不同显示驱动支持的缓存区大小不同,需要依据驱动型号适配缓冲区大小

查阅lvgl_helpers.h文件,这里定义了一个DISP_BUF_SIZE值,代表显示缓冲区传递给LVGL的缓冲区的大小。同时对不同驱动进行了适配:

#if defined (CONFIG_LV_TFT_DISPLAY_CONTROLLER_ST7789)
#define DISP_BUF_SIZE  (LV_HOR_RES_MAX * 40)

#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_ST7735S
#define DISP_BUF_SIZE  (LV_HOR_RES_MAX * 40)

#elif defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_ST7796S
#define DISP_BUF_SIZE  (LV_HOR_RES_MAX * 40)

...

因此,这里将中括号内的变量替换为DISP_BUF_SIZE即可。修改后的代码如下:

/* Example for 2) */
static lv_disp_draw_buf_t draw_buf_dsc_2;
static lv_color_t buf_2_1[DISP_BUF_SIZE];     
static lv_color_t buf_2_2[DISP_BUF_SIZE];     
lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, DISP_BUF_SIZE);

这里要包含lvgl_helpers.h头文件。Cmakelists文件在后面编辑

其余注释掉或者删除(避免warning)

// /* Example for 1) */
// ...

/* Example for 2) */
static lv_disp_draw_buf_t draw_buf_dsc_2;
static lv_color_t buf_2_1[DISP_BUF_SIZE];     
static lv_color_t buf_2_2[DISP_BUF_SIZE];     
lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, DISP_BUF_SIZE);

// /* Example for 3)
// / ...

...

/*Set a display buffer*/
disp_drv.draw_buf = &draw_buf_dsc_2;
e. 定义刷屏方式

将缓冲区的内容复制到显示器,需要定义一个屏幕刷新函数。在lv_port_disp.c文件中定义了一个刷屏函数,其代码为:

/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush;

...

//定义刷屏函数
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    if(disp_flush_enabled) {
        int32_t x;
        int32_t y;
        for(y = area->y1; y <= area->y2; y++) {
            for(x = area->x1; x <= area->x2; x++) {
                color_p++;
            }
        }
    }
    lv_disp_flush_ready(disp_drv);
}

带段代码定义了一个名为disp_flush()的函数,该函数传入了指向显示驱动器的指针、指向一个矩形区域的指针、指向颜色数组的指针。同时,从x=0/y=0坐标处逐个像素刷新。该函数刷新效率最低。

查阅disp_driver.c文件定义了disp_driver_flush()函数,并且根据不同驱动有不同的刷屏方式。例如,显示屏驱动文件st7796s.c也进行了刷新函数定义

void st7796s_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
    uint8_t data[4];
    
    /*Column addresses*/
    st7796s_send_cmd(0x2A);  
	    //ST7796S_CMD_COLUMN_ADDRESS_SET
    data[0] = (area->x1 >> 8) & 0xFF;
    data[1] = area->x1 & 0xFF;
    data[2] = (area->x2 >> 8) & 0xFF;
    data[3] = area->x2 & 0xFF;
    st7796s_send_data(data, 4);

    /*Page addresses*/
    st7796s_send_cmd(0x2B);
	    //ST7796S_CMD_PAGE_ADDRESS_SET
    data[0] = (area->y1 >> 8) & 0xFF;
    data[1] = area->y1 & 0xFF;
    data[2] = (area->y2 >> 8) & 0xFF;
    data[3] = area->y2 & 0xFF;
    st7796s_send_data(data, 4);

    /*Memory write*/
    st7796s_send_cmd(0x2C);
		//ST7796S_CMD_MEMORY_WRITE

    uint32_t size = lv_area_get_width(area) * lv_area_get_height(area);
    st7796s_send_color((void *)color_map, size * 2);
}

该函数通过一个数组实现刷新指定的区域。这种方式可以显著提高效率,因此将该函数赋值给显示器刷屏函数并注释掉原函数即可。

/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_driver_flush;

//注释掉静态变量声明
//static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);

//注释掉函数
//void st7796s_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
//{
//}

这里要包含disp_driver.h头文件。Cmakelists文件在后面编辑

f. 增加屏幕初始化函数

在静态函数里定义了一个屏幕初始化函数disp_init()里面为空,目的是初始化显示器和所需的外围设备,需要自行添加。这里将驱动启动函数添加在内

查阅lvgl_helpers.c文件,这里定义了一个lvgl_driver_init()变量,其作用如下:

接口和驱动程序初始化lvgl_driver_init():
	1. 打印屏幕分辨率;
	2. if初始化FT81X的SPI主机和触摸(略);
	3. if初始化共享SPI主机;
	4. 显示控制器初始化
		1. 初始化SPI和I2C总线:lvgl_spi_driver_init()
		2. 初始化显示屏驱动:disp_driver_init()
	5. 触摸控制器初始化
		1. 初始化SPI和I2C总线:lvgl_spi_driver_init()
		2. 初始化触摸驱动:touch_driver_init()

其中:
1. 初始化显示屏驱动在disp_driver.c文件中定义;
2. 初始化触摸驱动touch_driver.c文件中定义。

所以这里仅将lvgl_driver_init()置入就可以了。修改后为

static void disp_init(void)
{
    lvgl_driver_init();
}
g. 修改lv_port_disp.h头文件
  1. 使能,将templ有关表述删除(文件名和LV_PORT_DISP_TEMPL_H变量
/*Copy this file as "lv_port_disp.h" and set this value to "1" to enable content*/

#if 1

#ifndef LV_PORT_DISP_H
#define LV_PORT_DISP_H

...

#endif /*LV_PORT_DISP_H*/
h. 编辑Cmakelists文件

因为在disp函数里调用了lvgl和lvgl_esp32_drivers里的函数,所以还要修改CMakeList

file(GLOB_RECURSE SOURCES   ./*.c
							)

idf_component_register(SRCS ${SOURCES}
                   INCLUDE_DIRS
                                .
                    REQUIRES  lvgl
                              lvgl_esp32_drivers
                              )
3.4.4 移植触摸屏
a. 原理分析

要注册输入设备,必须初始化lv_indev_drv_t变量。注意:在注册任何输入设备之前,请务必注册至少一个显示器。

官方事例,初始化输入驱动,设置输入设备驱动的类型和读取回调函数,然后注册到lvgl中:


lv_disp_drv_register(&disp_drv);
	注册输入设备前要注册显示器,3.4.3已完成

static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type =...
	该`type`成员可以是:
		- `LV_INDEV_TYPE_POINTER`触摸板或鼠标
	    - `LV_INDEV_TYPE_KEYPAD`键盘或小键盘
	    - `LV_INDEV_TYPE_ENCODER`编码器
	    - `LV_INDEV_TYPE_BUTTON`外部按钮虚拟地按压屏幕

indev_drv.read_cb =...
	`read_cb`是一个函数指针,它将被定期调用以报告输入设备的当前状态。

lv_indev_t * my_indev = lv_indev_drv_register(&indev_drv);
	在LVGL中注册驱动程序并保存创建的输入设备对象

其中,对于触摸板设备配置参考如下,这里提供了一个触摸按下的read函数,用于读取触摸输入设备的状态并填充到给定的数据结构中:

indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = my_input_read;
	//上一段代码有关变量定义为触摸板参数

...

void my_input_read(lv_indev_drv_t * drv, lv_indev_data_t*data)
{
  if(touchpad_pressed) {
    data->point.x = touchpad_x;
    data->point.y = touchpad_y;
    data->state = LV_INDEV_STATE_PRESSED;
  } else {
    data->state = LV_INDEV_STATE_RELEASED;
  }
}

lvgl-examples-porting文件夹下的lv_port_indev_template.c文件包含了上述操作,这里仅分析触摸屏有关代码,其结构如下,:

void lv_port_indev_init(void)
{
	static lv_indev_drv_t indev_drv;
	touchpad_init();
		初始化触摸板,注意:这里是空函数,需要配置
	lv_indev_drv_init(&indev_drv);
		设置输入类型和读取参数
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    
    indev_touchpad = lv_indev_drv_register(&indev_drv);
	    注册触摸板输入设备
b. 使能及包含更名

将文件复制到lv_porting文件夹下并改名为lv_port_indev.clv_port_indev.h

  1. lv_port_indev.c文件修改:
#if 1
	将0改为1
#include "#include "lv_port_indev.h"
	将#include "lv_port_indev_template.h"改名
  1. lv_port_indev.h文件修改:

使能,将templ有关表述删除(文件名和_TEMPL_H变量

#if 1

#ifndef LV_PORT_INDEV_H
#define LV_PORT_INDEV_H
...
#endif /*LV_PORT_INDEV_H*/
c. 配置touchpad_init();

lvgl_helpers.c文件中,当执行屏幕初始化函数调用到lvgl_driver_init()函数时,已经进行了屏幕和触摸的初始化。
关于触摸初始化调用函数如下:

#if CONFIG_LV_TOUCH_CONTROLLER != TOUCH_CONTROLLER_NONE
    #if defined (CONFIG_LV_TOUCH_DRIVER_PROTOCOL_SPI)
        ESP_LOGI(TAG, "Initializing SPI master for touch");

        lvgl_spi_driver_init(TOUCH_SPI_HOST,
            TP_SPI_MISO, TP_SPI_MOSI, TP_SPI_CLK,
            0 /* Defaults to 4094 */, 2,
            -1, -1);

        tp_spi_add_device(TOUCH_SPI_HOST);

        touch_driver_init();
    #elif defined (CONFIG_LV_I2C_TOUCH)
        touch_driver_init();
    #elif defined (CONFIG_LV_TOUCH_DRIVER_ADC)
        touch_driver_init();
    #elif defined (CONFIG_LV_TOUCH_DRIVER_DISPLAY)
        touch_driver_init();
    #else
    #error "No protocol defined for touch controller"
    #endif
#else
#endif

该函数进行了一些判断,不同接口进行不同方式的启动,最后调用touch_driver_init()启动触摸驱动。

因为屏幕启动时触摸驱动也启动了,所以这里可以不进行配置了。

d. 配置read()回调函数

touch_driver.c函数中定义了有关read()函数如下:

#if LVGL_VERSION_MAJOR >= 8
void touch_driver_read(lv_indev_drv_t *drv, lv_indev_data_t *data)
#else
bool touch_driver_read(lv_indev_drv_t *drv, lv_indev_data_t *data)
#endif
{
    bool res = false;
#if defined (CONFIG_LV_TOUCH_CONTROLLER_XPT2046)
    res = xpt2046_read(drv, data);
#elif defined (CONFIG_LV_TOUCH_CONTROLLER_FT6X06)
    res = ft6x36_read(drv, data);
#elif defined (CONFIG_LV_TOUCH_CONTROLLER_STMPE610)
    res = stmpe610_read(drv, data);
#elif defined (CONFIG_LV_TOUCH_CONTROLLER_ADCRAW)
    res = adcraw_read(drv, data);
#elif defined (CONFIG_LV_TOUCH_CONTROLLER_FT81X)
    res = FT81x_read(drv, data);
#elif defined (CONFIG_LV_TOUCH_CONTROLLER_RA8875)
    res = ra8875_touch_read(drv, data);
#elif defined (CONFIG_LV_TOUCH_CONTROLLER_GT911)
    res = gt911_read(drv, data);
#endif

#if LVGL_VERSION_MAJOR >= 8
    data->continue_reading = res;
#else
    return res;
#endif
}

这里将touch_driver_read放入回调函数即可

    /*Register a touchpad input device注册触摸板输入设备*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touch_driver_read;  //这里
    indev_touchpad = lv_indev_drv_register(&indev_drv);

其余函数可以通过 if 0 关闭掉。

注意包含头文件

#include "touch_driver.h"
e. 编译调试
  1. 编译报错
H:/ESP32/ESP_IDF_develop_env/4_LVGL_Touch_Port/components/lv_porting/lv_port_indev.c:13:10: fatal error: ../../lvgl.h: No such file or directory
 #include "../../lvgl.h"

此处lvgl的头文件引用错误,修改为

#include "lvgl/lvgl.h"
  1. 编译报错
H:/ESP32/ESP_IDF_develop_env/4_LVGL_Touch_Port/components/lv_porting/lv_port_indev.c: In function 'lv_port_indev_init':
....
H:/ESP32/ESP_IDF_develop_env/4_LVGL_Touch_Port/components/lv_porting/lv_port_indev.c:61:16: warning: 'encoder_diff' defined but not used [-Wunused-variable]       
 static int32_t encoder_diff;

这里是以下几个函数未使用,编译时题型warning。将有关函数和定义注释掉即可

touchpad_init();
touchpad_read();
touchpad_is_pressed();
touchpad_get_xy();

编译完成。

f. 修正GT911驱动

编译不报错后,烧录到开发板后输出错误log:

...
I (969) lvgl_i2c: Initialised port 0 (SDA: 18, SCL: 16, speed: 100000 Hz.)
W (979) lvgl_i2c: Error: -1
E (979) GT911: Error reading from device: ERROR
I (1019) lvgl_i2c: Initialised port 0 (SDA: 18, SCL: 16, speed: 100000 Hz.)
I (1029) GT911:         Product ID: 

查阅出处为gt911.c驱动代码:

void gt911_init(uint8_t dev_addr) {
    if (!gt911_status.inited) {
        gt911_status.i2c_dev_addr = dev_addr;
        uint8_t data_buf;
        esp_err_t ret;

        ESP_LOGI(TAG, "Checking for GT911 Touch Controller");
        if ((ret = gt911_i2c_read(dev_addr, GT911_PRODUCT_ID1, &data_buf, 1) != ESP_OK)) {
            ESP_LOGE(TAG, "Error reading from device: %s",
                        esp_err_to_name(ret));    // Only show error the first time
            return;
        }
...

说明没有读取到触摸屏型号,因为GPIO没有完成初始化。因此,LVGL给出的GT911驱动没有进行初始化上电操作,要按照官方手册进行移植。

参考GT911编程指南,GT9 系列在通信中始终作为从设备,其 I2C 设备地址由 7 位设备地址加 1 位读写控制位组成,为高 7 位为地址, bit 0 为读写控制位。GT9 系列有两个从设备地址可供选择,每次上电或复位时需要使用 INT 脚进行 I2C 地址设置。如下表:

在这里插入图片描述

在这里插入图片描述

GT911的IIC从设备地址有两组,分别为0x28/0x29和0xBA和0xBB。不同设定地址的上电时序不同:

在这里插入图片描述

这里官方驱动声明了#define GT911_I2C_SLAVE_ADDR 0x5D地址,经过高七位的换算,应该为第二种上电时序0xBA/0xBB。这里按照上电时序编写复位驱动。驱动板设计接口为RTS=IO4, INT=IO17,所以封装GT911_RST()函数进行复位:

void GT911_RST()
{
    // RTS IO4 and INT IO17
    
    //设置4号和17号引脚为输出模式
    gpio_set_direction( GPIO_NUM_4, GPIO_MODE_OUTPUT);
    gpio_set_direction( GPIO_NUM_17, GPIO_MODE_OUTPUT);

    //低电平复位
    gpio_set_level(4, 0);
    gpio_set_level(17, 0);
    
    //延迟150毫秒,这里为避免时序过短,增加50毫秒
    vTaskDelay(pdMS_TO_TICKS(15));
    
    //RTS拉高
    gpio_set_level(4, 1);

    //延迟5ms
    vTaskDelay(pdMS_TO_TICKS(50));

    //将INT转为悬浮输入态
    gpio_set_direction(17, (GPIO_MODE_INPUT)| (GPIO_MODE_DEF_OD));

注意,这里的有关函数调用和变量出自gpio.cgpio_types.h。引脚模式变量如下:

typedef enum {
    GPIO_MODE_DISABLE = GPIO_MODE_DEF_DISABLE,                                                         /*!< GPIO mode : disable input and output             */
    GPIO_MODE_INPUT = GPIO_MODE_DEF_INPUT,                                                             /*!< GPIO mode : input only                           */
    GPIO_MODE_OUTPUT = GPIO_MODE_DEF_OUTPUT,                                                           /*!< GPIO mode : output only mode                     */
    GPIO_MODE_OUTPUT_OD = ((GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)),                               /*!< GPIO mode : output only with open-drain mode     */
    GPIO_MODE_INPUT_OUTPUT_OD = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)), /*!< GPIO mode : output and input with open-drain mode*/
    GPIO_MODE_INPUT_OUTPUT = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT)),                         /*!< GPIO mode : output and input mode                */
} gpio_mode_t;

当然也可以设定为另一个地址,总体封装函数如下:

// 触摸复位操作以便于设定IIC地址
void GT911_RST()
{
    // 设置4号RTS和17号INT引脚为输出模式
    gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT);
    gpio_set_direction(GPIO_NUM_17, GPIO_MODE_OUTPUT);

    // 低电平复位
    gpio_set_level(4, 0);
    gpio_set_level(17, 0);

    if (GT911_I2C_SLAVE_ADDR == 0x5D)
    {
        vTaskDelay(pdMS_TO_TICKS(15));
        gpio_set_level(4, 1); // RTS拉高
        vTaskDelay(pdMS_TO_TICKS(50));
        gpio_set_direction(17, (GPIO_MODE_INPUT) | (GPIO_MODE_DEF_OD)); // 将INT转为悬浮输入态
    }
    else if (GT911_I2C_SLAVE_ADDR == 0x14)
    {
        vTaskDelay(pdMS_TO_TICKS(5));
        gpio_set_level(17, 1);
        vTaskDelay(pdMS_TO_TICKS(10));
        gpio_set_level(4, 1);// RTS拉高
        vTaskDelay(pdMS_TO_TICKS(50));
        gpio_set_direction(17, (GPIO_MODE_INPUT) | (GPIO_MODE_DEF_OD)); // 将INT转为悬浮输入态
    }
}

将该函数放在gt911_init()函数一开头调用即可。

3.5 编写main文件

上述修改完成后删除build然后重新编译,未发现有报错。

下面进行main文件编写。

3.5.1 LVGL使用说明

详官方文档:https://lvgl.100ask.net/8.3/porting/project.html#initialization

要使用图形库,您必须对其进行初始化并设置所需的组件。初始化的顺序是:

1. 调用`lv_init()`。    
2. 初始化您的驱动程序。
3. 在LVGL中注册显示和输入设备驱动程序。
4. 在中断中每隔`x`毫秒调用`lv_tick_inc(x)`以向LVGL报告经过的时间。
5. 每隔几毫秒调用`lv_timer_handler()`来处理与LVGL相关的任务。
3.5.2 初始化lvgl函数

首先,为确保lvgl以及有关设备能被调用,在main.c中包含必要的头文件:

#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"

同时,按照初始化顺序的1-3项,在main()函数中添加初始化函数:

lv_init();              // 初始化lvgl
lv_port_disp_init();    // 初始化显示器
lv_port_indev_init();   // 初始化触摸屏

其中前面说到,lv_port_disp_init()函数调用到disp_init()lvgl_driver_init()lv_disp_drv_register(),说明第二行函数初始化了显示和输入设备驱动。

3.5.3 设置定时器

参考官方文档:https://lvgl.100ask.net/8.3/porting/tick.html

LVGL需要一个系统刻度来知道动画和其他任务的经过时间。所以要周期性调用lv_tick_inc(tick_period)函数,并以毫秒为单位提供调用周期。例如,每毫秒调用一次时lv_tick_inc(1)

lv_tick_inc应该在比lv_task_handler()更高优先级的例程中调用(例如在中断中)以精确知道经过的毫秒,即使执行lv_task_handler需要更多时间。

使用FreeRTOSlv_tick_inc可以在vApplicationTickHook中调用。注意,这里lv_tick_inc()函数属于硬件层面心跳,不建议使用freeRTOS中的esp_register_freertos_tick_hook(lv_tick_task)函数替代。在FreeRTOS的每个系统心跳中断执行可能会受到FreeRTOS系统滴答频率的限制,并且可能不是最精确的方法。

当不使用操作系统的实时时钟功能(如RTOS的tick中断)来自动调用 lv_task_handler 时,您可能需要自己实现一个定时器来定期调用 lv_tick_inc。

这里参考学习lvgl官方在github上面移植esp32案例库中的有关代码。https://github.com/lvgl/lv_port_esp32/blob/master/main/main.c

  • 创建并启动一个周期性的定时器中断以调用lv_tick_inc
  1. lv_tick_inc进行函数封装
static void lv_tick_task(void *arg) {
    (void) arg;
    
    lv_tick_inc(LV_TICK_PERIOD_MS);
}

其中LV_TICK_PERIOD_MS要进行变量声明,此处设置为10ms

#define LV_TICK_PERIOD_MS 10
  1. 在主函数中创建并启动周期性定时器中断
    const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,
        .name = "periodic_gui"};
    esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LV_TICK_PERIOD_MS * 1000));

上述代码表示创建一个ESP-IDF的硬件定时器,用于定期调用lv_tick_task函数,这里每10ms调用一次。从而为LVGL GUI库提供时钟信号。

3.5.4 定时器处理程序

要处理LVGL的任务,您需要在以下命令之一中定期调用lv_timer_handler()

1. main()函数的while(1)
2. 定时器周期性中断(优先级低于`lv_tick_inc()`)
3. 定期执行操作系统任务
 while (1) {
	vTaskDelay(pdMS_TO_TICKS(10));
	lv_task_handler();
 }
3.5.5 使用DEMO例程
a. 加入demo
  1. 打开menuconfig,在demos中开启例程,这里以music demo为例。
Demos
	√ Music player demo

在这里插入图片描述

  1. 打开lvgl-demos文件夹下的lv_demos.h文件,当开启上述选项后将包含music/lv_demo_music.h文件。打开该文件,将例程函数加入到主函数中,同时要包含头文件
#include lv_demos.h
...
lv_demo_music();
b. 在menuconfig中增加music demo的字号

编译发现报错

H:/ESP32/ESP_IDF_develop_env/3_Fit_LVGL/components/lvgl/demos/music/lv_demo_music_list.c: In function '_lv_demo_music_list_create':
H:/ESP32/ESP_IDF_develop_env/3_Fit_LVGL/components/lvgl/demos/music/lv_demo_music_list.c:59:19: error: 'lv_font_montserrat_12' undeclared (first use in this function); did you mean 'lv_font_montserrat_14'?
     font_small = &lv_font_montserrat_12;
                   ^~~~~~~~~~~~~~~~~~~~~
                   lv_font_montserrat_14
H:/ESP32/ESP_IDF_develop_env/3_Fit_LVGL/components/lvgl/demos/music/lv_demo_music_list.c:59:19: note: each undeclared identifier is reported only 
once for each function it appears in
H:/ESP32/ESP_IDF_develop_env/3_Fit_LVGL/components/lvgl/demos/music/lv_demo_music_list.c:60:20: error: 'lv_font_montserrat_16' undeclared (first use in this function); did you mean 'lv_font_montserrat_14'?
     font_medium = &lv_font_montserrat_16;

这是music demo 使用了12/14/16号字体,需要在menuconfig中修改。

修改后编译成功


PS-对于其它lvgl版本,在编译时会报错找不到lv_demo_music()路径,需要编辑Cmake使识别。报错案例:

H:/ESP32/ESP_IDF_develop_env/3_Fit_LVGL/main/main.c:56:5: error: implicit declaration of function 'lv_demo_music'; did you mean 'lv_mem_test'? [-Werror=implicit-function-declaration]
     lv_demo_music();

存在demo事例未加入引用的情况,处理方式如下:

解决此方案可以使用多种方法:
1. 原位编辑lvgl中esp cmake使其包含demo;
2. 将demo文件复制到其它路径下并在主函数引用,并挨个包含。

  • 对于方案一:

打开lvgl-env_support-cmake-esp.cmake文件进行如下修改,添加demos路径下的.c文件并包含demos文件夹:

file(GLOB_RECURSE SOURCES
						${LVGL_ROOT_DIR}/src/*.c
						${LVGL_ROOT_DIR}/demos/*.c
)

if(LV_MICROPYTHON)
  idf_component_register(
    SRCS
    ${SOURCES}
    INCLUDE_DIRS
    ${LVGL_ROOT_DIR}
    ${LVGL_ROOT_DIR}/src
    ${LVGL_ROOT_DIR}/../
    ${LVGL_ROOT_DIR}/demos
    REQUIRES
    main)
else()
...

  idf_component_register(SRCS ${SOURCES} ${EXAMPLE_SOURCES} ${DEMO_SOURCES}
      INCLUDE_DIRS ${LVGL_ROOT_DIR} ${LVGL_ROOT_DIR}/src ${LVGL_ROOT_DIR}/../
                   ${LVGL_ROOT_DIR}/examples ${LVGL_ROOT_DIR}/demos
      REQUIRES esp_timer)
3.5.6 main.c案例代码
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
  
// LVGL有关移植引用
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lv_demos.h"
  
/*********************
 *      DEFINES
 *********************/
// 定义一个心跳周期为10ms
#define LV_TICK_PERIOD_MS 10
  
/**********************
 *  STATIC PROTOTYPES
 **********************/
static void lv_tick_task(void *arg);
  
/**********************
 *   STATIC FUNCTIONS
 **********************/
  
/* 将定时器lv_tick_inc() 函数封装为lv_tick_task(),为LVGL提供模拟时钟信号。
 * LVGL需要时钟信号来更新其内部状态,如动画等*/
static void lv_tick_task(void *arg)
{
    (void)arg;
  
    lv_tick_inc(LV_TICK_PERIOD_MS);
}
  
/**********************
 *   APPLICATION MAIN
 **********************/
  
// 主函数入口
void app_main(void)
{
    printf("Hello world!\n");
  
    lv_init(); // 初始化lvgl
    printf("LVGL完成初始化!\n");
    lv_port_disp_init(); // 初始化显示器
    printf("显示屏完成初始化!\n");
    lv_port_indev_init(); // 初始化触摸屏
    printf("触摸完成初始化!\n");
  
    /* 创建并启动一个周期性的定时器中断以调用lv_tick_inc */
    const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,                                           // 定时器触发回调函数
        .name = "periodic_gui"};                                             // 定时器名称
    esp_timer_handle_t periodic_timer;                                       // 定义句柄以引用
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); // 创建定时器
    printf("定时器创建完成!\n");
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LV_TICK_PERIOD_MS * 1000)); // 启动周期性定时器,每10ms调用一次
    printf("周期性定时器启动,周期为10ms!\n");
    // ESP_ERROR_CHECK 是一个宏,检查ESP-IDF API调用的错误,并在出现错误时停止程序
  
    // 使用lvgl music的demo事例
    lv_demo_music();
    printf("加载music demo完成!\n");
  
    /*调用 lv_task_handler 来处理LVGL的异步任务,并使用 vTaskDelay 延时10毫秒,这样不会阻塞CPU,同时允许LVGL和其他任务有机会运行*/
    while (1)
    {
        /* 延迟1个刻度(假设FreeRTOS刻度为10ms) */
        vTaskDelay(pdMS_TO_TICKS(10));
        lv_task_handler();
    }
}

4. 烧录测试

编译烧录后屏幕和触摸操作正常,串口监视器输出正常。

在这里插入图片描述
在这里插入图片描述

I (27) boot: ESP-IDF GIT-NOTFOUND 2nd stage bootloader
I (27) boot: compile time 14:47:27
I (27) boot: Multicore bootloader
I (31) boot: chip revision: v3.1
I (35) boot.esp32: SPI Speed      : 80MHz
I (40) boot.esp32: SPI Mode       : DIO
I (44) boot.esp32: SPI Flash Size : 8MB
I (49) boot: Enabling RNG early entropy source...
I (54) boot: Partition Table:
I (58) boot: ## Label            Usage          Type ST Offset   Length
I (65) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (72) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (80) boot:  2 factory          factory app      00 00 00010000 00100000
I (87) boot: End of partition table
I (92) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=711b0h (463280) map
I (240) esp_image: segment 1: paddr=000811d8 vaddr=3ffb0000 size=01ef4h (  7924) load
I (243) esp_image: segment 2: paddr=000830d4 vaddr=40080000 size=0cf44h ( 53060) load
I (264) esp_image: segment 3: paddr=00090020 vaddr=400d0020 size=4c9b8h (313784) map
I (359) esp_image: segment 4: paddr=000dc9e0 vaddr=4008cf44 size=01dd0h (  7632) load
I (369) boot: Loaded app from partition at offset 0x10000
I (370) boot: Disabling RNG early entropy source...
I (382) cpu_start: Multicore app
I (382) cpu_start: Pro cpu up.
I (382) cpu_start: Starting app cpu, entry point is 0x400812d4
0x400812d4: call_start_cpu1 at C:/Users/l/esp/v4.4.7/esp-idf/v4.4.7/esp-idf/components/esp_system/port/cpu_start.c:151

I (0) cpu_start: App cpu up.
I (402) cpu_start: Pro cpu start user code
I (402) cpu_start: cpu freq: 160000000
I (402) cpu_start: Application information:
I (407) cpu_start: Project name:     hello_world
I (412) cpu_start: App version:      1
I (417) cpu_start: Compile time:     May 16 2024 15:34:15
I (423) cpu_start: ELF file SHA256:  4310bf1bfc5772fc...
I (429) cpu_start: ESP-IDF:          GIT-NOTFOUND
I (434) cpu_start: Min chip rev:     v0.0
I (439) cpu_start: Max chip rev:     v3.99 
I (443) cpu_start: Chip rev:         v3.1
I (448) heap_init: Initializing. RAM available for dynamic allocation:
I (455) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (461) heap_init: At 3FFCB6C8 len 00014938 (82 KiB): DRAM
I (468) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (474) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (480) heap_init: At 4008ED14 len 000112EC (68 KiB): IRAM
I (487) spi_flash: detected chip: gd
I (491) spi_flash: flash io: dio
I (496) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
Hello world!
LVGL完成初始化!
I (509) lvgl_helpers: Display buffer size: 12800
I (519) lvgl_helpers: Initializing SPI master for display
I (519) lvgl_helpers: Configuring SPI host SPI2_HOST
I (529) lvgl_helpers: MISO pin: -1, MOSI pin: 19, SCLK pin: 23, IO2/WP pin: -1, IO3/HD pin: -1
I (539) lvgl_helpers: Max transfer size: 25600 (bytes)
I (549) lvgl_helpers: Initializing SPI bus...
I (549) disp_spi: Adding SPI device
I (549) disp_spi: Clock speed: 40000000Hz, mode: 0, CS pin: 22
I (759) ST7796S: Initialization.
I (959) ST7796S: Display orientation: PORTRAIT_INVERTED
I (959) ST7796S: 0x36 command value: 0x88
I (959) disp_backlight: Setting LCD backlight: 100%
I (1019) GT911: Checking for GT911 Touch Controller
I (1019) lvgl_i2c: Starting I2C master at port 0.
I (1019) lvgl_i2c: Initialised port 0 (SDA: 18, SCL: 16, speed: 100000 Hz.)
I (1029) GT911:         Product ID: 911
I (1029) GT911:         Vendor ID: 0x00
I (1029) GT911:         X Resolution: 320
I (1039) GT911:         Y Resolution: 480
gt911触摸驱动初始化!
显示屏完成初始化!
触摸完成初始化!
定时器创建完成!
周期性定时器启动,周期为10ms!
加载music demo完成!
I (10589) GT911: X=22 Y=57
I (10619) GT911: X=22 Y=57
  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ESP32 移植 LVGL 需要使用 ESP-IDF 的支持,它提供了 ESP32 平台基本功能的驱动程序和配置文件。你可以按照 ESP-IDF 的官方文档,使用 ESP-IDF 的平台支持进行 LVGL 移植。 ### 回答2: ESP32是一款强大的微控制器,支持运行各种应用程序。LVGL是一种开源的GUI库,可以轻松实现图形化界面。下面我将介绍如何在ESP32移植LVGL。 首先,我们需要下载LVGL库的源代码。可以从官方网站或者GitHub上下载最新版本的LVGL库。 接下来,我们需要用到Arduino开发环境来编译和烧录代码。确保你已经安装了最新版本的Arduino开发环境,并且已经将ESP32开发板支持库安装。 然后,将LVGL源代码复制到你的Arduino项目文件夹中的一个子文件夹中。然后打开Arduino开发环境,找到你的项目文件夹并打开LVGL源代码文件夹。 在LVGL源代码文件夹中找到`lv_conf_template.h`文件,并将其复制为`lv_conf.h`文件。然后,打开`lv_conf.h`文件并根据你的需求进行配置。在配置文件中,你可以设置屏幕大小、颜色深度、字体等。 然后,在Arduino开发环境中打开ESP32的示例程序。找到一个空闲的示例程序,例如“Blink”示例程序,并将其复制为一个新的文件夹。 将LVGL源代码文件夹中的`lvgl`文件夹复制到ESP32示例程序文件夹中。 打开ESP32示例程序文件夹中的`platformio.ini`文件,并取消注释以下两行代码: ``` lib_ignore = ESP32_BLE_Arduino # lib_deps = ``` 然后,在`lib_deps =`后面添加以下代码: ``` ${env.LIBS_DIR}/lvgl ``` 保存并退出`platformio.ini`文件。 最后,编译和烧录ESP32示例程序。在Arduino开发环境中,选择正确的端口和开发板,并点击“编译并上传”按钮。完成后,你的ESP32就成功移植LVGL,并可以使用LVGL库创建图形化界面了。 总之,移植LVGLESP32可以通过将LVGL源代码复制到你的项目文件夹中,配置LVGL并将其添加到ESP32示例程序中来实现。这样,你就可以在ESP32上轻松创建图形化界面了。 ### 回答3: ESP32是一款功能强大的微控制器,LVGL是一种用于嵌入式系统的开源图形库。要将LVGL移植ESP32上,可以按照以下步骤进行操作: 1. 下载LVGL软件包:在LVGL官方网站上下载最新版本的LVGL软件包,并解压缩到ESP32开发环境的工作目录中。 2. 配置开发环境:打开ESP32的开发环境(如ESP-IDF或Arduino IDE),创建新的项目或打开现有的项目。 3. 配置LVGL:在ESP32的项目文件中,找到并打开LVGL的配置文件(如lv_conf.h)。根据项目的需求,设置LVGL的配置选项,如分辨率、颜色深度、绘制驱动等。 4. 添加LVGL的源代码:将LVGL的源代码文件添加到ESP32项目的源文件目录中。可以复制整个LVGL文件夹,或者将相关文件手动复制到项目目录。 5. 添加驱动程序:根据项目的显示设备,选择并添加相关的驱动程序。LVGL支持多种显示设备和操作系统接口,如SPI、I2C、UART等。 6. 初始化LVGL:在ESP32的程序中,添加LVGL的初始化函数。该函数负责初始化LVGL图形库,并配置所需的驱动程序。 7. 编写应用程序:根据项目的需求,在ESP32的程序中编写LVGL应用程序。可以使用LVGL提供的各种图形元素和功能,如按钮、标签、文本框、进度条等。 8. 编译和烧录:将ESP32与计算机连接,并进行编译和烧录操作。确保所有的依赖文件和驱动程序都正确配置,并且LVGL的源代码能够正确编译。 9. 调试和优化:在ESP32上运行LVGL应用程序,并进行调试和优化。可以使用串口输出或者调试工具来查找和修复可能出现的问题。 10. 测试和部署:在完成调试和优化后,进行系统测试以及部署到实际项目中。 以上是将LVGL移植ESP32的一般步骤,具体的细节和操作方式可能会因项目的需求和开发环境而有所不同。希望这些简要的说明可以帮助您成功地将LVGL移植ESP32上。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值