怎样设计复杂多任务程序.docx

上传人:b****1 文档编号:10680640 上传时间:2023-05-27 格式:DOCX 页数:13 大小:22.17KB
下载 相关 举报
怎样设计复杂多任务程序.docx_第1页
第1页 / 共13页
怎样设计复杂多任务程序.docx_第2页
第2页 / 共13页
怎样设计复杂多任务程序.docx_第3页
第3页 / 共13页
怎样设计复杂多任务程序.docx_第4页
第4页 / 共13页
怎样设计复杂多任务程序.docx_第5页
第5页 / 共13页
怎样设计复杂多任务程序.docx_第6页
第6页 / 共13页
怎样设计复杂多任务程序.docx_第7页
第7页 / 共13页
怎样设计复杂多任务程序.docx_第8页
第8页 / 共13页
怎样设计复杂多任务程序.docx_第9页
第9页 / 共13页
怎样设计复杂多任务程序.docx_第10页
第10页 / 共13页
怎样设计复杂多任务程序.docx_第11页
第11页 / 共13页
怎样设计复杂多任务程序.docx_第12页
第12页 / 共13页
怎样设计复杂多任务程序.docx_第13页
第13页 / 共13页
亲,该文档总共13页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

怎样设计复杂多任务程序.docx

《怎样设计复杂多任务程序.docx》由会员分享,可在线阅读,更多相关《怎样设计复杂多任务程序.docx(13页珍藏版)》请在冰点文库上搜索。

怎样设计复杂多任务程序.docx

怎样设计复杂多任务程序

在网上发现这个贴子非常好!

不多说了自己看吧!

有原文。

如何设计复杂的多任务程序

田开坤

湖北师范学院物电学院电工电子中心435002tkaikun@

我们在入门阶段,一般面对的设计都是单一的简单的任务,流程图可以如图1所示,通

常会用踏步循环延时来满足任务需要。

面对多任务,稍微复杂的程序设计,沿用图1的思想,我们会做出如图2所示的程序,

在大循环体中不断增加任务,通常还要用延时来满足特定任务节拍,这种程序设计思想它有

明显的不足,主要是各个任务之间相互影响,增加新的任何之后,以前很好的运行的任务有

可能不正常,例如数码管动态扫描,本来显示效果很好的驱动函数,在增加新的任务后出现

闪烁,显示效果变差了。

初始化

任务体1

延时

任务体2任务体?

任务体N

变量

任务体0

图1单一任务简单流程图图2多任务简单流程图

很明显,初学者在设计程序时,需要从程序构架思想上下功夫,在做了大量基本模块练

习之后,需要总结提炼自己的程序设计思路(程序架构思想)。

首先我们来理解“任务”,所谓任务,就是需要CPU周期“关照”的事件,绝大多数任

务不需要CPU一直“关照”,例如启动ADC的启动读取。

甚至有些任务“害怕”CPU一直

“关照”例如LCD的刷新,因为LCD是显示给人看的,并不需要高速刷新,即便是显示的

内容在高速变化,也不需要高速刷新,道理是一样的。

这样看来,让CPU做简单任务一定很

浪费,事实也是如此,绝大多数简单任务,CPU都是在“空转”(循环踏步延时)。

对任务总

结还可以知道,很多任务需要CPU不断“关照”,其实这种“不断”也是有极限的,比如数

码管动态扫描,能够做到40Hz就可以了,又如键盘扫描,能够做到20Hz(经验值),基本上

也就不会丢有效按键键值了,再如LCD刷新,我觉得做到10Hz就可以了,等等。

看来,绝

大多数任务都是工作在低速频度。

而我们的CPU一旦运行起来,速度又很快,CPU本身就是

靠很快的速度执行很简单的指令来胜任复杂的任务(逻辑)的。

如果有办法把“快”的CPU

分成多个慢的CPU,然后给不同的任务分配不同速度的CPU,这种设想是不是很好呢!

确实

很好,下面就看如何将“快”的CPU划分成多个“慢”的CPU。

根据这种想法,我们需要合理分配CPU资源来“关照”不同的任务,最好能够根据任务

本身合理占用CPU资源,首先看如图3所示的流程图,各个任务流程独立,各任务通过全局

变量来交互信息,在流程中有一个重要的模块“任务切换”,就是任务切换模块实现CPU合

理分配,这个任务切换模块是怎么实现的呢?

图3多任务复杂流程图

首先需要理解,CPU一旦运行起来,就无法停止(硬件支持时钟停止的不在这里讨论),

谁能够控制一批脱缰的马呢?

对了,有中断,中断能够让CPU回到特定的位置,设想,能不

能用一个定时中断,周期性的将CPU这匹运行着的脱缰的马召唤回来,重新给它安排特定的

任务,事实上,任务切换就是这样实现的。

图A

图B

图4定时中断实现任务切换

如图4A所示,CPU在空闲任务循环等待,定时中断将CPU周期性唤回,根据任务设计

了不同的响应频度,满足条件的任务将获得CPU资源,CPU为不同任务“关照”完成后,再

次返回空闲任务,如此周而复始,对于各个任务而言,好像各自拥有一个独立的CPU,各自

独立运行。

用这种思想构建的程序框架,最大的好处是任务很容易裁剪,系统能够做得很复

杂。

在充分考虑单片机中断特性(在哪里中断就返回到哪里)后,实际可行的任务切换如图

4B所示,定时中断可能发生在任务调度,随机任务执行的任何时候,图中最大的框框所示,

不管中断在何时发生,它都会正常返回,定时中断所产生的影响只在任务调度模块起作用,

即依次让不同的任务按不同的节拍就绪。

任务调度会按一定的优先级执行就绪任务。

总结不同的任务需要CPU关照的频度,选择最快的那个频度来设定定时器中断的节拍,

一般选择200Hz,或者100Hz都可以。

另外再给每个任务设定一个节拍控制计数器C,也就

是定时器每中断多少次后执行任务一次。

例如取定时中断节拍为200Hz,给任务设定的C=10,

则任务执行频度为200/10=20Hz,如果是数码管扫描,按40Hz不闪烁规律,则任务节拍控制

计数器C=5即可。

在程序设计中,C代表着任务运行的节拍控制参数,我们习惯用delay来

描述,不同的任务用task0,task1……来描述。

明天继续写如何用代码实现!

2009-6-29

下面我们来用代码实现以上多任务程序设计思想。

首先是任务切换

while

(1)

{

if(task_delay[0]==0)task0();//task0就绪,

if(task_delay[1]==0)task1();//task1就绪,

……

}

很显然,执行任务的条件是任务延时量task_delay=0,那么任务延时量谁来控制呢?

定时

器啊!

定时器中断对任务延时量减一直到归零,标志任务就绪。

当没有任务就绪时,任务切

换本身就是一个Idle任务。

voidtimer0(void)interrupt1

{

if(task_delay[0])task_delay[0]--;

if(task_delay[1])task_delay[1]--;

……

}

例如timer0的中断节拍为200Hz,task0_delay初值为10,则task0()执行频度为

200/10=20Hz。

有了以上基础,我们来设计一个简单多任务程序,进一步深入理解这种程序设计思想。

任务要求:

用单片机不同IO脚输出1Hz,5Hz,10Hz,20Hz方波信号,这个程序很短,将

直接给出。

#include"reg51.h"

#defineTIME_PER_SEC200//定义任务时钟频率,200Hz

#defineCLOCK22118400//定义时钟晶振,单位Hz

#defineMAX_TASK4//定义任务数量

externvoidtask0(void);//任务声明

externvoidtask1(void);

externvoidtask2(void);

externvoidtask3(void);

sbitf1Hz=P1^0;//端口定义

sbitf5Hz=P1^1;

sbitf10Hz=P1^2;

sbitf20Hz=P1^3;

unsignedchartask_delay[4];//任务延时变量定义

//定时器0初始化

voidtimer0_init(void)

{

unsignedchari;

for(i=0;i

TMOD=(TMOD&0XF0)|0X01;//定时器0工作在模式1,16Bit定时器模

TH0=255-CLOCK/TIME_PER_SEC/12/256;

TL0=255-CLOCK/TIME_PER_SEC/12%256;

TR0=1;

ET0=1;//开启定时器和中断

}

//系统OS定时中断服务

voidtimer0(void)interrupt1

{

unsignedchari;

TH0=255-CLOCK/TIME_PER_SEC/12/256;

TL0=255-CLOCK/TIME_PER_SEC/12%256;

for(i=0;i

//每节拍对任务延时变量减1,减至0后,任务就绪。

}

/*main主函数*/

voidmain(void)

{

timer0_init();

EA=1;//开总中断

while

(1)

{

if(task_delay[0]==0){task0();task_delay[0]=TIME_PER_SEC/2;}

//要产生1hz信号,翻转周期就是2Hz,以下同

if(task_delay[1]==0){task1();task_delay[1]=TIME_PER_SEC/10;}

//要产生5hz信号,翻转周期就是10Hz,以下同

if(task_delay[2]==0){task2();task_delay[2]=TIME_PER_SEC/20;}

if(task_delay[3]==0){task3();task_delay[3]=TIME_PER_SEC/40;}

}

}

voidtask0(void)

{

f1Hz=!

f1Hz;

}

voidtask1(void)

{

f5Hz=!

f5Hz;

}

voidtask2(void)

{

f10Hz=!

f10Hz;

}

voidtask3(void)

{

f20Hz=!

f20Hz;

}

仿真效果如图5所示。

图5仿真波形图

同样的程序,同学们可以考虑用图2所示的思想设计,看看容易不容易,如果你的程序

实现了相同的功能,如果我改变要求,改变信号的频率,你的程序容易修改吗?

要进一步完善这种程序设计思想,有几个问题还需要考虑:

对任务本身有什么要求?

不同任务之间有没有优先级?

(不同的事情总有个轻重缓急吧!

任务间如何延时?

……

为了回答这些问题,下面我们来分析CPU的运行情况。

图6CPU运行情况示意图

CPU运行情况如图6所示,黑色区域表示CPU进程,系统启动后,CPU将无休止的运行,

CPU资源将如何分配呢?

程序首先进入“任务切换”进程,如果当前没有任务就绪,就在任

务切换进程循环(也可以理解为空闲进程),定时中断将CPU当前进程打断,在定时中断进

程可能让某些任务就绪,中断返回任务切换进程,很快会进入就绪任务0,CPU“关照”完

任务0,再次回到任务切换进程,如果还有其它任务就绪,还会再次进入其它任务,没有任

务就循环等待,定时中断会不断让新的任务就绪,CPU也会不断进入任务“关照”。

这样不

同的任务就会获得不同的CPU资源,每一个任务都像是拥有一个独立的CPU为之服务。

从这种进程切换我们可以看出,在定时中断和任务切换过程中,额外的占用了一些CPU

资源,这就是定时中断频度不宜太快,否则将大大降低CPU的有效资源率,当然太慢也不行。

另外就是CPU每次关照任务的时间不能太长,如果超过一个中断周期,就会影响到其它任务

的实时性。

所谓的实时性就是按定时中断设定的节拍,准时得到CPU关照。

这样,每一个子

任务就必须简单,每次“关照”时间最好不要超过定时中断节拍周期(5ms或10ms,初学者

要对ms有一个概念,机器周期为us级的单片机,1ms可以执行上千条指令,对于像数码管

扫描,键盘扫描,LCD显示等常规任务都是绰绰有余的,只是遇到大型计算,数据排序就显

得短了)

关于任务优先级的问题:

一个复杂系统,多个任务之间总有“轻重缓急”之区别,那些

需要严格实时的任务通常用中断实现,中断能够保证第一时间相应,我们这里讨论的不是那

种实时概念,是指在最大允许时差内能够得到CPU“关照”,例如键盘扫描,为了保证较好

的操作效果,快的/慢的/长的/短的(不同人按键不一样)都能够正确识别,这就要保证足够

的扫描速度,这种扫描速度对不同的按键最好均等,如果我们按50Hz来设计,那么就要保

证键盘扫描速度在任何情况下都能够做到50Hz扫描频度,不会因为某个新任务的开启而被

破坏,如果确实有新的任务有可能破坏这个50Hz扫描频度,我们就应该在优先级安排上让

键盘扫描优先级高于那个可能影响键盘扫描的任务。

这里体现的就是当同时多个任务就绪时,

最先执行哪个的问题,任务调度时要优先执行级别高的任务。

关于“长”任务的问题:

有些任务虽然很独立,但完成一次任务执行需要很长时间,例

如DS18B20,从复位初始化到读回温度值,最长接近1s,这主要是DS18B20温度传感器完

成一次温度转换需要500到750ms,这个时间对CPU而言,简直是太长了,就像一件事情需

要我们人等待10年一样,显然这样的任务是其它任务所耽搁不起的。

像类似DS18B20这样

的器件(不少ADC也是这样),怎么设计任务体解决“长”的问题。

进一步研究这些器件发

现,真正需要CPU“关照”它们的时间并不长,关键是等待结果要很长时间。

解决的办法就

是把类似的器件驱动分成多个段:

初始化段、启动段、读结果段,而在需要花长时间等待时

间段,不要CPU关照,允许CPU去关照其它任务。

图7任务分段

任务分段如图7所示,将一个任务分成若干段,确保每段需要CPU关照时长小于定时器

中断节拍长,这样CPU在处理这些长任务时,就不会影响到其它任务的执行。

Easy51RTOS

正是基于以上程序设计思想,总结完善后提出一种耗费资源特别少并且不使用堆栈的多

线程操作系统,这个操作系统以纯C语言实现,无硬件依赖性,需要单片机的资源极少。

名为Easy51RTOS,特别适合初学者学习使用。

有任务优先级,通过技巧可以任务间延时,

缺点是高优先级任务不具有抢占功能,一个具有抢占功能的操作系统,一定要涉及到现场保

护与恢复,需要更多的RAM资源,涉及到堆栈知识,文件系统将很复杂,初学者学习难度

大。

为了便于初学者学习,将代码文件压缩至4个文件。

Easy51RTOS.Uv2Keil工程文件,KEIL用户很熟悉的

main.cmain函数和用户任务task函数文件

os_c.cEasy51RTOS相关函数文件

os_cfg.hEasy51RTOS相关配置参数头文件

文件解读如下:

os_cfg.h

#include"reg51.h"

#defineTIME_PER_SEC200//定义任务时钟频率,200Hz

#defineCLOCK22118400//定义时钟晶振,单位Hz

#defineMAX_TASK4//定义任务数量

//函数变量声明,在需要用以下函数或变量的文件中包含此头文件即可

externvoidtask0(void);

externvoidtask1(void);

externvoidtask2(void);

externvoidtask3(void);

externunsignedchartask_delay[MAX_TASK];

externvoidrun(void(*ptask)());

externvoidos_timer0_init(void);

os_c.c

#include"os_cfg.h"

unsignedchartask_delay[MAX_TASK];//定义任务延时量变量

//定时器0初始化

voidos_timer0_init(void)

{

unsignedchari;

for(i=0;i

TMOD=(TMOD&0XF0)|0X01;//定时器0工作在模式1,16Bit定时器模式

TH0=255-CLOCK/TIME_PER_SEC/12/256;

//CRY_OSC,TIME_PER_SEC在os_cfg.h中定义

TL0=255-CLOCK/TIME_PER_SEC/12%256;

TR0=1;

ET0=1;//开启定时器和中断

}

//系统OS定时中断服务

voidos_timer0(void)interrupt1

{

unsignedchari;

TH0=255-CLOCK/TIME_PER_SEC/12/256;

TL0=255-CLOCK/TIME_PER_SEC/12%256;

for(i=0;i

//每节拍对任务延时变量减1,减至0后,任务就绪。

}

//指向函数的指针函数

voidrun(void(*ptask)())

{

(*ptask)();

}

main.c

#include"os_cfg.h"

#defineTASK_DELAY0TIME_PER_SEC/1//任务执行频度为1Hz

#defineTASK_DELAY1TIME_PER_SEC/2//任务执行频度为2Hz

#defineTASK_DELAY2TIME_PER_SEC/10//任务执行频度为10Hz

#defineTASK_DELAY3TIME_PER_SEC/20//任务执行频度为20Hz

void(*codetask[])()={task0,task1,task2,task3};//获得任务PC指针

sbitLED0=P1^0;//演示用LED接口定义

sbitLED1=P1^1;

sbitLED2=P1^2;

sbitLED3=P1^3;

/*main主函数*/

voidmain(void)

{

unsignedchari;

os_timer0_init();//节拍发生器定时器初始化

EA=1;//开总中断

while

(1)

{

for(i=0;i

if(task_delay==0){run(task);break;}//就绪任务调度

}//上一行break有特殊作用,详细解释见后文

}

voidtask0(void)//任务0

{

LED0=!

LED0;

task_delay[0]=TASK_DELAY0;

}

voidtask1(void)//任务1

{

LED1=!

LED1;

task_delay[1]=TASK_DELAY1;

}

voidtask2(void)//任务2

{

LED2=!

LED2;

task_delay[2]=TASK_DELAY2;

}

voidtask3(void)//任务内分段设计

{

staticunsignedcharstate=0;//定义静态局部变量

switch(state)

{

case0:

LED3=!

LED3;

state=1;

task_delay[3]=TASK_DELAY3;

break;

case1:

LED3=!

LED3;

state=2;

task_delay[3]=TASK_DELAY3*2;

break;

case2:

LED3=!

LED3;

state=0;

task_delay[3]=TASK_DELAY3*4;

break;

default:

state=0;

task_delay[3]=TASK_DELAY3;

break;

}

}

仿真图如图8所示

图8仿真波形图

主程序巧妙实现优先级设定:

for(i=0;i

if(task_delay==0){run(task);break;}//就绪任务调度

这里的break将跳出for循环,使得每次重新任务调度总是从task0开始,就意味着优先

级高的任务就绪会先执行。

这样task0具有最高优先级,task1、task2、task3优先级依次降低。

特别是voidtask3(void)用switch(state)状态机实现了任务分段,这也是任务内系统延时的

一种方法。

田开坤2009-6-30

后记:

希望同学们参考我的这种程序设计思想,完成以下系统软件设计。

数控直流稳压电源:

系统结构框图如图9所示(并非原理框图),整合这些资源,完成软

硬件设计。

图9数控稳压电源结构框图

要求如下:

1.8只数码管只能动态扫描方式驱动,4位显示电压设定值,4位显示实际测量值;

2.DS18B20用来测量关键器件温度,超过70度,关闭输出并报警:

蜂鸣器发声提示;

3.矩阵键盘要用动态扫描方式驱动,每次按键有蜂鸣器发声提示;

4.ADC+DAC实现闭环控制(AD测量值等于设定值,不等则修正DA输出);

5.使用具有EEPROM的单片机,能够存储当前设定状态;

6.尽量减少硬件成本,尽量用复杂的软件代替硬件(例如数码管不允许用专用芯片)。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2