目录
任务管理
创建任务
创建任务示例1:创建两个同等级的任务
创建任务示例2:使用任务参数
删除任务
删除任务示例:删除任务
挂起任务
任务优先级
优先级实验:修改优先级
Tick
延时函数
延时示例
空闲任务
钩子函数
调度算法
任务管理
创建任务
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针,任务函数。退出函数时需调用vTaskDelete(NULL)
const char * const pcName, // 任务名称,FreeRTOS内部不使用它,仅起调试作用。长度为configMAX_TASK_NAME_LEN
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小。单位为word(4字节),最大值为uint16_t的最大值。精确确定栈大小的方法是看反汇编码
void * const pvParameters, // 调用任务函数时传入的参数,调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters)
UBaseType_t uxPriority, // 优先级,范围0~(configMAX_PRIORITIES-1),值超时会调整为最大值。值越小优先级越低。
TaskHandle_t * const pxCreatedTask ); // 任务句柄。用来保持xTaskCreate输出结果task handle。
// 句柄:如果想操作这个任务(如修改优先级)时就需要这个,如果不用可以设为NULL。
// 返回值
// 成功:pdPASS
// 失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY,即-1(失败原因只有内存不足)
创建任务示例1:创建两个同等级的任务
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
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++ )
{}
}
}
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++ )
{}
}
}
实验现象:task2先运行。
任务开始执行顺序:先看优先级,高等优先级的先执行。同优先级的看任务的创建顺序,后创建的任务先执行。
创建任务示例2:使用任务参数
多个任务可以使用同一个函数,区别是:栈不同,且创建任务时可以传入不同的参数。
static const char *pcTextForTask1 = "T1 run\r\n";
static const char *pcTextForTask2 = "T2 run\r\n";
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask, "Task 1", 1000, (void *)pcTextForTask1, 1, NULL);
xTaskCreate(vTask, "Task 2", 1000, (void *)pcTextForTask2, 1, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
void vTask( void *pvParameters )
{
const char *pcTaskName = pvParameters;
volatile uint32_t ul; /* volatile用来避免被优化掉 */
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 打印任务1的信息 */
printf( pcTaskName );
/* 延迟一会(比较简单粗暴) */
for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
{}
}
}
删除任务
void vTaskDelete( TaskHandle_t xTaskToDelete );
// 参数为任务句柄,也可传入NULL。
常用:
自杀:vTaskDelete( NULL )。
被杀:其他任务执行vTaskDelete( pvTaskCode ),pvTaskCode是当前任务的句柄。
杀人:执行vTaskDelete( pvTaskCode ),pvTaskCode是其他任务的句柄。
删除任务示例:删除任务
int main( void )
{
prvSetupHardware();
xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
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 );
}
}
void vTask2( void *pvParameters )
{
/* 打印任务的信息 */
printf("Task2 is running and about to delete itself\r\n");
// 可以直接传入参数NULL, 这里只是为了演示函数用法
vTaskDelete(xTask2Handle);
}
任务现象:
Task1 is running
Task2 is running and about to delete itself
...重复上面两行
// 当无法执行延时函数时
Task1 is running
Create Task2 Failed
...重复上面两行
创建任务1后开始调度,则执行任务1。
在任务1中创建任务2,任务2的优先级高,马上执行,打印并自杀。
任务1继续执行,执行延时函数进入阻塞状态,轮到空闲任务执行,空闲任务释放任务2的内存(TCB、栈)。
延时时间到,任务1继续执行。如此循环。
任务1中如果不调用vTaskDelay,则空闲任务没有机会执行,也就无法释放创建任务2时分配的内存。而任务1不断地创建任务,不断消耗内存,最终会内存耗尽而无法再创建新任务。
挂起任务
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
参数xTaskToSuspend表示要暂停的任务,如果为NULL则表示暂停当前任务。
要退出暂停状态,只能由其他任务或中断程序来操作:
别的任务调用:vTaskResume
中断程序调用:xTaskResumeFromISR
实际开发中,暂停状态用得不多。
任务优先级
优先级的取值范围: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时,使用该方法。
优先级实验:修改优先级
/* 获取任务的优先级,参数使用时表示指定任务,参数为NULL时表示指定当前任务 */
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
/* 设置任务的优先级 */
/* 第一个参数:使用时表示指定任务,NULL时表示指定当前任务 */
/* 第一个参数:新优先级,取值范围是0~configMAX_PRIORITIES-1 */
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
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;
}
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肯定把自己的优先级降低了 */
}
}
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 ) );
}
}
Tick
同优先级的任务轮流执行,执行时间是tick。tick也称为节拍、心跳、滴答,是使用定时器产生固定间隔的中断。
时间片的长度由configTICK_RATE_HZ决定,假设configTICK_RATE_HZ为100,则时间片就是10ms。
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改变了,我们也不需要去修改代码。
延时函数
vTaskDelay:至少等待指定个数的Tick interrupt才能变为就绪状态。
vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。
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 );
延时示例
int main( void )
{
prvSetupHardware();
/* Task1的优先级更高, Task1先执行 */
xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
return 0;
}
void vTask1( void *pvParameters )
{
const TickType_t xDelay50ms = pdMS_TO_TICKS( 50UL );
TickType_t xLastWakeTime;
int i;
/* 获得当前的Tick Count */
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
flag = 1;
/* 故意加入多个循环,让程序运行时间长一点 */
for (i = 0; i < 5; i++)
printf( "Task 1 is running\r\n" );
##if 1
vTaskDelay(xDelay50ms);
##else
vTaskDelayUntil(&xLastWakeTime, xDelay50ms);
##endif
}
}
void vTask2( void *pvParameters )
{
for( ;; )
{
flag = 0;
printf( "Task 2 is running\r\n" );
}
}
空闲任务
在使用vTaskStartScheduler()函数来创建、启动调度器时,函数内部会创建空闲任务:
空闲任务优先级为0:不能阻碍用户任务运行。
空闲任务只能处于就绪态或运行态,永远不会阻塞。
注意:如果使用vTaskDelete()删除任务,则需确保空闲任务有机会执行,否则就无法释放被删除任务的内存。
我们可以添加一个空闲任务的钩子函数,空闲任务的循环每执行一次就会调用一次钩子函数。
钩子函数
作用是:
执行一些低优先级的、后台的、需要连续执行的函数。
测量系统的空闲时间:空闲时间能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间就可以算出处理器占用率。
让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要处理,可进入省电模式降低功耗。
空闲任务的钩子函数的限制:
不能导致空闲任务进入阻塞状态、暂停状态。
如果使用vTaskDelete()删除任务,则钩子函数要非常高效地执行。如果空闲任务一直卡在钩子函数里,它就无法释放内存。
使用钩子函数的前提是:在FreeRTOS\Source\tasks.c文件中,更改宏,实现函数。
configUSE_IDLE_HOOK宏定义为1。
实现vApplicationIdleHook函数。
调度算法
调度算法:确定某个就绪态任务切换为运行态。
通过配置文件FreeRTOSConfig.h的两个配置项来配置调度算法:configUSE_PREEMPTION、configUSE_TIME_SLICING。第三个配置项为高级选项configUSE_TICKLESS_IDLE,用于关闭tick中断来实现省电,一般为0,即不使用。
配置项 | 可抢占+时间片轮询+空闲任务让步 | 可抢占+时间片轮询+空闲任务不让步 | 可抢占+非时间片轮询+空闲任务让步 | 可抢占+非时间片轮询+空闲任务不让步 | 合作调度 |
configUSE_PREEMPTION | 1 | 1 | 1 | 1 | 0 |
configUSE_TIME_SLICING | 1 | 1 | 0 | 0 | x |
configIDLE_SHOULD_YIELD | 1 | 0 | 1 | 0 | x |
说明 | 常用 | 很少用 | 很少用 | 很少用 | 几乎不用 |