MCS-51系列单片机在目前和今后的相当一段时间内都将是我国的单片机主流机种。但在早期的开发过程中,程序员不得不从深奥的汇编语言开始摸索,同时要求开发人员对硬件亦有相当的子解。相比而言,专用8051系列单片机设计的Franklin C51语言是一种通用的高级结构化的程序设计语言。入门容易,程序可读性强,调试、移植都很方便,故开发效率高,尤其在数值运算处理方便具有很大的优势(这正是ASM51汇编语言的薄弱环节)。不过,C语言虽然也可对计算机的硬件系统进行操作,但在处理特殊I/O口和中断向量方面,不如汇编那样直接、有效。因而,在效率为重的今天,将ASM51汇编与C51语言结合起来,充分发挥各自的优势,无疑是单片机开发人员的最佳选择。
1 、汇编与C51的混合编程
一般的做法都是利用C51上手容易、便于理解的优势来编写主程序,在C51语言不便处理或者效率比较低时调用汇编函数。考虑到MCS-51(尤其是 8031内部的资源配置情况:可用的RAM不到256字节,5个固定地址的有限中断源,4个8位并口中实际可作I/O口的只有P1口。因而要求开发都对单片机的内部结构有清楚的了解,并尽可能地统筹安排这些资源。事实也证明,不理解汇编语言是很难写出高效程序的。故笔者的观点是利用汇编语言对I/O接口、中断向量及程序空间分配的茂大优势,让程序员对MCS-51内的第一个字节甚至是每一比特(可位寻址的空间)全部进行统筹安排,设计好各个程序模块,包括 I/O口地址和中断向量地址的处理;同时在具体的数据处理、通信等不需要过多与硬件直接打交道的程序模块中,充分利用C51语言强大高效的编程能力。
最后的关键是如何让汇编模块能正确识别C51函数并调用它来完成相应的功能。ASM51汇编与C51语言之间的调用约定并不简单,而且各种编译器使用的约定不尽相同,甚至还依赖于程序所选择的大、中、小存储模式。通常每个需传递的参数按调用顺序和类型分别由约定的寄存器来传递。如果参数过多或者无足够寄存器可用时,参数的传递将在固定持存储器区域内进行,相同类型的参数共享一个参数传递段,按参数调用顺序递增其存放地址,返回值也由约定的寄存器或地址段返回。由此可见程序调用的效率必将受到接口复杂度的影响。尽管目前的单片机仿真器已经提供了标准接口的全自动转换功能,减少了接口工作量,但在程序的调试及移植中,如果程序员不了解这些接口的各种约定,将对出现的错误不知所措。比如返回值不止一个时,编译器自己就无法正确完成接口配置。这里力荐一种简洁有效的调用方法——无参数化调用。
2 、ASM51无参数化调用C51函数的实现原理
所谓的无参数化调用是指让C51子函数不带任何参数,这样就可以从根本上避开调用参数的传递和返回值的安排等繁琐易出错的问题,只需要简单地在汇编语言开头说明一下外部C51子函数(“EXTRN code(<C51模块名称>)”)。至于C51函数中需要使用的外部参数值及其返回值,完全可以通过加入C51的< absacc.h>头文件来解决。
<absacc.h>头文件中的函数原型为:
- # define CBYTE((unsigned char *)0x50000L)
- # define DBYTE ((unsigned char *)0x50000L)
- # define PBYTE ((unsigned char *)0x50000L)
- # define XBYTE((unsigned char *)0x50000L)
其中CBYTE定义为寻址CODE程序区;DBYTE定义为寻址DATA数据区;PBYTE定义为寻址相对于MOVX @R0"指令的分页数据XDATA区;XBYTE定义为寻址相对于MOVX @DPTR"指令的分布数据XDATA区。它们的类型决定了绝对地址空间的位置。
引进该头文件后,程序员就可以对8051系列单片机的存储器进行绝对地址的访问,把对参数值和返回值的操作转化为对存储器绝对地址的操作,像纯汇编操作一样,根本不用定义C51函数与汇编接口的参数和返回值的配置,从而提高了调用效率。具体做法是:先在C51函数中定义好传递参数和返回值所需要的各个绝对地址(视程序员自己的空间配置而定),在其它汇编模块中将C51函数中将要使用的参数值放入这些绝对地址中,把被调用C51模块将输出的计算值(可以不止一个)也放入类似的绝对地址中。于是,当C51函数中需要使用某个参数值时,就直接从相应的绝对地址中读取该值;当别的汇编模块中需要使用C51函数返回值时,也直接对存放返回值的绝对地址进行读操作即可。下面以一个调试通过的汇编调用C51函数的简单程序为例进行具体说明。
3 、ASM51无参数化调用C51函数的实现示例
该系统要示然而单片机根据实时采样输入的转速实现机车速度的测量,并可随键盘输入的车轮直径变化实时调整车速,最后将车速和轮径值都显示出来。设计任务很简单,编程中的最大难度就在于车速的计算程序。由于轮径值要求精确到mm(最大值超过了1000),车速的计算结果要保留到小数点后一位,因此需要进行浮点数运算,期间还要完成数的各种进制间的换算。虽然算法简单,但实际用汇编语言实现起来经常考虑不周,调试起来费时费力(笔者调试通过的这段汇编代码长达近400行)。这样,自然就想到调用C51函数了,充发发挥两种语言的优势。先用汇编语言设计好各个模块,包括循环显示车速和轮径值的主程序模块,响应采样转速值和键盘输入两个中断模块,代码如下所示。
- EXTRN CODE(CALL1) ;声明外部C51函数
- ORG 0000H
- LJMP MAIN
- ORG 0003H
- AJMP KEYINPUT ;键盘输入中断
- ORG 000BH
- AJMP SETTIME ;采样时间到,采样转速值中断
- ORG 0100H
- KEYINPUT:…… ;键盘输入中断
- …… ;将键盘输入信号保存在
- ;70h~73h的地址空间中
- RETI
- ORG 0600H
- SETTIME:…… ;采样时间到,采样转速值中断
- …… ;将转速值放置在地址为3Ah的空间中
- ;紧接着调用外部C51函数CALL1()进
- ;行车速的计算
- LCALL CALL1
- RETI
- ORG 2000H ;主程序模块
- MAIN:…… ;首先进行初始化操作
- ……
- ;直接从地址空间70h~77h中读取显示数据,循环显示车速和轮径值
- END
这些小模块用汇编实现起来不仅容易,而且程序员可以清楚地了解到各个模块的出入口及其相应的功能,实现对程序空间的充分配置。最后用C51语言来实现车速的计算模块CALL1()。以前用汇编编写的近400行代码,一下子被压缩到20~30行(真正的计算代码仅9行),不仅简短易懂,而且几乎就不需要调试了。
下面的代码是计算模块CALL1()及其需要的绝对地址定义。
- #pragma code small
- #include <absacc.h>
- #include <math.h>
- #define PI 3.1415926
- #define NCIRCLE DBYTE[0x3A] //定义放置转速的绝对地址
- #define DIRECT1 DBYTE[0x70] //定义放置轮径千位的绝对地址
- #define DIRECT2 DBYTE[0x71] //定义放置轮径百位的绝对地址
- #define DIRECT3 DBYTE[0x72] //定义放置轮径十位的绝对地址
- #define DIRECT4 DBYTE[0x73] //定义放置轮径个位的绝对地址
- #define VELOCITY1 DBYTE[0x74] //定义返回车速的千位绝对地址
- #define VELOCITY2 DBYTE[0x75] //定义返回车速的百位绝对地址
- #define VELOCITY3 DBYTE[0x76] //定义返回车速的十位绝对地址
- #define VELOCITY4 DBYTE[0x77] //定义返回车速的个位绝对地址
- void call1()
- {
- float data result;
- int data DIRECT;
- DIRECT=DIRECT1*1000+DIRECT2*100+DIRECT3*10+DIRECT4;
- result=(DIRECT/1000.0)*PI*NCIRCLE*3.6;
- VELOCITY1=result/100;
- Result=result-VELOCITY1*100;
- VELOCITY2=result/10;
- result=result-CELOCITY 2*10;
- VELOCITY3=result;
- result=result-VELOCITY3;
- VELOCITY4=result*10;
- }
在本例中定义了绝对地址空间70h~77H和3AH。其中3AH存放采样转速值输入模块输入的转速;70H~73H的地址空间中存放键盘输入中断模块中键盘输入的轮径值;而地址为74H~77H的空间中则存放计算模块中的车速计算返回值。尽管需要传递和返回的参数比较多,但通过这些绝对地址的定义,完全解决了原来复杂的汇编与C51之间的调用接口配置。计算模块中需要使用转速和轮径值时,将自动从绝对地址3AH和70H~73H中取值;在循环显示车速和轮径值的主程序模块中则直接读取绝对地址空间70H~77H的各个数据进行循环显示。当然,程序员可以根据自己的空间配置另外定义这些绝对地址。
以上程序代码均已在Dais-52.196P仿真器上顺利调试通过。
由上面的简单程序可以看出这种无参数化调用方法的优越性和有效:从程序代码看,无论是编写C51子程序还是汇编主程序,都与编写纯C51函数或者纯汇编主程序的格式完全一样,从根本上简化了C51与汇编函数之间的接口编程,提高了程序调用的效率;充分利用了汇编与高测验C51语言各自的优点,开发、调试快速方便,通用性强,尤其适合于初学者。对于复杂程序,同样可以利用无参数化方法来帮助实现。这对于提高单片机应用程序的开发效率很有意义。
无参数化调用实质上在C51函数中定义了几个全局变量(绝对地址),依靠它们直接完成参数值的传递和返回值的调用,相当于一种程序员自定义的传递方式,抛弃了传统C与汇编之间的接口约定。只要程序员安排得当,还可以进一步人工实现C51中的动态覆盖重用,提高RAM区的利用效率。由上也可看出:无参数化调用方法要在ASM汇编调用C51函数时才分依速无参数化思想,就违背了利用C51编程,过衷,得不偿失。当然,如果开发人员已经对C51与汇编函数之间的参数传递接口很熟悉,完全可以按接口约定或者由编译器自动完成参数的传递。