引言
汇编语言是一种用助记符表示的面向机器的程序设计语言。助记符使得原来的机器语言变得相对较为直观、易懂、易用,并且汇编语言与机器语言具有一一对应的关系,因此它继承了机器语言直接、快速、高效的特点,是一种底层语言。但是汇编语言的劣势也十分明显,如对于编写较大的程序需要考虑诸多硬件存储器的分配以及中断程序的处理等非常细节的问题,否则容易出现寄存器冲突,从而导致程序崩溃。为了简化汇编语言的编写过程,本文提出了一种结构化的汇编编程思路,并以基于AT89C51芯片(以下对汇编语言的讨论针对51单片机系统)的俄罗斯方块游戏为例,来展现在51单片机中汇编语言结构化编写的优势。
1 汇编语言的结构化设计思想
1.1 变量定义
汇编语言中无需变量的声明,因为汇编语言是直接对具体的内存单元操作,而每个单元有16进制的地址码,因此所有变量都可人为地由该地址码表示。但是汇编语言提供了EQU伪指令,可以将特定的内存空间标记为特定的名称,这就为变量定义提供了可能。而使用EQU伪指令的好处就是将抽象的物理内存分化为具体的变量名,避免了内存冲突,同时又增加了程序可读性。
1.2 子函数设计
子函数对程序结构化的作用是其可简化主函数的编写,使得程序主干的编写思路清晰化,而一些复杂的算法与功能则放在一层层的子函数中实现。但是,汇编语言在调用子函数的过程中如果处理不当,极其容易造成堆栈错误、内存冲突等问题。本文提出了一种优化的子函数设计方案。
图1 工作寄存器区临时变量存放层次结构
首先,把51单片机内存的4组工作寄存器区(00H~1FH),用作子函数的临时变量存放区,如图1所示;另一部分是用户区(20H~7FH),用作主函数变量与堆栈区域。其次,4组工作寄存器区的每一组用作同一层次的子函数的临时变量,低层次的子函数只能被高层次的子函数调用,同一层次的子函数不允许相互嵌套调用。所有的子函数在编写时需要声明其使用的工作寄存器组编号,以防止冲突。在函数嵌套时,用RS1、RS0两个标志位的切换来实现工作寄存器组的切换,如此就可以方便可靠地实现子函数的调用和嵌套。
1.3 中断函数设计
与顺序设计的程序不同,51系列单片机还需考虑中断函数的设计。51单片机的中断有外部中断、定时器中断、串口中断等。中断程序在中断源触发后即起作用,换句话说,中断程序可能随时中止主程序的运行。如果在这个时候,中断函数与主程序中的主函数或子函数享用相同的临时变量,那么在中断发生时,这些临时变量就会被改写,从而导致内存冲突。因此,中断函数的临时变量体系应与主程序有别,以下是三种可选方案:
第一种方案是将工作寄存器区分为两类,一类用作主程序函数的临时变量,另一类用作中断函数的临时变量。这种方案中,单片机工作寄存器的组数对函数设计起限制作用。
第二种方案允许中断函数与主程序的子函数共用工作寄存器区,但是代价是在调用中断时必须保护和恢复现场,即在中断函数的开始、结尾必须将中断函数及其子函数使用的工作寄存器的数据压入、弹出堆栈,从而保证中断前后主程序函数临时变量的一致。
第三种方法是通过设置标志变量,避免在中断函数中插入子函数。在中断程序中,根据状态修改标志变量后即返回主函数。在主函数中,判断相应的中断标志执行相应的子程序。这种编程方法的优点是中断程序十分简单,能在很短的时间内完成,减少了中断出错的可能性;其缺点是中断执行的反应速度会有所降低,因为主函数对中断标志位一定是滞后于中断发生的,且如果主函数的结构是大循环型的,那么一次循环中只能处理若干次中断(大多数情况往往只为一次),这种编程方法对需要高频中断的功能是不合适的。
2 俄罗斯方块的软件实现方法
俄罗斯方块是一款风靡全世界的十分经典的休闲游戏。本文在基于MCS51单片机和具有矩阵式按键、双色LED点阵和数码管等功能模块的实验系统上,采用以上所述的汇编语言结构化的编程思想,编写能够运用按键操作游戏、将游戏图像显示于16×8的LED双色点阵上,将玩家分数显示在静态数码管上、并伴随游戏产生音乐效果的俄罗斯方块游戏。
2.1 功能分析
俄罗斯方块游戏的规则很简单,屏幕上方随机产生不同形状的方块并以一定速度落下,玩家可以控制方块的左右位置以及旋转方块,巧妙地布置安排使方块落下后充分利用屏幕空间。每当屏幕的一整行被方块排满时,该行方块从屏幕上消失,其上的方块依次下降一行,玩家获得一定的分数。当方块堆积达到屏幕顶端的时候,游戏结束。本游戏的主要功能包括:
① 开机进入开机欢迎界面。按任意键进入游戏难度选择界面,难度选择后,按确定键进入游戏界面。
② 每4个格点(双色LED)组成一个图形,游戏中共有7种方块图形。屏幕上端随机产生一种方块图形,并按着一定的时间周期向下移动。当前一个方块无法再次移动时,产生下一个方块。
③ 当方块向下移动时,玩家可以通过上、下、左、右4个按键分别调整方块的角度、加速方块的下移速度、向左移动方块1格、向右移动方块1格。
④ 游戏中,玩家可以按停止键,选择停止游戏并返回到开始界面;或者是按暂停键,暂停游戏;再次按暂停键时,游戏继续进行。
⑤ 当一个方块无法继续向下移动时,判断此时能否将屏幕的一行或多行完全填满。若能则将这些行的方块闪烁后消除,玩家获得相应的分数(每消去一行,玩家得到10分),并显示玩家总的分数;而未被消除的方块则会一直积累。随着玩家分数的增加,游戏的难度会增加,方块下落的速度会加快。
⑥ 如果未被消除的方块堆放的高度很高,达到屏幕顶端以至无法产生新方块,则游戏结束,返回到开始欢迎界面。
⑦ 游戏开始、结束、按键以及消行时会产生一定的音乐效果。
2.2 变量定义与子函数模块
根据结构化的编程思想,程序中需要对变量进行空间分配,并根据其功能进行命名,以增加程序的可读性,使得后期的调试工作更加方便。变量定义的具体内容包括单片机及功能模块所需的引脚命名、功能模块所需的常量命名、单片机用户储存空间的预分配和命名
首先列出需要用到的所有引脚和变量,并将总程序空间分块并合理分配每一块的大小。本程序将RAM空间划分为即时调用区、固定区和堆栈区。即时调用区为通用寄存器组,地址00H~1FH;固定区为用户存储区的20H~5FH;堆栈区为60H开始的剩余空间。
对于函数的调用方法、数据的应用输出、寄存器工作组的使用等关系到程序储存空间的细节问题,前文已经作出论述。子函数与主函数的数据接口采用C51的4个工作寄存器组存放,在子函数调用时将临时数据存入相应的工作寄存器进行处理,执行完毕后将数据返回上一级函数。
2.3 中断的设计
中断的使用和中断程序的设计是单片机应用的难点之一。
首先,要根据程序功能设计中断的逻辑流程。80C51单片机中有两个定时器/计数器T0、T1。程序要求同时实现定时扫描显示以及播放音乐的功能(音乐功能通过一条口线和蜂鸣器实现),所以要同时使用T0和T1的中断:T0控制显示模块,中断间隔时间较长,优先级较低;T1控制音乐模块,中断间隔较短,中断中执行的代码也较短,优先级较高。
然后,根据中断的特点,合理设计中断的使用规则。本程序中设计使用双中断,这使得程序的主体逻辑流程变得简单,但同时也使得中断函数本身的设计,尤其是即时数据的空间分配和断点的保护等,变得十分重要。为了使函数简单可靠,程序中允许中断函数与主程序的其他函数共用工作寄存器区,但是在每次调用中断函数时都需要全面保护和恢复现场。音乐中断因为不涉及工作寄存器,所以只需要保护、恢复基本的数据就可以。
2.4 主函数流程和伪代码描述
根据俄罗斯方块游戏的功能以及结构化的汇编设计方法,主函数流程如图2所示。
图2 主函数流程
伪代码如下:
欢迎界面;
难度选择;
数据初始化;
主循环 {
难度设置;
产生新方块;
判断新方块是否已经无法移动,如果无法移动则游戏结束;
检测按键,如果有按键则判断方块是否可执行相应的动作 {
如果可以执行,则执行;
如果不可以执行,则保持不动;
}
判断方块是否已经无法继续向下移动 {
如果可以移动则循环继续检测按键;
如果不可以移动则判断能否消行,如果能则消行、得分;
判断分数是否需要加快游戏速度;
}
}
中断1:定时方块下落一格;
中断2:产生相应的音乐效果;
2.5 实验测试与结果
开机测试与结果:开机全速运行程序,LED双色显示器上显示的“Hello”欢迎界面如图3所示。按任意键可进入难度选择界面。
难度选择界面测试:进入难度选择界面,双色LED上显示红色的“123”字样,并在‘3’下有一绿色小圆点,如图4所示。按数字键1、2、3键,用于选择相应的等级,同时绿色小圆点会移到相应等级的下方;按“enter”键结束难度选择,进入游戏界面。
图3 开机界面 图4 难度选择界面
游戏流程测试:进入游戏主界面。LED上方产生方块,不操作时方块可以自由下落,此时方块为红色。按动‘7’键,方块改变其角度,再次按动,角度可以继续改变;按动‘6’键,方块下移一格,并且不影响其自由下落,表现为方块下移速度加快;按动‘2’键,方块向左移动一位,当移动到左边边界时,无法再移动;按动‘A’键,方块向右移动一位,当移动到右边边界时,无法再移动。方块移动到无法再向下时,颜色变为绿色,并且在LED上方产生新的方块,如图5所示。满足消行条件时,该行闪烁后即消失,同时在数码管上显示分数增加10分;多行消除时,为每行依次消除,增加相应分数,如图6所示。随着分数的增加,方块的自由下落速度加快。按动‘8’键,游戏暂停,此时按动除‘8’键的其他键均无效。当再次按动‘8’,游戏继续进行。游戏进入、按键、消行时,均有音乐产生,并且两者同步。
游戏结束测试:按‘C’键或者游戏结束时,屏幕所有LED灯从底端到上端逐行点亮,再从上端到底端逐行熄灭,此时伴随着音乐产生,然后进入欢迎界面。
结语
本文以俄罗斯方块游戏的程序编写为例子,提出、分析并具体说明了在功能复杂的汇编程序设计过程中,采用的结构化编程思路。并从变量定义、子函数设计、中断函数设计等方面探讨了汇编语言结构化设计的具体方法,从而有效解决了汇编程序编写中易发生的寄存器内存冲突等问题。这种汇编语言编程的结构化思维,对于运用编写汇编大程序具有重要的指导和借鉴作用。