RTOS Once More
大三的时候,我也看过有关实时操作系统的论文与程序,并没有太在意这一知识点、没有去深刻的学习有关的知识,显然我是被论文中的一句话所坑了“实时性最好的还是裸机跑程序”,确实。想想我大学四年的开发中无论是学习还是比赛,都是“裸奔”的。由于STM32F103的主频够快,也感觉不到控制输出有延迟。现在当我去做FlightFight游戏时,却实现不了两个对象的分别控制,才想起RTOS。2020/4/19今天是第一天学习,总结一下今天的成果。今天是从文章“建立一个属于自己的AVR的RTOS”开始的。
知识点回顾
专门与堆栈和PC指针打交道的一类指令
rcall 相对调用子程序指令
icall 间接调用子程序指令
ret 子程序返回指令
reti 中断返回指令
ret与reti,都是将堆栈顶的两个字节弹出来送入程序计数器PC中,一般用来从程序或中断中推出。其中reti还可以在退出中断时,重新开启全局中断使能。这些都是学习汇编的时候提到的。
启发
有了相应的知识点基础,并加上RTOS是利用函数地址调用函数的。我们可以模拟一下操作系统调用函数的过程:将函数的地址存到私有堆栈中。当某任务就绪时,将该任务的函数入口地址,给SP,当使用ret 返回时SP的值恢复到了PC中,进而PC指向了某个函数。所以先建立私有堆栈试一试。
void fun1(void)
{
unsigned char i=0;
while(1)
{
PORTB=i++;
PORTC = 0X01<<(i%8);
}
}
unsigned char Stack[100];//建立一个100字节的人工堆栈
void RunFunInNewStack(void (*pfun)(),unsigned char *pStack)
{
*pStack --=(unsigned )pfun;//将函数的地址低位压入堆栈
*pStack --=(unsigned int)pfun>>8;//将函数的地址高位压入堆栈
SP = pStack;//将堆栈指针指向人工堆栈的栈顶
}
int main(void)
{
RunFunInNewStack(fun1,&Stack[99]);
}
该程序的运行过程是:函数RunFunInNewStack()将指向函数的指针的值保存到一个unsigned char的数组Stack中,作为人工堆栈。并且将栈顶的数组传递给堆栈指针SP。因此当用ret返回时,从SP中恢复到PC中的值,就变成了指向fun1()的地址,开始运行fun1()。
问题发现
其一:通常大家都会认为,在任务调度时,当然要将所有的通用寄存器都保存,并且还应该保存程序状态寄存器SREG。然后再根据相反的次序,将任务内容恢复。这是不对的。反复试验用到的寄存器为第一类第二类第三类寄存器。如果在中断函数中有调用其它函数,会在进入中断后,固定的将第一类寄存器和第三类寄存器入栈 ,在退出中断又将他们入栈。
其二:前后台系统,协作式内核系统 ,抢占式内核系统,有何不同?
举个例子:排队打饭。你正在打饭,身后是院长,院长身后是校长。
如果是前后台系统:不管是谁都得排队一个一个来。也就是我们的裸奔系统。
如果是协作式系统:等你打完饭,让校长先吃上饭,院长最后才吃上饭。
如果是抢占式系统:不论是谁在打饭还是排队,只要有更高级别的人在等着,都要第一时间让出来,让最高级别的校长第一个打饭。
实战1--------写一个只有延时服务的协作式内核
#define OS_TASKS 3
unsigned char Stack[200];
register unsigned char OSRdyTb1 asm("r2"); //运行任务就绪表
register unsigned char OSTaskRunningPrio asm("r3");
//正在运行的任务
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"