(十二)ESP32-S3之lvgl移植(基于st7789v)

LCD驱动移植

在 ESP32 上移植 LVGL 之前,需要先准备好 显示驱动(LCD) 和 触摸驱动(如果有触摸)。
以下是主要的要点:
(1)确保屏幕的驱动可用,并且可以实现在屏幕上 画点,矩形填充的功能
(2)确保屏幕的触摸驱动可用(如果有触摸功能),并且可以实现获取触摸的XY坐标的功能
SPI初始化

esp_err_t my_spi_init(void)
{
    spi_bus_config_t buscfg = {
        .sclk_io_num     = SPI_SCLK_PIN,    /* 时钟引脚 */
        .mosi_io_num     = SPI_MOSI_PIN,    /* 主机输出从机输入引脚 */
        .miso_io_num     = SPI_MISO_PIN,    /* 主机输入从机输出引脚 */
        .quadwp_io_num   = -1,              /* 用于Quad模式的WP引脚,未使用时设置为-1 */
        .quadhd_io_num   = -1,              /* 用于Quad模式的HD引脚,未使用时设置为-1 */
        .max_transfer_sz = 320 * 240 * sizeof(uint16_t),   /* 最大传输大小(整屏(RGB565格式)) */
    };
    /* 初始化SPI总线 */
    ESP_ERROR_CHECK(spi_bus_initialize(MY_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));

    /* SPI驱动接口配置,SPISD卡时钟是20-25MHz */
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 20 * 1000 * 1000, /* SPI时钟 */
        .mode = 0,                          /* SPI模式0 */
        .spics_io_num = SD_CS_PIN,          /* 片选引脚 */
        .queue_size = 7,                    /* 事务队列尺寸 7个 */
    };

    /* 添加SPI总线设备 */
    ESP_ERROR_CHECK(spi_bus_add_device(MY_SPI_HOST, &devcfg, &MY_SD_Handle));

    return ESP_OK;
}

LCD初始化

esp_err_t spilcd_init(void)
{
    gpio_config_t io_conf = {
        .intr_type = GPIO_INTR_DISABLE,        // 必须禁用中断
        .mode = GPIO_MODE_OUTPUT,              // 输出模式
        .pull_up_en = GPIO_PULLUP_DISABLE,     // 必须明确禁用上拉
        .pull_down_en = GPIO_PULLDOWN_DISABLE,// 必须明确禁用下拉
        .pin_bit_mask = (1ULL << LCD_PWR_PIN) | (1ULL << LCD_RST_PIN),
    };
    
    ESP_ERROR_CHECK(gpio_config(&io_conf));
    LCD_RST(0);
    vTaskDelay(pdMS_TO_TICKS(100));
    LCD_RST(1);
    vTaskDelay(pdMS_TO_TICKS(100));

    esp_lcd_panel_io_handle_t io_handle = NULL;     /* LCD IO设备句柄 */
    /* spi配置 */
    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num         = LCD_DC_PIN,          /* DC IO */
        .cs_gpio_num         = LCD_CS_PIN,          /* CS IO */
        .pclk_hz             = 60 * 1000 * 1000,    /* PCLK为60MHz */
        .lcd_cmd_bits        = 8,                   /* 命令位宽 */
        .lcd_param_bits      = 8,                   /* LCD参数位宽 */
        .spi_mode            = 0,                   /* SPI模式 */
        .trans_queue_depth   = 7,                   /* 传输队列 */
    };
    /* 将LCD设备挂载至SPI总线上 */
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));
    /* LCD设备配置 */
    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = LCD_RST_PIN,                  /* RTS IO */
        .rgb_ele_order  = COLOR_RGB_ELEMENT_ORDER_RGB,  /* RGB颜色格式 */
        .bits_per_pixel = 16,                           /* 颜色深度 */
        .data_endian    = LCD_RGB_DATA_ENDIAN_BIG,      /* 大端顺序 */
    };
    /* 为ST7789创建LCD面板句柄,并指定SPI IO设备句柄 */
    ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
    /* 复位LCD */
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    /* 反显 */
    ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
    /* 初始化LCD句柄 */
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
    /* 打开屏幕 */
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = notify_lcd_flush_ready,
    };
    /* 注册屏幕刷新完成回调函数 */
    ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, NULL));
     ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, false));
     ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, false));
    spilcd_clear(WHITE);        /* 清屏 */
    LCD_PWR(1);/*打开背光*/
    return ESP_OK;
}

void spilcd_clear(uint16_t color)
{
    /* 以40行作为缓冲,提高速率,若出现内存不足,可以减少缓冲行数 */
    uint16_t *buffer = heap_caps_malloc(spilcddev.width * sizeof(uint16_t) * 40, MALLOC_CAP_DMA);
    uint16_t color_tmp = ((color & 0x00FF) << 8) | ((color & 0xFF00) >> 8);   /* 需要转换一下颜色值 */
    if (NULL == buffer)
    {
        ESP_LOGE("TAG", "Memory for bitmap is not enough");
    }
    else
    {
        for (uint32_t i = 0; i < spilcddev.width * 40; i++)
        {
            buffer[i] = color_tmp;
        }
        
        for (uint16_t y = 0; y < spilcddev.height; y+=40)
        {
            esp_lcd_panel_draw_bitmap(panel_handle, 0, y, spilcddev.width, y + 40, buffer);
        }
    }

    refresh_done_flag = 0;

    do
    {
        /* 等待内部缓存刷新完成 */
        vTaskDelay(1);
    }
    while (refresh_done_flag != 1);

    heap_caps_free(buffer);
}

LVGL移植

第一步:调用 lv_init 函数,初始化 LVGL 图形库。 在这一步的初始化中,涉及的内容非
常的多,包括:内存管理、文件系统、定时器,等等。
第二步:调用 lv_port_disp_init函数和 lv_port_indev_init函数,注册显示设备和输入设备。
注意: 在注册显示设备和输入设备之前,必须先初始化 LVGL 图形库(调用 lv_init 函数)。
第三步:为 LVGL 提供时基。用户可以使用定时器,在其中断里面定时调用 lv_tick_inc 函
数,为 LVGL 提供时基。如果工程中带有 OS 操作系统,则可以使用相应的时钟函数来为
LVGL 提供时基。
第四步:定时处理 LVGL 任务。用户需要每隔几毫秒调用一次 lv_timer_handler 函数,以
处理 LVGL 相关的任务,该函数可以放在 while 循环中,但延时不宜过大,确保 5 毫秒以内。

LVGL 源码下载
LVGL 相关的源码和工程都是存放在 GitHub 远程仓库中,该 GitHub 远程仓库地址为
https://github.com/lvgl/lvgl/,用户可以该仓库中获取 LVGL的 V8.3版本源码。

各文件夹和文件的功能如下表所示
在这里插入图片描述
上表中,与 LVGL 移植相关的有 examples 文件夹、src 文件夹、lv_conf_template.h 和 lvgl.h
文件,其他的部分均与移植无关,用户可以选择忽略。

拷贝 LVGL 文件夹到工程
首先把“lvgl-release-v8.3.zip”在工程的 components 文件夹下解压出来,并重命名为
“LVGL”,然后删除“lvgl-release-v8.3.zip”文件。
在这里插入图片描述
编写 LVGL与LCD的接口代码
在 main 文件夹下,新建 Lvgl_port 文件夹,并新建两个文件:lvgl_port.c、lvgl_port.h,这两个文件用于存放 LVGL 移植相关的代码,主要就是配置显示屏和触摸输入驱动(目前屏幕无触摸,注释触摸功能)。如下图所示:
在这里插入图片描述
lvgl_port.c


#include "lvgl_port.h"
#include "spilcd.h"
#include "esp_timer.h"
#include "lvgl.h"
#include "demos/lv_demos.h"
#include "my_spi.h"
#ifndef MY_DISP_HOR_RES
    #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now.
    #define MY_DISP_HOR_RES    240
#endif

#ifndef MY_DISP_VER_RES
    #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen height, default value 240 is used for now.
    #define MY_DISP_VER_RES    240
#endif

/**
 * @brief       lvgl_demo入口函数
 * @param       无
 * @retval      无
 */
void lvgl_demo(void)
{
    lv_init();              /* 初始化LVGL图形库 */
    lv_port_disp_init();    /* lvgl显示接口初始化,放在lv_init()的后面 */
    //lv_port_indev_init();   /* lvgl输入接口初始化,放在lv_init()的后面 */

    /* 为LVGL提供时基单元 */
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &increase_lvgl_tick,    /* 设置定时器回调 */
        .name = "lvgl_tick"                 /* 定时器名称 */
    };
    esp_timer_handle_t lvgl_tick_timer = NULL;
    ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));     /* 创建定时器 */
    ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, 1 * 1000));           /* 启动定时器 */

    /* 官方demo,需要在SDK Configuration中开启对应Demo */
    //lv_demo_music();      
     //lv_demo_benchmark();
    lv_demo_widgets();
    // lv_demo_stress();
    // lv_demo_keypad_encoder();

    while (1)
    {
        lv_timer_handler();             /* LVGL计时器 */
        vTaskDelay(pdMS_TO_TICKS(10));  /* 延时10毫秒 */
    }
}

/**
 * @brief       初始化并注册显示设备
 * @param       无
 * @retval      lvgl显示设备指针
 */
lv_disp_t *lv_port_disp_init(void)
{
    void *lcd_buffer[2];        /* 指向屏幕双缓存 */
   
     my_spi_init();
    /* 初始化显示设备LCD */
    spilcd_init();                /* LCD初始化 */

    /*-----------------------------
     * 创建一个绘图缓冲区
     *----------------------------*/
    /**
     * LVGL 需要一个缓冲区用来绘制小部件
     * 随后,这个缓冲区的内容会通过显示设备的 'flush_cb'(显示设备刷新函数) 复制到显示设备上
     * 这个缓冲区的大小需要大于显示设备一行的大小
     *
     * 这里有3种缓冲配置:
     * 1. 单缓冲区:
     *      LVGL 会将显示设备的内容绘制到这里,并将他写入显示设备。
     *
     * 2. 双缓冲区:
     *      LVGL 会将显示设备的内容绘制到其中一个缓冲区,并将他写入显示设备。
     *      需要使用 DMA 将要显示在显示设备的内容写入缓冲区。
     *      当数据从第一个缓冲区发送时,它将使 LVGL 能够将屏幕的下一部分绘制到另一个缓冲区。
     *      这样使得渲染和刷新可以并行执行。
     *
     * 3. 全尺寸双缓冲区
     *      设置两个屏幕大小的全尺寸缓冲区,并且设置 disp_drv.full_refresh = 1。
     *      这样,LVGL将始终以 'flush_cb' 的形式提供整个渲染屏幕,您只需更改帧缓冲区的地址。
     */
    /* 使用双缓冲 */
   
    //缓冲区开辟
        int DISP_BUF_SIZE =MY_DISP_HOR_RES * ((MY_DISP_VER_RES)/10);


        static lv_disp_draw_buf_t draw_buf_dsc;
        #if defined(CONFIG_GRAPHICS_USE_PSRAM)
        lv_color_t *buf_1 = heap_caps_malloc(DISP_BUF_SIZE*2, MALLOC_CAP_SPIRAM);
        lv_color_t *buf_2 = heap_caps_malloc(DISP_BUF_SIZE*2, MALLOC_CAP_SPIRAM);
        #else
        lv_color_t *buf_1 = heap_caps_malloc(DISP_BUF_SIZE*2, MALLOC_CAP_DMA);
        lv_color_t *buf_2 = heap_caps_malloc(DISP_BUF_SIZE*2, MALLOC_CAP_DMA);
        #endif
         lv_disp_draw_buf_init(&draw_buf_dsc, buf_1, buf_2,DISP_BUF_SIZE);


    /* 在LVGL中注册显示设备 */
    static lv_disp_drv_t disp_drv;      /* 显示设备的描述符(HAL要注册的显示驱动程序、与显示交互并处理与图形相关的结构体、回调函数) */
    lv_disp_drv_init(&disp_drv);        /* 初始化显示设备 */
    
    /* 设置显示设备的分辨率 
     * 这里为了适配正点原子的多款屏幕,采用了动态获取的方式,
     * 在实际项目中,通常所使用的屏幕大小是固定的,因此可以直接设置为屏幕的大小 
     */
    disp_drv.hor_res = spilcddev.width;                    /* 设置水平分辨率 */
    disp_drv.ver_res = spilcddev.height;                   /* 设置垂直分辨率 */

    /* 用来将缓冲区的内容复制到显示设备 */
    disp_drv.flush_cb = lvgl_disp_flush_cb;             /* 设置刷新回调函数 */

    /* 设置显示缓冲区 */
    disp_drv.draw_buf = &draw_buf_dsc;                      /* 设置绘画缓冲区 */

    disp_drv.user_data = panel_handle;       /* 传递屏幕控制句柄 */
    disp_drv.full_refresh = 1;                          /* 设置为完全刷新 */
    /* 注册显示设备 */
    return lv_disp_drv_register(&disp_drv);                    
}

/**
 * @brief       初始化并注册输入设备
 * @param       无
 * @retval      lvgl输入设备指针
 */
// lv_indev_t *lv_port_indev_init(void)
// {
    

//     /* 初始化输入设备 */
//     static lv_indev_drv_t indev_drv;
//     lv_indev_drv_init(&indev_drv);

//     /* 配置输入设备类型 */
//     indev_drv.type = LV_INDEV_TYPE_POINTER;

//     /* 设置输入设备读取回调函数 */
//     indev_drv.read_cb = touchpad_read;

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

/**
* @brief        将内部缓冲区的内容刷新到显示屏上的特定区域
* @note         可以使用 DMA 或者任何硬件在后台加速执行这个操作
*               但是,需要在刷新完成后调用函数 'lv_disp_flush_ready()'
* @param        disp_drv : 显示设备
* @param        area : 要刷新的区域,包含了填充矩形的对角坐标
* @param        color_map : 颜色数组
* @retval       无
*/
static void lvgl_disp_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
    esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)drv->user_data;

    /* 特定区域打点 */
    esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);

    /* 重要!!! 通知图形库,已经刷新完毕了 */
    lv_disp_flush_ready(drv);
}

/**
 * @brief       告诉LVGL运行时间
 * @param       arg : 传入参数(未用到)
 * @retval      无
 */
static void increase_lvgl_tick(void *arg)
{
    /* 告诉LVGL已经过了多少毫秒 */
    lv_tick_inc(1);
}


/**
 * @brief       获取触摸屏设备的状态
 * @param       无
 * @retval      返回触摸屏设备是否被按下
 */
// static bool touchpad_is_pressed(void)
// {
//     tp_dev.scan(0);     /* 触摸按键扫描 */

//     if (tp_dev.sta & TP_PRES_DOWN)
//     {
//         return true;
//     }

//     return false;
// }


/**
 * @brief       在触摸屏被按下的时候读取 x、y 坐标
 * @param       x   : x坐标的指针
 * @param       y   : y坐标的指针
 * @retval      无
 */
// static void touchpad_get_xy(lv_coord_t *x, lv_coord_t *y)
// {
//     (*x) = tp_dev.x[0];
//     (*y) = tp_dev.y[0];
// }

/**
 * @brief       图形库的触摸屏读取回调函数
 * @param       indev_drv   : 触摸屏设备
 * @param       data        : 输入设备数据结构体
 * @retval      无
 */
// void touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
// {
//     static lv_coord_t last_x = 0;
//     static lv_coord_t last_y = 0;

//     /* 保存按下的坐标和状态 */
//     if(touchpad_is_pressed())
//     {
//         touchpad_get_xy(&last_x, &last_y);  /* 在触摸屏被按下的时候读取 x、y 坐标 */
//         data->state = LV_INDEV_STATE_PR;
//     } 
//     else
//     {
//         data->state = LV_INDEV_STATE_REL;
//     }

//     /* 设置最后按下的坐标 */
//     data->point.x = last_x;
//     data->point.y = last_y;
// }

lvgl_port.h


 
#ifndef __LVGL_PORT_H
#define __LVGL_PORT_H

#include "lvgl.h"


/* 函数声明 */
void lvgl_demo(void);                                                                               /* lvgl_demo入口函数 */
lv_disp_t *lv_port_disp_init(void);                                                                 /* 初始化并注册显示设备 */
//lv_indev_t *lv_port_indev_init(void);                                                               /* 初始化并注册输入设备 */
static void increase_lvgl_tick(void *arg);                                                          /* 告诉LVGL运行时间 */
static void lvgl_disp_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map);   /* 将内部缓冲区的内容刷新到显示屏上的特定区域 */

#endif

CMakeLists.txt

idf_component_register(
    SRC_DIRS 
        "."
        "Lvgl_port"
    INCLUDE_DIRS 
        "."
        "Lvgl_port")

main.c

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include <stdio.h>
#include "lvgl_port.h"


/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    esp_err_t ret;
    uint8_t x = 0;

    ret = nvs_flash_init();     /* 初始化NVS */
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ESP_ERROR_CHECK(nvs_flash_init());
    }

   lvgl_demo();
}

解决显示颜色重影bug
在这里插入图片描述
解决music demo 的字体
在这里插入图片描述
打开demo
在这里插入图片描述
内存不足bug
这里解决

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值