
1.1 C语言与MCS-51单片机编程
嵌入式单片机在开发过程中的编程语言主要有汇编语言和C语言。汇编语言作为传统的嵌入式系统的编程语言,已经不能满足实际需要了,而C语言的结构化和高效性满足了这样的需要,成为电子工程师在进行嵌入式系统编程时的首选语言,并得以广泛应用。尤其是C语言编译系统的发展,更加促进了C语言的应用。1985年出现了针对8051单片机的C51编译器,进而又出现了其他流行的嵌入式处理器系统,如196系列、PIC系列、MOTORAL系列、MSP430系列、AD公司和TI公司的DSP系列的C语言编译系统,以及丰富的C语言库函数。本书主要讨论8位嵌入式单片机——MCS-51单片机及其派生产品的C语言编程问题,简称C51的程序设计。
1.1.1 单片机的C语言的特点
单片机的C语言的特点主要体现在以下几个方面:
① 无须了解机器硬件及其指令系统,只需初步了解MCS-51的存储器结构;
② C51语言能方便地管理内部寄存器的分配、不同存储器的寻址和数据类型等细节问题,但对硬件控制有限,而汇编语言可以完全控制硬件资源;
③ C51语言在小应用程序中产生的代码量大,执行速度慢,但在较大的程序中代码效率高;
④ C51语言程序由若干函数组成,具有良好的模块化结构,便于改进和扩充;
⑤ C51语言程序具有良好的可读性和可维护性,而汇编语言在大应用程序开发中,开发难度增加,可读性差;
⑥ C51语言有丰富的库函数,可以大大减少用户的编程量,显著缩短编程与调试时间,大大提高软件开发效率;
⑦ 使用汇编语言编制的程序,当机型改变时,无法直接移植使用,而C语言程序是面向用户的程序设计语言,能在不同类型的机器上运行,可移植性好。
1.1.2 单片机的C语言和标准C语言的比较
标准C语言,或称为ANSI C语言。单片机的C语言和标准C语言之间有许多相同的地方,但也有其自身的一些特点。不同的嵌入式C语言编译系统之所以与ANSI C语言有不同的地方,主要是由于它们所针对的硬件系统不同,对于MCS-51系列单片机,称为C51语言。C51语言与标准C语言的不同点主要体现在以下几方面。
(1)库函数
标准C语言定义的库函数是按照通用微型计算机来定义的,而C51语言中的库函数是按MCS-51单片机的应用情况来定义的。
(2)数据类型
在C51语言中增加了几种针对MCS-51单片机的特有数据类型。例如,MCS-51系列单片机包含位操作空间和丰富的位操作指令,因此,C51语言与ANSI C语言相比多了一种位类型,从而使其能同汇编语言一样,灵活地进行位指令操作。
(3)变量的存储模式
C51语言中变量的存储模式与MCS-51单片机的存储器紧密相关。从数据存储类型上,MCS-51系列单片机有片内、片外程序存储器,片内、片外数据存储器。在片内程序存储器中,又有直接寻址区和间接寻址区之分,其分别对应code、data、xdata、idata,以及根据MCS-51系列单片机特点而设定的pdata类型。使用不同存储器将会影响程序执行的效率,不同的模式对应不同的硬件系统和不同的编译结果。但ANSI C语言对存储模式要求不高。
(4)输入/输出
C51语言中的输入/输出是通过MCS-51串行口来完成的,输入/输出指令执行前必须对串行口进行初始化。
(5)函数使用
C51语言中有专门的中断函数。
1.1.3 单片机的C语言与汇编语言的优势对比
在国内,汇编语言在单片机开发过程中是比较流行的开发工具。长期以来对编译效率的偏见,以及不少程序员对使用汇编语言开发硬件环境的习惯性,使得C语言在很多地方遭到冷落。优秀的程序员写出的汇编语言程序的确有执行效率高的优点,但汇编语言其可移植性和可读性差的特点,使得使用其开发出来的产品在维护和功能升级时的确有极大的困难,从而导致整个系统的可靠性和可维护性比较差。而使用C语言进行嵌入式系统的开发,有着汇编语言不可比拟的优势。
(1)编程调试灵活方便
C语言编程灵活,当前几乎所有的嵌入式系统都有相应的C语言级别的仿真调试系统,调试十分方便。
(2)生成的代码编译效率高
当前较好的C编译系统编译出来的代码效率只比直接使用汇编语言低20%,如果使用优化编译选项甚至可以更低。
(3)模块化开发
目前的软硬件开发都向模块化、可复用性的目标集中。不管是硬件还是软件,都希望其有比较通用的接口,在以后的开发中如果需要实现相同或者相近的功能,就可以直接使用以前开发过的模块,尽量不做或少做改动,以减少重复劳动。如果使用C语言开发,那么数据交换可以方便地通过约定实现,有利于多人协同进行大项目的合作开发。同时C语言的模块化开发方式使开发出来的程序模块可以不经修改而直接被其他项目使用,这样就可以很好地利用已有的大量C语言程序资源与丰富的库函数,从而最大程度地实现资源共享。
(4)可移植性好
由于不同系列嵌入式系统的C语言编译工具都是以标准C语言作为基础进行开发的,因此一种C语言环境下所编写的C语言程序,只需要将部分与硬件相关的地方和编译链接的参数进行适当修改,就可以方便地移植到另一种系列上。例如,在C51下编写的程序通过改写头文件及少量的程序行,就可以方便地移植到196或PIC系列上。也就是说,基于C语言环境下的嵌入式系统能基本实现平台的无关性。
(5)便于项目的维护
用C语言开发的代码便于开发小组计划项目、灵活管理、分工合作及后期维护,基本可以杜绝因开发人员变化而给项目进度、后期维护或升级所带来的影响,从而保证整个系统的品质、可靠性及可升级性。
下面通过一个例子对汇编语言和C语言进行比较。
【例1-1】 将外部数据存储器的000BH和000CH单元的内容相互交换。
用汇编语言编程,源程序如下:
ORG 0000H MOV DPTR,#000BH MOVX A,@DPTR ;将000BH内容读入A MOV R7,A ;暂存000BH内容 INC DPTR MOVX A,@DPTR ;将000CH内容读入A MOV DPTR,#000BH MOVX @DPTR,A INC DPTR MOV A,R7 MOVX @DPTR,A SJMP $ END
用C语言编程,C51源程序如下(注:C51语言对地址的指示可以采用指针变量,也可以引用头文件absacc.h作为绝对地址访问。下面的程序采用绝对地址访问方法):
#include<absacc.h> void main(void) { char c; c=XBYTE[11]; XBYTE[11]=XBYTE[12]; XBYTE[12]=c; while(1); }
上面的程序经过编译,生成的反汇编程序如下:
0x0000 020013 LJMP STARTUP1(C:0013) ;跳转 0x0003 90000B MOV DPTR,#0x000B 0x0006 E0 MOVX A,@DPTR 0x0007 FF MOV R7,A 0x0008 A3 INC DPTR 0x0009 E0 MOVX A,@DPTR 0x000A 90000B MOV DPTR,#0x000B 0x000D F0 MOVX @DPTR,A 0x000E A3 INC DPTR 0x000F EF MOV A,R7 0x0010 F0 MOVX @DPTR,A 0x0011 80FE SJMP C:0011 0x0013 787F MOV R0,#0x7F ;以下是清零部分 0x0015 E4 CLR A 0x0016 F6 MOV @R0,A 0x0017 D8FD DJNZ R0,IDATALOOP(C:0016) 0x0019 758107 MOV SP(0x81),#0x07 0x001C 020003 LJMP main(C:0003)
对照C语言编写的源程序与反汇编程序,可以看出:
① 进入C语言程序后,首先将RAM地址为7FH开始的128个单元清零,然后置SP为07,因此如果要对内部RAM置初值,那么一定是在执行了一条C语言语句之后;
② 对于C语言程序设定的变量,C51编译器自行安排寄存器或存储器作为参数传递区,通常在R0~R7(一组或两组,根据参数多少而定),因此如果对具体地址置数,则应避开R0~R7这些地址;
③ 如果不特别指定变量的存储类型,那么变量通常被安排在内部RAM区。
下面再给出几个C语言和汇编语言对照的例子。
【例1-2】 二进制数转换成十进制数(BCD码)。将累加器A中给定的二进制数,转换成3个十进制数(BCD码),并存入Result开始的3个单元。
汇编语言源程序如下:
Result EQU 20H ORG 0000H LJMP START START: MOV SP,#60H ;主程序 MOV A,#123 LCALL BINTOBCD SJMP $ BINTOBCD: MOV B,#100 ;设置转换子程序 DIV AB MOV Result,A ;除以100得百位数 MOV A,B MOV B,#10 DIV AB MOV Result+1,A ;余数除以10得十位数
MOV Result+2,B ;余数为个位数 RET END
调试结果:
片内RAM 20H、21H、22H中的数值分别为01H、02H、03H。
用C语言编程,C51源程序如下:
void main(void) { unsigned char Result[3]; unsigned char Number; Number=123; Result[0]=Number/100; //除以100得百位数 Result[1]=(Number%100)/10; //余数除以10得十位数 Result[2]=Number%10; //余数为个位数 while(1); //等待暂停 }
调试结果:
片内RAM 07H中的数据为7BH,08H、09H、0AH中的数据分别为01H、02H、03H。
【例1-3】 二进制数转换成ASCII码程序。将累加器A中的内容分为两个ASCII码,并存入Result开始的两个单元。
汇编语言源程序如下:
Result EQU 20H ORG 0000H LJMP START START: MOV SP,#40H MOV A,#00011010B LCALL BINTOHEX SJMP $ BINTOHEX: MOV DPTR,#ASCIITAB MOV B,A SWAP A ANL A,#0FH ;取A的高4位 MOVC A,@A+DPTR MOV Result,A MOV A,B ANL A,#0FH ;取A的低4位 MOVC A,@A+DPTR MOV Result+1,A RET ASCIITAB: DB '0123456789ABCDEF' END
调试结果:
片内RAM20H、21H中的数据分别为31H、41H。
用C语言编程,C51源程序如下:
code unsigned char ASCIITAB[16]="0123456789ABCDEF"; void main(void)
{ unsigned char Result[2]; unsigned char Number; Number=0x1a; Result[0]=ASCIITAB[Number/16]; //高4位 Result[1]=ASCIITAB[Number&0x0f]; //低4位 while(1); }
调试结果:
片内RAM07H中的数据为1AH,08H、09H中的数据分别为31H、41H。