基于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 连接、天气更新、音乐播放等功能,并分别创建独立任务,敬请期待!

相关推荐
炸膛坦客17 小时前
FreeRTOS 学习:(二十九)任务切换的底层逻辑(了解)
单片机·操作系统·freertos
qq_401700411 天前
FreeRtos——1、多任务与“上下文切换”的代价
freertos
螺丝钉的扭矩一瞬间产生高能蛋白1 天前
深入剖析FreeRTOS优先级继承机制:vTaskPriorityInherit与xTaskPriorityDisinherit源码解析
stm32·freertos·嵌入式软件·优先级反转
济6171 天前
FreeRTOS基础知识---为什么使用FreeRTOS以及其核心功能
嵌入式·freertos
炸膛坦客2 天前
FreeRTOS 学习:(二十八)任务调度器 + 启动第一个任务(了解)
stm32·单片机·操作系统·freertos
炸膛坦客2 天前
FreeRTOS 学习:(二十七)死等延时函数会对任务调度产生什么影响
stm32·操作系统·freertos
Zeku4 天前
TCP交错传输多通道实现原理
stm32·freertos·linux应用开发
MR_Promethus6 天前
FreeRTOS 学习笔记
freertos
Hello_Embed7 天前
Modbus 传感器开发:STM32F030 libmodbus 移植
笔记·stm32·学习·freertos·modbus