目录
1.4.1 获得任务的优先级uxTaskPriorityGet
1.4.2 设置任务的优先级vTaskPrioritySet
1.5 启动任务调度器vTaskStartScheduler
1.6 空闲任务钩子函数vApplicationIdleHook
2.3.3 xPortGetMinimumEverFreeHeapSize
3.1.2 静态分配内存创建队列xQueueCreateStatic
8.2 xTaskNotifyGive/ulTaskNotifyTake
8.3 xTaskNotify/xTaskNotifyWait
1. 任务相关函数
1.1 创建任务xTaskCreate
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务
参数说明:
| 参数 | 描述 |
|---|---|
| pvTaskCode | 函数指针,可以简单地认为任务就是一个C函数。 它稍微特殊一点:永远不退出,或者退出时要调用"vTaskDelete(NULL)" |
| pcName | 任务的名字,FreeRTOS内部不使用它,仅仅起调试作用。 长度为:configMAX_TASK_NAME_LEN |
| usStackDepth | 每个任务都有自己的栈,这里指定栈大小。 单位是word,比如传入100,表示栈大小为100 word,也就是400字节。 最大值为uint16_t的最大值。 怎么确定栈的大小,并不容易,很多时候是估计。 精确的办法是看反汇编码。 |
| pvParameters | 调用pvTaskCode函数指针时用到:pvTaskCode(pvParameters) |
| uxPriority | 优先级范围:0~(configMAX_PRIORITIES – 1) 数值越小优先级越低, 如果传入过大的值,xTaskCreate会把它调整为(configMAX_PRIORITIES – 1) |
| pxCreatedTask | 用来保存xTaskCreate的输出结果:task handle。 以后如果想操作这个任务,比如修改它的优先级,就需要这个handle。 如果不想使用该handle,可以传入NULL。 |
| 返回值 | 成功:pdPASS; 失败:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因只有内存不足) 注意:文档里都说失败时返回值是pdFAIL,这不对。 pdFAIL是0,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY是-1。 |
1.2 删除任务vTaskDelete
void vTaskDelete( TaskHandle_t xTaskToDelete );
参数说明:
| 参数 | 描述 |
|---|---|
| pvTaskCode | 任务句柄,使用xTaskCreate创建任务时可以得到一个句柄。 也可传入NULL,这表示删除自己。 |
怎么删除任务?举个不好的例子:
-
自杀:
vTaskDelete(NULL) -
被杀:别的任务执行
vTaskDelete(pvTaskCode),pvTaskCode是自己的句柄 -
杀人:执行
vTaskDelete(pvTaskCode),pvTaskCode是别的任务的句柄
1.3 暂停任务vTaskSuspend
FreeRTOS中的任务也可以进入暂停状态,唯一的方法是通过vTaskSuspend函数。函数原型如下:
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。
1.4 优先级相关函数
1.4.1 获得任务的优先级uxTaskPriorityGet
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。
1.4.2 设置任务的优先级vTaskPrioritySet
void vTaskPrioritySet( TaskHandle_t xTask,
UBaseType_t uxNewPriority );
使用参数xTask来指定任务,设置为NULL表示设置自己的优先级; 参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。
1.5 启动任务调度器vTaskStartScheduler
1.6 空闲任务钩子函数vApplicationIdleHook
空闲任务(Idle任务)的作用:释放被删除的任务的内存。
除了上述目的之外,为什么必须要有空闲任务?一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。在使用vTaskStartScheduler() 函数来创建、启动调度器时,这个函数内部会创建空闲任务:
-
空闲任务优先级为0:它不能阻碍用户任务运行
-
空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞
空闲任务的优先级为0,这意味着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。
要注意的是:如果使用vTaskDelete() 来删除任务,那么你就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。
我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions),空闲任务的循环每执行一次,就会调用一次钩子函数。钩子函数的作用有这些:
-
执行一些低优先级的、后台的、需要连续执行的函数
-
测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率。
-
让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式了。
空闲任务的钩子函数的限制:
-
不能导致空闲任务进入阻塞状态、暂停状态
-
如果你会使用
vTaskDelete()来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植卡在钩子函数里的话,它就无法释放内存。
使用钩子函数的前提
在FreeRTOS\Source\tasks.c中,可以看到如下代码,所以前提就是:
-
把这个宏定义为1:configUSE_IDLE_HOOK
-
实现
vApplicationIdleHook函数

2. Tick相关函数
2.1 延时函数vTaskDelay
至少等待指定个数的Tick Interrupt才能变为就绪状态
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改变了,我们也不用去修改代码。
2.2 绝对延时函数vTaskDelayUntil
等待到指定的绝对时刻,才能变为就绪态。
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement );
下面画图说明:
-
使用vTaskDelay(n)时,进入、退出vTaskDelay的时间间隔至少是n个Tick中断
-
使用xTaskDelayUntil(&Pre, n)时,前后两次退出xTaskDelayUntil的时间至少是n个Tick中断
-
退出xTaskDelayUntil时任务就进入的就绪状态,一般都能得到执行机会
-
所以可以使用xTaskDelayUntil来让任务周期性地运行
-

示例:
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
}
}
2.3 Heap相关的函数
2.3.1 pvPortMalloc/vPortFree
函数原型:
void * pvPortMalloc( size_t xWantedSize ); // 分配内存,如果分配内存不成功,则返回值为NULL。 void vPortFree( void * pv ); // 释放内存
作用:分配内存、释放内存。
如果分配内存不成功,则返回值为NULL。
2.3.2 xPortGetFreeHeapSize
函数原型:
size_t xPortGetFreeHeapSize( void );
当前还有多少空闲内存,这函数可以用来优化内存的使用情况。比如当所有内核对象都分配好后,执行此函数返回2000,那么configTOTAL_HEAP_SIZE就可减小2000。
注意:在heap_3中无法使用。
2.3.3 xPortGetMinimumEverFreeHeapSize
函数原型:
size_t xPortGetMinimumEverFreeHeapSize( void );
返回:程序运行过程中,空闲内存容量的最小值。
注意:只有heap_4、heap_5支持此函数。
2.3.4 malloc失败的钩子函数
在pvPortMalloc函数内部:
void * pvPortMalloc( size_t xWantedSize )
{
......
#if ( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
所以,如果想使用这个钩子函数:
-
在FreeRTOSConfig.h中,把configUSE_MALLOC_FAILED_HOOK定义为1
-
提供vApplicationMallocFailedHook函数
-
pvPortMalloc失败时,才会调用此函数
3. 队列相关函数
3.1 创建队列
队列的创建有两种方法:动态分配内存、静态分配内存,
3.1.1 动态分配内存创建队列xQueueCreate
队列的内存在函数内部动态分配
函数原型如下:
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
| 参数 | 说明 |
|---|---|
| uxQueueLength | 队列长度,最多能存放多少个数据(item) |
| uxItemSize | 每个数据(item)的大小:以字节为单位 |
| 返回值 | 非0:成功,返回句柄,以后使用句柄来操作队列 NULL:失败,因为内存不足 |
3.1.2 静态分配内存创建队列xQueueCreateStatic
队列的内存要事先分配好
函数原型如下:
QueueHandle_t xQueueCreateStatic(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSize,
uint8_t *pucQueueStorageBuffer,
StaticQueue_t *pxQueueBuffer
);
| 参数 | 说明 |
|---|---|
| uxQueueLength | 队列长度,最多能存放多少个数据(item) |
| uxItemSize | 每个数据(item)的大小:以字节为单位 |
| pucQueueStorageBuffer | 如果uxItemSize非0,pucQueueStorageBuffer必须指向一个uint8_t数组, 此数组大小至少为"uxQueueLength * uxItemSize" |
| pxQueueBuffer | 必须执行一个StaticQueue_t结构体,用来保存队列的数据结构 |
| 返回值 | 非0:成功,返回句柄,以后使用句柄来操作队列 NULL:失败,因为pxQueueBuffer为NULL |
示例代码:
// 示例代码
#define QUEUE_LENGTH 10
#define ITEM_SIZE sizeof( uint32_t )
// xQueueBuffer用来保存队列结构体
StaticQueue_t xQueueBuffer;
// ucQueueStorage 用来保存队列的数据
// 大小为:队列长度 * 数据大小
uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];
void vATask( void *pvParameters )
{
QueueHandle_t xQueue1;
// 创建队列: 可以容纳QUEUE_LENGTH个数据,每个数据大小是ITEM_SIZE
xQueue1 = xQueueCreateStatic( QUEUE_LENGTH,
ITEM_SIZE,
ucQueueStorage,
&xQueueBuffer );
}
3.2 复位队列xQueueReset
队列刚被创建时,里面没有数据;使用过程中可以调用xQueueReset()把队列恢复为初始状态,此函数原型为:
/* pxQueue : 复位哪个队列;
* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);
3.3 删除队列vQueueDelete
删除队列的函数为vQueueDelete(),只能删除使用动态方法创建的队列,它会释放内存。原型如下:
void vQueueDelete( QueueHandle_t xQueue );
3.4 写队列
可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在ISR中使用。函数原型如下:
/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
QueueHandle_t xQueue,
const void *pvItemToQueue,

本文详细介绍了FreeRTOS的任务创建、删除、暂停,以及任务优先级管理。此外,还涵盖了Tick相关函数,如延时函数vTaskDelay和绝对延时函数vTaskDelayUntil。接着,讨论了内存管理函数,如动态和静态内存分配。队列创建、复位、删除和操作函数也被逐一解析。信号量的创建、删除和使用,以及互斥量和递归锁的概念和操作也被提及。事件组的创建、删除、设置和等待操作,以及任务通知的功能和用法也进行了讲解。最后,提到了软件定时器的创建、删除、启动、停止、复位和修改周期等操作。

417

被折叠的 条评论
为什么被折叠?



