FreeRTOS-04-内核控制函数+时间管理函数
说明
本文仅作为学习FreeRTOS的记录文档,作为初学者肯定很多理解不对甚至错误的地方,望网友指正。
FreeRTOS是一个RTOS(实时操作系统)系统,支持抢占式、合作式和时间片调度。适用于微处理器或小型微处理器的实时应用。
本文档使用的FreeRTOS版本:FreeRTOS Kernel V10.4.1
参考文档:《FreeRTOS_Reference_Manual_V10.0.0.pdf》《FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf》《STM32F4 FreeRTOS开发手册_V1.1.pdf》
参考视频:正点原子FreeRTOS手把手教学-基于STM32_哔哩哔哩_bilibili
6 内核控制函数
内核控制函数就是FreeRTOS内核所使用的函数,一般情况下应用程序不使用这些函数。
官网API说明:FreeRTOS - Task Control Functions and Macros for the Free Open Source RTOS FreeRTOS
6.1 任务切换
函数原型:
#include “FreeRTOS.h”
#include “task.h”
void taskYIELD( void );
函数描述:任务切换函数。如果没有与当前任务同等优先级或高优先级的任务,则任务调度器会选择当前任务继续运行。必须在调度器初始化之后使用。
函数参数:无
返回值:无
测试代码:创建两个任务,任务task0优先级为2,任务函数中每次从0计数到2进行一次任务切换;任务task1优先级也为2,任务函数中每次打印之后就挂起自己。
configSTACK_DEPTH_TYPE Task0_STACK_SIZE = 5;
UBaseType_t Task0_Priority = 2;
TaskHandle_t Task0_xhandle;
configSTACK_DEPTH_TYPE Task1_STACK_SIZE = 5;
UBaseType_t Task1_Priority = 2;
TaskHandle_t Task1_xhandle;
void task0_code(void *para)
{
unsigned int i = 0;
for (;;)
{
for (i = 0; i < 4; i++) {
PRINT(" task0 cnt %u...", i);
if (i == 2) {
vTaskResume(Task1_xhandle);
taskYIELD();
}
}
vTaskDelay(2000);
}
}
void task1_code(void *para)
{
static unsigned int cnt = 0;
for (;;)
{
PRINT(" task1 cnt %u...", cnt);
cnt++;
vTaskSuspend(Task1_xhandle);
}
}
void creat_task(void)
{
taskENTER_CRITICAL();
if (xTaskCreate(task0_code, "task0 task",
Task0_STACK_SIZE, NULL, Task0_Priority,
&Task0_xhandle) != pdPASS)
{
PRINT("creat task failed!\n");
}
if (xTaskCreate(task1_code, "task1 task",
Task1_STACK_SIZE, NULL, Task1_Priority,
&Task1_xhandle) != pdPASS)
{
PRINT("creat task failed!\n");
}
taskEXIT_CRITICAL();
vTaskStartScheduler();
}
编译、运行,结果符合预期,每次调度taskYIELD()之后执行一次任务切换,结果如下:
$ ./build/freertos-simulator
task0 cnt 0...
task0 cnt 1...
task0 cnt 2...
task1 cnt 0...
task0 cnt 3...
task0 cnt 0...
task0 cnt 1...
task0 cnt 2...
task1 cnt 1...
task0 cnt 3...
现在将任务task0的优先级改为3,大于任务task1的优先级:
UBaseType_t Task0_Priority = 3;
编译、运行,结果符合预期,每次调度taskYIELD()之后不会任务切换,结果如下:
$ ./build/freertos-simulator
task0 cnt 0...
task0 cnt 1...
task0 cnt 2...
task0 cnt 3...
task1 cnt 0...
task0 cnt 0...
task0 cnt 1...
task0 cnt 2...
task0 cnt 3...
task1 cnt 1...
task0 cnt 0...
6.2 进入临界区
函数原型:
#include “FreeRTOS.h”
#include “task.h”
void taskENTER_CRITICAL( void );
函数描述:进入临界区,不能在中断服务函数中调用。中断服务函数中调用taskENTER_CRITICAL_FROM_ISR()进入临界区。
函数参数:无
返回值:无
6.3 退出临界区
函数原型:
#include “FreeRTOS.h”
#include “task.h”
void taskEXIT_CRITICAL( void );
函数描述:退出临界区,不能在中断服务函数中调用。中断服务函数中调用taskEXIT_CRITICAL_FROM_ISR()退出临界区。
函数参数:无
返回值:无
taskENTER_CRITICAL()和taskEXIT_CRITICAL()函数用于临界段代码保护(任务级)。
taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()函数用于中断级临界段代码保护。
临界段代码:也叫做临界区,指那些必须完整运行,不能被打断的代码。比如某些外设的初始化。FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。
6.4 关闭可屏蔽中断
函数原型:
#include “FreeRTOS.h”
#include “task.h”
void taskDISABLE_INTERRUPTS( void );
函数描述:关闭可屏蔽中断。不可嵌套使用。
函数参数:无
返回值:无
6.4 打开可屏蔽中断
函数原型:
#include “FreeRTOS.h”
#include “task.h”
void taskENABLE_INTERRUPTS( void );
函数描述:关闭可屏蔽中断。不可嵌套使用。
函数参数:无
返回值:无
6.5 启动任务调度器
函数原型:
#include “FreeRTOS.h”
#include “task.h”
void vTaskStartScheduler( void );
函数描述:启动任务调度器。典型应用为:main()函数先于调度器使用,调度器启动之后,执行任务及中断函数。调度器启动之后将选择优先级最高的任务进行执行。调度器启动之后,空闲任务(Idle task)将自动被创建。
函数参数:无
返回值:无
6.5 关闭任务调度器
函数原型:
#include “FreeRTOS.h”
#include “task.h”
void vTaskEndScheduler( void );
函数描述:关闭任务调度器。仅支持x86架构处理器。关闭任务调度器之后,内核时钟将停止计数,所有创建的任务都会自动删除。
函数参数:无
返回值:无
7 时间管理
FreeRTOS延时函数分为相对模式和绝对模式。vTaskDelay()是相对延时函数。vTaskDelayUntil()是绝对延时函数。
7.1 相对延时
函数原型:
#include "FreeRTOS.h"
#include "task.h"
void vTaskDelay( const TickType_t xTicksToDelay );
函数描述:调用该函数的任务将进入阻塞态,中断一段固定的时钟周期。使用这个函数必须将宏INCLUDE_vTaskDelay置1。
函数参数:xTicksToDelay表示调用函数的任务的阻塞态保持时间,单位为时钟节拍数。真正的延时时间取决于时钟节拍频率。宏 portTICK_PERIOD_MS被用来根据时钟节拍数来计算一个时钟节拍的延时周期。
#define portTICK_PERIOD_MS ( ( TickType_t ) 1000 / configTICK_RATE_HZ )
#define configTICK_RATE_HZ ( 1000 )
可以看出,单个时钟节拍计数时间为1ms,比如参数xTicksToDelay设为100,就表示延时100ms。
延时达到之后将进入就绪态。例如:当时钟计数到10000时,函数调用了vTaskDelay(100),然后任务进入阻塞态,并且保持阻塞态直到时钟计数到10100。
宏pdMS_TO_TICKS()可以被使用来延时毫秒。例如:调用vTaskDelay( pdMS_TO_TICKS(100) ),任务将进入阻塞态100毫秒。
如果参数xTicksToDelay为0,则等同于调用了一次taskYIELD()函数进行了一次任务切换。
返回值:无
7.2 绝对延时
函数原型:
#include “FreeRTOS.h”
#include “task.h”
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement );
函数描述:调用该函数的任务将进入阻塞态直到一个绝对的时间到来。周期任务可以调用这个函数来实现一个固定的执行频率。使用这个函数必须将宏INCLUDE_vTaskDelayUntil置1。
函数参数:pxPreviousWakeTime:上一次任务延时结束被唤醒的时间点,任务中第一次调用函数vTaskDelayUntil()需要将pxPreviousWakeTime初始化为进入任务的while()循环体的时间点值。在以后的运行中函数vTaskDelayUntil()会自动更新pxPreviousWakeTime。
xTimeIncrement:任务需要延时的节拍数(相对于pxPreviousWakeTime本次延时的节拍数),也就是任务在pxPreviousWakeTime+xTimeIncrementpd时钟计数时从阻塞态恢复。MS_TO_TICKS()宏可用于延时毫秒。
(1)为任务主体,也就是任务执行的工作;(2)为任务调用vTaskDelayUntil()函数;(3)为其它任务执行。任务延时时间为xTimeIncrement,可看出任务总的执行时间一定小于任务的延时时间,也就是说使用vTaskDelayUntil()函数任务的执行周期永远是xTimeIncrement,而任务一定要在这个时间内完成,这个延时值就是绝对延时时间。
上面图中,xConstTickCount和xTimeToWake可能溢出,这些情况暂不讨论,这里仅说明函数的用法。
测试代码:创建一个任务,使用绝对延时函数延时50ms。
configSTACK_DEPTH_TYPE Task1_STACK_SIZE = 5;
UBaseType_t Task1_Priority = 2;
TaskHandle_t Task1_xhandle;
void task1_code(void *para)
{
unsigned int cnt = 0;
TickType_t xLastWakeTime;
const TickType_t xPeriod = pdMS_TO_TICKS(500);
xLastWakeTime = xTaskGetTickCount();
for (;;)
{
vTaskDelayUntil(&xLastWakeTime, xPeriod);
PRINT(" task1 cnt %u...", cnt);
cnt++;
}
}
void creat_task(void)
{
if (xTaskCreate(task1_code, "task1 task",
Task1_STACK_SIZE, NULL, Task1_Priority,
&Task1_xhandle) != pdPASS)
{
PRINT("creat task failed!\n");
}
}
7.3 系统时钟节拍
xTickCount是FreeRTOS系统时钟节拍计数器,每个滴答定时器中断中xTickCount就会加1。xTickCount具体操作过程在xTaskIncrementTick()函数中进行,这个函数在时钟计数器中断函数中调用。