51单片机多任务运行最近发现有的幺弟在对系统的内核感兴趣,加上我也是部分内核的初学者,突然来兴,便用了两天写了一个简单的内核。
这个内核简单得不能再简单了,加上空格行、大括号和详细的注解只有246行,还带了4个点亮LED的任务。
至今为止我所见最简单的内核~~~ 就跟这个内核取个“多任务分时处理内核”吧!这个内核和ucos系统思想有很大的差异,但是能够帮助我们学习理解ucos系统,能够帮我们了解51的内部结构,以及大多数的单片机运行处理数据的原理~~~ 好废话就不说啦!希望我们能互相学习共同进步1、先来讲讲原理:首先,我们看书时会知道51单片机在执行中断的时候,会有以下几个步骤和几种情况。
根据KEIL的编译惯例(这个编译惯例你可以在编完程序后点仿真,里面有个后缀为.src 的文件,这个文件里面是一句C对应一句汇编,你就可以知道你编译的C代码它是怎么处理的,能帮助你学习汇编哦~~~),通常把进入中断后的所使用的通用寄存器组根据情况选择压栈。
也就是说,中断前后使用的寄存器组可能不一样,中断前可能使用0,中断中可能使用1。
如果使用的同一组寄存器,为了保存现场,KEIL就PUSH现场数据,然后POP就行啦。
但是keil很多时候不是你想象中那样,你叫它怎样他就怎样编译。
所以在程序中嵌入了少量的汇编。
其实,嵌入汇编是很简单的事情。
只要在C代码中加入#pragma asm 和#pragma endasm并在他俩的中间加入汇编就行。
别忘了还要在工程文件中添加C51S.LIB,这个文件在KEIL/C51/LIB中,这个文件也很重要,不然编译会出现警告,记得把文件类型选择为全部文件,不然看不见它。
接下来说说KEIL的中断汇编。
在C51中,中断到来时,CPU响应中断保存当前PC 指针地址压栈SP所指地址。
然后将PC指针指向中断向量地址,在中断向量地址中只有一句汇编程序:LJMP XX 意思是跳转到某地址。
因为中断后只有8个寄存器,但是你的代码量远远不只有8个寄存器能装下的。
这也就是说,响应中断后,先跳转到硬件规定的地址,再由那个地址跳转到中断程序入口。
然后,PC指针跳转到中断程序地址,开始从SP所指地址压栈ACC,B,DPH,DPL,PSW,按理说还需要压栈R0~R7,但KEIL一般是通过换通用寄存器来实现的(也就是改变RS1和RS0来实现的)。
也就说KEIL根本不压栈R0~R7。
这个怎么能行,当然不行!不保存我们就不能完全的返回先前压栈的任务啦!好吧,那我们就只有手动保存压栈,这样不就行了,简单吧!所以我们来帮它。
已经通过前面知道它在进入中断的时候已经把中断前的PC指针压栈到中断前SP所指的地址了,所以进入中断后,实际在SP中断前所指地址中已经按顺序压栈了PC低8位,PC高8位,ACC,B,DPH,DPL,PSW总共7个数据,SP是向上增长的,也就是说每压一次堆栈SP+1。
然后再把我们的R0~R7寄存器压入堆栈,这不就行啦,就保护现场所需的全部数据,就算有时R0~R7寄存器用不上我们也得加进去,为了为了保证正确的返回现场。
因此我们保存一次数据就需要7+8=15字节的堆栈,每个任务的起始地址保存一次,中间临时要保存一次,共需要15+15=30字节的堆栈。
所以定义程序空间为现场保存空间为0~29。
名字叫:unsigned char TASK_STACK[TASK_MAX][30];//程序现场保存数组。
TASK_MAX是程序个数,因为每一个程序都需要保存两次,每次15个变量来保存现场,并且51是8位的单片机所以用unsigned char。
然后就是程序现场保存数组的初始化使每个数据都是0。
首先,根据响应中断后的压栈顺序,知道了数组0位和1位保存的是中断前程序的地址,现在,我们需要把自己写的程序的起始地址给数组,以便第一次中断结束后程序从自己写的程序的起始地址开始。
我们都知道,程序的名字类似于一个指针,叫函数指针。
在c51中程序地址为16位,即一个unsigned int 型变量。
所以如果子程序名称为:TASK_1(),则,程序的地址为:(TASK_1)。
定义一个unsigned int address;然后让address=(unsigned int)(TASK_1);address中保存就是程序入口地址了。
然后把adress的高八位给TASK_STACK[TASK_MAX][0],低位给TASK_STACK[TASK_MAX][1]。
这样程序的初始化就完成了。
接下来就是由于进入中断时程序自己为我们保存了很多变量,所以我们只需要保存R0~R7就行。
嵌入汇编:进入中断时#pragma asmPUSH AR0省略。
PUSH AR7#pragma endasm中断结束之前#pragma asmPOP AR7省略。
POP AR0#pragma endasm这样,基本的任务切换就完成了。
前面看过别人的出栈过程,是:#pragma asmPOP AR7POP AR6POP AR5POP AR4POP AR3POP AR2POP AR1POP AR0POP PSWPOP DPLPOP DPHPOP BPOP ACCRETI#pragma endasm但是我是没有写全,我发现汇编中每次都编译这个东西,所以我就把他省略啦!希望我提供的这些能为大家起到帮助。
让我们共同进步!部分思想来自互联网!由于能力有限可能部分有错,或者写的不够好,希望大家多多海涵!如需要详细工程,请到我QQ留言,2745466252、程序:/****************************************************************************************************** * 描述:多任务运行程序* 主要类容:四个单独点亮LED的任务,间隔不同时间点亮* 平台:52单片机* 注释:多任务分时运行,一个任务运行os_times时间片,P2口接LED* 作者:苟强* 结束时间:2014/12/5***************************************************************************************************** */#include <reg52.h> //52单片机包含的头文件#define task_max 4 //任务的个数#define task_stack_max 30 //任务堆栈的大小#define os_start EA=1;ET0=1;TR0=1; //开启总中断,开启定时器中断,启动定时器#define os_stop EA=0;ET0=0;TR0=0; //关闭总中断,关闭定时器中断,关闭定时器unsigned int os_times; //每片轮回时间unsigned char os_stack[15]; //第一次压栈位置和大小unsigned int os_delays[task_max]; //每个任务的等待时间(延时函数)unsigned char os_i,os_is; //os_i循环执行任务的标号,os_is循环查询任务等待时间的标号unsigned char memory_data_size[task_max]; //记忆入栈时的偏移量unsigned char idata task_stack[task_max][task_stack_max]; //任务堆栈void os_init(unsigned int times); //初始化系统变量函数声明void os_creat_task(unsigned int address,unsigned char task_num); //创建任务函数声明void os_delay(unsigned int sleeps); //延时函数声明sbit LED0=P2^0; //任务1所使用的LEDsbit LED1=P2^1; //任务2所使用的LEDsbit LED2=P2^2; //任务3所使用的LEDsbit LED3=P2^3; //任务4所使用的LED/****************************************************************************************************** * void task_1()* 描述:任务1* 主要内容:实现LED闪烁* 参数:无* 返回值:无* 被调用:void main()* 注释:所创建任务***************************************************************************************************** */void task_1(){while(1){LED0=~LED0;os_delay(1000);}}/****************************************************************************************************** * void task_2()* 描述:任务2* 主要内容:实现LED闪烁* 参数:无* 返回值:无* 被调用:void main()* 注释:所创建任务***************************************************************************************************** */void task_2(){while(1){LED1=~LED1;os_delay(2000);}}/****************************************************************************************************** ***** void task_3()* 描述:任务3* 主要内容:实现LED闪烁* 参数:无* 返回值:无* 被调用:void main()* 注释:所创建任务***************************************************************************************************** */void task_3(){while(1){LED2=~LED2;os_delay(3000);}}/****************************************************************************************************** * void task_4()* 描述:任务4* 主要内容:实现LED闪烁* 参数:无* 返回值:无* 被调用:void main()* 注释:所创建任务***************************************************************************************************** */void task_4(){while(1){LED3=~LED3;os_delay(4000);}}/****************************************************************************************************** * void main() using 0* 描述:主函数* 主要内容:任务的创建和运行* 参数:无* 返回值:无* 被调用:无* 注释:无***************************************************************************************************** */void main() using 0 //主函数,using 0是配置R0~R7存放的位置{os_init(100); //初始化多任务系统os_creat_task(task_1,0); //创建任务1os_creat_task(task_2,1); //创建任务2os_creat_task(task_3,2); //创建任务3os_creat_task(task_4,3); //创建任务4SP=(unsigned char)(&os_stack); //给SP送一个地址,等一会堆栈就从这个地址开始压栈os_start; //打开中断开始任务while(1); //任务开始启动等待第一次任务就绪}/****************************************************************************************************** ***** void os_init(unsigned int times)* 描述:系统初始化* 主要内容:装定时器初值* 参数:定时器装入时间* 返回值:无* 被调用:void main()* 注释:创建时钟***************************************************************************************************** *****/void os_init(unsigned int times){unsigned char j,k; //声明变量在本函数使用os_times=times; //初始化系统中断时间片TMOD=0X01; //选择定时器模式TH0=0xff-(os_times>>8); //定时器高位装初值TL0=0xff-(os_times&0xff); //定时器低位装初值for(k=0;k<task_max;k++) //外层循环for(j=0;j<task_stack_max;j++) //内层循环task_stack[k][j]=0; //初始化堆栈为零os_i=0; //初始化当前任务for(os_is=0;os_is<task_max;os_is++) //循环初始化os_delays[os_is]=0; //每个任务的延时量清零}/****************************************************************************************************** ***** void os_creat_task(unsigned int address,unsigned chartask_num)* 描述:创建任务* 主要内容:保存任务的指针和优先级* 参数:任务地址和优先级* 返回值:无* 被调用:void main()* 注释:创建一个新任务***************************************************************************************************** *****/void os_creat_task(unsigned int address,unsigned char task_num){task_stack[task_num][0]=address&0xff; //每个任务初始PC指针位置高位task_stack[task_num][1]=address>>8; //每个任务初始PC指针位置低位}/****************************************************************************************************** ***** void os_delay(unsigned int sleeps)* 描述:延时函数* 主要内容:任务中延时调用* 参数:装入延时的时间* 返回值:无* 被调用:任务函数调用* 注释:当12M晶振时,sleeps=1延时***************************************************************************************************** *****/void os_delay(unsigned int sleeps){os_delays[os_i]=sleeps; //为任务中的延时变量赋值while(os_delays[os_i]!=0); //等于零时延时结束}/****************************************************************************************************** ***** void task_swith() interrupt 1 using 0* 描述:定时器中断0* 主要内容:实现任务的调度* 参数:无* 返回值:无* 被调用:无* 注释:定时器被占用,作为任务的调度***************************************************************************************************** *****/void task_swith() interrupt 1 using 0 //进入中断压栈保存{#pragma asm //开始使用汇编PUSH AR0 //压栈R0PUSH AR1 //......PUSH AR2 //......PUSH AR3 //......PUSH AR4 //......PUSH AR5 //......PUSH AR6 //......PUSH AR7 //压栈R7#pragma endasm //结束使用汇编memory_data_size[os_i]=(SP)-(unsigned char)(&task_stack[os_i]); //记住当前程序到起始位置的偏移量os_stop; //关闭中断,防止调度时被打断TH0=0xff-(os_times>>8); //装入定时器高位,提供时钟节拍TL0=0xff-(os_times&0xff); //装入定时器低位,提供时钟节拍os_i++;if(os_i>=task_max) //是否超出任务上限os_i=0;if(os_delays[os_i]==0) //当前任务是否结束memory_data_size[os_i]=15; //调到任务的起始位置SP=(unsigned char)(&task_stack[os_i]+memory_data_size[os_i]); //未结束则装入中断前地址for(os_is=0;os_is<task_max;os_is++) //循环每个任务if(os_delays[os_is]>0) //判断延时是否结束,如果没结束大于0os_delays[os_is]--; //延时减1os_start; //调度结束恢复开启中断#pragma asm //开始使用汇编POP AR7 //出栈R7POP AR6 //......POP AR5 //......POP AR4 //......POP AR3 //......POP AR2 //......POP AR1 //......POP AR0 //出栈R0#pragma endasm //结束使用汇编}。