任务管理是FreeRTOS的核心功能,除内核函数中的任务创建,挂起,恢复,删除和任务切换之外,还有用于让出CPU使用权的阻塞式延时,任务优先级查询,设置,获取任务状态信息,以及获取任务运行时间信息等辅助函数。下面是常用的FreeRTOS任务函数。
1 延时函数
FreeRTOS 提供了两种延时函数:相对延时函数vvTaskDelay()和绝对延时函数vTaskDelayUntil()。这两种延时函数都会使调用它们的任务进入阻塞态,待延时结束后再恢复到就绪态,是高优先级任务主动让出CPU使用权的一种有效方法。这两种延时函数都以系统时钟节拍作为计算依据。
1.1 系统时钟节拍
任何操作系统都有一个系统时钟节拍,以供系统处理延时、超时等与时间相关的事件。系统时钟节拍有时也被称为心跳,是一个周期性的中断。系统时钟节拍越快,系统的额外开销就越大。
在STM32 微控制器中,系统时钟节拍由嘀嗒定时器提供。FreeRTOS定义了一个静态全局变量xTickCount,用来对系统时钟节拍计数,嘀嗒定时器每中断一次xTickCount就会加1。系统时钟节拍长短在FreeRTOSConfig.h头文件中配置。本书中使用的系统时钟节拍如无特殊指明都是1000Hz,由下面的宏所决定。
/*系统时钟节拍配置,其倒数就是一个时间片的长度*/
tdefine configTICK_RATE_HZ ((TickType_t)1000)
1.2 相对延时
相对延时是指每次延时都从vTaskDelay()函数开始,到指定的系统时钟节拍后结束,任务恢复到就绪态。相对延时函数的原型如下。
void vTaskDelay(const TickType_t xTicksToDelay );
参数说明如下。
xTicksToDelay: 要延时的系统时钟节拍数,范围为1~portMAX_DELAY。对于STM32微控制器,portMAX_DELAY的值为0xFFFFFFFF。
返回值:无。
在程序设计中,有时使用系统时钟节拍数进行延时不方便。FreeRTOS提供了将延时时间转换为系统时钟节拍的宏pdMS_TO_TICKS(),可以方便地将要延时的以ms为单位的时间转换为系统时钟节拍。
#define pdMS_TO_TICKS( xTimeInMs ) ( ( TickType_t ) \
( ( ( TickType_t ) ( xTimeInMs ) * \
( TickType_t ) configTICK_RATE_HZ ) / ( TickType_t ) 1000 ) )
vTaskDelay()是相对延时函数,延时时间从调用这个函数开始计算,任务进入阻塞态,在到达指定的系统时钟节拍数时,将任务加入就绪列表。若任务在运行过程中发生了中断,则任务的运行时间会边长,所以相当延时时间不准确。
假设系统中有一个具有最高优先级的任务A,任务A调用vTaskDelayO函数,如果某次调用没有中断发生,而另一次调用发生了中断,那么无论中断发生在哪个时刻,都将导致任务A两次运行的时间间隔不一样。
1.3 相对延时
绝对延时是指每隔指定的系统时钟节拍运行一次调用vTaskDelayUntil()函数的任务。也就是说,任务以固定的频率运行。
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
参数说明如下。
pxPreviousWakeTime: 指向用于保存上次任务退出阻塞态时的变量。
xTimeIncrement: 周期性的延时值。
返回值:无。
与相对延时函数vTaskDelay()不同,绝对延时函数vTaskDelayUntil()增加了一个参数pxPreviousWakeTime,用于指向一个变量,该变量保存上次任务退出阻塞态的时间。这个变量在任务开始时必须被设置成当前系统时钟节拍值,此后vTaskDelayUntil()函数在内部会自动更新这个变量。
假设系统中有一个具有最高优先级的任务B,当任务 B 调用vTaskDelayUntil()函数时,即使发生中断,整个任务的运行时间也不会变长,即绝对延时函数能以固定频率周期性地运行某个任务。
2 延时函数使用示例
本示例通过appStartTask()函数创建两个具有相同优先级的FreeRTOS任务,均运行于
优先级3。
任务1的任务函数为Led0Task(),其功能是使LED0闪烁,在任务中调用相对延时函数vTaskDelay(500),延时500ms,并将任务1的运行总系统时钟节拍数通过串口发送。
任务2的任务函数是Led1Task(),其功能是使LED1闪烁,在任务中调用绝对延时函数vTaskDelayUntil(&xNextTime,500),延时500ms,并将任务2的运行总系统时钟节拍数通过串口发送。
配置系统时钟节拍configTICK_RATE_HZ为1000Hz,即1个系统时钟节拍为1ms。
2.1 任务函数
/**********************************************************************
函 数 名:Led0Task
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 3
**********************************************************************/
static void Led0Task(void *pvParameters)
{
uint16_t cnt = 0; //检测灯闪烁次数的局部变量
TickType_t xFirstTime; //用于保存延时前的系统时钟节拍
while(1)
{
xFirstTime = xTaskGetTickCount(); //获取任务进入点的系统时钟节拍
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_4))); //LED闪烁
Delay(1200000); //模拟任务的运行200ms
vTaskDelay(pdMS_TO_TICKS(500)); //延时500ms
cnt = xTaskGetTickCount() - xFirstTime;
taskENTER_CRITICAL(); //进入临界段,开中断
printf("任务1:LED0 闪烁,任务1运行的节拍数 %3d \r\n",cnt);
taskEXIT_CRITICAL();//退出临界段,关中断
}
}
/**********************************************************************
函 数 名:Led1Task
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
优 先 级: 3
**********************************************************************/
static void Led1Task(void *pvParameters)
{
uint16_t cnt = 0; //检测灯闪烁次数的局部变量
TickType_t xFirstTime; //用于保存延时前的系统时钟节拍
TickType_t xNextTime; //用于保存任务退出阻塞态时的系统时钟节拍值
while(1)
{
xFirstTime = xTaskGetTickCount(); //获取任务进入点的系统时钟节拍
xNextTime = xFirstTime; //保存任务进入点的系统时钟节拍
GPIO_WriteBit(GPIOA,GPIO_Pin_5,(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_5))); //LED1闪烁
Delay(1200000); //模拟任务的运行200ms
vTaskDelayUntil(&xNextTime,pdMS_TO_TICKS(500)); //绝对延时500ms
cnt = xTaskGetTickCount() - xFirstTime;
taskENTER_CRITICAL(); //进入临界段,开中断
printf("任务2: LED1 闪烁,任务1运行的节拍数 %3d \r\n",cnt);
taskEXIT_CRITICAL();//退出临界段,关中断
}
}
这里的软件模拟函数的参数为什么是1200000,这是根据不同芯片试出来的结果。
2.2 任务创建
static TaskHandle_t Led0TaskHandle = NULL;//任务LED0任务句柄
static TaskHandle_t Led1TaskHandle = NULL;//任务LED1任务句柄
/**********************************************************************
函 数:Delay
形 参:延时的时间
**********************************************************************/
void Delay(__IO uint32_t nCount)
{
for(; nCount != 0; nCount--);
}
/**********************************************************************
函 数 名:appStartTask
功能说明:任务开始函数,用于创建其他函数并且开启调度器
形 参:pvParameters 是在创建该任务时传递的参数
返 回 值:无
**********************************************************************/
void appStartTask(void)
{
taskENTER_CRITICAL(); /*进入临界段,关中断*/
xTaskCreate(Led0Task,"Led0Task",128,NULL,3,&Led0TaskHandle);
xTaskCreate(Led1Task,"Led1Task",128,NULL,3,&Led1TaskHandle);
taskEXIT_CRITICAL(); /*退出临界段,关中断*/
vTaskStartScheduler();/*开启调度器*/
}
2.3 下载测试
编译无误后,将程序下载到开发板,可以看到LED0和LED1均在闪烁,虽然任务中调用延时函数都希望延时500ms,但是LED1闪烁的更加快,这也是可以通过串口调式助手显示的俩个任务运行的系统时钟节拍数可以看出来的。相对延时和绝对延时的区别如下所示。
在程序中,两个任务本身运行时间均模拟成200ms。任务1使用相对延时,延时500ms,加上任务本身的200ms,运行任务1共耗时约700ms。任务2使用绝对延时,同样延时500ms,但任务的阻塞时间会减去任务本身的运行时间,运行任务2同样共耗时500ms。由此可见,绝对延时函数能使任务以固定频率周期性地运行。