FreeRTOS学习.docx
《FreeRTOS学习.docx》由会员分享,可在线阅读,更多相关《FreeRTOS学习.docx(26页珍藏版)》请在冰点文库上搜索。
FreeRTOS学习
FREERToS操作系统学习笔记--主要函数记录
1.2任务函数
原型:
voidATaskFunction(void*pvParameters);
一般来说每个任务都会运行在自己的死循环中。
每个任务不允许从函数实现中返回,绝对不能有return语句,当一个任务不再需要时可以将其删除。
一个任务函数可以用来创建若干个任务——创建出的任务均是独立的执行实例,拥有属于自己的栈空间,以及属于自己的自动变量(栈变量),即任务函数本身定义的变量。
1.3任务删除函数
vTaskDelete(NULL);传入NULL参数表示删除当前任务
FreeRTOS的调度器是能让任务切入切出的唯一实体。
1.4创建任务
创建任务使用FreeRTOS的API函数xTaskCreate()。
函数原型
portBASE_TYPExTaskCreate(pdTASK_CODEpvTaskCode,
constsignedportCHAR*constpcName,
unsignedportSHORTusStackDepth,
void*pvParameters,
unsignedportBASE_TYPEuxPriority,
xTaskHandle*pxCreatedTask);
参数
pvTaskCode参数:
一个指向任务的实现函数的指针(效果上仅仅是函数名)。
pcName具有描述性的任务名--------应用程序可以通过定义常量config_MAX_TASK_NAME_LEN来定义任务名的最大长度——包括’\0’结束符。
如果传入的字符串长度超过了这个最大值,字符串将会自动被截断。
usStackDepth参数值用于告诉内核为它分配多大的栈空间。
---这个值指定的是栈空间可以保存多少个字(word),而不是多少个字节(byte)。
-----栈深度乘以栈宽度的结果千万不能超过一个size_t类型变量所能表达的最大值。
pvParameters任务函数接受一个指向void的指针(void*)。
pvParameters的值即
是传递到任务中的值。
uxPriority指定任务执行的优先级。
优先级的取值范围可以从最低优先级0到最高优先级(configMAX_PRIORITIES–1)。
----如果uxPriority的值超过了(configMAX_PRIORITIES–1),将会导致实际赋给任务的优先级被自动封顶到最大合法值。
pxCreatedTask用于传出任务的句柄。
这个句柄将在API调用中对该创建出来的任务进行引用,比如改变任务优先级,或者删除任务。
如果应用程序中不会用到这个任务的句柄,则pxCreatedTask可以被设为NULL。
-----一般应用程序中不会再次改变任务的优先级,或者删除任务(除非任务死掉,删除后重新建立任务)
返回值有两个可能的返回值:
1.pdTRUE-------表明任务创建成功。
2.errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY----由于内存堆空间不足,FreeRTOS无法分配足够的空间来保存任务结构数据和任务栈,因此无法创建任务。
intmain(void)
{
/*创建第一个任务。
需要说明的是一个实用的应用程序中应当检测函数xTaskCreate()的返回值,以确保任务创建成功。
*/
xTaskCreate(vTask1,/*指向任务函数的指针*/
"Task1",/*任务的文本名字,只会在调试中用到*/
1000,/*栈深度–大多数小型微控制器会使用的值会比此值小得多*/
NULL,/*没有任务参数*/
1,/*此任务运行在优先级1上.*/
NULL);/*不会用到任务句柄*/
xTaskCreate(vTask2,"Task2",1000,NULL,1,NULL);
/*启动调度器,任务开始执行*/
vTaskStartScheduler();
/*如果一切正常,main()函数不应该会执行到这里。
但如果执行到这里,很可能是内存堆空间不足导致空闲任务无法创建。
第五章有讲述更多关于内存管理方面的信息*/
for(;;);
}
1.5任务优先级
任务优先级修改函数vTaskPrioritySet()
应用程序在文件FreeRTOSConfig.h中设定的编译时配置常量configMAX_PRIORITIES的值,即是最多可具有的优先级数目。
FreeRTOS本身并没有限定这个常量的最大值,但这个值越大,则内核花销的内存空间就越多。
所以总是建议将此常量设为能够用到的最小值。
低优先级号表示任务的优先级低,优先级号0表示最低优先级。
有效的优先级号范围从0到(configMAX_PRIORITES–1)。
如果被选中的优先级上具有不止一个任务,调度器会让这些任务轮流执行
时间片的长度通过心跳中断的频率进行设定,心跳中断频率由FreeRTOSConfig.h中的编译时配置常量configTICK_RATE_HZ进行配置。
比如说,如果configTICK_RATE_HZ设为100(HZ),则时间片长度为10ms
常量portTICK_RATE_MS用于将以心跳为单位的时间值转化为以毫秒为单位的时间值。
有效精度依赖于系统心跳频率。
1.6扩充“非运行态”
阻塞状态----如果一个任务正在等待某个事件,则称这个任务处于”阻塞态(blocked)”。
阻塞态是非运行态的一个子状
互斥信号量,因为其主要用于实现互斥访问)和互斥量都可以用来实现同步事件。
挂起状态----“挂起(suspended)”也是非运行状态的子状态。
处于挂起状态的任务对调度器而言是不可见的。
让一个任务进入挂起状态的唯一办法就是调用vTaskSuspend()API函数;而把一个挂起状态的任务唤醒的唯一途径就是调用vTaskResume()或vTaskResumeFromISR()API函数。
大多数应用程序中都不会用到挂起状态。
就绪状态----如果任务处于非运行状态,但既没有阻塞也没有挂起,则这个任务处于就(ready,准备或就绪)状态。
处于就绪态的任务能够被运行,但只是”准备(ready)”运行,而当前尚未运行。
voidvTaskDelay(portTickTypexTicksToDelay)
xTicksToDelay延迟多少个心跳周期。
调用该延迟函数的任务将进入阻塞态,经延迟指定的心跳周期数后,再转移到就绪态。
--------常数portTICK_RATE_MS可以用来将以毫秒为单位的时间值转换为以心跳周期为单位的时间值。
空闲任务是在调度器启动时自动创建的,以保证至少有一个任务可运行(至少有一个任务处于就绪态)。
vTaskDelayUntil()API函数原型
voidvTaskDelayUntil(portTickType*pxPreviousWakeTime,
portTickTypexTimeIncrement);
vTaskDelayUntil()类似于vTaskDelay()。
函数vTaskDelay()的参数用来指定任务在调用vTaskDelay()到切出阻塞态整个过程包含多少个心跳周期。
任务保持在阻塞态的时间量由vTaskDelay()的入口参数指定,但任务离开阻塞态的时刻实际上是相对于vTaskDelay()被调用那一刻的。
vTaskDelayUntil()的参数就是用来指定任务离开阻塞态进入就绪态那一刻的精确心跳计数值。
API函数vTaskDelayUntil()可以用于实现一个固定执行周期的需求(当你需要让你的任务以固定频率周期性执行的时候)。
由于调用此函数的任务解除阻塞的时间是绝对时刻,比起相对于调用时刻的相对时间更精确(即比调用vTaskDelay()可以实现更精确的周期性)。
pxPreviousWakeTime此参数命名时假定vTaskDelayUntil()用于实现某个任务以固定频率周期性执行。
这种情况下pxPreviousWakeTime保存了任务上一次离开阻塞态(被唤醒)的时刻。
这个时刻被用作一个参考点来计算该任务下一次离开阻塞态的时刻。
pxPreviousWakeTime指向的变量值会在API函数vTaskDelayUntil()调用过程中自动更新,应用程序除了该变量第一次初始化外,通常都不要修改它的值。
voidvTaskFunction(void*pvParameters)
{
char*pcTaskName;
portTickTypexLastWakeTime;
/*Thestringtoprintoutispassedinviatheparameter.Castthistoa
characterpointer.*/
pcTaskName=(char*)pvParameters;
/*变量xLastWakeTime需要被初始化为当前心跳计数值。
说明一下,这是该变量唯一一次被显式赋值。
之后,
xLastWakeTime将在函数vTaskDelayUntil()中自动更新。
*/
xLastWakeTime=xTaskGetTickCount();
/*Aspermosttasks,thistaskisimplementedinaninfiniteloop.*/
for(;;)
{
/*Printoutthenameofthistask.*/
vPrintString(pcTaskName);/*本任务将精确的以250毫秒为周期执行。
同vTaskDelay()函数一样,时间值是以心跳周期为单位的,
可以使用常量portTICK_RATE_MS将毫秒转换为心跳周期。
变量xLastWakeTime会在
vTaskDelayUntil()中自动更新,因此不需要应用程序进行显示更新。
*/
vTaskDelayUntil(&xLastWakeTime,(250/portTICK_RATE_MS));
}
}
1.7空闲任务与空闲任务钩子函数
当调用vTaskStartScheduler()时,调度器会自动创建一个空闲任务。
空闲任务是一个非常短小的循环——空闲任务拥有最低优先级(优先级0)以保证其不会妨碍具有更高优先级的应用任务进入运行
空闲任务钩子函数
通过空闲任务钩子函数(或称回调,hook,orcall-back),可以直接在空闲任务中添加应用程序相关的功能。
空闲任务钩子函数会被空闲任务每循环一次就自动调用一次。
通常空闲任务钩子函数被用于:
执行低优先级,后台或需要不停处理的功能代码。
测试处系统处理裕量(空闲任务只会在所有其它任务都不运行时才有机会执行,所以测量出空闲任务占用的处理时间就可以清楚的知道系统有多少富余的处理时间)。
将处理器配置到低功耗模式——提供一种自动省电方法,使得在没有任何应用功能需要处理的时候,系统自动进入省电模式
空闲任务钩子函数必须遵从以下规则
1.绝不能阻或挂起。
空闲任务只会在其它任务都不运行时才会被执行(除非有应用任务共享空闲任务优先级)。
以任何方式阻塞空闲任务都可能导致没有任务能够进入运行态!
2.如果应用程序用到了vTaskDelete()AP函数,则空闲钩子函数必须能够尽快返回。
因为在任务被删除后,空闲任务负责回收内核资源。
如果空闲任务一直运行在钩子函数中,则无法进行回收工作。
空闲任务钩子函数的函数名和函数原型。
voidvApplicationIdleHook(void);
/*Declareavariablethatwillbeincrementedbythehookfunction.*/
unsignedlongulIdleCycleCount=0UL;
/*空闲钩子函数必须命名为vApplicationIdleHook(),无参数也无返回值。
*/
voidvApplicationIdleHook(void)
{
/*Thishookfunctiondoesnothingbutincrementacounter.*/
ulIdleCycleCount++;
}
FreeRTOSConfig.h中的配置常量configUSE_IDLE_HOOK必须定义为1,这样空闲任务钩子函数才会被调用。
voidvTaskFunction(void*pvParameters)
{
char*pcTaskName;
/*Thestringtoprintoutispassedinviatheparameter.Castthistoa
characterpointer.*/
pcTaskName=(char*)pvParameters;
/*Aspermosttasks,thistaskisimplementedinaninfiniteloop.*/
for(;;)
{
/*打印输出任务名,以及调用计数器ulIdleCycleCount的值。
*/
vPrintStringAndNumber(pcTaskName,ulIdleCycleCount);
/*Delayforaperiodfor250milliseconds.*/
vTaskDelay(250/portTICK_RATE_MS);
}
}
1.8改变任务优先级
vTaskPrioritySet()API函数
API函数vTaskPriofitySet()可以用于在调度器启动后改变任何任务的优先级。
voidvTaskPrioritySet(xTaskHandlepxTask,unsignedportBASE_TYPEuxNewPriority);
pxTask被修改优先级的任务句柄(即目标任务)——参考xTaskCreate()API函数的参数pxCreatedTask以了解如何得到任务句柄方面的信息。
任务可以通过传入NULL值来修改自己的优先级。
uxNewPriority目标任务将被设置到哪个优先级上。
如果设置的值超过了最大可用优先级(configMAX_PRIORITIES–1),则会被自动封顶为最大值。
常量configMAX_PRIORITIES是在FreeRTOSConfig.h头文件中设置的一个编译时选项。
uxTaskPriorityGet()API函数
uxTaskPriorityGet()API函数用于查询一个任务的优先级。
unsignedportBASE_TYPEuxTaskPriorityGet(xTaskHandlepxTask);
pxTask被查询任务的句柄(目标任务)——参考xTaskCreate()API函数的参pxCreatedTask以了解如何得到任务句柄方面的信息。
任务可以通过传入NULL值来查询自己的优先级。
返回值被查询任务的当前优先级。
1.9删除任务
vTaskDelete()API函数
函数原型:
voidvTaskDelete(xTaskHandlepxTaskToDelete);
任务可以使用API函数vTaskDelete()删除自己或其它任务。
需要说明一点,只有内核为任务分配的内存空间才会在任务被删除后自动回收。
任务自己占用的内存或资源需要由应用程序自己显式地释放。
pxTaskToDelete被删除任务的句柄(目标任务)——参考xTaskCreate()API函数的参数pxCreatedTask以了解如何得到任务句柄方面的信息。
任务可以通过传入NULL值来删除自己。
1.10调度算法–简述
优先级抢占式调度
每个任务都赋予了一个优先级。
每个任务都可以存在于一个或多个状态。
在任何时候都只有一个任务可以处于运行状态。
调度器总是在所有处于就绪态的任务中选择具有最高优先级的任务来执行。
抢占式”是指当任务进入就绪态或是优先级被改变时,如果处于运行态的任务优先级更低,则该任务总是抢占当前运行的任务。
选择任务优先级
作为一种通用规则,完成硬实时功能的任务优先级会高于完成软件时功能任务的优先级。
实现混合调度方案也是可行的,这需要在中断服务例程中显式地进行上下文切换,从而允许同步事件产生抢占行为,但时间事件却不行。
这样做的结果是得到了一个没有时间片机制的抢占式系统。
或许这正是所期望的,因为获得了效率,并且这也是一种常用的调度器配置。
2.1概览
基于FreeRTOS的应用程序由一组独立的任务构成——每个任务都是具有独立权限的小程序。
这些独立的任务之间很可能会通过相互通信以提供有用的系统功能。
FreeRTOS中所有的通信与同步机制都是基于队列实现的。
2.2队列的特性
数据存储
队列可以保存有限个具有确定长度的数据单元。
队列可以保存的最大单元数目被称为队列的“深度”。
在队列创建时需要设定其深度和每个单元的大小。
通常情况下,队列被作为FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。
当然,由队列首写入也是可能的。
往队列写入数据是通过字节拷贝把数据复制存储到队列中;从队列读出数据使得把队列中的数据拷贝删除。
可被多任务存取
队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。
所有任务都可以向同一队列写入和读出。
一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。
读队列时阻塞
当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。
在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。
当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。
当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列数据有效。
这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。
而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。
写队列时阻塞
同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。
这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。
由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。
这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。
而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。
2.3使用队列
xQueueCreate()API函数
队列在使用前必须先被创建。
队列由声明为xQueueHandle的变量进行引用。
xQueueCreate()用于创建一个队列,并返回一个xQueueHandle句柄以便于对其创建的队列进行引用。
当创建队列时,FreeRTOS从堆空间中分配内存空间。
分配的空间用于存储队列数据结构本身以及队列中包含的数据单元。
如果内存堆中没有足够的空间来创建队列,xQueueCreate()将返回NULL。
队列创建函数原型
xQueueHandlexQueueCreate(unsignedportBASE_TYPEuxQueueLength,
unsignedportBASE_TYPEuxItemSize);
uxQueueLength队列能够存储的最大单元数目,即队列深度。
uxItemSize队列中数据单元的长度,以字节为单位。
返回值NULL表示没有足够的堆空间分配给队列而导致创建失败。
非NULL值表示队列创建成功。
此返回值应当保存下来,以作为操作此队列的句柄。
xQueueSendToBack()与xQueueSendToFront()API函数
xQueueSendToBack()用于将数据发送到队列尾;
而xQueueSendToFront()用于将数据发送到队列首。
xQueueSend()完全等同于xQueueSendToBack()。
但切记不要在中断服务例程中调用xQueueSendToFront()或
xQueueSendToBack()。
系统提供中断安全版本的xQueueSendToFrontFromISR()与xQueueSendToBackFromISR()用于在中断服务中实现相同的功能。
xQueueSendToFront()API函数原型
portBASE_TYPExQueueSendToFront(xQueueHandlexQueue,constvoid*pvItemToQueue,
portTickTypexTicksToWait);
xQueueSendToBack()API函数原型
portBASE_TYPExQueueSendToBack(xQueueHandlexQueue,constvoid*pvItemToQueue,
portTickTypexTicksToWait);
xQueue目标队列的句柄。
这个句柄即是调用xQueueCreate()创建该队列时的返回值。
pvItemToQueue发送数据的指针。
其指向将要复制到目标队列中的数据单元。
由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域。
xTicksToWait阻塞超时时间。
如果在发送时队列已满,这个时间即是任务处于阻塞态等待队列空间有效的最长等待时间。
如果xTicksToWait设为0,并且队列已满,则xQueueSendToFront()与xQueueSendToBack()均会立即返回。
阻塞时间是以系统心跳周期为单位的,所以绝对时间取决于系统心跳频率。
常量portTICK_RATE_MS可以用来把心跳时间单位转换为毫秒时间单位。
如果把xTicksToWait设置为portMAX_DELAY,并且在FreeRTOSConig.h中设定INCLUDE_vTaskSuspend为1,那么阻塞等待将没有超时限制。
返回值有两个可能的返回值:
1.pdPASS
返回pdPASS只会有一种情况,那就是数据被成功发送到队列中。
如果设定了阻塞超时时间(xTicksToWait非0),在函数返回之前任务将被转移到阻塞态以等待队列空间有效—在超时到来前能够将数据成功写入到队列,函数则会返回pdPASS。
2.errQUEUE_FULL
如果由于队列已满而无法将数据写入,则将返回errQUEUE_FULL。
如果设定了阻塞超时时间(xTicksToWait非0),在函数返回之前任务将被转移到阻塞态以等待队列空间有效。
但直到超时也没有其它任务或是中断服务例程读取队列而腾出空间,函数则会返回errQUEUE_FULL。
xQueueReceive()与xQue