开发环境:ESP-IDF 4.3
操作系统:Windows10 专业版
开发板:自制的ESP32-WROOM-32E
上节内容我们学习了创建任务、删除任务、传递参数,现在我们接着往下深入
下面这份代码使用了 vTaskPrioritySet(p,N); 函数设置任务优先级,其中p是任务句柄,N是任务优先级,N可以取0-31,N为0时是空闲状态,N为31时为最高优先级
#include
#include
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "../build/config/sdkconfig.h"
#include "esp_log.h"
static const char *TAG = "Priority";
void Task1(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task1");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
void Task2(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task2");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
//void app_main(void)
//{
// xTaskCreate(Task1,"Task1",2048,NULL,1,NULL);
// xTaskCreate(Task2,"Task2",2048,NULL,1,NULL);
//}
void app_main(void)
{
TaskHandle_t pxtask=NULL;//定义一个任务句柄
xTaskCreate(Task1,"Task1",2048,NULL,1,NULL);
xTaskCreate(Task2,"Task2",2048,NULL,1,&pxtask);
vTaskPrioritySet(pxtask,3);//更改或设置 任务优先级
}
运行效果如图所示,我们可以看到1313ms时刻发生了CPU资源争夺的情况,这种情况下先执行的是Task2。
由于我们使用了**vTaskPrioritySet(pxtask,3);**函数将任务2的任务优先级调到了3级,所以在发生多个任务争夺CPU资源时,谁的优先级高那么就会先执行谁,如果任务优先级相同,则会按照创建的顺序依次执行。
如果想测试一下不更改优先级,会是怎样的输出结果,可以把我main函数的代码注释,将我注释的代码取消注释执行试试看。
如果我们希望某个任务在短时间内不在执行,但是又不至于将其删除的时候,我们可以选择将这个任务挂起,使用到的函数是vTaskSuspend() 。
下面来看示例代码:
#include
#include
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "../build/config/sdkconfig.h"
#include "esp_log.h"
static const char *TAG = "Suspend";
void Task1(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task1");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
void Task2(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task2");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
void app_main(void)
{
TaskHandle_t pxtask=NULL;//任务句柄定义
xTaskCreate(Task1,"Task1",2048,NULL,1,NULL);
xTaskCreate(Task2,"Task2",2048,NULL,1,&pxtask);
vTaskDelay(2000/portTICK_PERIOD_MS);//2s后将Task2挂起
vTaskSuspend(pxtask);
}
可以看到在任务挂起后,被挂起的任务(Task2)不再被执行,其他任务则正常执行。
目前来看,任务挂起与任务删除的效果相同。
使用vTaskSuspendAll() 来挂起所有任务。
任务在挂起后无法执行Freertos自身函数。
有任务挂起那么就有任务恢复,那不然还不如直接删除了呢。
现在我们来看任务恢复:
#include
#include
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "../build/config/sdkconfig.h"
#include "esp_log.h"
static const char *TAG = "Resume";
void Task1(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task1");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
void Task2(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task2");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
void app_main(void)
{
TaskHandle_t pxtask=NULL;//任务句柄定义
xTaskCreate(Task1,"Task1",2048,NULL,1,NULL);
xTaskCreate(Task2,"Task2",2048,NULL,1,&pxtask);
vTaskSuspend(pxtask);//任务挂起
vTaskDelay(4000/portTICK_PERIOD_MS);//4S后恢复任务
vTaskResume(pxtask);//任务恢复
}
效果很直观,Task2在创建后的极短时间内就被挂起了,直到我们4s后使用了vTaskResume(pxtask) ,Task2才得以继续执行。
使用vTaskResumeAll() 来恢复所有任务。
我们要想使用vTaskList(pcWriteBuffer); 查看任务列表还需要先进行一些设置。
输入下方指令来打开工程设置界面:
idf.py menuconfig
根据我的引导找到FreeRTOS的设置,把我圈的这两个地方勾上(使用Enter勾选)
第一个是:启用 FreeRTOS 跟踪工具
第二个是:启用 FreeRTOS 统计信息格式化功能
然后我们按ESC,直到它出现这个界面,我们输入"Y"保存并退出。
然后就完成了我们的freertos设置,这个menuconfig配置界面是在kconfig中写的,我们可以编写我们自己的配置界面以供更好的分享给他人或者帮助自己测试(如spi连线可以采用这种方式)。
现在我们来查看我们的任务:
#include
#include
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "../build/config/sdkconfig.h"
#include "esp_log.h"
static const char *TAG = "Resume";
void Task1(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task1");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
void Task2(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task2");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
void app_main(void)
{
xTaskCreate(Task1,"The_Task1",2048,NULL,1,NULL);
xTaskCreate(Task2,"The_Task2",2048,NULL,1,NULL);
static char pcWriteBuffer[512]={0};
vTaskList(pcWriteBuffer);
printf("-----------------Task List--------------\n");
printf("任务名\t\t状态\t优先级\t剩余栈 任务号\n");
printf("%s\n",pcWriteBuffer);
}
由于我们在menuconfig中修改了配置,所以整个项目需要重新编译,我们慢慢等待吧。
编译完成后运行效果如下:
任务状态说明:
X=运行状态 executing
B=阻塞状态 Blocked state
R=就绪状态 Ready state
S=挂起状态 Suspended state
D=删除状态 Deleted state
如果不对做Freertos做设置这一步将无法看到预期结果,如果没有设置请查看上一步进行设置。
接下来我们使用uxTaskGetStackHighWaterMark(pxtask) 来获取任务堆栈大小
#include
#include
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "../build/config/sdkconfig.h"
#include "esp_log.h"
static const char *TAG = "2_Task";
void Task1(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task1");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
void Task2(void* param)
{
while(1)
{
ESP_LOGI(TAG,"Task2");
vTaskDelay(1000/portTICK_PERIOD_MS);
}
}
void app_main(void)
{
//定义任务句柄
TaskHandle_t pxTask1;
TaskHandle_t pxTask2;
xTaskCreate(Task1,"Task1",2048,NULL,1,&pxTask1);
xTaskCreate(Task2,"Task1",2048,NULL,1,&pxTask2);
UBaseType_t Stack1;
UBaseType_t Stack2;
while(1)
{
//获取任务堆栈大小
Stack1=uxTaskGetStackHighWaterMark(pxTask1);
Stack2=uxTaskGetStackHighWaterMark(pxTask2);
printf("Task1 stack=%d\n",Stack1);
printf("Task2 stack=%d\n",Stack2);
vTaskDelay(3000/portTICK_PERIOD_MS);
}
}
这里我们使用了while死循环,这个程序每隔3s将打印程序的堆栈空间。
在Freertos操作系统中,如果任务较多,出现高优先级任务长时间占用CPU资源,低优先级任务长时间得不到执行这一种现象,那么我们的系统就是有问题的系统。
中断看门狗可确保 FreeRTOS 任务切换中断长时间不被阻止。
这种情况很不好,因为其他任务没有能获得 CPU 运行时间,包括可能重要的任务,如 WiFi 任务和空闲任务。
阻塞的任务切换中断可能发生当一个程序运行到无限循环并且中断被禁用或挂起中断。
任务看门狗定时器 (TWDT) 负责检测运行的任务在长时间没有让出 CPU 的情况。
这通常是由一个高优先级任务不让出 CPU 资源的循环引起,从而使较低优先级任务无法获得 CPU 资源。这可能是陷入无限循环的任务。
这里我们使用官方提供的例程,函数的预期效果是为看门狗超时时间设置为3s,如果没有喂狗将导致程序重启,现在让程序休眠10s,看看喂狗和不喂狗的区别:
#include
#include
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
#define TWDT_TIMEOUT_S 3
#define TASK_RESET_PERIOD_S 2
/*
* Macro to check the outputs of TWDT functions and trigger an abort if an
* incorrect code is returned.
*/
#define CHECK_ERROR_CODE(returned, expected) ({ \
if(returned != expected){ \
printf("TWDT ERROR\n"); \
abort(); \
} \
})
static TaskHandle_t task_handles[portNUM_PROCESSORS];
//Callback for user tasks created in app_main()
void reset_task(void *arg)
{
//Subscribe this task to TWDT, then check if it is subscribed
CHECK_ERROR_CODE(esp_task_wdt_add(NULL), ESP_OK);
CHECK_ERROR_CODE(esp_task_wdt_status(NULL), ESP_OK);
while(1){
//reset the watchdog every 2 seconds
CHECK_ERROR_CODE(esp_task_wdt_reset(), ESP_OK); //Comment this line to trigger a TWDT timeout
vTaskDelay(pdMS_TO_TICKS(TASK_RESET_PERIOD_S * 1000));
}
}
void app_main(void)
{
printf("Initialize TWDT\n");
//Initialize or reinitialize TWDT
CHECK_ERROR_CODE(esp_task_wdt_init(TWDT_TIMEOUT_S, false), ESP_OK);
//Subscribe Idle Tasks to TWDT if they were not subscribed at startup
#ifndef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(0));
#endif
#ifndef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1
esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(1));
#endif
//Create user tasks and add them to watchdog
for(int i = 0; i < portNUM_PROCESSORS; i++){
xTaskCreatePinnedToCore(reset_task, "reset task", 1024, NULL, 10, &task_handles[i], i);
}
printf("Delay for 10 seconds\n");
vTaskDelay(pdMS_TO_TICKS(10000)); //Delay for 10 seconds
printf("Unsubscribing and deleting tasks\n");
//Delete and unsubscribe Users Tasks from Task Watchdog, then unsubscribe idle task
for(int i = 0; i < portNUM_PROCESSORS; i++){
vTaskDelete(task_handles[i]); //Delete user task first (prevents the resetting of an unsubscribed task)
CHECK_ERROR_CODE(esp_task_wdt_delete(task_handles[i]), ESP_OK); //Unsubscribe task from TWDT
CHECK_ERROR_CODE(esp_task_wdt_status(task_handles[i]), ESP_ERR_NOT_FOUND); //Confirm task is unsubscribed
//unsubscribe idle task
CHECK_ERROR_CODE(esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(i)), ESP_OK); //Unsubscribe Idle Task from TWDT
CHECK_ERROR_CODE(esp_task_wdt_status(xTaskGetIdleTaskHandleForCPU(i)), ESP_ERR_NOT_FOUND); //Confirm Idle task has unsubscribed
}
//Deinit TWDT after all tasks have unsubscribed
CHECK_ERROR_CODE(esp_task_wdt_deinit(), ESP_OK);
CHECK_ERROR_CODE(esp_task_wdt_status(NULL), ESP_ERR_INVALID_STATE); //Confirm TWDT has been deinitialized
printf("Complete\n");
}
如果注释掉喂狗函数 {第32行CHECK_ERROR_CODE(esp_task_wdt_reset(), ESP_OK);} 后将是下面这种情况,由于没有喂狗而导致重启。
这一节我们学习了freertos的下列函数:
任务优先级
任务挂起
任务恢复
任务列表
获取任务堆栈大小
看门狗
这些对于我们的开发和调试都有着很大的作用,尤其是我们在调试环节中它们可以带来很大的帮助。
多多练习使用。