Part1 无缝移植 EEZ Studio 生成的 UI 工程

在成功驱动屏幕并移植好 LVGL 之后,下一个关键步骤就是将我们在 EEZ Studio 中精心设计的 UI 界面,真正地运行在 ESP32 硬件上。本文将提供一份清晰、完整的指南,帮助你完成从 EEZ Studio 到 ESP-IDF 工程的移植工作。

一、移植前的准备工作

重要提示: 请确保你的 ESP32 工程已经成功驱动了显示屏幕,并完成了 LVGL 库的移植。本教程的所有操作都基于这两个前提,否则无法进行。

二、开始移植

我们将通过创建一个自定义组件的方式,将 EEZ 生成的 UI 文件优雅地集成到 ESP-IDF 工程结构中。

1. 创建 UI 组件

在 ESP-IDF 工程的 components 目录下,使用以下命令创建一个名为 ui 的新组件:

# 在工程根目录下打开终端,或使用 ESP-IDF 插件提供的命令创建
idf.py create-component ui

或者,如果你使用的是 VS Code 的 ESP-IDF 插件,可以通过命令面板执行 ESP-IDF: Create New ESPIDF-Component

2. 清理组件目录

创建完成后,进入 components/ui 文件夹,删除该目录下自动生成的 ui.cinclude 文件夹,只保留 CMakeLists.txt 文件。

3. 修改 CMakeLists.txt

打开 components/ui/CMakeLists.txt,将其内容替换为以下配置。这告诉编译系统如何构建我们的 UI 组件,并声明它依赖于 lvglesp_lvgl_port

idf_component_register(
    SRC_DIRS "."          # 源文件就在当前目录
    INCLUDE_DIRS "."       # 头文件也在当前目录
    REQUIRES lvgl esp_lvgl_port # 声明依赖
)

4. 复制 EEZ Studio 生成的文件

现在,将你在 EEZ Studio 中生成的所有 UI 文件(通常包括 ui.c, ui.h, screens.c,以及可能存在的资源文件等),全部复制到 components/ui/ 目录下。

小贴士: 如果你的开发环境是 Windows(运行 EEZ Studio)和 Linux(编写 ESP-IDF)分离的,手动复制是最直接的方式。你也可以考虑在 EEZ Studio 中设置输出路径到共享文件夹,但手动操作更简单可控。

移植完成后的文件结构应该类似于:

your_project/
├── components/
│   └── ui/
│       ├── CMakeLists.txt
│       ├── ui.c
│       ├── ui.h
│       ├── screens.c
│       ├── images/       (如果生成了图片文件夹)
│       │   └── ...
│       └── ...
├── main/
│   ├── CMakeLists.txt
│   ├── main.c
│   └── ...
└── ...

5. 在 main 组件中引用 UI 组件

为了让 main 组件能够找到并使用我们新建的 ui 组件,需要修改两处地方:

5.1 修改 main 组件的 CMakeLists.txt

打开 main/CMakeLists.txt,在 REQUIRES 列表中添加 ui,确保 main 组件在链接时能找到 ui 组件的符号。

idf_component_register(
    SRCS "main.c"
    INCLUDE_DIRS "."
    REQUIRES ui          # 添加 ui 依赖
)

5.2 在 main.c 中包含头文件

main/main.c 文件中,添加必要的头文件引用:

#include "ui.h"              // EEZ Studio 生成的 UI 主头文件
#include "esp_lvgl_port.h"   // ESP32 的 LVGL 端口,用于线程安全操作

6. 初始化 UI

UI 的初始化必须在 LCD 初始化完成并且 LVGL 就绪之后进行。为了线程安全,所有 LVGL 相关的操作(包括创建 UI 对象)都应放在 lvgl_port_locklvgl_port_unlock 之间执行。

main.c 中,找到初始化 LCD 和 LVGL 之后的代码位置,添加以下内容:

// ... 前面是 LCD 和 LVGL 初始化代码 ...

// 初始化 UI 组件(在 LVGL 锁内进行对象创建)
lvgl_port_lock(0);          // 0 表示无限等待锁
ui_init();                  // 调用 EEZ 生成的初始化函数
lvgl_port_unlock();

// ... 后续代码 ...

7. 创建 UI 刷新任务

EEZ Studio 生成的 UI 需要一个 ui_tick() 函数来定期刷新。我们需要创建一个独立的 FreeRTOS 任务来专门做这件事。

main.c 中添加任务函数和任务创建代码:

// 静态函数声明
static void ui_task(void *arg);

void app_main(void)
{
    // ... 初始化 LCD, LVGL ...

    // 初始化 UI
    lvgl_port_lock(0);
    ui_init();
    lvgl_port_unlock();

    // 创建 UI 刷新任务
    BaseType_t ui_task_ok = xTaskCreate(ui_task, "ui_task", 4096, NULL, 5, NULL);
    ESP_ERROR_CHECK(ui_task_ok == pdPASS ? ESP_OK : ESP_FAIL); // 检查任务是否创建成功

    // ... 其他任务 ...
}

// UI 刷新任务函数
static void ui_task(void *arg)
{
    (void)arg; // 消除未使用参数的警告

    for (;;)
    {
        // 在锁内调用 ui_tick,保证 LVGL 操作安全
        lvgl_port_lock(0);
        ui_tick();          // 通知 EEZ UI 进行周期性处理
        lvgl_port_unlock();

        // 刷新周期设为 20ms (约 50Hz),在流畅度和 CPU 占用之间取得良好平衡
        vTaskDelay(pdMS_TO_TICKS(20));
    }
}

8. 见证成果

完成以上所有步骤后,编译并烧录程序到你的 ESP32 开发板。如果一切顺利,你将在屏幕上看到与 EEZ Studio 设计完全一致的 UI 界面。

EEZ Studio 设计画面:

ESP32 实际显示效果:


恭喜!你已经成功地将一个通过 GUI 设计工具开发的 UI 工程,完整地移植到了嵌入式设备上。这种方式不仅极大地提升了 UI 开发的效率,也让设计和代码实现之间有了清晰的界限,便于后续的迭代和维护。