【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   阅读(1)   评论(0)
 标签: 编程 ESP32 FreeRTOS

涨知识
步进电机

步进电机是将电脉冲信号,转变为角位移或线位移的开环控制电机,又称为脉冲电机。

评论:
相关文章
【ESP32 C++教程】Unit6-1 定时器

本节主要讲解Timer类,FreeRTOS定时器的使用。


【ESP32 C++教程】Unit5-2 执行器件之舵机

本节主要讲解舵机驱动类和用按键控制舵机。


【ESP32 C++教程】Unit5-1 执行器件之继电器

本节主要讲解执行器件类型和用按键控制继电器。


【ESP32 C++教程】Unit4-3 红外接收和遥控

本小节主要讲解红外接收和遥控器件,以及遥控操作LED。


【ESP32 C++教程】Unit4-2 模拟量传感器

本小节讲解模拟量传感器使用,旋转电位器,DHT11温湿度传感器和实现自定义传感器类。


【ESP32 C++教程】Unit4-1 数字量传感器

本小节讲解Sensor类及派生类、数字量传感器使用和传感器的推荐交互流程。


【ESP32 C++教程】Unit3-2 触摸输入

本小节讲解ESP32内置触摸引脚的用法,


【ESP32 C++教程】Unit3-1 按键输入

本小节主要介绍按键信号转换、Button类及派生类、和Button交互推荐流程。


【ESP32 C++教程】Unit2-2 Ws2812灯珠

本小节主要介绍Ws2812灯珠的使用、对父类进行扩展实现自定义功能,和指针向下强制转换的使用。


【ESP32 C++教程】Unit2-1 RGB三色LED

本小节主要介绍RGB三色LED的使用,以及多态的具体实现。