-
FREE RToS
操作系统学习笔记
--
p>
主要函数记录
1.2
任务函数
原型:
void ATaskFunction( void
*pvParameters );
一般来说每个任务都会运行在自己的死循环中。
每个任务不允许从函数实现中返回,绝对不能
有
return<
/p>
语句,当一个任务不再需要时可以将其删除。
一个任务函数可以用来创建若干个任务——创建出的任务均是
独立的执行实例,拥有属于自己
的栈空间,以及属于自己的自动变量
(
栈变量
)
,即任务函数本身定义
的变量。
1.3
任务删除函数
vTaskDelete( NULL );
传入
NULL
参数表示删除当前任务
FreeRTOS
的调度器是能让任务切入切出的唯一实体。
1.4
创建任务
创建任务使用
FreeRTOS
的
API
函数
xTaskCreate()
。
函数原型
portBASE_TYPE xTaskCreate( pdTASK_CODE
pvTaskCode,
const signed portCHAR *
const pcName,
unsigned portSHORT
usStackDepth,
void *pvParameters,
unsigned portBASE_TYPE uxPriority,
xTaskHandle *pxCreatedTask );
参数
pv
TaskCode
参数:
一个指向任务的实现
< br>函数的指针
(
效果上仅仅是函数名
)
。
pcName
具有描述性的任务名
-
-------
应用程序可以通过定义常量
config_MA
X_TASK_NAME_LEN
来定义任务名的最大长度—
—包括’
0
’结束符。如果传入的字符串长度超过了这个最大值
,
字符串将会自动被截断。
usStackDepth
参数值用于告诉内核为它分配多大
的栈空间
。
---
这个值指定的是栈空
间可以保
存多少个字
(word)
,<
/p>
而不是多少个字节
(byte)
。
-----
栈深度乘以栈宽度的结果千万不能超过一
< br>个
size_t
类型变量所能表达的最大值。
pvParameters
任务函数接受一个指向
void
的指
针
(void*)
。
pvParame
ters
的值即
是传递到任务中的值。
uxPriority
指定任务执行的优先级
。优先级的取值范围可以从最低优先级
0
到最高优先级
(configMAX_PRIORITIES
–
1)
。
-
---
如果
uxPriority
的值超过了
(configMAX_PRIORITIES
–
1)
,将
会导致实际赋给任务的优先级被自动封顶到最大合法值。
pxCreatedTask
用于传出任务的句柄
。这个句柄将在
API
调用中对该创建出来的
任务进行引
用,比如改变任务优先级,或者删除任务。
如果应用
程序中不会用到这个任务的句柄,则
pxCreatedTask
可以被设为
NULL
。
-----
一
般应用程序中不会再次改变任务的优先级,或者删
除任务(除非任务死掉,删除后重新建立任务)
返回值
有两个可能的返回值:
1.
pdTRUE-------
表明任务创建成功。
2.
errCOULD_NOT_ALLOCATE_REQ
UIRED_MEMORY----
由于内存堆空间不足,
Fr
eeRTOS
无法分配足
够的空间来保存任务结构数据和任务
栈,因此无法创建任务。
int
main( void )
{
/*
创建第一个任务。需要说明的是一个实用的应用程序中应当检测函数
xTaskCre
ate()
的
返回值,以确保任务创建成功。
< br> */
xTaskCreate( vTask1,
/*
指向任务函数的指针
*/
Task 1
/*
任务的文本名字,只会在调试中用到
*/
1000,
/*
栈深度
–
大多数小型微控制器会使用的值会比此值小得多
*/
NULL,
/*
没有任务参数
*/
1,
/*
此任务运行在优先级
1
上
. */
NULL );
/*
不会用到任务句柄
*/
xTaskCreate( vTask2,
/*
启动调度器,任务开始执行
*/
vTaskStartScheduler();
/*
p>
如果一切正常,
main()
函数不应该会
执行到这里。但如果执行到这里,很可能是内存堆
空间不足导致空闲任务无法创建。第五
章有讲述更多关于内存管理方面的信息
*/
for( ;
);
}
1.5
任务优先级
任务优先级修改函数
vTaskPrioritySet()
应
用
程
序
在
文
件
FreeRTOSConfig.h
中
设定的编译时配置常量
configMAX_PRIORITIES
的值,即是最多
可具有的优先级数目
。
FreeRTOS
本身并没有限定这个常量的最大值,
但这个值
越大,则内核
花销的内存空间就越多。
所以总是建议将此常量设为能够用到的最小值。
低优先级号表示任务的优先级低,优先级号
0
表示最低优先级。有效的优先级号范围从
0
到
(configMAX_PRIORITES
–
1)
。
如果被选中的优先级上具有不止一个任务,调度器会让这些任
务轮流执行
时间片的长度通过心跳
中断的频率进行设定,
心跳中断频率由
FreeRTOSCon
fig.h
中的编译时
配置常量
co
nfigTICK_RATE_HZ
进行配置
。比如说,如果
configTICK_RATE_HZ
设为
100(HZ)
,
则时间片长度为
10ms
常量
portTICK_RATE_MS
用于将以心跳为单位的时间值转化为以毫秒为单位的时间值
。有效精
度依赖于系统心跳频率。
1.6
扩充“非运行态”
阻塞状态
----
< br>如果一个任务正在等待某个事件,则称这个任务处于”阻塞态
(blocked)
”。阻塞
态是非运行态的一个子状
互斥信号量,因为其主要用于实现互斥访问
< br>)
和互斥量都可以用来实现同步事件。
挂起状态
----
< br>“挂起
(suspended)
”
也是非运行状态的子状态。
处于挂起状态的任务对调度器而
言
是不可见的
。让一个任务进入挂起状态的唯一办法就是调用
vT
askSuspend() API
函数;而
把一个挂起状态
的任务唤醒的唯一途径就是调用
vTaskResume()
或
vTaskResumeFromISR()
API
函数。
大多数应用程序中都不
会用到挂起状态。
就绪状态
----
如果任务处于非运行状态,
但既没有
阻塞也没有挂起,
则这个任务处于就
(ready
,
准备或就绪
)
状态。
p>
处于就绪态的任务能够被运行
,但只是”准备
(ready)
”运行,而当前尚未
运行。
< br>
void vTaskDelay(
portTickType xTicksToDelay )
xTicksToDelay
延迟多少个心跳周期。
调用该延迟函数的任务将进入阻塞态
,经延迟指定的心
< br>跳周期数后,再转移到就绪态。
--------
常数<
/p>
portTICK_RATE_MS
可以用来将以毫秒为单位
的
时间值转换为以心跳周期为单位的时间值。
空闲任务是在调度器启动时自动创建的,
以保证至少有一个任务可运行
(
至少有一个任务处于就
p>
绪态
)
。
vTaskDelayUntil() API
函数原型
void
vTaskDelayUntil( portTickType *
pxPreviousWakeTime,
portTickType
xTimeIncrement );
vTaskDelayUntil()
类似于
vTaskDelay()
。函数
vTaskDelay()
的参数用来指定任务在调用
vTaskDelay()
到切出阻塞态整个过程包含多少个心跳周期。
任务保持在阻塞态的时间量由
vTaskDelay()
的入口参数指定,但任务离开阻塞态的时刻实际
上
是相对于
vTaskDelay()
被调用那一刻的
。
vTaskDelayUntil()
的参数就是用来指定任务离开阻
塞态进入就绪态那一刻的精确心跳计数
值。
API
函数
vTaskDela
yUntil()
可以用于实现一个固
定执行周期的需求
(
当你需要让你的任务以固定频率周期性执行的时候
< br>)
。由于调用此函数的任
务解除阻塞的时间是绝对时刻,
比起相对于调用时刻的相对时间更精确
(
即比调用
vTaskDelay()
可以实现更精确的周期性
)
。
pxPreviousWakeTime
此参数命名时假定<
/p>
vTaskDelayUntil()
用于实现某个任务以固定频
率周期
性执行。
这种情况下
pxPre
viousWakeTime
保存了任务上一次离开阻塞态
(<
/p>
被唤醒
)
的时刻。
这个
时刻被用作一个参考点来计算该任务下一次离开阻塞态的时刻。
< br>
pxPreviousWakeTime
指向的变量值会在
API
函数
vTaskDelayUntil()
调用过程中自动更新,
应
用程序除了该变量第一次初始化外,通常都不要修改它的值。
void vTaskFunction(
void *pvParameters )
{
char
*pcTaskName;
portTickType
xLastWakeTime;
/* The string to print
out is passed in via the parameter. Cast this to a
character pointer. */
pcTaskName = ( char * ) pvParameters;
/*
变量
xLastWakeTim
e
需要被初始化为当前心跳计数值。
说明一下,
这是该变量唯一一次
被显式赋值。之后,
xLastWakeTime
将在函数
vTask
DelayUntil()
中自动更新。
*/
xLastWakeTime = xTaskGetTickCount();
/* As per most tasks, this task is
implemented in an infinite loop. */
for( ; )
{
/*
Print out the name of this task. */
vPrintString( pcTaskName
);
/*
本任务将精确的以
250<
/p>
毫秒为周期执行。同
vTaskDelay()
< br>函数一样,时间值是以心跳周期为单位的,
可以使用常
量
portTICK_RATE_MS
将毫秒转换为心跳周期。
变量
xLastWakeTime
会在
vTaskDelayUntil()
中自动更新,因此不需要
应用程序进行显示更新。
*/
vTaskDelayUntil( &xLastWakeTime, ( 250
/ portTICK_RATE_MS ) );
}
}
1.7
空闲任务与空闲任务钩子函数
当调用
vTaskStartScheduler()
时,调度器会自动创建一个空闲任务。空闲任务是一个非常短
小的循环—
—空闲任务拥有最低优先级
(
优先级
0
)
以保证其不会妨碍具有更高优先级的应用任
务进入运行
空闲任务钩子函数
通过空闲任务钩子函数
(
或称回调,
< br>hook, or call-back)
,可以直接在空闲任务中添加应用程<
/p>
序相关的功能。
空闲任务钩子函数会被空闲任务每循环一次就自动
调用一次。
通常空闲任务钩子函数被用于:
执行低优先级,后台或需要不停处理的功能代码。
测试处系统处理裕量
(
空闲任务只会在所有其它任务都不运行时才有机会执行,所
以测量出
空闲任务占用的处理时间就可以清楚的知道系统有多少富余的处理时间
)
。
将处理
器配置到低功耗模式——提供一种自动省电方法,
使得在没有任何应用功能需要处理
p>
的时候,系统自动进入省电模式
空闲任务钩子函数必须遵从以下规则
1.
绝不能阻或挂起。
空闲任务只会
在其它任务都不运行时才会被执行
(
除非有应用任务共享空
p>
闲任务优先级
)
。
以任何方式阻塞空闲任务都可能导致没有任务能够进入运行态!
2.
如果应用程序用到了
vTaskDelete()
AP
函数,则空闲钩子函数必须能够尽快返回。
因为在
任务被删除后,空闲任务负责回收内核资源。
如果空闲任务一直运行在钩子函数中,则无法进
行回收工作。
空闲任务钩子函数的函数名和函数原型。
void vApplicationIdleHook( void );
/* Declare a variable that
will be incremented by the hook function. */
unsigned long ulIdleCycleCount = 0UL;
/*
空闲钩子函数必须命名为
vAp
plicationIdleHook(),
无参数也无返回值。
*/
void vApplicationIdleHook( void )
{
/* This hook function does
nothing but increment a counter. */
ulIdleCycleCount++;
}
FreeRTOSConfig.h
中的配置常量
configUSE_IDLE_HOOK
p>
必须定义为
1
,
这
样空闲任务钩子函数
才会被调用。
void vTaskFunction( void *pvParameters
)
{
char *pcTaskName;
/* The string to print out is passed in
via the parameter. Cast this to a
character pointer. */
pcTaskName = ( char * ) pvParameters;
/* As per most tasks, this task is
implemented in an infinite loop. */
for( ; )
{
/*
p>
打印输出任务名,以及调用计数器
ulIdleCycleCoun
t
的值。
*/
vPrintStringAndNumber( pcTaskName,
ulIdleCycleCount );
/* Delay for a
period for 250 milliseconds. */
vTaskDelay( 250 / portTICK_RATE_MS );
}
}
1.8
改变任务优先级
vTaskPrioritySet() API
函数
API
函数
vTaskPriofitySet()
可以用于在调度
器启动后改变任何任务的优先级。
void
vTaskPrioritySet( xTaskHandle pxTask, unsigned
portBASE_TYPE uxNewPriority );
pxTask
被修改优先级的任务
句柄
(
即目标任务
)
< br>——参考
xTaskCreate()
API
函数的参数
pxCreatedTask
以了解如何得到任务句柄方面的信息。
任务可以通过传入
NULL
值来修改自己
的优先级。
uxNewPriority
p>
目标任务将被设置到哪个优先级上。如果设置的值超过了最大可用优先级
(configMAX_PRIORITIES
–
1)
,则会被自动封顶为最大值。
常量
configMAX_PRIORITIES
是在
FreeRTOSConfig.h
头文件中设置的一个编译时选项。
uxTaskPriorityGet() API
函数
uxTaskPriorityGet() API
函数用于查询一个任务的优先级。
unsigned portBASE_TYPE
uxTaskPriorityGet( xTaskHandle pxTask );
pxTask
被查询任务的句柄<
/p>
(
目标任务
)
——参考
xTaskCreate() API
函数的参
pxCreatedTask
以了解如何得到任务句柄方面的信息。
任务可以通过传入
NULL
值来查询自己的优先级。
返回值
被查询任务的当前优先级。
1.9
删除任务
vTaskDelete() API
函数
函数原型:
void vTaskDelete(
xTaskHandle pxTaskToDelete );
任务可以使用
API
函数
vTaskDelete()
删除自己或其它任务。需要说明一点,只有
内核为任务
分配的内存空间才会在任务被删除后自动回收。
任务
自己占用的内存或资源需要由应用程序自
己显式地释放。
pxTaskToDelete
被删除任务的句柄
(
目标任务
)
——
参考
xTaskCreate() API
函数的参数
pxCreatedTask
以了解如何得到任务句柄方面的信息。
任务可以通过传入
NULL
值来删除自己。
1.10
调度算法
–
简述
优先级抢占式调度
调度器总是在所有处于就绪态的任
务中选择具有最高优先级的任务来执行。
< br>抢占式”是指当任务进入就绪态或是优先级被改变时,如果处于运行态的任务优先级更低,则
该任务总是抢占当前运行的任务。
选择任务优先级
作为一种通用规则,
完成硬实时功能的任务优先级会高于完成软件时功能任务的优先级。
实现混合调度方案也是可行的,这需要在中断服务例程中显式
地进行上下文切换,从而允许同
步事件产生抢占行为,但时间事件却不行。这样做的结果
是得到了一个没有时间片机制的抢占
式系统。或许这正是所期望的,
因为获得了效率,并且这也是一种常用的调度器配置。
2.1
概览
基于
FreeRTOS
的应用程序由一组独立的任务构成——每个任务都是具有独立权限的
小程序。
这些独立的任务之间很可能会通过相互通信以提供有用的系统功能。
FreeRTOS
中所有的通信与
同步机制都是
基于队列实现的。
2.2
队列的特性
数据存储
队列可以保存有限个具有确
定长度的数据单元。
队列可以保存的最大单元数目被称为队列的
“深
度”。
在队列创建时需要设定其深度和每个单元的大小。<
/p>
通常情况下,队列被作为
FIFO(
先进先出
)
使
用,即数据由队列尾写入,从队列首读出。当然,
由队列首写入也是可能的。
往队列写入数据是通过字节拷贝把数据复制存
储到队列中;从队列读出数据使得把队列中的数
据拷贝删除。
可被多任务存取
< br>队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。
所有任务都可以
向同一队列
写入和读出。一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。
读队列时阻塞
当某个任务试图读一个
队列时,其可以指定一个阻塞超时时间。
在这段时间中,
如果队
列为空,
该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程往其等
待的队列中写
入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定
的阻塞时间,即使
队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
由于队列可以被多个任务读取,
所以对单个队列而言,也可能有多个任务
处于阻塞状态以等待队列数据有效。
这种情况下,一
旦队列数据
有效,
只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的
任务。
而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是
等待最久的任务。
写队列时阻塞
同读队列一样,
任务也可以在写队列时指定一个阻塞超时时间。
这个时间是当被写队列
已满时,
任务进入阻塞态以等待队列空间有效的最长时间。
由于
队列可以被多个任务写入,所以对单个
队列而言,也可能有多个任务处于阻塞状态以等待
队列空间有效。
这种情况下,一旦队列空间
有效,
只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务
。而如
果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。<
/p>
2.3
使用队列
xQueueCreate() API
函数
队列在使用前必须先被创建。
队列由声明为
xQueueHandle
< br>的变量进行引用
。
xQueueCreate()
用于创建一个队列,并返回一
个
xQueue
Handle
句柄以便于对其创建的队列进行引用。
当创建队
列时,
FreeRTOS
从堆空间中
分配内存空间。分配的空间用于存储队列数据结构本身以及队列中包含的数据单元。
如果
内存
堆中没有足够的空间来创建队列,
xQueueCreat
e()
将返回
NULL
。
队列创建函数原型
xQueueHandle xQueueCreate( unsigned
portBASE_TYPE uxQueueLength,
unsigned portBASE_TYPE uxItemSize );
uxQueueLength
队列能够存储的最大单元数目,即队列深度。
uxItemSize
队列中数据单元的长度,
以字节为单位
。
返回值
NULL
表示没有足够的堆空间分配给队列而导致创建失败。非
NULL
值表示队列创建成
功。
此返
回值应当保存下来,以作为操作此队列的句柄。
xQueueSendToBack()
与
xQueueSendToFront() API
函数
xQ
ueueSendToBack()
用于将数据发送到队列尾;
而
xQueueSendToFront()
< br>用于将数据发送到队列首。
xQueueSend()
完全等同于
xQueueSendToBack()
。
但
切
记
不
要
在
中
断
服
务
例
程
中
调
用
xQueueSendToFront()
或
xQueueSendToBac
k()
。系统提供中断安全版本的
xQueueSendToF
rontFromISR()
与
xQueueSendToBa
ckFromISR()
用于在中断服务中实现相同的功能。
xQueueSendToFront()
API
函数原型
portBASE_TYPE xQueueSendToFront(
xQueueHandle xQueue,const void * pvItemToQueue,
portTickType xTicksToWait );
xQueueSendToBack() API
函数原型
portBASE_TYPE xQueueSendToBack(
xQueueHandle xQueue,const void * pvItemToQueue,
portTickType xTicksToWait );
xQueue
目标队列的句柄
p>
。这个句柄即是调用
xQueueCreate()
创建该队列时的返回值。
pvItemToQueue
发送数据的指针。
其指向将要复制到目标队列中的数据单元。由于在创建队列
时设置了队列中数
据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存
储区域。
p>
xTicksToWait
阻塞超时时间
。
如果在发送时队列已满,
这个时间即是任务处于阻塞态等待队列
空间有效的最长等待时间。
p>
如
果
xTicksToWait
设为
0
,
并且队列已满,
则
< br>xQueueSendToFront()
与
xQueu
eSendToBack()
均会立即返回。
阻塞时间是以系统
心跳周期为单位的,所以绝对时间取决于系统心跳频率。
常
量<
/p>
portTICK_RATE_MS
可以用来把心跳时间单位转换为毫秒时间单位。
如
果
把
xTicksToWait
设置为
portMAX_DELAY
,
并且在
FreeRTOSConig.h
中设定
INCLUDE_vTaskSuspend
为
1
,那么阻塞等待将没有超时限制。
返回值
有两个可能的返回值
:
1.
pdPASS
返回
pdPASS
只
会有一种情况,那就是数据被成功发送到队列中
。如果设定了阻塞超时时间
(xTicksToWait
非
0)
,在函数返回之前任务将被转移到阻塞态以等待队列空间有效—在超时到
来前
能够将数据成功写入到队列,函数则会返回
pdPASS
。
p>
2. errQUEUE_FULL
如果由于队列已满而无法将数据写入,
则将返回
errQUEUE_FULL
。如果设定了阻塞超时
时间
(
xTicksToWait
非
0
),在函数返回之前任务将被转移到阻塞态以等待队列空间有
效。
但直到
超时也没有其它任务或是中断服务例程读取队列而腾
出空间,函数则会返回
errQUEUE_FULL
。
xQueueReceive()
与
xQueuePeek() API
函数
xQ
ueueReceive()
用于从队列中接收
(
读取)数据单元。
接收到的单元同时会从队列中删除。
xQueuePeek()
也是从从队列中接收数据单元,
不同的是并不从队列中删出接收到的单元。
xQueuePee
k()
从队列首接收到数据后,
不会修改队列中的数据,也不会
改变数据在队列中的存
储序顺。
切记不要在中断服务例程中调用
xQueueRceive(
)
和
xQueuePeek()
。中断
安全版本
的替代
API
函数
xQueueReceiveFromISR()
xQueueReceive() API
函数原型
portBASE_TYPE xQueueReceive(
xQueueHandle xQueue,const void * pvBuffer,
portTickType xTicksToWait );
xQueuePeek() API
函数原型
portBASE_TYPE xQueuePeek( xQueueHandle
xQueue,const void * pvBuffer,
portTickType xTicksToWait );
xQueue
被读队列的句柄
p>
。这个句柄即是调用
xQueueCreate()
创建该队列时的返回值。
pvBuffer
接收缓存指针
。其
指向一段内存区域,用于接收从队列中拷贝来的数据。
数据单
元的长度在创建队列时就已经被设定,所以该指针指向的内存区域大小应当足够保存一
个
数据单元。
xTicksToWait
阻塞超时时间
。
如果在接收时队列为空,
则这个时间是任务处于阻塞状态
以等待
队列数据有效的最长等待时间。
FreeRTOSConig.h
中设定
INCLUDE_vTaskSuspend
为
1
,那么阻塞等待将没有超时限制。
返回值
有两个可能的返回值
:
1.
pdPASS
-
-
-
-
-
-
-
-
-
上一篇:高级英语19春在线作业1-0001
下一篇:RTX-实时操作系统