摘要(From AI):
本文是基于 FreeRTOS 和 ESP_IDF 的学习笔记,详细讲解了任务管理、优先级设置、任务堆栈监控、看门狗定时器(IWDT 和 TWDT)等关键功能。内容涵盖任务创建与删除、任务挂起与恢复、时间片轮转调度机制,以及任务看门狗的使用与问题排查。文中提供了丰富的代码示例、常见错误的分析及调试方法,例如如何通过 vTaskList() 获取任务状态、利用 uxTaskGetStackHighWaterMark() 检测栈溢出风险,以及通过 TWDT 检测任务长时间运行问题。还介绍了 FreeRTOS 的调度器工作原理、时间片机制及任务状态切换。针对 ESP-IDF 平台的特殊配置,如 Menuconfig 中的设置项,也有详细说明。本文结合实践经验和官方文档,提供了一些性能优化建议,非常适合初学者和开发者参考,以提升对 FreeRTOS 核心概念和任务调度的理解。
前言:本文档是本人在依照B站UP:Michael_ee的视频教程进行学习时所做的学习笔记,可能存在疏漏和错误,如有发现,望指正。
文章目录
- Introduction(简介)
-
- [Why use FreeRTOS?(为什么使用 FreeRTOS)](#Why use FreeRTOS?(为什么使用 FreeRTOS))
- [VScode Development Environment Configuration(Vscode 中的 esp-idf 环境配置)](#VScode Development Environment Configuration(Vscode 中的 esp-idf 环境配置))
- [The Meaning of FreeRTOS Function Name Prefixes(FreeRTOS 中函数前缀的意义)](#The Meaning of FreeRTOS Function Name Prefixes(FreeRTOS 中函数前缀的意义))
- [FreeRTOS Startup Process(FreeRTOS 程序启动过程)](#FreeRTOS Startup Process(FreeRTOS 程序启动过程))
-
- [Application Startup Phase(程序启动过程)](#Application Startup Phase(程序启动过程))
- Task(任务)
-
- [Task Creation and Deletion(任务的创建和删除)](#Task Creation and Deletion(任务的创建和删除))
- [Task Parameters(任务参数)](#Task Parameters(任务参数))
- [Task Priority(任务优先级)](#Task Priority(任务优先级))
-
- [Priority define](#Priority define)
- [Same Priority](#Same Priority)
- [Different Priority](#Different Priority)
- [Modify Priority](#Modify Priority)
- [Task Suspend and Resume(任务挂起和恢复)](#Task Suspend and Resume(任务挂起和恢复))
-
- [Task States](#Task States)
- [vTaskSuspend() and vTaskResume()](#vTaskSuspend() and vTaskResume())
- [vTaskSuspendAll() and xTaskResumeAll()](#vTaskSuspendAll() and xTaskResumeAll())
- xTaskResumeFromISR()
- [Task List(任务列表)](#Task List(任务列表))
-
- [Task List 的作用](#Task List 的作用)
- vTaskList()
- Example
- [Task Stack Setting(任务堆栈设置)](#Task Stack Setting(任务堆栈设置))
-
- [usStackDepth 参数描述](#usStackDepth 参数描述)
- uxTaskGetStackHighWaterMark()
- [Task Watch Dog(任务看门狗)](#Task Watch Dog(任务看门狗))
-
- [Interrupt Watchdog Timer (IWDT)](#Interrupt Watchdog Timer (IWDT))
- [Task Watchdog Timer (TWDT)](#Task Watchdog Timer (TWDT))
-
- [Core functions](#Core functions)
- Usage
- Configuration
- Example
- [esp_task_wdt_add() and esp_task_wdt_reset()](#esp_task_wdt_add() and esp_task_wdt_reset())
参考资料:
Introduction(简介)
Why use FreeRTOS?(为什么使用 FreeRTOS)
- 免费、开源项目
- 是一种操作系统,便于分工、测试、复用代码
- 可以更具任务要求分配优先级
- 系统占用小且简单,仅需
task.c queue.c list.c - 已移植到多平台
VScode Development Environment Configuration(Vscode 中的 esp-idf 环境配置)
基于 VScode 中的 ESP-IDF 插件进行开发
安装插件->下载 ESP-IDF->下载工具包->通过 Hello_World 例程测试环境
在 ESP-IDF 中查看示代码
shell
>ESP-IDF:Shwo Example....
The Meaning of FreeRTOS Function Name Prefixes(FreeRTOS 中函数前缀的意义)
函数名前缀的缩写通常用来表示函数的返回类型或功能类别,以下为具体意义
v(void)- 示例 :
vTaskDelete(TaskHandle_t xTask); - 解释:删除指定的任务,且无返回值。
- 示例 :
x(BaseType_t)- 示例 :
xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, configSTACK_DEPTH_TYPE usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask); - 解释 :创建任务,返回
pdPASS(成功)或errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败)。
- 示例 :
pd(port dependent)- 示例 :
pdTRUE和pdFALSE - 解释 :在 FreeRTOS 中表示布尔值,其中
pdTRUE表示成功或真,pdFALSE表示失败或假。
- 示例 :
pv(pointer to void)- 示例 :
pvPortMalloc(size_t xSize); - 解释 :分配一块内存并返回其指针,指针类型为
void*。
- 示例 :
ul(unsigned long)- 示例 :
ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait); - 解释 :等待任务通知,返回一个
unsigned long类型的通知计数。
- 示例 :
uc(unsigned char)- 示例 :
ucQueueGetQueueType(QueueHandle_t xQueue); - 解释 :返回队列类型,值为
unsigned char类型。
- 示例 :
us(unsigned short)- 示例 :
usTaskGetStackHighWaterMark(TaskHandle_t xTask); - 解释 :返回指定任务的栈剩余量(high watermark),类型为
unsigned short。
- 示例 :
ux(UBaseType_t)- 示例 :
uxTaskPriorityGet(TaskHandle_t xTask); - 解释 :获取指定任务的优先级,返回类型为无符号整数
UBaseType_t。
- 示例 :
pc(pointer to char)- 示例 :
pcTaskGetName(TaskHandle_t xTask); - 解释 :返回任务的名称字符串,返回类型为
char*。
- 示例 :
prv(private)- 示例 :
prvIdleTask(void *pvParameters); - 解释:空闲任务的实现,通常只在 FreeRTOS 内部文件中使用,不对外暴露。
- 示例 :
e(enum)- 示例 :
eTaskState eTaskGetState(TaskHandle_t xTask); - 解释 :返回任务的状态(例如
eRunning、eReady等),类型为枚举eTaskState。
- 示例 :
FreeRTOS Startup Process(FreeRTOS 程序启动过程)
-
一级 (ROM) 引导加载程序 被固化在了 ESP32 内部的 ROM 中(只读),它会从 flash 的 0x1000 偏移地址处加载二级引导加载程序至 RAM (IRAM & DRAM) 中。
-
二级引导加载程序 从 flash 中加载分区表和主程序镜像至内存中(可更改),主程序中包含了 RAM 段和通过 flash 高速缓存映射的只读段。
-
应用程序启动阶段 运行,这时第二个 CPU 和 RTOS 调度器启动,接着运行
main_task,从而执行app_main。(应用程序启动包含了从应用程序开始执行到app_main函数在主任务内部运行前的所有过程)
Application Startup Phase(程序启动过程)
函数调用顺序
//esp_system
ENTRY(call_start_cpu0)(最终入口,所有程序从这里开始进行初始化) <- call_start_cpu0 <- SYS_STARTUP_FN <- g_startup_fn <- start_cpu0 <- start_cpu0_default <- esp_startup_start_app
//freeRTOS
<- main_task <- app_main
由 GPT-4o 生成
在 ESP32 的启动过程中,函数调用顺序决定了系统的初始化步骤,加载的顺序从底层硬件初始化到应用程序的入口。以下是对每个函数的解释和调用顺序的描述:
ENTRY(call_start_cpu0):- 这是启动代码的入口点。
ENTRY通常是汇编语言定义的宏,指向call_start_cpu0,并由复位向量在芯片复位时触发。它将控制权转交给call_start_cpu0函数。
- 这是启动代码的入口点。
call_start_cpu0:call_start_cpu0是引导代码的核心初始化函数,它执行一些重要的系统初始化,包括时钟、缓存和其他硬件设置。- 这个函数负责将 CPU0 初始化到运行模式,为后续的系统初始化准备好环境。
SYS_STARTUP_FN:SYS_STARTUP_FN是宏定义,用于确定g_startup_fn函数的调用。它是可移植的宏,确保不同的启动函数可以根据系统需求被调用。- 通常,它会在启动流程中插入系统特定的初始化函数。
g_startup_fn:g_startup_fn是一个指向启动函数的指针。它会指向start_cpu0,并负责调用该函数。- 通过使用指针,可以灵活地配置不同的启动行为,而无需更改启动顺序。
start_cpu0:- 该函数继续初始化工作,包括设置存储器和中断。它为接下来的
start_cpu0_default函数创建一个清晰的环境。 start_cpu0主要用于处理 CPU0 的硬件层面初始化。
- 该函数继续初始化工作,包括设置存储器和中断。它为接下来的
start_cpu0_default:- 这个函数主要是 CPU0 的默认启动程序,用于配置其他硬件模块并启动操作系统内核(如 FreeRTOS)。
- 在 FreeRTOS 中,它负责创建一个名为
main_task的主任务。
esp_startup_start_app:esp_startup_start_app是一个关键函数,开始运行应用程序层代码。它将启动操作系统内核,并最终创建应用程序的主任务(main_task)。
main_task:main_task是应用程序的起点,由操作系统调度并管理。该任务初始化应用程序环境并启动app_main函数。
app_main:app_main是用户定义的主函数,负责应用程序的实际逻辑。此函数是用户代码的入口点,所有应用逻辑都从这里开始执行。
Task(任务)
Task Creation and Deletion(任务的创建和删除)
xTaskCreate()
- 每个任务都需要一定的 RAM 来保存任务状态(即任务控制块,TCB)和任务堆栈。
- 如果使用
xTaskCreate()函数创建任务,那么所需的 RAM 将自动从 FreeRTOS 的堆中分配。 - 新创建的任务初始状态为"就绪(Ready)"状态,如果当前没有更高优先级的任务可以运行,那么这个新任务将立即进入"运行(Running)"状态。
- 任务可以在调度器(调度器是一个核心组件,负责管理和控制多个任务的执行顺序 ,以确保系统的实时性和响应能力。它依据任务的优先级和状态(就绪、运行、阻塞等),动态地分配 CPU 给不同的任务,以实现多任务并行执行的效果)启动之前或之后创建。
cpp
#include "FreeRTOS.h"
#include "task.h"
//创建任务的新实例
BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,
const char* const pcName,
unsigned short usStackDepth,
void* pvParameters,
UBaseType_t usPriority,
TaskHandle_t* pxCreatedTask);
pvTaskCode任务(Tasks),本质上就是不会退出的 C 函数,通常实现为一个无限循环,pvTaskCode 参数是指向实现该任务的函数(函数名)的指针pcName任务名称,每个任务都可以指定一个描述性名称,主要用于调试usStackDepth指定堆栈深度,即堆栈可以容纳的字数(words)而不是字节数(bytes)。(不同平台的字长(word size)不同,这意味着最终分配的堆栈大小会因架构而异)pvParameters传入任务的参数,此参数的类型为"指向 void 的指针",以允许任务参数有效地、并通过强制转换间接地接收任何类型的参数。uxPriority任务优先级,优先级可以从最低优先级 0 分配到最高优先级(configMAX_PRIORITIES-1)。configMAX_PRIORITIES 是一个用户定义的常量。高优先级任务会优先占用 CPU 资源,而低优先级任务可能因"抢占式调度"机制被暂停执行,以便高优先级任务运行;如果有多个相同优先级的任务在就绪状态,调度器会轮流执行它们(时间片轮转),确保公平。pxCreatedTask句柄,将句柄传递给正在创建的任务的句柄。这个句柄可以用于引用 API 调用中的任务
vTaskDelete()
vTaskDelete() 函数用于删除由 xTaskCreate() 或 xTaskCreateStatic() 创建的任务。
- 被删除的任务不再存在,因此无法进入"运行"状态,也不能再通过任务句柄引用该任务。
- 当一个任务被删除后,系统会通过空闲任务来释放与该任务相关的内存(包括堆栈和任务控制块)。因此,如果系统使用了
vTaskDelete(),就必须确保空闲任务有足够的处理时间去完成这个清理过程。如果空闲任务被"饿死"(即没有获得运行时间),那么内存将无法被释放,从而可能导致内存泄漏。在 FreeRTOS 中,空闲任务是一个低优先级的后台任务,其职责之一就是回收被删除任务的内存。因此,如果有频繁创建和删除任务的需求,确保空闲任务有足够的 CPU 时间至关重要。
cpp
#include "FreeRTOS.h"
#include "task.h"
void vTaskDelete(TaskHandle_t pxTask);
pxTask被删除任务的句柄,任务可以通过传递 NULL 来代替有效的任务句柄来删除自身(即在 Task 中使用vTaskDelete(NULL);)
在编程中,句柄 (Handle)是一种抽象的引用 ,用于标识和访问操作系统或库中的资源或对象,而不直接暴露该资源的内部细节。它通常是一个指针或整数,通过该引用可以对资源进行操作。
在 FreeRTOS 中,句柄广泛用于引用各种内核对象,例如任务、队列、信号量、互斥量等。通过句柄,应用程序可以执行相关操作(如暂停、删除任务,或向队列发送消息),而无需了解对象的内部实现。
TaskHandle_t:用于任务的句柄
QueueHandle_t:用于队列的句柄
SemaphoreHandle_t:用于信号量的句柄
TimerHandle_t:用于定时器的句柄
Example
cpp
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void myTask(void *pvParam)
{
while (true)
{
printf("Hello world!\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);//任务延时 1 秒
}
}
void app_main(void)
{
TaskHandle_t myHandle = NULL;//创建句柄,用于引用新任务
//创建任务,将任务句柄传入 myHandle 中
xTaskCreate(myTask, "myTask1", 1024, NULL, 1, &myHandle);//正确传入 &myHandle,xTaskCreate函数会给myHandle赋值
vTaskDelay(2000 / portTICK_PERIOD_MS);//主任务延时 2 秒
if (myHandle != NULL)
vTaskDelete(myHandle); // 通过句柄删除 myTask
}
xTaskCreatePinnedToCore()
xTaskCreatePinnedToCore 是 FreeRTOS 中的一个函数,专门用于在多核环境(如 ESP32)中创建任务,并将任务绑定到指定的核心上运行
c
BaseType_t xTaskCreatePinnedToCore(
TaskFunction_t pvTaskCode,
const char *const pcName,
const uint32_t usStackDepth,
void *const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *const pxCreatedTask,
const BaseType_t xCoreID
);
xCoreID
- 类型:
BaseType_t - 含义:指定任务绑定的核心
- 可选值:
0:绑定到核心0(PRO_CPU)1:绑定到核心1(APP_CPU)tskNO_AFFINITY:不绑定特定核心,任务可以在任何核心上运行- 超过
portNUM_PROCESSORS - 1(如ESP32是1)会导致任务创建失败
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
// 核心0上的任务
void task_core0(void *pvParameters)
{
while (1)
{
printf("Task running on Core 0\n");
vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时1秒
}
}
// 核心1上的任务
void task_core1(void *pvParameters)
{
while (1)
{
printf("Task running on Core 1\n");
vTaskDelay(1000 / portTICK_PERIOD_MS); // 延时1秒
}
}
void app_main()
{
// 创建任务绑定到核心0
xTaskCreatePinnedToCore(
task_core0, // 任务函数
"TaskCore0", // 任务名称
2048, // 栈大小(以字节为单位)
NULL, // 任务参数
1, // 任务优先级
NULL, // 任务句柄
0 // 核心ID(0)
);
// 创建任务绑定到核心1
xTaskCreatePinnedToCore(
task_core1, // 任务函数
"TaskCore1", // 任务名称
2048, // 栈大小(以字节为单位)
NULL, // 任务参数
1, // 任务优先级
NULL, // 任务句柄
1 // 核心ID(1)
);
}
Task Parameters(任务参数)
任务函数接受一个类型为"指向无类型的指针"(void*)的参数。赋给 pvParameters 的值将是传递给任务的值。这个参数的类型为"指向无类型的指针",旨在允许任务参数有效地接收任何类型的参数,且可以通过类型转换间接地实现。例如,可以通过在创建任务时将整数类型转换为无类型指针,然后在任务函数定义中将无类型指针参数转换回整数类型,从而将整数类型传递给任务函数。
这里传参的本质都是传递地址
Integer Type
cpp
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void myTask(void *pvPamra)
{
int *pInt = (int*)pvPamra;
printf("I got a test number %d.\n", *pInt);
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
int testNumber = 5;
void app_main(void)
{
xTaskCreate(myTask, "myTask", 2048, (void*)&testNumber, 1, NULL);//此处需要比较大的堆栈,否则会触发看门狗
Array
cpp
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void myTask(void *pvPamra)
{
int *pArr = (int*)pvPamra;
for(int i = 0; i < 3; i++)
{
printf("I got a test number %d.\n", pArr[i]);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
int testArray[] = {6, 7, 8};
void app_main(void)
{
xTaskCreate(myTask, "myTask", 2048, (void*)testArray, 1, NULL);//此处需要比较大的堆栈,否则会触发看门狗
}
看门狗(Watchdog Timer,WDT)是一个计时器,通常会设定一个倒计时时间。在正常运行的情况下,程序需要在这个时间内定期地"喂狗"(也称为复位计时器)。如果计时器到期(即超时),说明系统可能陷入了死循环、卡住或长时间无响应。此时,看门狗会认为系统出现异常,从而触发一个重启来尝试恢复正常。
Struct
cpp
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
typedef struct a
{
int m_num1;
int m_num2;
} xStruct;
xStruct xStrTest = {6, 9};
void myTask(void *pvPamra)
{
xStruct* pStr = (xStruct*)pvPamra;
printf("I got a %d.\n", pStr->m_num1);
printf("I got a %d.\n", pStr->m_num2);
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void app_main(void)
{
xTaskCreate(myTask, "myTask", 2048, (void*)&xStrTest, 1, NULL);//此处需要比较大的堆栈,否则会触发看门狗
}
String
cpp
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
static const char *pStr = "Hello World!";
void myTask(void *pvPamra)
{
char* pStr = (char*)pvPamra;
printf("I got a string:%s.\n", pStr);
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void app_main(void)
{
xTaskCreate(myTask, "myTask", 2048, (void*)pStr, 1, NULL);//此处需要比较大的堆栈,否则会触发看门狗
}
Task Priority(任务优先级)
Priority define
每个任务的优先级从0到 (configMAX_PRIORITIES - 1),其中 configMAX_PRIORITIES 是在 FreeRTOSConfig.h 中定义的最大优先级数。
低优先级数字表示低优先级任务。空闲任务的优先级为零(tskIDLE_PRIORITY)。
文件路径
"idf 安装路径"\idf\v5.3.1\esp-idf\components\freertos\config\include\freertos

总共 25 个优先级别(可被修改)(ESP32_WROON 在这里设置过高的优先级(25 及以上)会触发看门狗,err:assert failed: prvInitialiseNewTask tasks.c:1088 (uxPriority < ( 25 )),assert 断言失败了,原因是提供给 prvInitialiseNewTask 函数的 uxPriority 参数超出了允许的范围)
如果 FreeRTOS 的当前移植版本(即在某个平台上运行的实现)使用了一种特殊的"端口优化任务选择机制",通过一个"计数前导零(count leading zeros)"的指令来选择任务(这是一种单条指令内完成任务选择的方式),并且 configUSE_PORT_OPTIMISED_TASK_SELECTION 在 FreeRTOSConfig.h 中被设置为 1,那么 configMAX_PRIORITIES 的值不能超过 32。
这种优化利用硬件支持的指令来加速任务选择,但限制了最大优先级的数量
普通情况的优先级配置 :
如果没有启用上述优化机制,则 configMAX_PRIORITIES 可以是任何合理的值。但是,为了提高 RAM 使用效率,应将其设置为实际需要的最小值
uxTaskPriorityGet()
查询分配给任务的优先级
c
#include "FreeRTOS.h"
#include "task.h"
UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask );
pxTask 任务的句柄
vTaskPrioritySet()
修改目标任务的优先级
c
#include "FreeRTOS.h"
#include "task.h"
void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority );
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
static const char *pStr = "Hello World!";
void myTask(void *pvPamra)
{
char *pStr = (char *)pvPamra;
printf("I got a string:%s.\n", pStr);
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void app_main(void)
{
UBaseType_t iPriority = 0;
TaskHandle_t myHandle = NULL;
xTaskCreate(myTask, "myTask", 4096, (void *)pStr, 24, &myHandle);
iPriority = uxTaskPriorityGet(myHandle);
printf("iPriority = %d\n", iPriority); // 输出24
vTaskPrioritySet(myHandle, 2);
iPriority = uxTaskPriorityGet(myHandle);
printf("iPriority = %d\n", iPriority); // 输出2
}
Same Priority
任何数量的任务可以共享同一优先级,谁先创建谁先执行。如果没有定义 configUSE_TIME_SLICING,或将其设置为 1,那么处于"就绪"状态且优先级相同的任务会通过时间片轮转的方式共享 CPU 时间。
时间片轮转是一种调度策略,同一优先级的任务会按顺序轮流获得 CPU 时间,从而确保这些任务能够公平地执行。
configUSE_TIME_SLICING 是 FreeRTOS 的一个配置选项,用于控制是否在同优先级的任务之间启用 时间片轮转 (time slicing)调度机制,时间片轮转允许同一优先级的任务公平共享 CPU 时间
启用 configUSE_TIME_SLICING(设为 1)
当 configUSE_TIME_SLICING 被设置为 1 时,FreeRTOS 会在同一优先级的多个"就绪"状态的任务之间启用时间片轮转调度。也就是说,同一优先级的任务会按照时间片轮转的方式依次获得 CPU 时间。具体来说:
- 每个任务会在指定的时间片(通常是一个时钟节拍周期)内运行。
- 时间片结束后,当前任务会让出 CPU 控制权,调度器将执行下一个相同优先级的任务。
- 这种机制确保相同优先级的任务都能得到 CPU 资源,避免某个任务长时间占用 CPU。
禁用 configUSE_TIME_SLICING(设为 0)
当 configUSE_TIME_SLICING 被设置为 0 时,同一优先级的多个任务不会自动共享 CPU 时间。表现为:
- 一旦某个同优先级任务开始运行,除非该任务主动让出 CPU(例如通过调用
taskYIELD()或等待事件、延时等),否则它将一直运行。 - 这种模式下,同优先级任务需要手动控制让出 CPU,适合特定应用场景(例如实时控制系统),以保证任务间的精确调度和更高的确定性。
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void myTask(void *pvPamra)
{
while(true)
{
printf("Task1 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void myTask2(void *pvParam)
{
while(true)
{
printf("Task2 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main(void)
{
TaskHandle_t myHandle = NULL;
TaskHandle_t myHandle2 = NULL;
xTaskCreate(myTask, "myTask", 4096, NULL, 1, &myHandle);
xTaskCreate(myTask2, "myTask2", 4096, NULL, 1, &myHandle2);
}
// 轮流打印"Task1/2 is running."
Different Priority
当多个任务处于"就绪"状态时,优先级最高的任务会优先进入"运行"状态。因此,调度器会始终将优先级最高且能够运行的任务置于运行状态
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void myTask(void *pvPamra)
{
while(true)
{
printf("Task1 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void myTask2(void *pvParam)
{
while(true)
{
printf("Task2 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main(void)
{
TaskHandle_t myHandle = NULL;
TaskHandle_t myHandle2 = NULL;
xTaskCreate(myTask, "myTask", 4096, NULL, 1, &myHandle);
xTaskCreate(myTask2, "myTask2", 4096, NULL, 2, &myHandle2);
}
// 这里先输出 Task1 is running.
// 随后输出 Task2 is running.
// 随后输出 Task2 is running.
// 随后输出 Task1 is running.
// 随后交替输出 Task2/1 is running.
可能的原因:
- 任务的创建顺序 :
app_main中先创建了Task1,再创建了Task2。虽然Task2优先级较高,但由于任务刚创建时还未正式进入调度器运行状态,因此Task1在此时可能会先开始运行。 - 调度延迟 :创建任务后,任务的调度需要一些时间才能生效。
Task1可能在Task2被完全启动前获得了 CPU 时间。、
Modify Priority
使用 vTaskPrioritySet() 修改优先级,用于升级或降级
Task Suspend and Resume(任务挂起和恢复)
Task States
Running(运行中)
当任务正在执行时,称其处于"运行中"状态。此时任务正在使用处理器资源。
若系统只有单核处理器,则在任何时间点只能有一个任务处于"运行中"状态。
Ready(就绪)
处于"就绪"状态的任务可以被执行,但因另一个优先级更高或相等的任务处于运行中状态,暂时未被执行。
Blocked(阻塞)
当任务在等待某个时间或外部事件时,它会进入"阻塞"状态。例如,调用 vTaskDelay() 时,任务会被阻塞直到延迟时间到达(时间事件)。
任务也可以因等待队列、信号量、事件组或通知而被阻塞,通常具有一个"超时"期限。
阻塞状态的任务不会消耗处理器资源,且不能被选为运行任务。
Suspended(挂起)
与阻塞状态类似,挂起状态的任务也不会被选为运行任务。
挂起任务没有超时,只能通过 vTaskSuspend() 和 xTaskResume() API调用显式地进入或退出挂起状态。
![[Pasted image 20241102115258.png]]
vTaskSuspend() and vTaskResume()
挂起/恢复指定 Task
c
#include "FreeRTOS.h"
#include "task.h"
void vTaskSuspend( TaskHandle_t pxTaskToSuspend );// 注意这里是直接传Handle,不是传指针
// ---------------------------------------
#include "FreeRTOS.h"
#include "task.h"
void vTaskResume( TaskHandle_t pxTaskToResume );
pxTaskToSuspend要挂起的任务的句柄。
- 使用任务的名称调用
xTaskGetHandle()来获取句柄 - 任务可以通过将
NULL传入而不是有效任务句柄的方式挂起自己
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void myTask(void *pvPamra)
{
while(true)
{
printf("Task1 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
//vTaskSuspend(NULL);// Task内部挂起自己
}
}
void myTask2(void *pvParam)
{
while(true)
{
printf("Task2 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main(void)
{
TaskHandle_t myHandle = NULL;
TaskHandle_t myHandle2 = NULL;
xTaskCreate(myTask, "myTask", 4096, NULL, 1, &myHandle);
xTaskCreate(myTask2, "myTask2", 4096, NULL, 2, &myHandle2);
vTaskDelay(3000 / portTICK_PERIOD_MS);
vTaskSuspend(myHandle);// 主程序挂起Task1,此时只剩任务2在打印
vTaskDelay(3000 / portTICK_PERIOD_MS);
vTaskResume(myHandle);// 主程序恢复Task1
}
vTaskSuspendAll() and xTaskResumeAll()
挂起/恢复调度器,可用于运行不希望被别的任务打扰的任务时
c
#include "FreeRTOS.h"
#include "task.h"
void vTaskSuspendAll( void );
// ---------------------------------------
#include "FreeRTOS.h"
#include "task.h"
BaseType_t xTaskResumeAll( void );
vTaskSuspendAll() 用于挂起 FreeRTOS 调度器 (scheduler)。挂起调度器会阻止任务切换的发生,但不会禁用中断。如果在挂起调度器期间中断请求进行任务切换,该请求会被暂时搁置,直到调度器恢复后才会执行
xTaskResumeAll()只能从执行中的任务中调用;它不能在调度器启动之前调用- 在调度器挂起期间,不能调用其他 FreeRTOS API 函数,但是可以调用自己编写的函数,要注意看门狗(记得"喂狗")
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void myTask(void *pvPamra)
{
printf("Test begin.\n");
vTaskSuspendAll();// 挂起调度器,此时Task2停止打印
for(int i = 0; i < 9999; i++)// 数值不能过大,否则有可能触发看门狗【E (5313) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time】
{
for(int j = 0; j < 999; j++)
{
;// 空循环,占用一些CPU时间,模拟计算
}
}
xTaskResumeAll();// 恢复调度器,Task2恢复打印
vTaskDelay(1000 / portTICK_PERIOD_MS);
printf("Test end.\n");
vTaskDelete(NULL);
}
void myTask2(void *pvParam)
{
while(true)
{
printf("Task2 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main(void)
{
TaskHandle_t myHandle = NULL;
TaskHandle_t myHandle2 = NULL;
xTaskCreate(myTask, "myTask", 4096, NULL, 1, &myHandle);// 两个Task设置为相同优先级
xTaskCreate(myTask2, "myTask2", 4096, NULL, 1, &myHandle2); // 在Task1模拟计算的过程中,在不挂起调度器时,由于调度器的调度,Task2会不断进行打印
}
xTaskResumeFromISR()
从中断中恢复
c
#include "FreeRTOS.h"
#include "task.h"
BaseType_t xTaskResumeFromISR( TaskHandle_t pxTaskToResume );
Task List(任务列表)
Task List 是一个保存所有任务信息的数据结构,用于管理和跟踪系统中所有任务的状态。任务列表分为多个队列,每个队列存储不同状态的任务,比如"就绪"、"阻塞"、"挂起"等,FreeRTOS 通过这些列表实现任务调度和状态管理
Task List 的作用
- 任务调度 :
调度器根据任务列表中的信息决定哪个任务应被调度执行。任务状态改变时,调度器会更新任务列表 - 任务状态跟踪 :
系统通过任务列表了解每个任务的状态(例如运行、就绪、阻塞或挂起),从而确保任务在合适的状态下被调度 - 任务优先级管理 :
任务列表按优先级存储任务。FreeRTOS 会优先调度高优先级任务,因此高优先级任务通常排在任务列表的前面 - 资源管理 :
当任务等待某些资源(例如队列、信号量或事件)时,任务会进入"阻塞"状态并被放入相应的阻塞列表中,等待条件满足后再回到就绪列表
常见的任务列表类型
- 就绪任务列表:存储所有准备运行的任务
- 延时任务列表 :存储被延迟的任务(例如调用
vTaskDelay()的任务) - 阻塞任务列表:存储等待事件(如队列、信号量等)的任务
vTaskList()
vTaskList() 用于生成一个包含当前系统中所有任务状态的表格,并将表格数据写入指定的字符缓冲区,显示了每个任务的状态、优先级、堆栈使用情况等信息
c
#include "FreeRTOS.h"
#include "task.h"
void vTaskList( char *pcWriteBuffer );
pcWriteBuffer存储表格文本的缓冲区。该缓冲区必须足够大,否则可能导致溢出vTaskList()是一个工具函数,不是内核的一部分vTaskList()会在执行期间禁用中断,可能不适合硬实时应用- 使用前需要在
FreeRTOSConfig.h中将configUSE_TRACE_FACILITY和configUSE_STATS_FORMATTING_FUNCTIONS设置为 1
FreeRTOSConfig.h 路径
shell
"idf安装路径"\v5.3.1\esp-idf\components\freertos\config\include\freertos
在实际使用中,需要点击 VSCode 左下角的 menuconfig(小齿轮) -> Partition Table -> FAT Filesystem support -> Kernel 中勾选 configUSE_TRACE_FACILITY 和 configUSE_STATS_FORMATTING_FUNCTIONS(勾选第一个以后会出现第二个)

表格内容
- Name:任务创建时指定的名称
- State :任务的当前状态:
X任务正在执行(即调用vTaskList()的任务)B任务处于阻塞状态R任务处于就绪状态S任务处于挂起状态,或处于无超时的阻塞状态。D任务已被删除,但其内存尚未被空闲任务释放。
- Priority:任务的优先级
- Stack:任务堆栈的"高水位标记",显示任务生命周期中堆栈的最小可用量,值越接近零,任务越接近堆栈溢出,这个参数用于检测堆栈是否够用
- Num:任务的唯一编号,用于区分名称相同的任务
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void myTask(void *pvPamra)
{
printf("Test begin.\n");
vTaskSuspendAll();
for (int i = 0; i < 9999; i++)
{
for (int j = 0; j < 999; j++)
{
;
}
}
xTaskResumeAll();
vTaskDelay(1000 / portTICK_PERIOD_MS);
printf("Test end.\n");
vTaskDelete(NULL);
}
void myTask2(void *pvParam)
{
while (true)
{
printf("Task2 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main(void)
{
TaskHandle_t myHandle = NULL;
TaskHandle_t myHandle2 = NULL;
static char pcWriteBuffer[512] = {0}; // 创建缓冲区
xTaskCreate(myTask, "myTask", 4096, NULL, 1, &myHandle);
xTaskCreate(myTask2, "myTask2", 4096, NULL, 1, &myHandle2);
while (true)
{
vTaskList(pcWriteBuffer); // 传入缓冲区
printf("-------------------------------------------\n");
printf("Name State Priority Stack Num\n"); // 打印Title
printf("%s\n", pcWriteBuffer);
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
输出结果:

Task Stack Setting(任务堆栈设置)
在使用 xTaskCreate() 时,需要设置堆栈大小
usStackDepth 参数描述
freeRTOS 中的 usStackDepth 参数用于指定任务栈的大小。每个任务在创建时由内核分配一个独立的栈。usStackDepth 表示栈可以容纳的字数(而非字节数)。例如,在一个栈宽为 4 字节的架构中,如果 usStackDepth 设为 100,那么将分配 400 字节的栈空间(100 * 4 字节)。栈深度与栈宽的乘积不能超过 size_t 类型变量能表示的最大值。
空闲任务使用的栈大小由应用程序定义的常量 configMINIMAL_STACK_SIZE 来设定。这个常量在特定微控制器架构的示例程序中设为推荐的最小值。若任务需要更多的栈空间,则需要为 usStackDepth 指定更大的值。
uxTaskGetStackHighWaterMark()
在FreeRTOS中,每个任务维护自己的栈空间,栈的总大小在任务创建时指定。uxTaskGetStackHighWaterMark() 函数用于查询任务的栈空间使用情况,返回一个称为"高水位标记"的值,用于判断任务在执行过程中栈空间使用是否接近溢出。
通过这个函数估计程序栈的大小,相较于 TaskList() 占用资源少
c
#include "FreeRTOS.h"
#include "task.h"
UBaseType_t uxTaskGetStackHighWaterMark( TaskHandle_t xTask );
- xTask :需要查询栈高水位标记的任务句柄
- 通过
xTaskCreate()创建任务后可使用pxCreatedTask获取句柄,或通过xTaskCreateStatic()创建静态任务并存储返回值,还可以直接用任务名调用xTaskGetHandle()来获取句柄 - 若任务查询自身栈高水位标记,可以传递
NULL作为句柄
- 通过
uxTaskGetStackHighWaterMark()返回任务开始执行以来的最小剩余栈空间量,即栈使用最深时的未使用栈空间。高水位标记越接近零,任务的栈空间越接近溢出
注意
uxTaskGetStackHighWaterMark()执行时间较长,因此建议仅在测试和调试版本中使用。需要在FreeRTOSConfig.h中将INCLUDE_uxTaskGetStackHighWaterMark设置为1,才能使用该函数(这个貌似不用设置,但是手册中有写)
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void myTask(void *pvPamra)
{
while (true)
{
printf("Task1 is running.\n");// 这行起不起作用,对iStack影响较大
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main(void)
{
TaskHandle_t myHandle = NULL;
xTaskCreate(myTask, "myTask", 1024, NULL, 1, &myHandle);
UBaseType_t iStack = 0;
while (true)
{
iStack = uxTaskGetStackHighWaterMark(myHandle);
printf("task1 iStack = %d\n", iStack);
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
如果堆栈设置太小:
ERROR: A stack overflow in task myTask has been detected.
Task Watch Dog(任务看门狗)
官方文档
https://docs.espressif.com/projects/esp-idf/en/v5.2.3/esp32/api-reference/system/wdts.html
ESP-IDF 支持以下类型的看门狗定时器:
- 中断看门狗定时器 (IWDT)
- 任务看门狗定时器 (TWDT)
中断看门狗负责确保 ISR(中断服务程序)不被长时间阻塞,TWDT 负责检测任务长时间运行而不让步的情况
Interrupt Watchdog Timer (IWDT)
中断看门狗定时器 (IWDT) 的目的 是确保中断服务程序 (ISR) 不会长时间被阻止执行,从而避免中断延迟,并保证任务切换正常进行(因为任务切换是通过 ISR 实现的)。IWDT 会监控 FreeRTOS 的时钟中断,确保在设定的超时时间内每个 CPU 都能运行时钟中断,如果没有运行,说明有某些情况正在阻止 ISR 的执行。可能导致这种阻塞的原因包括:
- 禁用中断
- 进入关键区段(也会禁用中断)
- 其他相同或更高优先级的 ISR 阻止当前 ISR 的执行
IWDT 的实现 是通过定时器组 1 的看门狗定时器,结合 FreeRTOS 在每个 CPU 上的时钟中断来喂狗。如果某个 CPU 的时钟中断未能在 IWDT 超时时间内执行,则看门狗会触发超时。此时,默认行为是调用 panic 处理程序,并显示超时原因,如 Interrupt wdt timeout on CPU0 或 Interrupt wdt timeout on CPU1。用户可以通过调试工具(如回溯、OpenOCD、gdbstub)定位超时的原因,或在生产环境中选择直接重启芯片
如果 panic 处理程序在 IWDT 超时后也未能执行,IWDT 会触发第二阶段超时,导致系统硬重启
Panic 处理程序 (Panic Handler)是 ESP-IDF 中的一个机制,用于在系统检测到严重错误(如看门狗超时、非法内存访问等)时触发紧急处理。它的主要作用是收集错误信息,便于调试,并根据配置执行特定的恢复操作,例如重启系统
系统硬重启(Hard Reset 或 Hardware Reset)是指通过硬件的方式将系统直接重启,而不经过正常的关机或软件重启过程。硬重启会立即停止当前所有操作,清除 RAM 中的数据,并重新启动系统。在嵌入式设备(如 ESP32)中,硬重启通常是为了在系统出现严重错误、无法继续执行时快速恢复系统的正常运行。
配置
- 启用 IWDT :IWDT 默认通过
CONFIG_ESP_INT_WDT选项启用 - 设置超时时间 :通过
CONFIG_ESP_INT_WDT_TIMEOUT_MS配置超时时间。如果启用了 PSRAM 支持,默认超时时间会更高,因为访问大量 PSRAM 的中断或关键区段执行时间较长。建议超时时间至少为 FreeRTOS 时钟周期的两倍

优化建议
如果 IWDT 超时是由于中断或关键区段执行时间过长导致,可以考虑以下优化:
- 缩短关键区段:将非关键的代码和计算放在关键区段外执行,以减少在关键区段内的处理时间。
- 简化 ISR:中断处理程序应尽可能少地进行计算,最好将计算任务转移给任务来执行(例如使用队列将数据传递给任务)。
- 避免阻塞 :关键区段和 ISR 都不应阻塞等待事件发生。
如果无法缩短执行时间,可以通过增加CONFIG_ESP_INT_WDT_TIMEOUT_MS的值来延长超时时间。
Task Watchdog Timer (TWDT)
任务看门狗定时器(TWDT)用于监控特定任务,确保它们能够在设定的超时时间内执行。它主要监控每个 CPU 的空闲任务(Idle Task,缺省状态下监控),但也支持其他任务或用户自定义代码订阅。通过监控,TWDT 能够检测任务长时间运行未让出 CPU 的情况,例如死循环或不正确的代码逻辑。
Core functions
- 目标: 检测任务未及时让出 CPU 的问题,这可能导致系统卡死。
- 机制: 基于硬件看门狗定时器(Hardware Watchdog Timer),当发生超时时触发中断。
- 自定义: 用户可定义
esp_task_wdt_isr_user_handler来处理超时事件。 - TWDT 有助于检测并处理无响应或卡死任务,提高系统稳定性。
Usage
需要包含
c
components/esp_system/include/esp_task_wdt.h
#include "esp_task_wdt.h"
- 初始化与监控:
esp_task_wdt_init(): 初始化 TWDT 并监控空闲任务。esp_task_wdt_add(): 订阅特定任务以接受 TWDT 监控。esp_task_wdt_reset(): 在任务中调用以重置 TWDT,防止触发超时。
- 订阅管理:
esp_task_wdt_delete(): 取消对某任务的监控。esp_task_wdt_deinit(): 取消对空闲任务的监控并注销 TWDT。
Configuration
- 默认超时时间: 通过
CONFIG_ESP_TASK_WDT_TIMEOUT_S配置,需设置为任务所需最长运行时间。 - 动态调整: 可使用
esp_task_wdt_init()在运行时修改超时时间。
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void myTask(void *pvPamra)
{
while (true)
{
; // 不断占用
}
}
void app_main(void)
{
TaskHandle_t myHandle = NULL;
xTaskCreate(myTask, "myTask", 1024, NULL, 1, &myHandle);
}
这个程序会导致 TWDT 触发
error 分析(来自 GPT-4o):
c
E (15315) task_wdt: Task watchdog got triggered.
// 错误时间戳为 15315 ms,任务看门狗(TWDT)触发超时。说明至少一个被监控的任务未在指定时间内重置看门狗。
E (15315) task_wdt: The following tasks/users did not reset the watchdog in time:
// 以下任务或用户未按时重置看门狗。
E (15315) task_wdt: - IDLE0 (CPU 0)
// CPU 0 的空闲任务(IDLE0)未及时运行,从而导致看门狗超时。
// 这通常是由于高优先级任务长期占用 CPU,阻塞了空闲任务的运行。
E (15315) task_wdt: Tasks currently running:
// 当前正在运行的任务状态。
E (15315) task_wdt: CPU 0: myTask
// CPU 0 正在运行用户定义的任务 `myTask`,说明此任务可能长期占用了 CPU,没有及时让出给空闲任务。
E (15315) task_wdt: CPU 1: IDLE1
// CPU 1 正常运行其空闲任务(IDLE1),表明问题仅发生在 CPU 0 上。
E (15315) task_wdt: Print CPU 0 (current core) backtrace
// 打印 CPU 0 上的回溯信息,以帮助进一步定位问题的代码路径。
Backtrace: 0x400D6D6B:0x3FFB0F80 0x400D7130:0x3FFB0FA0 0x40082DD1:0x3FFB0FD0 0x400E207C:0x3FFB5960 0x40085F89:0x3FFB5980
// 回溯路径,显示出错时的函数调用栈。
//
// 1. 0x400D6D6B: task_wdt_timeout_handling at task_wdt.c:434
// - `task_wdt_timeout_handling` 是任务看门狗的超时处理函数。
// 它被调用以记录超时信息并触发回溯打印。
//
// 2. 0x400D7130: task_wdt_isr at task_wdt.c:507
// - `task_wdt_isr` 是任务看门狗中断服务程序,负责在超时时触发中断并调用相应的超时处理函数。
//
// 3. 0x40082DD1: _xt_lowint1 at xtensa_vectors.S:1240
// - 这是 FreeRTOS 的低级中断处理程序,处理任务切换和中断管理。
//
// 4. 0x400E207C: myTask at hello_world_main.c:17
// - 用户定义的任务 `myTask` 在运行时阻塞了 CPU 0 的空闲任务,导致看门狗触发。
// 这里指出 `myTask` 的实现可能存在问题,需进一步检查代码逻辑。
//
// 5. 0x40085F89: vPortTaskWrapper at port.c:134
// - FreeRTOS 的任务封装函数,负责调用用户任务并处理任务调度。
原因:myTask 优先级是 1,比 Idle Task(0)高,且其中没有阻塞函数,因此不断在运行,而 Idle Task 得不到运行
改进:
- 在 Task 中调用阻塞函数(如 delay),此时 scheduler 可以让低优先级别的 Task 得以运行,此时低优先级 Task 可以"喂狗"
- 改变当前任务的优先级别,此时 scheduler 会自动分配给两个任务相同的时间片
esp_task_wdt_add() and esp_task_wdt_reset()
将一个任务订阅到TWDT。订阅后,该任务需要定期调用 esp_task_wdt_reset() 来防止 TWDT 超时。如果未及时调用,将触发 TWDT 超时。
c
esp_err_t esp_task_wdt_add(TaskHandle_t)
task_handle: 任务的句柄。- 传入
NULL时,表示将当前运行的任务订阅到 TWDT
- 传入
esp_err_tESP_OK: 成功订阅任务到 TWDT- 其他值: 订阅失败
Example
c
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
#include "esp_task_wdt.h"
void myTask(void *pvPamra)
{
while (true)
{
printf("Task1 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void myTask2(void *pvPamra)
{
esp_task_wdt_add(NULL);
while (true)
{
printf("Task2 is running.\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
//esp_task_wdt_reset();// 注释掉时,即使优先级相同,依然会触发watch dog
}
}
void app_main(void)
{
TaskHandle_t myHandle = NULL;
xTaskCreate(myTask, "myTask", 1024, NULL, 0, &myHandle);
xTaskCreate(myTask2, "myTask2", 1024, NULL, 0, NULL);
}
error:
c
E (5315) task_wdt: Task watchdog got triggered.
// 表示任务看门狗定时器(TWDT)触发了超时,因为某些任务没有按时调用 `esp_task_wdt_reset()`。
E (5315) task_wdt: The following tasks/users did not reset the watchdog in time:
// 以下是未按时重置看门狗的任务或用户。
E (5315) task_wdt: - myTask2 (CPU 0/1)
// 任务 `myTask2` 是导致 TWDT 超时的任务。该任务运行在 CPU 0 或 CPU 1 上,没有按时调用 `esp_task_wdt_reset()`。
E (5315) task_wdt: Tasks currently running:
// 列出了当前运行在每个核心上的任务。
E (5315) task_wdt: CPU 0: IDLE0
// CPU 0 当前运行的是 IDLE 任务(空闲任务)。
E (5315) task_wdt: CPU 1: IDLE1
// CPU 1 当前运行的是 IDLE 任务(空闲任务)。
E (5315) task_wdt: Print CPU 0 (current core) backtrace
// 打印 CPU 0 上当前任务的回溯信息,以便调试和定位问题。