目录
一、前言
前几天在ardiuno框架下基于platformIO用TFT_eSPI库实现了lvgl库的移植,这种方法虽然在lvgl的版本上有很大优势,但是platformIO的自定义库的添加有点麻烦,而且网上的大部分esp32资料都是基于ESP-IDF框架,因此还是在尝试一下ESP-IDF框架的lvgl开发。
esp32移植lvgl有官方的lvgl_port_esp32项目可以直接移植,不过目前的lvgl_port_esp32项目是基于lvgl7,主要是由于这个项目的重要组件lvgl_esp32_drivers库的lvgl8版本还没升级出来,但也没关系,作为一个初学者,版本的优劣可以先不考虑。下面将参考lvgl_port_esp32项目的内容简单实现一下lvgl7的移植,其中遇到了一些bug做一个记录。
二、代码
首先用ESP-IDF插件创建一个示例项目,并把lvgl_port_esp32项目内componets的lvgl和lvgl_esp32_drivers库复制到新建项目的componets内。

然后把lvgl_port_esp32的main的内容复制到项目里,但是这里不用官方demo示例,自己添加一个简单的库做一个spinner效果测试一下添加自定义库的方法。这一步骤有四个文件的添加,参考了b站一位大佬的代码,即preloader.h,preloader.c,CMakeLists.txt(perloader库内),main.c。下面贴出来代码:
preloader.h
#ifndef _PRELOADER_H
#define _PRELOADER_H
void create_preloader_demo();
#endif
preloader.c
#include "preloader.h"
#include "lvgl.h"
void create_preloader_demo(){
lv_obj_t * preload = lv_spinner_create(lv_scr_act(),NULL);
lv_obj_set_size(preload,100,100);
lv_obj_align(preload,NULL,LV_ALIGN_CENTER,0,0);
}
CMakeLists.txt(perloader库内)
idf_component_register(SRCS "preloader.c"
INCLUDE_DIRS "."
REQUIRES lvgl)
main.c
/* LVGL Example project
*
* Basic project to test LVGL on ESP32 based projects.
*
* This example code is in the Public Domain (or CC0 licensed, at your option.)
*
* Unless required by applicable law or agreed to in writing, this
* software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_freertos_hooks.h"
#include "freertos/semphr.h"
#include "esp_system.h"
#include "driver/gpio.h"
/* Littlevgl specific */
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
#include "lvgl_helpers.h"
#include "preloader.h"
/*********************
* DEFINES
*********************/
#define LV_TICK_PERIOD_MS 1
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_tick_task(void *arg);
static void guiTask(void *pvParameter);
static void create_demo_application(void);
/**********************
* APPLICATION MAIN
**********************/
void app_main() {
/* If you want to use a task to create the graphic, you NEED to create a Pinned task
* Otherwise there can be problem such as memory corruption and so on.
* NOTE: When not using Wi-Fi nor Bluetooth you can pin the guiTask to core 0 */
xTaskCreatePinnedToCore(guiTask, "gui", 4096*2, NULL, 0, NULL, 1);
}
/* Creates a semaphore to handle concurrent call to lvgl stuff
* If you wish to call *any* lvgl function from other threads/tasks
* you should lock on the very same semaphore! */
SemaphoreHandle_t xGuiSemaphore;
static void guiTask(void *pvParameter) {
(void) pvParameter;
xGuiSemaphore = xSemaphoreCreateMutex();
lv_init();
/* Initialize SPI or I2C bus used by the drivers */
lvgl_driver_init();
lv_color_t* buf1 = heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1 != NULL);
/* Use double buffered when not working with monochrome displays */
#ifndef CONFIG_LV_TFT_DISPLAY_MONOCHROME
lv_color_t* buf2 = heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf2 != NULL);
#else
static lv_color_t *buf2 = NULL;
#endif
static lv_disp_buf_t disp_buf;
uint32_t size_in_px = DISP_BUF_SIZE;
#if defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820 \
|| defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_JD79653A \
|| defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_UC8151D \
|| defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1306
/* Actual size in pixels, not bytes. */
size_in_px *= 8;
#endif
/* Initialize the working buffer depending on the selected display.
* NOTE: buf2 == NULL when using monochrome displays. */
lv_disp_buf_init(&disp_buf, buf1, buf2, size_in_px);
lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.flush_cb = disp_driver_flush;
/* When using a monochrome display we need to register the callbacks:
* - rounder_cb
* - set_px_cb */
#ifdef CONFIG_LV_TFT_DISPLAY_MONOCHROME
disp_drv.rounder_cb = disp_driver_rounder;
disp_drv.set_px_cb = disp_driver_set_px;
#endif
disp_drv.buffer = &disp_buf;
lv_disp_drv_register(&disp_drv);
/* Register an input device when enabled on the menuconfig */
#if CONFIG_LV_TOUCH_CONTROLLER != TOUCH_CONTROLLER_NONE
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.read_cb = touch_driver_read;
indev_drv.type = LV_INDEV_TYPE_POINTER;
lv_indev_drv_register(&indev_drv);
#endif
/* Create and start a periodic timer interrupt to call 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));
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LV_TICK_PERIOD_MS * 1000));
/* Create the demo application */
create_demo_application();
while (1) {
/* Delay 1 tick (assumes FreeRTOS tick is 10ms */
vTaskDelay(pdMS_TO_TICKS(10));
/* Try to take the semaphore, call lvgl related function on success */
if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY)) {
lv_task_handler();
xSemaphoreGive(xGuiSemaphore);
}
}
/* A task should NEVER return */
free(buf1);
#ifndef CONFIG_LV_TFT_DISPLAY_MONOCHROME
free(buf2);
#endif
vTaskDelete(NULL);
}
static void create_demo_application(void)
{
create_preloader_demo();
}
static void lv_tick_task(void *arg) {
(void) arg;
lv_tick_inc(LV_TICK_PERIOD_MS);
}
其中,main文件就是lvgl_port_esp32的main的create_demo_application函数内容删除,并改成我们自己的 create_preloader_demo函数。
三、硬件说明
开发板:ESP-WROOM-32
屏幕:中景园1.47寸lcd显示屏高清ips172x320 st7789驱动液晶屏
四、ESP-IDF设置(重要)
ESP-IDF设置是有个小坑的,这里先贴一下主要需要改的部分:



这些设定大部分没什么值得说的,按照自己的情况修改即可。但是这个LVGL TFT Display controller的频率设定应该是有坑的,我的设备这里只能设定为80MHz,其他情况下都会全黑屏,什么都不显示。
五、屏幕偏移修改驱动文件
到上一步编译其实是已经能点亮屏幕并显示的,但是屏幕会出现偏移的现象,经过多方查找,找到了这个屏幕的偏移修改方法。
在lvgl_esp32_drivers库的lvgl_tft文件夹内找到st7789.c驱动文件。并对函数void st7789_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map)进行修改增加两行:
offsety1+=34;
offsety2+=34;
修改后的函数内容变为:
/* The ST7789 display controller can drive 320*240 displays, when using a 240*240
* display there's a gap of 80px, we need to edit the coordinates to take into
* account that gap, this is not necessary in all orientations. */
void st7789_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map)
{
uint8_t data[4] = {0};
uint16_t offsetx1 = area->x1;
uint16_t offsetx2 = area->x2;
uint16_t offsety1 = area->y1;
uint16_t offsety2 = area->y2;
offsety1+=34;
offsety2+=34;
#if (CONFIG_LV_TFT_DISPLAY_OFFSETS)
offsetx1 += CONFIG_LV_TFT_DISPLAY_X_OFFSET;
offsetx2 += CONFIG_LV_TFT_DISPLAY_X_OFFSET;
offsety1 += CONFIG_LV_TFT_DISPLAY_Y_OFFSET;
offsety2 += CONFIG_LV_TFT_DISPLAY_Y_OFFSET;
#elif (LV_HOR_RES_MAX == 240) && (LV_VER_RES_MAX == 240)
#if (CONFIG_LV_DISPLAY_ORIENTATION_PORTRAIT)
offsetx1 += 80;
offsetx2 += 80;
#elif (CONFIG_LV_DISPLAY_ORIENTATION_LANDSCAPE_INVERTED)
offsety1 += 80;
offsety2 += 80;
#endif
#endif
/*Column addresses*/
st7789_send_cmd(ST7789_CASET);
data[0] = (offsetx1 >> 8) & 0xFF;
data[1] = offsetx1 & 0xFF;
data[2] = (offsetx2 >> 8) & 0xFF;
data[3] = offsetx2 & 0xFF;
st7789_send_data(data, 4);
/*Page addresses*/
st7789_send_cmd(ST7789_RASET);
data[0] = (offsety1 >> 8) & 0xFF;
data[1] = offsety1 & 0xFF;
data[2] = (offsety2 >> 8) & 0xFF;
data[3] = offsety2 & 0xFF;
st7789_send_data(data, 4);
/*Memory write*/
st7789_send_cmd(ST7789_RAMWR);
uint32_t size = lv_area_get_width(area) * lv_area_get_height(area);
st7789_send_color((void*)color_map, size * 2);
}
自此,已经成功实现esp32移植lvgl7驱动st7789屏幕。

参考资料
ESP32 IDF LVGL 1.47寸圆角屏幕测试_史达芬林的博客-CSDN博客(屏幕设定)