ESP32入门(三)使用 FreeRTOS 多任务系统实现 LED 点灯控制

在之前的博客中,我们已经学习了 ESP32 的基础点灯操作以及通过 WiFi 和浏览器实现远程控制。今天我们将进一步探索 ESP32 的强大性能,引入 FreeRTOS 实时操作系统,通过多任务机制实现更复杂的 LED 控制场景。FreeRTOS 作为嵌入式领域应用广泛的实时操作系统,能够帮助我们更高效地管理 ESP32 的资源,实现多个任务的并行执行。

一、FreeRTOS 基础概念与 ESP32 集成优势

1.1 FreeRTOS 简介

FreeRTOS 是一款轻量级、开源的实时操作系统,专为嵌入式系统设计。它提供了任务调度、内存管理、消息队列、信号量等核心功能,能够让开发者轻松实现多任务并行处理。对于 ESP32 这样的双核处理器来说,FreeRTOS 的多任务机制可以充分发挥其硬件性能,让多个任务(如 LED 控制、传感器读取、网络通信等)同时运行而不会互相阻塞。

1.2 ESP32 与 FreeRTOS 的结合优势

ESP32 内置的双核 Xtensa 处理器与 FreeRTOS 结合后,具有以下显著优势:

  • 任务并行执行:利用 ESP32 的双核特性,可在两个核心上同时运行不同任务,提高系统响应速度
  • 资源高效管理:FreeRTOS 的任务调度机制确保关键任务优先执行,避免资源竞争
  • 模块化开发:将复杂系统拆分为独立任务,提高代码可读性和可维护性
  • 实时性保障:适合对实时性要求高的场景,如工业控制、智能家居等

二、开发环境准备与 FreeRTOS 集成

2.1 环境准备

在开始之前,确保已准备好以下资源:

  • ESP32 开发板(如 ESP32-DevKitC)
  • Arduino IDE(已配置 ESP32 开发环境)
  • USB 数据线
  • LED 灯及限流电阻(可选,若开发板未集成 LED)

2.2 在 Arduino IDE 中集成 FreeRTOS

Arduino IDE 中使用 ESP32 的 FreeRTOS 功能需要通过 ESP32 Arduino Core 来实现,该核心已内置 FreeRTOS 支持。具体配置步骤如下:

  1. 打开 Arduino IDE,进入 "工具"-> "开发板"-> "开发板管理器"
  2. 确保已安装最新版本的 "ESP32 by Espressif Systems" 开发板支持
  3. 在 "工具" 菜单中,确认以下选项:
    • 开发板:选择对应的 ESP32 开发板型号
    • 芯片:根据开发板选择(如 ESP32-D0WDQ6)
    • 处理器:默认即可(如双核,80MHz/240MHz)
    • 编译选项:确保勾选 "FreeRTOS Support"(在 "工具"-> "ESP32 Arduino" 中)

2.3 硬件连接

如果开发板未集成 LED,按如下方式连接:

  • LED 正极通过 220Ω 限流电阻连接到 ESP32 的 GPIO2 引脚
  • LED 负极连接到开发板 GND 引脚

三、FreeRTOS 多任务点灯实例实现

下面我们通过一个实例来展示如何使用 FreeRTOS 在 ESP32 上创建多个任务控制 LED 灯。这个实例将实现三个独立任务:

  1. 主控制任务:处理用户输入和任务协调
  2. LED 闪烁任务:控制 LED 以固定频率闪烁
  3. 状态监测任务:实时监测 LED 状态并输出到串口     完整代码实现
    #include 
    #include 
    #include 
    #include 
    
    // 定义LED控制引脚
    #define LED_PIN 2
    
    // 定义任务句柄
    TaskHandle_t mainTaskHandle = NULL;
    TaskHandle_t ledBlinkTaskHandle = NULL;
    TaskHandle_t statusMonitorTaskHandle = NULL;
    
    // 定义信号量,用于任务间同步
    SemaphoreHandle_t ledControlSemaphore = NULL;
    
    // LED状态枚举
    typedef enum {
        LED_OFF = 0,
        LED_ON = 1
    } LedStatus;
    
    // LED当前状态
    LedStatus currentLedStatus = LED_OFF;
    
    // LED闪烁任务函数
    void ledBlinkTask(void *pvParameters) {
        TickType_t xLastWakeTime;
        const TickType_t xFrequency = 500 / portTICK_PERIOD_MS; // 500ms周期
        
        // 初始化延时
        xLastWakeTime = xTaskGetTickCount();
        
        while (1) {
            // 从信号量获取LED状态
            if (xSemaphoreTake(ledControlSemaphore, portMAX_DELAY) == pdPASS) {
                // 根据当前状态控制LED
                digitalWrite(LED_PIN, currentLedStatus);
                
                // 释放信号量
                xSemaphoreGive(ledControlSemaphore);
            }
            
            // 延时
            vTaskDelayUntil(&xLastWakeTime, xFrequency);
        }
    }
    
    // 状态监测任务函数
    void statusMonitorTask(void *pvParameters) {
        while (1) {
            // 从信号量获取LED状态并打印
            if (xSemaphoreTake(ledControlSemaphore, portMAX_DELAY) == pdPASS) {
                Serial.print("LED Status: ");
                Serial.println(currentLedStatus == LED_ON ? "ON" : "OFF");
                xSemaphoreGive(ledControlSemaphore);
            }
            
            // 每2秒监测一次
            vTaskDelay(pdMS_TO_TICKS(2000));
        }
    }
    
    // 主控制任务函数
    void mainControlTask(void *pvParameters) {
        uint32_t buttonPressCount = 0;
        
        while (1) {
            Serial.println("FreeRTOS LED Control Demo");
            Serial.println("1. Turn LED ON");
            Serial.println("2. Turn LED OFF");
            Serial.println("3. Toggle LED");
            Serial.println("0. Exit");
            Serial.print("Enter your choice: ");
            
            // 模拟用户输入(实际项目中可替换为串口读取或按键输入)
            // 这里为简化演示,使用固定延时切换状态
            vTaskDelay(pdMS_TO_TICKS(3000));
            
            // 模拟用户输入选择
            buttonPressCount++;
            int choice = buttonPressCount % 4;
            
            // 根据选择控制LED
            switch (choice) {
                case 1:
                    xSemaphoreTake(ledControlSemaphore, portMAX_DELAY);
                    currentLedStatus = LED_ON;
                    xSemaphoreGive(ledControlSemaphore);
                    Serial.println("LED turned ON");
                    break;
                case 2:
                    xSemaphoreTake(ledControlSemaphore, portMAX_DELAY);
                    currentLedStatus = LED_OFF;
                    xSemaphoreGive(ledControlSemaphore);
                    Serial.println("LED turned OFF");
                    break;
                case 3:
                    xSemaphoreTake(ledControlSemaphore, portMAX_DELAY);
                    currentLedStatus = (currentLedStatus == LED_ON) ? LED_OFF : LED_ON;
                    xSemaphoreGive(ledControlSemaphore);
                    Serial.println("LED toggled");
                    break;
                case 0:
                    Serial.println("Exiting...");
                    break;
            }
            
            // 主任务延时
            vTaskDelay(pdMS_TO_TICKS(5000));
        }
    }
    
    void setup() {
        Serial.begin(115200);
        delay(100);
        
        // 初始化LED引脚
        pinMode(LED_PIN, OUTPUT);
        digitalWrite(LED_PIN, LED_OFF);
        
        // 创建信号量
        ledControlSemaphore = xSemaphoreCreateMutex();
        if (ledControlSemaphore == NULL) {
            Serial.println("Failed to create semaphore!");
            while (1); // 信号量创建失败,程序挂起
        }
        
        // 创建任务
        xTaskCreatePinnedToCore(
            mainControlTask,        // 任务函数
            "MainCtrlTask",         // 任务名称
            4096,                   // 任务栈大小
            NULL,                   // 任务参数
            3,                      // 任务优先级
            &mainTaskHandle,        // 任务句柄
            0                       // 运行在核心0
        );
        
        xTaskCreatePinnedToCore(
            ledBlinkTask,           // 任务函数
            "LedBlinkTask",         // 任务名称
            2048,                   // 任务栈大小
            NULL,                   // 任务参数
            2,                      // 任务优先级
            &ledBlinkTaskHandle,    // 任务句柄
            1                       // 运行在核心1
        );
        
        xTaskCreate(
            statusMonitorTask,      // 任务函数
            "StatusMonitorTask",    // 任务名称
            2048,                   // 任务栈大小
            NULL,                   // 任务参数
            1,                      // 任务优先级
            &statusMonitorTaskHandle // 任务句柄
        );
        
        // 启动调度器
        vTaskStartScheduler();
        
        // 如果程序执行到这里,说明调度器启动失败
        while (1);
    }
    
    void loop() {
        // FreeRTOS启动后,loop函数不会被执行
    }

代码功能解析

3.1 任务架构设计

本实例采用了三层任务架构:

  1. 主控制任务(mainControlTask)

    • 运行在 CPU 核心 0,优先级最高
    • 负责处理用户输入(本例中模拟输入)和任务协调
    • 通过信号量与其他任务通信
  2. LED 闪烁任务(ledBlinkTask)

    • 运行在 CPU 核心 1,优先级中等
    • 负责根据当前 LED 状态控制 GPIO 引脚
    • 使用周期性延时实现稳定的闪烁频率
  3. 状态监测任务(statusMonitorTask)

    • 运行在任意核心,优先级最低
    • 定期读取 LED 状态并输出到串口
    • 展示任务间数据共享的实现方式
3.2 任务通信机制

本例中使用了 FreeRTOS 的互斥信号量(Mutex)作为任务间通信的机制:

  • ledControlSemaphore信号量用于保护 LED 状态变量currentLedStatus
  • 多个任务可以安全地读取和修改 LED 状态而不会产生竞争条件
  • xSemaphoreTakexSemaphoreGive函数用于获取和释放信号量
3.3 任务创建与调度

setup函数中,通过xTaskCreatePinnedToCorexTaskCreate函数创建任务:

  • xTaskCreatePinnedToCore用于将任务固定在特定 CPU 核心上运行
  • 每个任务指定了唯一的名称、栈大小、优先级等参数
  • 任务优先级数值越大,优先级越高
  • 最后通过vTaskStartScheduler启动任务调度器,FreeRTOS 开始管理任务执行

四、程序上传与运行测试

4.1 上传代码

将上述代码复制到 Arduino IDE 中,注意修改以下部分:

  1. 确保 LED_PIN 定义与实际连接的引脚一致
  2. 根据需要调整任务优先级和栈大小
  3. 若使用实际按键输入,修改主控制任务中的输入处理部分

 

4.2 运行与测试

上传完成后,打开串口监视器(波特率 115200),可以看到以下输出:

FreeRTOS LED Control Demo
1. Turn LED ON
2. Turn LED OFF
3. Toggle LED
0. Exit
Enter your choice: 

程序会每隔 5 秒模拟一次用户输入,依次执行开灯、关灯、切换状态和退出操作。同时,状态监测任务会每隔 2 秒输出一次 LED 的当前状态,LED 闪烁任务会根据当前状态控制 LED 的开关。

你可以通过观察 LED 的闪烁情况和串口输出,验证三个任务是否正常并行运行。由于使用了 FreeRTOS 的任务调度机制,即使主控制任务在处理输入时,LED 闪烁和状态监测任务也会继续运行,不会出现阻塞现象。

五、FreeRTOS 在 ESP32 开发中的进阶应用方向

通过这个实例,我们初步了解了 FreeRTOS 在 ESP32 上的应用。实际上,FreeRTOS 的强大功能远不止于此,以下是一些进阶应用方向:

  1. 多核任务分配:充分利用 ESP32 的双核特性,将计算密集型任务和 IO 任务分配到不同核心
  2. 消息队列应用:使用 FreeRTOS 的消息队列实现任务间大量数据的传递
  3. 事件组机制:处理任务间复杂的同步关系
  4. 中断与任务交互:实现硬件中断与任务之间的通信
  5. 低功耗管理:结合 FreeRTOS 的任务调度实现系统低功耗模式

六、总结与资源推荐

通过本文的学习,我们掌握了以下关键技能:

  • ESP32 与 FreeRTOS 的集成方法
  • FreeRTOS 任务的创建、调度和管理
  • 任务间通信机制(信号量的使用)
  • 多核处理器上的任务分配策略

你可能感兴趣的:(ESP32入门,单片机,物联网,51单片机,嵌入式硬件)