1. FreeRTOS任务优先级介绍
当我们使用xTaskCreate() API函数创建一个任务的时候,会为任务赋予一个初始的优先级,当然这个优先级可以在调度器启动后,我们可以使用vTaskPrioritySet() API函数来进行优先级修改的。
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
其中xTask参数是传递进某个任务的句柄,NULL则表示修改自己的优先级。uxNewPriority参数表示新设置的优先级,取值范围0~(configMAX_PRIORITIES – 1)。
使用uxTaskPriorityGet来获得任务的优先级:
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
xTask参数是某个任务句柄,NULL表示获取自己的优先级。返回值就是该任务的优先级。
优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。其中configMAX_PRIORITIES这个宏的值可以在FreeRTOSConfig.h 中设定。FreeRTOS虽然没有规定这个宏的最大值,但是实际开发中,我们应该尽量选择一个合适的取值,因为这个值越大,那么内核对内存的开销就越大。
FreeRTOS的调度器总是会确保:
- 在所有可以运行的任务中,选择其中具有最高优先级的任务运行;
- 对于优先级相同的、可以运行的任务,轮流执行。
2. 系统心跳 — tick
对于相同优先级的任务,FreeRTOS要轮流执行的话,就需要有系统心跳。系统心跳来源于SysTick系统定时器产生周期性中断,比如我们设置SysTick定时器10ms产生一次中断,那么FreeRTOS的心跳就是10ms。
如下图所示:
- 其中t1、t2、t3时刻发生定时器中断
- 两次中断的时间间隔,称为时间片(time slice、tick period)
- 时间片的长度在在FreeRTOSConfig.h 文件中,由configTICK_RATE_HZ这个宏确定,比如设定为1000,那么时间片长度就是1/1000s,也就是1ms。
那么对于相同优先级的任务,他们是怎么轮流进行切换的呢?如下图可以概括:
- Task2任务是后面创建的,所以会首先运行Tssk2任务;
- Task2任务运行完一个时间片长度后,发生SysTick系统定时器中断,进入对于的中断处理函数:
- 中断处理函数选择下一个可以运行的任务
- 执行完中断处理函数后,就跳转到新的任务,即Task1任务继续运行
- Task1运行t2~t3这个时间片;
- 上图也可以看出,进行任务切换的时候,需要使用到中断处理函数,中断运行也需要时间的。所以任务的运行并不是在t1,t2,t3的这些时间点上运行的。
使用系统心跳来进行延时:
很明显系统心跳—Tick可以用来衡量时间,自然我们就可以用它来实现延时。FreeRTOS提供了专门的API延时函数给我们使用,函数原型如下:
void vTaskDelay( const TickType_t xTicksToDelay );
该函数使用时会引起使用的任务进入阻塞态。其中,参数xTicksToDelay就是我们想要延时的时间长度,延时多长除了和该参数大小有关,还与设置的系统心跳频率有关。
用法如下:
/* 假设configTICK_RATE_HZ=100, Tick周期时10ms, 那么等待2个Tick,也就是等待20ms */
vTaskDelay(2);
/* 还可以使用pdMS_TO_TICKS宏把ms转换为tick */
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms
使用vTaskDelay函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为Tick。这样的代码就与configTICK_RATE_HZ无关,即使配置项configTICK_RATE_HZ改变了,我们也不用去修改代码。
3. 优先级示例代码
示例代码创建2个任务。
任务1代码如下:
/* Task1,Task2都不会进入阻塞或者暂停状态,根据优先级决定谁能运行*/
void vTask1( void *pvParameters )
{
UBaseType_t uxPriority;
/* 得到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 ) );
}
}
任务2代码如下:
/* Task1,Task2都不会进入阻塞或者暂停状态,根据优先级决定谁能运行*/
void vTask2( void *pvParameters )
{
UBaseType_t uxPriority;
/* 得到Task2自己的优先级 */
uxPriority = uxTaskPriorityGet( NULL );
for( ; ; )
{
printf( "Task 2 is running\r\n" );
printf("About to lower the Task 2 priority\r\n" );
/* 降低了Task2的优先级,使得它的优先级比Task1还小,那么Task1会马上得到运行 */
vTaskPrioritySet( xTask2Handle, ( uxPriority - 2 ) );
}
}
main函数
#include <stdio.h>
#include "bsp_usart.h"
#include "FreeRTOS.h"
#include "task.h"
TaskHandle_t xTask2Handle;
int main( void )
{
USART_Config(); // 串口初始化
/* Task1的优先级更高, Task1先执行 */
xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
代码运行过程如下:
- 首先创建任务1和任务2,因为任务1优先级更高,所以任务1首先运行。
- 为了让任务2有机会运行,在任务1中提升任务2的优先级高于任务1
- 由于任务1提升了任务2的优先级,所以任务2此时在运行,然后任务2降低自己的优先级
- 这时任务1优先级最高,任务1得以运行
- 反复以上循环
- 由于任务1和2都不会进入阻塞状态,所以空闲任务永远也不会得到执行
如下图可直观了解整个运行过程: