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

前言

开发环境: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 <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <esp_log.h>

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 <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <esp_log.h>

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 <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <esp_log.h>

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 <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <esp_log.h>

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 <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <esp_log.h>

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 <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <esp_log.h>

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中创建任务、删除任务、传递参数的函数。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
esp32-freertos-sdk 工具包 See the Getting Started guide links above for a detailed setup guide. This is a quick reference for common commands when working with ESP-IDF projects: Setup Build Environment (See Getting Started guide for a full list of required steps with details.) Install host build dependencies mentioned in Getting Started guide. Add tools/ directory to the PATH Run python -m pip install -r requirements.txt to install Python dependencies Configuring the Project idf.py menuconfig Opens a text-based configuration menu for the project. Use up & down arrow keys to navigate the menu. Use Enter key to go into a submenu, Escape key to go out or to exit. Type ? to see a help screen. Enter key exits the help screen. Use Space key, or Y and N keys to enable (Yes) and disable (No) configuration items with checkboxes "[*]" Pressing ? while highlighting a configuration item displays help about that item. Type / to search the configuration items. Once done configuring, press Escape multiple times to exit and say "Yes" to save the new configuration when prompted. Compiling the Project idf.py build ... will compile app, bootloader and generate a partition table based on the config. Flashing the Project When the build finishes, it will print a command line to use esptool.py to flash the chip. However you can also do this automatically by running: idf.py -p PORT flash Replace PORT with the name of your serial port (like COM3 on Windows, /dev/ttyUSB0 on Linux, or /dev/cu.usbserial-X on MacOS. If the -p option is left out, idf.py flash will try to flash the first available serial port. This will flash the entire project (app, bootloader and partition table) to a new chip. The settings for serial port flashing can be configured with idf.py menuconfig. You don't need to run idf.py build before running idf.py flash, idf.py flash will automatically rebuild anything which needs it. Viewing Serial Output The idf.py monitor target uses the idf_monitor tool to display se

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IoT_H2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值