【ESP-IDF FreeRTOS】任务管理

2024-08-22 17:36
文章标签 管理 任务 idf freertos esp

本文主要是介绍【ESP-IDF FreeRTOS】任务管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇我们介绍了延时函数。讲到了因为FreeRTOS是多任务的,因此我们使用延时函数可以将当前任务挂起,就可以让CPU去执行其他任务,等到延时时间结束,再接着执行之前的任务,这样可以充分利用CPU的性能。

那么既然ESP-IDF FreeRTOS是多任务的,那么我们必然是可以创建任务的,之前一直用的app_main是ESP-IDF FreeRTOS默认创建的main任务。

创建任务可用的函数有俩,如果是多核的ESP32,那么可以使用四个(不过本质上还是两个,这个后面再说)

static inline BaseType_t xTaskCreate(TaskFunction_t pxTaskCode, const char *const pcName, const configSTACK_DEPTH_TYPE usStackDepth, void *const pvParameters, UBaseType_t uxPriority, TaskHandle_t *const pxCreatedTask)

上面这个函数就可以创建出一个新的任务并且添加到准备运行的任务列表中,也就是说我们用这个函数一旦创建了任务,那么马上就会准备开始执行。

第一个参数传入函数指针,这个函数里的内容就是我们这个任务要执行的内容。需要注意的是这个任务无返回值,可以接受一个void*类型的参数(也可以不接收)。另外这个函数要么内部是死循环,要么需要自己把自己杀死,否则编译报错。具体如何杀死自己,后面会介绍。

我们可以看看参数一的类型,看看这个任务函数的原型是什么。

类型很简单,就是无返回值,可以有个void*类型参数的函数。

第二个参数传入一个字符串表示这个任务的名字,这个随便起,不懂起名字的小伙伴可以和自己的任务函数的函数名一致(记得是传入字符串)。

第三个参数传入这个任务函数的栈大小,单位是字(word),我们ESP32是32位的,因此一个字是4个Byte,这个单位换算要清楚。每个任务函数都有各自独立的栈,我们需要各自指定栈的大小,这个栈的大小我们大概的估计一下就行,不用很精确。一般来说不确定就给个1024,多余总比不够用好,不过还是要具体问题具体分析。

如果这个栈大小给的少了,那么当执行这个任务的时候,会导致我们ESP32不停地重启。

第四个参数传入给任务函数传的参数,没有就填NULL。

第五个是优先级,数值越小优先级越小(注意这个,这边和STM32中的优先级不一样)。优先级用于任务的调度,也就是说多个任务理论上是可以看成是并行的,这个我们后面会验证。

优先级的数值我们可以根据宏定义configMAX_PRIORITIES来判断,这个宏定义就是我们能用的最大的优先级+1(优先级从0开始,一共有configMAX_PRIORITIES这么多个的等级)。

第六个是任务句柄,是传出参数,我们可以先定义一个任务句柄,然后在创建任务的时候传进去。任务句柄主要是用来删除任务的,当然删除任务并不一定是要使用到任务句柄,也可以让任务自杀,这个后面会说。

多个任务可以共用同一个函数。可以使用传入的参数来区分是哪个任务调用的。

那么下面就简单举个例子。

下面创建了两个任务,调用的是同一个函数,通过参数的不同来打印不同的内容。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"void test(void* who){int* val = who;printf("this is test%d\r\n",*val);while(1){printf("hello%d\r\n",*val);vTaskDelay(pdMS_TO_TICKS(1000));}
}void app_main(void){TaskHandle_t test1_handle,test2_handle;int t1 = 1,t2 = 2;xTaskCreate(test,"test1",1024*2,&t1,configMAX_PRIORITIES/2,&test1_handle);xTaskCreate(test,"test2",1024*2,&t2,configMAX_PRIORITIES/2,&test2_handle);printf("this is main\r\n");while(1){printf("world\r\n");vTaskDelay(pdMS_TO_TICKS(1000));}
}

上面这个算是我们用的最多的创建任务的函数了,它会帮我们自动分配内存,当然我们也可以使用其他函数来自己给任务分配内存。

static inline TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode, const char *const pcName, const uint32_t ulStackDepth, void *const pvParameters, UBaseType_t uxPriority, StackType_t *const puxStackBuffer, StaticTask_t *const pxTaskBuffer)

前面几个参数和自动分配内存的函数的参数是一样的,不一样的是最后两个。

分别是我们给它静态分配的栈,最后一个是一个结构体,它保存的是任务的数据结构。

且这个函数的返回值是任务的句柄,而我们动态分配的函数的任务句柄是通过最后一个传出参数给我们的,也就是说静态分配比动态分配要多出两个参数。

静态分配内存比较复杂,且我们大多情况使用动态分配内存的函数创建任务就够了,所以这边就贴上官方的示例代码,感兴趣的小伙伴看看就行。

// Dimensions of the buffer that the task being created will use as its stack.
// NOTE:  This is the number of words the stack will hold, not the number of
// bytes.  For example, if each stack item is 32-bits, and this is set to 100,
// then 400 bytes (100 * 32-bits) will be allocated.
#define STACK_SIZE 200
// Structure that will hold the TCB of the task being created.
StaticTask_t xTaskBuffer;
// Buffer that the task being created will use as its stack.  Note this is
// an array of StackType_t variables.  The size of StackType_t is dependent on
// the RTOS port.
StackType_t xStack[ STACK_SIZE ];
// Function that implements the task being created.
void vTaskCode( void * pvParameters ){
// The parameter value is expected to be 1 as 1 is passed in the
// pvParameters value in the call to xTaskCreateStatic().configASSERT( ( uint32_t ) pvParameters == 1UL );for( ;; ){// Task code goes here.}
}// Function that creates a task.
void vOtherFunction( void ){TaskHandle_t xHandle = NULL;// Create the task without using any dynamic memory allocation.xHandle = xTaskCreateStatic(vTaskCode,       // Function that implements the task."NAME",          // Text name for the task.STACK_SIZE,      // Stack size in words, not bytes.( void * ) 1,    // Parameter passed into the task.tskIDLE_PRIORITY,// Priority at which the task is created.xStack,          // Array to use as the task's stack.&xTaskBuffer );  // Variable to hold the task's data structure.
// puxStackBuffer and pxTaskBuffer were not NULL, so the task will have
// been created, and xHandle will be the task's handle.  Use the handle
// to suspend the task.vTaskSuspend( xHandle );
}

细心的小伙伴可能发现了,上面出现了一个我们没见过的函数vTaskSuspend。

我们先简单介绍一下任务的几种状态。

首先我们通过ESP-IDF FreeRTOS可以创建多个任务,正常情况下任务是“并行”的(看起来是并行的,实际上是通过分割时间片来轮流运行,因为太快了所以在我们看来就是并行的),但严格说来,在一个时间点上,CPU只能执行一个任务(多核除外,多核是每个核都可以执行一个任务),那么正在执行的任务就处在运行状态,而其他没执行的,在等待执行的任务就处在就绪状态。

如果有个任务执行了vTaskDelay或者xTaskDelayUntil,那么它会暂时挂起,也就是阻塞状态,当然除了这俩函数会让任务阻塞,其他还有很多办法,这个我们后面再说。

如果有个任务,我现在不想让它执行了,那我可以删除它,可我万一以后还需要它,我只要它暂时消失怎么办呢?那么我们可以让它处于暂停状态,等我要它了,我再唤醒它,让它再次处于就绪状态。

而vTaskSuspend就是让任务处于暂停状态的函数。

void vTaskSuspend(TaskHandle_t xTaskToSuspend)

传入任务句柄即可将此任务暂停,如果传入的是NULL,那么是暂停自己(调用这个函数的任务)。

当我们需要它了,我们用下面这个函数再次唤醒它。

void vTaskResume (TaskHandle_t xTaskToResume)

传入任务句柄即可唤醒对应任务,但是需要注意的是,不能通过传入NULL来自己唤醒自己,这个应该不难理解。

接下来来个小例子示范一下这俩函数。

在主函数里计数,计到3秒的时候把test任务暂停(打印hello),计到六秒的时候再继续。

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"void test(void*){printf("this is test\r\n");while(1){printf("hello\r\n");vTaskDelay(pdMS_TO_TICKS(1000));}
}void app_main(void){TaskHandle_t test1_handle;xTaskCreate(test,"test1",1024*2,NULL,configMAX_PRIORITIES/2,&test1_handle);printf("this is main\r\n");int count = 0;TickType_t curTime = xTaskGetTickCount();while(1){printf("count is %d\r\n",++count);if(count == 3) vTaskSuspend(test1_handle);else if(count ==6) vTaskResume(test1_handle);xTaskDelayUntil(&curTime,pdMS_TO_TICKS(1000));}
}

关于任务状态,我们再介绍一个函数。

void vTaskDelete(TaskHandle_t xTaskToDelete)

传入任务句柄来删除对应任务,如果传入NULL,那么当前任务自己删除自己(紫砂)。

关于创建任务,ESP-IDF FreeRTOS还提供了另外两个函数。

BaseType_t xTaskCreatePinnedToCore(TaskFunction_t pxTaskCode, const char *const pcName, const uint32_t ulStackDepth, void *const pvParameters, UBaseType_t uxPriority, TaskHandle_t *const pxCreatedTask, const BaseType_t xCoreID)

TaskHandle_t xTaskCreateStaticPinnedToCore(TaskFunction_t pxTaskCode, const char *const pcName, const uint32_t ulStackDepth、void *const pvParameters、UBaseType_t uxPriority、StackType_t *const puxStackBuffer、 StaticTask_t *const pxTaskBuffer, const BaseType_t xCoreID)

简单来说这两个函数就是乐鑫为了适配多核系统给整出来的,也就是说这俩函数只能在ESP-IDF FreeRTOS里使用。它们相较于上面动态分配和静态分配的创建任务的函数就多了一个参数,这个参数来指定我们这个任务固定到哪个核心上去。

但我们平常使用不用专门去记这两个,第一是普适性不强,只能在ESP-IDF FreeRTOS中,并且是双核的ESP32才能使用。第二是就算我们使用了xTaskCreate,如果我们的芯片支持双核,那么它底层还是会帮我们调用多核版本的。

默认帮我设置的核心是不固定核心,可以在任意一个核心上运行,这也算是自动分配了。

我们可以使用下面这个函数获取当前内核管理的任务数。

UBaseType_t uxTaskGetNumberOfTasks(void)

这篇关于【ESP-IDF FreeRTOS】任务管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1096938

相关文章

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用

如何使用Python实现一个简单的window任务管理器

《如何使用Python实现一个简单的window任务管理器》这篇文章主要为大家详细介绍了如何使用Python实现一个简单的window任务管理器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 任务管理器效果图完整代码import tkinter as tkfrom tkinter i

Spring Boot 集成 Quartz 使用Cron 表达式实现定时任务

《SpringBoot集成Quartz使用Cron表达式实现定时任务》本文介绍了如何在SpringBoot项目中集成Quartz并使用Cron表达式进行任务调度,通过添加Quartz依赖、创... 目录前言1. 添加 Quartz 依赖2. 创建 Quartz 任务3. 配置 Quartz 任务调度4. 启

Java使用多线程处理未知任务数的方案介绍

《Java使用多线程处理未知任务数的方案介绍》这篇文章主要为大家详细介绍了Java如何使用多线程实现处理未知任务数,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 知道任务个数,你可以定义好线程数规则,生成线程数去跑代码说明:1.虚拟线程池:使用 Executors.newVir

nvm如何切换与管理node版本

《nvm如何切换与管理node版本》:本文主要介绍nvm如何切换与管理node版本问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录nvm切换与管理node版本nvm安装nvm常用命令总结nvm切换与管理node版本nvm适用于多项目同时开发,然后项目适配no

Spring Boot中定时任务Cron表达式的终极指南最佳实践记录

《SpringBoot中定时任务Cron表达式的终极指南最佳实践记录》本文详细介绍了SpringBoot中定时任务的实现方法,特别是Cron表达式的使用技巧和高级用法,从基础语法到复杂场景,从快速启... 目录一、Cron表达式基础1.1 Cron表达式结构1.2 核心语法规则二、Spring Boot中定

Redis实现RBAC权限管理

《Redis实现RBAC权限管理》本文主要介绍了Redis实现RBAC权限管理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1. 什么是 RBAC?2. 为什么使用 Redis 实现 RBAC?3. 设计 RBAC 数据结构

mac安装nvm(node.js)多版本管理实践步骤

《mac安装nvm(node.js)多版本管理实践步骤》:本文主要介绍mac安装nvm(node.js)多版本管理的相关资料,NVM是一个用于管理多个Node.js版本的命令行工具,它允许开发者在... 目录NVM功能简介MAC安装实践一、下载nvm二、安装nvm三、安装node.js总结NVM功能简介N

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

Spring Boot 整合 ShedLock 处理定时任务重复执行的问题小结

《SpringBoot整合ShedLock处理定时任务重复执行的问题小结》ShedLock是解决分布式系统中定时任务重复执行问题的Java库,通过在数据库中加锁,确保只有一个节点在指定时间执行... 目录前言什么是 ShedLock?ShedLock 的工作原理:定时任务重复执行China编程的问题使用 Shed