freeRTOS系列教程之第三章任务管理

Posted 韦东山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了freeRTOS系列教程之第三章任务管理相关的知识,希望对你有一定的参考价值。

需要获取更好阅读体验的同学,请访问我专门设立的站点查看,地址:http://rtos.100ask.net/

系列教程总目录

本教程连载中,篇章会比较多,为方便同学们阅读,点击这里可以查看文章的 目录列表,目录列表页面地址:https://blog.csdn.net/thisway_diy/article/details/121399484

概述

在本章中,会涉及如下内容:

  • FreeRTOS如何给每个任务分配CPU时间
  • 如何选择某个任务来运行
  • 任务优先级如何起作用
  • 任务有哪些状态
  • 如何实现任务
  • 如何使用任务参数
  • 怎么修改任务优先级
  • 怎么删除任务
  • 怎么实现周期性的任务
  • 如何使用空闲任务

3.1 基本概念

对于整个单片机程序,我们称之为application,应用程序。

使用FreeRTOS时,我们可以在application中创建多个任务(task),有些文档把任务也称为线程(thread)。

以日常生活为例,比如这个母亲要同时做两件事:

  • 喂饭:这是一个任务
  • 回信息:这是另一个任务

这可以引入很多概念:

  • 任务状态(State):
    • 当前正在喂饭,它是running状态;另一个"回信息"的任务就是"not running"状态
    • "not running"状态还可以细分:
      • ready:就绪,随时可以运行
      • blocked:阻塞,卡住了,母亲在等待同事回信息
      • suspended:挂起,同事废话太多,不管他了
  • 优先级(Priority)
    • 我工作生活兼顾:喂饭、回信息优先级一样,轮流做
    • 我忙里偷闲:还有空闲任务,休息一下
    • 厨房着火了,什么都别说了,先灭火:优先级更高
  • 栈(Stack)
    • 喂小孩时,我要记得上一口喂了米饭,这口要喂青菜了
    • 回信息时,我要记得刚才聊的是啥
    • 做不同的任务,这些细节不一样
    • 对于人来说,当然是记在脑子里
    • 对于程序,是记在栈里
    • 每个任务有自己的栈
  • 事件驱动
    • 孩子吃饭太慢:先休息一会,等他咽下去了、等他提醒我了,再喂下一口
  • 协助式调度(Co-operative Scheduling)
    • 你在给同事回信息
      • 同事说:好了,你先去给小孩喂一口饭吧,你才能离开
      • 同事不放你走,即使孩子哭了你也不能走
    • 你好不容易可以给孩子喂饭了
      • 孩子说:好了,妈妈你去处理一下工作吧,你才能离开
      • 孩子不放你走,即使同事连发信息你也不能走

这涉及很多概念,后续章节详细分析。

3.2 任务创建与删除

3.2.1 什么是任务

在FreeRTOS中,任务就是一个函数,原型如下:

void ATaskFunction( void *pvParameters );

要注意的是:

  • 这个函数不能返回
  • 同一个函数,可以用来创建多个任务;换句话说,多个任务可以运行同一个函数
  • 函数内部,尽量使用局部变量:
    • 每个任务都有自己的栈
    • 每个任务运行这个函数时
      • 任务A的局部变量放在任务A的栈里、任务B的局部变量放在任务B的栈里
      • 不同任务的局部变量,有自己的副本
    • 函数使用全局变量、静态变量的话
      • 只有一个副本:多个任务使用的是同一个副本
      • 要防止冲突(后续会讲)

下面是一个示例:

void ATaskFunction( void *pvParameters )

	/* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */
	int32_t lVariableExample = 0;
	
    /* 任务函数通常实现为一个无限循环 */
	for( ;; )
	
		/* 任务的代码 */
	

    /* 如果程序从循环中退出,一定要使用vTaskDelete删除自己
     * NULL表示删除的是自己
     */
	vTaskDelete( NULL );
    
    /* 程序不会执行到这里, 如果执行到这里就出错了 */

3.2.2 创建任务

创建任务时使用的函数如下:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
                        const char * const pcName, // 任务的名字
                        const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
                        void * const pvParameters, // 调用任务函数时传入的参数
                        UBaseType_t uxPriority,    // 优先级
                        TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务

参数说明:

参数描述
pvTaskCode函数指针,可以简单地认为任务就是一个C函数。
它稍微特殊一点:永远不退出,或者退出时要调用"vTaskDelete(NULL)"
pcName任务的名字,FreeRTOS内部不使用它,仅仅起调试作用。
长度为:configMAX_TASK_NAME_LEN
usStackDepth每个任务都有自己的栈,这里指定栈大小。
单位是word,比如传入100,表示栈大小为100 word,也就是400字节。
最大值为uint16_t的最大值。
怎么确定栈的大小,并不容易,很多时候是估计。
精确的办法是看反汇编码。
pvParameters调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters)
uxPriority优先级范围:0~(configMAX_PRIORITIES – 1)
数值越小优先级越低,
如果传入过大的值,xTaskCreate会把它调整为(configMAX_PRIORITIES – 1)
pxCreatedTask用来保存xTaskCreate的输出结果:task handle。
以后如果想操作这个任务,比如修改它的优先级,就需要这个handle。
如果不想使用该handle,可以传入NULL。
返回值成功:pdPASS;
失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因只有内存不足)
注意:文档里都说失败时返回值是pdFAIL,这不对。
pdFAIL是0,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY是-1。

3.2.3 示例1: 创建任务

代码为:FreeRTOS_01_create_task

使用2个函数分别创建2个任务。

任务1的代码:

void vTask1( void *pvParameters )

	const char *pcTaskName = "T1 run\\r\\n";
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	
		/* 打印任务1的信息 */
		printf( pcTaskName );
		
		/* 延迟一会(比较简单粗暴) */
		for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
		
		
	

任务2的代码:

void vTask2( void *pvParameters )

	const char *pcTaskName = "T2 run\\r\\n";
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	
		/* 打印任务1的信息 */
		printf( pcTaskName );
		
		/* 延迟一会(比较简单粗暴) */
		for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
		
		
	

main函数:

int main( void )

	prvSetupHardware();
	
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
	xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);

	/* 启动调度器 */
	vTaskStartScheduler();

	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;

运行结果如下:

注意:

  • task 2先运行!
  • 要分析xTaskCreate的代码才能知道原因:更高优先级的、或者后面创建的任务先运行。

任务运行图:

  • 在t1:Task2进入运行态,一直运行直到t2
  • 在t2:Task1进入运行态,一直运行直到t3;在t3,Task2重新进入运行态

3.2.4 示例2: 使用任务参数

代码为:FreeRTOS_02_create_task_use_params

我们说过,多个任务可以使用同一个函数,怎么体现它们的差别?

  • 栈不同
  • 创建任务时可以传入不同的参数

我们创建2个任务,使用同一个函数,代码如下:

void vTaskFunction( void *pvParameters )

	const char *pcTaskText = pvParameters;
	volatile uint32_t ul; /* volatile用来避免被优化掉 */
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	
		/* 打印任务的信息 */
		printf(pcTaskText);
		
		/* 延迟一会(比较简单粗暴) */
		for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
		
		
	

上述代码中的pcTaskText来自参数pvParameterspvParameters来自哪里?创建任务时传入的。

代码如下:

  • 使用xTaskCreate创建2个任务时,第4个参数就是pvParameters
  • 不同的任务,pvParameters不一样
static const char *pcTextForTask1 = "T1 run\\r\\n";
static const char *pcTextForTask2 = "T2 run\\r\\n";

int main( void )

	prvSetupHardware();
	
	xTaskCreate(vTaskFunction, "Task 1", 1000, (void *)pcTextForTask1, 1, NULL);
	xTaskCreate(vTaskFunction, "Task 2", 1000, (void *)pcTextForTask2, 1, NULL);

	/* 启动调度器 */
	vTaskStartScheduler();

	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;

3.2.5 任务的删除

删除任务时使用的函数如下:

void vTaskDelete( TaskHandle_t xTaskToDelete );

参数说明:

参数描述
pvTaskCode任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。
也可传入NULL,这表示删除自己。

怎么删除任务?举个不好的例子:

  • 自杀:vTaskDelete(NULL)
  • 被杀:别的任务执行vTaskDelete(pvTaskCode),pvTaskCode是自己的句柄
  • 杀人:执行vTaskDelete(pvTaskCode),pvTaskCode是别的任务的句柄

3.2.6 示例3: 删除任务

代码为:FreeRTOS_03_delete_task

本节代码会涉及优先级的知识,可以只看vTaskDelete的用法,忽略优先级的讲解。

我们要做这些事情:

  • 创建任务1:任务1的大循环里,创建任务2,然后休眠一段时间
  • 任务2:打印一句话,然后就删除自己

任务1的代码如下:

void vTask1( void *pvParameters )

	const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL );		
	BaseType_t ret;
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	
		/* 打印任务的信息 */
		printf("Task1 is running\\r\\n");
		
		ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );
		if (ret != pdPASS)
			printf("Create Task2 Failed\\r\\n");
		
		// 如果不休眠的话, Idle任务无法得到执行
		// Idel任务会清理任务2使用的内存
		// 如果不休眠则Idle任务无法执行, 最后内存耗尽
		vTaskDelay( xDelay100ms );
	

任务2的代码如下:

void vTask2( void *pvParameters )
	
	/* 打印任务的信息 */
	printf("Task2 is running and about to delete itself\\r\\n");

	// 可以直接传入参数NULL, 这里只是为了演示函数用法
	vTaskDelete(xTask2Handle);

main函数代码如下:

int main( void )

	prvSetupHardware();
	
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);

	/* 启动调度器 */
	vTaskStartScheduler();

	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;

运行结果如下:

任务运行图:

  • main函数中创建任务1,优先级为1。任务1运行时,它创建任务2,任务2的优先级是2。
  • 任务2的优先级最高,它马上执行。
  • 任务2打印一句话后,就删除了自己。
  • 任务2被删除后,任务1的优先级最高,轮到任务1继续运行,它调用vTaskDelay()进入Block状态
  • 任务1 Block期间,轮到Idle任务执行:它释放任务2的内存(TCB、栈)
  • 时间到后,任务1变为最高优先级的任务继续执行。
  • 如此循环。

在任务1的函数中,如果不调用vTaskDelay,则Idle任务用于没有机会执行,它就无法释放创建任务2是分配的内存。

而任务1在不断地创建任务,不断地消耗内存,最终内存耗尽再也无法创建新的任务。

现象如下:

任务1的代码中,需要注意的是:xTaskCreate的返回值。

  • 很多手册里说它失败时返回值是pdFAIL,这个宏是0
  • 其实失败时返回值是errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY,这个宏是-1
  • 为了避免混淆,我们使用返回值跟pdPASS来比较,这个宏是1

3.3 任务优先级和Tick

3.3.1 任务优先级

在上个示例中我们体验过优先级的使用:高优先级的任务先运行。

优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。

FreeRTOS的调度器可以使用2种方法来快速找出优先级最高的、可以运行的任务。使用不同的方法时,configMAX_PRIORITIES 的取值有所不同。

  • 通用方法
    使用C函数实现,对所有的架构都是同样的代码。对configMAX_PRIORITIES的取值没有限制。但是configMAX_PRIORITIES的取值还是尽量小,因为取值越大越浪费内存,也浪费时间。
    configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为0、或者未定义时,使用此方法。
  • 架构相关的优化的方法
    架构相关的汇编指令,可以从一个32位的数里快速地找出为1的最高位。使用这些指令,可以快速找出优先级最高的、可以运行的任务。
    使用这种方法时,configMAX_PRIORITIES的取值不能超过32。
    configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为1时,使用此方法。

在学习调度方法之前,你只要初略地知道:

  • FreeRTOS会确保最高优先级的、可运行的任务,马上就能执行
  • 对于相同优先级的、可运行的任务,轮流执行

这无需记忆,就像我们举的例子:

  • 厨房着火了,当然优先灭火
  • 喂饭、回复信息同样重要,轮流做

3.3.2 Tick

对于同优先级的任务,它们“轮流”执行。怎么轮流?你执行一会,我执行一会。

"一会"怎么定义?

人有心跳,心跳间隔基本恒定。

FreeRTOS中也有心跳,它使用定时器产生固定间隔的中断。这叫Tick、滴答,比如每10ms发生一次时钟中断。

如下图:

  • 假设t1、t2、t3发生时钟中断
  • 两次中断之间的时间被称为时间片(time slice、tick period)
  • 时间片的长度由configTICK_RATE_HZ 决定,假设configTICK_RATE_HZ为100,那么时间片长度就是10ms

相同优先级的任务怎么切换呢?请看下图:

  • 任务2从t1执行到t2
  • 在t2发生tick中断,进入tick中断处理函数:
    • 选择下一个要运行的任务
    • 执行完中断处理函数后,切换到新的任务:任务1
  • 任务1从t2执行到t3
  • 从下图中可以看出,任务运行的时间并不是严格从t1,t2,t3哪里开始

有了Tick的概念后,我们就可以使用Tick来衡量时间了,比如:

vTaskDelay(2);  // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms

// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100));	 // 等待100ms

注意,基于Tick实现的延时并不精确,比如vTaskDelay(2)的本意是延迟2个Tick周期,有可能经过1个Tick多一点就返回了。

如下图:

使用vTaskDelay函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为Tick。

这样的代码就与configTICK_RATE_HZ无关,即使配置项configTICK_RATE_HZ改变了,我们也不用去修改代码。

3.3.3 示例4: 优先级实验

代码为:FreeRTOS_04_task_priority

本程序会创建3个任务:

  • 任务1、任务2:优先级相同,都是1
  • 任务3:优先级最高,是2

任务1、2代码如下:

void vTask1( void *pvParameters )

	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	
		/* 打印任务的信息 */
		printf("T1\\r\\n");				
	


void vTask2( void *pvParameters )
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	
		/* 打印任务的信息 */
		printf("T2\\r\\n");				
	

任务3代码如下:

void vTask3( void *pvParameters )
	
	const TickType_t xDelay3000ms = pdMS_TO_TICKS( 3000UL );		
	
	/* 任务函数的主体一般都是无限循环 */
	for( ;; )
	
		/* 打印任务的信息 */
		printf("T3\\r\\n");				

		// 如果不休眠的话, 其他任务无法得到执行
		vTaskDelay( xDelay3000ms );
	

main函数代码如下:


	prvSetupHardware();
	
	xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
	xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
	xTaskCreate(vTask3, "Task 3", 1000, NULL, 2, NULL);

	/* 启动调度器 */
	vTaskStartScheduler();

	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;

运行情况如下图所示:

  • 任务3优先执行,直到它调用vTaskDelay主动放弃运行
  • 任务1、任务2:轮流执行

调度情况如下图所示:

3.3.4 示例5: 修改优先级

本节代码为:FreeRTOS_05_change_priority

使用uxTaskPriorityGet来获得任务的优先级:

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。

使用vTaskPrioritySet 来设置任务的优先级:

void vTaskPrioritySet( TaskHandle_t xTask,
                       UBaseType_t uxNewPriority );

使用参数xTask来指定任务,设置为NULL表示设置自己的优先级;
参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。

main函数的代码如下,它创建了2个任务:任务1的优先级更高,它先执行:

int main( void )

	prvSetupHardware();
	
	/* Task1的优先级更高, Task1先执行 */
	xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
	xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );

	/* 启动调度器 */
	vTaskStartScheduler();

	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;

任务1的代码如下:

void vTask1( void *pvParameters )

	UBaseType_t uxPriority;
	
	/* Task1,Task2都不会进入阻塞或者暂停状态
	 * 根据优先级决定谁能运行
	 */
	
	/* 得到Task1自己的优先级 */
	uxPriority = uxTaskPriorityGet( NULL );
	
	for( ;; )
	
		printf( "Task 1 is running\\r\\n" );

		printf("About to raise the Task 2 priority\\r\\n" );
		
		/* 提升Task2的优先级高于Task1
		 * Task2会即刻执行
 		 */
		vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );
		
		/* 如果Task1能运行到这里,表示它的优先级比Task2高
		* 那就表示Task2肯定把自己的优先级降低了
 		 */
	

任务2的代码如下:

void vTask2( void *pvParameters )

	UBaseType_t uxPriority;

	/* Task1,Task2都不会进入阻塞或者暂停状态
	 * 根据优先级决定谁能运行
	 */
	
	/* 得到Task2自己的优先级 */
	uxPriority = uxTaskPriorityGet( NULL );
	
	for( ;; )
	
		/* 能运行到这里表示Task2的优先级高于Task1
		 * Task1提高了Task2的优先级
		 */
		printf( "Task 2 is running\\r\\n" );
		
		printf( "About to lower the Task 2 priority\\r\\n" );

		/* 降低Task2自己的优先级,让它小于Task1
		 * Task1得以运行
 		 */
		vTaskPrioritySet( NULL, ( uxPriority - 2 ) );
	

调度情况如下图所示:

  • 1:一开始Task1优先级最高,它先执行。它提升了Task2的优先级。
  • 2:Task2的优先级最高,它执行。它把自己的优先级降低了。
  • 3:Task1的优先级最高,再次执行。它提升了Task2的优先级。
  • 如此循环。
  • 注意:Task1的优先级一直是2,Task2的优先级是3或1,都大于0。所以Idel任务没有机会执行。

3.4 任务状态

以前我们很简单地把任务的状态分为2中:运行(Runing)、非运行(Not Running)。

对于非运行的状态,还可以继续细分,比如前面的FreeRTOS_04_task_priority中:

  • Task3执行vTaskDelay后:处于非运行状态,要过3秒种才能再次运行
  • Task3运行期间,Task1、Task2也处于非运行状态,但是它们随时可以运行
  • 这两种"非运行"状态就不一样,可以细分为:
    • 阻塞状态(Blocked)
    • 暂停状态(Suspended)
    • 就绪状态(Ready)

3.4.1 阻塞状态(Blocked)

在日常生活的例子中,母亲在电脑前跟同事沟通时,如果同事一直没回复,那么母亲的工作就被卡住了、被堵住了、处于阻塞状态(Blocked)。重点在于:母亲在等待

FreeRTOS_04_task_priority实验中,如果把任务3中的vTaskDelay调用注释掉,那么任务1、任务2根本没有执行的机会,任务1、任务2被"饿死"了(starve)。

在实际产品中,我们不会让一个任务一直运行,而是使用"事件驱动"的方法让它运行:

  • 任务要等待某个事件,事件发生后它才能运行
  • 在等待事件过程中,它不消耗CPU资源
  • 在等待事件的过程中,这个任务就处于阻塞状态(Blocked)

在阻塞状态的任务,它可以等待两种类型的事件:

  • 时间相关的事件
    • 可以等待一段时间:我等2分钟
    • 也可以一直等待,直到某个绝对时间:我等到下午3点
  • 同步事件:这事件由别的任务,或者是中断程序产生
    • 例子1:任务A等待任务B给它发送数据
    • 例子2:任务A等待用户按下按键
    • 同步事件的来源有很多(这些概念在后面会细讲):
      • 队列(queue)
      • 二进制信号量(binary semaphores)
      • 计数信号量(counting semaphores)
      • 互斥量(mutexes)
      • 递归互斥量、递归锁(recursive mutexes)
      • 事件组(event groups)
      • 任务通知(task notifications)

在等待一个同步事件时,可以加上超时时间。比如等待队里数据,超时时间设为10ms:

  • 10ms之内有数据到来:成功返回
  • 10ms到了,还是没有数据:超时返回

3.4.2 暂停状态(Suspended)

在日常生活的例子中,母亲正在电脑前跟同事沟通,母亲可以暂停:

  • 好烦啊,我暂停一会
  • 领导说:你暂停一下

FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend函数。函数原型如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。

要退出暂停状态,只能由别人来操作:

  • 别的任务调用:vTaskResume
  • 中断程序调用:xTaskResumeFromISR

实际开发中,暂停状态用得不多。

3.4.3 就绪状态(Ready)

这个任务完全准备好了,随时可以运行:只是还轮不到它。这时,它就处于就绪态(Ready)。

3.4.4 完整的状态转换图

3.5 Delay函数

3.5.1 两个Delay函数

有两个Delay函数:

  • vTaskDelay:至少等待指定个数的Tick Interrupt才能变为就绪状态
  • vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。

这2个函数原型如下:

void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给Tick */

/* pxPreviousWakeTime: 上一次被唤醒的时间
 * xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
 * 单位都是Tick Count
 */
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
                            const TickType_t xTimeIncrement );

下面画图说明:

  • 使用vTaskDelay(n)时,进入、退出vTaskDelay的时间间隔至少是n个Tick中断
  • 使用xTaskDelayUntil(&Pre, n)时,前后两次退出xTaskDelayUntil的时间至少是n个Tick中断
    • 退出xTaskDelayUntil时任务就进入的就绪状态,一般都能得到执行机会
    • 所以可以使用xTaskDelayUntil来让任务周期性地运行

3.5.2 示例6: Delay

本节代码为:FreeRTOS_06_taskdelay

本程序会创建2个任务:

  • Task1:
    • 高优先级
    • 设置变量flag为1,然后调用vTaskDelay(xDelay50ms);vTaskDelayUntil(&xLastWakeTime, xDelay50ms);
  • Task2:
    • 低优先级
    • 设置变量flag为0

main函数代码如下:

int main( void )

	prvSetupHardware();
	
	/* Task1的优先级更高, Task1先执行 */
	xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
	xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );

	/* 启动调度器 */
	vTaskStartScheduler();

	/* 如果

以上是关于freeRTOS系列教程之第三章任务管理的主要内容,如果未能解决你的问题,请参考以下文章

韦东山freeRTOS系列教程之第十一章中断管理(Interrupt Management)

韦东山freeRTOS系列教程之第十二章资源管理(Resource Management)

韦东山freeRTOS系列教程之第十三章调试方法

韦东山freeRTOS系列教程之第九章任务通知(Task Notifications)

freeRTOS系列教程之第二章内存管理

freeRTOS系列教程之第二章内存管理