四极管:只有延时服务的协作式内核

四极管记得当时是参考了某位大侠的文章,好像是在阿莫哪里看见的 记不得出处了,

若原作者有意见,四极管可以删除


/* 
   第四篇:只有延时服务的协作式的内核   

                    Cooperative Multitasking  
   前后台系统,协作式内核系统,与占先式内核系统,有什么不同呢?  
   记得在***上看过这样的比喻,“(小工)在用厕所,经理在外面排第一, 
   老板在外面排第二。如果是前后台,不管是谁,都必须按排队的次序使用 
   厕所;如果是协作式,那么可以等你用完厕所,老板就要比经理先进入; 
   如果是占先式,只要有更高级的人在外面等,那么厕所里无论是谁,都要 
   第一时间让出来,让最高级别的人先用。”  
*/ 

#include <avr/io.h>  
#include <avr/Interrupt.h>  
#include <avr/signal.h>  
unsigned char Stack[200];    //人工堆栈 

register unsigned char OSRdyTbl           asm("r2");   //任务运行就绪表  
register unsigned char OSTaskRunningPrio  asm("r3");   //正在运行的任务  

#define OS_TASKS 3 //设定运行任务的数量  
struct TaskCtrBlock //任务控制块  
{  
  unsigned int OSTaskStackTop;  //保存任务的堆栈顶  
  unsigned int OSWaitTick; //任务延时时钟  
} TCB[OS_TASKS+1];  


//防止被编译器占用  
register unsigned char tempR4  asm("r4");  
register unsigned char tempR5  asm("r5");  
register unsigned char tempR6  asm("r6");  
register unsigned char tempR7  asm("r7");  
register unsigned char tempR8  asm("r8");  
register unsigned char tempR9  asm("r9");  
register unsigned char tempR10 asm("r10");  
register unsigned char tempR11 asm("r11");  
register unsigned char tempR12 asm("r12");  
register unsigned char tempR13 asm("r13");  
register unsigned char tempR14 asm("r14");  
register unsigned char tempR15 asm("r15");  
register unsigned char tempR16 asm("r16");  
register unsigned char tempR17 asm("r17");  



//建立任务(每个任务在其本身的人工堆栈里压了19个字节
void OSTaskCreate(void (*Task)(void),unsigned char *Stack,unsigned char TaskID)  
{  
  unsigned char i;   
  *Stack--=(unsigned int)Task; //将任务的地址低位压入堆栈 
  *Stack--=(unsigned int)Task>>8; //将任务的地址高位压入堆栈 
      
  *Stack--=0x00; //R1 __zero_reg__              
  *Stack--=0x00; //R0 __tmp_reg__  
  *Stack--=0x80; //SREG 在任务中,开启全局中断          
  for(i=0;i<14;i++) // avr-libc 中的 FAQ中的 What registers are used by the C compiler?  
    *Stack--=i; //描述了寄存器的作用      
  TCB[TaskID].OSTaskStackTop=(unsigned int)Stack;    
  //将人工堆栈的栈顶,保存到TCB结构体数组中  
  OSRdyTbl|=0x01<<TaskID;           //任务就绪表相应位置1,表明处于就绪态 
}  

//开始任务调度,从最低优先级的任务TaskScheduler开始  
void OSStartTask()          
{  
  OSTaskRunningPrio=OS_TASKS; //首先运行TaskScheduler任务 
  SP=TCB[OS_TASKS].OSTaskStackTop+17; //等号右边得到任务TaskScheduler的入口地址 
  __asm__ __volatile__("reti" "\t");       //retreti,它们都可以将堆栈栈顶SP的两个字节弹出来送入程序计数器PC中 
}  

//进行任务调度  
void OSSched(void)  
{   
   //  根据中断时保存寄存器的次序入栈(压栈不是在人工堆栈里面,而是在avr芯片本来分配的堆栈里),模拟一次中断后,入栈的情况    
  __asm__ __volatile__("PUSH __zero_reg__\t");       //R1  
  __asm__ __volatile__("PUSH __tmp_reg__\t"); //R0   
  __asm__ __volatile__("IN   __tmp_reg__,__SREG__ \t");  //保存状态寄存器SREG  
  __asm__ __volatile__("PUSH __tmp_reg__\t");  
  __asm__ __volatile__("CLR  __zero_reg__\t");       //R0重新清零  
  __asm__ __volatile__("PUSH R18\t");  
  __asm__ __volatile__("PUSH R19\t");  
  __asm__ __volatile__("PUSH R20\t");  
  __asm__ __volatile__("PUSH R21\t");  
  __asm__ __volatile__("PUSH R22\t");  
  __asm__ __volatile__("PUSH R23\t");  
  __asm__ __volatile__("PUSH R24\t");  
  __asm__ __volatile__("PUSH R25\t");  
  __asm__ __volatile__("PUSH R26\t");  
  __asm__ __volatile__("PUSH R27\t");  
  __asm__ __volatile__("PUSH R30\t");      
  __asm__ __volatile__("PUSH R31\t");  
  __asm__ __volatile__("PUSH R28\t");  //R28R29用于建立在堆栈上的指针  
  __asm__ __volatile__("PUSH R29\t");  //入栈完成  
      
  TCB[OSTaskRunningPrio].OSTaskStackTop=SP;     //将正在运行的任务的堆栈顶保存  

  unsigned char OSNextTaskID;     //在现有堆栈上开设新的空间   
  for (OSNextTaskID = 0;     //进行任务调度  
    OSNextTaskID < OS_TASKS && !(OSRdyTbl & (0x01<<OSNextTaskID));  //有已就绪任务就退出循环 
    OSNextTaskID++);     //注意分号位置,这里循环体只是那个For语句 
    OSTaskRunningPrio = OSNextTaskID ;     //得到一个新任务,调度到就执行态 

  cli();  //关中断,保护堆栈转换 
  SP=TCB[OSTaskRunningPrio].OSTaskStackTop;     //OSSched函数一执行完,就可以运行这个新任务 
  sei();  
      
    //根据中断时的出栈次序      
  __asm__ __volatile__("POP  R29\t");      
  __asm__ __volatile__("POP  R28\t");          
  __asm__ __volatile__("POP  R31\t");      
  __asm__ __volatile__("POP  R30\t");      
  __asm__ __volatile__("POP  R27\t");      
  __asm__ __volatile__("POP  R26\t");      
  __asm__ __volatile__("POP  R25\t");      
  __asm__ __volatile__("POP  R24\t");      
  __asm__ __volatile__("POP  R23\t");      
  __asm__ __volatile__("POP  R22 \t");      
  __asm__ __volatile__("POP  R21\t");      
  __asm__ __volatile__("POP  R20\t");      
  __asm__ __volatile__("POP  R19\t");  
  __asm__ __volatile__("POP  R18\t");  
  __asm__ __volatile__("POP  __tmp_reg__\t");    //SERG 出栈并恢复  
  __asm__ __volatile__("OUT  __SREG__,__tmp_reg__\t"); 
  __asm__ __volatile__("POP  __tmp_reg__\t");    //R0 出栈  
  __asm__ __volatile__("POP  __zero_reg__\t");           //R1 出栈  
  //中断时出栈完成  
}  

//使正在运行的任务自动放弃CPU,但不是进入就绪态 
void OSTimeDly(unsigned int ticks)  
{  
  if(ticks) //当延时有效 
  {  
    OSRdyTbl &= ~(0x01<<OSTaskRunningPrio); //任务运行就绪表相应位清0        
    TCB[OSTaskRunningPrio].OSWaitTick=ticks;//设置任务延时时钟 
    OSSched(); //从新调度  
  }  
}  


void TCN0Init(void)    // 计时器0  
{  
  TCCR0 = 0;  
  TCCR0 |= (1<<CS02);  // 256预分频  
  TIMSK |= (1<<TOIE0); // T0溢出中断允许                    
  TCNT0 = 100;         // 置计数起始值 (递增,往255计数)    
}  


SIGNAL(SIG_OVERFLOW0)  
{  
  unsigned char i;  
  for(i=0;i<OS_TASKS;i++) //任务时钟  
  {  
    if(TCB[i].OSWaitTick)   
    {  
      TCB[i].OSWaitTick--;  
      if(TCB[i].OSWaitTick==0) //当任务时钟到时,必须是由定时器减时的才行  
      {    
        OSRdyTbl |= (0x01<<i); //使任务在就绪表中置位     
      }  
    }  
  }  
  TCNT0=100;  
}  

void Task0()  
{  
  unsigned int j=0;  
  while(1)  
  {              
    PORTB=j++;  
    OSTimeDly(2);  
  }  
}  



void Task1()  
{  
  unsigned int j=0;  
  while(1)  
  {  
    PORTC=j++;  
    OSTimeDly(4);  
  }  
}  

void Task2()  
{  
  unsigned int j=0;  
  while(1)  
  {  
    PORTD=j++;     
    OSTimeDly(8);  
  }  
}  

// 任务调度任务(相当于Task3),一般优先级最低 
void TaskScheduler()  
{   
  while(1)  
  {          
     OSSched();      //反复进行调度  
  }  
}  


int main(void)  
{      
  TCN0Init();  
  OSRdyTbl=0;  
  OSTaskRunningPrio=0;  
  OSTaskCreate(Task0,&Stack[49],0);  
  OSTaskCreate(Task1,&Stack[99],1);  
  OSTaskCreate(Task2,&Stack[149],2);  
  OSTaskCreate(TaskScheduler,&Stack[199],OS_TASKS);  
  //经过上面四个语句后,建立了四个任务,都处于就绪态 
  OSStartTask();  
}  
/* 
   在上面的例子中,一切变得很简单,三个正在运行的主任务,都通过延时服务,主动放弃对CPU的控制权。  
   在时间中断中,对各个任务的的延时进行计时,如果某个任务的延时结束,将任务重新在就绪表中置位。  
   最低级的系统任务TaskScheduler(),在三个主任务在放弃对CPU的控制权后开始不断地进行调度。如果 
   某个任务在就绪表中置位,通过调度,进入最高级别的任务中继续运行。 
*/ 

下面就我自己的总结,花了很大力气啊,写点以后希望对一些新手有用,声明,本人也是新手,刚学AVR,想做点小项目

总结如下

按函数执行顺序的详细流程文字说明: 
主函数main() 
OSTaskCreate() 
函数建立了4个任务,分别是task0,task1,task2TaskScheduler,在这个函数中,我们做了三件主要的事情 
1、我们先把每个任务的函数地址分别压到它相对应的人工堆栈里面 
2、压完了任务函数地址,我们就把人工堆栈的栈顶指针赋给TCB结构体数组里面的OSTaskStackTop中, 
   保存住(其实就是为了后面的寻找相应任务的函数地址
3、把每个任务都在任务就绪表的相应位置1,表示任务可以被调用得到CPU的能力之一。但其实还要靠任务的优先级,有 
   点需要注意的是:任务就绪表中相应位越低,优先级越高,比如,bit0就比bit1低,bit0的那个任 
   务优先级就高。优先级体现在任务调度函数OSSched()里面的任务调度for循环语句中。应该仔细体会。 
OSStartTask() 
准备开始执行任务,因为函数里面“OSTaskRunningPrio=OS_TASKS;”这里(只是这里)根本就没需要考虑优先级的情况, 
所以第一个准备执行(还没开始)的任务是TaskScheduler。 
第二句SP=TCB[OS_TASKS].OSTaskStackTop+17;等号右边得到任务TaskScheduler的入口地址,接着赋给SP。 
给它干吗,当然是有用了,你还记得“2人工堆栈里面有一句话“ 对于retreti,它们都可以将堆栈栈顶的 
两个字节弹出来送入程序计数器PC中,一般用来从子程序或中断中退出。,显然,那就是等接着的第三句去完 
成他的使命了。呵呵,第三句"reti"一完事,你的avrCPU就可以去程序计数器PC里面取地址,而这个地址呢, 
就正是任务TaskScheduler的函数地址,哈哈,这才是正式开始执行第一个任务TaskScheduler。 

现在,你看,我们已经不用再看主函数里面的函数了,因为以后也不会涉及到。以后的任务就是靠任务调度了。我们接着来看吧 

刚才不是任务TaskScheduler是执行态嘛,这个任务又去反复不断调用OSSched函数,OSSched是个关键哦,这个函数在 
这个“4只有延时服务的协作式的内核.”里面应该是最主要的,操作系统要靠调度,调度就是你妈,吃饭的时候,给 
你吃饭你就可以吃,不给你吃你就那里先撑着,还给"TMD"按孩子从大到小(优先级)来分,不过不会相信你妈有那么偏心。 
OK,不说废话,我们接着讲。 

调度任务里面的调度函数OSSched()  
OSSched() 
一大堆PUSH指令 
这里的一大堆PUSH汇编指令是为了保存一些通用积存器寄存器,也就是保护现场,不懂的,先得看看操作系统里面的书。 
中断就是这样处理的,和这个差不多。刚才我们不是运行了第一个任务TaskScheduler吗,这里保存就是任务TaskScheduler 
的现场,保存了大堆的寄存器。需要注意的是,这里PUSH的压可不是象建立任务OSTaskCreate()里面的把寄存器压到 
每人任务相应的人工堆栈里面,而是压到avr芯片里面专门定义的一片堆栈里面。 
接着TCB[OSTaskRunningPrio].OSTaskStackTop=SP;呵呵,你看了这里的SP是区别于人工堆栈里面的栈顶指针,一旦 
这里的SP赋给了TCB[OSTaskRunningPrio].OSTaskStackTop,那么相应的,此任务的OSTaskStackTop原本指向人工堆 
栈的栈顶指针就再也找不到,看OSTaskCreate()仔细对照一下吧。现在呢,我们OSTaskStackTop保存是正在运行的任务 
的栈顶指针。其实保存SP也应该算是保存现场的一部分工作。 
         
接着 
unsigned char OSNextTaskID; //在现有堆栈上开设新的空间   
for (OSNextTaskID = 0; //进行任务调度  
 OSNextTaskID < OS_TASKS && !(OSRdyTbl & (0x01<<OSNextTaskID));  //有已就绪任务就退出循环 
 OSNextTaskID++); //注意分号位置,这里循环体只是那个For语句 
OSTaskRunningPrio = OSNextTaskID ; //得到一个新任务(有可能是原来的任务),调度到就执行态 
注释也都很清楚了,关键有一点就是得到一个新任务(有可能是原来的任务)”这句。这个要等我后面就会提到了,这 
里暂时不会。 
for里面的循环查找任务就绪表里面优先级最高的任务,都是从bit0向高位开始查找的。很显然,在这之前,我们从来 
没有运行过task0,task1,task2,况且,他们在建立任务(OSTaskCreate())的时候的就绪位都还是置1,具体 
OSRdyTbl = 0000_1111。在检查了bit0位后就跳出了循环,所以,我们得到了将可执行的任务task0。相应的, 
我们把此时的TCB[O].OSTaskStackTop赋给SPOSTaskStackTop是等于指向人工堆栈里面的那个栈顶指针。哈哈,你 
有点明白了吗,我们为什么在OSTaskCreate里面压那么多次的栈,其实有有用的,只可惜,它就只用一次,一夜情,用 
完就走,哈哈。SP=TCB[OSTaskRunningPrio].OSTaskStackTop后接下来我们就开始大规模的弹栈了,这次(也只有这次, 
好好再想想,再想想),我们弹出的是人工堆栈里面的那些东西。哦,还是弹17次哦,呵呵,看看OSTaskCreate(),是 
不是也是压17次,想明白了吧。你看,弹了栈后,OSSched函数准备退出,那肯定也执行了"ret",呵呵,这不,SP不是 
给了程序计数器PC,所以,我们就可以从TaskScheduler跳到task0了 
提醒一下,弹栈的时候,我们只会弹一次人工堆栈里面的东西,也只有当这个任务(这个任务可能是task0,task1,task2, 
之一,但不会是TaskScheduler,看for循环里OSNextTaskID < OS_TASKS=3)第一次准备被执行的时候(搞清楚点吧,多看下
也就都弹出去了,以后也不会有了,所以,task0,task1,task2的人工堆栈最后只剩下任务入口地址在那里,它没有被弹出, 
但其实它也没什么用了,一旦运行过它本身的任务后,任务函数入口地址就无意义了(为什么无意义,注意一下任务函数 
里面的那个while(1)无限循环)。用一次后就无意义,不是个套吗,一次性,哈哈。 

说到这里,感觉写得真多,不知道能不能让你明白。呵呵,接着 

我们因为上面的调度,task0就开始执行了,我们看 
void Task0()  
{  
unsigned int j=0;  
while(1)  
{              
PORTB=j++;  
OSTimeDly(2);  
}  
}  
如果我们把OSTimeDly(2);注释掉,那这个任务就永远在那里运行了,也不会被调度,因为OSTimeDly()函数里面有个OSSched()。 
没有它,就不会让其他任务得到运行的机会。 
OSTimeDly() 
OSTimeDly()函数会让任务自动放弃CPU,并且任务就绪表OSRdyTbl相应为清0,任务就暂时没有执行权利了.OSRdyTbl = 00001110 
哦,只是暂时哦,什么时候有呢?那要看定时器/计数器的工作,也就是看定时器的那个中断服务程序。 
SIGNAL(SIG_OVERFLOW0) 
当每个任务时钟到时,使各自任务在就绪表中置位,这样任务就重新得到了运行的机会条件(还需要考虑优先级)之一了。 

刚才不是说到任务Task0通过OSTimeDly()-->OSSched()有进行了一次调度。 
这次调度有点象Task0任务被调度到一样。有一点不同的是,OSRdyTbl = 00001110,根据优先级,知道任务Task1会被调度到, 
调度的过程,比如说压栈,弹栈什么的同第一次调用Task0一样。 
依次类推,会接着执行到Task2,直到执行完........ 

现在,你仔细想过没有,上面的过程 

TaskScheduler-->OSSched()-->Task0--->OSSched()-->Task1--->OSSched()-->Task2 
有可能不是这样吗? 
答案是有可能,不过是有可能,编写这个操作系统的人肯定考虑到这种状况了,所以没出现那个问题。 
问题出在哪?就出现在OSTimeDly(unsigned int ticks)函数的那个任务时钟ticks的设定上,期间,可能会出现很多情况。 
就用我们这个操作系统的的例子说一下吧 
有三个任务(不包括调度任务)执行,任务就绪表OSRdyTbl = 00001111 
Task0任务比较大,4.5 才能执行完这个任务,void Task0{ ...OSTimeDly(2);} 
Task1任务比较大,5.0 才能执行完这个任务,void Task1{ ...OSTimeDly(4);} 
Task2任务比较大,0.5 就能执行完这个任务,void Task2{ ...OSTimeDly(8);} 
现在,我执行了Task0,花掉了4.5秒,期间,我也让它OSTimeDly(2)OSRdyTbl = 00001110。当然,调用这个函数肯定不能是 
这样的,这里只是打个比方。就让它延时两秒。Task0执行完了,根据优先级,Task1将会被调用,但执行了Task1,要执行5.0秒, 
在执行Task15秒期间,Task0延时的2秒早就完了,它会让任务就绪表中Task0的相应位置1,重新得到执行可能。也正是它,
Task1执行完后,又去搜索任务就绪表,而此刻OSRdyTbl = 00001101,我们就又能执行Task0了,任务Task2没有执行的份!! 
接着,执行Task0需要4.5秒,但,Task1延时的那个4秒早就过了,它在任务Task0没执行完就已经得到就绪机会了....周而复始。 
任务Task2不管大小,永远也轮不到它去执行,死在那里看人家吃饭了。 
所以,这个ticks的设置在这个"只有延时服务的协作式的内核"是有点讲究的。 

调度任务里面的调度函数OSSched() 我曾经说过: 
OSTaskRunningPrio = OSNextTaskID ; //得到一个新任务(有可能是原来的任务),调度到就执行态 
注释也都很清楚了,关键有一点就是得到一个新任务(有可能是原来的任务)”这句。这个要等我后面就会提到了,这 
里暂时不会。 
你想到了答案没有? 
比如说 
Task0任务比较大,0.5 才能执行完这个任务,void Task0{ ...OSTimeDly(2);} 
Task1任务比较大,0.5 才能执行完这个任务,void Task1{ ...OSTimeDly(10);} 
Task2任务比较大,0.5 就能执行完这个任务,void Task2{ ...OSTimeDly(20);} 
Task0有一段是将在那里一直执行几次,当然,期间有有点延时的。 

其实,还有其他的问题,大家按这种思路,也不会很难,就可能在有一点上不开窍而已,多看几次,如果你会用仿真,那就更好 
了,知道具体程序怎么走。我没用过,只是凭思路的。希望对大家有点用处

通常大家都会认为,在任务调度开始时,当然要将所有的通用寄存器都保存,并且还应该保存程序状态寄存器SREG。然后再根据相反的次序,将新任务的寄存器的内容恢复。 
     
    但是,事实真的是这样吗?如果大家看过陈明计先生写的small rots51,就会发现,它所保存的通用寄存器不过是4组通用寄存器中的1组。 
     
    Win AVR中的帮助文件 avr-libc Manual中的Related Pages中的Frequently Asked Questions,其实有一个问题是"What registers are used by the C compiler?"  回答了编译器所需要占用的寄存器。一般情况下,编译器会先用到以下寄存器 
    1 Call-used registers (r18-r27, r30-r31): 调用函数时作为参数传递,也就是用得最多的寄存器。 

    2 Call-saved registers (r2-r17, r28-r29): 调用函数时作为结果传递,当中的r28r29可能会被作为指向堆栈上的变量的指针。 
     
    3 Fixed registers (r0, r1): 固定作用。r0用于存放临时数据,r1用于存放0。 
     
     
    还有另一个问题是"How to permanently bind a variable to a register?",是将变量绑定到通用寄存器的方法。而且我发现,如果将某个寄存器定义为变量,编译器就会不将该寄存器分配作其它用途。这对RTOS是很重要的。 

    "Inline Asm"中的"C Names Used in Assembler Code"明确表示,如果将太多的通用寄存器定义为变量,刚在编译的过程中,被定义的变量依然可能被编译器占用。 

    大家可以比较以下两个例子,看看编译器产生的代码:(*.lst文件中

第一个例子:没有定义通用寄存器为变量 

#include <avr/io.h> 

unsigned char add(unsigned char b,unsigned char c,unsigned char d) 

   return b+c*d; 


int main(void) 

  unsigned char a=0; 
  while(1) 
  { 
    a++; 
    PORTB=add(a,a,a); 
  } 


   在本例中,"add(a,a,a);"被编译如下
   mov r20,r28 
   mov r22,r28 
   mov r24,r28 
   rcall add 

第二个例子:定义通用寄存器为变量 

#include <avr/io.h> 

unsigned char add(unsigned char b,unsigned char c,unsigned char d) 

   return b+c*d; 



register unsigned char a asm("r20");  //r20定义为 变量a  

int main(void) 


    while(1) 
    { 
      a++; 
        PORTB=add(a,a,a); 
    } 


    在本例中,"add(a,a,a);"被编译如下:   
    mov r22,r20 
    mov r24,r20 
    rcall add 

    当然,在上面两个例子中,有部份代码被编译器优化了。  
     
    通过反复测试,发现编译器一般使用如下寄存器
    1类寄存器,第2类寄存器的r28,r29,3类寄存器 

    如在中断函数中有调用基它函数,刚会在进入中断后,固定地将第1类寄存器和第3类寄存器入栈,在退出中断又将它们出栈。

*******************************************************************************************************************************

自己的实现文件内容如下,

#include <avr/io.h>  
#include <avr/Interrupt.h>
#include  <util/delay.h>  
#include <friend.h>  

unsigned char Stack[200];  

register unsigned char OSRdyTbl          asm("r2");    //任务运行就绪表  
register unsigned char OSTaskRunningPrio asm("r3");    //正在运行的任务  

#define OS_TASKS 3      //设定运行任务的数  
struct TaskCtrBlock     //任务控制块  
{  
  unsigned int OSTaskStackTop;  //保存任务的堆栈顶  
  unsigned int OSWaitTick;      //任务延时时钟  
} TCB[OS_TASKS+1];  
//防止被编译器占用  
register unsigned char tempR4  asm("r4");  
register unsigned char tempR5  asm("r5");  
register unsigned char tempR6  asm("r6");  
register unsigned char tempR7  asm("r7");  
register unsigned char tempR8  asm("r8");  
register unsigned char tempR9  asm("r9");  
register unsigned char tempR10 asm("r10");  
register unsigned char tempR11 asm("r11");  
register unsigned char tempR12 asm("r12");  
register unsigned char tempR13 asm("r13");  
register unsigned char tempR14 asm("r14");  
register unsigned char tempR15 asm("r15");  
register unsigned char tempR16 asm("r16");  
register unsigned char tempR17 asm("r17");  
//建立任务 (每个任务在其本身的人工堆栈里压了19个字节) 
void OSTaskCreate(void (*Task)(void),unsigned char *Stack,unsigned char TaskID)  
{  
  unsigned char i;  
  *Stack--=(unsigned int)Task;         //将任务的地址低位压入堆栈 
  *Stack--=(unsigned int)Task>>8;    //将任务的地址高位压入堆栈  
   
  *Stack--=0x00;                     //R1 __zero_reg__              
  *Stack--=0x00;                     //R0 __tmp_reg__  
  *Stack--=0x80;                      //SREG 在任务中,开启全局中断          
  for(i=0;i<14;i++)  //在 avr-libc 中的 FAQ中的 What registers are used by the C compiler?  
    *Stack--=i;                    //描述了寄存器的作用      
    TCB[TaskID].OSTaskStackTop=(unsigned int)Stack;  //将人工堆栈的栈顶,保存到堆栈的数组中  
    OSRdyTbl|=0x01<<TaskID;      //任务就绪表已经准备好  
}  
//开始任务调度,从最低优先级的任务的开始  
void OSStartTask(void)          
{  
  OSTaskRunningPrio=OS_TASKS;  //首先运行TaskScheduler任务
  SP=TCB[OS_TASKS].OSTaskStackTop+17;  //等号右边得到任务TaskScheduler的入口
  __asm__ __volatile__(    "reti"       "\n\t"  );  //ret和reti,它们都可以将堆栈栈顶SP的两个字节弹出来送入程序计数器PC中
  
}  
//进行任务调度  
void OSSched(void)  
{ //  根据中断时保存寄存器的次序入栈,模拟一次中断后,入栈的情况    
  __asm__ __volatile__("PUSH __zero_reg__         \n\t");  //R1  
  __asm__ __volatile__("PUSH __tmp_reg__          \n\t");  //R0  
  __asm__ __volatile__("IN   __tmp_reg__,__SREG__ \n\t");  //保存状态寄存器SREG  
  __asm__ __volatile__("PUSH __tmp_reg__          \n\t");  
  __asm__ __volatile__("CLR  __zero_reg__         \n\t");  //R0重新清零  
  __asm__ __volatile__("PUSH R18                  \n\t");  
  __asm__ __volatile__("PUSH R19                  \n\t");  
  __asm__ __volatile__("PUSH R20                  \n\t");  
  __asm__ __volatile__("PUSH R21                  \n\t");  
  __asm__ __volatile__("PUSH R22                  \n\t");  
  __asm__ __volatile__("PUSH R23                  \n\t");  
  __asm__ __volatile__("PUSH R24                  \n\t");  
  __asm__ __volatile__("PUSH R25                  \n\t");  
  __asm__ __volatile__("PUSH R26                  \n\t");  
  __asm__ __volatile__("PUSH R27                  \n\t");  
  __asm__ __volatile__("PUSH R30                  \n\t");      
  __asm__ __volatile__("PUSH R31                  \n\t");  
  __asm__ __volatile__("PUSH R28                  \n\t");  //R28与R29用于建立在堆栈上的指针  
  __asm__ __volatile__("PUSH R29                  \n\t");  //入栈完成  
  TCB[OSTaskRunningPrio].OSTaskStackTop=SP;           //将正在运行的任务的堆栈底保存  
  unsigned char OSNextTaskID;                             //在现有堆栈上开设新的空间    
  for (OSNextTaskID = 0;                                  //进行任务调度  
    OSNextTaskID < OS_TASKS && !(OSRdyTbl & (0x01<<OSNextTaskID));   //如果有了已经就绪的任务就退出循环 
    OSNextTaskID++);  //注意分号位置,这里循环体只是那个For语句
    OSTaskRunningPrio = OSNextTaskID ;    //得到一个新任务,调度到就执行态

  cli();  //保护堆栈转换  
  SP=TCB[OSTaskRunningPrio].OSTaskStackTop;  //OSSched函数一执行完,就可以运行这个新任务

  sei();  
//根据中断时的出栈次序  
  __asm__ __volatile__("POP  R29                  \n\t");  
  __asm__ __volatile__("POP  R28                  \n\t");  
  __asm__ __volatile__("POP  R31                  \n\t");  
  __asm__ __volatile__("POP  R30                  \n\t");  
  __asm__ __volatile__("POP  R27                  \n\t");  
  __asm__ __volatile__("POP  R26                  \n\t");  
  __asm__ __volatile__("POP  R25                  \n\t");      
  __asm__ __volatile__("POP  R24                  \n\t");  
  __asm__ __volatile__("POP  R23                  \n\t");  
  __asm__ __volatile__("POP  R22                  \n\t");  
  __asm__ __volatile__("POP  R21                  \n\t");  
  __asm__ __volatile__("POP  R20                  \n\t");  
  __asm__ __volatile__("POP  R19                  \n\t");  
  __asm__ __volatile__("POP  R18                  \n\t");  
  __asm__ __volatile__("POP  __tmp_reg__          \n\t"); //SERG 出栈并恢复  
  __asm__ __volatile__("OUT  __SREG__,__tmp_reg__ \n\t");  
  __asm__ __volatile__("POP  __tmp_reg__          \n\t"); //R0 出栈  
  __asm__ __volatile__("POP  __zero_reg__         \n\t"); //R1 出栈  
  //中断时出栈完成  
}  


//使正在运行的任务自动放弃CPU,但不是进入就绪态

void OSTimeDly(unsigned int ticks)  
{  
  if(ticks)                             //当延时有效  
  {  
    OSRdyTbl &= ~(0x01<<OSTaskRunningPrio);    //任务运行就绪表相应位清0           
    TCB[OSTaskRunningPrio].OSWaitTick=ticks;  //设置任务延时时钟

    OSSched();                          //从新调度  
  }  
  
  
}  


void TCN0Init(void)    // 计时器0  
{  
  TCCR0 = 0;  
  TCCR0 |= (1<<CS02);  // 256预分频  
  TIMSK |= (1<<TOIE0); // T0溢出中断允许                    
  TCNT0 = 100;         // 置计数起始值  (递增,往255计数)  
      
}  




SIGNAL(SIG_OVERFLOW0)  
{  
  unsigned char i;  
  for(i=0;i<OS_TASKS;i++)       //任务时钟  
  {  
    if(TCB[i].OSWaitTick)    
    {  
      TCB[i].OSWaitTick--;  
      if(TCB[i].OSWaitTick==0)     //当任务时钟到时,必须是由定时器减时的才行  
      {    
        OSRdyTbl |= (0x01<<i);     //使任务在就绪表中置位      
      }  
    }  
  }  
  TCNT0=100;  
}  



void delay_s(uchar time)
{
    uchar i;
	for (i=0;i<time;i++)
	{
	    _delay_ms(200);  
		_delay_ms(200); 
		_delay_ms(200); 
		_delay_ms(200); 
		_delay_ms(200); 
	}
}

	
void Task0(void)  
{  

  while(1)  
  {              
    Pa0_1;
    delay_s(2); 
	Pa0_0;
    delay_s(2);
    Pa0_1;
    delay_s(2);   
    OSTimeDly(200);  
  }  
}  



void Task1(void)  
{  
  
  while(1)  
  {  
    Pa1_1;
    delay_s(2);
	Pa1_0;
    delay_s(2);
    Pa1_1;
    delay_s(2);
    OSTimeDly(1);  
  }  
}  



void Task2(void)  
{  

  while(1)  
  {  
    Pa2_1;
    delay_s(2);
	Pa2_0;
    delay_s(2);
	Pa2_1;
    delay_s(2);
   
    OSTimeDly(1);  
  }  
}  



void TaskScheduler(void)  
{    
  while(1)  
  {          
     OSSched();      //反复进行调度  
  }  
}  




int main(void)  
{  
  DDRA=0xff;
  PORTA=0X00;
  TCN0Init();  
  OSRdyTbl=0;  
  OSTaskRunningPrio=0;  
  OSTaskCreate(Task2,&Stack[159],2);  
  OSTaskCreate(TaskScheduler,&Stack[209],OS_TASKS);
  OSTaskCreate(Task0,&Stack[59],0);  
  OSTaskCreate(Task1,&Stack[109],1);  
  
   //经过上面四个语句后,建立了四个任务,都处于就绪态
  OSStartTask();  
}  




 

 

转载请注明出处。作者:四极管。广西师范大学 电子工程学院大学生科技创新基地 邮箱: [email protected]

 

你可能感兴趣的:(OS,任务调度,任务,编译器,Signal,delay)