【ESP32 C++教程】Unit6-2 FreeRTOS多任务

本节主要讲解Task类,FreeRTOS多任务的使用。

常规单核处理器一次只能执行一个任务,但多任务操作系统通过将处理器时间进行分片,并在时间片上运行不同任务, 使所有任务看起来像是同时在执行。下图展示了 三个任务相对于时间的执行模式。任务名称用不同颜色标示,并写在左侧。时间从左向右移动, 彩色线条显示在特定时间执行的任务。上方展示了所感知的并发执行模式, 下方展示了实际的多任务执行模式。

【ESP32 C++教程】Unit6-2 FreeRTOS多任务

使用多任务操作系统可以简化原本复杂的软件应用程序的设计:

  • 操作系统的多任务处理和任务间通信功能允许将复杂的应用程序划分为一组更小且更易于管理的任务。
  • 这种划分可以简化软件测试,确保团队分工明确,并促进代码复用。
  • 复杂的时序和排序细节将由 RTOS 内核负责,从而减轻了应用程序代码的负担。

FreeRTOS的任务创建

xTaskCreate 函数是用来创建一个新的任务,原型如下:

BaseType_t xTaskCreate( 
              TaskFunction_t pvTaskCode,    /* 任务函数 */
              const char * const pcName,     /* 任务名称 */
              const uint32_t uxStackDepth,  /* 任务堆栈字数 */
              void *pvParameters,                 /* 自定义参数指针 */
              UBaseType_t uxPriority,            /* 任务优先级 */
              TaskHandle_t *pxCreatedTask  /* 任务句柄指针 */
         );

xTaskCreatePinnedToCore() 函数是是 ESP-IDF 对 FreeRTOS 的扩展函数,可创建一个在指定处理器核心上执行的任务,原型如下:

BaseType_t xTaskCreatePinnedToCore(
              TaskFunction_t pvTaskCode,    /* 任务函数 */
              const char * const pcName,     /* 任务名称 */
              const uint32_t usStackDepth,  /* 任务堆栈字数 */
              void *pvParameters,                 /* 自定义参数指针 */
              UBaseType_t uxPriority,            /* 任务优先级 */
              TaskHandle_t *pxCreatedTask,  /* 任务句柄指针 */
              const BaseType_t xCoreID         /* 内核ID,对于ESP32通常为0或1 */
          );

FreeRTOS中的任务状态

任务可以存在于以下状态中:
运行:当任务实际执行时,它被称为处于运行状态。任务当前正在使用处理器。 如果运行 RTOS 的处理器只有一个内核, 那么在任何给定时间内都只能有一个任务处于运行状态。
准备就绪:准备就绪任务指那些能够执行(它们不处于阻塞或挂起状态), 但目前没有执行的任务, 因为同等或更高优先级的不同任务已经处于运行状态。
阻塞:如果任务当前正在等待时间或外部事件,则该任务被认为处于阻塞状态。 例如,如果一个任务调用vTaskDelay(),它将被阻塞(被置于阻塞状态), 直到延迟结束——一个时间事件。 任务也可以通过阻塞来等待队列、信号量、事件组、通知或信号量事件。处于阻塞状态的任务通常有一个"超时"期, 超时后任务将被超时,并被解除阻塞, 即使该任务所等待的事件没有发生。“阻塞”状态下的任务不使用任何处理时间,不能被选择进入运行状态。
挂起:与“阻塞”状态下的任务一样, “挂起”状态下的任务不能被选择进入运行状态,但处于挂起状态的任务没有超时。相反,任务只有在分别通过 vTaskSuspend() 和 xTaskResume() API 调用明确命令时才会进入或退出挂起状态。

【ESP32 C++教程】Unit6-2 FreeRTOS多任务

Task类

Task是开发框架中封装FreeRTOS任务相关函数的类,方便使用者操作任务,如创建、挂起、恢复、改变优先级等。

在Task类的实现上,通过定义初始化函数(执行一次)和循环体函数(重复执行)来简化使用。

任务启动,代码如下(src/framework/src/task.h):

    int Start(uint32_t stack_depth, uint8_t priority) {
        return xTaskCreate(
                    [](void *parameter) {
                        Task* task = (Task *)parameter;
                        
                        if (task->init_function_!=nullptr) {
                            task->init_function_();
                        }

                        if (task->loop_function_!=nullptr) {
                            while (1) {
                                task->loop_function_();
                            }
                        }

                        vTaskDelete(NULL);
                    },                        /* 任务函数 TaskFunction_t */
                    name_.c_str(),          /* 任务名称 const char* */
                    stack_depth,            /* 堆栈字数 */
                    this,                     /* 自定义参数指针 void* */
                    priority,               /* 任务优先级 UBaseType_t */
                    &task_handle_          /* 任务句柄指针 TaskHandle_t* */
                );
    }

Start方法接收两个参数,其中stack_depth为堆栈字数,priority为优先级,
xTaskCreate创建任务,任务函数为lambda表达式函数,若设置了初始化函数,则执行它,若设置了循环体函数,则在循环内执行它,若未设置循环体函数,则删除任务,相当于只执行一次的任务。

示例:多任务应用

从 https://gitee.com/billyzh/esp32-cpp-lesson 下载本教程的源码到本地硬盘文件夹,如d:\esp32-cpp-lesson
在VSCode中,选择【文件】->【打开文件夹...】选择上一步保存的文件夹打开

打开项目后,选择config.h文件,修改第10行为
#define APP_LESSON62 1

打开unit6-lesson62/board_config.h文件,设置LED使用的引脚,
#define BUILTIN_LED_PIN GPIO_NUM_4
配置启用GpioLed
#define CONFIG_USE_LED_GPIO 1

创建LED实例,代码如下(unit6-lesson62/my_board.cpp):

MyBoard::MyBoard() : Board() {
    Log::Info(TAG, "===== Create Board ...... =====");

    Log::Info(TAG, "initial led.");
    led_ = new GpioLed(BUILTIN_LED_PIN, false);

    Log::Info( TAG, "===== Board config completed. =====");
}

程序很简单,就是创建一个GpioLed实例。

多任务应用

代码如下(unit6-lesson62/my_application.cpp):

void MyApplication::OnInit() {
    task1_ = new Task(std::string("task1"));
    task1_->OnLoop([this](){
        Task1Loop();
    });
    task1_->Start(4096, tskIDLE_PRIORITY+1);

    task2_ = new Task(std::string("Task2"));
    task2_->OnLoop([this](){
        Task2Loop();
    });
    task2_->Start(4096, tskIDLE_PRIORITY+1);
}

/**
 * 任务1的循环执行体
 */
void MyApplication::Task1Loop() {
    Led *led = Board::GetInstance().GetLed();
    if (state_==0) {
        led->TurnOn();
        state_ = 1;
    } else {
        led->TurnOff();
        state_ = 0;
    }

    delay(1000);
}

/**
 * 任务2的循环执行体
 */
void MyApplication::Task2Loop() {
    long long n = 0;
    for (int i=1; i<10000000; i++) { 
        n += i; 
    }
    Log::Info(TAG, "1...10000000 = %lld", n);
    delay(2000); 
} 

程序解读
1. 在OnInit方法内,创建两个Task实例,然后启动任务,Task是封装FreeRTOS任务相关函数的类;
2. Task1Loop方法是任务1的循环体方法,具体为控制Led的打开和关闭;
3. Task2Loop方法是任务2的循环体方法,具体为计算从1到10000000的累加值;

编译项目并上传开发板检验

- 本文由用户 老张 发布,文中观点仅代表作者本人,不代表本站立场。
- 如需转载,请联系作者;如有侵权,请联系本站处理。

02-19   阅读(19)   评论(0)
 标签: 创客电子 ESP32 FreeRTOS ESP32-ArduinoFx

涨知识
SPI

SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚。

评论:
相关文章
MimiClaw 配置飞书机器人和添加硬件控制技能

本文本介绍配置飞书机器人为MimiClaw的一个输入/输出端,和添加一个控制WS2812与LED的控制技能。


ESP32-S3 部署 MimicLaw 完整教程:从零到成功调用 DeepSeek

一块 30 块钱的开发板 + 一个大模型 API,就能做出可以听懂人话的智能硬件。 本文记录完整安装过程和踩坑经验,确保你跟着做就能跑通。


MimiClaw 架构全解析,把 “智能龙虾” 跑在 ESP32 上

本文将从手绘架构图入手,逐层拆解 MimiClaw 的分层设计、核心模块、数据流转与底层实现,带你解剖这只“智能虾”的技术骨架,看懂在 C 语言加持下,AI 智能体如何以可穿戴设备的形态,在你身边稳稳运行、离线服务、主动响应。


如何用 platform.local.txt 深度定制 ESP32 编译流程?

本文介绍如何在不脱离 ArduinoIDE 可视化开发的前提下,通过一个名为 platform.local.txt 的小文件,实现对 ESP32 编译流程的精准控制。


优化Arduino-ESP32程序体积

本文将系统分析程序体积增长的五大根源,并提供经过验证的优化方案,帮助减小固件大小。


开发ESP32大模型AI语音助手-从软件到硬件

本文所DIY的语音助手设备端使用的是MicroPython、服务端是Python,对于很多开发者来说MicroPython入门没难度。


【ESP32 C++教程】Unit10-2:音频录制

本小节使用音频开发框架实现一个音频录制到文件的示例。


ESP32 I2S 接口深度解析:从时序、格式到 ESP-IDF 驱动实战

I2S协议通过BCLK、LRCLK和DATA三线精准传输音频数据,但时序边沿、帧格式、时钟源等细节常引发噪声或断连。本文详解ESP32的I2S实现,从协议原理到ESP-IDF v5.x代码配置,助你避开常见陷阱,确保音频稳定传输。


【ESP32 C++教程】Unit10-1:音频播放

本小节介绍音频的基础知识、音频开发框架和AudioCodec的简介,用一个音频播放示例来说明音频管道的使用。


MimiClaw – 开源超轻量级AI助手,无需高级运行环境

MimiClaw是基于ESP32-S3芯片的超轻量级AI助手,通过Telegram或WebSocket提供Claude/GPT智能服务。