林大侠
2018.08.09
第一节 Task管理
(一)Task管理常用API
创建Task API
static BaseType_t xTaskCreate(TaskFunction_t pvTaskCode,
const char *constpcName,
const uint32_t usStackDepth,
void *constpvParameters,
UBaseType_t uxPriority,
TaskHandle_t *constpvCreatedTask)
功能
创建一个新任务,并将其添加到准备运行的任务列表中
参数描述
pvTaskCode: 指向任务实现函数的指针。
constpcName :任务名称,只是单纯的用于描述任务方便记忆,不会被freeRTOS使用。
usStackDepth:栈深度,这个值指定的是栈空间可以保存多少个字(word),而不是多少个字节(byte)。
比如说,如果是32位宽的栈 空间,传 入的usStackDepth值为100,则将会分配400 字节的栈空间(100 * 4bytes)。栈深度 乘以栈宽度的结果千万不能超过一个size_t 类型变量所能表达的最大值。
constpvParameters: 任务的传参。可以为NULL。
uxPriority: 任务优先级。最小优先级为0,最高优先级没有上限,但一般用户会自定义一个上线。
根据使用情况,数值尽量 设置小一点。
constpvCreatedTask:任务句柄。可以使用该句柄对任务进行引用,比如删除任务和改变任务优先级等。可以为NULL。
返回值
pdPASS : 任务创建成功。
其它值 :任务创建失败,可以根据返回的错误值在 projdefs.h文件中找到错误原因。
删除Task API
void vTaskDelete(TaskHandle_t xTaskToDelete)
功能
从RTOS实时内核管理中删除任务。删除的任务将从所有就绪、阻塞、暂停和事件列表中删除
参数描述
xTaskToDelete:创建任务时传出的句柄。如果传入参数为NULl,将会删除正在调用的任务。
返回值
NULL
开始调度Task API
void vTaskStartScheduler()
功能
开始执行任务调度
参数描述
NULL
返回值
NULL
Task延时阻塞API
void vTaskDelay(const TickType_t xTicksToDelay)
功能
从调用 vTaskDelay()开始阻塞任务,直到指定的滴答计数次数。
参数描述
xTicksToDelay:阻塞时间(ticks 时钟数)。一般使用参数 ms/ portTICK_RATE_MS来阻塞以毫秒为单位的时间。
ms为需要阻塞单位为毫秒的数值。
返回值
NULL
将Task阻塞到指定时间API
void vTaskDelayUntil(TickType_t *constpxPreviousWakeTime, const TickType_t xTimeIncrement)
功能
将任务阻塞到指定的时间。利用此API可以实现任务周期性调用。
参数描述
constpxPreviousWakeTime:指向一个变量的指针,该变量保存了任务最后一次解除阻塞的时间。
变量在首次使用之前必须用当前时间初始化。在此之后,变量将在vTaskDelayUntil中自动更新。
xTimeIncrement:周期时间。任务将在*pxPreviousWakeTime + xTimeIncrement时被解除阻塞。
使用相同的xTimeIncrement参数值调用vTaskDelayUntil将导致任务以固定的接口周期执行。
返回值
NULL
获取Task优先级API
UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask)
功能
获取已经创建的任务的优先级
参数描述
xTask: 创建任务时传出的句柄。当传入参数为NULL,则返回当前运行任务的优先级。
返回值
需要获取任务的优先级。
设置Task优先级API
void vTaskPrioritySet(TaskHandle_t xTask, UBaseType_t uxNewPriority)
功能
设置指定任务的优先级
参数描述
xTask:需要设置任务的句柄。
uxNewPriority:需要改变成的优先级。
返回值
NULL
获取Task状态API
eTaskStateeTaskGetState(TaskHandle_t xTask)
功能
获取想要获取任务的状态。
参数描述
xTask:需要获取任务状态的句柄。
返回值
任务当前的状态。
挂起TaskAPI
void vTaskSuspend(TaskHandle_t xTaskToSuspend)
功能
把指定的任务挂起
参数描述
xTaskToSuspend:需要挂起任务的句柄。
返回值
NULL
取消Task挂起API
void vTaskResume(TaskHandle_t xTaskToResume)
功能
让需要被调用 vTaskSuspend() 函数挂起的任务回到可以运行的状态。
参数描述
-xTaskToResume:需要重新回到可运行状态的任务的句柄。
返回值
NULL
挂起调度器API
void vTaskSuspendAll(void)
功能
停止上下文切换,而不用关中断。
参数描述
NULL
返回值
NULL
取消挂起调度器API
BaseType_t xTaskResumeAll(void)
功能
在调用 vTaskSuspendAll() 后调用,以恢复调度器
参数描述
NULL
返回值
pdTRUE:成功
pdFALSE:失败
(二)Task使用流程
新建一个Task
定义一个任务句柄 TaskHandle_t xTask。
创建一个任务执行函数,函数的返回值类型必须为 void,形参为 void *data。
在任务执行函数内配置任务阻塞时间。
调用 xTaskCreate() API创建一个新任务,分配好栈深度,设置好优先级。
调用 vTaskStartScheduler() API,开始任务调度。
修改Task优先级
调用 vTaskPrioritySet()API
删除Task
1.调用 vTaskDelete()API
第二节 队列管理
基于FreeRTOS 的应用程序由一组独立的任务构成——每个任务都是具有独立权
限的小程序。这些独立的任务之间很可能会通过相互通信以提供有用的系统功能。
FreeRTOS 中所有的通信与同步机制都是基于队列实现的。
(一)队列管理常用的API
创建一个新队列的宏
xQueueCreate(uxQueueLength, uxItemSize)
功能
创建一个新队列,为后面队列的引用提供句柄
参数描述
uxQueueLength:队列深度。
uxItemSize:队列中数据单元的长度,以字节为单位。
返回值
队列创建成功,则返回队列句柄;队列创建失败,返回 NULL。
删除一个队列的函数
void vQueueDelete(QueueHandle_t xQueue)
功能
删除队列——释放分配给存储放在队列上的项的所有内存。
参数描述
xQueue:要删除的队列的句柄。
返回值
NULL
向队列写入数据
向队列写入数据(写到队列尾部)
xQueueSend(xQueue, pvItemToQueue, xTicksToWait)
向队列头部写入数据
xQueueSendToFront(xQueue, pvItemToQueue, xTicksToWait)
向队列尾部写入数据(等同于 xQueueSend(xQueue, pvItemToQueue, xTicksToWait) )
xQueueSendToBack(xQueue, pvItemToQueue, xTicksToWait)
以上三个API都不能用于中断中,中断中要使用以下中断安全的API
向队列写入数据( xQueueSend(xQueue, pvItemToQueue, xTicksToWait) 的中断安全API)
xQueueSendFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken)
向队头写入数据(xQueueSendToFront(xQueue, pvItemToQueue, xTicksToWait) 的中断安全API)
xQueueSendToFrontFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken)
向队尾写入数据(xQueueSendToBack(xQueue, pvItemToQueue, xTicksToWait) 的中断安全API )
xQueueSendToBackFromISR(xQueue, pvItemToQueue, pxHigherPriorityTaskWoken)
参数描述
xQueue:需要写入数据的队列的句柄。
pvItemToQueue:需要写入的数据的指针。
xTicksToWait:队列阻塞超时时间。
pxHigherPriorityTaskWoken:优先级状态输出指针。对某个队列而言,可能有不止一个任务处于
阻塞态在等待其数据有效。调用 xQueueSendToFrontFromISR()或xQueueSendToBackFromISR()
会使得队列数据变为有效,所以会让其中一个等待任务切出阻塞态。如果调用这两个API函数使得一个任务
解除阻塞,并且这个任务的优先级高于当前任务(也就是被中断的任务),那么API会在函数内部将
*pxHigherPriorityTaskWoken 设为pdTRUE。
如果这两个API 函数将此值设为pdTRUE,则在中断退
出前应当进行一次上下文切换。这样才能保证中断直接
返回到就绪态任务中优先级最高的任务中。
返回值
pdTRUE:成功
errQUEUE_FULL:失败
读取队列中的数据
读取队列中的数据但不删除队列中的内容,也不会改变队列中的数据存储顺序
xQueuePeek(xQueue, pvBuffer, xTicksToWait)
读取队列中的数据,并且读取完后把数据从队列中删除
xQueueReceive(xQueue, pvBuffer, xTicksToWait)
以上两个API不能用于中断中,中断要使用以下中断安全的API
中断中读取队列中断数据,并把数据从队列中删除
xQueueReceiveFromISR(xQueue, pvBuffer, pxHigherPriorityTaskWoken)
中断中读取队列数据,但不删除队列中的数据,也不会改变队列中数据的顺序
xQueuePeekFromISR(xQueue, pvBuffer, pxHigherPriorityTaskWoken)
参数描述
xQueue:需要读取队列的句柄
pvBuffer:接收数据缓冲区的指针
pxHigherPriorityTaskWoken:任务优先级状态输出指针。如果值为 pdTRUE,需要在中断中进行上下文切换。
返回值
pdTRUE:成功
errQUEUE_FULL:失败
清空队列中的数据
xQueueReset(xQueue)
参数描述
xQueue:队列的句柄。
返回值
-永远都是返回 pdPASS
(二)队列使用流程
声明一个xQueueHandle类型变量,用于存放队列句柄。
调用 xQueueCreate(uxQueueLength, uxItemSize)创建一个新队列。
根据需要调用队列的读、写、复位等API。
第三节 信号量操作
(一)常用的信号量操作API
创建二值信号量
xSemaphoreCreateBinary()
功能
创建一个二值的信号量
参数描述
NULL
返回值
创建成功返回一个 SemaphoreHandle_t类型的信号量句柄,如果创建失败则返回 NULL
创建计数信号量
xSemaphoreCreateCounting(uxMaxCount, uxInitialCount)
功能
创建一个多值信号量
参数说明
uxMaxCount:信号量的深度。
uxInitialCount:信号量的初始值
返回值
创建创建成功返回一个句柄,如果创建失败则返回NULL
创建互斥信号量
xSemaphoreCreateMutex()
功能
创建一个互斥信号量
参数描述
NULL
返回值
创建成功返回一个句柄,如果创建失败则返回NULL
Take 信号量
非中断安全
xSemaphoreTake(xSemaphore, xBlockTime)
中断安全
xSemaphoreTakeFromISR(xSemaphore, pxHigherPriorityTaskWoken)
功能
信号量值 -1
参数
xSemaphore:信号量句柄
xBlockTime:阻塞时间
pxHigherPriorityTaskWoken:任务优先级状态的一个输出,如果值为 pdTRUE,
需要在中断中进行上下文切换。
返回值
成功pdTRUE ,信号量无效 pdFALSE
Give 信号量
非中断安全
xSemaphoreGive(xSemaphore)
中断安全
xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken)
功能
信号量值 +1
参数
xSemaphore:信号量句柄
pxHigherPriorityTaskWoken:任务优先级状态的一个输出,如果值为 pdTRUE,
需要在中断中进行上下文切换。
删除信号量
vSemaphoreDelete(xSemaphore)
参数描述
xSemaphore:需要删除信号量的句柄
获取信号量的当前值
uxSemaphoreGetCount(xSemaphore)
参数描述
xSemaphore:信号量句柄
返回值
如果信号量是计数信号量,那么uxSemaphoreGetCount()将返回其当前计数值。
如果信号量是二进制信号量,那么如果信号量可用, uxSemaphoreGetCount()返回1,如果信号量不可用,返回0。
获取互斥锁持有者
xSemaphoreGetMutexHolder(xSemaphore)
参数描述
信号量句柄
返回值
如果xMutex确实是互斥型信号量,返回当前的互斥锁持有者。
如果xMutex不是互斥型信号量,或者互斥型信号量可用(不是由任务持有),返回NULL。
(二)信号量的运用
延迟中断处理中,使用二值信号量进行同步。使用步骤
定义一个 SemaphoreHandle_t类型的变量,用于存放信号量句柄。
使用 xSemaphoreCreateBinary() 宏创建一个二值信号量。
在中断处理函数中使用 xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken),每来
一次中断,使二值信号量生效,解除延时处理任务的阻塞状态。
在中断延时处理任务中使用 xSemaphoreTake(xSemaphore, xBlockTime),阻塞时间设置为无限大,
当有中断触发时,信号量就会生效,xSemaphoreTake会得到一个值,使当前阻塞接触,同时使信号量失效,
下次再运行任务时会回到阻塞状态。
多值信号量的使用
多值信号量可以用来同步多个中断,每来一个中断,使用一次
xSemaphoreGiveFromISR(xSemaphore, pxHigherPriorityTaskWoken)
让信号量的值+1,在延时处理任务中使用 xSemaphoreTake(xSemaphore, xBlockTime)使信号量值-1。
互斥信号量的使用
互斥信号量用于多个任务共享资源使用。使用步骤如下:
定义一个 SemaphoreHandle_t类型的变量,用于存放信号量句柄。
使用 xSemaphoreCreateMutex()创建一个互斥信号量。
使用共享资源之前使用xSemaphoreTake获取令牌。
使用完共享资源之后,使用 xSemaphoreGive(xSemaphore)把令牌还回去。一定要还回去。
第四节 内存管理
(一)概述
每当任务,队列或是信号量被创建时,内核需要进行动态内存分配。虽然可以调用
标准的malloc()与free()库函数,但必须承担以下若干问题:
这两个函数在小型嵌入式系统中可能不可用。
这两个函数的具体实现可能会相对较大,会占用较多宝贵的代码空间。
这两个函数通常不具备线程安全特性。
这两个函数具有不确定性。每次调用时的时间开销都可能不同。
这两个函数会产生内存碎片。
这两个函数会使得链接器配置得复杂。
不同的嵌入式系统具有不同的内存配置和时间要求。所以单一的内存分配算法只可
能适合部分应用程序。因此,FreeRTOS 将内存分配作为可移植层面(相对于基本的内
核代码部分而言)。这使得不同的应用程序可以提供适合自身的具体实现。
当内核请求内存时,其调用pvPortMalloc()而不是直接调用malloc();当释放内存
时,调用vPortFree()而不是直接调用free()。pvPortMalloc()具有与malloc()相同的函
数原型;vPortFree()也具有与free()相同的函数原型。
FreeRTOS 自带有三种pvPortMalloc()与vPortFree()实现范例。
这三种方式都会在本节描述。FreeRTOS 的用户可以选用其中一种,也可以采用自己的内存管理方式。
这三个范例对应三个源文件:heap_1.c,heap_2.c,heap_3.c
(二)内存分配案例
Heap_1.c
Heap_1.c 实现了一个非常基本的pvPortMalloc()版本,而且没有实现vPortFree()。
如果应用程序不需要删除任务,队列或者信号量,则具有使用heap_1 的潜质。Heap_1
总是具有确定性。
这种分配方案是将FreeRTOS 的内存堆空间看作一个简单的数组。当调用
pvPortMalloc()时,则将数组又简单地细分为更小的内存块。
数组的总大小(字节为单位)在FreeRTOSConfig.h 中由configTOTAL_HEAP_SIZE
定义。以这种方式定义一个巨型数组会让整个应用程序看起来耗费了许多内存——即使
是在数组没有进行任何实际分配之前。
需要为每个创建的任务在堆空间上分配一个任务控制块(TCB)和一个栈空间。
Heap_2.c
Heap_2.c 也是使用了一个由configTOTAL_HEAP_SIZE 定义大小的简单数组。不
同于heap_1 的是,heap_2 采用了一个最佳匹配算法来分配内存,并且支持内存释放。
由于声明了一个静态数组,所以会让整个应用程序看起来耗费了许多内存——即使是在
数组没有进行任何实际分配之前。
最佳匹配算法保证pvPortMalloc()会使用最接近请求大小的空闲内存块。比如,考
虑以下情形:
堆空间中包含了三个空闲内存块,分别为5 字节,25 字节和100 字节大小。
pvPortMalloc()被调用以请求分配20 字节大小的内存空间。
匹配请求字节数的最小空闲内存块是具有25字节大小的内存块——所以pvPortMalloc()
会将这个25 字节块再分为一个20 字节块和一个5 字节块3,然后返回一个指向20 字
节块的指针。剩下的5 字节块则保留下来,留待以后调用pvPortMalloc()时使用。
Heap_2.c 并不会把相邻的空闲块合并成一个更大的内存块,所以会产生内存碎片
——如果分配和释放的总是相同大小的内存块,则内存碎片就不会成为一个问题。
Heap_2.c 适合用于那些重复创建与删除具有相同栈空间任务的应用程序。
Heap_2.c 虽然不具备确定性,但是比大多数标准库实现的malloc()与free()更有效率。
Heap_3.c
Heap_3.c 简单地调用了标准库函数malloc()和free(),但是通过暂时挂起调度器使
得函数调用备线程安全特性。其实现代码参如下。
此时的内存堆空间大小不受configTOTAL_HEAP_SIZE 影响,而是由链接器配置
决定。
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn;
vTaskSuspendAll();
{
pvReturn = malloc( xWantedSize );
}
xTaskResumeAll();
return pvReturn;
}
void vPortFree( void *pv )
{
if( pv != NULL )
{
vTaskSuspendAll();
{
free( pv );
}
xTaskResumeAll();
}
}