说明:
- 本文档由DuRuofu撰写,由DuRuofu负责解释及执行。
- 这篇文章主要记录SPD2010(带触摸)驱动的LVGL移植,仅作为记录,并非教程。
修订历史:
| 文档名称 | 版本 | 作者 | 时间 | 备注 | | ---- | ---- | ------------ | ------ | | 基于ESP-IDF官方组件移植LVGL(SPD2010(带触摸)) |v1.0.0| DuRuofu | 2024-03-08 | 首次建立 |
基于ESP-IDF官方组件移植LVGL(SPD2010(带触摸))
一、介绍
1.1 QSPI协议
SPI协议其实是包括:Standard SPI、Dual SPI和Queued SPI三种协议接口,分别对应3-wire, 4-wire, 6-wire。
- 通常我们说的SPI就是Standard SPI,有4根信号线,分别为CLK、CS、MOSI和MISO。数据线工作在全双工。
- Dual SPI,它只是针对SPI Flash而言,不是针对所有SPI外设。对于SPI Flash,全双工并不常用,因此扩展了mosi和miso的用法,让它们工作在半双工,用以加倍数据传输。也就是对于Dual SPI Flash,可以发送一个命令字节进入dual mode,这样mosi变成SIO0(serial io 0),mosi变成SIO1(serial io 1),这样一个时钟周期内就能传输2个bit数据,加倍了数据传输。
- 类似的,还可以扩展,与也是针对SPI Flash,Qual SPI Flash增加了两根I/O线(SIO2,SIO3),目的是一个时钟内传输4个bit而QSPI就是Queued SPI的简写。
1.2 硬件介绍:
屏幕使用华夏彩光电 1.46寸412*412高清圆屏智能穿戴家电旋钮QSPI、MIPI接口触摸屏
参数:
接口定义:
主控使用ESP32-S3,开发板为合宙ESP32S3开发板
二、移植过程
2.1 新建工程并添加依赖
新建工程,添加依赖的组件库,分别是:lvgl, esp_lcd_spd2010, esp_lcd_touch_spd2010
bash
idf.py add-dependency "lvgl/lvgl^8.3.11"
idf.py add-dependency "espressif/esp_lcd_spd2010^1.0.1"
idf.py add-dependency "espressif/esp_lcd_touch_spd2010^0.0.1"
2.2 硬件接线
接线对应关系如图所示:
bash
// 引脚配置
// ESP Board SPD1020 Panel (QSPI)
// ┌──────────────────────┐ ┌────────────────────┐
// │ GND ├─────────────►│ GND │
// │ │ │ │
// │ 3V3 ├─────────────►│ VCC │
// │ │ │ │
// │ CS ├─────────────►│ CS │
// │ │ │ │
// │ SCK ├─────────────►│ CLK │
// │ │ │ │
// │ D3 ├─────────────►│ IO3 │
// │ │ │ │
// │ D2 ├─────────────►│ IO2 │
// │ │ │ │
// │ D1 ├─────────────►│ IO1 │
// │ │ │ │
// │ D0 ├─────────────►│ IO0 │
// │ │ │ │
// │ RST ├─────────────►│ RSTN │
// │ │ │ │
// │ (SCL) ├─────────────►│ TP_SCL │
// │ │ │ │
// │ (SDA) ├─────────────►│ TP_SDA │
// │ │ │ │
// │ (TP_INT) ├─────────────►│ TP_INT │
// │ │ │ │
// │ (3V3) ├─────────────►│ TP_RST │
// │ │ │ │
// └──────────────────────┘ └────────────────────┘
实际引脚配置见下节:
2.3 配置屏幕
导入头文件
c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "driver/spi_master.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_err.h"
#include "esp_log.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "esp_lcd_spd2010.h"
#include "esp_lcd_touch_spd2010.h"
选择显示SPI和触摸I2C的控制器
c
// LCD和触摸控制器配置
#define LCD_HOST SPI2_HOST
#define TOUCH_HOST I2C_NUM_0
定义分辨率
c
// LCD分辨率
#define EXAMPLE_LCD_H_RES 400
#define EXAMPLE_LCD_V_RES 400
定义引脚接线
c
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 0
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_LCD_CS (GPIO_NUM_9)
#define EXAMPLE_PIN_NUM_LCD_PCLK (GPIO_NUM_10)
#define EXAMPLE_PIN_NUM_LCD_DATA0 (GPIO_NUM_11)
#define EXAMPLE_PIN_NUM_LCD_DATA1 (GPIO_NUM_12)
#define EXAMPLE_PIN_NUM_LCD_DATA2 (GPIO_NUM_13)
#define EXAMPLE_PIN_NUM_LCD_DATA3 (GPIO_NUM_14)
#define EXAMPLE_PIN_NUM_LCD_RST (GPIO_NUM_4)
#define EXAMPLE_PIN_NUM_BK_LIGHT (GPIO_NUM_3)
// 触摸引脚配置
#define EXAMPLE_PIN_NUM_TOUCH_SCL (GPIO_NUM_18)
#define EXAMPLE_PIN_NUM_TOUCH_SDA (GPIO_NUM_8)
#define EXAMPLE_PIN_NUM_TOUCH_RST (GPIO_NUM_7)
#define EXAMPLE_PIN_NUM_TOUCH_INT (GPIO_NUM_17)
配置背光(main)
c
// 背光引脚配置
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
// 打开背光
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
配置SPI总线(main)
按照屏幕驱动库里的SPI配置函数,配置SPI总线:
c
ESP_LOGI(TAG, "Initialize SPI bus");
const spi_bus_config_t buscfg = SPD2010_PANEL_BUS_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_PCLK, EXAMPLE_PIN_NUM_LCD_DATA0,EXAMPLE_PIN_NUM_LCD_DATA1, EXAMPLE_PIN_NUM_LCD_DATA2,EXAMPLE_PIN_NUM_LCD_DATA3,EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * LCD_BIT_PER_PIXEL / 8);
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
创建屏幕IO句柄(main)
屏幕IO句柄用于给屏幕发送指令和数据。
c
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_handle_t io_handle = NULL;
const esp_lcd_panel_io_spi_config_t io_config = SPD2010_PANEL_IO_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_CS,example_notify_lvgl_flush_ready,&disp_drv);
notify_lvgl_flush_ready
是一个回调函数,用于颜色传输完成被调用。
c
// 回调函数:颜色传输完成 (通知LVGL刷新)
static bool notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
lv_disp_flush_ready(disp_driver);
return false;
}
我们在这个回调函数里通知LVGL刷新
disp_drv
用于给回调函数传参:
c
static lv_disp_drv_t disp_drv; // contains callback functions
将LCD连接至SPI(main)
c
spd2010_vendor_config_t vendor_config = {
.flags = {
.use_qspi_interface = 1,
},
};
// Attach the LCD to the SPI bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_config,
};
配置屏幕驱动(main):
c
ESP_LOGI(TAG, "Install SPD2010 panel driver");
ESP_ERROR_CHECK(esp_lcd_new_panel_spd2010(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// user can flush pre-defined pattern to the screen before we turn on the screen or backlight
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
到这里屏幕就应该点亮了。
2.4 配置触摸
初始I2C总线(main)
c
ESP_LOGI(TAG, "Initialize I2C bus");
const i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EXAMPLE_PIN_NUM_TOUCH_SDA,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = EXAMPLE_PIN_NUM_TOUCH_SCL,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 400 * 1000,
};
ESP_ERROR_CHECK(i2c_param_config(TOUCH_HOST, &i2c_conf));
ESP_ERROR_CHECK(i2c_driver_install(TOUCH_HOST, i2c_conf.mode, 0, 0, 0));
这里的上拉不一定有效,若调试阶段发现I2C无法通信,可以尝试外加上拉电阻。
创建触摸IO句柄(main)
c
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_SPD2010_CONFIG();
const esp_lcd_touch_config_t tp_cfg = {
.x_max = EXAMPLE_LCD_H_RES,
.y_max = EXAMPLE_LCD_V_RES,
.rst_gpio_num = EXAMPLE_PIN_NUM_TOUCH_RST,
.int_gpio_num = EXAMPLE_PIN_NUM_TOUCH_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
.interrupt_callback = example_touch_isr_cb,
};
interrupt_callback
是触屏回调函数,如下:
c
static void example_touch_isr_cb(esp_lcd_touch_handle_t tp)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(touch_mux, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
配置触摸驱动
c
// Attach the TOUCH to the I2C bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TOUCH_HOST, &tp_io_config, &tp_io_handle));
// 初始化触摸驱动
ESP_LOGI(TAG, "Initialize touch controller SPD2010");
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_spd2010(tp_io_handle, &tp_cfg, &tp));
2.5 配置LVGL
初始化LVGL:
c
// 初始化LVGL
ESP_LOGI(TAG, "Initialize LVGL");
lv_init();
// 申请内存 两个buf
lv_color_t *buf1 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 50 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1); // 检查内存是否申请成功
lv_color_t *buf2 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 50 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf2);
// 初始化LVGL显示缓冲区
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LCD_H_RES * 50);
注册LVGL显示驱动:
c
// 初始化LVGL显示驱动
ESP_LOGI(TAG, "Initialize LVGL display driver");
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = EXAMPLE_LCD_H_RES; // 设置屏幕水平分辨率
disp_drv.ver_res = EXAMPLE_LCD_V_RES; // 设置屏幕垂直分辨率
disp_drv.flush_cb = lvgl_flush_cb; // 设置刷新回调函数
disp_drv.draw_buf = &disp_buf; // 设置显示缓冲区
disp_drv.user_data = panel_handle; // 设置用户数据
lv_disp_t *disp = lv_disp_drv_register(&disp_drv); // 注册显示驱动
c
// 回调函数:刷新屏幕
static void lvgl_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;
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
/* 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, color_map);
}
注册LVGL触摸驱动:
c
static lv_indev_drv_t indev_drv; // Input device driver (Touch)
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.disp = disp;
indev_drv.read_cb = example_lvgl_touch_cb;
indev_drv.user_data = tp;
lv_indev_drv_register(&indev_drv);
example_lvgl_touch_cb
为触屏回调,如下:
c
// 触屏信号量
static SemaphoreHandle_t touch_mux = NULL;
// lvgl触摸回调函数
static void example_lvgl_touch_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
esp_lcd_touch_handle_t tp = (esp_lcd_touch_handle_t)drv->user_data;
assert(tp);
uint16_t tp_x;
uint16_t tp_y;
uint8_t tp_cnt = 0;
/* Read data from touch controller into memory */
if (xSemaphoreTake(touch_mux, 0) == pdTRUE) {
esp_lcd_touch_read_data(tp);
}
/* Read data from touch controller */
bool tp_pressed = esp_lcd_touch_get_coordinates(tp, &tp_x, &tp_y, NULL, &tp_cnt, 1);
if (tp_pressed && tp_cnt > 0) {
data->point.x = tp_x;
data->point.y = tp_y;
data->state = LV_INDEV_STATE_PRESSED;
ESP_LOGI(TAG, "Touch position: %d,%d", tp_x, tp_y);
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
创建LVGL定时器及任务:
c
// 创建定时器
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, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));
// 创建LVGL任务
ESP_LOGI(TAG, "Create LVGL task");
lvgl_mux = xSemaphoreCreateMutex();
assert(lvgl_mux);
xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
c
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
// LVGL任务参数
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1
#define EXAMPLE_LVGL_TASK_STACK_SIZE (5 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY 2
c
static bool example_lvgl_lock(int timeout_ms)
{
assert(lvgl_mux && "bsp_display_start must be called first");
const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xSemaphoreTake(lvgl_mux, timeout_ticks) == pdTRUE;
}
static void example_lvgl_unlock(void)
{
assert(lvgl_mux && "bsp_display_start must be called first");
xSemaphoreGive(lvgl_mux);
}
// LVGL任务
static void example_lvgl_port_task(void *arg)
{
ESP_LOGI(TAG, "Starting LVGL task");
ESP_LOGI(TAG, "Display LVGL UI");
//test_ui();
lv_demo_widgets();
uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
while (1) {
ESP_LOGI(TAG, "LVGL task");
/* Lock the mutex due to the LVGL APIs are not thread-safe */
if (example_lvgl_lock(-1)) {
task_delay_ms = lv_timer_handler();
/* Release the mutex */
example_lvgl_unlock();
}
if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS) {
task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
} else if (task_delay_ms < EXAMPLE_LVGL_TASK_MIN_DELAY_MS) {
task_delay_ms = EXAMPLE_LVGL_TASK_MIN_DELAY_MS;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
}
三、最终代码
代码仓库:https://github.com/DuRuofu/ESP32_Learning/tree/master/09.gui/lvgl_transplant_spd2010
c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "driver/spi_master.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_err.h"
#include "esp_log.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "esp_lcd_spd2010.h"
#include "esp_lcd_touch_spd2010.h"
static const char *TAG = "main";
static SemaphoreHandle_t lvgl_mux = NULL;
// LCD和触摸控制器配置
#define LCD_HOST SPI2_HOST
#define TOUCH_HOST I2C_NUM_0
// 颜色深度
#define LCD_BIT_PER_PIXEL (16)
// 引脚配置
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 0
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_LCD_CS (GPIO_NUM_9)
#define EXAMPLE_PIN_NUM_LCD_PCLK (GPIO_NUM_10)
#define EXAMPLE_PIN_NUM_LCD_DATA0 (GPIO_NUM_11)
#define EXAMPLE_PIN_NUM_LCD_DATA1 (GPIO_NUM_12)
#define EXAMPLE_PIN_NUM_LCD_DATA2 (GPIO_NUM_13)
#define EXAMPLE_PIN_NUM_LCD_DATA3 (GPIO_NUM_14)
#define EXAMPLE_PIN_NUM_LCD_RST (GPIO_NUM_4)
#define EXAMPLE_PIN_NUM_BK_LIGHT (GPIO_NUM_3)
// 触摸引脚配置
#define EXAMPLE_PIN_NUM_TOUCH_SCL (GPIO_NUM_18)
#define EXAMPLE_PIN_NUM_TOUCH_SDA (GPIO_NUM_8)
#define EXAMPLE_PIN_NUM_TOUCH_RST (GPIO_NUM_7)
#define EXAMPLE_PIN_NUM_TOUCH_INT (GPIO_NUM_17)
esp_lcd_touch_handle_t tp = NULL;
// LCD分辨率
#define EXAMPLE_LCD_H_RES 412
#define EXAMPLE_LCD_V_RES 412
// LVGLtask配置
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1
#define EXAMPLE_LVGL_TASK_STACK_SIZE (4 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY 2
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_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
lv_disp_flush_ready(disp_driver);
return false;
}
static void example_lvgl_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;
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
// 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, color_map);
}
/* Rotate display and touch, when rotated screen in LVGL. Called when driver parameters are updated. */
static void example_lvgl_update_cb(lv_disp_drv_t *drv)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
switch (drv->rotated) {
case LV_DISP_ROT_NONE:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, false);
esp_lcd_panel_mirror(panel_handle, true, false);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
break;
case LV_DISP_ROT_90:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, true, true);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
break;
case LV_DISP_ROT_180:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, false);
esp_lcd_panel_mirror(panel_handle, false, true);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
break;
case LV_DISP_ROT_270:
// Rotate LCD display
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, false, false);
// Rotate LCD touch
esp_lcd_touch_set_mirror_y(tp, false);
esp_lcd_touch_set_mirror_x(tp, false);
break;
}
}
void example_lvgl_rounder_cb(struct _lv_disp_drv_t *disp_drv, lv_area_t *area)
{
uint16_t x1 = area->x1;
uint16_t x2 = area->x2;
// round the start of coordinate down to the nearest 4M number
area->x1 = (x1 >> 2) << 2;
// round the end of coordinate up to the nearest 4N+3 number
area->x2 = ((x2 >> 2) << 2) + 3;
}
// 触屏信号量
static SemaphoreHandle_t touch_mux = NULL;
// lvgl触摸回调函数
static void example_lvgl_touch_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
esp_lcd_touch_handle_t tp = (esp_lcd_touch_handle_t)drv->user_data;
assert(tp);
uint16_t tp_x;
uint16_t tp_y;
uint8_t tp_cnt = 0;
/* Read data from touch controller into memory */
if (xSemaphoreTake(touch_mux, 0) == pdTRUE) {
esp_lcd_touch_read_data(tp);
}
/* Read data from touch controller */
bool tp_pressed = esp_lcd_touch_get_coordinates(tp, &tp_x, &tp_y, NULL, &tp_cnt, 1);
if (tp_pressed && tp_cnt > 0) {
data->point.x = tp_x;
data->point.y = tp_y;
data->state = LV_INDEV_STATE_PRESSED;
ESP_LOGI(TAG, "Touch position: %d,%d", tp_x, tp_y);
} else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
static void example_touch_isr_cb(esp_lcd_touch_handle_t tp)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(touch_mux, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
// LVGL任务
static void example_increase_lvgl_tick(void *arg)
{
/* Tell LVGL how many milliseconds has elapsed */
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
static bool example_lvgl_lock(int timeout_ms)
{
assert(lvgl_mux && "bsp_display_start must be called first");
const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
return xSemaphoreTake(lvgl_mux, timeout_ticks) == pdTRUE;
}
static void example_lvgl_unlock(void)
{
assert(lvgl_mux && "bsp_display_start must be called first");
xSemaphoreGive(lvgl_mux);
}
// LVGL任务
static void example_lvgl_port_task(void *arg)
{
ESP_LOGI(TAG, "Starting LVGL task");
uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
while (1) {
ESP_LOGI(TAG, "LVGL task");
// Lock the mutex due to the LVGL APIs are not thread-safe
if (example_lvgl_lock(-1)) {
task_delay_ms = lv_timer_handler();
// Release the mutex
example_lvgl_unlock();
}
if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS) {
task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
} else if (task_delay_ms < EXAMPLE_LVGL_TASK_MIN_DELAY_MS) {
task_delay_ms = EXAMPLE_LVGL_TASK_MIN_DELAY_MS;
}
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
}
void app_main(void)
{
static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
static lv_disp_drv_t disp_drv; // contains callback functions
// 背光引脚配置
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
ESP_LOGI(TAG, "Initialize SPI bus");
const spi_bus_config_t buscfg = SPD2010_PANEL_BUS_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_PCLK,
EXAMPLE_PIN_NUM_LCD_DATA0,
EXAMPLE_PIN_NUM_LCD_DATA1,
EXAMPLE_PIN_NUM_LCD_DATA2,
EXAMPLE_PIN_NUM_LCD_DATA3,
EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * LCD_BIT_PER_PIXEL / 8);
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
ESP_LOGI(TAG, "Install panel IO");
esp_lcd_panel_io_handle_t io_handle = NULL;
const esp_lcd_panel_io_spi_config_t io_config = SPD2010_PANEL_IO_QSPI_CONFIG(EXAMPLE_PIN_NUM_LCD_CS,
example_notify_lvgl_flush_ready,
&disp_drv);
spd2010_vendor_config_t vendor_config = {
.flags = {
.use_qspi_interface = 1,
},
};
// Attach the LCD to the SPI bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = LCD_BIT_PER_PIXEL,
.vendor_config = &vendor_config,
};
ESP_LOGI(TAG, "Install SPD2010 panel driver");
ESP_ERROR_CHECK(esp_lcd_new_panel_spd2010(io_handle, &panel_config, &panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
// user can flush pre-defined pattern to the screen before we turn on the screen or backlight
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
// 初始化I2C总线
touch_mux = xSemaphoreCreateBinary();
assert(touch_mux);
ESP_LOGI(TAG, "Initialize I2C bus");
const i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EXAMPLE_PIN_NUM_TOUCH_SDA,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = EXAMPLE_PIN_NUM_TOUCH_SCL,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 400 * 1000,
};
ESP_ERROR_CHECK(i2c_param_config(TOUCH_HOST, &i2c_conf));
ESP_ERROR_CHECK(i2c_driver_install(TOUCH_HOST, i2c_conf.mode, 0, 0, 0));
// 触屏驱动IO句柄
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_SPD2010_CONFIG();
const esp_lcd_touch_config_t tp_cfg = {
.x_max = EXAMPLE_LCD_H_RES,
.y_max = EXAMPLE_LCD_V_RES,
.rst_gpio_num = EXAMPLE_PIN_NUM_TOUCH_RST,
.int_gpio_num = EXAMPLE_PIN_NUM_TOUCH_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
.interrupt_callback = example_touch_isr_cb,
};
// Attach the TOUCH to the I2C bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TOUCH_HOST, &tp_io_config, &tp_io_handle));
// 初始化触摸驱动
ESP_LOGI(TAG, "Initialize touch controller SPD2010");
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_spd2010(tp_io_handle, &tp_cfg, &tp));
// 打开背光
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
// 初始化LVGL
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
// 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
lv_color_t *buf1 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 10 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf1);
lv_color_t *buf2 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 10 * sizeof(lv_color_t), MALLOC_CAP_DMA);
assert(buf2);
// initialize LVGL draw buffers
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LCD_H_RES * 10);
ESP_LOGI(TAG, "Register display driver to LVGL");
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = EXAMPLE_LCD_H_RES;
disp_drv.ver_res = EXAMPLE_LCD_V_RES;
disp_drv.flush_cb = example_lvgl_flush_cb;
disp_drv.rounder_cb = example_lvgl_rounder_cb;
disp_drv.drv_update_cb = example_lvgl_update_cb;
disp_drv.draw_buf = &disp_buf;
disp_drv.user_data = panel_handle;
lv_disp_t *disp = lv_disp_drv_register(&disp_drv);
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, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));
static lv_indev_drv_t indev_drv; // Input device driver (Touch)
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.disp = disp;
indev_drv.read_cb = example_lvgl_touch_cb;
indev_drv.user_data = tp;
lv_indev_drv_register(&indev_drv);
lvgl_mux = xSemaphoreCreateMutex();
assert(lvgl_mux);
xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
ESP_LOGI(TAG, "Display LVGL demos");
// Lock the mutex due to the LVGL APIs are not thread-safe
if (example_lvgl_lock(-1)) {
lv_demo_widgets(); /* A widgets example */
//lv_demo_music(); /* A modern, smartphone-like music player demo. */
// lv_demo_stress(); /* A stress test for LVGL. */
// lv_demo_benchmark(); /* A demo to measure the performance of LVGL or to compare different settings. */
// Release the mutex
example_lvgl_unlock();
}
}
效果展示: