ESP32保姆级实战开发
1 文件系统
2 屏幕触控
下图是一个简单的结构示意图:

代码程序流程:

主要还是使用ESP32官方提供的组件(需要下载):managed_components\espressif__esp_lcd_touch_ft5x06\esp_lcd_touch_ft5x06.c。
-
第一步,I2C外设通信接口初始化
- 注意,ESP32的I2C外设初始化似乎分为两版——旧版(
"driver/i2c.h")与新版("driver/i2c_master.h")
- 注意,ESP32的I2C外设初始化似乎分为两版——旧版(
-
第二步,使用官方组件进行触摸配置初始化
- 2.1 触摸控制io权柄
- 配置结构体:
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); - 使用
esp_lcd_new_panel_io_i2c()函数
- 配置结构体:
- 2.2 触摸控制权柄
- 配置触摸结构体:
esp_lcd_touch_config_t touch_cfg - 使用
esp_err_t esp_lcd_touch_new_i2c_ft5x06(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch)函数
- 配置触摸结构体:
- 2.1 触摸控制io权柄
示例:
#include "esp_log.h"
#include "esp_err.h"
#include "esp_lcd_io_i2c.h"
#include "driver/i2c.h" // 旧的I2C驱动
#include "driver/i2c_master.h" // 新的I2C驱动
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_lcd_touch_ft5x06.h"
#include "bsp_lcd_driver.h"
#include "bsp_lcd_touch.h"
#define USE_LVGL 1
esp_err_t initI2c0(void);
esp_err_t initI2c0Old(void);
void _touch_interrupt_callback(esp_lcd_touch_handle_t tp);
// I2C主机总线
i2c_master_bus_handle_t i2c0_bus_handle = NULL;
// 触控权柄(这是个指针变量?)
static esp_lcd_touch_handle_t _tp = NULL;
/**
* @brief 初始化屏幕触摸功能
*/
void initLcdTouch(void)
{
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG();
// 1. I2C0通信接口初始化(新版驱动)
initI2c0();
// 2. 触摸配置初始化
esp_lcd_touch_config_t touch_cfg = {
// 屏幕竖屏转横屏,需要交换xy轴
.x_max = BSP_LCD_Y_V_AXIS, // 这里是x轴,但是旋转屏幕为y轴
.y_max = BSP_LCD_X_H_AXIS, // 这里是y轴,但是旋转屏幕为x轴
.rst_gpio_num = GPIO_NUM_NC, // 复位引脚
.int_gpio_num = BSP_TOUCH_INT, // 输入中断引脚,用于唤醒,或休眠
#if USE_LVGL
.interrupt_callback = NULL,
.user_data = NULL,
#else
.interrupt_callback = _touch_interrupt_callback, // 通过 @int_gpio_num 触摸后中断回调
.user_data = xSemaphoreCreateBinary(), // @用户自定义数据,_touch_interrupt_callback 内释放信号量
#endif
.levels = {
.reset = 0, // 复位电平 @rst_gpio_num 未使用
.interrupt = 0, // 输入中断电平@int_gpio_num
},
.flags = {
.swap_xy = 1, // 交换xy轴
.mirror_x = 1, // x轴镜像
.mirror_y = 0, // y轴镜像
},
};
// 3.生成触摸控制io权柄
tp_io_config.scl_speed_hz = 100000;//100k
esp_lcd_new_panel_io_i2c(i2c0_bus_handle, &tp_io_config, &tp_io_handle);
// 4.生成触摸控制权柄
esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &touch_cfg, &_tp);
}
/**
* @brief 初始化I2C0外设(新的I2C驱动)
*/
esp_err_t initI2c0(void)
{
if(i2c0_bus_handle != NULL)
return ESP_OK;
i2c_master_bus_config_t bus_config ={
.clk_source = I2C_CLK_SRC_DEFAULT, // I2C的时钟源
.flags.enable_internal_pullup = 1, // 启用内部上拉电阻
.i2c_port = TOUCH_PORT_NUM, // 使用的I2C第0个外设
.scl_io_num = TOUCH_SCL_IO,
.sda_io_num = TOUCH_SDA_IO,
};
i2c_new_master_bus(&bus_config, &i2c0_bus_handle);
return ESP_OK;
}
/**
* @brief 初始化I2C0外设(旧的I2C驱动)
*/
esp_err_t initI2c0Old(void)
{
//旧的i2c驱动,已抛弃
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = TOUCH_SDA_IO, // 数据线
.sda_pullup_en = GPIO_PULLUP_DISABLE, // GPIO_PULLUP_ENABLE
.scl_io_num = TOUCH_SCL_IO, // 时钟线
.scl_pullup_en = GPIO_PULLUP_DISABLE, // GPIO_PULLUP_ENABLE
.master.clk_speed = TOUCH_FREQ_HZ, // 通信速率
.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL};
// 1.配置参数
esp_err_t err = i2c_param_config(TOUCH_PORT_NUM, &conf);
if (err != ESP_OK)
return err;
// 2.安装驱动
return i2c_driver_install(TOUCH_PORT_NUM, I2C_MODE_MASTER, 0, 0, 0);
}
/**
* @brief 获取触摸权柄
*/
esp_lcd_touch_handle_t getToucHandle(void)
{
return _tp;
}
/**
* @brief 触摸后的中断回调函数
* @param tp 触摸权柄
*/
void _touch_interrupt_callback(esp_lcd_touch_handle_t tp)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
SemaphoreHandle_t xSemaphore = (SemaphoreHandle_t)tp->config.user_data;
if (xSemaphore != NULL)//释放当前触摸中断
{
// 释放信号量
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
}
}
/**
* @brief 获取触摸坐标
* @param x 横坐标
* @param y 纵坐标
*/
void getTouchCoord(uint16_t* x, uint16_t* y)
{
if(xSemaphoreTake((SemaphoreHandle_t)_tp->config.user_data, portMAX_DELAY) == pdTRUE) // 接收到触摸信号
{
uint8_t touch_cnt = 0;
// 读取面板数据
esp_lcd_touch_read_data(_tp);
// 获取坐标
esp_lcd_touch_get_coordinates(_tp, x, y, NULL, &touch_cnt, 1);
}
}
一种触摸控制逻辑:

3 LVGL移植
3.1 LVGL移植方法介绍


- 第一步,为ESP32安装LVGL的相关组件
# 进入ESP-IDF终端内并依次输入下列指令,将自动下载组件:
# 1.添加LVGL组件
idf.py add-dependency "lvgl/lvgl^9.3.0"
# 2.添加LVGL移植接口
idf.py add-dependency "espressif/esp_lvgl_port^2.6.0"
# 3.下载依赖
idf.py menuconfig
- 第二步,初始化LVGL接口,使用
esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg)函数,示例用法如下:
lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
lvgl_port_init(&lvgl_cfg);
-
第三步,设置LVGL的屏幕显示接口
- 3.1 首先,初始化LCD(ST7789芯片)的硬件底层显示
- 3.2 其次,获取硬件LCD初始化之后的权柄,包括① 面板权柄,② 面板IO权柄
- 3.3 最后,向LVGL注册显示屏幕,使用
lv_display_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg)函数
-
第四步,设置LVGL的屏幕输入(触摸)接口
- 4.1 首先,触摸(FT6336U芯片)接口底层初始化
- 4.2 其次,获取触摸初始化后的权柄
- 4.3 最后,向LVGL注册触摸,使用
lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg)函数
-
第五步,提供一个定时器给LVGL使用
- 5.1 解释原因:LVGL中支持很多动态效果,这些动态效果是要用到定时器的时间。
-
第六步,使用LVGL的函数创建各自显示组件
- 比如:按钮、文本框、进度条等组件
3.2 LVGL移植示例
#include "lvgl.h"
#include "esp_lvgl_port.h"
#include "bsp_lcd_brightness.h"
#include "bsp_lcd_driver.h"
#include "bsp_lcd_touch.h"
#include "lvgl_port.h"
static void initLvglDispInterface(void);
static void initLvglTouchInterface(void);
// LVGL显示权柄
static lv_disp_t* _lv_disp = NULL;
// LVGL触摸权柄
static lv_indev_t* _lv_indev = NULL;
/**
* @brief 初始化LVGL
*/
void initLvglDisp(void)
{
// 1. 初始化LVGL接口(这里是为了创建一个LVGL的任务???)
lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
lvgl_port_init(&lvgl_cfg);
// 2. 屏幕的三大部分初始化
initLcdBrightness(); // LCD亮度初始化
initLvglDispInterface(); // LCD显示接口
initLvglTouchInterface(); // LCD触摸接口
}
/**
* @brief 注册LVGL的LCD显示接口
*/
static void initLvglDispInterface(void)
{
// 1. LCD显示接口底层初始化
initLcd();
// 2. 获取LCD初始化之后的权柄
esp_lcd_panel_handle_t panel_handle = getPanel();
esp_lcd_panel_io_handle_t panel_io_handle = getPanelIo();
// 3. 注册LVGL显示屏幕
lvgl_port_display_cfg_t disp_cfg = {
.panel_handle = panel_handle,
.io_handle = panel_io_handle,
.buffer_size = (BSP_LCD_X_H_AXIS/10) * (BSP_LCD_Y_V_AXIS), // 缓存大小:屏幕像素点个数的1/10
.double_buffer = true, // 使用双缓冲区
.hres = BSP_LCD_X_H_AXIS,
.vres = BSP_LCD_Y_V_AXIS,
.monochrome = false, // 关闭单色模式
.rotation = {
.mirror_x = false,
.mirror_y = false,
.swap_xy = true, // 横竖屏翻转
},
.flags = {
.swap_bytes = true, // 将rgb转换位bgr
.buff_dma = false,
.buff_spiram = false,
}
};
_lv_disp = lvgl_port_add_disp(&disp_cfg);
}
/**
* @brief 注册LVGL的LCD触摸接口
*/
static void initLvglTouchInterface(void)
{
// 1. 触摸接口底层初始化
initLcdTouch();
// 2. 获取触摸初始化后的权柄
esp_lcd_touch_handle_t tp = getToucHandle();
// 3. lvgl接口初始化
lvgl_port_touch_cfg_t touch_cfg = {
.disp = _lv_disp,
.handle = tp,
};
_lv_indev = lvgl_port_add_touch(&touch_cfg);
}
/**
* @brief 获取屏幕的旋转角度
* @return 返回屏幕旋转角度
*/
lv_display_rotation_t getLcdRotation(void)
{
return lv_disp_get_rotation(_lv_disp);
}
/**
* @brief 获取屏幕的旋转角度
* @param rotation 屏幕的旋转角度
*/
void setLcdRotation(lv_display_rotation_t rotation)
{
lv_disp_set_rotation(_lv_disp, rotation);
}
下面是第五步的示例:
void lv_timer_cb(void* arg)
{
// 1. 把传入的void*类型参数强制转换成uint32_t类型,拿到定时器的时间间隔值
uint32_t tick_interval = (uint32_t)arg;
// 2. 调用LVGL的核心函数,告诉LVGL“时间增加了tick_interval毫秒”
lv_tick_inc(tick_interval);
}
void lv_tick_init(void)
{
// 1. 定义定时器的时间间隔为5ms
static uint32_t tick_interval = 5;
// 2. 配置ESP32定时器的参数结构体
const esp_timer_create_args_t arg = {
.arg = tick_interval, // 传给回调函数的参数
.callback = lv_timer_cb, // 定时器触发时要执行的函数(就是上面的lv_timer_cb)
.name = "", // 定时器名称(可选,空也可以)
.dispatch_method = ESP_TIMER_TASK, // 回调函数的执行方式:在ESP_TIMER任务中执行
.skip_unhandled_events = true, // 若回调没处理完,跳过未处理的触发事件(避免堆积)
};
// 3. 定义定时器句柄(相当于定时器的“身份证”,用来操作定时器)
esp_timer_handle_t timer_handle;
// 4. 根据上面的参数创建定时器
esp_timer_create(&arg, &timer_handle);
// 5. 启动周期性定时器,周期是 tick_interval*1000 微秒(5*1000=5000微秒=5毫秒)
esp_timer_start_periodic(timer_handle, tick_interval*1000);
}
下面是第六步的示例:
/**
* @brief 按键的信号槽函数,作用将屏幕旋转90度
*/
static void _app_button_cb(lv_event_t *e)
{
// 获取屏幕当前的旋转角度
lv_disp_rotation_t rotation = getLcdRotation();
// 将角度+90
rotation++;
if (rotation > LV_DISPLAY_ROTATION_270)
{
rotation = LV_DISPLAY_ROTATION_0;
}
// 设置旋转后的屏幕角度
setLcdRotation(rotation);
}
static void test_lvgl_disp(void)
{
// 获取当前的屏幕
lv_obj_t* screen = lv_screen_active();
// 锁住互斥锁
lvgl_port_lock(0);
// 创建标签 Label,父对象为screen
lv_obj_t *label = lv_label_create(screen);
// 允许设置标签内的颜色
lv_label_set_recolor(label, true);
// 设置Label的宽度,若Label中文本的宽度超过设置宽度则会自动换行
lv_obj_set_width(label, 300);
// 设置Label中文本的对齐方式
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
// 设置显示文本(多行文本 + 颜色标签 + 图标)
lv_label_set_text(label, "#ff002f " LV_SYMBOL_BATTERY_FULL " Hello world Espressif and LVGL " LV_SYMBOL_WARNING " # For:\nsimplier initialization\n"
"use BSP " LV_SYMBOL_GPS);
// 设置Label在父类screen中的对齐方式
lv_obj_align(label, LV_ALIGN_CENTER, 0, -30); // 对齐方式
// 创建按钮 Button,父对象是screen
lv_obj_t *btn = lv_button_create(screen);
// 创建Label,父对象是Button
label = lv_label_create(btn);
// 设置Label中的文本
lv_label_set_text_static(label, "Rotate screen");
// 设置按钮在屏幕中的对齐方式
lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -30); // 对齐方式
// 设置信号槽,类似QT,定义点击按钮后执行的功能
lv_obj_add_event_cb(btn, _app_button_cb, LV_EVENT_CLICKED, NULL); // 信号槽,类 QT
// 释放互斥锁
lvgl_port_unlock();
}


ESP32S3的实战项目
浙公网安备 33010602011771号