原创 14. ESP32开发之ST7789显示

2025-6-29 20:40 1462 3 3 分类: MCU/ 嵌入式 文集: ESP32
  • SPI概述
  • ESP IDF中SPI LCD的相关API
  • 简单使用LVGL
  • 完整代码
  • 总结

SPI概述

当进入嵌入式行业开始,SPI总线是最需要且基础的知识了,它是高速全双工串行总线,可做到同时收发数据。时序和控制根据各家的芯片或者屏幕等设备的数据手册进行阅读和进行编程,比如总线模式就有四种,通过寄存器CPOL和CPHA配置

模式

CPOL

CPHA

数据采样时刻

适用场景

0

0

0

SCK下降沿,第1个跳变沿采样

多数传感器(如BME280)

1

0

1

SCK下降沿,第2个跳变沿采样

部分ADC芯片

2

1

0

SCK上升沿,第1个跳变沿采样

特殊存储器

3

1

1

SCK上升沿,第2个跳变沿采样

某些RF模块

更多的SPI相关基础,可以在网上查询

ESP IDF中SPI LCD相关API

在ESP32开发库ESP IDF中有完整的SPI LCD实现的代码,我们只需要合适的调用相关API函数就可以了,在此案例中用到如下函数和结构体

1. 配置SPI总线
  • SPI总线结构体


typedef struct {
    union {
      int mosi_io_num;    //spi的mosi接口,不用赋值-1
      int data0_io_num;   ///< GPIO pin for spi data0 signal in quad/octal mode, or -1 if not used.
    };
    union {
      int miso_io_num;    ///< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used.
      int data1_io_num;   ///< GPIO pin for spi data1 signal in quad/octal mode, or -1 if not used.
    };
    int sclk_io_num;      ///< GPIO pin for SPI Clock signal, or -1 if not used.
    union {
      int quadwp_io_num;  ///< GPIO pin for WP (Write Protect) signal, or -1 if not used.
      int data2_io_num;   ///< GPIO pin for spi data2 signal in quad/octal mode, or -1 if not used.
    };
    union {
      int quadhd_io_num;  ///< GPIO pin for HD (Hold) signal, or -1 if not used.
      int data3_io_num;   ///< GPIO pin for spi data3 signal in quad/octal mode, or -1 if not used.
    };
    int data4_io_num;     ///< GPIO pin for spi data4 signal in octal mode, or -1 if not used.
    int data5_io_num;     ///< GPIO pin for spi data5 signal in octal mode, or -1 if not used.
    int data6_io_num;     ///< GPIO pin for spi data6 signal in octal mode, or -1 if not used.
    int data7_io_num;     ///< GPIO pin for spi data7 signal in octal mode, or -1 if not used.
    int max_transfer_sz;  ///< Maximum transfer size, in bytes. Defaults to 4092 if 0 when DMA enabled, or to `SOC_SPI_MAXIMUM_BUFFER_SIZE` if DMA is disabled.
    uint32_t flags;       ///< Abilities of bus to be checked by the driver. Or-ed value of ``SPICOMMON_BUSFLAG_*`` flags.
    esp_intr_cpu_affinity_t  isr_cpu_id;    ///< Select cpu core to register SPI ISR.
    int intr_flags;       /**< Interrupt flag for the bus to set the priority, and IRAM attribute, see
                           *  ``esp_intr_alloc.h``. Note that the EDGE, INTRDISABLED attribute are ignored
                           *  by the driver. Note that if ESP_INTR_FLAG_IRAM is set, ALL the callbacks of
                           *  the driver, and their callee functions, should be put in the IRAM.
                           */
} spi_bus_config_t;


  • 初始化SPI总线函数


esp_err_t spi_bus_initialize(
    spi_host_device_t host_id,    //使用哪个主机SPI,有SPI0_HOST、SPI1_HOST、SP2_HOST
    const spi_bus_config_t *bus_config, //使用spi_bus_config_t创建的结构体
    spi_dma_chan_t dma_chan  //dma通道,设置SPI_DMA_DISABLED则不开启
)


2. 创建LCD SPI句柄,并与SPI总线相连
  • LCD SPI面板相关io结构体


typedef struct {
    int cs_gpio_num; /*!< GPIO used for CS line */
    int dc_gpio_num; /*!< GPIO used to select the D/C line, set this to -1 if the D/C line is not used */
    int spi_mode;    /*!< Traditional SPI mode (0~3) */
    unsigned int pclk_hz;    /*!< Frequency of pixel clock */
    size_t trans_queue_depth; /*!< Size of internal transaction queue */
    esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data transfer has finished */
    void *user_ctx;    /*!< User private data, passed directly to on_color_trans_done's user_ctx */
    int lcd_cmd_bits;   /*!< Bit-width of LCD command */
    int lcd_param_bits; /*!< Bit-width of LCD parameter */
    struct {
        unsigned int dc_low_on_data: 1;  /*!< If this flag is enabled, DC line = 0 means transfer data, DC line = 1 means transfer command; vice versa */
        unsigned int octal_mode: 1;      /*!< transmit with octal mode (8 data lines), this mode is used to simulate Intel 8080 timing */
        unsigned int quad_mode: 1;       /*!< transmit with quad mode (4 data lines), this mode is useful when transmitting LCD parameters (Only use one line for command) */
        unsigned int sio_mode: 1; /*!< Read and write through a single data line (MOSI) */
        unsigned int lsb_first: 1;       /*!< transmit LSB bit first */
        unsigned int cs_high_active: 1;  /*!< CS line is high active */
    } flags; /*!< Extra flags to fine-tune the SPI device */
} esp_lcd_panel_io_spi_config_t;


  • 创建LCD SPI面板相关io的句柄


esp_err_t esp_lcd_new_panel_io_spi(
    esp_lcd_spi_bus_handle_t bus,   //SPI总线
    const esp_lcd_panel_io_spi_config_t *io_config,//填充完成的esp_lcd_panel_io_spi_config_t结构体变量
    esp_lcd_panel_io_handle_t *ret_io //返回的LCD SPI面板句柄
)
//在使用这个函数之前需先创建个esp_lcd_panel_io_spi_config_t结构体变量,比如esp_lcd_panel_io_handle_t io_handle = NULL;


  • LCD SPI面板驱动配置结构体


typedef struct {
    int reset_gpio_num; /*!< GPIO used to reset the LCD panel, set to -1 if it's not used */
    union {
        lcd_rgb_element_order_t color_space;   /*!< @deprecated Set RGB color space, please use rgb_ele_order instead */
        lcd_rgb_element_order_t rgb_endian;    /*!< @deprecated Set RGB data endian, please use rgb_ele_order instead */
        lcd_rgb_element_order_t rgb_ele_order; /*!< Set RGB element order, RGB or BGR */
    };
    lcd_rgb_data_endian_t data_endian;         /*!< Set the data endian for color data larger than 1 byte */
    unsigned int bits_per_pixel;       /*!< Color depth, in bpp */
    struct {
        unsigned int reset_active_high: 1; /*!< Setting this if the panel reset is high level active */
    } flags;                               /*!< LCD panel config flags */
    void *vendor_config; /*!< vendor specific configuration, optional, left as NULL if not used */
} esp_lcd_panel_dev_config_t;


  • 创建 LCD 面板句柄,并指定 SPI IO 设备句柄

以下函数是ESP IDF套件中已经实现的st7789相关函数,其内部还有很多适配的LCD,可以在如下路径找到

esp-idf/components/esp_lcd/src


//其实就是将实现的st7789相关函数注册到LCD面板这个大的虚拟对象中,这是C语言的面向对象编程
esp_err_t
esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io,   //创建过的lcd面板io句柄
                         const esp_lcd_panel_dev_config_t *panel_dev_config,//创建过的LCD面板驱动句柄
                         esp_lcd_panel_handle_t *ret_panel  //返回一个LCD面板句柄
                        )


  1. 3. 通过以上一步步完善结构体,和创建的LCD面板句柄,我们将可以调用关于LCD 面板的所有API函数,比如:


//控制LCD复位
esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel)   
//初始化LCD
esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel)
//开关屏幕
 esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t panel, bool on_off)
//控制屏幕是否反色,白天模式暗黑模式的控制
esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert_color_data)


使用LVGL

  1. 1. 将LVGL的库加入到项目中

ESP IDF开发套件有完善的组件管理方式,在UBUNTU中我们可以如下操作

  • 在项目根目录下使用如下命令,将会在main文件夹下生成一个idf_component.yml的文件
idf.py create-manifest

增加参数:

--path:显示的指定在什么目录下创建组件清单文件

--component:在componets目录下,为相关组件创建(此参数所赋值)清单,比如--component=my_components

  • 使用如下命令重新配置项目
idf.py reconfigure

这样就后面每次在构建项目时,则会跟踪上一步生成的idf_component.yml文件

  • 编辑idf_component.yml
## IDF Component Manager Manifest File
dependencies:
## Required IDF version
idf:
version: ">=4.1.0"
lvgl/lvgl: "~9.2.0" //这是我需要添加的LVGL库,版本时9.2.0

更多操作可以参考如下链接;

https://docs.espressif.com/projects/esp-idf/zh_CN/release-v5.2/esp32s3/api-guides/tools/idf-component-manager.html

  1. 2. 使用LVGL在屏幕上显示"hello world"
  • 初始化lvgl
lv_init();
  • 创建lvgl显示器
lv_display_t * lv_display_create(int32_t hor_res, int32_t ver_res)//参数为实际屏幕的分辨率

  • 申请绘制使用的buffer

void lv_display_set_buffers(lv_display_t * disp,//上一步创建的显示器
void * buf1, //缓存1
void * buf2,//缓存2
uint32_t buf_size,//缓存大小
lv_display_render_mode_t render_mode//绘制图像的模式
)


  • 将LVGL与上面创建的LCD面板进行连接


void lv_display_set_user_data(lv_display_t * disp, void * user_data)


  • 设置显示的颜色格式


void lv_display_set_color_format(lv_display_t * disp, lv_color_format_t color_format)


  • 设置显示的方向


void lv_display_set_rotation(lv_display_t * disp, lv_display_rotation_t rotation)


  • 设置渲染回调函数


void lv_display_set_flush_cb(lv_display_t * disp, lv_display_flush_cb_t flush_cb)
//回调函数如果设置为NULL,则使用默认函数


  • 为LVGL创建定时器


static void example_increase_lvgl_tick(void *arg)
{
/* Tell LVGL how many milliseconds has elapsed */
lv_tick_inc(2); //这里是2ms,建议跟定时器定时时间同步
}

const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &example_increase_lvgl_tick, //定时事件,定时通知lvgl已经多久了
.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, 2 * 1000)); //2ms触发一次事件


  • 注册一事件回调函数,当刷新就绪


const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = example_notify_lvgl_flush_ready,
    };
    /* Register done callback */

ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display));

//回调函数中调用lvgl的刷新就绪函数,通知LVGL就绪状态,让lvgl进行下一步刷新

LV_ATTRIBUTE_FLUSH_READYvoid lv_display_flush_ready(lv_display_t * disp)


  • 为lvgl创建任务


//通过lv_timer_handler(void)持续更新lvgl,这是LVGL的心脏
static void example_lvgl_port_task(void *arg)
{
ESP_LOGI(TAG, "Starting LVGL task");
uint32_t time_till_next_ms = 0;
while (1) {
_lock_acquire(&lvgl_api_lock);
time_till_next_ms = lv_timer_handler();
_lock_release(&lvgl_api_lock);
// in case of triggering a task watch dog time out
time_till_next_ms = MAX(time_till_next_ms, EXAMPLE_LVGL_TASK_MIN_DELAY_MS);
// in case of lvgl display not ready yet
time_till_next_ms = MIN(time_till_next_ms, EXAMPLE_LVGL_TASK_MAX_DELAY_MS);
usleep(1000 * time_till_next_ms);
}
}
xTaskCreatePinnedToCore(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL,1);


  • 绘制显示内容,如下绘制“hello world"


lv_obj_t *scr = lv_display_get_screen_active(disp);
lv_obj_t *label1= lv_label_create(scr);
lv_label_set_text(label1, "Hello\nworld");
lv_obj_align(label1, LV_ALIGN_CENTER,0,0);


完整代码

/**
 * Copyright (C) 2024-2034 HalfMoon2.
 * All rights reserved.
 * 
 * @file 	 Filename without the absolute path
 * @brief 	 Brief description
 * @author 	 HalfMoon2
 * @date 	 2025-06-23
 * @version	 v0.1
 * 
 * @revision history:
 * 	 2025-06-23 - Initial version.
 */
#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <driver/gpio.h>
#include <driver/spi_master.h>
#include "driver/spi_common.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "esp_dma_utils.h"
#include "lvgl.h"
#include "esp_task_wdt.h"

static const char *TAG = "ST7789";

//LCD使用的SPI接口
#define LCD_SPI SPI2_HOST

//SPI相关引脚定义
#define SPI_LCD_MOSI GPIO_NUM_12
#define SPI_LCD_MISO GPIO_NUM_18
#define SPI_LCD_SCLK GPIO_NUM_11
#define SPI_LCD_CS	 GPIO_NUM_15
#define SPI_LCD_DC	 GPIO_NUM_17
#define SPI_LCD_RST	 GPIO_NUM_21
#define SPI_LCD_BL	 GPIO_NUM_26

//LCD显示屏的大小
#define LCD_SIZE_WIDTH 240
#define LCD_SIZE_HIGHT 240

#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1000/CONFIG_FREERTOS_HZ
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_STACK_SIZE   (4 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY     2

// LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it
static _lock_t lvgl_api_lock;
static lv_obj_t * btn;
static esp_lcd_panel_io_handle_t io_handle = NULL;

static void set_angle(void * obj, int32_t v)
{
	ESP_LOGI(TAG, "Starting set_angle");
    lv_arc_set_value(obj, v);
}

static void anim_x_cb(void *var, int32_t v) {
	lv_obj_set_x(var, v);
}

void example_lvgl_demo_ui(lv_display_t *disp)
{
    lv_obj_t * scr= lv_display_get_screen_active(disp);
	lv_obj_t *label1= lv_label_create(scr);
    lv_label_set_text(label1, "Hello\nworld");
	lv_obj_align(label1, LV_ALIGN_CENTER,0,0);

#if 1
    btn = lv_button_create(scr);
    lv_obj_t * lbl = lv_label_create(btn);
    lv_label_set_text_static(lbl, LV_SYMBOL_REFRESH" ROTATE");
    lv_obj_align(btn, LV_ALIGN_BOTTOM_MID,0, 0);
    /*Button event*/
 //   lv_obj_add_event_cb(btn, btn_cb, LV_EVENT_CLICKED, disp);
#endif
    /*Create an Arc*/
    lv_obj_t * arc = lv_arc_create(scr);
    lv_arc_set_rotation(arc, 270);
    lv_arc_set_bg_angles(arc, 0, 360);
    lv_obj_remove_style(arc, NULL, LV_PART_KNOB);   /*Be sure the knob is not displayed*/
    lv_obj_remove_flag(arc, LV_OBJ_FLAG_CLICKABLE);  /*To not allow adjusting by click*/
    lv_obj_center(arc);
#if 1
    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, arc);
    lv_anim_set_exec_cb(&a, set_angle);
    lv_anim_set_duration(&a, 1000);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);    /*Just for the demo*/
    lv_anim_set_repeat_delay(&a, 500);
    lv_anim_set_values(&a, 0, 100);
    lv_anim_start(&a);
#endif
}

static bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_display_t *disp = (lv_display_t *)user_ctx;
    lv_display_flush_ready(disp);
    return false;
}

static void example_lvgl_port_task(void *arg)
{
    ESP_LOGI(TAG, "Starting LVGL task");
    uint32_t time_till_next_ms = 0;
    while (1) {
        _lock_acquire(&lvgl_api_lock);
        time_till_next_ms = lv_timer_handler();
        _lock_release(&lvgl_api_lock);
        // in case of triggering a task watch dog time out
        time_till_next_ms = MAX(time_till_next_ms, EXAMPLE_LVGL_TASK_MIN_DELAY_MS);
        // in case of lvgl display not ready yet
        time_till_next_ms = MIN(time_till_next_ms, EXAMPLE_LVGL_TASK_MAX_DELAY_MS);
        usleep(1000 * time_till_next_ms);
    }
}

/* Rotate display and touch, when rotated screen in LVGL. Called when driver parameters are updated. */
static void example_lvgl_port_update_callback(lv_display_t *disp)
{
    esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
    lv_display_rotation_t rotation = lv_display_get_rotation(disp);

    switch (rotation) {
    case LV_DISPLAY_ROTATION_0:
        // Rotate LCD display
        esp_lcd_panel_swap_xy(panel_handle, false);
        esp_lcd_panel_mirror(panel_handle, false, false);
        break;
    case LV_DISPLAY_ROTATION_90:
        // Rotate LCD display
        esp_lcd_panel_swap_xy(panel_handle, true);
        esp_lcd_panel_mirror(panel_handle, true, true);
        break;
    case LV_DISPLAY_ROTATION_180:
        // Rotate LCD display
        esp_lcd_panel_swap_xy(panel_handle, false);
        esp_lcd_panel_mirror(panel_handle, true, true);
        break;
    case LV_DISPLAY_ROTATION_270:
        // Rotate LCD display
        esp_lcd_panel_swap_xy(panel_handle, true);
        esp_lcd_panel_mirror(panel_handle, true, false);
        break;
    }
}

static void example_increase_lvgl_tick(void *arg)
{
    /* Tell LVGL how many milliseconds has elapsed */
    lv_tick_inc(2);
}

static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
    example_lvgl_port_update_callback(disp);
    esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
    int offsetx1 = area->x1;
    int offsetx2 = area->x2;
    int offsety1 = area->y1;
    int offsety2 = area->y2;
    // because SPI LCD is big-endian, we need to swap the RGB bytes order
    lv_draw_sw_rgb565_swap(px_map, (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1));
    // copy a buffer's content to a specific area of the display
    esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
}

static esp_lcd_panel_handle_t  st7789_init(void)
{
	//spi bus的相关配置
	spi_bus_config_t busconfig={
//		.flags=SPICOMMON_BUSFLAG_MASTER,  //设置SPI为主机模式
		.sclk_io_num=SPI_LCD_SCLK,
		.miso_io_num=SPI_LCD_MISO,
		.mosi_io_num=SPI_LCD_MOSI,
		.quadwp_io_num = -1,
    	.quadhd_io_num = -1,
		.max_transfer_sz=LCD_SIZE_HIGHT*20*sizeof(uint16_t)//单次最多可传输80行像素
	};
	ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI, &busconfig, SPI_DMA_CH_AUTO)); // 启用 DMA  

	//创建SPI LCD句柄
	esp_lcd_panel_io_spi_config_t io_config = {
    	.dc_gpio_num = SPI_LCD_DC,
    	.cs_gpio_num = SPI_LCD_CS,
    	.pclk_hz = 40*1000*1000,
    	.lcd_cmd_bits = 8,
    	.lcd_param_bits = 8,
    	.spi_mode = 0,
    	.trans_queue_depth = 10
	};
	// 将 LCD 连接到 SPI 总线
	ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI, &io_config, &io_handle));

	
	esp_lcd_panel_handle_t panel_handle = NULL;
	esp_lcd_panel_dev_config_t panel_config = {
   	 	.reset_gpio_num = SPI_LCD_RST,
    	.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
    	.bits_per_pixel = 16,
	};
	// 为 ST7789 创建 LCD 面板句柄,并指定 SPI IO 设备句柄
	ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));

	ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
	ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
	esp_lcd_panel_invert_color(panel_handle,true);
	

	ESP_LOGI(TAG, "Initialize LVGL library");

	return panel_handle;
}

void app_main(void)
{
	esp_lcd_panel_handle_t panel_handle=st7789_init();
	lv_init();
	// create a lvgl display
    lv_display_t *display = lv_display_create(240, 240);


    // alloc draw buffers used by LVGL
    // it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized
    size_t draw_buffer_sz = LCD_SIZE_WIDTH * 24 * sizeof(lv_color16_t);

    void *buf1 = heap_caps_malloc(draw_buffer_sz, MALLOC_CAP_DMA);
    assert(buf1);
    void *buf2 = heap_caps_malloc(draw_buffer_sz, MALLOC_CAP_DMA);
    assert(buf2);
    // initialize LVGL draw buffers
    lv_display_set_buffers(display, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_PARTIAL);
    // associate the mipi panel handle to the display
    lv_display_set_user_data(display, panel_handle);
    // set color depth
    lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB565);
    // set the callback which can copy the rendered image to an area of the display

	lv_display_set_rotation(display,LV_DISPLAY_ROTATION_0);


    lv_display_set_flush_cb(display, example_lvgl_flush_cb);

    ESP_LOGI(TAG, "Install LVGL tick timer");
    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &example_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, 2*1000));

	ESP_LOGI(TAG, "Register io panel event callback for LVGL flush ready notification");
    const esp_lcd_panel_io_callbacks_t cbs = {
        .on_color_trans_done = example_notify_lvgl_flush_ready,
    };
    /* Register done callback */
    ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, display));

	 ESP_LOGI(TAG, "Create LVGL task");
    xTaskCreatePinnedToCore(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL,1);

    ESP_LOGI(TAG, "Display LVGL Meter Widget");
    // Lock the mutex due to the LVGL APIs are not thread-safe
	
   	_lock_acquire(&lvgl_api_lock);
   	example_lvgl_demo_ui(display);
   	_lock_release(&lvgl_api_lock);
}


显示结果:

fig:

总结

一定要注意以下几点:

  • 缓存区大小的设定,如果设置不正确将会出现如下情况,偏移也会如此,所以碰到这个情况可以想想会不会是缓存区的问题

fig:

  • 显示镜像,根据不同的实现方式,要仔细调试


//这里只是设置了标志
lv_display_set_rotation(display,LV_DISPLAY_ROTATION_0);
//要通过如下函数进行具体实现
static void example_lvgl_port_update_callback(lv_display_t *disp)
{
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
lv_display_rotation_t rotation = lv_display_get_rotation(disp);

switch (rotation) {
case LV_DISPLAY_ROTATION_0:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, false);
esp_lcd_panel_mirror(panel_handle, false, false);
break;
case LV_DISPLAY_ROTATION_90:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, true, true);
break;
case LV_DISPLAY_ROTATION_180:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, false);
esp_lcd_panel_mirror(panel_handle, true, true);
break;
case LV_DISPLAY_ROTATION_270:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, true, false);
break;
}
}

static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
example_lvgl_port_update_callback(disp);
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
// because SPI LCD is big-endian, we need to swap the RGB bytes order
lv_draw_sw_rgb565_swap(px_map, (offsetx2 + 1 - offsetx1) * (offsety2 + 1 - offsety1));
// copy a buffer's content to a specific area of the display
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
}


如下情况,就需要改example_lvgl_port_update_callback函数里的具体实现

fig:

  • esp idf开发套件里有大量的案例,完全可以参考相关代码进行设计,当然从零搭建也是可以的,如果时间充裕的话

  • lvgl的相关配置,可以使用idf.py menuconfig进行更改

  • 一定要在刷新就绪后,使用lv_display_flush_ready(lv_display_t * disp)函数通知lvgl,否则lvgl无法进行下一步刷新
  • 作者: 二月半, 来源:面包板社区

    链接: https://mbb.eet-china.com/blog/uid-me-1862109.html

    版权声明:本文为博主原创,未经本人允许,禁止转载!

    PARTNER CONTENT

    文章评论1条评论)

    登录后参与讨论

    eeNick 2025-7-2 14:14

    奖励80E币
    相关推荐阅读
    二月半 2025-06-17 16:39
    13. ESP32开发之定时器中断
    概述相关API函数举例:定时发送一个事件总结概述ESP32有一组外设--定时器组。它可以选择不同的时钟源和分配系数。该定时器应用灵活,超时报警可以自动更新计数值。相关API函数1.定时器配置结构体ty...
    二月半 2025-06-12 14:32
    【拆解】一款远程控制开关
    七年前买了个远程控制开关,想想那个时候应该物联网才兴起的时候吧。如今因为控制麻烦且经常出现连接掉线问题,于是给淘汰了。这个设备我是拿来控制吊灯,特别麻烦的是,当晚上关灯后,会有一点灯点亮着,掉线的时候...
    二月半 2025-06-12 10:11
    ESP32开发之GPIO中断
    电路图GPIO的中断类型相关API函数应用举例总结电路图在ESP32中内部有完整的控制电路,比如上下拉以及滤波器等,所以我们这里可以直接用一个微动开关连接到地。GPIO的中断类型GPIO_INTR_D...
    二月半 2025-06-09 22:37
    ESP32开发之WS2812B控制
    WS2812B数据手册重点摘录硬件电路Remote Control Transceive(RMT)概念RMT的相关API函数一段简单的控制WS2812B的应用举例总结WS2812B数据手册重点摘录WS...
    二月半 2025-06-04 09:07
    10. ESP32开发之LED闪烁和呼吸的实现
    硬件电路介绍GPIO输出模式GPIO配置过程闪烁灯的源码LED PWM的控制器(LEDC)概述LEDC配置过程及现象整体流程硬件电路介绍电路图如下:只要有硬件基础的应该都知道上图中,当GPIO4的输出...
    EE直播间
    更多
    我要评论
    1
    3
    关闭 站长推荐上一条 /5 下一条