FreeRTOS任务实例
一、简要说明
从这里开始正式进入实操环节,由于移植部分已经完成,为了加快学习部分,在以后的学习中都使用官方提供的VS2010项目作为学习的主要工具。
1. 官方例程下载
下载完成就是一个压缩包,解压即可
解压完成打开后应该是下面这样,打开 .sln 文件(前提是下载有Microsoft Visual Studio工具)。
打开以后应该是下面这样
任意打开一个官方示例
双击 main.c 打开main文件单击键盘上的F5运行(可能会报错),如果报错按照下图处理
直接点击确定就可以
二、学习任务的创建
1. 创建一个任务
打开第一个官方例程,在上一篇对任务的概述中已经有很详细的介绍了,这里只是简单一提。
创建一个任务
这就是上面创建的这个任务的具体实现,每隔一段事件就会打印 Task 1 is running 这个字符串在控制台上,例程中还类似的创建了另一个任务vTask2。
在任务2创建之后就启动了调度器 vTaskStartScheduler() 开始调度任务。
两个任务的优先级相同都是1,默认情况下是启用了时间片调度的
运行结果如下,任务1和任务2是交替运行的:
2. 任务中传递参数
打开第二个官方例程 Example002 ,结合第一个例程来看,发现创建了两个任务,但是在创建的任务中,使用的确实同一个任务函数。
但是多的是,在创建任务的同时向任务函数中传递了不同的参数。
把视角切换到任务函数这边,可以发现他用了一个字符指针变量 pcTaskName 来存储了这个出入任务函数的参数,当然这是在已知传入的参数是字符串类型的才能这样一概而论,如果传入的参数类型是变动的是未知的但是可以预料的,那么直接传入一个固定类型的方式将不再适用,这是需要特别注意的。
运行一下,结果与第一个例程有着一样的效果,但是代码量却少了不少,这不失为一种在工程应用开发中对与有着几乎一模一样的功能的任务提供的一种解决方案:
3. 不同优先级的任务
在上面的两个例程中,两个任务的优先级都是相同的都是1,这几乎是最低的优先级了(前提是configMAX_PRIORITIES设置的比1大)
打开第三个官方例程 Example003 ,Task1的优先级是1,Task2的有优先级是2,这两个的任务函数和第二个例程的任务函数相同。
运行一下:
发现任务1根本没有得到运行
单片机中当下状态只能有一个任务在运行,在非阻塞式的任务中当然就只能运行高优先级的任务了,下面我们编写单片机的程序,观察一下是不是这么回事:
唯一改变的是两个任务的函数部分
故意把两个延时改成不相同的情况,这样会得到更加肯定的现象(只会有一个灯闪烁,而另一个灯是不会有任何变化的)
三、任务的延时
1.使用阻塞式延时
打开官方例程 Example004 ,与之前不同是任务函数中使用了FreeRTOS官方的延时函数,这是一个具有阻塞式功能的延时,就是说当任务在延时的过程中,让出CPU的使用权,让调度器去选择当前高优先级的就绪任务去使用CPU。
仔细一看,这里不是普通的一个250,这里是用了一个宏 pdMS_TO_TICKS 来接收的一个250,最后将计算的值给了变量xDelay250ms。
同样运行一下:
任务1又出来了,开始运行了。
这个宏是什么呢,都干了哪些事呢?
追寻一下,在 projdefs.h 中可以找到他的身影。在这里发现了一个宏定义的常量 configTICK_RATE_HZ ,在宏 pdMS_TO_TICKS 中是把接收的变量 xTimeInMs 和这个宏定义的常量 configTICK_RATE_HZ 的乘积再除以了一个1000。继续追寻宏定义的常量 configTICK_RATE_HZ 。(这里可以看到,官方怕这里定义的内容不适用于所有的场景,表示这个可以再 FreeRTOSConfig.h 这个文件中重写以至于适用在自己的应用上)
追寻一下 configTICK_RATE_HZ 这个宏,这个宏在 FreeRTOSConfig.h 文件中可以找到。这是系统节拍时钟周期,决定着系统的中断频率。
系统节拍中断服务程序会调用函数 xTaskIncrementTick() ,该函数返回值为真(不等于pdFALSE),说明处于就绪态任务的优先级比当前运行的任务优先级高。这会触发一次PendSV中断,进行上下文切换。
回过头来,宏 pdMS_TO_TICKS 主要的作用是将输入的 ms 时间转换为时钟节拍数。这正是 vTaskDelay() 这个函数的意义:延时多少个时钟节拍以后再运行当前的任务。
这里用pc模拟依然看不出效果,还是在单片机上跑一跑吧,还是把延时调成不一样的,下载进开发板,可以明显发现另外一个灯开始闪烁了,这就是阻塞延时的效果。
2. 精确的任务定时
打开官方例程 Example005。
运行一下:
在这一个例程中使用了 vTaskDelayUntil() 这个函数,类似于 vTaskDelay() 。
函数 vTaskDelay() 的参数用来指定任务在调用 vTaskDelay() 到切出阻塞态整个过程包含多少个心跳周期。任务保持在阻塞态的时间量由 vTaskDelay() 的入口参数指定,但任务离开阻塞态的时刻实际上是相对于 vTaskDelay() 被调用那一刻的。
vTaskDelayUntil() 的参数就是用来指定任务离开阻塞态进入就绪态那一刻的精确心跳计数值。API 函数 vTaskDelayUntil() 可以用于实现一个固定执行周期的需求(当你需要让你的任务以固定频率周期性执行的时候)。由于调用此函数的任务解除阻塞的时间是绝对时刻,比起相对于调用时刻的相对时间更精确(即比调用 vTaskDelay() 可以实现更精确的周期性)。
什么意思呢,就是说在 vTaskDelayUntil() 这个函数调用之前,就需要指定任务遇到延时进入就绪状态的那一刻的具体时钟数,时钟数一到任务立即进入就绪状态;而在 vTaskDelay() 这个函数中表示的是这个任务在遇到这个函数的时候需要时钟计数多少时钟再从这个函数之后继续运行,这显然不精准。
最开始指定离开阻塞态进入就绪态那一刻的精确心跳计数值的函数是 xTaskGetTickCount() ,这个函数返回的变量 xLastWakeTime 将会在 vTaskDelayUntil() 这个函数中自动更新,而且最开始指定的心跳计数值只执行一次。
使用 vTaskDelay() 无法保证它们具有固定的执行频率,因为这两个任务退出阻塞态的时刻相对于调用 vTaskDelay() 的时刻。通过调用 vTaskDelayUntil() 代替 vTaskDelay() ,把这两个任务进行转换,以解决这个潜在的问题。
在简单的示例中,运行的结果和普通的延时效果几乎没有差别。
3.低优先级任务无延时,高优先级延时
打开 Example006,main 函数内容如下,与前面的内容一样。
不一样的是低优先级的实例任务中没有延时。
而高一级优先级的实例任务中具有一个延时。
运行的效果也是几乎可以预见的,结果如下:
高一级的任务在阻塞式延时的时候,将CPU的使用权让出来了,让给了优先级为1的两个任务,这两个具有相同的低优先级的任务是由时间轮片控制依次运行相同时间,但是一旦高优先级的任务处于就绪状态,就会打断低优先级任务的执行,从而让高优先级任务获得使用CPU的权利。