基于Freertos的ESP-IDF开发——3.使用任务(上)

基于Freertos的ESP-IDF开发——3.使用任务(上)

  • 前言
  • 一、创建一个任务
  • 二、删除任务
  • 三、传入参数
  • 小结

前言

开发环境:ESP-IDF 4.3
操作系统:Windows10 专业版
开发板:自制的ESP32-WROOM-32E

我们已经学习了在主函数中执行代码,这篇文章我们将学习使用任务。

此外,由于本节需要消化的知识点较多,我们分为上中下来进行讲解。

任务也是Freertos中的一大特色功能

一般我们使用stm32、Arduino、micropython开发(未使用系统)的时候,一般都是使用的轮询代码,也就是在main函数里写一个while(1)循环来执行所有的任务,偶尔也会用中断来处理一些突发事件。

基于Freertos的ESP-IDF开发——3.使用任务(上)_第1张图片

对于多任务系统而言,这种执行方式就是单任务系统。

多任务系统的事件响应也是在中断中完成的,但事件的处理则是在任务中完成的。

每个任务都是独立的,互不干扰的,并且也具有相应的执行的优先级。

当同一时刻有两个任务想要同时执行时,那么则会根据任务优先级的次序来决定先执行哪个任务,否则先执行先被创建的任务

当一个外部紧急事件在中断里被标记后,如果处理该事件的任务的优先级足够高,该事件就会被立刻处理。

相比于轮询系统,多任务系统的实时性被提高了不少。

一、创建一个任务

这里是创建任务,并非新创建工程。

创建工程就不用多说了,可以看我的这篇文章来学习如何创建新的空工程基于Freertos的ESP-IDF开发——1.HelloWorld

我们在idf虚拟环境中创建一个新的空工程并进入其目录下:

基于Freertos的ESP-IDF开发——3.使用任务(上)_第2张图片

然后一如既往打开./main/2_Task.c文件进行主程序的编写。

先来看创建第一个任务:

#include 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include 

void Task1(void* param) //传入空指针方便后期传入参数:
{
    while(1)
    {
        printf("Hello Task!\n");//打印Hello Task!
        vTaskDelay(1000/portTICK_PERIOD_MS);//延时1000ms=1s,使系统执行其他任务
    }
 
}

void app_main(void)
{
    xTaskCreate(Task1,"Task1",1024,NULL,1,NULL);//创建任务函数
	
	//xTaskCreate(TaskFun,TaskName,StackSize,Param,Priority,*Task)
    //1:TaskFun 任务函数
    //2:TaskName 任务名字
    //3:StackSize 任务堆栈大小
    //4:Param 任务传入参数
    //5:Priority 任务优先级,最低优先级为0=空闲任务,可以设置0-31
    //6:Task 任务句柄任务创建成功后会返回这个句柄,其他api任务接口可调用这个句柄    
}

然后烧录至ESP32观察效果:
基于Freertos的ESP-IDF开发——3.使用任务(上)_第3张图片
与预期效果一致,那就是不断打印Hello Task!

我们如果想要看到内部系统在什么时刻做了什么事情,我们可以这样写:

#include 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include 

static const char * TAG = "Task";

void Task1(void* param) //传入空指针方便后期传入参数:
{
    while(1)
    {
		ESP_LOGI(TAG,"执行任务1");//打印时刻信息和TAG信息以及“执行任务1”
        //printf("Hello Task!\n");//打印Hello Task!
        vTaskDelay(1000/portTICK_PERIOD_MS);//延时1000ms=1s,使系统执行其他任务
    }
 
}

void app_main(void)
{
	ESP_LOGI(TAG,"FREERTOS 已启动!");
	
	//注意我这里将堆栈大小修改为了2048,因为使用了ESP_LOGI()函数将花费更多CPU资源
    xTaskCreate(Task1,"Task1",2048,NULL,1,NULL);//创建任务函数
	
	//xTaskCreate(TaskFun,TaskName,StackSize,Param,Priority,*Task)
    //1:TaskFun 任务函数
    //2:TaskName 任务名字
    //3:StackSize 任务堆栈大小
    //4:Param 任务传入参数
    //5:Priority 任务优先级,最低优先级为0=空闲任务,可以设置0-31
    //6:Task 任务句柄任务创建成功后会返回这个句柄,其他api任务接口可调用这个句柄    
}

基于Freertos的ESP-IDF开发——3.使用任务(上)_第4张图片
可以看到输出的结果变为了绿色,并且报告了什么时刻做了什么事情,为了使这个时间分片更加明显,我们再再加入任务2:

#include 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include 

static const char * TAG = "Task";

void Task1(void* param) //传入空指针方便后期传入参数:
{
    while(1)
    {
		ESP_LOGI(TAG,"执行任务1");
        //printf("Hello Task!\n");//打印Hello Task!
        vTaskDelay(1000/portTICK_PERIOD_MS);//延时1000ms=1s,使系统执行其他任务
    }
 
}

void Task2(void* param) //传入空指针方便后期传入参数:
{
    while(1)
    {
		ESP_LOGI(TAG,"执行任务2");
        //printf("Hello Task!\n");//打印Hello Task!
        vTaskDelay(1000/portTICK_PERIOD_MS);//延时1000ms=1s,使系统执行其他任务
    }
 
}

void app_main(void)
{
	ESP_LOGI(TAG,"FREERTOS 已启动!");
    xTaskCreate(Task1,"Task1",2048,NULL,1,NULL);//创建任务1
	vTaskDelay(800/portTICK_PERIOD_MS);
	xTaskCreate(Task2,"Task2",2048,NULL,1,NULL);//创建任务2
	
	//xTaskCreate(TaskFun,TaskName,StackSize,Param,Priority,*Task)
    //1:TaskFun 任务函数
    //2:TaskName 任务名字
    //3:StackSize 任务堆栈大小
    //4:Param 任务传入参数
    //5:Priority 任务优先级,最低优先级为0=空闲任务,可以设置0-31
    //6:Task 任务句柄任务创建成功后会返回这个句柄,其他api任务接口可调用这个句柄    
}

基于Freertos的ESP-IDF开发——3.使用任务(上)_第5张图片
313ms时刻创建了任务1并执行它,然后紧接着我们延时800ms,所以1113ms时刻创建任务2并执行。

紧接着就是313的1000ms后(1313ms)执行任务1,1113的1000ms后(2113时刻)执行任务2

二、删除任务

实时操作系统的好处之一也是可以在特定时刻创建任务,也可以在特定条件下删除任务,这样做的好处是不用像轮询系统那样冗余的if语句来执行多余的操作,并且如果标志位使用不当将带来糟糕的后果。

我们使用vTaskDelete()函数来进行任务删除。

现在我们来演示删除任务:

#include 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include 

static const char * TAG = "Task";

void Task1(void* param) //传入空指针方便后期传入参数:
{
    while(1)
    {
		ESP_LOGI(TAG,"执行任务1");
        //printf("Hello Task!\n");//打印Hello Task!
        vTaskDelay(1000/portTICK_PERIOD_MS);//延时1000ms=1s,使系统执行其他任务
    }
}

void Task2(void* param) //传入空指针方便后期传入参数:
{
    while(1)
    {
		ESP_LOGI(TAG,"执行任务2");
        //printf("Hello Task!\n");//打印Hello Task!
        vTaskDelay(1000/portTICK_PERIOD_MS);//延时1000ms=1s,使系统执行其他任务
    }
}

void app_main(void)
{
	ESP_LOGI(TAG,"FREERTOS 已启动!");
	
	TaskHandle_t TaskPoint=NULL;//创造一个TaskHandle_t类型的指针变量,用于存放任务句柄
	
	
    xTaskCreate(Task1,"Task1",2048,NULL,1,NULL);//创建任务1
	vTaskDelay(800/portTICK_PERIOD_MS);
	
	xTaskCreate(Task2,"Task2",2048,NULL,1,&TaskPoint);//创建任务2,并将句柄值返回给TaskPoint
	
	//5s后 删除任务2
	vTaskDelay(5000/portTICK_PERIOD_MS);
	vTaskDelete(TaskPoint);
}

基于Freertos的ESP-IDF开发——3.使用任务(上)_第6张图片

我们可以很清晰的看到在创建完任务2的5s内,任务1和任务2交替执行,但是在5s后任务2不在被执行,这证明我们的删除任务函数起了作用。

除了在主函数中删除任务,我们也可以让任务在执行完毕后自己删除自己,示例代码如下:

#include 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include 

static const char * TAG = "Task";

void Task1(void* param) //传入空指针方便后期传入参数:
{
    while(1)
    {
		ESP_LOGI(TAG,"执行任务1");
        //printf("Hello Task!\n");//打印Hello Task!
        vTaskDelay(1000/portTICK_PERIOD_MS);//延时1000ms=1s,使系统执行其他任务
    }
 
}

void Task2(void* param) //传入空指针方便后期传入参数:
{
    while(1)
    {
		ESP_LOGI(TAG,"执行任务2");
        //printf("Hello Task!\n");//打印Hello Task!
        vTaskDelay(1000/portTICK_PERIOD_MS);//延时1000ms=1s,使系统执行其他任务
		
		vTaskDelete(NULL);
    }
 
}

void app_main(void)
{
	
	
	ESP_LOGI(TAG,"FREERTOS 已启动!");
	
	TaskHandle_t TaskPoint=NULL;//创造一个TaskHandle_t类型的指针变量,用于存放任务句柄
	
	
    xTaskCreate(Task1,"Task1",2048,NULL,1,NULL);//创建任务1
	vTaskDelay(800/portTICK_PERIOD_MS);
	
	xTaskCreate(Task2,"Task2",2048,NULL,1,&TaskPoint);//创建任务2,并将句柄值返回给TaskPoint
	
	//5s后 删除任务2
	vTaskDelay(5000/portTICK_PERIOD_MS);
	vTaskDelete(TaskPoint);
}

基于Freertos的ESP-IDF开发——3.使用任务(上)_第7张图片
任务二的第一次循环就将自身给删除了,具体效果我不做过多赘述。

值得注意的那就是vTaskDelete(NULL)可以用于删除任务自身。

三、传入参数

如果任务函数只能输出这个输出那个,而不能做运算,那还不如不要这样的垃圾函数。

所以在此我们介绍传入参数的办法。

如下示例:

#include 
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include 

static const char * TAG = "Task";


void Task1(void* param)
{
    int *Pint;//创建一个int类型的指针
    Pint=(int*)param;//将传入函数的参数赋值给Pint
	
	
	ESP_LOGI(TAG,"param = %d",*Pint);
	
    vTaskDelay(1000/portTICK_PERIOD_MS);
    vTaskDelete(NULL);//删除当前任务
}


int Num=1;

void app_main(void)
{
    xTaskCreate(Task1,"Task1",2048,(void*)&Num,1,NULL);
    //创建任务函数 并将TestNum参数传入函数中
}

基于Freertos的ESP-IDF开发——3.使用任务(上)_第8张图片

我们可以看到成功将整型数据的值传入了其中,事实上,传入字符串、数组、结构体也未必不行。

传入结构体的示例代码如下


基于Freertos的ESP-IDF开发——3.使用任务(上)_第9张图片
我们可以看到它准确传递了结构体中的变量,并将其打印。

小结

这里我们学习了freertos中创建任务、删除任务、传递参数的函数。

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