OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 6651|回复: 9

浅谈多任务设计

[复制链接]

5

主题

19

帖子

0

精华

初级会员

Rank: 2

积分
87
金钱
87
注册时间
2015-4-29
在线时间
5 小时
发表于 2016-3-6 22:38:38 | 显示全部楼层 |阅读模式
本帖最后由 fzwwj95 于 2016-3-6 22:40 编辑

     我们在入门阶段,一般面对的设计都是单一的简单的任务,流程图可以如图 1 所示,通常会用踏步循环延时来满足任务需要。面对多任务,稍微复杂的程序设计,沿用图 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 合理分配,这个任务切换模块是怎么实现的呢?
     首先需要理解, CPU 一旦运行起来,就无法停止(硬件支持时钟停止的不在这里讨论),谁能够控制一批脱缰的马呢?对了,有中断,中断能够让 CPU 回到特定的位置,设想,能不能用一个定时中断,周期性的将 CPU 这匹运行着的脱缰的马召唤回来,重新给它安排特定的任务,事实上,任务切换就是这样实现的。如图 4A 所示, CPU 在空闲任务循环等待,定时中断将 CPU 周期性唤回,根据任务设计了不同的响应频度,满足条件的任务将获得 CPU 资源, CPU 为不同任务“关照”完成后,再次返回空闲任务,如此周而复始,对于各个任务而言,好像各自拥有一个独立的 CPU,各自独立运行。用这种思想构建的程序框架,最大的好处是任务很容易裁剪,系统能够做得很复杂。在充分考虑单片机中断特性(在哪里中断就返回到哪里)后,实际可行的任务切换如图4B 所示,定时中断可能发生在任务调度,随机任务执行的任何时候,图中最大的框框所示,不管中断在何时发生,它都会正常返回,定时中断所产生的影响只在任务调度模块起作用,即依次让不同的任务按不同的节拍就绪。任务调度会按一定的优先级执行就绪任务。总结不同的任务需要 CPU 关照的频度,选择最快的那个频度来设定定时器中断的节拍,一般选择 200Hz,或者 100Hz 都可以。另外再给每个任务设定一个节拍控制计数器 C,也就是定时器每中断多少次后执行任务一次。例如取定时中断节拍为 200Hz,给任务设定的 C=10,则任务执行频度为 200/10=20Hz,如果是数码管扫描,按 40Hz 不闪烁规律,则任务节拍控制计数器 C=5 即可。在程序设计中, C 代表着任务运行的节拍控制参数,我们习惯用 delay 来描述,不同的任务用 task0, task1……来描述。
      


         未完待续。。。。。。。。。。。。
         下次说说代码如何实现
QQ图片20160306223652.png
QQ图片20160306223746.png
QQ图片20160306223958.png
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

22

主题

751

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1605
金钱
1605
注册时间
2015-6-10
在线时间
222 小时
发表于 2016-3-7 10:13:58 | 显示全部楼层
回复 支持 反对

使用道具 举报

5

主题

19

帖子

0

精华

初级会员

Rank: 2

积分
87
金钱
87
注册时间
2015-4-29
在线时间
5 小时
 楼主| 发表于 2016-3-7 16:12:13 | 显示全部楼层
止天 发表于 2016-3-7 10:13
这是要做系统吗?

不是,一种裸奔编程思想
回复 支持 反对

使用道具 举报

22

主题

751

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1605
金钱
1605
注册时间
2015-6-10
在线时间
222 小时
发表于 2016-3-7 17:06:38 | 显示全部楼层
有点像Protothreads
回复 支持 反对

使用道具 举报

5

主题

19

帖子

0

精华

初级会员

Rank: 2

积分
87
金钱
87
注册时间
2015-4-29
在线时间
5 小时
 楼主| 发表于 2016-3-7 20:42:15 | 显示全部楼层
止天 发表于 2016-3-7 17:06
有点像Protothreads

Protothreads我感觉是状态机
回复 支持 反对

使用道具 举报

5

主题

163

帖子

0

精华

高级会员

Rank: 4

积分
615
金钱
615
注册时间
2011-11-7
在线时间
180 小时
发表于 2016-3-7 20:51:01 来自手机 | 显示全部楼层
期待楼主更新
回复 支持 反对

使用道具 举报

5

主题

19

帖子

0

精华

初级会员

Rank: 2

积分
87
金钱
87
注册时间
2015-4-29
在线时间
5 小时
 楼主| 发表于 2016-4-2 01:09:57 | 显示全部楼层
我们来用代码实现以上多任务程序设计思想。
首先是任务切换
while(1)
{
if(task_delay[0]==0) task0(); //task0 就绪,
if(task_delay[1]==0) task1(); //task1 就绪,
……
}
很显然,执行任务的条件是任务延时量 task_delay=0,那么任务延时量谁来控制呢?定时
器啊!定时器中断对任务延时量减一直到归零,标志任务就绪。当没有任务就绪时,任务切
换本身就是一个 Idle 任务。
void timer0(void) interrupt 1
{
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"
#define TIME_PER_SEC 200 //定义任务时钟频率, 200Hz
#define CLOCK 22118400 //定义时钟晶振,单位 Hz
#define MAX_TASK 4 //定义任务数量
extern void task0(void); //任务声明
extern void task1(void);
extern void task2(void);
extern void task3(void);
sbit f1Hz = P1^0; //端口定义
sbit f5Hz = P1^1;
sbit f10Hz = P1^2;
sbit f20Hz = P1^3;
unsigned char task_delay[4]; //任务延时变量定义
//定时器 0 初始化
void timer0_init(void)
{
unsigned char i;
for(i=0;i<MAX_TASK;i++) task_delay[i]=0; //任务延时量清零
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 定时中断服务
void timer0(void) interrupt 1
{
unsigned char i;
TH0 = 255-CLOCK/TIME_PER_SEC/12/256;
TL0 = 255-CLOCK/TIME_PER_SEC/12%256;
for(i=0;i<MAX_TASK;i++) if(task_delay[i]) task_delay[i]--;
//每节拍对任务延时变量减 1 ,减至 0 后,任务就绪。
}
/*main 主函数*/
void main(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;}
}
}
void task0(void)
{
f1Hz = !f1Hz;
}
void task1(void)
{
f5Hz = !f5Hz;
}
void task2(void)
{
f10Hz = !f10Hz;
}
void task3(void)
{
f20Hz = !f20Hz;
}
回复 支持 反对

使用道具 举报

10

主题

271

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1236
金钱
1236
注册时间
2015-5-14
在线时间
352 小时
发表于 2016-4-6 14:49:45 | 显示全部楼层
既然用了定时器切换任务,做的彻底一点,做一个基于时间片的调度器不就好了。
30年众生牛马,60年诸佛龙象!
回复 支持 反对

使用道具 举报

10

主题

271

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1236
金钱
1236
注册时间
2015-5-14
在线时间
352 小时
发表于 2016-4-6 14:52:50 | 显示全部楼层
fzwwj95 发表于 2016-3-7 20:42
Protothreads我感觉是状态机

楼主所说的任务调度,也可以说是一种状态机。
30年众生牛马,60年诸佛龙象!
回复 支持 反对

使用道具 举报

0

主题

2

帖子

0

精华

初级会员

Rank: 2

积分
73
金钱
73
注册时间
2016-4-25
在线时间
6 小时
发表于 2016-4-28 11:00:45 | 显示全部楼层
謝謝樓主   解決困擾我多天的問題
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2024-11-23 16:44

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表