当前位置:文档之家› 一种裸奔多任务嵌入式操作系统模型

一种裸奔多任务嵌入式操作系统模型

一种裸奔多任务模型一个网友的总结:stateMachine + timerTick + queue。

在RTOS环境下的多任务模型:任务通常阻塞在一个OS调用上(比如从消息队列取数据)。

外部如果想让该任务运转,就要向消息队列发送消息。

任务收到消息时,根据当前状态,决定如何处理消息。

这就是状态机。

任务将消息队列中的消息处理完毕后,重新进入阻塞状态。

任务在处理中,有时要延时一段时间,然后才继续工作:为了充分使用CPU,可以通过OS调用让其它任务去工作。

OS通常会提供一个taskDelay调用。

当任务调用taskDelay时,即进入阻塞状态,直到超时,才重新进入可工作状态(就绪状态)。

下面说说裸奔环境下的多任务模型:裸奔也可以多任务,但调度是由用户自主控制。

在RTOS环境下,一般提供抢占式调度。

在裸奔时,一般是任务在处理告一段落后,主动结束处理。

RTOS环境下的任务,一般处于一个while(1)循环中。

while(1){从消息队列接收消息。

如果没有,将阻塞。

处理消息。

}裸奔下的任务,一般采用查询方式:{查询是否有待处理的事件。

如果没有,返回。

如果有,根据任务的当前状态,进行处理。

处理完毕后,可能返回,也可能将待处理事件全部处理完毕后再返回。

}裸奔任务其实也处于一个while(1)循环中,只不过这个循环在任务外部。

main(){A_taskInit(); //任务的初始化B_taskInit();...while(1){A_taskProc(); //任务的处理B_taskProc();}}状态机既适用于OS环境,也适用于裸奔环境。

但在裸奔环境下,状态可能被切分得更细。

例如后面讲的如何在裸奔环境实现taskDelay()。

消息队列既适用于OS环境,也适用于裸奔环境。

在OS环境下,消息队列机制由OS提供。

在裸奔环境下,消息队列要自己来实现。

如果对队列的概念不清楚,可参考《数据结构》教材。

这个队列机制,可做成通用模块,在不同的程序中复用。

消息队列用于缓冲事件。

事件不知道什么时候会到来,也不能保证来了就能迅速得到处理。

使用消息队列,可以保证每个事件都被处理到,以及处理顺序。

一般在两种情况下会用到消息队列:存储外部事件:外部事件由中断收集,然后存储到队列。

串口接收程序中的接收循环缓冲区,可理解为消息队列。

任务间通讯:一个任务给其它任务发送消息。

timerTick,就是系统中的时钟基准。

OS中总是有一个这样的基准。

在裸奔时,我们要用一个定时器(或RTC或watchdog)来建立这个时间基准。

一个tick间隔可以设置为10ms(典型RTOS的缺省设置)。

让定时器10ms中断一次,中断发生时给tickNum++。

以前,我在定时器中断中设置1S标志、200ms标志等等。

时间相关的任务根据这些标志判断是否要执行。

近来,一般让任务直接去察看tickNum。

两次相减来判断定时是否到达。

也可以在系统中建立一个通用定时器任务,管理与不同任务相关的多个定时器;在定时到达时,由定时器任务去调用相应的callback。

系统时钟基准是所谓“零耗时裸奔”的基础。

timerTick的分辨率,决定了只适于于较大的时间延时。

在做时序时的小延时,用传统方法好了。

OS中的taskDelay()在裸奔环境下的一种实现:OS环境:void xxxTask(void){while(1){//waitEvent//do step_1taskDelay(TIME_OUT_TICK_NUM);//do step_2}}裸奔环境:void xxxTask(void)static unsigned int taskStat = STA T_GENERAL; //任务状态变量static timer_t startTick;timer_t currTick;if (taskStat == STA T_GENERAL){//check event//if no eventreturn;//do step_1startTick = sysGetTick(); //sysGetTick()就是察看系统时间taskStat = STAT_WAIT;return;}else if (taskStat == STAT_WAIT){currTick = sysGetTick(); //sysGetTick()就是察看系统时间if ((currTick - startTick) >= TIME_OUT_TICK_NUM){//do step_2taskStat = STA T_GENERAL;return;}elsereturn;}}老生常谈---一种裸奔多任务模型ourdev_629752P0O6JH.txt(文件大小:4K)(原文件名:老生常谈---一种裸奔多任务模型.txt)C51多任务编程思想ourdev_629753EW A0LM.pdf(文件大小:143K)(原文件名:C51多任务编程思想.pdf)基于51单片机的C语言多任务操作完美版ourdev_629754PETS4B.rar(文件大小:3K)(原文件名:基于51单片机的C语言多任务操作完美版.rar)Easy51RTOS的原理//Easy51RTOS操作系统头文件#include "os_cfg.h"#include "functns.h" //常用一些功能函数unsigned char TempBuffer[6]; //显示温度字符串unsigned char str2[12]={' ',' ',' ',0,0,0,0,0,0,0xdf,0x43,0}; //任务0:测温度送显void task0(void){temp=ReadTemperature();IntToStr(temp,TempBuffer);str2[3]=TempBuffer[0];str2[4]=TempBuffer[1];str2[5]=TempBuffer[2];str2[6]=TempBuffer[3];str2[7]=TempBuffer[4];str2[8]=TempBuffer[5];GotoXY(0,1);Print(str2);delay_nms(300);}//任务1:键盘扫描,LCD显示void task1(void){if(CHANGE==0) //判断change温度键是否按下{set_temp=key_set(); //设定需要更改的温度值if(set_temp<temp){fengshan(); //设定的温度<实际温度,则打开电机风扇}else if(set_temp>temp){dianlu(); //若大于,则打开电炉(这里用LED模拟一下) }}}//任务2void task2(){}//任务3void task3() {}//任务4void task4(void) {}//任务5void task5(void) {}//任务6void task6() {}//任务7void task7(){}//main主函数void main(void){OS_InitTimer0();EA=1;LCD_Init();LCD_w_data(1,1,Temp_Str);LCD_w_data(2,1,Key_Str);while(1){if (OS_Delay[0]==0){task0();OS_Delay[0]=100;} //温度测量,每秒1次if (OS_Delay[1]==0){task1();OS_Delay[1]=10;} //键盘扫描,键值存储if (OS_Delay[2]==0){task2();OS_Delay[2]=100;} //读出存储的键值,LCD显示 if (OS_Delay[3]==0){task3();OS_Delay[3]=50;}if (OS_Delay[4]==0){task4();OS_Delay[4]=100;}if (OS_Delay[5]==0){task5();OS_Delay[5]=60;}if (OS_Delay[6]==0){task6();OS_Delay[6]=70;}if (OS_Delay[7]==0){task7();OS_Delay[7]=80;}Delay(50);//Taskturn;}}//定时中断服务void OS_Timer0(void) interrupt 1 using 2{uchar i;//CRY_OSC,TIME_PER_SEC在easycfg.h中配置TH0 = 255-CRY_OSC/TIME_PER_SEC/12/256;TL0 = 255-CRY_OSC/TIME_PER_SEC/12%256;//每节拍对任务延时变量减1 ,减至 0 后,任务就绪。

for(i=0;i<MAX_TASK;i++){if(OS_Delay[i]!=0) OS_Delay[i]--;}//Runing(On);}//和传统的前后感觉基本上是一样的…//唯一的优点呢,是感觉OS_Delay[n]数组起到了分配各Easy51RTOS的原理ourdev_629755MEIQGP.txt(文件大小:3K)(原文件名:Easy51RTOS的原理.txt)基于51单片机的C语言多任务操作完美版/*1.本程序不使用任何汇编指令2.由定时器T0产生中断,切换进程3.由于中断或调用子程序,要把PC堆栈,故可以以SP为基址的地方找到PC4.中断或子程序返回,要把SP出栈给PC,故可以操作SP改变程序入口5.本程序经调试运行电路图已上传6.程序编译是会有一个警告提示,为正常现象,因为保存R0-R7时,重新定义地址,出现地址覆盖的警告提示。

7.用户以此模板写程序只需写用户的进程子程序和用户初始化子程序,并把各进程参数放在规定地方,各程序放在规定地方就可以;所有的任务调度已处理好。

*///头文件#include<reg52.h>//#include<absacc.h>//#include<intrins.h>//宏定义#define uchar unsigned char#define uint unsigned int#define TN 65436//进程1,2,3执行时间之比为 T1:T2:T3 (时间单位us)#define TN1 55536 //1个进程循环周期内进程1执行的时间T1us TN1=(65536-T1)#define TN2 55536 //1个进程循环周期内进程2执行的时间T2us TN2=(65536-T1)#define TN3 55536 //1个进程循环周期内进程3执行的时间T3us TN3=(65536-T1)//#define N1 4 // 进程1的延时参数#define N2 4 // 进程2的延时参数#define N3 4 // 进程3的延时参数idata uchar temp[8] _at_ 0x00; //R0--R7uchar tempbf1[8]; //用于保存R0--R7 进程1uchar tempbf2[8]; //用于保存R0--R7 进程2uchar tempbf3[8]; //用于保存R0--R7 进程3//定义全局变量uint address1,address2,address3;uchar test1_1=0,test2_1=0,test3_1=0,PID=1;//各进程的标志位,是否为第一次执行,0第一次,非0非第一次;PID进程号;uint ac1,ac2,ac3; //, PC_Next; 各进程的初始地址寄存器.//test1 的参数由于进程切换时没有保存普通变量,//所以各进程的普通参数需要定义成全局变量.uint m1,i1,j1,k1;uchar table1[4];//在此加入用户进程1参数//test2 的参数int m2,i2,j2,k2;uchar table2[4];//在此加入用户进程2参数//test3 的参数int m3,i3,j3,k3;uchar table3[4];//在此加入用户进程1参数//声明//unsigned int Get_Next_PC(void);//调用子程序,获取PCvoid chushihua(void); //初始化函数void yonghuchushihua(void); //用户初始化函数void test1(void); //进程一void test2(void);void test3(void);//main函数void main(void){// PC_Next=Get_Next_PC();chushihua();ac1=(unsigned int)(test1); //获取进程1的入口地址ac2=(unsigned int)(test2); //获取进程2的入口地址ac3=(unsigned int)(test3); //获取进程3的入口地址yonghuchushihua();TR0=1;while(1);}//初始化时钟void chushihua(void){TMOD=0x01; //EA=1;ET0=1;TH0=TN/256;TL0=TN%256;}//中断处理,进程调度void time0() interrupt 1 using 1{ uchar ib;TR0=0;//进程顺序分配PID++;if(PID==4){PID=1;}//进程调度switch(PID){case 1:{if(test3_1!=0) //第一次否?{ //保存现场,还回地址address3=*((unsigned char *)(SP-4)); //PC的高字节address3 <<= 8;address3+=*((unsigned char *)(SP-5)); //PC的低字节 table3[0]=*((unsigned char *)(SP)); //现场保护table3[1]=*((unsigned char *)(SP-1)); //现场保护table3[2]=*((unsigned char *)(SP-2)); //现场保护table3[3]=*((unsigned char *)(SP-3)); //现场保护for(ib=0;ib<=7;ib++) //保护R0--R7{tempbf3[ib]=temp[ib];}}if(test1_1==0) //第一次执行{ //执行新进程,恢复现场test1_1=1;*((unsigned char *)(SP-4))=ac1>>8; //PC的高字节*((unsigned char *)(SP-5))=ac1 & 0x00ff; //PC的低字节}else //非第一次执行{ //执行新进程,恢复现场*((unsigned char *)(SP-4))=address1>>8; //PC的高字节*((unsigned char *)(SP-5))=address1 & 0x00ff; //PC的低字节*((unsigned char *)(SP))=table1[0]; //现场恢复*((unsigned char *)(SP-1))=table1[1]; //现场恢复*((unsigned char *)(SP-2))=table1[2]; //现场恢复*((unsigned char *)(SP-3))=table1[3]; //现场恢复for(ib=0;ib<=7;ib++) //恢复R0--R7{temp[ib]=tempbf1[ib];}}TH0=TN1/256;TL0=TN1%256;TR0=1;}break;case 2:{if(test1_1!=0) //第一次否?{ //保存现场,还回地址,否address1=*((unsigned char *)(SP-4)); //PC的高字节address1 <<= 8;address1+=*((unsigned char *)(SP-5)); //PC的低字节table1[0]=*((unsigned char *)(SP)); //现场保护table1[1]=*((unsigned char *)(SP-1)); //现场保护table1[2]=*((unsigned char *)(SP-2)); //现场保护table1[3]=*((unsigned char *)(SP-3)); //现场保护for(ib=0;ib<=7;ib++) //保护R0--R7{tempbf1[ib]=temp[ib];}}if(test2_1==0) //第一次{ //执行进程2,恢复现场test2_1=1;*((unsigned char *)(SP-4))=ac2>>8; //PC的高字节*((unsigned char *)(SP-5))=ac2 & 0x00ff; //PC的低字节}else //非第一次{ //执行进程2,恢复现场*((unsigned char *)(SP-4))=address2>>8; //PC的高字节*((unsigned char *)(SP-5))=address2 & 0x00ff; //PC的低字节*((unsigned char *)(SP))=table2[0]; //现场恢复*((unsigned char *)(SP-1))=table2[1]; //现场恢复*((unsigned char *)(SP-2))=table2[2]; //现场恢复*((unsigned char *)(SP-3))=table2[3]; //现场恢复for(ib=0;ib<=7;ib++) //恢复R0--R7{temp[ib]=tempbf2[ib];}}TH0=TN2/256;TL0=TN2%256;TR0=1;}break;case 3:{if(test2_1!=0){ //保存现场,还回地址address2=*((unsigned char *)(SP-4)); //PC的高字节address2 <<= 8;address2+=*((unsigned char *)(SP-5)); //PC的低字节table2[0]=*((unsigned char *)(SP)); //现场保护table2[1]=*((unsigned char *)(SP-1)); //现场保护table2[2]=*((unsigned char *)(SP-2)); //现场保护table2[3]=*((unsigned char *)(SP-3)); //现场保护for(ib=0;ib<=7;ib++) //保护R0--R7{tempbf2[ib]=temp[ib];}}if(test3_1==0){ //执行进程3test3_1=1;*((unsigned char *)(SP-4))=ac3>>8; //PC的高字节*((unsigned char *)(SP-5))=ac3 & 0x00ff; //PC的低字节}else{ //执行进程3,恢复现场*((unsigned char *)(SP-4))=address3>>8; //PC的高字节*((unsigned char *)(SP-5))=address3 & 0x00ff; //PC的低字节*((unsigned char *)(SP))=table3[0]; //现场恢复*((unsigned char *)(SP-1))=table3[1]; //现场恢复*((unsigned char *)(SP-2))=table3[2]; //现场恢复*((unsigned char *)(SP-3))=table3[3]; //现场恢复for(ib=0;ib<=7;ib++) //恢复R0--R7{temp[ib]=tempbf3[ib];}}TH0=TN3/256;TL0=TN3%256;TR0=1;}break;default:TH0=TN/256;TL0=TN%256;TR0=1;break;}}//以下部分是需要按用户要求添加的部分void yonghuchushihua(void) //用户初始化函数{//加入用户初始化函数}//进程一 P1演示二进制加法死循环void test1(void){while(1) //用户可以删除while(1)循环中的全部内容(演示用)安排用户进程 {for(i1=0;i1<256;i1++){for(k1=0;k1<=N1;k1++){for(j1=0;j1<=20;j1++)for(m1=0;m1<=113;m1++);} // 约1ms*T1/T1+T2+T3P1=i1;//P2=0x0;}}}//进程二P2演示二进制加法死循环void test2(void){while(1) //用户可以删除while(1)循环中的全部内容(演示用)安排用户进程 {for(i2=0;i2<256;i2++){for(k2=0;k2<=N2;k2++){for(j2=0;j2<=20;j2++)for(m2=0;m2<=113;m2++);}P2=i2;}}}//进程三 P0口演示二进制加法死循环进程3的延时参数void test3(void){while(1) //用户可以删除while(1)循环中的全部内容(演示用)安排用户进程 {for(i3=0;i3<256;i3++){for(k3=0;k3<=N3;k3++){for(j3=0;j3<=20;j3++)for(m3=0;m3<=113;m3++);}P0=i3;}}}/* //获取下一条语句地址:PCunsigned int Get_Next_PC(void){unsigned int address;address=*((unsigned char *)SP);address <<= 8;address+=*((unsigned char *)(SP-1));return address+4;}*/。

相关主题