




免费预览已结束,剩余41页可下载查看
下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
西南大学本科毕业论文(设计) 目 录摘要3Abstract.40文献综述50.1研究背景50.2研究现状50.3本人的研究思路51引言71.1 任务71.2 多任务71.3 中断71.4 任务切换71.5 时钟节拍81.6 代码的临界段81.7 可重入函数81.8 调度91.9 空闲任务92 KEIL平台上的C语言92.1 从C到C5192.2 C51中代码和数据的存储区域和访问方式92.3 编译模式102.4 指针102.5 C51中的可重入函数112.6 C51函数参数和返回值的传递122.7 C语言和汇编语言混合编程133 以KEIL为开发平台基于AT89C51单片机的多任务调度器设计143.1创建任务163.2任务堆栈初始化173.3任务控制块初始化203.4任务切换系统函数203.5时钟中断处理函数223.6任务延时224具体实例的设计234.1 ampire 128X64lcd显示驱动设计234.2 按键电路设计264.3 贪吃蛇和俄罗斯方块的设计265 结论29附录30参考文献44致谢45基于单片机的多任务程序设计李洋西南大学工程技术学院,重庆 400716摘要:本文介绍了基于AT89C51单片机的多任务程序设计。首先介绍了多任务系统的基本概念,然后设计了以KEIL为开发平台基于AT89C51单片机的多任务调度器,主要实现了任务调度函数、时钟中断处理函数、任务创建函数、任务堆栈初始化函数以及系统延时函数。最后编写了在128X64 LCD上同时显示俄罗斯方块和贪吃蛇游戏的实例,并给出了游戏流程图和硬件电路图。本课题所设计的多任务系统具有结构简单,使用方便灵活的特点。关键词:单片机;多任务;调度器;128X64LCDmultitask program design based on MCULI YangCollege of Engineering and Technology, Southwest University, Chongqing 400716,ChinaAbstract: This article describes the design of multi-tasking program based on AT89C51 microcontroller. First introduces the basic concepts of multi-tasking system, and then design the AT89C51 microcontroller-based task scheduling, task scheduling function to KEIL development platform, the clock interrupt handler, the task creation function, task stack initialization function, and the system delay function. Last written on 128X64 the LCD display an instance of the game of Tetris and Snack, and the flow chart of the game and the hardware circuit. This project designed by the multi-tasking system has a simple structure, easy to use and flexible features.Key Words: MCU; multitasking; scheduling; 128X64LCD0文献综述0.1研究背景单片机系统是嵌入式系统中十分重要的组成部分,在智能控制领域和测试系统中有着非常广泛的应用。通常单片机上的程序为一个无限循环单任务结构,程序从上到下顺序执行,在无限循环过程中采用调用函数的方式来完成相关操作,面对一些短小的实时任务可以通过中断来处理,并且只有当一个任务执行完之后,另一个任务才能被执行,单任务系统具有简单直观和易于控制的特点,于此同时它又具有灵活性差、在复杂系统中难以胜任的缺点。比如当程序延时等待时,CPU将处于空转状态即造成CPU效率低下,而当所面对的问题越来越复杂时此结构极其不方便,不利于编程。为了克服以上缺点将多任务机制引入单片机系统显得十分有必要,这里说的多任务并不是指计算机真的在特定的时间段内同时运行多个任务,而是按照时间片在各个任务之间进行快速的切换,各个任务轮流占用CPU,当切换非常迅速时,将产生从微观上看轮流执行而宏观上并行执行的效果。 0.2研究现状 目前在单片机上实现多任务机制的方式主要还是移植现有商业嵌入式实时操作系统如UC/OS、VxWorks、WinCE等,它们主要完成任务管理、任务的同步和通信、以及对存储资源的管理,同时用户利用实时系统提供的API或系统函数来完成相应的实时处理。采用这种方法在一定程度上能够缓解当前单片机系统以单一任务为核心的窘境,提高CPU的利用率,但是在单片机上移植商业实时操作系统除了要支付数额不小的的专利费用外,由于单片机自身的片上资源十分有限,特别是数据存储区空间和程序存储区空间十分有限,即使对于比较高档的单片机而言,在移植的过程中往往一个系统内核就要占去一大部分程序存储空间,同时在内核运行时也要消耗不少RAM资源,这样就严重限制了用户对于应用程序的开发,对于那些低档的单片机绝大多数是不能运行商业性实时操作系统的。所以,如何在没有操作系统的情况下,采用一种简单的方法在单片机上实现多任务机制,那将是十分有意义的。0.3本人的研究思路对于像AT89C51这种内部资源很有限的单片机而言,将一个完整的实时操作系统移植上去显然是奢侈和不现实的,为了能够在51上实现多任务机制,本文设计了基于时间片调度算法的多任务调度器,它为子任务提供延时和任务切换系统函数。根据多任务机制的特点,系统必须提供一个基准时钟,系统通过基准时钟将CPU运行时间均等分割成若干时间片,多任务调度器将时间片分给各个任务,这个时间分割的任务就交由51单片机中的定时器来完成。当定时中断到来时,调度器会强制剥夺正在运行任务的CPU使用权,然后找到下一个可以运行的任务,并转去执行它。在调度器的设计过程中要用到C语言和汇编语言的混合编程,因为C语言不能直接操作寄存器,所以在进行工作环境保存和恢复的时候必须要用到汇编语言。同时因为KEIL的“可覆盖技术”在保存任务的工作环境时,除了保存硬件堆栈以外还要保存可重入堆栈和可重入堆栈指针。为了验证调度器能否正常工作,本文设计了在128X64LCD上同时运行两个子游戏任务,共8个按键分两组各控制一个游戏,预期的效果是两个游戏互相不干扰且能够同时运行。1引言在上大学之前,一直对操作系统很困惑,在只有一个CPU的系统中,很难想象多个任务是如何同时运行的?其实它的基本思想还是很好理解的,就是让每个任务通过调度程序依次执行一小会儿,当这个“一小会儿”很短的时候从整体上看就像是同时在运行了,所以调度程序的设计就非常关键。本文设计了以KEIL为开发平台基于AT89C51单片机的多任务调度器,它利用51单片机的T0定时器分割CPU时间给各个任务,实现各个任务之间的跳转,多任务调度器实现了任务创建、任务堆栈初始化、时钟中断处理、系统延时等功能。同时设计了以多任务调度器为平台在128X64LCD上显示两个游戏的任务实例。在进行调度器设计之前,本章简要介绍多任务系统设计必须了解的基本概念。1.1 任务一个任务就是一个单独的程序或函数,当启动多任务系统后每个任务可以认为CPU完全属于它自己。多任务程序设计首先要解决的就是如何将问题分割成多个子任务,每个任务都是整个程序的一部分,分别完成不同的功能,拥有自己的CPU寄存器和任务堆栈。一般的,每个任务都是一个无限循环结构,任务在整个执行过程中将分别处于运行,等待,就绪,3种状态,运行状态表示此任务正在使用CPU,等待状态表示此任务正等待某个事件的发生,在此之前它不会得到运行,就绪状态表示此任务一切就绪随时可以得到运行。1.2 多任务在只有一个CPU的系统中,每一时刻只可能有一个任务在运行,而多任务其实是通过CPU在各个任务之间转换和调度来实现的,当转换非常迅速时,从宏观上看就像是同时在运行。1.3 中断中断就是一种硬件机制,CPU通过它感知异步事件的发生,中断一旦被响应,CPU将保存相关的寄存器,同时跳转到中断处理子程序。微处理器中的开中断和关中断指令可以让系统响应或不响应中断,但是在多任务系统中,关中断的时间应尽可能的短。1.4 任务切换任务切换就是将正在运行任务的CPU寄存器中的全部内容保存到此任务的任务堆栈中,然后把下一个将要运行的任务在任务堆栈中的内容重新装入CPU寄存器中,最后跳转到将要运行任务的代码处。任务切换过程增加了程序的额外负担,CPU的寄存器越多,额外负担就越重。任务切换所需时间取决于有多少CPU寄存器需要入栈。1.5 时钟节拍时钟节拍是周期性的中断,可以把它看作是系统心脏的跳动,时钟的节拍式中断可以将任务延时若干个整数时钟节拍,时钟节拍的拍率越快,系统任务切换就越频繁,系统的额外开销就越大。同时任务切换程序在时钟节拍中断发生时根据任务的状态来决定是否要进行任务切换。1.6 代码的临界段代码的临界段表示在运行过程中不能分割的代码段,一旦这部分代码开始执行,系统将不会响应任何中断打入,临界段代码通过在代码首尾处加入关中断和开中断指令来实现不被中断。1.7 可重入函数可重入函数可以被一个以上的任务所调用,而不必担心数据被破坏,可重入函数在任何时候都可以被中断,一段时间后又可以得到运行,同时相应的数据不会丢失。可重入函数要么只使用局部变量,变量保存在cpu寄存器中或堆栈中,要么使用全局变量,在任务切换时就要保存和恢复相应的全局变量。可重入函数范例int max(int *x,int*y) int a; a=*x;if (a=*y) return a;return *y;不可重入函数范例int var;int max(int *x,int*y) var=*x;if (var=*y) return var;return *y;1.8 调度调度就是系统通过某种策略决定该轮到那个任务运行,调度算法主要有时间片轮转法和优先级调度法。时间片轮转法就是赋予每个任务相同的时间片计数,每当时钟节拍中断发生时,系统会将正在运行任务的时间片计数减1,当时间片计数为零时,当前任务会由运行状态变成就绪状态,同时将进行任务切换。优先级调度算法就是系统赋予每个任务各异的数值,数值越小代表优先级越高,在进行任务切换时系统总是找到优先级最高的任务进行任务切换。1.9 空闲任务用户在启动多任务系统之前,必须要创建一个空闲任务,当用户创建的其它任务都处于等待状态时,CPU将会转去执行空闲任务,同时空闲任务将永远处于就绪状态,即它可以随时得到执行。2 KEIL平台上的C语言下面将介绍C51在多任务调度器设计时有关编译器的某些特性。2.1 从C到C51 C51是面向51单片机的C语言,不同的软件厂商提供不同版本的C51和C51编译器,本文介绍的是目前国内广泛使用的由德国KEIL software公司推出的C51,与其它版本相比KEIL C51编译速度快,其代码生成效率很高。与汇编语言相比,C语言具有程序结构清晰,库函数资源丰富的特点,以及在程序的可阅读性,可维护性和可重用性上具有明显的优势。但这里并不是说汇编语言没有了用武之地,后面会谈到这一点。2.2 C51中代码和数据的存储区域和访问方式 51单片机所拥有的内部资源十分有限,但是花样很多,它不仅体现在存储资源的物理分布,也体现在对他们的寻址。51单片机的存储资源可以分为:程序存储器ROM,片内数据存储器RAM,片外数据存储器XRAM,特殊功能寄存器和FAR存储区。这里着重介绍片内数据存储区和片外数据存储区。片内数据存储区可读可写,用于动态数据的保存,访问速度很快。如果用户欲将数据存于其中,可以用以下关键字进行说明:(1)data-指片内RAM的00H-7FH,容量为128B,通过直接寻址进行访问(2)idata-指片内RAM的00H-FFH,容量256B,通过R0或R1间接寻址进行访问(3)bdata-指片内RAM的20H-2FH(位寻址区),总的字节容量为16B片外数据存储区一般通过在系统外扩展RAM芯片来实现,总容量最多为64KB,可以用关键字xdata来声明存储于此的变量,它的范围为整个片外RAM,特别的存储区xdata的数据访问速度是最慢的。2.3 编译模式编译模式就是编译器根据程序的代码规模和数据规模所采取的不同编译方法。针对数据规模即变量总体规模的编译选项有如下3种:(1)small(小模式)所有未说明存储区域的变量和重入堆栈将保存在data区(2)compact(紧凑模式)此模式不常用(3)large(大模式)所有未说明存储区域的变量和重入堆栈将保存在xdata区系统的默认设置为small,即小模式。需要强调的是,不管整个程序采用的哪种编译模式,用户仍可以用关键字data,idata,xdata将程序中的某个或某些变量定义为此存储区域。针对代码规模的编译选项和以上相同,而其默认为large,一般情况下所说的编译模式指的是数据规模。2.4指针指针的本质是地址,它可以指向变量,代码(函数入口),在C51中指针的完整定义如下:所指数据的类型 所指数据所在的存储区 * 指针本身所在的存储区 指针变量名;注意“所指数据所在的存储区”决定了指针的长度,可以分为以下3类:(1)用data,idata等关键字修饰的指针为单字节指针,它要访问的存储空间小于等于256B,所以为一个字节。(2)用code,xdata等关键字修饰的指针为双字节指针,他要访问的存储空间小于等于64KB,所欲为双字节。(3)通用指针(未指定数据所在区域)为三字节指针,通用指针可以指向任何区域,其指针长度为三个字节,第一个字节用来存储数据的存储区类型(idata,data区的代码均为0x00,xdata区的代码为0x01,code区的代码为0xff),其它两个字节用来存储数据的地址(高地址在前低地址在后)。通用指针常用作于函数参数,能够使函数更通用,所指数据的类型要特别注意void,在某些时候很有用。2.5 C51中的可重入函数首先介绍一下keil c51中的 “覆盖技术” (1)局部变量存储在全局RAM空间中(不考虑扩展外部存储器的情况); (2)在编译链接时,即已经完成局部变量的定位; (3)如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖即拥有相同的地址,当从一个函数跳到另一个函数时,前一个函数的局部变量所保存的数据将被破坏。 由于上述原因,在Keil C51环境中,纯粹的函数如果不加处理(如增加一个仿真堆栈),那么它是无法重入的。为了使函数成为可重入函数,C51编译器为函数提供了一个扩展关键字reentrant,当需要将一个函数声明为可重入函数时,只要在函数后面加上关键字reentrant即可,同时该函数不能带位参数和不能定义位局部变量。与不可重入函数的参数传递和局部变量的存储分配方式不同,C51编译器为可重入函数生成一个仿真堆栈(相对于系统堆栈或是硬件堆栈来说),通过这个仿真堆栈来完成参数传递和存放局部变量。仿真堆栈以全局变量?C_IBP、?C_PBP和?C_XBP作为堆栈指针(系统堆栈栈顶指针为SP),这些变量定义在DATA地址空间,并且可在文件startup.a51中进行初始化。根据编译时采用的存储器模式,仿真堆栈区可位于内部(IDATA)或外部(PDATA或XDATA)存储器中。同时此处要特别说明一点,C51中的硬件堆栈指针(即SP指向的堆栈)的增长方向是向上的,而仿真堆栈指针的增长方向是向下的,其首地址由系统给定,但可在程序中修改,入栈时指针依次递减。对于声明为可重入的函数,编译器会按照其编译模式在相应的存储区中替字节型局部变量和字节型参数开辟一个“模拟堆栈”,从而对其进行入栈和出栈处理。2.6 C51函数参数和返回值的传递在多任务系统中,函数参数传递规则尤为重要,特别是在任务堆栈初始化时。C51函数的完整定义如下:alien 返回类型 函数名(参数列表)编译属性 重入属性中断属性寄存器组属性重入属性说明该函数是否按“可重入函数”来处理(后面会着重说明),用reentrant关键字说明该函数为可重入,缺省则不可重入。中断属性说明该函数是否为“中断函数”,用“interrupt 中断编号”声明该函数为中断函数,缺省则不为中断函数。在标准C语言中,函数参数和返回值的传递是通过系统堆栈(先进先出的数据结构)来实现的,虽然51单片机也提供堆栈以及堆栈指针SP和入栈出栈指令PUSH/POP,但是由于它的内部数据存储区非常小一般只有128B,所欲C51中函数和返回值的传递并没有直接使用这一堆栈,而是采用其它对存储资源更节约的方法。表1 寄存器参数传递Tab.1 register parameter passing参数长度第一个参数第二个参数第三个参数1B(char或单字节指针)R7R5R32B(int或双字节指针)R6(高字节),R7R4(高字节),R5R2高字节),R33B(通用指针)R3(类型码),R2(高字节),R1 无 无4B(long)R4(高字节)-R7 无 无4B(float)R4(数符阶码)-R7(尾数低) 无 无在C51中函数参数传递的总体思想是:尽可能通过寄存器进行参数传递,万不得已时也采用编译器分配的固定存储位置进行传递,对于可重入函数则通过专门的模拟堆栈来进行传递。下面分别讨论上述3种情况。(1)利用工作寄存器组进行参数传递C51编译器将首先采用此方法传递参数,而利用工作寄存器组R0R7最多可传递3个参数,但是不包括位数据。至于位数据和寄存器无法安排的参数,就必须通过编译器分配固定存储地址进行传递。对于位数据,需要在位存储区中分配固定地址来进行参数传递;对于字节型参数编译器将通过函数的编译模式在相应的存储区中分配固定地址进行参数传递。默认情况下,系统通过工作寄存器组0进行参数传递。(2)通过编译器分配的固定存储地址进行参数传递如果不适用寄存器来传递参数,或者参数太多,或者根本无法用寄存器进行传递(bit类型),则这些参数就得由编译器分配固定的地址进行参数传递。有一点需注意,如果函数的第一个参数为bit类型,那么其后的参数都不在由寄存器来传递,因此bit类型通常放在参数表的最后面。(3)通过“可重入堆栈”进行参数传递C51只允许最多只有一个返回值,可以没有。其中,位参数将通过进位标志CF返回,而字节型数据的返回是通过工作寄存器的组合来实现。表2 返回值传递Tab.2 return value passing返回类型传递返回值的载体Bit进位标志CF1B(char或单字节指针)R72B(int或双字节指针)R6(高字节),R73B(通用指针)R3(类型),R2,R14B(long)R4(高字节)-R74B(float)R4(数符阶码)-R7(尾数低)2.7 C语言和汇编语言混合编程虽然C语言在编写单片机程序时有诸多优势,但是在某些特殊场合,如程序对时序要求非常严格或需要直接操作寄存器的时候,就不得不考虑汇编语言,但是汇编语言的阅读和编写都比较困难,而采用两种语言的混合编程将是一个双赢的选择。混合编程的总体思想是用C语言编写程序主体,用汇编语言来编写具体的某个功能模块,下面主要介绍编写具有C51接口的汇编模块。对于汇编模块首先将其处理成一个单独的汇编源代码文件(扩展名为.asm),然后在C51程序中用关键字extern将该模块声明为外部函数。所以主要的工作是编写汇编模块,具体有一下两种方法:(1)编写一个有函数头无函数体或者函数体很简单的C语言函数,把它放置在单独的文件中,通过生成资源文件(.src)来获得相应的汇编模块结构,然后在这个文件中进行汇编语言的编写,最后将资源文件的扩展名改为.asm.(2)编写功能完善的C语言函数,同样将其放置在单独的文件中,然后编译生成资源文件(.src),最后将其扩展名改为.asm3 以KEIL为开发平台基于AT89C51单片机的多任务调度器设计 多任务调度器是多任务系统的核心,他就像是一位管家,管理CPU在各个任务(本文中的任务与函数在某种程度上是等价的)之间进行跳转以及为用户创建的任务提供系统函数。用户在设计各个任务时只需调用它提供的系统函数,而不用关心调度器的原理和运行情况,就好像它不存在一样。调度器在任务需要延时和系统时钟中断到来时将进行任务切换 调度器为用户提供的系统函数如图1所示: 多任务调度器系统函数创建任务函数任务堆栈初始化函数任务延时函数任务切换函数时钟中断处理函数时钟中断重置函数启动多任务系统函数 图 1 系统函数 Fig.1 system function系统函数(C函数)都被定义为可重入函数,因为他们都可能在任务中被调用即可能被打断。 多任务系统结构图如图2所示:任务1任务2任务3多任务调度器时钟中断到来调用延时函数图2 多任务结构图Fig.2 multitasking structure chart多任务系统程序设计的主要结构如下void main()Initial_System();Task_Create(task0,&Task_Stack00,0);Task_Create(task1,&Task_Stack10,1);Task_Create(task2,&EMPTYTASK0,3);Start_System();Task0,task1,task2是任务函数名并且它们都被声明为可重入函数。用户在针对不同问题编程时只需编写task0,task1,Initial_System()和task2,而task2是空闲任务它只需进行任务切换而task0和task1不能互相调用,它们是两个相对独立的函数。task0和task1的结构如下:void task0() reentrantWhile(1)添加代码Void task1() reentrant While(2) 添加代码 当函数task0正在运行时,因为它自身是无限循环结构,通常情况下task1是不会得到运行的。为了能让task1也得到运行,调度器首先在task0运行前开启时钟中断,并为task0和task1开辟各自的任务堆栈空间,当时钟中断到来时,系统会去执行时钟中断处理函数,而时钟中断处理函数将task0任务的工作环境保存到task0的任务堆栈中,然后恢复task1的任务堆栈,因为任务堆栈中保存了任务程序代码的地址,当中断处理函数返回时,系统会自动跳到task1代码中,同时task0通过调用调度器的系统延时函数,task1也能得到运行,因为系统延时函数调用了任务切换函数。 3.1创建任务对于每一个任务(函数),它都有一套自己的寄存器,局部变量和仿真堆栈,当进行任务切换时,这些内容都必须保护起来,等到下次运行时再恢复,而保护在这里的意思就是将它们放在一个安全的地方,这个地方就是任务堆栈。为了使系统能够找到任务,必须在创建任务时定义一种全局数据结构,它将保存任务的状态(运行状态,任务堆栈地址,任务延时计数),本文中此结构就是任务控制块TCB,它的定义如下:struct TCBuchar xdata *Taskstack_Bottom;uchar T_state;uint T_delay;Taskstack_Bottom为任务堆栈底指针,T_state为任务状态变量,T_delay为任务延时计数。首先根据任务数创建一个全局任务控制块数组和全局任务堆栈数组,然后在创建具体任务时对其进行初始化。 所以创建任务就是初始化任务堆栈和初始化任务控制块。创建任务函数定义如下:void Task_Create(void (code *pointer)(void),uchar xdata *stack,uchar Taskid) reentrantpointer为函数指针,stack为任务堆栈栈底指针,Taskid为任务id创建任务函数流程图如图3所示:开始结束任务堆栈初始化任务控制块初始化图3 创建任务函数流程图Fig.3 create task function flow chart3.2 任务堆栈初始化 在启动多任务系统之前,创建的各个任务的任务堆栈看起来应该是像刚发生过中断一样,也就通过任务堆栈系统能够找到该任务所对应的代码。在C51中由于内部ram资源太小只有128B,因此本文将全局变量、任务堆栈、仿真堆栈都放在外部数据存储区中,在任务创建过程中,对任务堆栈初始化是关键点。由于每一个任务都被声明为可重入函数,系统会将任务的局部变量和参数放置在仿真堆栈中,所以保存仿真堆栈和仿真堆栈指针也是必须的,C51系统仿真堆栈指针有3种类型,由于本文将仿真堆栈放置在任务堆栈的顶部,而任务堆栈被安排在外部数据存储区中,所以此时的仿真堆栈指针为C_XBP(指向仿真堆栈的栈底),为双字节指针。任务堆栈要保存的内容就是任务运行时所有寄存器的值,仿真堆栈的内容和仿真堆栈指针,图4较直观的说明了任务堆栈的整体结构。硬件堆栈长度硬件堆栈C_XBPC_XBP+1仿真堆栈空间任务堆栈顶部剩余空间仿真堆栈底部图4 任务堆栈结构Fig.4 task stack structure硬件堆栈为SP所指向的堆栈C_BXP和C_BXP+1指向仿真堆栈底部任务堆栈初始化设计如图5所示:17(uint)p&0xFF (uint)(s+LENGTH)8(uint)p8 ACCB PSW psw DPHDPL从此处往上八格为R0-R7(uint)(s+LENGTH)&0XFF图5 初始化后的任务堆栈Fig.5 the task stack after initial因为任务堆栈初始化关系到系统能否正常启动,所以特别将该函数代码写出来,如下所示:void Task_Initial(void (code *p)(void),uchar xdata *s) reentrantuchar xdata *stack=s;*stack+=17;*stack+=(uint)p&0xFF;*stack+=(uint)p8;*stack+=0x11;/ACC*stack+=0x22;/B*stack+=0x00;/PSW*stack+=0x00;/DPH*stack+=0x00;/DPL*stack+=0x00;/R0*stack+=0x01;/R1*stack+=0x02;/R2*stack+=0x03;/R3*stack+=0x04;/R4*stack+=0x05;/R5*stack+=0x06;/R6*stack+=0x07;/R7*stack+=(uint)(s+LENGTH)8;/?C_XBP高字节*stack+=(uint)(s+LENGTH)&0XFF;/?C_XBP+1其中p为函数指针(指向函数首部),s为任务堆栈首地址,LENGTH为任务堆栈总长度,17为硬件堆栈长度。对于任务堆栈初始化最重要的就是设置好任务堆栈长度,函数指针,和仿真堆栈指针。3.3任务控制块初始化任务控制块初始化就是对结构图成员变量Taskstack_Bottom,T_state和T_delay初始化。Taskstack_Bottom指向任务堆栈栈底;任务有ready和delay状态,初始化时将T_state设置为ready就绪态;T_delay赋值0即任务没有延时。3.4任务切换系统函数当任务调用任务切换函数或调用系统延时函数后,系统将转去执行其它任务,即此任务主动放弃CPU使用权。对于多任务系统任务切换函数是相当重要的,它仅次于时钟中断处理函数。任务切换就是将当前任务的硬件堆栈和仿真堆栈指针入任务堆栈,同时将下一个将要运行的任务的硬件堆栈和仿真堆栈指针出任务堆栈。因为进行出栈入栈要直接操作硬件寄存器,而C51无法直接操作系统寄存器,所以必须用汇编语言编写任务切换函数,所以编写时要特别注意相关数据的传递,和指针的变化情况。这里就要用到前面介绍的混合编程,在C文件中用extern声明该函数即可直接调用。任务切换函数流程图如图6所示:开始重新设置系统节拍时钟找到下次要运行的任务开中断,中断返回关中断寄存器和仿真堆栈指针入硬件堆栈计算硬件堆栈长度存入T由CURRENT找到当前任务的任务堆栈将T传送至任务堆栈底将硬件堆栈复制到任务堆栈T之后将该任务的TCB指针赋给CURRENT由CURRENT找到下一个任务的堆栈出栈(其顺序与入栈相反)图6 任务切换函数流程图Fig.6 task switch function flow chart对于任务切换,它根据全局任务控制块结构体指针变量CURRENT(在启动多任务系统时被初始化),找到正在运行任务的任务控制块和即将运行任务的任务控制块,在汇编文件中声明其为外部变量后,就可以根据它们找到正在运行任务的任务堆栈和将要被运行的任务的任务堆栈(此处需要特别注意,通过CURRENT和NEXT可以找到Taskstack_Bottom),然后进行入栈和出栈操作,完成任务切换。在进行任务切换时首先是将中断关闭,切换完成后再开中断,这样做是为了保证在切换时不被外部打扰,防止程序进入不可预知状态。任务切换函数为switchtask(),任务可直接调用。3.5 时钟中断处理函数由于本文采用的是时间片调度算法,所以时钟中断的处理就非常重要,同样它也是用汇编语言编写。当时钟中断到来时,系统自动跳转到时钟中断处理函数,处理过程首先是将当前任务硬件堆栈和仿真堆栈指针入栈,然后将任务控制块数组中处于等待状态任务的等待时间计数减一,如果该值由1变成0则将该任务的状态设置为ready(就绪态),然后找到下一个将要运行的任务,恢复该任务寄存器并执行该任务,它的功能与任务切换函数类似。特别说明,51单片机中的中断处理函数不能被其它函数调用,也没有参数传递,它只在发生中断时由系统调用。其流程图如图7所示:开始当前任务入栈各等待任务时间计数减1找到下一个将要运行的任务将找到的任务的寄存器出中断返回RETI图 7 时钟中断处理函数流程图Fig.7 timer interrupt processing flow chart3.6任务延时当前运行的任务除了在时钟到来时会放弃CPU使用权之外,也可以利用延时函数主动放弃CPU,正是因为这样才使得系统能在某个任务需要等待延时时,可以继续执行其它的任务,其实现主要是调用了前面介绍的任务切换函数switchtask(),这个特性极大的提升了系统的性能。对于延时多少是根据系统节拍来设定的,也就是时钟中断。当一个任务调用了延时函数,并且延时T个单位,它的含义是执行完延时函数后该任务将处于waiting(等待状态),同时每一次时钟中断到来时T减1,直到T减为0时,该任务由等待状态变成就绪状态。任务延时函数定义为void Delay(uint count) 其中的count为延时计数,它为时钟节拍的整数倍。其流程图如图8所示:开始该任务由就绪变为等待等待计数赋值调用任务切换函数结束图8 延时函数流程图Fig.8 delay function flow chart至此,多任务调度器的主要函数已介绍完。4具体实例的设计实例设计就是根据多任务调度器提供的系统函数进行设计。本实例为在128X64lcd上显示两个游戏,左半屏显示俄罗斯方块,右半屏显示贪吃蛇,有8个按键分两组各控制一个游戏。4.1 ampire 128X64lcd显示驱动设计对于任何一种涉及到显示的驱动设计,其基本思想就是点亮显示终端的一个点,然后灭掉该点,同时显示任意n个点,然后灭掉n个点。只要完成以上功能后,显示任何图形就不是问题。本文仿真使用的是proteus自带的ampire128X64lcd,它为用户提供了显示缓冲区,如果在缓冲区中某位为1则会点亮该位所对应lcd上的点,该位为0则灭掉相应的点。本文所写lcd驱动为外部提供两个基本函数,即是在显示终端上点亮任意一个点,然后是灭掉该点。X行地址范围为0-63(分成8页),Y列地址范围为0-127.也就是通过X和Y找到其所对应的点。其显示缓冲区结构如图9所示:0 1 2 3 4 5 6 -60 61 62 63 YX=0X=1X=2X=3X=4X=5X=6X=7DB0 | PAGE 0DB7DB0 | PAGE 1DB7DB0 | PAGE 2DB7DB0 | PAGE 3DB7DB0 | PAGE 4DB7 DB0 | PAGE 5DB7DB0 | PAGE 6DB7DB0 | PAGE 7DB7图9 显示缓冲区Fig.9 display buffer写点函数就是通过lcd提供的操作指令,对其显示缓冲区进行写入和读取操作。根据写点函数又可以写一个方块函数。写点函数流程图如图10所示:开始输入X和YX除以8商存m余存n由m(页)和Y(列)读出该单元存T将0x01左移n个单元后存n将T与n进行或运算后存T由m和Y将T写入显示缓存结束图10 写点函数流程图Fig.10 writint point flow chart显示电路如图11所示: 图11 显示电路 Fig.11 showing circuit 电路图中lcd的数据端口被连接在51单片机的P0口,CS1CS2片选口连接在P24和P23口。4.2 按键电路设计本例中的按键通过7个与门连接到外部中断1P32口,上面四个按键按键控制俄罗斯方块,下面四个控制贪吃蛇。当有任何一个按键被按下时,将会触发中断。按键电路原理如图12所示: 图 12 按键电路 Fig.12 button circuit4.3 贪吃蛇和俄罗斯方块的设计对于贪吃蛇它的主要关键点就是蛇身的增长和身体整体运动的设计,利用一个全局数组记录蛇身的具体位置(数组要大一点),然后根据头部的移动来改变其它部位的位置。同时要记录蛇身长度,当蛇吃到食物后,蛇身长度加1.蛇身运动原理是先画蛇头再将蛇尾擦除,这样只需画两个单位,不用整个画蛇身和擦蛇身了。正如前面介绍的,贪吃蛇游戏的主体应该写在task0的while()循环体内,俄罗斯方块应写在task0的while()循环体内。对于需要延时的地方调用任务调度器提供的系统函数Delay(),而在调用显示部分的函数时,最好关中断,调用完之后再开中断。贪吃蛇原理如图13所示:有按键否游戏开始有否根据按键改变蛇头位置和方向变量蛇身位置从后到前依次改变画蛇头清蛇尾蛇头按照原方向改变一个单位吃到食物否否有蛇身长度加1产生新的食物撞壁或撞自己否有游戏结束否图13 贪吃蛇流程图Fig.13 snack flow chart对于俄罗斯方块,其难点主要是下落时遇到障碍物时要停止,以及当一行被填满时消层的问题。对于消层,主要的思路就是检测128X64lcd内部显示缓冲区在该行是否全为1。俄罗斯方块游戏原理如图14所示:游戏开始按键否有变形否否由方向变量改变方块位置由按键改变方向变量否清除方块画方块清方块变形触底否否消层有触顶有游戏结束否下一个方块图14 俄罗斯方块流程图Fig.14 Tetris flow chart对于控制部分采用下降沿触发中断,两组键盘共用一个外部中断,即它们的中断处理程序相同,当其中一组键盘中的键被按下之后,中断被触发,中断处理程序检测P1口8个位的电平情况,为什么是八个,是因为当中断触发后关闭中断,若有另外一组键盘在此时也被按下将不会响应中断,为了不使按键丢失所以检测八个位的电平。5 结论本文在AT89C51上实现了一个简单的任务调度程序,编写了任务创建函数、堆栈初始化函数、时钟中断处理函数、系统延时函数和任务切换函数,之后编写了两个测试任务,经测试两个任务运行良好,这说明在51单片机上进行多任务程序设计是可行的。但是当创建的任务增加时,显示画面会有明显的延时滞后,并且任务在调用显示模块时中断被关闭,此时时钟中断将无法响应。同时本设计只涉及到多任务系统中的任务切换,而没有讨论任务之间的通信、同步以及存储器管理。通过这次设计,我对C51和汇编有了更深的认识,成功实现多任务调度程序坚定了我进一步学习操作系统的信心。同时在设计过程中也暴露出自己很多问题,比如在程序调试不通或仿真结果与预期相悖时,很容易急躁和不知所措,还有就是对于编写文字性叙述部分总有一种有话说不出或不知道怎么说的感觉,这些都是自己要克服的。附录:硬件电路图如下:仿真结果如下:程序代码如下:46头文件:#include#include#include#include#include#define uchar unsigned char#define uint unsigned int#define TASK_NUM 3#define LENGTH 64#define running 0#define ready 1#define waiting 2#define interrupted 3#define DATA P0#define left 0#define right 1#define up 2#define down 3#define transform 4#define closeinterrupt() EA=0#define openinterrupt() EA=1sbit EN=P20;sbit RW=P21;sbit RS=P22;sbit CS1=P23;sbit CS2=P24;sbit LED=P30;void WriteDisplayData(uchar var) ;uchar ReadDisplayData() ;void DrawPoint(uchar x,uchar y) ;void ErasePoint(uchar x,uchar y) ;void DrawBlock(uchar x,uchar y) ;vo
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025浙江温州市人才资源开发有限公司招聘2人考试备考题库及答案解析
- 2025四川内江市第二人民医院考核招聘工作人员23人备考考试题库附答案解析
- 2025年合肥某事业单位面向社会招聘驾驶员1人考试参考试题及答案解析
- 2025年河北沧州高校毕业生临时公益性岗位招聘备考考试题库附答案解析
- 2025福建福州市鼓楼区水部股份经合社招聘1人备考考试题库附答案解析
- 2025贵州黔东南州黄平县选聘城市社区工作者工作8人备考考试题库附答案解析
- 2025年下半年陕西汉中市事业单位招聘262人备考考试题库附答案解析
- 2025海南东方市第二次招聘事业编制工作人员80人备考考试题库附答案解析
- 2025甘肃省商务厅厅属事业单位招聘工作人员5人备考考试题库附答案解析
- 2025江苏苏州市卫生健康委员会直属事业单位招聘卫生专业技术人员29人备考考试题库附答案解析
- 体育教案睡眠与健康课件
- 2025年《数字孪生与虚拟调试技术应用》课程标准
- 生物●安徽卷丨2024年安徽省普通高中学业水平选择性考试生物试卷及答案
- 蓝牙耳机委托加工协议书
- T/CGTA 03-2023大豆油加工质量安全技术规范
- 反诈知识进校园主题团课
- 雷雨剧本文件完整版电子书下载
- 土建施工方案范本
- 人教版小学一年级上册数学第一单元测试题
- T-SXPFS 0004-2024 山西省银行业金融机构转型贷款实施指引(试行)
- 老年透析护理常规课件
评论
0/150
提交评论