单片机C语言编程_第1页
单片机C语言编程_第2页
单片机C语言编程_第3页
单片机C语言编程_第4页
单片机C语言编程_第5页
已阅读5页,还剩68页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、内 容 提 要 C51 C51的数据类型 数据的存贮类型和存贮模式 C51对SFR、可寻址位、存储器和I/O口的定义 C51的运算符 函 数 C语言编程实例 单片机资源的C语言编程实例 汇编语言和C语言的混合编程 C语言函数库的管理与使用 小结 51系列单片机支持三种高级语言,即PL/M,C和BASIC。8052单片机内固化有解释BASIC语言 ,BASIC语言适用于简单编程而对编程效率运行速度要求不高的场合;PL/M是一种结构化的语言,很象PASCAL,PL/M 编译器好象汇编器一样产生紧凑的机器代码,可以说是高级汇编语言,但它不支持复杂的算术运算,无丰富库函数支持,学习PL/M无异于学习一

2、种新的语言。C语言是一种通用的程序设计语言,其代码率高,数据类型及运算符丰富,并具有良好的程序结构,适用于各种应用的程序设计,是目前使用较广的单片机编程语言。 10.2.2 关于指针型数据 (1)关于指针型变量 在汇编语言程序中,要取存贮单元m的内容可用直接寻址方式,也可用寄存器间接寻址方式 ,如果用R1寄存器指示m的地址,用R1取m单元的内容。相对应的在C语言中用变量名表示取变量的值(相当于直接寻址),也可用另一个变量(如P)存放m的地址,P就相当于R1寄存器 。用*P取得m单元的内容(相当于汇编的间接寻址方式)这里P即为指针型变量。下面表格表示两种语言将m单元的内容送n单元的对照语句。 注

3、: 上表省略了汇编语言程序中对符号地址n和m用EQU伪指令进行具体地址定义的 语句以及C语言对变量n、m和指针变量P进行类型定义的语句,实际程序设计中,此步是不可 缺少的。表中&为取地址运算符,*为取内容运算符。 表10-2 汇编语言和C语言的对照 (2)指针型数据的类型 由于C51是结合51单片机硬件的,51单片机的不同存贮空间,有不同的地址范围,即使对于同一外部数据存贮器,又有用Ri分页寻址(Ri为八位)和用DPTR寻址(DPTR为十六位)两种寻址方式,而指针本身也是一个变量,有它存放的存贮区和数据长度。因此,在指针类型的定义中要说明:被指的变量的数据类型和存贮类型;指针变量本身的数据类型

4、(占几个字节)和存贮类型(即指针本身存放在什么存贮区)。 例如类型定义为data或idata,表示指针指示内部数据存贮器;而pdata表示指针指向外部数据存贮器,用Ri间址。以上均为八位地址;而类型code/xdata表示指针指向外部程序存贮器或外部数据存贮器指针,本身(即被指 示地址)应为十六位长度。如果想使指针能适用于指向任何存贮空间,则可以定义指针为通用型,此时指针长度为3字节,第一字节表示存贮器类型编码,第二、三字节分别表示所指 地址的高位和低位。第一字节表示的存贮器类型编码见表10-3: 表10-3 通用型指针的存贮类型编码 10.3 数据的存贮类型和存贮模式 10.3.1数据的存贮

5、类型 C51是面向8XX51系列单片机及硬件控制系统的开发语言,它定义的任何变量必须以一定的存贮类型的方式定位在8XX51的某一存贮区中,否则便没有意义。因此在定义变量类型时,还必须定义它的存贮类型,C51的变量的存贮类型如表10-4所示: 表10-4 C51的变量的存贮类型 访问内部数据存贮器(idata)比访问外部数据存贮器(xdata)相对要快一些,因此,可将经常使用的变量置于内部数据存贮器中,而将较大及很少使用的数据变量置于外部数据存贮器中。例如定义变量x语句:data char x (等价于char data x)。如果用户不对变量的存贮类型定义,则编译器承认默认存贮类型,默认的存贮

6、类型由编译控制命令的存贮的模式部分决定。 10.3.2 存贮器模式 存贮器模式决定了变量的默认存贮器类型、参数传递区和无明确存贮区类型的说明。C51的存贮器模式有SMALL、LARGE和COMPACT(见表10-5)。 在固定的存贮器地址进行变量参数传递是C51的一个标准特征,在SMALL模式下参数传递是在内部数据存贮区中完成的。LARGE和COMPACT模式允许参数在外部存贮器中传递。C51同时也支持混 合模式,例如在LARGE模式下生成的程序可将一些函数分页放入SMALL模式中从而加快执行速度。 例如设C语言源程序为PROR.C,若使程序中的变量类型和参数传递区限定 在外部数据存贮区 ,有

7、两种方法: 方法1:用C51对PROR.C进行编译时,使用命令C51 PROR.C COMPACT。 方法2:在程序的第一句加预处理命令 #pragma compact 表10-5 存贮器模式 10.3.3 变量说明举例 data char var; /*字符变量var定位在片内数据存贮区*/ char code MSG=PARAMETER:; /*字符数组MSG 定位在程序存 贮区*/unsigned long xdata array100; /*无符号长型数组定位在片外RAM区,每元素占4bytes*/ float idata x,y,z; /*实型变量x,y,z,定位在片内用间址访问的内

8、部 RAM区*/ bit lock; /*位变量Lock定位在片内RAM可位寻址区*/unsigned int pdata sion; /*无符号整型变量sion定位在分页的外部RAM*/unsigned char xdata vector1044 /*无符号字符型三维数组, 定位在片外RAM区*/ sfr P0=0 x80; /*定义P0口,地址为80H*/char bdata flags; /*字符变量flags定位在可位寻址内部RAM区*/ sbit flag0=flags0; /*定义flag0为flags.0 */ 如果在变量说明时略去存贮器类型标志符,编译器会自动选择默认的存贮器类

9、型。默认的存贮器类型由控制指令SMALL、COMPACT和LARGE限制。例如如果声明char var,则默认的存贮器模式为SMALL,var放在data存贮区;如果使用COMPACT模式,var放入idata存贮区 ;在使用LARGE模式的情况下,var被放入外部数据存贮区(xdata存贮区)。 10.3.4指针变量说明举例long xdata *px; /*指针px指向long型xdata区(每个数据占四个单元,指针自身在默认存贮器(如不指定编译模式在data区),指针长度为2个字节*/char xdata *data pd;/*指针pd指向字符型xdata区,自身在data区,长度2字

10、节*/data char xdata *pd; /*与上例等效*/ data int *pn;(和int *data pn及intr*pn等效) /*定义一个类型为int型的通用型指针,指针自身在data区长度为3字节*/ 在上例的指针声明中包含如下几个内容: 1) 指针变量名(如px)前面冠以“*”,表示px为指针型变量,此处*不带取内容之意。 2) 指针指向的存贮类型,即指向哪个存贮区,它决定了指针本身的长度(见表10.1)。存贮类型声明的位置在数据类型和指针名(如*px)之间,如无次项声明,则此指针型变量为通用型。 3) 指针指向的存贮区的数据类型,即被指向的存贮区以多少个单元作一个数据

11、单位,当程序通过指针对该区操作时,将按此规定的单元个数的内容作为一个数据操作。 4) 指针变量自身的存贮类型,即指针处于什么区与自身的长度无关,该声明可位于声明语句的开头,也可在“*”和变量名之间。此项由编译模式放在默认区,如无规定编译模式,通常在data区。 10.4 C51对SFR、可寻址位、存储器和I/O口的定义 10.4.1 特殊功能寄存器SFR定义 C51提供了一种自主形式的定义方式,使用特定关键字sfr 如 sfr SCON=0 x98; /*串行通信控制寄存器地址98H*/ sfr TMOD=0 x89; /*定时器模式控制寄存器地址89H*/ sfr ACC=0 xe0; /*

12、A累加器地址E0H*/ sfr P1=0 x90; /*P1端口地址90H*/ 定义了以后,程序中就可以直接引用寄存器名。 C51也建立了一个头文件reg51.h (增强型为reg52.h),在该文件中对所有的特殊功能寄存器的进行了sfr定义, 对特殊功能寄存器的有位名称的可寻址位进行了sbit定义,因此,只要用包含语句#include,就可以直接引用特殊功能寄存器名,或直接引用位名称。要特别注意:在引用时特殊功能寄存器或者位名称必须大写。 10.4.2 对位变量的定义 C51对位变量的定义有三种方法: 1. 将变量用bit类型的定义符定义为bit类型: 如 bit mn; mn为位变量,其值

13、只能是“0”或“1”,其位地址C51自行安排在可位寻址区的bdata区。 2. 采用字节寻址变量.位的方法: 如 bdata int ibase; /*ibase定义为整型变量*/ sbit mybit=ibase15; /*mybit定义为ibase的D15位*/ 这里位是运算符“”相当于汇编中的“”,其后的最大取值依赖于该位所在的字节寻址变量的定义类型,如定义为char最大值只能为7。 3. 对特殊功能寄存器的位的定义 方法1:使用头文件及sbit定义符;多用于无位名的可寻址位。 例如 #include sbit P1-1=P11; /*P1-1为P1口的第1位*/ sbit ac=ACC

14、7; /*ac定义为累加器A的第7位*/ 方法2:使用头文件reg51.h,再直接用位名称。 例如 #include RS1=1; RS0=0; 方法3:用字节地址位表示 例如 sbit OV=0 xD02; 方法4:用寄存器名.位定义 例如 sfr PSW=0 xd0; /*定义PSW地址为d0H*/ sbit CY=PSW7; /*CY为PSW7*/ 10.4.3 C51对存贮器和外接I/O口的绝对地址访问 1.对存贮器的绝对地址访问 利用绝对地址访问的头文件absacc.h可对不同的存贮区进行访问。该头文件的函数有: CBYTE (访问code区字符型) DBYTE (访问data区字符

15、型) PBYTE (访问pdata或I/O区字符型) XBYTE (访问xdata或I/O区字符型) 还有CWORD、DWORD、PWORD和XWORD四个函数,它们的访问区域同上,只是访问的类型为int 型。例10-1 #include #define com XBYTE0 x07ff 那么后面程序com变量出现的地方,就是对地址为07ffH的外部RAM或I/O口进行访问。 例10-2 XWORD0=0 x9988; 即将9988H(int类型)送入外部RAM的0号和1号单元。 使用中要注意:absacc.h一定要包含进程序,XBYTE必须大写。 2.对外部I/O口的访问 由于单片机的I/O

16、口和外部RAM统一编址,因此对I/O口地址的访问可用XBYTE (MOVX DPTR )或PBYTE (MOVX Ri)进行。例10-3 XBYTE0Xefff=0 x10;将10H输出到地址为EFFFH端口 7复合赋值运算符:+=;-=;*=;/=;%=;=;&=;=;|=。例:a+=b相当于a=a+b。a=7. 相当于a=a 7.。 8. 对指针操作的运算符: &取地址运算 *间址运算符例 a=&b;取b变量的地址送变量a c=*b;将以b的内容为地址的单元的内容送c这里要注意: “&”与按位与运算符的差别,如果“&”为“与”,&的两边必须为变量或常量; “*”与指针定义时指针前的“*”的

17、差别。如char *pt,这里的“*”只表示pt为指针变量,不代表间址取内容的运算。 10.6.4对被调函数的说明 如果被调函数出现在主调函数之后,在主调函数前应对被调函数作以说明,形式为: 返回值类型 被调函数名(形参表列); 如果被调函数出现在主调函数之前,可以不对被调函数说明。下面以一个简单例子来说明int fun1(a,b) int a,b; int c; c=a+b; return(c); main() int d,0u=3,v=2;d=2*fun(u,v); 上例被调函数在主调函数前,不用说明。 int fun1(a,b); main() int d,u=3,v=2; d=2*fu

18、n1(u,v); int fun1(a,b); int a,b; int c; c=a+b; return(c); 上例中被调函数在主调函数后,在前面对被调函数进行说明。 10.7.1 顺序程序的设计 例105 完成1980524503的编程 分析:两个乘数比较大,其积更大,采用unsigned long类型,设乘积存放在外部数据存贮器0号开始的单元。程序如下: main() unsigned long xdata *p; /*设定指针p指向类型为unsigned long的外部RAM区*/ unsigned long a=19805; /* 设置a为unsigned long类型,并赋初值

19、*/ unsigned long b=24503,c; /*设置b和积为unsigned long类型,并赋初值 */ p=0; /*设地址指向0号单元*/ c=a*b; *p=c; /*积存入外部RAM 0号单元*/ 上机通过软件仿真调试,在变量观察窗口看到运算结果c=48528195,即为乘积的十进制数。观察XDATA区(外部RAM)的0000H0003H单元分别为1C EC D0 7B,即存放的为乘积的十六进制数。 观察DATA区(内部RAM区): 地址 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 1C EC D0 7B 00 00 4D 5D 00 00 5

20、F B7 C变量(积) a变量 b变量 可见定义为unsigned long类型,给每个变量分配四个单元,如果定义类型不对,将得不到 正确的结果。 对于复杂的运算通常采用查表的方法。如同汇编程序设计一样,在程序存贮器建立一张表, 在C语言中表格定义为数组,表内数据(元素)的偏移量表现为下标。数组的使用如同变量一样,要先进行定义:说明数组名、维数、数据类型和存贮类型,在定义数组的同时,还可以给数组各元素赋初值。通过下例说明C51数组的定义方法和用C语言编查表程序的方法。 例106 片内RAM 20H单元存放着一个005H的数,用查表法,求出该数的平方值放入内部RAM 21H单元。 main()

21、char x,*p char code tab6=0,1,4,9,16,25;p=0 x20; x=tab*p; p+; *p=x; 例10-7 while(P1&0 x01)=0); 即如果P1.0=0,循环执行空语句,直到P1.0变为1,此语句用于对P1.0进行检测。 例10-8 分析下列程序的执行结果:main() int sum=0,i; do sum+=I; i+; while(i=10); 本程序完成0+1+2+10的累加,执行后sum=55例10-9 将例10-8改用for语句编程 main int sum=0,i; for(i=0;i10;i+) sun+=i; 例10-10

22、片内RAM的20H单元存放一个有符号数x,函数y与x有如下关系式: x x0 y= 20H x=0 x+5 x0 设y存放于21H单元,程序如下main() char x,*p,*y;p=0 x20; y=0 x21; for(;) x=*p; if(x0)*y=x; if(x0)*y=x+5; if(x=0)*y=0 x20; 程序中为观察不同数的执行结果,采用了死循环语句for(;) 。 例10-11 有两个数a和b,根据R3的内容转向不同的处理子程序:r3=0,执行子程序pr0(完成两数相加) r3=1,执行子程序pr1(完成两数相减) r3=2,执行子程序pr2(完成两数相乘) r3=

23、3,执行子程序pr3(完成两数相除) 分析: C语言中的子程序即为函数,因此需编四个处理的函数,如果主函数在前,主函数要对子函数进行说明;如果子函数在前,主函数无须对子函数说明,但是无论子、主函数的顺序如何,程序总是从主函数开始执行,执行到调用子函数就会转到子函数执行. 在C51编译器中通过头文件reg51.h可以识别特殊功能寄存器,但不能识别R0R7通用寄存器,因此R0R7只有通过绝对地址访问识别,程序如下: #include #define r3 DBYTE0 x03 int c,c1,a,b; pr0() c=a+b; pr1() c=a-b; pr2() c=a*b; pr3() c=

24、a/b; main() a=90;b=30; for (;) switch(r3) case 0: pr0();break; case 1: pr1();break; case 2: pr2();break; case 3: pr3();break; c1=56; 在上述程序中,为便于调试观察,加了C1=56语句,并使用了死循环语句for(;),用Ctrl+C 可退出死循环。10.8 单片机资源的C语言编程实例 例10-12 在3.1节曾用汇编语言完成了外部RAM的000EH单元和000FH单元的内容交换,现改用C语言编程。C语言对地址的指示方法可以采用指针变量,也可以引用absacc.h头文

25、件作绝对地址访问,下面采用绝对地址访问方法。#include main() char c; for(;) c=XBYTE14; XBYTE14=XBYTE15; XBYTE15=c; 程序中为方便反复观察,使用了死循环语句for(;)只要用Ctrl+C即可退出死循环。上面程序通过编译,生成的机器代码和反汇编程序如下: 0000 020014 LJMP 0014H 0003 90000E MOV DPTR,#000EH 0006 E0 MOVX A,DPTR0007 FF MOV R7,A0008 A3 INC DPTR 0009 E0 MOVX A,DPTR000A 90000E MOV DP

26、TR,#000EH 000D F0 MOVX DPTR,A 000E A3 INC DPTR 000F EF MOV A,R7 0010 F0 MOVX DPTR,A 0011 80F0 SJMP 0003H 0013 22 RET 0014 787F MOV R0,#7FH 0016 E4 CLR A 0017 F6 MOV R0,A 0018 D8FD DJNZ R0,0017H 001A 758107 MOV SP,#07H 001D 020003 LJMP 0003H 例中可见: 一进入C语言程序,首先执行初始化,将内部RAM的07FH 128个单元清零,然后置SP为07H(视变量多少

27、不同,SP置不同值,依程序而定),因此如果要对内部RAM置初值,一定要在执行了一条C语言语句后进行。 C语言程序设定的变量,C51自行安排寄存器或存贮器作参数传递区,通常在R0R7(一组或两组,视参数多少定),因此,如果对具体地址置数据,应避开这些R0R7的地址。 如果不特别指定变量的存贮类型,通常被安排在内部RAM中。 10.8.2 并行口及键盘的C语言编程 例10-13 用P1.0输出1KHz和500Hz的音频信号驱动扬声器,作报警信号,要求1KHz信号响100ms,500Hz信号响200ms,交替进行,P1.7接一开关进行控制,当开关合上,响报警信号,当开关断Kk告警信号停止,编出程序.

28、分析 500Hz信号周期为2ms,信号电平为每1ms变反一次.1KHz信号周期为1ms,信号电平每500s变反一次。用C语言编程如下: #includesbit P10=P10;sbit P17=P17;main()unsigned char i, j;while(1) while(P17=0)for(i=1;i=150;i+) /*控制音响时间*/ P10=P10;for(j=0;j=50;j+); /*延时完成信号gou周期时间*/for(i=1;i=100;i+) /*控制音响时间*/ P10=P10; for(j=0;j=100;j+); /*延时,完成信号周期时间*/ 例10-14

29、在 下图中8XX51接有五个共阴极数码管的动态显示接口电路,开关打向位置“1”时,显示“12345”字样,当开关打向“2”时,显示HELLO字样,C语言编程程序清单如下。 图10-7 接五个共阴极数码管的动态显示接口 用C语言完成上述功能编程#include#define uint unsigned int#deefine uchar unsigned charsbitP17=P17;main ( )uchar code tab15=0 x86,0 xdb,0 xcf,0 xe6,0 xed ; /*“15”的字形码,因P1.7接的开关,最高位送的“1”*/ uchar code tab25=

30、0 xf8,0 xf9,0 xb8,0 xb8,0 x,bf; /*“HELLO”的段码 “1”*/uchar i;unit j;while(1) p3=0 x011 for(i=0;i+) if(p17=1)P1=tab1i; elseP1=tab2i; P3=1;for(j=0;j=25000;j+); 例10-15 以P1.0P1.3作输出线,以P1.4P1.7作输入线,如图4-6所示。 C语言编程程序清单如下:#include#define uchar unsigned char#define uint unsigned intvoid dlms (void);uchar kbscan

31、(void); / * 函数说明 * /void main (void0;uchar key;while (1) key=kbscan( ); / * 键盘扫描函数,返回键码送key保存 * / dlms( ); void dlms (void) /* 延时 * /uchar i; for (i=200;i0;i- -) 图10-8 44矩阵键盘 uchar kbscan(void) / * 键盘扫描函数 * /uchar sccode,recode;P1=0 xf0; / *P1.0P1.3发全0,P1.4P1.7输入 * / if ( (P1 & 0 xf0)! =0 xf0) / * 如

32、P1口高四位不全为1有键按下 * / dlms( ); / * 延时去抖动 * / if ( (P1 & 0 xf0) ! =0 xf0) / * 在读输入值 * / sccode =0 xfe / * 最低位置0 * / while ( ( sccode & 0 x10)! =0) / * 不到最后一行循环 * / P1 =sccode; /*P1口输出扫描码 * / If ( (P1 & 0 xf0)! =0 xf0) / * 如P1.4P1.7不全为1,该行有键按下 * / recode = (P1 & 0 xf0;); / * 保留P1口高四位输入值,低四位变为全“1”, 作为列值 *

33、 / return( (sccode) + (recode) ); /*行码+列值=键编码返回主程序 * / else sccode =(sccode1) | 0 x01; / * 如该行无键按下,查下一行,行扫描值左移一位*/ return( 0 ); / * 无键按下,返回值为0 * / 10.8.3 C51中断程序的编制 C51使用户能编写高效的中断服务程序,编译器在规定的中断源的矢量地址中放入无条件转移指令,使CPU响应中断后自动地从矢量地址跳转到中断服务程序的实际地址,而无需用户去安排。中断服务程序定义为函数,函数的完整定义如下。返回值 函数名(参数)模式再入interrupt nu

34、sing m 其中必选项 interrupt n表示将函数声明为中断服务函数,n为中断源编号,可以是031间的整数 ,不允许是带运算符的表达式,n通常取以下值:0 外部中断0;1 定时器/计数器0溢出中断2 外部中断1;3 定时器/计数器1溢出中断4 串行口发送与接收中断5 定时器/计数器2中断 各可选项的意义如下:using m 定义函数使用的工作寄存器组,m的取值范围为03,可缺省。它对目标代码的影响是:函数入口处将当前寄存器保存,使用 m 指定的寄存器组,函数退出时 原寄存器组恢复。选不同的工作寄存器组,可方便实现寄存器组的现场保护。再入属性关键字reentrant将函数定义为再入的,在

35、C51中,普通函数(非再入的)不能递归调用, 只有再入函数才可被递归调用。中断服务函数不允许用于外部函数,它对目标代码影响如下:当调用函数时,SFR中的ACC、B、DPH、DPL和PSW当需要时入栈。如果不使用寄存器组切换,中断函数所需的所有工作寄存器Rn都入栈。函数退出前,所有工作寄存器都出栈。函数由“RETI”指令终止。下面示例说明C语言的编程方法。 例10-15 对10.2.3的例10-4(见图)要求每中断一次,发光二极管显示开关状态 用C语言编程 #include int0() interrupt 0 /*INT0中断函数*/ P1=0 x0f; /*输入端先置1,灯灭*/ P=4;

36、/* 读入开关状态,并左移四位, 使开关反映在发光二极管上*/ main() EA=1; /*开中断总开关*/EX0=1; /*允许INT0中断*/ IT0=1; /*下降沿产生中断*/while(1); /*等待中断*/ 例10-16 记录并显示中断次数用C语言编程,可有两种编程方法。法1:在主程序中判断中断次数,程序如下:#includechar i;code char tab16=0 x3f,0 x06,0 x5b,0 x4F,0 x66,0 x6d,0 x7d,0 x07,0 x7f,0 x6f,0 x77,0 x7c,0 x39,0 x5e,0 x79,0 x71;int() int

37、errupt 2 i+; /*计中断次数*/ P1=tabi; /*查表,次数送显示*/ main() EA=1; EX1=1; IT1=1;ap5: P1=0 x3f /*显示“0”*/for(i=0;i16;); /*当i小于16等待中断*/ goto ap5; /*当i=16重复下一轮16次中断*/ 法2:在中断程序中判断中断次数:#includechar i;code char tab16=0 x3f,0 x06,0 x5b,0 x4F,0 x66,0 x6d,0 x7d,0 x07, 0 x7f,0 x6f,0 x77,0 x7c,0 x39,0 x5e,0 x79,0 x71;in

38、t() interrupt 1 i+ if(i16)P1=tabi; elsei=0;P1=0 x3f; main() EA=1; EX1=1; IT1=1; P1=0 x3f; while(1); /*等待中断*/ 10.8.4 定时/计数器的C语言编程 例10-17 在P1.7端接一个发光二极管LED,要求利用定时控制使LED亮一秒灭一秒周而复始,设fo sc=6MHz。分析 T0定时100ms初值=100103/2=50000,即初值为-50000。T1计数5个脉冲工作于方式2,计数初值为-5,T0和T1均采用中断方式。程序如下:#include reg51.hsbit P1_0=P10

39、;sbit P1_7=P17;timer0() interrupt 1 using 1 /* T0中断服务程序 */ P1_0=! P1_0; /* 100ms到P1.0反相*/ TH0=-50000/256; /* 重载计数初值 */ TL0=-50000%256; timerl() interrupt 3 using 2 /* T1中断服务程序 */ P1_7=! P1_7; /* 1s到,灯改变状态 */ main () P1_7=0; /* 置灯初始灭 */ P1_0=1; /* 保证第一次反相便开始计数 */ TMOD=0 x61; /* T0方式 1 定时,T1方式 2 计数 */

40、 TH0=-50000/256; /* 预置计数初值 */ TL0=-50000%256; TH1=-5; TL1=-5; IP=0 x08; /* 置优先级寄存器 */ EA=1;ET0=1;ET1=1; /* 开中断 */ TR0=1;TR1=1; /* 启动定时/计数器 */ for(;) /* 等待中断 */ 例10-18 在内部数据存贮器20H3FH单元中共有32个数据,要求采用方式1串行发送出去, 传送速率为1200波特,设fosc12MHZ。方法:T1工作于方式 2 作波特率发生器,取SMOD0,T1的时间常数计算如下:波特率 (2SMOD/32)fosc/(12(256-x))

41、 1200(1/32)(12106) )/ (12(256-x) x230E6H(1)查询方式编程C语的编程: 发送程序:#includemain( ) unsingned char i;char *p;TMOD=0 x20; TH1=0 xe6;TL1=0 xe6;TR1=1;SCON=0 x40;p=0 x20;for (i=0;i=32;i+) SBUF=*p p+ while (!TI); TI=0; 接收程序:#include main( ) unsingned char i;char *p;TMOD=0 x20;TH1=0 xe6;TL1=0 xe6;TR1=1;SCON=0 x5

42、0;p=0 x20;for (i=0;i=32;i+) while (!RI); RI=0; *p=SBUF; p+ 10.8.6 外扩并行I/O 口的C语言编程 例10-19 用8155作6位共阴极LED显示器接口,PB口经驱动器7407接LED的段选,PA0PA5位反相驱动器7406接位选,待显示字符依次存于dis-buf数组,从右向左顺序显示。8155命令字03,table为段码表,动态显示6个字符。8155和8XX51的接口见图10.12。 图10.12 8155和8XX51单片机的接口电路各口的地址:A口7FF1H B口7FF2H C口7FF3H 命令/状态口7FF0HC语言程序如下

43、:#include#include#define uchar unsigned char#define COM8155 XBYTE0 x7ff0#define PA8155 XBYTE0 x7ff1#define PB8155 XBYTE0 x7ff2#define PC8155 XBYTE0 x7ff3uchar idata dis6=2,4,6,8,10,12; /* 存放显示字符2、4、6、8、A,C */uchar code table18=0 x3f,0 x06,0 x5b,0 x4f,0 x66,0 x6d,0 x7d,0 x07,0 x7f,0 x6f,0 x77,0 x7c,0

44、 x39,0 x5e,0 x79,0 x71,0 x40,0 x00; void display(uchar idata *p)uchar sel,i,j;COM8155=0 x03;scl=0 x01; /*送命令字,选最右边的LED*/for(i=0;i6;i+)PB8155=table*p;PA8155=sel; /*送段码和位码*/for(j=400;j0;j-); /*延时*/p-; /*地址指针下移位*/sel=sel1; /*左移一位*/ main()display(dis+5); 10.8.7 D/A的 C 语言编程 单级缓冲工作方式下的DAC0832与51系列单片机的接口地址

45、为7FFFH,要求输出端得到锯齿波电压信号用C语言编程,程序如下:#include#include#define da0832 XBYTE0X7fffmain()unsigned char i,j;while(1)for(i=0;i=255;i+)da0832=i; /* 启动转换 */for(j=0;j=255;j+); /* 延时 */ 图10.4 锯齿波电压信号 10.9 汇编语言和C语言的混合编程 本节介绍不同的模块,不同的语言相结合的编程方法。 通常情况下以高级语言编写主程序,用汇编语言编与硬件有关的子程序。高级语言不同的编译程序对汇编的调用方法不同,在KEIL C51中,是将不同的

46、模块(包括不同语言的模块)分别汇编或编译,再通过连接生成一个可执行文件。 C语言程序调用汇编语言程序要注意以下几点。 1.被调函数要在主函数中说明,在汇编程序中,要使用伪指令使CODE选项有效并声明为可再定位段类型,并且根据不同情况对函数名作转换,见表10.6。 表10.6 函数名的转换 2. 对为其他模块使用的符号进行PUBLIC声明,对外来符号进行EXTRN声明。 3 要注意参数的正确传递。 10.9.1 C语言程序和汇编语言程序参数的传递 在混合语言编程中,关键是入口参数和出口参数的传递,KEIL C51编译器可使用寄存器传递参数,也可以使用固定存贮器或使用堆栈,由于8XX51的堆栈深度

47、有限,因此多用寄存器或存贮器传递。用寄存器传递最多只能传递三个参数,选择固定的寄存器,见表10.7。 表10. 7 参数传递的寄存器选择 例如 funcl(int a) “a”是第一个参数,在R6,R7传递 func2(int b, int c, int *d) “b”在R6,R7中传递,“c”在R4,R5中传递,指针变量“d”在R1,R2,R3中传递。 如果传递参数寄存器不够用,可以使用存贮器传送,通过指针取得参数。 汇编语言通过寄存器或存贮器传递参数给C语言程序,汇编语言通过寄存器传递给C语言 的返回值见表10.8 。表10.8 汇编语言通过寄存器传递给C语言的返回值 下面通过实例说明混合

48、编程的方法及参数传递过程。 10.9. 2 C 语言程序调用汇编语言程序举例 例1. 用P1.0产生周期为4ms的方波,同时用P1.1产生周期为8ms的方波。 说明:用C语言编写主程序,使P1.1产生周期为8ms的方波为模块一; P1.0产生周期为4ms的 方波为模块二; 用汇编语言编写的延时1ms程序为模块三。 模块一调用模块二获得8ms方波, 模块二调模块三时向汇编程序传递了字符型参数(x=2),延时2ms,程序如下: C语言程序模块一: #include #define uchar unsigned char sbit P1-1=P11; void delay4ms(void); /*

49、定义延时4ms 函数(模块二) */ main() uchar i; for(;) P1-1=0;delay4ms();/* 调模块二延时4ms */P1-1=1;delay4ms();/* 调模块二延时4ms*/ 模块二: #include #define uchar unsigned char sbit P1-0=P10; delaylms(uchar x); /* 定义延时1ms函数(模块三) */ void delay4ms(void) P1-0=0;delaylms(2);/* 调汇编函数(模块三) */P1-0=1;delaylms(2);/* 调汇编函数(模块三)*/ 模块三:

50、PUBLIC -DELAY1MS ;DE LAY1MS为其他模块调用 DE SEGMENT CODE ;定义DE段为再定位程序段 RSEG DE ;选择DE为当前段 - DELAY1MS: NOP DELA: MOV R1,#0F8H ;延时 LOP1: NOP NOP DJNZ R1,LOP1 DJNZ R7,DELA ;R7为C程序传递过来的参数 EXIT: RET END上例可见汇编语言程序从R7中获取参数(x=2)。 模块编译连接方法 以上各模块可以先分别汇编或编译(选择DEBUG编译控制项),生成各自的.OBJ文件,然后运行L51将各OBJ文件连接,生成一个新的文件。 在集成环境下的

51、连接调试可以连续进行,比上面方法更为方便,现使用wave(伟福)的仿真软件的编译连接步骤如下: 1.编辑好各个模块,保存。 2.点击文件/新建项目,弹出项目窗口。 3.点击项目菜单,选加入模块,此时弹出有文件目录的对话框,选中要加入刚才编辑好的文件(模块),并打开。此时在项目窗口中可以看到加入的模块文件。 4.点击项目菜单中的全部编辑,并取名保存项目。于是系统对加入各模块进行编译,并进行连接。 5.编译连接完成会弹出信息窗口,如编译连接有错,信息窗口将出现错误信息。 6.模块连接成功,生成二进制文件(.BIN)和十六进制文件(.HEX)。7.点击跟踪或单步按钮,就可对程序进行跟踪调试,程序运行

52、到不同模块时,wave就会弹出相应的模块源程序窗口,显示程序运行情况。 例2.在汇编程序中比较两数大小,将大数放到指定的存储区,由C程序的主调函数取出模块一:C语言程序 #define uchar unsigned char void max(uchar a, uchar b); /* 定义汇编函数 */ main()uchar a=5,b=35,*c,d;c=0 x30;/* c指针变量指向内部 RAM 30H单元 */max(a,b);/* 调汇编函数,a,b为传递的参数 */d=*c;/* d存放模块二传递过来的参数 */ 模块二:汇编语言程序PUBLIC -MAX MAX为其 他模块调

53、 DE SEGMENT CODE ;定义DE段为再定位程序段 RSEG DE ;选择DE为当前段 MAX: MOV A,R7 ;取模块一的参数aMOV 30H,R5 ;取模块一的参数bCJNE A,30H,TAG1 ;比较a,b的大小 TAG1: JC EXITMOV 30H,R7 ;大数存于30H单元 EXIT: RET END此例中,C语言程序通过R7和R5传递字符型参数a和b到汇编语言程序,汇编语言程序将返回值放在固定存贮单元,主调函数通过指针取出返回值。 10.9.3 C和汇编混合编程传递的参数多于三个的编程方法 C语言程序调用汇编程序最多只能传递三个参数,如果多于三个参数,就需要通过

54、存贮区传递,可以通过数组,也可以在汇编程序中建立数据段,下面例中C语言程序向汇编传递的参数多于三个的编程方法例3 A/D采用查询方式采样50个数据(A/D地址为7FF8H),将其求平均并送数码管显示.分析 8位A/D最大值255,用三个数码管显示,以P3.4为查询位,电路设计如图,:以汇编编A/D转换程序,采集50个数据,以C编求平均值,变十进制显示,程序如下: 图 例3电路 #include #define uint unsigned int#define uchar unsigned charextern void callasm(uchar); /* 定义外部汇编函数 */extern

55、void dayl(uint); /* 定义外部汇编函数dayl */void main(void)uint i,j,m,total = 0;uchar idata buf50,dis3;uchar code tab16=0 x3f,0 x06,0 x5b,0 x4f,0 x66,0 x6d,0 x7d,0 x07,0 x7f,0 x6f,0 x77,0 x7c,0 x39,0 x5e,0 x79,0 x71;/*段码表*/P1 =0 xf8;while(1) total = 1;callasm( buf ); /* 调汇编函数,传递参数为数组首址*/for( i = 50; i0;i- )

56、/ *汇编函数执行完后返回于此*/total += bufi-1; /*50个数累加 */total=total/50; /*求平均 */dis0=total%10; /*求个位,并存入显示缓冲区 */total=total/10;dis1=total%10; /*求十位,并存入显示缓冲区 */dis2=total/10; /*求百位,并存入显示缓冲区 */P3=0 x01; /*P3口位选 */for(m=0;m=50;m+) for(i=0;i=3;i+) /*显示*/ P1=tabdisi; dayl (50) ;/* 调汇编函数DAYL,延时*/P3=1; 汇编语言程序CALLASM.

57、ASM-完成50个数据采集并存于BUF为首址的单元 PUBLIC CALLASM ;公共符号定义DFFE SEGMENT CODE ;DFFE定为可再定位段RSEG DFFE;DFFE为当前段-CALLASM: PUSH 07H PUSH 00H;保护变量,因在下述程序中要用R7和R0 MOV A, R7;取BUF地址 MOV R0, A;R0指示存放地址MOV R7, #50MOV DPTR, #7FF8H;DPTR 指向A/D地址 AGA:MOV A, #0 MOVX DPTR, A ;启动转换JB P3.4, $ ;等待转换结束MOVX A,DPTR ;读转换数据MOV R0,A ;存入

58、BUF数组 INC R0DJNZ R7,AGAPOP 00HPOP 07H;恢复BUF地址RETEND 汇编语言程序 DAYL.ASM延时 PUBLIC -DAYL ;公共符号定义 DTE SEGMENT CODE ;定义DTE段为再定位程序段 RSEG DTE ;选择DTE为当前段 -DAYL: NOP DELA: MOV R1,#0F8H ;延时 LOP1: NOP NOP DJNZ R1,LOP1 DJNZ R7,DELA ;R7为C程序传递过来的参数 EXIT: RET END 10.10 C语言函数库的管理与使用 C语言作为一种高级编程语言,其主要的优势之一就是有大量的丰富的库函数可直接使用。而库函数的使用是解决程序共享和提高编程效率的最有效的途径之一。函数库是具有目标代码形式的函数的集合。虽然在许多方面,库就像一个独立编译的模块,但它有一个不同于目标文件的特别之处:当某个独立编译的目标文件与其它文件连接时,所有该目标文件中的函数,无论它们是否真正被程序所用,都成为可执行的一部分;而当一个库文件与其它文件连接时,可执行程序中只包含那些真正由程序所用的库函数。例如,C51标准库中包含很多函数,而你的程序只包含真正由你的程序所调用的函数。 10.10.1 库函数的编写 库函数的编写同普通的函数编写的方法一样,需要主要的几点是:库函数命名时,不能用

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论