ESP32学习笔记_FreeRTOS(1)——Task的创建和使用

摘要(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(任务优先级))
    • [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 Stack Setting(任务堆栈设置)](#Task Stack Setting(任务堆栈设置))
    • [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())

参考资料:

Michael_ee视频教程
freeRTOS官网
espressif 在线文档

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)
    • 示例pdTRUEpdFALSE
    • 解释 :在 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);
    • 解释 :返回任务的状态(例如 eRunningeReady 等),类型为枚举 eTaskState

FreeRTOS Startup Process(FreeRTOS 程序启动过程)

  1. 一级 (ROM) 引导加载程序 被固化在了 ESP32 内部的 ROM 中(只读),它会从 flash 的 0x1000 偏移地址处加载二级引导加载程序至 RAM (IRAM & DRAM) 中。

  2. 二级引导加载程序 从 flash 中加载分区表和主程序镜像至内存中(可更改),主程序中包含了 RAM 段和通过 flash 高速缓存映射的只读段。

  3. 应用程序启动阶段 运行,这时第二个 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 的启动过程中,函数调用顺序决定了系统的初始化步骤,加载的顺序从底层硬件初始化到应用程序的入口。以下是对每个函数的解释和调用顺序的描述:

  1. ENTRY(call_start_cpu0)
    • 这是启动代码的入口点。ENTRY 通常是汇编语言定义的宏,指向 call_start_cpu0,并由复位向量在芯片复位时触发。它将控制权转交给 call_start_cpu0 函数。
  2. call_start_cpu0
    • call_start_cpu0 是引导代码的核心初始化函数,它执行一些重要的系统初始化,包括时钟、缓存和其他硬件设置。
    • 这个函数负责将 CPU0 初始化到运行模式,为后续的系统初始化准备好环境。
  3. SYS_STARTUP_FN
    • SYS_STARTUP_FN 是宏定义,用于确定 g_startup_fn 函数的调用。它是可移植的宏,确保不同的启动函数可以根据系统需求被调用。
    • 通常,它会在启动流程中插入系统特定的初始化函数。
  4. g_startup_fn
    • g_startup_fn 是一个指向启动函数的指针。它会指向 start_cpu0,并负责调用该函数。
    • 通过使用指针,可以灵活地配置不同的启动行为,而无需更改启动顺序。
  5. start_cpu0
    • 该函数继续初始化工作,包括设置存储器和中断。它为接下来的 start_cpu0_default 函数创建一个清晰的环境。
    • start_cpu0 主要用于处理 CPU0 的硬件层面初始化。
  6. start_cpu0_default
    • 这个函数主要是 CPU0 的默认启动程序,用于配置其他硬件模块并启动操作系统内核(如 FreeRTOS)。
    • 在 FreeRTOS 中,它负责创建一个名为 main_task 的主任务。
  7. esp_startup_start_app
    • esp_startup_start_app 是一个关键函数,开始运行应用程序层代码。它将启动操作系统内核,并最终创建应用程序的主任务(main_task)。
  8. main_task
    • main_task 是应用程序的起点,由操作系统调度并管理。该任务初始化应用程序环境并启动 app_main 函数。
  9. 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(如 ESP321)会导致任务创建失败
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_SELECTIONFreeRTOSConfig.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 的作用

  1. 任务调度
    调度器根据任务列表中的信息决定哪个任务应被调度执行。任务状态改变时,调度器会更新任务列表
  2. 任务状态跟踪
    系统通过任务列表了解每个任务的状态(例如运行、就绪、阻塞或挂起),从而确保任务在合适的状态下被调度
  3. 任务优先级管理
    任务列表按优先级存储任务。FreeRTOS 会优先调度高优先级任务,因此高优先级任务通常排在任务列表的前面
  4. 资源管理
    当任务等待某些资源(例如队列、信号量或事件)时,任务会进入"阻塞"状态并被放入相应的阻塞列表中,等待条件满足后再回到就绪列表

常见的任务列表类型

  • 就绪任务列表:存储所有准备运行的任务
  • 延时任务列表 :存储被延迟的任务(例如调用 vTaskDelay() 的任务)
  • 阻塞任务列表:存储等待事件(如队列、信号量等)的任务

vTaskList()

vTaskList() 用于生成一个包含当前系统中所有任务状态的表格,并将表格数据写入指定的字符缓冲区,显示了每个任务的状态、优先级、堆栈使用情况等信息

c 复制代码
#include "FreeRTOS.h"
#include "task.h"

void vTaskList( char *pcWriteBuffer );
  • pcWriteBuffer 存储表格文本的缓冲区。该缓冲区必须足够大,否则可能导致溢出
  • vTaskList() 是一个工具函数,不是内核的一部分
  • vTaskList() 会在执行期间禁用中断,可能不适合硬实时应用
  • 使用前需要在 FreeRTOSConfig.h 中将 configUSE_TRACE_FACILITYconfigUSE_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 CPU0Interrupt 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"
  1. 初始化与监控:
    • esp_task_wdt_init(): 初始化 TWDT 并监控空闲任务。
    • esp_task_wdt_add(): 订阅特定任务以接受 TWDT 监控。
    • esp_task_wdt_reset(): 在任务中调用以重置 TWDT,防止触发超时。
  2. 订阅管理:
    • 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 得不到运行

改进:

  1. 在 Task 中调用阻塞函数(如 delay),此时 scheduler 可以让低优先级别的 Task 得以运行,此时低优先级 Task 可以"喂狗"
  2. 改变当前任务的优先级别,此时 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_t
    • ESP_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 上当前任务的回溯信息,以便调试和定位问题。
相关推荐
寒笙LED7 分钟前
C++详细笔记(六)string库
开发语言·c++·笔记
码上有前13 分钟前
解析后端框架学习:从单体应用到微服务架构的进阶之路
学习·微服务·架构
IT书架14 分钟前
golang面试题
开发语言·后端·golang
初遇你时动了情31 分钟前
uniapp 城市选择插件
开发语言·javascript·uni-app
岳不谢40 分钟前
VPN技术-VPN简介学习笔记
网络·笔记·学习·华为
海害嗨1 小时前
阿里巴巴官方「SpringCloudAlibaba全彩学习手册」限时开源!
学习·开源
zongzi_4941 小时前
二次封装的天气时间日历选择组件
开发语言·javascript·ecmascript
平头哥在等你1 小时前
求一个3*3矩阵对角线元素之和
c语言·算法·矩阵
kikyo哎哟喂2 小时前
Java 代理模式详解
java·开发语言·代理模式
duration~2 小时前
SpringAOP模拟实现
java·开发语言