四极管记得当时是参考了某位大侠的文章,好像是在阿莫哪里看见的 记不得出处了,
若原作者有意见,四极管可以删除
/*
第四篇:只有延时服务的协作式的内核
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"); //ret和reti,它们都可以将堆栈栈顶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"); //R28与R29用于建立在堆栈上的指针
__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,task2和TaskScheduler,在这个函数中,我们做了三件主要的事情
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人工堆栈”里面有一句话“ 对于ret和reti,它们都可以将堆栈栈顶的
两个字节弹出来送入程序计数器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赋给SP,OSTaskStackTop是等于指向人工堆栈里面的那个栈顶指针。哈哈,你
有点明白了吗,我们为什么在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秒,
在执行Task1的5秒期间,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): 调用函数时作为结果传递,当中的r28和r29可能会被作为指向堆栈上的变量的指针。
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]。