uCOSii任务设计
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
周期性执行的任务函数编程比较单纯,只要创建一次,就能周期运行。在实际应用中, 很多任务都具有周期性,它们的任务函数都使用这种结构,如键盘扫描任务、显示刷新任务、 模拟信号采样任务等等,键盘任务的示意代码参阅上一小节。
4.1.3 事件触发执行的任务
此类任务在创建后,虽然很快可以获得运行权,但任务实体代码的执行需要等待某种事 件的发生,在相关事件发生之前,则被操作系统挂起。相关事件发生一次,该任务实体代码 就执行一次,故该类型任务称为事件触发执行的任务,其任务函数的结构如下:
程序清单 L4-5 在创建任务时传送参数
void TaskKey (void *pdata)
Fra Baidu bibliotek
//键盘任务函数(示意)
{
INT8U key; INT16U baud; for (;;)
//波特率,由用户通过键盘选定 //无限循环,也可用 while (1)
{ key=keyin();
//获取按键操作信息
信号量)。 z 如果该任务有关联任务(或 ISR),必须在自我删除之前将这种关联关系解除,而解
除关联关系需要删除关联任务和通信工具,这是得不偿失而又非常麻烦的事情。 适合采用“创建任务”的方式来启动的任务,通常是“孤立任务”,它们不和其它任务 进行通信(ISR 除外),只使用共享资源来获取信息和输出信息。如果不满足这个条件,应该 采用下面两种任务函数机构,并在系统启动时创建好。
}
void TaskStart(void *pdata) //启动任务 {
pdata = pdata; 系统硬件初始化; //时钟系统、中断系统、外设等等 创建各个任务; //如键盘任务、显示任务、采样任务、数据处理任务、打印任务等等 创建各种通信工具; //如信号量、消息邮箱、消息队列等等 OSTaskDel (OS_PRIO_SELF); //删除自己 }
第四章 任务设计
在基于实时操作系统的应用程序设计中,任务设计是整个应用程序的基础,其他软件设 计工作都是围绕任务设计来展开,任务设计就是设计“任务函数”和相关的数据结构。
4.1 任务函数的结构
在用户任务函数中,必须包含至少一次对操作系统服务函数的调用,否则比其优先级低 的任务将无法得到运行机会,这是用户任务函数与普通函数的明显区别。任务函数的结构按 任务的执行方式可以分为三类:单次执行类、周期执行类和事件触发类,下面分别介绍其结 构特点。
-5-
当延时时间到后,重新进入就绪状态,通常能够很快获得运行权。 通过合理设置调用 OSTimeDly( ) 或 OSTimeDlyHMSM( )时的参数值可以调整任务的执
行周期。当任务执行周期远大于系统时钟节拍时,任务执行周期的相对误差比较小;当任务 执行周期只有几个时钟节拍时,相邻两次执行的间隔时间抖动不能忽视,任务的执行周期的 相对误差比较大,只适用于对周期稳定性要求不高的任务(如键盘任务);当任务执行周期 只有一个时钟节拍时,可将该任务的功能放到 OSTimeTickHook( )(时钟节拍函数中的钩子 函数)中去执行;当任务执行周期小于一个时钟节拍或者不是时钟节拍的整数倍时,将无法 使用延时函数对其进行周期控制,只能采用独立于操作系统的定时中断来触发。采用独立定 时器触发的任务具有很高的周期稳定性。
//“发送”按钮,创建串行口发送任务
OSTaskCreate(TaskUart,(void *)0,&TaskUartStk[TASK_STK_SIZE-1],3);
break; case KEY_$$$: //其它按钮的处理代码
.
.
.
} OSTimeDly(2);
//延时
}
}
void TaskUart(void *pdata) {
//延时
}
}
void TaskUart(void *pdata) {
baud = *pdata; 串行口初始化; 组织发送帧; 数据指针初始化; 发送数据;
//串行口发送任务(示意) //获取波特率 //用获取波特率的波特率初始化串行口
-4-
OSTaskDel (OS_PRIO_SELF); //删除自己 }
由于“启动任务”不是用户系统的实质任务,又占用高优先级资源和任务资源,故不常 用。更常用的方法是将“启动任务”所完成的操作交给一个用户系统的实质任务来完成。这 时,主函数和有启动功能的任务函数的示意代码如下:
程序清单 L4-3 以用户任务代替启动任务
void main (void) //主函数
{ OSInit ();
虽然用“创建任务”的方式来启动一个任务有以上方便之处,但每次启动任务都要调用 “任务创建函数”,需要对“任务控制块”进行全面初始化,并对“任务控制块链表”和“任 务就绪表”进行操作,比较耗时,故只适用于实时性要求不高的任务(如键盘操作启动的任 务)。采用“创建任务”的方式来启动一个任务除了实时性差外,还可能在任务自我删除后 出现后遗症:
//调用 OSTimeDly( )或 OSTimeDlyHMSM( )
}
} 周期性执行的任务函数也由三部分组成:第一部分“进行准备工作的代码”和第二部分
“任务实体代码”的含义与单次执行任务的含义相同,第三部分是“调用系统延时函数”,
把 CPU 的控制权主动交给操作系统,使自己挂起,再由操作系统来启动其它已经就绪的任务。
//键盘任务函数(示意)
{
INT8U key; for (;;)
switch (key)
{ case KEY_SUART:
//“发送”按钮,创建串行口发送任务,带参数
OSTaskCreate(TaskUart,&baud,&TaskUartStk[TASK_STK_SIZE-1],3);
break; case KEY_$$$: //其它按钮的处理代码
.
.
.
} OSTimeDly(2);
z 占用的共享资源尚未释放,使其它需要使用该资源的任务不能运行。 z 通信关系的“上家”任务(或 ISR)发出的信号量或消息将被积压而得不到响应。 z 通信关系的“下家”任务因为得不到信号量或消息而被遗弃(被永远挂起)。 z 可能留下未删除干净的废弃变量。 因此: z 如果该任务使用了共享资源,必须在自我删除之前释放(如释放内存块,发送互斥
单次执行的任务采用“创建任务函数”来启动,当该任务被另外一个任务(或主函数) 创建时,就进入就绪状态,等到比它优先级高的任务都被挂起来时便获得运行权,进入运行 状态,任务完成后再自行删除,“启动任务”就是一个例子。
采用“启动任务”后,主函数就可以简化为三行,只负责与操作系统有关的事情,即初 始化操作系统、创建“启动任务”、启动操作系统,使主函数的内容固定下来,与具体的应 用系统无关。真正启动系统所需要的准备工作由“启动任务”来完成,它的内容与具体的系
程序清单 L4-7 事件触发的任务函数的结构
void MyTask (void *pdata)
//事件触发执行的任务函数
{ 进行准备工作的代码; for (;;)
//无限循环,也可用 while (1)
{ 调用获取事件的函数; 任务实体代码;
//如:等待信号量、等待邮箱中的消息等等。
}
}
事件触发执行的任务函数也由三部分组成:第一部分“进行准备工作的代码”和第三部 分“任务实体代码”的含义与前面两种任务的含义相同,第二部分是“调用获取事件的函数”, 使用了操作系统提供的某种通信机制,等待另外一个任务(或 ISR)发出的信息(如信号量 或邮箱中的消息),在取得这个信息之前处于等待状态(挂起状态),当另外一个任务(或 ISR) 发出相关信息时(调用了操作系统提供的通信函数),操作系统就使该任务进入就绪状态, 通过任务调度,任务的实体代码获得运行权,完成该任务的实际功能。
//初始化操作系统
OSTaskCreate(TaskUser1,(void *)0,&TaskUser1Stk[TASK_STK_SIZE-1],1);//创建任务 1 OSStart (); //启动操作系统,开始对任务进行调度管理
}
-2-
void TaskUser1(void *pdata)
//用户任务 1
pdata = pdata; 串行口初始化;
//串行口发送任务(示意)
-3-
组织发送帧; 数据指针初始化; 发送数据; OSTaskDel (OS_PRIO_SELF); //删除自己 }
采用“任务创建”的方式来启动任务,可以省略用通信手段触发任务的麻烦,还可以通
过*pdata 来传递原始参数,使得每次启动任务时可以有不同的工作状态。如下面的程序在创 建串行口发送任务时同时指定波特率:
4.1.1 单次执行的任务
此类任务在创建后只执行一次,执行结束后即自行删除,其任务函数的结构如下:
程序清单 L4-1 单次执行任务函数的结构
void MyTask (void *pdata)
//单次执行的任务函数
{ 进行准备工作的代码; 任务实体代码; 调用任务删除函数;
//调用 OSTaskDel(OS_PRIO_SELF)
如用一个“发送”按钮启动串行口通信任务,将数据发送到上位机。在键盘任务中,按 下“发送”按钮后就发出信号量。在串行口任务中,只要得到信号量就将数据发给上位机,
-6-
示意代码如下:
程序清单 L4-8 用信号量触发任务
OS_EVENT *Sem; //信号量指针
void TaskKey (void *pdata)
4.1.2 周期性执行的任务
此类任务在创建后按一个固定的周期来执行,其任务函数的结构如下:
程序清单 L4-6 周期性任务函数的结构
void MyTask (void *pdata)
//周期性执行的任务函数
{ 进行准备工作的代码; for (;;)
//无限循环,也可用 while (1)
{ 任务实体代码; 调用系统延时函数;
在“启动任务”中完成与系统硬件有关的各种初始化工作,然后创建各个实质任务和所 需要的各种通信工具,至此系统才真正完成准备工作,“启动任务”的使命也就结束了,最 后将自己删除。为了保证“启动任务”能够连续运行,必须将“启动任务”的优先级选择为 最高。否则,当“启动任务”创建一个优先级高于自己的任务时,刚刚创建的任务就会立即 进入运行状态,而与这个任务关联的其它任务可能还没有创建,它使用的通信工具也还没有 创建,系统必然出错。“启动任务”调用的“自我删除”函数会进行任务调度操作,从而使 系统开始运行各个实质任务。
-1-
统密切相关。主函数和“启动任务”的示意代码如下:
程序清单 L4-2 使用启动任务
void main (void) //主函数
{ OSInit ();
//初始化操作系统
OSTaskCreate(TaskStart,(void *)0,&TaskStartStk[TASK_STK_SIZE-1],1);//创建启动任务 OSStart (); //启动操作系统,开始对任务进行调度管理
{
pdata = pdata;
系统硬件初始化; //时钟系统、中断系统、外设等等 创建各个任务; //如键盘任务、显示任务、采样任务、数据处理任务、打印任务等等 创建各种通信工具; //如信号量、消息邮箱、消息队列等等
用户任务 1 本身的代码;
}
使用“单次执行”的任务函数结构的场合反而是可以多次执行的任务,每当需要执行该 任务时就将该任务创建一次。由键盘操作来启动的任务常采用这种结构,如用一个“发送” 按钮启动串行口通信任务,其程序如下:
程序清单 L4-4 用创建任务的方式启动任务
void TaskKey (void *pdata)
//键盘任务函数(示意)
{
INT8U key; for (;;)
//无限循环,也可用 while (1)
{ key=keyin();
//获取按键操作信息
switch (key)
{ case KEY_SUART:
}
单次执行的任务函数由三部分组成:第一部分是“进行准备工作的代码”,完成各项准 备工作,如定义和初始化变量、初始化某些设备等等,这部分代码的多少根据实际需要来决 定,也可能完全空缺。第二部分是“任务实体代码”,这部分代码完成该任务的具体功能, 其中通常包含对若干系统函数的调用,除若干临界段代码(中断被关闭)外,任务的其它代 码均可以被中断,以保证高优先级的就绪任务能够及时运行。第三部分是“调用任务删除函 数”,该任务将自己删除,操作系统将不再管理它。
4.1.3 事件触发执行的任务
此类任务在创建后,虽然很快可以获得运行权,但任务实体代码的执行需要等待某种事 件的发生,在相关事件发生之前,则被操作系统挂起。相关事件发生一次,该任务实体代码 就执行一次,故该类型任务称为事件触发执行的任务,其任务函数的结构如下:
程序清单 L4-5 在创建任务时传送参数
void TaskKey (void *pdata)
Fra Baidu bibliotek
//键盘任务函数(示意)
{
INT8U key; INT16U baud; for (;;)
//波特率,由用户通过键盘选定 //无限循环,也可用 while (1)
{ key=keyin();
//获取按键操作信息
信号量)。 z 如果该任务有关联任务(或 ISR),必须在自我删除之前将这种关联关系解除,而解
除关联关系需要删除关联任务和通信工具,这是得不偿失而又非常麻烦的事情。 适合采用“创建任务”的方式来启动的任务,通常是“孤立任务”,它们不和其它任务 进行通信(ISR 除外),只使用共享资源来获取信息和输出信息。如果不满足这个条件,应该 采用下面两种任务函数机构,并在系统启动时创建好。
}
void TaskStart(void *pdata) //启动任务 {
pdata = pdata; 系统硬件初始化; //时钟系统、中断系统、外设等等 创建各个任务; //如键盘任务、显示任务、采样任务、数据处理任务、打印任务等等 创建各种通信工具; //如信号量、消息邮箱、消息队列等等 OSTaskDel (OS_PRIO_SELF); //删除自己 }
第四章 任务设计
在基于实时操作系统的应用程序设计中,任务设计是整个应用程序的基础,其他软件设 计工作都是围绕任务设计来展开,任务设计就是设计“任务函数”和相关的数据结构。
4.1 任务函数的结构
在用户任务函数中,必须包含至少一次对操作系统服务函数的调用,否则比其优先级低 的任务将无法得到运行机会,这是用户任务函数与普通函数的明显区别。任务函数的结构按 任务的执行方式可以分为三类:单次执行类、周期执行类和事件触发类,下面分别介绍其结 构特点。
-5-
当延时时间到后,重新进入就绪状态,通常能够很快获得运行权。 通过合理设置调用 OSTimeDly( ) 或 OSTimeDlyHMSM( )时的参数值可以调整任务的执
行周期。当任务执行周期远大于系统时钟节拍时,任务执行周期的相对误差比较小;当任务 执行周期只有几个时钟节拍时,相邻两次执行的间隔时间抖动不能忽视,任务的执行周期的 相对误差比较大,只适用于对周期稳定性要求不高的任务(如键盘任务);当任务执行周期 只有一个时钟节拍时,可将该任务的功能放到 OSTimeTickHook( )(时钟节拍函数中的钩子 函数)中去执行;当任务执行周期小于一个时钟节拍或者不是时钟节拍的整数倍时,将无法 使用延时函数对其进行周期控制,只能采用独立于操作系统的定时中断来触发。采用独立定 时器触发的任务具有很高的周期稳定性。
//“发送”按钮,创建串行口发送任务
OSTaskCreate(TaskUart,(void *)0,&TaskUartStk[TASK_STK_SIZE-1],3);
break; case KEY_$$$: //其它按钮的处理代码
.
.
.
} OSTimeDly(2);
//延时
}
}
void TaskUart(void *pdata) {
//延时
}
}
void TaskUart(void *pdata) {
baud = *pdata; 串行口初始化; 组织发送帧; 数据指针初始化; 发送数据;
//串行口发送任务(示意) //获取波特率 //用获取波特率的波特率初始化串行口
-4-
OSTaskDel (OS_PRIO_SELF); //删除自己 }
由于“启动任务”不是用户系统的实质任务,又占用高优先级资源和任务资源,故不常 用。更常用的方法是将“启动任务”所完成的操作交给一个用户系统的实质任务来完成。这 时,主函数和有启动功能的任务函数的示意代码如下:
程序清单 L4-3 以用户任务代替启动任务
void main (void) //主函数
{ OSInit ();
虽然用“创建任务”的方式来启动一个任务有以上方便之处,但每次启动任务都要调用 “任务创建函数”,需要对“任务控制块”进行全面初始化,并对“任务控制块链表”和“任 务就绪表”进行操作,比较耗时,故只适用于实时性要求不高的任务(如键盘操作启动的任 务)。采用“创建任务”的方式来启动一个任务除了实时性差外,还可能在任务自我删除后 出现后遗症:
//调用 OSTimeDly( )或 OSTimeDlyHMSM( )
}
} 周期性执行的任务函数也由三部分组成:第一部分“进行准备工作的代码”和第二部分
“任务实体代码”的含义与单次执行任务的含义相同,第三部分是“调用系统延时函数”,
把 CPU 的控制权主动交给操作系统,使自己挂起,再由操作系统来启动其它已经就绪的任务。
//键盘任务函数(示意)
{
INT8U key; for (;;)
switch (key)
{ case KEY_SUART:
//“发送”按钮,创建串行口发送任务,带参数
OSTaskCreate(TaskUart,&baud,&TaskUartStk[TASK_STK_SIZE-1],3);
break; case KEY_$$$: //其它按钮的处理代码
.
.
.
} OSTimeDly(2);
z 占用的共享资源尚未释放,使其它需要使用该资源的任务不能运行。 z 通信关系的“上家”任务(或 ISR)发出的信号量或消息将被积压而得不到响应。 z 通信关系的“下家”任务因为得不到信号量或消息而被遗弃(被永远挂起)。 z 可能留下未删除干净的废弃变量。 因此: z 如果该任务使用了共享资源,必须在自我删除之前释放(如释放内存块,发送互斥
单次执行的任务采用“创建任务函数”来启动,当该任务被另外一个任务(或主函数) 创建时,就进入就绪状态,等到比它优先级高的任务都被挂起来时便获得运行权,进入运行 状态,任务完成后再自行删除,“启动任务”就是一个例子。
采用“启动任务”后,主函数就可以简化为三行,只负责与操作系统有关的事情,即初 始化操作系统、创建“启动任务”、启动操作系统,使主函数的内容固定下来,与具体的应 用系统无关。真正启动系统所需要的准备工作由“启动任务”来完成,它的内容与具体的系
程序清单 L4-7 事件触发的任务函数的结构
void MyTask (void *pdata)
//事件触发执行的任务函数
{ 进行准备工作的代码; for (;;)
//无限循环,也可用 while (1)
{ 调用获取事件的函数; 任务实体代码;
//如:等待信号量、等待邮箱中的消息等等。
}
}
事件触发执行的任务函数也由三部分组成:第一部分“进行准备工作的代码”和第三部 分“任务实体代码”的含义与前面两种任务的含义相同,第二部分是“调用获取事件的函数”, 使用了操作系统提供的某种通信机制,等待另外一个任务(或 ISR)发出的信息(如信号量 或邮箱中的消息),在取得这个信息之前处于等待状态(挂起状态),当另外一个任务(或 ISR) 发出相关信息时(调用了操作系统提供的通信函数),操作系统就使该任务进入就绪状态, 通过任务调度,任务的实体代码获得运行权,完成该任务的实际功能。
//初始化操作系统
OSTaskCreate(TaskUser1,(void *)0,&TaskUser1Stk[TASK_STK_SIZE-1],1);//创建任务 1 OSStart (); //启动操作系统,开始对任务进行调度管理
}
-2-
void TaskUser1(void *pdata)
//用户任务 1
pdata = pdata; 串行口初始化;
//串行口发送任务(示意)
-3-
组织发送帧; 数据指针初始化; 发送数据; OSTaskDel (OS_PRIO_SELF); //删除自己 }
采用“任务创建”的方式来启动任务,可以省略用通信手段触发任务的麻烦,还可以通
过*pdata 来传递原始参数,使得每次启动任务时可以有不同的工作状态。如下面的程序在创 建串行口发送任务时同时指定波特率:
4.1.1 单次执行的任务
此类任务在创建后只执行一次,执行结束后即自行删除,其任务函数的结构如下:
程序清单 L4-1 单次执行任务函数的结构
void MyTask (void *pdata)
//单次执行的任务函数
{ 进行准备工作的代码; 任务实体代码; 调用任务删除函数;
//调用 OSTaskDel(OS_PRIO_SELF)
如用一个“发送”按钮启动串行口通信任务,将数据发送到上位机。在键盘任务中,按 下“发送”按钮后就发出信号量。在串行口任务中,只要得到信号量就将数据发给上位机,
-6-
示意代码如下:
程序清单 L4-8 用信号量触发任务
OS_EVENT *Sem; //信号量指针
void TaskKey (void *pdata)
4.1.2 周期性执行的任务
此类任务在创建后按一个固定的周期来执行,其任务函数的结构如下:
程序清单 L4-6 周期性任务函数的结构
void MyTask (void *pdata)
//周期性执行的任务函数
{ 进行准备工作的代码; for (;;)
//无限循环,也可用 while (1)
{ 任务实体代码; 调用系统延时函数;
在“启动任务”中完成与系统硬件有关的各种初始化工作,然后创建各个实质任务和所 需要的各种通信工具,至此系统才真正完成准备工作,“启动任务”的使命也就结束了,最 后将自己删除。为了保证“启动任务”能够连续运行,必须将“启动任务”的优先级选择为 最高。否则,当“启动任务”创建一个优先级高于自己的任务时,刚刚创建的任务就会立即 进入运行状态,而与这个任务关联的其它任务可能还没有创建,它使用的通信工具也还没有 创建,系统必然出错。“启动任务”调用的“自我删除”函数会进行任务调度操作,从而使 系统开始运行各个实质任务。
-1-
统密切相关。主函数和“启动任务”的示意代码如下:
程序清单 L4-2 使用启动任务
void main (void) //主函数
{ OSInit ();
//初始化操作系统
OSTaskCreate(TaskStart,(void *)0,&TaskStartStk[TASK_STK_SIZE-1],1);//创建启动任务 OSStart (); //启动操作系统,开始对任务进行调度管理
{
pdata = pdata;
系统硬件初始化; //时钟系统、中断系统、外设等等 创建各个任务; //如键盘任务、显示任务、采样任务、数据处理任务、打印任务等等 创建各种通信工具; //如信号量、消息邮箱、消息队列等等
用户任务 1 本身的代码;
}
使用“单次执行”的任务函数结构的场合反而是可以多次执行的任务,每当需要执行该 任务时就将该任务创建一次。由键盘操作来启动的任务常采用这种结构,如用一个“发送” 按钮启动串行口通信任务,其程序如下:
程序清单 L4-4 用创建任务的方式启动任务
void TaskKey (void *pdata)
//键盘任务函数(示意)
{
INT8U key; for (;;)
//无限循环,也可用 while (1)
{ key=keyin();
//获取按键操作信息
switch (key)
{ case KEY_SUART:
}
单次执行的任务函数由三部分组成:第一部分是“进行准备工作的代码”,完成各项准 备工作,如定义和初始化变量、初始化某些设备等等,这部分代码的多少根据实际需要来决 定,也可能完全空缺。第二部分是“任务实体代码”,这部分代码完成该任务的具体功能, 其中通常包含对若干系统函数的调用,除若干临界段代码(中断被关闭)外,任务的其它代 码均可以被中断,以保证高优先级的就绪任务能够及时运行。第三部分是“调用任务删除函 数”,该任务将自己删除,操作系统将不再管理它。