开发环境:ESP-IDF 4.3
操作系统:Windows10 专业版
开发板:自制的ESP32-WROOM-32E
我们已经学习了在主函数中执行代码,这篇文章我们将学习使用任务。
此外,由于本节需要消化的知识点较多,我们分为上中下来进行讲解。
任务也是Freertos中的一大特色功能
一般我们使用stm32、Arduino、micropython开发(未使用系统)的时候,一般都是使用的轮询代码,也就是在main函数里写一个while(1)循环来执行所有的任务,偶尔也会用中断来处理一些突发事件。
对于多任务系统而言,这种执行方式就是单任务系统。
多任务系统的事件响应也是在中断中完成的,但事件的处理则是在任务中完成的。
每个任务都是独立的,互不干扰的,并且也具有相应的执行的优先级。
当同一时刻有两个任务想要同时执行时,那么则会根据任务优先级的次序来决定先执行哪个任务,否则先执行先被创建的任务
当一个外部紧急事件在中断里被标记后,如果处理该事件的任务的优先级足够高,该事件就会被立刻处理。
相比于轮询系统,多任务系统的实时性被提高了不少。
这里是创建任务,并非新创建工程。
创建工程就不用多说了,可以看我的这篇文章来学习如何创建新的空工程基于Freertos的ESP-IDF开发——1.HelloWorld
我们在idf虚拟环境中创建一个新的空工程并进入其目录下:
然后一如既往打开./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观察效果:
与预期效果一致,那就是不断打印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任务接口可调用这个句柄
}
可以看到输出的结果变为了绿色,并且报告了什么时刻做了什么事情,为了使这个时间分片更加明显,我们再再加入任务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任务接口可调用这个句柄
}
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);
}
我们可以很清晰的看到在创建完任务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);
}
任务二的第一次循环就将自身给删除了,具体效果我不做过多赘述。
值得注意的那就是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中创建任务、删除任务、传递参数的函数。