只有延时服务的协作式的内核(DIY实时操作系统中)总结
原帖子是在21IC.COM,舍友后来传我看"DIY实时操作系统中",觉得很好理解,循序渐进.昨天刚认真开始看的,花了一天半的时间看得感觉还可以,不过只是看了只有延时服务的协作式的内核那部分,后面的暂时没看.后来自己就写了个总结,花了我三个钟头,太崩溃了,希望大家有空帮我指正指正.
一般我都会写一些东西,放在我的blog上:http://blog.csdn.net/zhiyu520/
先把原来的人家写的贴出来:
/*
第四篇:只有延时服务的协作式的内核
Cooperative Multitasking
前后台系统,协作式内核系统,与占先式内核系统,有什么不同呢?
记得在21IC上看过这样的比喻,“你(小工)在用厕所,经理在外面排第一,
老板在外面排第二。如果是前后台,不管是谁,都必须按排队的次序使用
厕所;如果是协作式,那么可以等你用完厕所,老板就要比经理先进入;
如果是占先式,只要有更高级的人在外面等,那么厕所里无论是谁,都要
第一时间让出来,让最高级别的人先用。”
*/
#include <avr/io.h>
#include <avr/Interrupt.h>
#include <avr/signal.h>
unsigned char Stack; //人工堆栈
register unsigned char OSRdyTbl asm("r2"); //任务运行就绪表
register unsigned char OSTaskRunningPrioasm("r3"); //正在运行的任务
#define OS_TASKS 3 //设定运行任务的数量
struct TaskCtrBlock //任务控制块
{
unsigned int OSTaskStackTop;//保存任务的堆栈顶
unsigned int OSWaitTick; //任务延时时钟
} TCB;
//防止被编译器占用
register unsigned char tempR4asm("r4");
register unsigned char tempR5asm("r5");
register unsigned char tempR6asm("r6");
register unsigned char tempR7asm("r7");
register unsigned char tempR8asm("r8");
register unsigned char tempR9asm("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.OSTaskStackTop=(unsigned int)Stack;
//将人工堆栈的栈顶,保存到TCB结构体数组中
OSRdyTbl|=0x01<<TaskID; //任务就绪表相应位置1,表明处于就绪态
}
//开始任务调度,从最低优先级的任务TaskScheduler开始
void OSStartTask()
{
OSTaskRunningPrio=OS_TASKS; //首先运行TaskScheduler任务
SP=TCB.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.OSTaskStackTop=SP; //将正在运行的任务的堆栈顶保存
unsigned char OSNextTaskID; //在现有堆栈上开设新的空间
for (OSNextTaskID = 0; //进行任务调度
OSNextTaskID < OS_TASKS && !(OSRdyTbl & (0x01<<OSNextTaskID));//有已就绪任务就退出循环
OSNextTaskID++); //注意分号位置,这里循环体只是那个For语句
OSTaskRunningPrio = OSNextTaskID ; //得到一个新任务,调度到就执行态
cli();//关中断,保护堆栈转换
SP=TCB.OSTaskStackTop; //OSSched函数一执行完,就可以运行这个新任务
sei();
//根据中断时的出栈次序
__asm__ __volatile__("POPR29\t");
__asm__ __volatile__("POPR28\t");
__asm__ __volatile__("POPR31\t");
__asm__ __volatile__("POPR30\t");
__asm__ __volatile__("POPR27\t");
__asm__ __volatile__("POPR26\t");
__asm__ __volatile__("POPR25\t");
__asm__ __volatile__("POPR24\t");
__asm__ __volatile__("POPR23\t");
__asm__ __volatile__("POPR22 \t");
__asm__ __volatile__("POPR21\t");
__asm__ __volatile__("POPR20\t");
__asm__ __volatile__("POPR19\t");
__asm__ __volatile__("POPR18\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.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.OSWaitTick)
{
TCB.OSWaitTick--;
if(TCB.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,0);
OSTaskCreate(Task1,&Stack,1);
OSTaskCreate(Task2,&Stack,2);
OSTaskCreate(TaskScheduler,&Stack,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.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.OSTaskStackTop=SP;呵呵,你看了这里的SP是区别于人工堆栈里面的栈顶指针,一旦
这里的SP赋给了TCB.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.OSTaskStackTop赋给SP,OSTaskStackTop是等于指向人工堆栈里面的那个栈顶指针。哈哈,你
有点明白了吗,我们为什么在OSTaskCreate里面压那么多次的栈,其实有有用的,只可惜,它就只用一次,一夜情,用
完就走,哈哈。SP=TCB.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有一段是将在那里一直执行几次,当然,期间有有点延时的。
其实,还有其他的问题,大家按这种思路,也不会很难,就可能在有一点上不开窍而已,多看几次,如果你会用仿真,那就更好
了,知道具体程序怎么走。我没用过,只是凭思路的。希望对大家有点用处 先占一栋楼,以后慢慢看. 谢谢共享! COOL ! 感觉:是协作式就不必要建立中断,中途退出,这样内存消耗比较大啊。另外应考虑增加同优先级的轮换调度。最近也在考虑OS,想实现协作式。希望和大家多交流。我用AVR的感觉,即使Tick是1mS,我的大部分任务都可以跑一圈,可能是我以前做的那些项目还不够大吧,呵呵。 好东西,顶一个再仔细学习。 基于优先级协作式调度,usmartx不二选择。 收藏了 喜欢写在前面的比方 usmartx的缺点(不是关于抢占任务方面):在Tick中进行Tick服务、内核调度关中断等。会降低中断的相应时间。我改进了一个操作系统这样实现:Tick中断中对Tick进行计数。然后设置一个事件标志,等任务返回后检查事件标志然后回调Tick服务函数(其它的中断也采用这种方式,避免中断中进行内核调用。),延时状态的任务直接减去Tick计数数目,也可以实现不丢失Tick。因为即使Tick能使某个任务就绪但是它也不能立即抢占执行,所以还不如等当前任务返回后一道处理。另外,我把就绪队列设置成FIFO,从链尾加任务,调度时遍历链表找到最高优先级的第一个任务,从而变相地实现了同优先级的轮换。如果每次添加任务都进行链表遍历把TCB插入到合适的位置,还不如一次遍历找到最优先任务。
对usmartx没有细致的研究(懒得看别人的代码),大家有什么意见可以交流一下。 楼上所说的 :
在Tick中进行Tick服务、内核调度关中断等.
Tick可以看作是操作系统临界段,临界段影响了中断的实时性。
操作系统提高中断实时性采用非操作系统管理中断。(程明计small rtos一书中称为软非屏蔽中断,这里我称硬件级中断,与系统级中断对应)
我把中断分成两个级别,硬件级中断和系统级中断,对应临界段分为硬件级临界段和系统级临界段。
系统级临界段关闭所有受操作系统管理的中断。
硬件级临界段关闭总中断。
硬件级中断可以几乎不受系统级临界段影响,以提高实时性,但不能调用操作系统内核函数。
只要尽量降低硬件级临界段的时间了,就可以得到很高的硬件级中断实时性。
一般情况下可以做到硬件级临界段最长不超过20个时钟周期,甚至低于8个时钟周期。
8M时钟下,硬件级临界段约为1-2us左右,几乎可认为硬件级中断完全不受临界断影响。
对于usmartx,把Tick当作系统级临界段处理。
在处理tick时,关闭系统级中断,不关闭硬件级中断。 好东西,顶了再学。 讨论一下:
协作式:其实挺简单,将抢占式的中断(包括时拍中断)中任务切换功能全部屏蔽,在任务中或者调用OSTimeDly(x)/OSSemPend(x,x,x)...甚至主动去调用OS_Sched(),学习"自我放弃"便实现目的了.
抢占式:最大的问题在avr单片机上的表现就是RAM资源瓶颈,造成了综合性能下降.速度上并不是主要问题,我将UCOS移植到M16上OS_Sched()函数7.3728m最大切换时间也仅仅24uS(快速保存20uS,MAXTASKS = 8).
硬实时中断,大概只有用在掉电保护方面了.单片机的项目如果工艺分析透了,就上面我移植的用OS做的一个项目中断几乎最长(最坏)响应也是<30uS.对于响应20Khz的输入信号没有影响.
其实绕来绕去,归根结底就是AVR的片内RAM太少, [[ "只要尽量降低硬件级临界段的时间了,就可以得到很高的硬件级中断实时性"]],如果RAM充裕,就不怕压爆了.对于再入性,AVR的几个常用编译器都有先天优势. 先下载,慢慢看。 这几天都在看人家写的UC/OS简化版操作系统,这样学习,不至于太乱,
昨天去了个就书店,买了两本书,一本是<<嵌入式实时操作系统UC/OS-ii原理与应用>>.另外一本是<<基于嵌入式实时操作系统的程序设计技术>>.因为本人电子信息类这些更接近底层的专业,avr方面几乎全部自学.不过在操作系统的学习上应该还是蛮快的.
关于这个帖子,只是看了人家写的一个小教程,原本阿莫也在ouravr上发过,具体的地址:
http://www.ouravr.com/bbs/bbs_content.jsp?bbs_sn=574348&bbs_page_no=1&sub_kind_id=2065&bbs_id=1000
后来就想着应该写点总结什么的.
其他操作系统我也没看过,
因为要用到,所以就专心看这个,能解决我的问题就先看.
从简单入手,那学起来还是蛮快的
再加上最近要学习设计模式,很想把它引入我们的单片机编程上,设计模式在项目开发上真是一个必备的东西,也许你会说那个东西更多的涉及到上位机什么的.呵呵,到了ARM,你就会发现那个可能挺有用的.
数据结构方面,牛书<<算法导论>>的以前也看了一些,不过很多很好的算法在这个资源受限的AVR芯片上跑得有点吃力.不过还好,已经满足了
做点小项目,在操作系统上跑一下,就会促使我们学习更多的东西.会得到蛮大的提高的. 占先式好啊,实时性好,真正感觉到OS的优越与强大。
协作式,我更喜欢裸奔。 占先式,,片子的其它硬件资源如何利用?如串口,SPI接口等等. 我之前也有用占先式做过些项目,小案子还是可以,一旦遇到大案子,就没法用了,也找不出个原因来,烦!
特别是有浮点数运算的场合,如果简单的就按照例子上的,控制几个灯闪几下,还是可以. 学习中 那void Task0{ ...OSTimeDly(2秒)中的延时究竟多少呢?怎么计算出啊? mark m 哈哈,刚好我最近也弄这方面的。
问个问题。
原文:“
编译器会先用到以下寄存器
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。
”
问题:入栈有必要把r0,r1,r18-r27,r28-r31都入栈吗? 这些寄存器不用堆栈也可以吧?
“(r18-r27, r30-r31): 调用函数时作为参数传递”,OSSched都没有形参。
“r28和r29可能会被作为指向堆栈上的变量的指针。”,不是有sp吗? 上下文切换不保存所有寄存器,典型有chibios 。
个人最喜chibios上下文切换方法。 好贴~顶~ 挖个坟,不好意思 学习一下~~ 怎么不行呀,端口运行了根本不变 void OSTimeDly(unsigned int ticks)
{
if(ticks) //当延时有效
{
OSRdyTbl &= ~(0x01<<OSTaskRunningPrio); //任务运行就绪表相应位清0
TCB.OSWaitTick=ticks;//设置任务延时时钟
OSSched(); //从新调度
}
}
执行这个函数的时候会出错,执行后OSTaskRunningPrio变为0xff造成对战数据错误
用的avr gcc20090213 强烈关注~ mark 虚心的学习!
定 mark 试了第四篇,出现以下提示后就过不去了,不知道什么原因
C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp/cclViXog.s: Assembler messages:
C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp/cclViXog.s:169: Error: garbage at end of line
C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp/cclViXog.s:809: Error: garbage at end of line mark MARK MARK mark mark
页:
[1]