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")
  • 第二步,使用官方组件进行触摸配置初始化

    • 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)函数

示例

#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();
}
posted @ 2026-01-23 17:51  博客侦探  阅读(93)  评论(0)    收藏  举报