基于ESP32S3的智能终端项目--4.1 FreeRTOS 任务调度&&设置屏幕亮度

目录

[使用 FreeRTOS 优化 ESP32-S3 LVGL 界面刷新并实现屏幕亮度调节](#使用 FreeRTOS 优化 ESP32-S3 LVGL 界面刷新并实现屏幕亮度调节)

[1. LVGL 工作机制与 FreeRTOS 的必要性](#1. LVGL 工作机制与 FreeRTOS 的必要性)

[2. 创建 LVGL 刷新任务(绑定 Core 1)](#2. 创建 LVGL 刷新任务(绑定 Core 1))

[2.1 任务函数声明](#2.1 任务函数声明)

[2.2 在 setup() 中创建任务](#2.2 在 setup() 中创建任务)

[2.3 实现任务函数](#2.3 实现任务函数)

[3. 实现屏幕亮度调节(滑块控制 PWM)](#3. 实现屏幕亮度调节(滑块控制 PWM))

[3.1 SquareLine Studio 中的设计](#3.1 SquareLine Studio 中的设计)

[3.2 编写滑块事件回调](#3.2 编写滑块事件回调)

[3.3 全局变量 Set 的定义](#3.3 全局变量 Set 的定义)

[3.4 硬件注意事项](#3.4 硬件注意事项)

[4. 效果与总结](#4. 效果与总结)

[5. 扩展思考](#5. 扩展思考)


使用 FreeRTOS 优化 ESP32-S3 LVGL 界面刷新并实现屏幕亮度调节

在嵌入式 GUI 开发中,LVGL 是一个轻量且强大的图形库,但它需要定期调用 lv_timer_handler() 来处理屏幕刷新、触摸事件、动画等任务。如果直接在 Arduino 的 loop() 中执行耗时操作(如网络请求、文件读写),就会导致界面卡顿。借助 FreeRTOS 的多任务特性,我们可以将 LVGL 刷新任务独立出来,并绑定到专用 CPU 核心,从而保证界面流畅。本文以 ESP32-S3 为例,介绍如何创建 LVGL 刷新任务,并通过滑块动态调节屏幕亮度。

1. LVGL 工作机制与 FreeRTOS 的必要性

LVGL 是一个轮询驱动 的 GUI 库,核心函数 lv_timer_handler() 需要每隔几毫秒(通常 5ms)调用一次,以便:

  • 刷新屏幕(将脏区域数据推送到显示器)

  • 处理触摸/按键输入并触发事件回调

  • 执行动画进度更新

  • 运行内部定时器(如长按检测、滚动惯性)

在简单程序中,可以直接在 loop() 中调用:

cs 复制代码
cpp

void loop() {
    lv_timer_handler();
    delay(5);
}

但这种方式的局限性很明显:

  • 阻塞问题 :如果 loop() 中有耗时代码(如 WiFi.scanNetworks()),LVGL 刷新就会被推迟,界面会卡顿。

  • 多任务冲突:无法同时处理 WiFi 连接、音频播放、传感器采集等任务。

  • 代码维护困难:所有逻辑挤在一个循环中,耦合度高。

FreeRTOS 的解决方案:

  • 将 LVGL 刷新、网络通信、音频播放等拆分为独立任务,互不阻塞。

  • 利用优先级调度,确保高优先级的 GUI 任务始终流畅。

  • 充分利用 ESP32-S3 的双核架构:将 LVGL 刷新任务固定在 Core 1,其他任务放在 Core 0,实现真正的并行处理。

2. 创建 LVGL 刷新任务(绑定 Core 1)

2.1 任务函数声明

在 main.cpp 顶部声明任务函数:

cs 复制代码
cpp

void lvgl_task(void *pt);

2.2 在 setup() 中创建任务

完成 LVGL、显示器、触摸初始化后,调用 ui_init() 加载 SquareLine Studio 设计的界面,然后创建 FreeRTOS 任务:

cs 复制代码
cpp

void setup() {
    Serial.begin(115200);

    // 初始化 LVGL、显示器、触摸等...
    lv_init();
    tft.begin();
    touch.begin();
    ui_init();   // 加载 SquareLine Studio 设计的界面

    // 创建 LVGL 刷新任务,绑定到 Core 1
    xTaskCreatePinnedToCore(
        lvgl_task,    // 任务函数
        "lvgl_task",  // 任务名称
        1024 * 15,    // 栈大小(15KB,LVGL 需要较大栈空间)
        NULL,         // 任务参数
        2,            // 优先级(数字越大优先级越高,GUI 建议设为 2)
        NULL,         // 任务句柄(不需要时可为 NULL)
        1             // 绑定到 Core 1(ESP32-S3 的第二个核心)
    );

    Serial.println("Setup done");
}

// loop() 可留空,所有任务由 FreeRTOS 调度
void loop() {}

2.3 实现任务函数

在任务函数中无限循环调用 lv_timer_handler(),并使用 vTaskDelay() 让出 CPU:

cs 复制代码
cpp

void lvgl_task(void *pt) {
    while (1) {
        lv_timer_handler();   // LVGL 核心刷新
        vTaskDelay(5);        // 延时 5ms 并让出 CPU 给其他任务
    }
}

关键点:

  • 任务必须是一个永不退出的无限循环。

  • vTaskDelay(5) 相当于 Arduino 的 delay(5),但不会阻塞其他任务。

  • 栈大小根据实际需求调整,LVGL 界面复杂时可适当增加。

3. 实现屏幕亮度调节(滑块控制 PWM)

3.1 SquareLine Studio 中的设计

在 SquareLine Studio 中,我们可以在"设置"屏幕(如 ui_Set)中添加一个滑块(Slider) 和一个**标签(Label)**用于显示亮度百分比。命名约定:

  • 滑块对象名:ui_SliderLight

  • 显示数值的标签:ui_LabelLightValue

3.2 编写滑块事件回调

在 SquareLine Studio 生成的事件文件(如 ui_events.c 或直接在 ui.c 中)中,找到或添加滑块的事件回调函数。当滑块值变化时(LV_EVENT_VALUE_CHANGED),我们需要:

  1. 获取滑块当前值(0-100)。

  2. 更新标签文本(可用 _ui_slider_set_text_value 辅助函数)。

  3. 将亮度值转换为 PWM 占空比(0-255),通过 analogWrite 输出到背光引脚(本例中为 GPIO 16)。

示例代码(SquareLine Studio 生成的代码风格可能略有不同,但逻辑一致):

cs 复制代码
cpp

void ui_event_SliderLight(lv_event_t * e) {
    lv_event_code_t event_code = lv_event_get_code(e);
    lv_obj_t * target = lv_event_get_target(e);

    if (event_code == LV_EVENT_VALUE_CHANGED) {
        // 更新标签显示当前亮度值(带 % 符号)
        _ui_slider_set_text_value(ui_LabelLightValue, target, "", "%");

        // 获取滑块值(0-100)并保存到全局变量
        Set.screen_light = lv_slider_get_value(target);

        // 通过 PWM 控制背光引脚(值映射到 0-255)
        if (Set.screen_light > 0) {
            analogWrite(16, Set.screen_light * 2.55);  // 2.55 = 255/100
        } else {
            analogWrite(16, 0);  // 关闭背光
        }
    }
}

3.3 全局变量 Set 的定义

我们需要在某个头文件(如 config.h)中定义保存亮度值的结构体,并声明全局变量:

cpp 复制代码
c

// config.h
typedef struct {
    int screen_light;   // 屏幕亮度 0-100
    // 其他设置项(如 WiFi 配置)暂时省略
} cfg_set;

extern cfg_set Set;

在对应的 .c 文件(如 config.c)中初始化:

cpp 复制代码
c

#include "config.h"
cfg_set Set = {
    .screen_light = 100,   // 默认亮度 100%
    // 其他成员初始化...
};

然后在 main.cpp 中包含该头文件即可使用 Set.screen_light。

3.4 硬件注意事项

  • 背光引脚 :本例中背光接在 GPIO 16,需确保该引脚支持 PWM 输出(ESP32-S3 所有 GPIO 均可通过 analogWrite 实现软件 PWM,或使用硬件 LEDC 通道)。

  • PWM 初始化 :如果使用 analogWrite,通常无需额外初始化(Arduino 框架会自动处理)。但为了确保正确,可以在 setup() 中设置引脚模式:

    cpp 复制代码
    cpp
    
    pinMode(16, OUTPUT);
  • 亮度映射:滑块值范围 0-100,PWM 值范围 0-255,因此乘以 2.55。当亮度为 0 时直接输出 0 关闭背光。

4. 效果与总结

编译并烧录程序后,你会看到:

  • LVGL 界面流畅刷新,不受任何后台任务影响。

  • 滑动亮度滑块时,屏幕背光实时变化,同时标签显示当前亮度百分比。

  • 所有任务和谐共存:LVGL 刷新在 Core 1,其他任务(如未来要添加的 WiFi 扫描、音乐播放)可在 Core 0 上运行,互不干扰。

为什么要这样做?

  • 用户体验:界面永远不卡,响应及时。

  • 代码结构:任务分离,逻辑清晰,易于维护和扩展。

  • 硬件潜力:充分利用双核性能,让嵌入式设备也能拥有流畅的图形交互。

5. 扩展思考

  • 如果遇到 PWM 输出不稳定,可以改用 ESP32 的 LEDC 硬件 PWM 库,精度更高且不占用 CPU 时间。

  • 如需保存亮度设置到非易失存储(如 Preferences),可在滑块事件中写入,并在启动时读取恢复。

  • 后续我们将继续添加 WiFi 连接、天气更新、音乐播放等功能,并分别创建独立任务,敬请期待!

相关推荐
人邮异步社区19 小时前
学习FreeRTOS推荐哪本书?
freertos·rtos·嵌入式固件工程师
La Pulga3 天前
【FreeRTOS源码学习笔记】空闲任务与阻塞延时
笔记·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga4 天前
【FreeRTOS源码学习笔记】任务切换
单片机·mcu·学习·嵌入式·freertos·实时操作系统
La Pulga6 天前
【FreeRTOS源码学习笔记】链表的实现
数据结构·笔记·stm32·mcu·链表·嵌入式·freertos
La Pulga6 天前
【FreeRTOS源码学习笔记】任务创建
笔记·stm32·mcu·学习·嵌入式·freertos·实时操作系统
天吾cc11 天前
FreeRTOS 事件标志组
freertos
编码追梦人12 天前
基于 ESP32-S3 的便携式多参数环境监护仪设计:从硬件选型到 FreeRTOS 多任务落地
数据库·单片机·嵌入式硬件·mongodb·esp32·freertos·低功耗设计
天吾cc16 天前
FreeRTOS 队列集
freertos
硅基手札16 天前
操作系统设计思想系列 06 · FreeRTOS 深度 — 极简可移植范本
架构·操作系统·freertos·实时操作系统·嵌入式开发·设计思想
天吾cc21 天前
FreeRTOS 列表和列表项
freertos