51调度器
大多内容来自《时间嵌入式系统设计模式8051系列为控制器开发可靠应用》?
1〉什么是调度器? ???可以从两个角度来看调度器: ·???一方面,调度器可以看作是一个简单的操作系统,允许以周期性或(更少见)单次方式来调度任务。 ·???从底层角度来看,调度器可以看作是一个由许多不同任务共享的定时器中断服务程序。因此,只需要初始化一个定时器,而且改变定时器的时候通常只需要改变一个函数。此外,无论需要运行1个、10个还是100个不同的任务,通常都可以使用同一个调度器完成。注意,这种”共用中断服务程序”与桌面操作系统的共用打印功能非常类似。 例如:下面用一个定时器来调度三个任务的模型: Void main(void) { ??//设置调度次数 ? SCH_Init(); ? //增加任务(1ms时间间隔) ? //Function_A将每隔2ms运行一次 ? SCH_Add_Task(Function_A,2); ? //Function_B将每隔10ms运行一次 ? SCH_Add_Task(Function_B,10); ? //Function_C将每隔15ms运行一次 ? SCH_Add_Task(Function_C,15); ? SCH_Start(); ? While(1) ? { ??? SCH_Dispatch_Tasks(); ? } } ? ?2>?调度器的分类:合作式调度和抢占式调度 v??????合作式调度器 ·???合作式调度器: ?????合作式调度器提供了一种单任务的系统结构 ·???操作 ?????任务在特定时刻被调度运行(以周期性或单次性) ?????当任务需要运行时,被添加到任务队列 ?????当CPU空闲时,运行等待任务中的下一个(如果有的话) ?????任务运行直到完成,然后由调度器来控制 ·???实现 ?????这种调度器很简单,用少量代码即可实现 ?????该调度器必须一次只为一个任务分配存储器 ?????该调度器通常完全由高级语言(如C语言)实现 ?????该调度器不是一种独立的系统,它是开发人员的代码的一部分 ·???性能 ?????设计阶段需要小心以快速响应外部事件 ·???可靠性和安全性 ?????合作式调度简单、可预测、可靠并且安全 合作式调度器不但可靠而且可预测的主要原因是在任一时刻只有一个任务是活动的。这个任务运行直到完成,然后由调度器来控制。与此同时,在完全的抢占式系统的情况下,有多个活动任务。在这种系统中,假设有一个任务正在从端口读数据时,调度器执行了“上下文切换”,是另一个任务访问同一个端口。在这种情况下,如果不采取措施阻止这种操作,数据将可能丢失或破坏。 v??????抢占式调度器 ·???抢占式调度器: ?????抢占式调度器提供了一种多任务的系统结构 ·???操作: ?????任务在特定的时刻被调度运行(以周期性或单次性) ?????任务需要运行时,被添加到等待队列 ?????等待的任务(如果有的话)运行一段固定的时间,如果没有完成,将被暂停并方回到等待队列。然后下一个等待任务运行一段固定的时间,诸如此类等等 ·???实现: ?????这种调度器相对复杂,因为必须实现诸如信号灯这样的特性,用来在“并行处理的”任务试图访问共同的资源时避免冲突 ?????该调度器必须为抢占任务的所有中间状态分配存储器 ?????该调度器通常将(至少是部分的)由汇编语言编写 ?????该调度器通常作为一个独立的系统被创建 ·???性能: ?????对外部事件响应速度快 ·???可靠性和安全性: ?????与合作式调度器相比,通常认为更不可测,并且可靠性低 v??????混合式调度器 ·???混合式调度器: ?????混合式调度器提供了有限的多任务功能 ·???操作: ?????支持多个合作式调度的任务 ?????支持一个抢占式任务(可以中断合作式任务) ·???实现: ?????调度器很简单,用少量代码即可实现 ?????该调度器必须一次为两个任务分配存储器 ?????该调度器通常完全由高级语言(如C)实现 ?????该调度器不是一种独立的系统,它是开发人员代码的一部分 ·???可靠性和安全性: ??????只要小心设计,可以和单纯的合作式调度器一样可靠 ? 3>合作式调度器祥解: ·???许多C程序员不熟悉函数指针。然而它确是创建调度器的关键。下面的例子说明了函数指针的基本特征: #include”main.h” #include”Printf51.h” #include<stdio.h> //私有函数原型 Void Square_Number(int,int*); Int main(void) { ??? Int a=2,b=3; ??? Void (* pFn)(int,int*);//声明pFn作为fn的指针,fn带有int参数和int指针参数 ??? Int Result_a,Result_b; ??? //准备在keil硬件模拟器上使用printf() ????Printf51_Init(); ??? pFn = Square_Number; //pFn存放Square_Number的地址 ??? printf(“Function code starts at address: %un”,(tWord) pFn); ??? Printf(“Data item a starts at address:%un”,(tWord) &a); ??? //以传统方式调用”Square_Number” ??? Square_Number(a,&Result_a); ??? //使用指针函数调用”Square_Number” ??? (*pFn)(b,&Result_b); ??? printf(“%d Square is %d (using normal fn call)n”,a,Result_a ); ??? Printf“%d Square is %d (using fn pointerl)n”,b,Result_b); ??? While(1); ??? Return 0; } Void Square_Number(int a,int *b) { ??? ?*b = a*a; } ·???调度器由以下几个部分组成: ??????调度器数据结构; ??????初始化函数; ??????中断服务程序(ISR),用来以一定的时间间隔刷新调度器; ??????向调度器增加任务的函数; ??????使任务在应当运行的时候被执行的调度函数。 ??????从调度器删除任务的函数(并不是所有的系统都需要); 下面是一个简单的调度器的例子:重复闪烁LED的调度器,一秒亮,一秒灭,如 此循环。 Void main(void) { ????//设置调度器 ????SCH_Init_T2(); ????//为”Flash_LED”任务做准备 ????LED_Flash_Init(); ????//增加”Flash_LED”任务(1000ms亮,1000ms灭),定时单位为时标(1ms时间间隔)(最大为65536个时标)? ????SCH_Add_Task(LED_Flash_Updata,1000); ????//开始调度器 ????SCH_Start();//刷新任务队列 ????While(1) ????{ ??????? SCH_Disptch_Task(); ????} } ? Void SCH_Updata(void) interrupt INTERRUPT_Timer_2_Overflow { ????//刷新任务队列 ????….. } 程序运行如下: 1、?假定LED将通过LED_Flash_Updata()任务被点亮和熄灭。这样,如果LED最初是熄灭的,则调用LED_Flash_Updata()两次,LED将被点亮然后再次熄灭。因此为了获得闪烁频率,要求调度器每秒调用LED_Flash_Updata()一次,且无限循环。 2、?使用函数SCH_Init_T2来准备调度器。 3、?调度器准备好之后,使用函数SCH_Add_Task()将函数LED_Flash_Updata()添加到调度任务队列中。同时,以如下方式指定LED以需要的频率闪烁: //增加”Flash_LED”任务(1000ms亮,1000灭) //定时单位为时标(1ms时标间隔) //(最大的间隔/延迟是65536) SCH_Add_Task(LED_Flash_Updata,1000); 4、?函数LED_Flash_Updata()的定时将由函数SCH_Updata()控制,SCH_Updata()是一个由定时器2溢出触发的中断服务程序: Void SCH_Updata(void) interrupt INTERRUPT_Timer_2_Overflow { ??? //刷新任务队列 ????…… } 5、?“刷新”中断服务程序不运行任务,而是计算任务应该在什么时候运行并设置标志。运行LED_Flash_Updata()的任务由调度函数(SCH_Dispatch_Tasks())完成,这个函数在主(超级)循环中运行。 While(1) { ????SCH_Disptch_Task(); } ???????调度器是一种“低成本的”方案,他只占用很小的CPU资源,此外,就调度器本身而言,每个任务只需要不超过7个字节的存储器。因为在一个典型的系统中不会超过4~6个任务,即使运行在8位微控制器上,所需要的任务预算(大约40个字节)也是不多的。 6、?调度器数据结构以及任务队列 ????????调度器的核心是调度器数据结构。这是一种用户自定义的数据类型,集中了每个任务所需要的信息。Sch51.h文件: //可能的话,存储在DATA区,以供快速存取 //每个任务的存储器总和是7个字节 Typedef data struct { ????//指向任务的指针(必须是一个”void(void)”函数) ????Void (code *pTask)(void); ????//延时(时标)直到函数将(下一次)运行 ????//详细说明参见SCH_Add_Task*() ????tWord Delay; ????//连续运行之间的间隔(时标) ??? //详细参见SCH_Add_Task() ??? tWord Period; ??? //当任务需要运行时(由调度器)加1 ????tByte RunMe; } sTask; ????????文件Sch51.h也包含一个常数SCH_MAX_TASKS; //在程序运行期间的任一时刻允许的任务最大数目 //每个新建项目都必须调整 #define SCH_TASKS (1) ????????在文件Sch51.c中,数据类型sTask和常数SCH_MAX_TASKS一起用来创建任务队列,并一直被调度器所引用: //任务队列 sTask??SCH_tasks_G[SCH_MAX_TASKS]; ????????任务队列的大小必须通过调整SCH_MAX_TASKS的值来保证足够长的任务队列,以保存系统所需要的任务。例如,如果需要调度如下三个任务: SCH_Add_Task(Function_A,2); SCH_ADD_Task(Function_B,1,10); SCH_ADD_Task(Function_C,3,15); 那么SCH_MAX_TASKS必须大于3(或更大),以保证调度器的正常运行。同时注 ????????意如果不满足这些条件,调度器将产生一个错误代码 ?7、?初始化函数: ??? 如同大多数需要被调度的任务一样,调度器本身也需要一个初始化函数。虽然该函数执行各种重要的操作,诸如准备调度器队列以及准备错误代码变量,然而这个函数的主要用途是设置定时器,用来产生驱动调度器的定期“时标”。51单片机的定时器2有自动重装功能的16位定时器,因此使用该定时器是合理的。 使用定时器2的一个初始化函数的例子: /*SCH_Init_T2()??调度器初始化函数,准备调度器数据结构并且设置定时器一所需要的频率中断,必须在使用调度器之前使用这个函数*/ Void SCH_Init_T2(void) { ??? tByte i; ??? for(i=0; i<SCH_MAX_TASKS;i++) ??? { ??????? SCH_Delete_Task(1); ??? } ??//复位全局错误变量 ? //SCH_Delete_Task()将产生一个错误代码(因为任务队列时空的) ??? Error_code_G = 0; ? //现在设置定时器2 ? //自动重装16位定时器功能,晶振为12M ? //定时器2的精度是0.000001s(1us),要求定时器2溢出为0.001s(1ms) ? //需要1000个定时器时标:65536-1000=64536 =0xFC18 ??? T2CON = 0x04; ??? T2MOD = 0x00; ??? TH2 = 0xFC; ? RCAP2H = 0xFC;//加载定时器2的重装捕获寄存器高位字节 ??? TL2 = 0x18; ? RCAP2L = 0x18; //加载定时器2的重装捕获寄存器低位字节 ? ET2 = 1;//使能定时器2中断 ? TR2 = 1;//启动定时器2 } ? “刷新”函数是调度器的中断服务程序。它由定时器的溢出激活,当刷新函数确定某个任务需要运行时,将这个任务的RunMe标志加1,然后改任务将由调度程序执行。 /*SCH_Updata()??这个调度器的中断服务程序,初始化函数中的定时器设置决定了它的调用频率*/ Void SCH_Updata(void) interrupt INTERRUPT_Timer_2_Overflow { ??? tByte Index; ? TF2 = 0; //必须手工清除?? ? //注意:计算单位为“时标”(不是毫秒) ??? For(Index = 0; Index < SCH_MAX_TASKS ; Index++) ??? { ??? //检测这里是否有任务 ??????? If(SCH_tsaks_G[Index].pTassk) ??????? { ??????????? If(SCH_tasks_G[Index].Delay == 0) ??????????? { ??????? //任务需要运行 ??????? SCH_tasks_G[Index].RunMe +=1;//”RunMe”标志加1 ??????????????? If(SCH_tasks_G[Index].Period) ??????????????? { ???????????? //调度周期性的任务再次运行 ?????????????????????? SCH_tasks_G[Index].Delay = SCH_tasks_G[Index].Period; ????????????????} ??????? } ??????? Else ??????? { ?????? //还没有准备好运行,延时减1 ????????????? SCH_tasks_G[Index].Delay -= 1; ??????? } } } } 8、?“添加任务”函数 ???????正如其名所示,它是用来添加任务到任务队列上的,以保证他们在需要的时候被调用。 /*SCH_Add_Task()??使任务(函数)每隔一定时间间隔或在用户定义的延时之后执行*/ tByte SCH_Add_Task(void (code * pFunction)(),const tWord Delay,const tWord PERIOD) { ????????tByte Index =0; ????????//首先在队列中找到一个空隙(如果有的话) ???????While((SCH_tasks_G[Index].pTask != 0) && (Index < SCH_MAX_TASKS)) ???????{ ??Index++; ???????} ???????//是否已经到达队列的结尾? ????????If(Index == SCH_MAX_TASKS) ???????{ ??//任务队列已满,设置全局错误变量 ??Error_code_G = ERROR_SCH_TOO_MANY_TASKS; ??//同时返回错误代码 ??Return??SCH_MAX_TASKS; ????????} ???????//如果能运行到这里,则说明任务队列中有空间 ???????SCH_tasks_G[Index].pTask = pFunction; ???????SCH_tasks_G[Index].Delay = DELAY; ???????SCH_tasks_G[Index].Period = PERIOD; SCH_tasks_G[Index].RunMe = 0; Return Index; //返回任务的位置(以便以后删除) } 9、??调度程度函数 ???????正如前面看到的,“刷新”函数不执行任何函数任务,需要运行任务由“调度程序”函数激活。 /*SCH_Dispatch_Tasks()???这是“调度程序”。当任务(函数)需要运行时,SCH_Dispatch_Tasks()将运行它。这个函数必须被主循环(重复)调用*/ Void SCH_Dispatch_Tasks(void) { ???????tByte Index; ???????//调度(运行)下一个任务(如果有任务就序) ??????Fro(Index =0;Index < SCH_MAX_TASKS; Index++) ??????{ ??If(SCH_tasks_G[Index].RunMe > 0) ??{ ????????*SCH_tasks_G[Index].pTask();//执行任务 ????????SCH_tasks_G[Index].RunMe -= 1;//复位/减小RunMe标志 ????????//周期性的任务将自动的在此执行,如果是单次任务,将它从队列中删除 ?????????If(SCH_tasks_G[Index].Period == 0) ????????{ ?????????SCH_Delete_Task(Index); ???????????????} } ??????} ??????//报告系统状态 ???????SCH_Report_Status(); ??????//这里调度器进入空闲模式 ??????SCH_Go_To_Sleep(); } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |