ESP32移植LVGL教程
- 效果图
- 下载lvgl
- 移植相关文件
- 更改相关配置文件
- lv_config.h
- main/CMakeLists.txt
- main.c
- 其它代码
- Src/bsp_lcd.c
- Include/bsp_lcd.h
- 工程文件下载
软件版本:
- lvgl -> 9.3.0
- idf -> 4.4.0
硬件需求:
- ESP32S3
- 一块调试好能够显示的屏幕,这里我使用的是ST7789 驱动的240×320TFT屏幕
效果图
下载lvgl
先下载lvgl点击这里,
下载完成之后,进行解压即可
移植相关文件
先在自己的项目目录下新建一个lvgl文件夹,做完之后看我的项目文件路径看是否正确:
->My_LVGL_Demo
->main
->Include #存放自己写的头文件
->Src #存放自己写的源文件
->lvgl #存放lvgl相关文件
->main.c
->CMakeLists.txt
->CMakeLists.txt
然后就可以开始移植lvgl了,先看看解压之后我们需要什么文件,如下图所示。
文件夹的画只需要src文件夹,将它复制到项目中创建的lvgl目录下。
然后移植相关文件。如下图所示。
将lvgl.h
和lv_version.h
移植到lvgl目录下,然后将lv_conf_template.h
先改名为lv_conf.h
再移植到main目录下,即与lvgl目录同级下。
移植完成后的目录如下所示:
->My_LVGL_Demo
->main
->Include #存放自己写的头文件
->Src #存放自己写的源文件
->lvgl #存放lvgl相关文件
->src
->lv_version.h
->lvgl.h
->main.c
->lv_conf.h
->CMakeLists.txt
->CMakeLists.txt
这样,lvgl的文件就相当于移植成功了。
更改相关配置文件
lv_config.h
首先是第15行的#if 0
要改成#if 1
然后就是第30行的#define LV_COLOR_DEPTH 16
要改成自己屏幕的颜色格式,这里我用是RGB565,所有就是16
如果你只需要这些基本改完,这里例如我自己增加了一种字体大小,就改了如下所示的地方,默认是只有14号字体大小,这里增加了30号字体。
还有一些其它的配置,可以去看官方文档点击这里。
main/CMakeLists.txt
main/CMakeLists.txt肯定是要改的,我们需要把lvgl需要的头文件和源文件增加进去,注意是main下面的CMakeLists.txt。
file(GLOB_RECURSE COMPONENT_SOURCES "Src/*.c")
file(GLOB_RECURSE LVGL_COMPONENT_SOURCES "lvgl/src/*.c")
idf_component_register(SRCS ${COMPONENT_SOURCES}
${LVGL_COMPONENT_SOURCES}
"main.c"
INCLUDE_DIRS "."
"lvgl/"
"lvgl/src/"
"lvgl/src/core/"
"lvgl/src/display/"
"lvgl/src/draw/"
"lvgl/src/drivers/"
"lvgl/src/font/"
"lvgl/src/indev/"
"lvgl/src/layouts/"
"lvgl/src/libs/"
"lvgl/src/misc/"
"lvgl/src/osal/"
"lvgl/src/others/"
"lvgl/src/stdlib/"
"lvgl/src/themes/"
"lvgl/src/tick/"
"lvgl/src/widgets/"
"Include/"
REQUIRES driver freertos)
main.c
最后就是main.c文件了
#include <stdio.h>
#include "bsp_lcd.h"
#include "lvgl/lvgl.h"
#include "lvgl/lv_version.h"
#include "esp_timer.h"
spi_device_handle_t my_spi = NULL;
#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))
static uint8_t buf1[1024];
uint32_t my_tick_get_cb(void)
{
register uint32_t ms_result;
ms_result = (uint32_t)esp_timer_get_time();
return ms_result;
}
void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)
{
lcd_full_area(my_spi, area, px_map);
lv_display_flush_ready(display);
}
void app_main(void)
{
printf("LVGL Version: lvgl(%d.%d.%d)\n", LVGL_VERSION_MAJOR, LVGL_VERSION_MINOR, LVGL_VERSION_PATCH);
lv_init();
my_spi = spi_init();
lcd_init(my_spi);
lv_tick_set_cb(my_tick_get_cb);
lv_display_t * display1 = lv_display_create(LCD_W, LCD_H);
lv_display_set_buffers(display1, buf1, NULL, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_display_set_flush_cb(display1, my_flush_cb);
// 设置屏幕背景颜色为黑色
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), LV_PART_MAIN);
// 创建一个标签,设置父对象为屏幕,文本为"hello lvgl"
lv_obj_t * label = lv_label_create(lv_scr_act());
lv_label_set_text(label, "hello lvgl");
// 设置标签的样式
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_black()); // 设置背景颜色为黑色
lv_style_set_text_color(&style, lv_color_white()); // 设置文本颜色为白色
lv_style_set_text_font(&style, &lv_font_montserrat_30); // 设置字体大小
lv_obj_add_style(label, &style, 0); // 应用样式到标签
// 设置标签对齐为中心
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
// 主循环需要运行 LVGL 的事件处理
while (1)
{
lv_timer_handler_run_in_period(5);
// printf("hello world!!!\r\n");
}
}
值得注意一下的是#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))
这里要换成自己的颜色深度。
void my_flush_cb(lv_display_t * display, const lv_area_t * area, uint8_t * px_map)
{
lcd_full_area(my_spi, area, px_map);
lv_display_flush_ready(display);
}
这里面的lcd_full_area(my_spi, area, px_map);需要自己定义自己的刷屏函数,如下所示,我自己定义的,其中lv_area_t 是刷屏的坐标x1,y1和x2,y2,也就是起点到终点,px_map也就是数据指针,spi_device_handle_t是我自己屏幕需要的spi接口回调。
int lcd_full_area(spi_device_handle_t spi, const lv_area_t * area, uint8_t * px_map)
{
// 设置地址范围
lcd_sendCom(spi, 0x2A); // CASET 设置列地址范围
uint8_t data3[] = {(((area->x1-1) >> 8) & 0xFF), ((area->x1-1) & 0xFF), (((area->x2-1) >> 8) & 0xFF), ((area->x2-1) & 0xFF)}; // 列地址从 0 到 W-1
lcd_sendData(spi, data3, sizeof(data3));
lcd_sendCom(spi, 0x2B); // RASET 设置行地址范围
uint8_t data4[] = {(((area->y1-1) >> 8) & 0xFF), ((area->y1-1) & 0xFF), (((area->y2-1) >> 8) & 0xFF), ((area->y2-1) & 0xFF)}; // 行地址从 0 到 H-1
lcd_sendData(spi, data4, sizeof(data4));
// 发送数据填充屏幕
lcd_sendCom(spi, 0x2C); // RAMWR
lcd_sendData(spi, px_map, ((area->x2 - area->x1) * (area->y2 - area->y1) * 2));
return 1;
}
然后lv_init();
需要在所有lvgl相关操作之前最先运行,lv_display_t * display1 = lv_display_create(LCD_W, LCD_H);
这里创建屏幕大小的时候填自己的屏幕的长宽LCD_W, LCD_H。
其它代码
Src/bsp_lcd.c
#include "bsp_lcd.h"
spi_device_handle_t spi_init(void)
{
esp_err_t ret;
spi_bus_config_t my_spi_config={
.miso_io_num = -1,
.mosi_io_num = LCD_MOSI_PIN,
.sclk_io_num = LCD_SCL_PIN,
.quadhd_io_num = -1,
.quadwp_io_num = -1,
.data4_io_num = -1,
.data5_io_num = -1,
.data6_io_num = -1,
.data7_io_num = -1,
.max_transfer_sz = 32*1024
};
ret = spi_bus_initialize(2, &my_spi_config, SPI_DMA_CH_AUTO);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
return NULL;
}
spi_device_interface_config_t my_spi_devcfg = {
.clock_speed_hz = SPI_FREQ,
.mode = 0, // SPI 模式 (0-3)
.spics_io_num = LCD_CS_PIN, // 片选引脚
.queue_size = 20, // 传输队列大小
.pre_cb = NULL,
.post_cb = NULL,
};
spi_device_handle_t spi;
ret = spi_bus_add_device(2, &my_spi_devcfg, &spi);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
return NULL;
}
ESP_LOGI(TAG, "SPI Master initialized successfully");
return spi;
}
void spi_sendData(spi_device_handle_t spi,uint8_t* data,size_t len)
{
spi_transaction_t trans = {
.tx_buffer = data,
.length = len * 8,
};
esp_err_t ret = spi_device_transmit(spi, &trans);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI data write failed: %s", esp_err_to_name(ret));
}
}
void lcd_sendData(spi_device_handle_t spi, uint8_t* data, size_t len) {
gpio_set_level(LCD_DC_PIN, 1);
const int batchSize = 32*1024; // 尝试更小的批次大小
int total = len;
int sent = 0;
while (sent < len) {
int remaining = len - sent;
int currentBatch = (remaining > batchSize) ? batchSize : remaining;
spi_transaction_t trans = {
.rxlength = 0,
.rx_buffer = NULL
};
trans.length = currentBatch * 8;
trans.tx_buffer = data + sent;
esp_err_t ret = spi_device_queue_trans(spi, &trans, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI data queue failed: %s", esp_err_to_name(ret));
return;
}
spi_transaction_t *ret_trans;
ret = spi_device_get_trans_result(spi, &ret_trans, portMAX_DELAY);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI data write failed: %s", esp_err_to_name(ret));
return;
}
sent += currentBatch;
}
}
void lcd_sendCom(spi_device_handle_t spi,uint8_t com)
{
gpio_set_level(LCD_DC_PIN, 0);
spi_sendData(spi,&com,1);
}
int lcd_init(spi_device_handle_t spi)
{
lcd_gpio_init();
// 拉高复位引脚
gpio_set_level(LCD_RST_PIN, 1);
vTaskDelay(100 / portTICK_PERIOD_MS); // 延迟 100ms
// 再拉低复位引脚,触发复位
gpio_set_level(LCD_RST_PIN, 0);
vTaskDelay(100 / portTICK_PERIOD_MS); // 延迟 100ms
// 发送初始化命令
lcd_sendCom(spi, 0x01); // SWRESET 软件重置
vTaskDelay(150 / portTICK_PERIOD_MS); // 延迟 150ms,等待重置完成
lcd_sendCom(spi, 0x11); // SLPOUT 退出睡眠模式
lcd_sendCom(spi, 0x13);
vTaskDelay(120 / portTICK_PERIOD_MS); // 延迟 120ms
// 设置像素格式和内存访问控制
lcd_sendCom(spi, 0x3A); // COLMOD 设置像素格式
uint8_t data1[] = {0x05}; // 16-bit RGB565
lcd_sendData(spi, data1, sizeof(data1));
lcd_sendCom(spi, 0x36); // MADCTL 设置内存访问控制
uint8_t data2[] = {0x08}; // RGB 格式,常规显示 0000 00
lcd_sendData(spi, data2, sizeof(data2));
lcd_sendCom(spi, 0x21);
// 设置地址范围
lcd_sendCom(spi, 0x2A); // CASET 设置列地址范围
uint8_t data3[] = {0x00, 0x00, (((LCD_W-1) >> 8) & 0xFF), ((LCD_W-1) & 0xFF)}; // 列地址从 0 到 W-1
lcd_sendData(spi, data3, sizeof(data3));
lcd_sendCom(spi, 0x2B); // RASET 设置行地址范围
uint8_t data4[] = {0x00, 0x00, (((LCD_H-1) >> 8) & 0xFF), ((LCD_H-1) & 0xFF)}; // 行地址从 0 到 H-1
lcd_sendData(spi, data4, sizeof(data4));
lcd_sendCom(spi, 0x29); // DISPON 开启显示
// 打开背光
gpio_set_level(LCD_BL_PIN, 1);
// 填充屏幕
lcd_full(spi, COLOR_BLACK);
// 调试信息
ESP_LOGI(TAG, "LCD initialized successfully");
return 1;
}
int lcd_full_area(spi_device_handle_t spi, const lv_area_t * area, uint8_t * px_map)
{
// 设置地址范围
lcd_sendCom(spi, 0x2A); // CASET 设置列地址范围
uint8_t data3[] = {(((area->x1-1) >> 8) & 0xFF), ((area->x1-1) & 0xFF), (((area->x2-1) >> 8) & 0xFF), ((area->x2-1) & 0xFF)}; // 列地址从 0 到 W-1
lcd_sendData(spi, data3, sizeof(data3));
lcd_sendCom(spi, 0x2B); // RASET 设置行地址范围
uint8_t data4[] = {(((area->y1-1) >> 8) & 0xFF), ((area->y1-1) & 0xFF), (((area->y2-1) >> 8) & 0xFF), ((area->y2-1) & 0xFF)}; // 行地址从 0 到 H-1
lcd_sendData(spi, data4, sizeof(data4));
// 发送数据填充屏幕
lcd_sendCom(spi, 0x2C); // RAMWR
lcd_sendData(spi, px_map, ((area->x2 - area->x1) * (area->y2 - area->y1) * 2));
return 1;
}
int lcd_full(spi_device_handle_t spi, uint16_t color)
{
// 设置地址范围
lcd_sendCom(spi, 0x2A); // CASET 设置列地址范围
uint8_t data3[] = {0x00, 0x00, (((LCD_W-1) >> 8) & 0xFF), ((LCD_W-1) & 0xFF)}; // 列地址从 0 到 W-1
lcd_sendData(spi, data3, sizeof(data3));
lcd_sendCom(spi, 0x2B); // RASET 设置行地址范围
uint8_t data4[] = {0x00, 0x00, (((LCD_H-1) >> 8) & 0xFF), ((LCD_H-1) & 0xFF)}; // 行地址从 0 到 H-1
lcd_sendData(spi, data4, sizeof(data4));
// 准备数据缓冲区
const int screenWidth = LCD_W;
const int screenHeight = LCD_H;
uint8_t *buffer = malloc(screenWidth * screenHeight * 2); // 每个像素 2 字节
if (buffer == NULL) {
ESP_LOGE(TAG, "Memory allocation failed");
return -1; // 内存分配失败
}
// 填充缓冲区
uint16_t *colorBuffer = (uint16_t *)buffer;
for (int i = 0; i < screenWidth * screenHeight; i++) {
colorBuffer[i] = color;
}
// 发送数据填充屏幕
lcd_sendCom(spi, 0x2C); // RAMWR
lcd_sendData(spi, buffer, screenWidth * screenHeight * 2);
free(buffer); // 释放缓冲区
ESP_LOGI(TAG, "LCD full color successfully");
return 1;
}
void lcd_gpio_init(void) {
// 配置引脚为输出模式,初始为低电平
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; // 禁用中断
io_conf.mode = GPIO_MODE_OUTPUT; // 设置为输出模式
io_conf.pin_bit_mask = (1ULL << LCD_DC_PIN) | (1ULL << LCD_RST_PIN) | (1ULL << LCD_BL_PIN); // 选择引脚
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
// 初始化引脚电平为 0
gpio_set_level(LCD_DC_PIN, 0);
gpio_set_level(LCD_RST_PIN, 0);
gpio_set_level(LCD_BL_PIN, 0);
}
Include/bsp_lcd.h
#ifndef BSP_SPI_H_
#define BSP_SPI_H_
#include <stdio.h>
#include <driver/gpio.h>
#include <driver/spi_master.h>
#include <esp_err.h>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
//|------------------------------------lvgl-------------------------------------------|
#include "lvgl/src/misc/lv_area.h"
int lcd_full_area(spi_device_handle_t spi, const lv_area_t * area, uint8_t * px_map);
//|------------------------------------------------------------------------------------|
static const char *TAG = "LCD";
#define LCD_SCL_PIN (39)
#define LCD_MOSI_PIN (38)
#define LCD_CS_PIN (45)
#define LCD_DC_PIN (42)
#define LCD_RST_PIN (0)
#define LCD_BL_PIN (1)
#define SPI_FREQ 80000000
#define LCD_W 240
#define LCD_H 320
#define RGB888_TO_RGB565(r, g, b) \
( (((uint16_t)((r) >> 3)) << 11) | (((uint16_t)((g) >> 2)) << 5) | ((uint16_t)((b) >> 3)) )
#define COLOR_RED RGB888_TO_RGB565(255, 0, 0) // 红
#define COLOR_ORANGE RGB888_TO_RGB565(255, 128, 0) // 橙
#define COLOR_YELLOW RGB888_TO_RGB565(255, 255, 0) // 黄
#define COLOR_GREEN RGB888_TO_RGB565(0, 255, 0) // 绿
#define COLOR_CYAN RGB888_TO_RGB565(0, 255, 255) // 青
#define COLOR_BLUE RGB888_TO_RGB565(0, 0, 255) // 蓝
#define COLOR_PURPLE RGB888_TO_RGB565(255, 0, 255) // 紫
#define COLOR_BLACK RGB888_TO_RGB565(0, 0, 0) // 黑
#define COLOR_WHITE RGB888_TO_RGB565(255, 255, 255) // 白
spi_device_handle_t spi_init(void);
void spi_sendData(spi_device_handle_t spi,uint8_t* data,size_t len);
void lcd_sendData(spi_device_handle_t spi,uint8_t* data,size_t len);
void lcd_sendCom(spi_device_handle_t spi,uint8_t com);
void lcd_gpio_init(void);
int lcd_init(spi_device_handle_t spi);
int lcd_full(spi_device_handle_t spi,uint16_t color);
#endif