ESP32移植LVGL教程(基于esp32_idf+cmake)

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.hlv_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

工程文件下载

点击这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月明Mo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值