基于51内核的多任务系统(原创)
以前写基于定时中断的顺序执行架构程序,实时性是没问题,但是逻辑过于复杂,于是想实现延时,超时切换的操作系统,利用多任务,可以编写更简洁的程序,本来想做无限任务的系统,但是调试发现任务切换开销很大,占用资源也多,于是将任务定在8任务上。
系统概况:
此系统最多八个任务,任务越多,任务开支越大,一个任务栈深度初定25字节,栈放在高128-255的RAM中,任务越多,占用越多,深度能调节,任务切换检测大概需要25us(STC 24Mhz),所有任务在10ms内完成执行,也就是说,在10ms内,如果不是任务忙,总有一段时间能执行。
任务优先级为ID优先和轮流优先,10ms内,优先级根据ID切换,如果当前任务ID准备就绪,当前ID优先级最高,如果当前任务ID未就绪,优先级按ID越小越高。
这段时间没精力去继续研究,所以将思路和程序贴出来,希望对大家有帮助。
#include <INTRINS.H>
#include "BASE.h"
#define uchar unsigned char
#define uint unsigned int
void Task0(void)
{
while(1)
{
Deply(100);//100us为单位
P30 = !P30;
}
}
void Task1(void)
{
while(1)
{
Deply(100);
P31 = !P31;
}
}
void Task2(void)
{
while(1)
{
Deply(100);
P32 = !P32;
}
}
void Task3(void)
{
while(1)
{
Deply(10);
P33 = !P33;
}
}
void Task4(void)
{
while(1)
{
Deply(100);
P34 = !P34;
}
}
void main(void)
{
P35 = 0;
Task_inti();
Task_Load(Task0, 0);
Task_Load(Task1, 1);
Task_Load(Task2, 2);
Task_Load(Task3, 3);
Task_Load(Task4, 4);
Task_Start();
while(1);
}
为了减少开销,我将空闲任务直接定在main函数里面的while(1);,系统进入无任务或者全任务延时,会进入main函数里面的while(1);进行空循环。
Task_inti();
#define Task_inti() SP = &SP_Main+1;Task_Id = 0;\
Task_Pro = 0;Task_Stack = 0;\
Task_Deply = 0;Task_Ready = 0;\
Task_Sum = 0;Task_Swh_Time = 0
这个其实是一个宏定义,包括任务的入口,还有变量的初始化
Task_Load(Task0, 0);
//------------------任务装载--------------------
unsigned char Task_Load(unsigned int Fn, unsigned char T_id)
{
unsigned char i;
if(Task_Stack & Get_1bit[T_id]) //创建任务失败
return TASK_FAIL;
Task_Stack |= Get_1bit[T_id];
Task_Deply &= ~(Get_1bit[T_id]);
Task_Ready |= Get_1bit[T_id];
for(i=0;i<MAX_SFA;i++)
{
SP_STACK[T_id][i] = 0;
}
SP_STACK[T_id][0] = (unsigned int)Fn & 0xff;
SP_STACK[T_id][1] = (unsigned int)Fn >> 8;
SP_Add[T_id] = &SP_STACK[T_id]+MAX_PUSP_DEEP;
Deply_ID[T_id] = 0;
if(Task_Sum < MAX_TASK)Task_Sum++;
Task_Swh_Time = T_10MS / Task_Sum;
return TASK_SUCCESS;
}
装载任务,根据任务的ID加载到对应的数组里面,然后初始化任务数据,给任务加载任务的地址,还有计算任务数量
Task_Start();
void Task_Start(void)
{
intiTimer1();
Timer0_K = 0;
Task_Deply = 0;
Deply_Ready_Sum = 0;
if(Task_Sum == 0)
{
Task_Swh_Time = T_10MS;
}
EA = 1;
}
开启任务,先初始化定时器,任务占用一个定时器作为延时和任务切换。
intiTimer1();
void intiTimer1(void)
{
TMOD |= 0x02; //设置定时器为模式2(8位自动重装载)
// TL0 = 0x00; //初始化计时值
// TH0 = 0x00;
TL0 = T0SET; //初始化计时值
TH0 = T0SET;
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
}
到这里,任务的创建,初始化,装载,运行已经开始,等第一个定时器中断来临,就进入任务执行。
任务的进入,为了不让编译器自动加载入栈出栈,用一个A51的文件设置了中断加载地址。
$NOMOD51
EXTRN CODE (timer0)
NAME TIMER
CSEG AT 0BH
LJMP timer0
; CSEG AT 0124H
; RETI
END
下面这个是中断处理函数:
/*
定时器0,延时100us
每次延时查看延时数组是否不为零
如果不为零,将不为零数组减一
根据任务数分配10ms时间进行倒计时
倒计时结束,更改任务优先级
如果需要切换任务,调用任务切换函数
*/
void timer0(void)
{
P36 = 1;/
if(Timer0_K == 0) //初始化
{
Timer0_K = 1;
Timer0_T = 0;
#pragma asm
POP SP_Main+1;
POP SP_Main;
MOV SP,#LOW (SP_STACK+01H);
MOV Task_Id,#0;
MOV Task_Pro,#0;
MOV Time_10ms,#T_10MS;
SETB Timer0_D;
SETB Main_W;
// RETI; //退出
#pragma endasm
}
/*
建立两个数组
一个数组存储延时变成1的ID,另外设立一个变量记录这样延时的数量
一个数组存储被激活ID,Task_pro提供获取,Task_Sum记录激活任务数量
延时变成1的最先优先级是当前Task_Pro,
*/
#pragma asm
XCH A,Task_Temp;
MOV A,Task_Deply; //延时数组存在数据
JZ DEPEND1; //不为0,跳转 (不等跳转)
DEPST1:
PUSH AR0;
PUSH AR1;
MOV Deply_Ready_Sum,#00H;
MOV Task_Temp1,#00H;
MOV Switch_i,#MAX_TASK;
MOV A,Task_Pro; //Task_Temp1 = Task_Pro + 1; ???未启动的任务?????
INC A;
CJNE A,#MAX_TASK,DEPST2; //不等转移 if(A == MAX_TASK)
MOV A,#00H;
DEPST2:
MOV Task_Temp1,A;
ADD A,ACC;
ADD A,#LOW(Deply_ID);
MOV R1,A;
INC A;
MOV R0,A;
DEPST:
CJNE @R1,#00H,DEP1; //高位不为0,跳转 (不等跳转)
CJNE @R0,#00H,DEP2; //低位不为0,跳转 (不等跳转)
SJMP DEPEND;
DEP1: //高位不为0的情况
DEC @R0;
CJNE @R0,#0FFH,DEP1; //低位不为0XFF,跳转 (不等跳转)
DEC @R1;
SJMP DEPEND;
DEP2: //这里高位为0,低位不为0
CJNE @R0,#01H,DEP3; //低位不为1,跳转 (不等跳转)
//Deply_Ready[Deply_Ready_Sum] = Task_Temp1;
PUSH AR0;
MOV A,#LOW(Deply_Ready);
ADD A,Deply_Ready_Sum;
MOV R0,A;
MOV A,Task_Temp1;
XCH A,@R0;
POP AR0;
INC Deply_Ready_Sum;
SETB Timer0_D;
SJMP DEPEND;
DEP3:
DEC @R0;
DEPEND:
INC R0;
INC R0;
INC R1;
INC R1;
INC Task_Temp1;
MOV A,Task_Temp1;
CJNE A,#MAX_TASK,DEPEND2; //不等转移 if(A == MAX_TASK)
MOV Task_Temp1,#00H;
MOV R0,#LOW(Deply_ID+1);
MOV R1,#LOW(Deply_ID);
DEPEND2:
DJNZ Switch_i,DEPST;
POP AR1;
POP AR0;
DEPEND1:
XCH A,Task_Temp;
#pragma endasm
#pragma asm
DJNZ Time_10ms,TIMEST1; //切换任务延时,总时间为10ms,根据任务多少划分时间
XCH A,Time_10ms;
MOV A,Task_Swh_Time;
XCH A,Time_10ms;
SETB Timer0_T;
TIMEST1:;
#pragma endasm
P36 = 0;/
if(Timer0_D == 1 || Timer0_T == 1)
{
Task_Switch();
}
__asm RETI;
}
我用P3.6查看任务的开销,基本上,任务切换开销大概在25us以内(单片机STC,频率24Mhz)
任务切换开销其实和任务数量有关,任务越少,开销越少。
任务优先级有两种,一种是当前优先任务,有个Task_Pro,这个变量每隔一段时间在改变,划分10ms完成一个任务改变周期,譬如任务有5个,那Task_Pro在10ms内改变5次,Task_Pro的值如果跟ID一致,那此时此ID任务优先级最高;还有一种优先级,如果Task_Pro在忙状态(延时或者挂起),根据ID越小,优先级越高。
执行过程大概如此:
1.第一次进入,也就是在main函数进入,需要做地址调整,先记录while(1);的入口地址,再调整任务0的地址;
2.进入延时检测,验收检测设定为1000us,也可以在H文件更改,任务如果延时未结束,将延时数据减一,如果任务延时结束,先登记任务;
3.任务优先级切换计数,之后根据标志位确定是否要启动任务切换。
任务切换函数:
//----------------------切换任务---------------------
/*
功能:1.直接调用 2.定时器调用 3.延时调用
调用改变任务
任务优先级: 1.当前默认任务 2.延时时间达到任务 3.ID越小任务
*/
void Task_Switch(void)
{
OS_SW_ENTER();
P35 = 1;//
if(Timer0_D == 1 || Timer0_T == 1 || Deply_D == 1) //定时器或者延时调用,减去
{
SP--;
SP--;
}
if(Main_W == 0)
{
#pragma asm
PUSH ACC;
PUSH B;
PUSH PSW;
PUSH DPL;
PUSH DPH;
PUSH AR0;
PUSH AR1;
PUSH AR2;
PUSH AR3;
PUSH AR4;
PUSH AR5;
PUSH AR6;
PUSH AR7;
#pragma endasm
SP_Add[Task_Id] = SP;
}
if(Timer0_T == 1 || (Timer0_D == 0 && Deply_D == 0)) //任务切换时间已到 或者 任务主动切换
{
for(Switch_i = 0;Switch_i < MAX_TASK;Switch_i++) //查找下一个可执行任务
{
Task_Pro++;
if(Task_Pro >= MAX_TASK)
{
Task_Pro = 0;
}
if(Task_Stack & (Get_1bit[Task_Pro]))
{
break;
}
}
}
//切换任务,当前Task_Pro优先级最高
//Task_Pro、Task_Id、Task_Ready、Task_Deply
if(Task_Ready == 0 && Deply_Ready_Sum == 0) //所有任务在忙
{
Main_W = 1;
SP = &SP_Main+1;
}
else
{
if(Deply_Ready_Sum > 0) //有延时结束任务
{
Deply_Ready_Sum--;
Task_Id = Deply_Ready[Deply_Ready_Sum];
Deply_ID[Task_Id] = 0;
Task_Deply &= ~(Get_1bit[Task_Id]);
Task_Ready |= Get_1bit[Task_Id];
}
else //一般任务
{
//条件:最优先Task_Pro任务,然后是其它任务
//Task_Ready == 1;
Task_Id = Task_Pro;
for(Switch_i = 0;Switch_i < MAX_TASK;Switch_i++) //查找下一个可执行任务
{
Task_Temp = Get_1bit[Task_Id];
if (Task_Ready & Task_Temp)
{
break;
}
Task_Id++;
if (Task_Id >= MAX_TASK)
{
Task_Id = 0;
}
}
}
Main_W = 0;
Deply_D = 0;
SP = SP_Add[Task_Id];
#pragma asm
POP AR7;
POP AR6;
POP AR5;
POP AR4;
POP AR3;
POP AR2;
POP AR1;
POP AR0;
POP DPH;
POP DPL;
POP PSW;
POP B;
POP ACC;
#pragma endasm
}
if(Timer0_T == 1 || Timer0_D == 1)
{
Timer0_T = 0;
Timer0_D = 0;
OS_SW_EXIT();
__asm RETI;
}
P35= 0;/
OS_SW_EXIT();
}
还有一个任务延时函数
//----------------------任务延时---------------------
void Deply(unsigned int i)
{
OS_SW_ENTER();
if(TL0>T0SET_PART)
{
if(i<0xffff)
{
i++;
}
}
if(i > 0)
{
Deply_ID[Task_Id] = i;
Task_Deply |= Get_1bit[Task_Id];
// Deply_Ready |= (1<<Task_Id);
Task_Ready &= ~(Get_1bit[Task_Id]);
}
else
{
OS_SW_EXIT();
return;
}
Deply_D = 1;
Task_Switch();
}
执行此函数,一个功能是登记延时,另外一个功能是切换任务
源文件我放在下载资源,提供参考
此系统参考了51_00_OS