《单片机嵌入式系统及应用》课件-第4章 8051单片机程序设计基础_第1页
《单片机嵌入式系统及应用》课件-第4章 8051单片机程序设计基础_第2页
《单片机嵌入式系统及应用》课件-第4章 8051单片机程序设计基础_第3页
《单片机嵌入式系统及应用》课件-第4章 8051单片机程序设计基础_第4页
《单片机嵌入式系统及应用》课件-第4章 8051单片机程序设计基础_第5页
已阅读5页,还剩123页未读 继续免费阅读

下载本文档

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

文档简介

1第4章8051单片机程序设计基础机械工业出版社语言:汇编还是C?

确定了单片机,另一个老生常谈的争论就不得不面对了,学汇编还是C好?现在很多宣传一味鼓吹C语言的好,似乎“万恶的汇编”早就该下地狱了。这种观点是有失偏颇的。客观的讲,汇编语言和C语言各有特点。汇编语言的本质是机器码,是直接和单片机对话的唯一途径。优点是效率高,缺点是难以驾驭。C语言的逻辑性更强,优点是只要掌握了语言本身编程就变得简单,而且移植性好。缺点是即使你写出了程序,完成了功能,但是对单片机本身的了解还是很少。2实际开发中大多会使用C语言,汇编语言在较为复杂的工程面前还是显得很无力。但是汇编语言在学习单片机的过程中却很有用,想要真正懂得单片机的内部奥妙,还得借助汇编语言这个窗口。因此,学习的正确流程应该是:先通过汇编语言将单片机硬件资源掌握透彻,C语言仅是一门语言,对硬件了解清楚的基础之上再学习C语言,很容易上手。3主要内容4.1汇编语言程序结构4.2算术运算程序设计4.3C51基础4.4C51和汇编语言的混合编程44.1汇编语言程序结构4.1.1简单程序结构4.1.2分支结构4.1.3循环结构4.1.4子程序及其参数传递方法4.1.5中断服务程序*4.1.6前后台结构*4.1.7并行结构54.1.1简单程序结构

简单结构程序执行时,从第一条指令开始顺序执行,直到最后一条指令为止。它是构成较大、较复杂程序的最基本的结构。但它本身只能完成一些简单的任务,所以叫做简单程序。6例4.1将单字节BCD码转换成二进制数ORG100H

MOVR2,#28H;要转换的BCD数送入R2,入口

MOVA,R2

ANLA,#0F0H

;屏蔽低4位,取高4位

SWAPA

;高低4位交换MOVB,#10MULAB

;2×10

MOVR3,A ;乘积送R3保存MOVA,R2ANLA,#0FH;取低4位ADDA,R3 ;2×10+8送AMOVR3,A ;结果1CH送R3END

;程序结束7例4.2双字节移位

将30H、31H单元存放的无符号数除2,商仍在原单元中,余数送入CY中。由于8051指令系统中只有单字节移位指令,故双字节移位需要分步进行,只给出程序段。8

CLRCMOVA,30HRRCAMOV30H,AMOVA,31HRRCAMOV31H,A4.1.2分支结构在大量的实用程序中,需要对某些指令的执行结果进行判断,根据判断的结果决定程序的走向。判断后有“是”和“非”两种结果,程序也就有两种可能的执行方向,也就是程序产生了分支,形成了分支结构。改变程序的执行顺序有两种方法:一是事先安排好的,程序执行到某条指令后转去执行指定的指令。这是通过无条件转移指令来实现的;第二种就是根据程序执行的结果来决定转移到何处去。这是通过条件转移指令来实现的,分支程序就属这种情况。9转移指令都有条件测试功能,根据测试后的结果来确定是否转移,条件成立则转移,否则执行下一条指令。例4.3给定8位有符号数X,求符号函数Y。所谓符号函数,即当X>0时,Y为1,当X<0时,Y等于-1,而当X=0时,Y=0。这是一个典型的分支程序。1011图4-1例4.3程序流程图

ORG100H

START:MOVR0,#30H

;存放有符号数的地址指针 ;

30H送R0,而(30H)=X

MOVA,@R0

;

(A)=X

ANLA,@R0;做与运算,出标志

JZZERO;判(A)=0否?(A)=0,转ZERO,(A)非0,;下一条

JNBACC.7,NEXT

;(A)的最高位不是1,即为正数,转

;标号NEXT,如是1,执行下一条MINUS:MOVR1,#0FFH

;(A)的最高位是1,即为负数,

;(R1)=-1

SJMPEXIT

;转出口ZERO:MOVR1,#0

;(R1)=0

SJMPEXIT

;转出口

NEXT:MOVR1,#1 ;(R1)=1,即R1做为出口Y=1EXIT:SJMPEXIT

;原地踏步124.1.3循环结构

一个程序若包含多次重复执行的程序段,则称为循环结构。循环程序和分支程序都是非顺序结构程序,但它们在程序走向和所实现的功能上是不同的。

先看实例:13例4.4求存放在片内RAM20H单元开始的10个无符号数的最小值

ORG100HMOVR0,#20H

;置地址指针MOVR7,#10

;计数器初值

MOVA,@R0 ;取第一个数

DECR7

;实际的比较次数LOOP:INCR0

;修改地址指针

MOV40H,@R0

;取后一个数

CJNEA,40H,NEXT;前数与后数比较1415NEXT:JCLOPP1

;前一个数小,不交换

MOVA,40H

;前一个数大,把后一个数送A

LOPP1:DJNZR7,LOOP;计数器减1,不为0转LOOP

MOV41H,A;循环结束,最小值送;41H单元HERE:SJMP$

;$指HERE标号,踏步

END16图4-2例4.4程序流程图17循环程序的构成设置初值部分,进入循环之前要给出初始状态,称为初始化,一般包括建立计数器,设置地址指针及其他变量的初值。初值又分为循环工作部分的初值和循环结束条件的初值。循环工作部分,这是循环结构的基本部分,也叫循环体,是为重复执行任务编写的程序段。循环控制部分,控制循环的次数,一般包括修改计数器,修改指针,检测循环结束条件等。结束部分用于分析和存储结果。18图4-3循环程序流程图

如果循环工作部分又包含新的循环程序,称为循环嵌套,如出现多次嵌套,就称为多重嵌套。而循环工作部分不包含另外的循环体就称为单重循环。19单重循环和多重循环单重循环及其控制方法

控制循环的方法有多种,这里只介绍其中的三种。当循环次数已知时,利用计数器控制循环最方便。当循环次数未知时,可采用按实际条件控制循环,或采用逻辑尺的方法。20

例4.5求存放于首地址为20H单元的多个学生考试成绩之和。这个问题可利用成绩是正数的特点,在成绩数据区后放一个负数作为结束条件。

ORG1000HSTART:MOVR0,#20H;首地址指针MOVR1,#0MOVR2,#021LOOP:MOVA,@R0 ;取第1个数JBACC.7,DONE

;是负数,结束循环ADDA,R1JNCNEXTINCR2NEXT:INCR0MOVR1,ASJMPLOOPDONE:SJMP$22用逻辑尺控制循环

例4.6若单片机进行8路巡回检测,采集的数据要用不同的函数加以处理。设第0、3、5路用FUNC1处理,而1、2、4、6、7路用FUNC2处理,这样在一个循环中包括两个支路,可使用一个二进制位串来控制程序沿那一个位串来循环,本题的位串设计为11010110,0表示用FUNC1处理,1表示用FUNC2处理,这个二进制位串称为逻辑尺。其长度根据需要可为一字节,或多字节。程序运行时,可将逻辑尺移位,判断是0还是1,以决定对数据如何处理。程序流程如图4-4所示。2324图4-4用逻辑尺控制循环多重循环

如果在一个循环体中又包含了其他的循环程序,即循环中还套着循环,这种程序称为多重循环程序。例4.710秒延时程序。

延时程序与MCS-51执行指令的时间有关,如果使用12MHz晶振,一个机器周期为1μs计算出执行一条指令以至一个循环所需要的时间,给出相应的循环次数,便能达到延时的目的。25DEL: MOVR5,#100DEL0: MOVR6,#200DEL1: MOVR7,#248

;248*2+1=497个机器周期DEL2: DJNZR7,DEL2

;(248*2+1+2)*200+1=99801个机器周期

DJNZR6,DEL1

;((248*2+1+2)*200+2)*100+1+2μs

DJNZR5,DEL0

RET264.1.4子程序及其参数传递方法

在实际程序中,常常会多次进行一些相同的计算和操作。如数制转换、函数式计算等等。如果每次都从头开始编制一段程序,不仅麻烦,而且浪费存储空间。因而对一些常用的程序段以子程序的形式,事先存放在存储器的某一个区域。当主程序在运行时,需要用子程序时,只要执行调用子程序的指令,使程序转至子程序。当子程序处理完毕,返回主程序,继续进行以后的操作。27

28调用子程序的优点:

(1)避免了对相同程序段的重复编制。

(2)简化程序的逻辑结构,同时也便于子程序调试。

(3)节省存储器空间。

8051指令系统中,提供了两条调用子程序指令ACALL及LCALL,并提供了一条返回主程序的指令RET。ACALL:子程序距离本指令的距离不超过2KLCALL:子程序距离本指令的距离在64K内子程序的调用,包含两个部分:保护现场和恢复现场。29参数传递

调用子程序时,主程序应先把有关的参数存放在约定的位置,子程序在执行时,可以从约定的位置取得参数,当子程序执行完,将得到的结果存入约定的位置,返回主程序后,主程序可以从这些约定的位置读取到需要的结果,这就是参数的传递。30用累加器或寄存器进行参数的传递

用累加器和寄存器存放输入参数及结果参数,可以提高程序的运算速度,而且程序也很简单。其不足之处是参数不能传递得很多,因为寄存器的数量有限;主程序在调用子程序前必须将参数先送入寄存器;由于子程序参数的个数是固定的,故主程序不能任意设定参数的多少。31用指针寄存器进行参数的传递当程序中所需处理的数据量比较大时,常常用存储器存放数据,而不用寄存器。用指针指示数据在存储器中所处的位置,可以大大节省参数传递中的工作量,使用指针的方法能实现数据长度可变的运算。8051指令系统中提供的由R0、R1作间址寄存器的指令很多,当参数存放在内部RAM时,用R0、R1作指针,使参数的传递十分方便。当参数在外部RAM或在程序存储器时,可用DPTR作指针。对可变长度运算时,数据长度可由寄存器指出,也可采用在数据后设置标志的办法。32用堆栈进行参数传递

堆栈可以用于主程序调用子程序时相互之间的参数传递。调用前,主程序用PUSH指令把参数压入堆栈,子程序在执行中按堆栈指针间接访问栈中参数,并且把运算结果送回堆栈。返回主程序后,主程序用POP指令得到堆栈中的结果参数。利用堆栈传递参数的方法比较简单,而且传递参数量比用寄存器来传递参数多得多,也不必为特定的参数分配存储单元。33例4.8一位16进制数转换成ASCII码HEASC:MOVR0,SP;借用R0,为堆栈指针

DECR0

DECR0

;R0指向被转换参数地址

XCHA,@R0;保护累加器,取被转换参数

ANLA,#0FH

ADDA,#2

;表首地址MOVCA,@A+PC

;查表

XCHA,@R0

;结果送回堆栈

RETATAB:DB30H,31H,32H,…,39H

DB41H,…,46H34例4.9把内部RAM中40H单元一字节的16进制数转换成两位ASCII码,存放在R1指出的两个单元中,调用HEASC子程序。程序如下:

HEX_TO_ASCII:MOVA,40H;直接寻址,(40H)→A

SWAPA

;两位16进制数半字节交换PUSHACC

ACALLHEASC

POPACC35

MOV@R1,A;高半字节转换成ASCII码存结果

INCR1

PUSH40H

ACALLHEASCPOPACC

MOV@R1,A;低半字节转换成ASCII码存结果RET

END364.1.5中断服务程序中断服务程序对实时事件请求作必要的处理,使系统能实时地并行完成各个操作,中断服务程序必须包括现场保护、中断服务、现场恢复、中断返回4个部分。中断服务程序编写方法与子程序类似,同时应注意以下问题:(1)在中断程序的结尾一定要使用RETI,以便返回到主程序中断处。(2)中断服务程序中要清除中断标志,以免重复进入。具体标志和清除方法参见各中断部分。37(3)中断服务程序的长度应尽量短小,以免执行时占用CPU过多时间。所以主程序与中断服务程序之间的数据交换多采用标志位。(4)中断嵌套深度受堆栈区的影响。系统复位后,栈指针SP的初始值为07H,与工作寄存器区重叠,所以程序中一般要重新定义。AT89S52内部虽有256B的RAM,但堆栈需利用低128B开辟,所以其堆栈深度有限。384.1.6前后台结构394.1.7并行结构(多任务结构)

一个系统可以实现多任务的轮转调度,并允许“准并行”地执行多个循环或任务。任务不是同时执行,而是以时间片(timeslice)调度执行。操作系统OS(OperationSystem)将可用的CPU时间划分成若干时间片,为每个任务指定一个时间片,每个任务允许在预先规定的一段时间内执行。然后,OS切换到另一个就绪的任务,使这个任务也执行一段时间。时间片是很短的,通常是几个毫秒。因此,任务看起来是同时执行的。404.2算术运算程序设计4.2.1不带符号的多字节加法4.2.2双字节二进制无符号数乘法4.2.3码制转换程序4.2.4查表程序414.2.1不带符号的多字节加法不带符号的多字节二进制数定点加法程序在处理多字节运算时,应注意低字节向高字节的进位(或借位),用进位位CY判别,当CY=0时表示无进位或借位,反之则表示有进位或借位。在进行不带符号的单字节二进制数加减运算时,用进位CY判别和溢出与否。例4.11两个多字节数P、Q均以低字节在前,高字节在后的次序,分别存放在由R0、R1指出的内部RAM中,相加后存入P数据区。

42

ORG1000HSTAT1:CLRC

;清进位

MOVR2,#N

;取字节数MADD:MOVA,@R0

;取加数(一个字节)ADDCA,@R1

;两数相加(由低字节开始)MOV@R0,AINCR0INCR1

DJNZR2,MADD

;两数加完?JC ERR

;和字节数大于N,则溢出RETERR:…

N EQU0AH

END434.2.2双字节二进制无符号数乘法将(R2R3)和(R6R7)中双字节无符号数相乘,结果送R4R5R6R7。本子程序使用累加器A、寄存器R0、R2~R7及标志CY。子程序及其框图如图4-5所示。

NMUL:MOVR4,#0;0→R4R5MOVR5,#0 MOVR0,#16;16→位计数器R0 CLRCNMLP:MOVA,R4 ;右移一位

RRCA MOVR4,A

4445图4-5NMUL子程序框图MOVA,R5 RRCA MOVR5,A MOVA,R6 RRCA MOVR6,A

MOVA,R7 RRCA MOVR7,A JNCNMLN;C为移出的乘数最低位MOVA,R5 ;执行加法 ADDA,R3 MOVR5,A MOVA,R4 ADDCA,R2 MOVR4,ANMLN:DJNZR0,NMLP;循环16次 MOVA,R4 ;最后结果再右移-位RRCAMOVR4,A MOVA,R5RRCA MOVR5,A MOVA,R6RRCAMOVR6,AMOVA,R7RRCAMOVR7,ARET464.2.3码制转换程序例4.13ASCII码到BCD码的转换

设ASCII字符置于工作寄存器R2中,转换结果放在R3中(高4位为0)。为提高程序的容错性,若转换结果<0或>9,R3为FFH。例如,ASCII码39H转换为BCD码应为9。47ASCII_TO_BCD: MOVA,R2

CLRCSUBBA,#30HMOVR3,AJCERRSUBBA,#0AHJCENNDERR:MOVR3,#0FFH ;转换结果<0,或

;≥10置出错标志ENND:SJMP$

END48例4.14BCD数到二进制数之间的转换。n+1位的BCD数可表示为(AnAn-1…A1A0)BCD

=An×10n+An-1×10n-1+…+A1×101+A0=(…(An×10+An‑1)×10+An-2)×10+…+A1)×10+A0可以根据此式编制转换程序,为方便编程,从BCD数的高位做起。下面是双字节BCD数到二进制数的转换程序。;功能:将4位BCD数转换为二进制数;入口:BCD码为R5(千位,百位),R4(十位,个位)。;出口为R5R4,为16位无符号数二进制整数。;使用资源:累加器ACC、寄存器R5、R4、B、R2、R6、R3、进位标志C49BCD4B:MOVA,R5MOVR2,AACALLBCD2BMOVB,#64HMULABMOVR6,AXCHA,BMOVR5,AMOVA,R4MOVR2,AACALLBCD2BADDA,R6MOVR4,AMOVA,R5ADDCA,#00HMOVR5,ARET50BCD2B:MOVA,R2 ANLA,#0F0HSWAPAMOVB,#0AHMULABMOVR3,AMOVA,R2ANLA,#0FHADDA,R3MOVR2,ARET4.2.4查表程序

查表是一种常用的非数值操作,利用查表可以使复杂的计算简单化,并能完成如数据补偿、转换、检索、实现程序的多分支转移等多种功能。有简单查表、查表散转和顺序检索三种。511.简单查表程序

利用指令MOVCA,@A+DPTR查表

例如,查表求输入数据X的函数值Y=f(X),假设表在ROM中,首地址为TAB;X的取值为1,2,…,N-1,N;对应的Y值存放地址为TAB+X;X,Y均占一个字节,输入数据X(X值在累加器A中),查表结果仍存于A

MOV DPTR,#TAB MOVCA,@A+DPTR

…TAB: DB…52

用MOVCA,@A+PC指令查表将16进制数0~F转换成ASCII码,程序入口在A,出口仍在A中

HASC2:INCAMOVCA,@A+PCHASC2+2:RETASCTAB: DB30H,31H,32H,33HDB34H,35H,36H,37H,38HDB39H,41H,42H,43H,44HDB45H,46H532散转程序设计

这是一种多分支程序,它可根据运算结果或输入数据将程序转至不同的分支,例如,根据工作寄存器R0内容的不同,使程序转入相应的分支。设R0=0,对应的分支程序标号为PR0;R0=1对应的分支程序标号为PR1;……R0=n对应的分支程序标号为PRn。

54LP0:MOVDPTR,#TAB ;取表头地址 MOVA,R0 ADDA,R0 ;R0内容乘以2 JNCLP1 ;无进位转移 INCDPH ;加进位位LP1:JMP@A+DPTR;跳至散转表中相应位置 …TAB:AJMPPR0;跳至不同的分支,2字节指令AJMPPR1…AJMPPRn553.顺序检索程序例4.18从片内RAM的表中,顺序检索出关键字,给出关键字在表中的序号,当找遍整个表而无关键字时,序号为00H。表首地址为#TABLE,表长为#LENTH。程序如下:TABLE EQU 20HLENGTHEQU 10HKEY EQU 33HSEARCH:MOVR0,#TABLEMOVR1,#LENGTHMOVR2,#00H56

LOOP:MOVA,#KEYXRLA,@R0INCR0INCR2JZEXITDJNZR1,LOOPMOVR2,#00HEXIT:MOVA,R2RET574.3C51基础4.3.1C51编程概述4.3.2KeilC51标识符与关键字4.3.3C51数据与数据类型4.3.4C51变量及其存储方式4.3.5C51数据的存储类型和存储模式4.3.6C51对8051特殊功能寄存(SFR)的定义4.3.7C51对8051并行接口的定义4.3.8中断服务函数与寄存器组定义4.3.9函数的参数和局部变量的存储器模式584.3.1C51编程概述

C51语言即具有一般高级语言的特点,又能直接对单片机的硬件进行操作,表达和运算能力强,其与标准C语言的主要不同如下:1)C51语言的学习与应用必须掌握51单片机的基本原理和组成结构、外部扩展芯片的编址和电路设计,硬件和软件设计能力都需要具备,而标准C语言对硬件的关联度小。2)标准C语言定义的变量不需要直接与计算机硬件的地址发生关联。C51应用单片机内部的寄存器组、特殊功能寄存器(包括内部可位寻址的位单元)以及扩展的I/O接口芯片端口和各种功能部件,其定义的变量必须定位到这些硬件每个寄存器和端口的地址。3)针对单片机位存储器和位操作指令,C51增加了位数据类型的定义和位处理功能。4)头文件的不同。由于51系列单片机生产厂家不同,以及增强型单片机内部资源的不同,其头文件也不同,头文件集中体现了各类系列单片机的不同资源及功能。例如,C51使用8051系列单片机内部资源的头文件:reg51.h;扩展资源使用的头文件:absacc.h。595)对于单片机所特有存储空间结构,C51增加了各种存储空间变量的定义。6)对于单片机的中断系统,寄存器组的选择,C51专门增加了处理的语句。7)库函数的不同。KeilC51的部分库函数是结合单片机硬件特点而开发的,其构成和应用方法有很大的不同。例如,库函数printf和scanf,标准C语言中是用于屏幕打印和接收数据,而KeilC51中主要用于串行口数据的接收和发送。8)针对51单片机通常的存储资源最多为64KB的现状,C51不允许过多的程序嵌套,对于标准C语言中的函数递归调用的功能必须用reentrant进行声明方可使用,否则不被支持。604.3.1C51编程概述

为了增强对单片机硬件的操作能力,C51编译器扩展了适合于51单片机硬件的数据类型、存储类型、存储模式、指针类型和中断函数等,以使单片机C51语言程序本身不依赖计算机硬件系统的特点,而只需要略加补充有关硬件的操作,就可以在不同单片机应用系统间进行快速移植。C51编译器针对51单片机硬件在下列几方面对标准C进行了扩展:1)扩展了专门访问51单片机硬件的数据类型。2)存储类型按51单片机存储空间分类。3)存储模式按51单片机存储空间选定编译器模式。4)指针分为通用指针和存储器指针。5)函数增加了中断函数和再入函数。614.3.1C51编程概述

为了增强对单片机硬件的操作能力,C51编译器扩展了适合于51单片机硬件的数据类型、存储类型、存储模式、指针类型和中断函数等,以使单片机C51语言程序本身不依赖计算机硬件系统的特点,而只需要略加补充有关硬件的操作,就可以在不同单片机应用系统间进行快速移植。C51编译器针对51单片机硬件在下列几方面对标准C进行了扩展:1)扩展了专门访问51单片机硬件的数据类型。2)存储类型按51单片机存储空间分类。3)存储模式按51单片机存储空间选定编译器模式。4)指针分为通用指针和存储器指针。5)函数增加了中断函数和再入函数。624.3.1C51编程概述

使用具有C51编译扩展功能的C语言进行51单片机嵌入式系统的开发编程,简称C51编程。C51编程具有以下特点:1)可管理内部寄存器和存储器的分配,编程时,无须考虑不同存储器的寻址和数据类型等细节问题。2)程序由若干函数组成,具有良好的模块化结构,可移植性好,便于项目维护和管理。3)有丰富的子程序可直接引用,从而大大减少用户编程工作量,提高编程效率。4)与汇编语言混合编程,用汇编语言编写与硬件有关的程序,用C51编写与硬件无关的运算程序,充分发挥两种语言的长处,提高开发效率。注意:虽然使用C51编程可以取代烦琐的汇编语言编程,但仍需要了解51单片机的硬件结构,而且C51程序的目标代码在效率上还是不如汇编程序,所以对于单片机嵌入式系统的开发应采用汇编语言与C51混合编程的方法更有效。634.3.2KeilC51标识符与关键字1.标识符C51编译器规定标识符最长可达255个字符,但只有前面32个字符在编译时有效,因此在编写源程序时标识符的长度不要超过32个字符,这对于一般应用程序来说已经足够了。程序中对于标识符的命名应简洁明了,含义清晰,便于阅读理解,如用标识符“max”表示最大值,用“Timer0”表示定时器0等。642.关键字C51编译器除了支持ANSIC标准关键字之外,还扩充了表4-1所示的关键字。表4-1C51扩展的关键字65关

字用

途说

明bit位标量声明声明一个位标量或位类型的函数sbit位变量声明声明一个可位寻址变量sfr特殊功能寄存器声明声明一个特殊功能寄存器(8位)sfr16特殊功能寄存器声明声明一个16位的特殊功能寄存器data存储器类型说明直接寻址的8051内部数据存储器bdata存储器类型说明可位寻址的8051内部数据存储器idata存储器类型说明间接寻址的8051内部数据存储器pdata存储器类型说明“分页”寻址的8051外部数据存储器xdata存储器类型说明8051外部数据存储器code存储器类型说明8051程序存储器interrupt中断函数声明定义一个中断函数reentrant再入函数声明定义一个再入函数using寄存器组定义定义8051的工作寄存器组4.3.3C51数据与数据类型C51支持的数据与数据类型和ANSIC基本相同,仅多了“bit”数据类型,如表4-2所列。由于8051是8位机,因而不存在字节对准问题。这意味着数据结构成员是顺序放置的。66数据类型长度(bit/byte)数值范围bit10,1unsignedchar8/10~255signedchar8/1-128~+127unsignedint16/20~65535signedint16/2-32768~+32767unsignedlong32/40~4294967295signedlong32/4-2147483648~2147483647float32/4±1.176E-38~±3.40E+38(6位数字)double64/8±1.176E-38~3.40E+38(10位数字)一般指针24/3存储空间0~65335表4-2KeilC51编译器支持的数据类型、长度和数域4.3.4C51变量及其存储方式

除了支持位变量外,C51变量定义和标准C变量定义是相似的,下面予以简要说明。位变量(bit)的值可以是1(true)或0(false)。与8051硬件特性操作有关的位变量必须定位在8051CPU片内存储区(RAM)的可位寻址空间中。671.位变量(1)位变量的C51定义的语法及语义:

bitdriverP11;/*将driverP11定义为位变量*/bitled_pointer;/*将led_pointer定义为位变量*/bitled_number;/*将led_number定义为位变量*/(2)函数可包含类型为bit的参数,也可以将其作为返回值。例

bitfunc(bitb0,bitb1){/*…*/ return(b1);}68注意:使用禁止中断[#pragmadisable]或包含明确的寄存器组切换(usingn)的函数不能返回位值,否则编译器会返回一个错误信息。(3)对位变量定义的限制:位变量不能定义成一个指针,如不能定义bit*led_pointer;也不存在位数组,如不能定义 bitb_array[];

在位定义中,允许定义存储类型,位变量都被放入一个位段,此段总位于8051内部RAM中,因此存储类型限制为data或idata。如果将位变量的存储类型定义成其他类型,都将导致编译出错。69(4)可位寻址对象指可以字节或位寻址的对象。该对象应位于8051片内可位寻址RAM区中,C51编译器允许数据类型为idata的对象放入8051片内可位寻址RAM区中。例

先定义变量的数据类型和存储类型:

bdata

int

ibase;/*ibase定义为bdata整型变量*/

bdatacharbary[4];/*bary[4]定义为bdata字符型数组*/然后可使用“sbit”定义可独立寻址访问的对象位,即

sbitmybit0=ibase^0;/*mybit0定义为ibase的第0位*/

sbitmybitl5=ibase^15; /*mybit15定义为ibase的第15位*/

sbitAry07=bary[0]^7; /*Ary07定义为bary[0]的第7位*/

sbitAry37=bary[3]^7; /*Ary37定义为bary[3]的第7位*/70对象“ibase”、“bary”也可以字节寻址。例Ary37=0;

/*bary[3]的第7位赋值为0*/bary[3]='a';

/*字节寻址:bary[3]赋值为'a'*/sbit定义要求基址对象的存储类型为bdata,否则只有绝对的特殊位定义(sbit)是合法的。位置('^'操作符)后的最大值依赖于指定的数据类型,对于char/uchar而言是0~7;对于int/uint而言是0~15;对于long/ulong而言是0~31。712.其它类型变量字符变量的长度为1字节(Byte)即8位。除非指明是有符号变量(signedchar),字符变量的值域范围是0~255(无符号)。对于有符号的变量,最具有重要意义的位是最高位上的符号标志位(MSB),在此位上,1代表“负”,0代表“正”。有符号字符变量(signedchar)和无符号字符变量(unsignedchar)在表示0~127的数值时,其含义是一样的,都是00~0x7F。负数一般用补码表示,即用11111111表示-1,用11111110表示-2等。72整型变量的长度为16位,8051系列CPU将int型变量的MSB存放在低地址字节。有符号整型变量(signedint)也使用MSB位作为标志位,并使用二进制的补码表示数值。可直接使用几种专用的机器指令来完成多字节的加、减、乘、除运算。整型变量值0x1234以图4-6a)所示的方式保存在内存中。长整型变量的长度是32位,占用4字节,其它方面与整型变量(int)相似。长整型变量(longint)值0x12345678以图4-6b)所示的方式保存在内存中。浮点型变量为32位,占4字节。许多复杂的数学表达式都采用浮点变量数据类型。它用符号位表示数的符号,用阶码和尾数表示数的大小。用它们进行任何数学运算都需要使用由编译器决定的各种不同效率等级的库函数。KeilC51的浮点变量数据类型的使用格式与IEEE-754标准(32)有关,具有24位精度,尾数的高位始终为“1”,因而不保存。73浮点变量的位分布如下:

1位符号位;8位指数位;23位尾数。符号位是最高位,尾数为最低的23位,内存中按字节存储如下:其中,S:符号位,1表示负,0表示正;E:阶码(在两个字节中)偏移为127;M:23位尾数,最高位为“1”。74字节地址+0+1+2+3内容SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM75

a)b)c)

图4-6变量储存方式a)整型变量b)长整型变量c)浮点变量4.3.5C51数据的存储类型和存储模式1.存储类型

在讨论KEILC51的数据类型的时候,必须同时提及它的存储类型以及它与8051单片存储器结构的关系,因为KEILC51是面向8051系列单片机及其硬件控制系统的开发工具。它定义的任何数据类型必须以一定的存储类型定位在8051的某一存储区中,否则便没有任何的实际意义。76由第2章,8051系列单片机在物理上有四个存储空间: ·片内程序存储器空间; ·片外程序存储器空间;·片内数据存储器空间;·片外数据存储器空间。KEILC51编译器完全支持8051单片机的硬件结构,可完全访问8051硬件系统的所有部分。该编译器通过将变量、常量定义成不同的存储类型(data,bdata,idata,pdata,xdata,code)的方法,将它们定位在不同的存储区中。77表4-3C51存储器类型与8051存储器空间的关系78存储类型与存储空间对应关系data直接寻址片内数据存储区,访问速度快(128字节)bdata可位寻址片内数据存储器,允许位与字节混合访问(16字节)idata间接寻址片内数据存储区,可访问片内全部RAM地址空间(256字节)pdata分页寻址片外数据存储区(256字节),由MOVX@R0访问xdata片外数据存储区(64KB),由MOVX@DPTR访问code代码存储区(64KB),由MOVC@DPTR访问当使用存储类型data、bdata定义常量和变量时,C51编译器会将它们定位在片内数据存储区中(片内RAM),这个存储区根据8051单片机CPU的型号不同,其长度分别为64,128,256或512字节。这个存储区不很大,但它能快速存取各种数据。外部数据存储器从物理上讲属于单片机的一个组成部分,但用这种存储器存放数据,在使用前必须将它们移到片内数据存储区中。片内数据存储区是存放临时性变量或使用频率较高的变量的理想场所。79当使用xdata存储类型定义常量、变量时,C51编译器会将其定位在外部数据存储空间(片外RAM)。该空间位于片外附加的8KB、16KB、32KB或64KBRAM芯片中(如一般常用的6264、62256等)。其最大可寻址范围为64KB。在使用外部数据区的信息之前,必须用指令将它们移动到内部数据区中;当数据处理完之后,将结果返回到片外数据存储区。片外数据存储区主要用于存放不常使用的变量,或收集等待处理的数据,或存放要被发往另一台计算机的数据。80还有两种存储类型是pdata和idata。pdata属于xdata类型,它的高8位地址被妥善保存在P2口中,用于I/O操作。idata:可以间接寻址内部数据存储器(可以超过128字节)。访问片内数据存储器(data、bdata、idata)比访问片外数据存储器(xdata、pdata)相对要快一些,因此可将经常使用的变量置于片内数据存储器,而将规模较大、不常使用的数据置于片外数据存储器中。81当使用code存储类型定义数据时,C51编译器会将其定义在代码空间(ROM或EPROM或Flash或ISPFlash)。这里存放着指令代码和其他非易失信息。调试完成的程序代码被写入8051单片机的片内ROM/EPROM或片外EPROM中。在程序执行过程中,不会有信息写入这个区域,因为程序代码是不能进行自我改变的。82表4-4C51存储器类型及其大小和值域83存储类型长度/bit长度/Byte值域范围data810~255idata810~255pdata810~255code1620~65535xdata1620~65535变量的存储类型定义举例:chardatavar1; /*jsu1*/bitbdataflags; /*jsu2*/floatidata

a,b,c; /*jsu3*/unsignedint

pdatadimension;/*jsu4*/unsignedcharxdatavector[10][4][4];/*jsu5*/842.存储模式如果在变量定义时略去存储类型标志符,则编译器会自动选择默认的存储类型。默认的存储类型进一步由SMALL、COMPACT和LARGE存储模式指令限制。例如,若声明charvar1,则在使用SMALL存储模式下,var1被定位在data存储区中;在使用COMPACT存储模式下,var1被定位在idata存储区中;在使用LARGE存储模式下,var1被定位在xdata存储区中。存储模式决定了变量的默认存储类型、参数传递区和无明确存储类型说明变量的存储类型。85在固定的存储器地址上进行变量的传递,是C51的标准特征之一。在SMALL模式下,参数传递是在片内数据存储区中完成的。LARGE和COMPACT模式允许参数在外部存储器中传递。C51同时也支持混合模式,例如,在LARGE模式下,生成的程序可将一些函数放入SMALL模式中,从而加快执行速度。存储模式的详细说明见表4-5。C51甚至允许在变量类型定义之前,指定存储类型。因此,定义datacharx与chardatax是等价的,但应尽量使用后一种方法。86表4-5存储模式及说明87存储模式说

明SMALL参数及局部变量放入可直接寻址的片内存储器(最大128字节,默认存储类型是DATA),因此访问十分方便。另外所有对象,包括堆栈,都必须嵌入片内RAM。栈长很关键,因为实际栈长依赖于不同函数的嵌套层数。COMPACT参数及局部变量放入分页片外存储器(最大256字节,默认存储类型是PDATA),通过寄存器R0和R1(@R0,@R1)间接寻址。栈空间位于8051系统内部数据存储器。LARGE参数及局部变量放入片外数据存储器(最大64KB,默认存储类型是XDATA),使用数据指针DPTR来访问。用此数据指针访问效率较低,尤其是对于两字节或多字节变量。4.3.6C51对8051特殊功能寄存(SFR)的定义为了能直接访问8051单片机的特殊功能寄存器SFR,KEILC51提供了一种自主形式的定义方法。这种定义方法与标准C语言不兼容,只适用于对8051系列单片机进行C编程。这种定义的方法是引入关键字“sfr”,语法如下:

sfr

sfr_name=

intconstant;

sfrP1=0x90;/*P1口地址90H*/

sfrTMOD=0x89;/*定时器/计数器方式控制寄存器地址89H*/注意:sfr后面必须跟一个特殊寄存器名,“=”后面的地址必须是常数,不允许带有运算符的表达式,这个常数值的范围必须在特殊功能寄存器地址范围内,位于0x80~0xFF之间。88对SFR的16位数据的访问:对SFR的16位数据的访问:在新的8051系列产品中,SFR在功能上经常组合为16位值。当SFR的高端地址直接位于其低端地址之后时,对SFR16位值可以进行直接访问。例如8052的定时器2就是这种情况。为了有效地访问这类SFR,可使用关键字“sfr16”。16位SFR定义的语法与8位SFR相同,16位SFR的低端地址必须作为“sfr16”的定义地址。89例

sfr16T2=0xCC;

/*定时器2:

T2低8位地址

=0CCHT2高8位地址

=0CDH*/定义中名字后面不是赋值语句,而是一个SFR地址,高字节必须位于低字节之后。这种定义适用于所有新的SFR,但不能用于定时器/计数器0和1。90访问8051

SFR的位在典型的8051应用问题中,经常需要单独访问SFR中的位,C51的扩充功能使之成为可能。特殊位(sbit)的定义,像SFR一样不与标准C兼容,使用关键字“sbit”可以访问位寻址对象。与SFR定义一样,用关键字“sbit”定义某些特殊位,并接受任何符号名,“=”号后将绝对地址赋给变量名。这种地址分配有三种方法。91第一种方法:sfr_name“^”int_constant当特殊寄存器的地址为字节(8位)时,可使用这种方法。sfr_name必须是已定义的SFR的名字。“^”后的常数定义了基地址上的特殊位的位置。该值必须是0~7的数。如:

sfrPSW=0x0D0;/*定义PSW寄存器地址为0xD0*/sbitOV=PSW^2;/*定义OV位为PSW.2,位地址为

0xD2*/sbitCY=PSW^7;/*定义CY位为PSW.7,位地址为0xD7*/92第二种方法:int_constant“^”int_constant这种方法以一个整常数作为基地址。该值必须在0x80~0xFF之间,并能被8整除,确定位置的方法同上。例如:sbitOV=0xD0^2;/*OV位地址为0xD2*/sbitCY=0xD0^7;/*CY位地址为0xD7*/93第三种方法:

intconstant;这种方法将位的绝对地址赋给变量,地址必须位于0x80~0xFF之间。例如:

sbitOV=0xD2;

sbitCY=0xD7;特殊功能位代表了一个独立的定义类,不能与其他位定义和位域互换。944.3.7C51对8051并行接口的定义对于8051片内I/O口用关键字sfr来定义。例sfrP0=0x80;/*定义P0口,地址80H*/sfrP1=0x90;/*定义P1口,地址90H*/95对于片外扩展I/O口,则根据其硬件译码地址,将其视为片外数据存储器的一个单元,使用#define语句进行定义:例 #include<absacc.h>/*将PORTA定义为外部I/O口,地址为0x0FFC0,长度为8位*/#definePORTAXBYTE[0x0FFC0]PORTA=0x01;/*向外部I/O口输出数据*/一旦在头文件或程序中对这些片内外I/O口进行定义以后,在程序中就可以自由使用这些口了。96实例:

sfrP1=0x90; /*P1的SFR定义*/sfrP3=0xb0;/*P3的SFR定义*/sbitDIPswitch=P1^4;/*P1口位4的DIP开关输入*/sbitgreenLED=P1^5;/*P1口位5的绿LED输出*/voidmain(void){unsignedcharinval; inval=0; /*inval的初始化值*/while(1){97

if(DIPswitch==1)

{//检查P1.4输出是否为高 inval=P1&0x0F; /*从P1读位0~3*/ greenLED=0;/*置P1.5输出为低*/}Else{/*若P1.4输入为低*/greenLED=1; /*置P1.5输出为高*/}P3=(P3&0xF0)|inval;/*值输出到P3.0~P3.3*/}}984.3.8中断服务函数与寄存器组定义定义中断服务函数的一般形式为:

函数类型函数名(形式参数表)〔interruptn〕[usingn]关键字interrupt后面的n是中断号,n的取值范围为0~31,编译器从8n+3处产生中断向量,具体的中断号n和中断向量取决于不同的8051系列单片机芯片。8051单片机的常用中断源和中断向量见表6-1。关键字using,专门用来选择8051单片机中不同的工作寄存器组。using后面的n是一个0~3的常整数,分别选中4个不同的工作寄存器组。在定义一个函数时using是一个选项,如果不用该选项,则由编译器选择一个寄存器组作绝对寄存器组访问。需要注意的是,关键字using和interrupt的后面都不允许跟带运算符的表达式。99关键字using对函数目标代码的影响如下:在函数的入口处将当前工作寄存器组保护到堆栈中;指定的工作寄存器内容不会改变。函数返回之前将被保护的工作寄存器组从堆栈中恢复。使用关键字using在函数中确定一个工作寄存器组时必须十分小心,要保证任何寄存器组的切换都只在仔细控制的区域内发生,如果不做到这一点将产生不正确的函数结果。另外还要注意,带using属性的函数原则上不能返回bit类型的值。并且关键字using不允许用于外部函数。100关键字interrupt也不允许用于外部函数,它对中断函数目标代码的影响如下:在进入中断函数时,特殊功能寄存器ACC,B,DPH,DPL,PSW将被保存入栈;如果不使用寄存组切换,则将中断函数中所用到的全部工作寄存器都入栈;函数返回之前,所有的寄存器内容出栈;中断函数由8051单片机指令RETI结束。101下面给出一个中断函数定义的例子。

staticvoidtimer0_isr(void); /*定时器0中断服务程序*/ staticvoidtimer0_isr(void)interrupt1using1 { unsignedi;

/* 停止定时器0,然后改变定时器的计数值,

以得到10ms的中断,再次启动定时器*/ TR0=0; /*停止定时器0*/ i=TIMER0_COUNT+TL0+(TH0<<8); TL0=i; TH0=i>>8; TR0=1; /*启动定时器0*/ timer0_tick++;}1024.3.9函数的参数和局部变量的存储器模式

C51编译器允许采用三种存储器模式:SMALL、COMPACT和LARGE。一个函数的存储器模式确定了函数的参数和局部变量在内存中的地址空间。处于SMALL模式下的函数的参数和局部变量位于8051单片机的内部RAM中,处于COMPACT和LARGE模式下的函数的参数和局部变量则使用8051单片机的外部RAM。在定义一个函数时可以明确指定该函数的存储器模式,一般形式为:

函数类型函数名(形式参数表)[存储器模式]其中,存储器模式是C51编译器扩展的一个选项。不用该选项时即没有明确指定函数的存贮器模式,这时该函数按编译时的默认存储器模式处理。103例4.19函数的存储器模式

#pragmalarge/*默认存储器模式为LARGE*/externintcalc(chari,intb)small; /*指定SMALL模式*/externintfunc(inti,floatf)compact;/*指定Compact模式*/externvoid*tcp(charxdata*xp,intndx)small;/*指定SMALL模式*/

intmtest(inti,inty)small

/*指定SMALL模式

*/{return(i*y);

}intlarge_func(inti,intk)/*未指定模式,按默认的LARGE模式处理*/{return(mtest(i,k)+2);}104上述例子程序的第一行用了一个预编译命令“#pragma”,它的意思是告诉C51编译器在对程序进行编译时,按该预编译命令后面给出的编译控制指令“LARGE”进行编译,即本例程序编译时的默认存储器模式为LARGE。程序中一共定义了五个函数:calc()、func()、*tcp()、mtest()、large_func(),其中前面四个函数都在定义时明确指定了其存储器模式,只有最后一个函数未指定。在用C51进行编译时,只有最后一个函数按LARGE存储器模式处理,其余四个函数则分别按它们各自指定的存储器模式处理。这个例子说明,C51编译器允许采用所谓存储器的混合模式,即允许在一个程序中某个(或几个)函数使用一种存储器模式,另一个(或几个)函数使用另一种存储器模式。采用存储器混合模式编程,可以充分利用8051系列单片机中有限的存储器空间,同时还可加快程序的执行速度。1054.4C51和汇编语言的混合编程4.4.1函数名的转换4.4.2参数传递和函数返回规则106汇编语言具有程序结构紧凑、占用存储空间小,实时性强、执行速度快,能直接管理和控制存储器及硬件接口的特点,故此C语言并不能完全替代汇编语言。单独应用汇编语言或C51语言进行编程时,都是应用同一种语言编程,程序应用不同的语言进行编程时,称为混合编程。混合编程中主要涉及两种情况:C51程序调用汇编程序和汇编程序调用C51程序。由于C语言提供了丰富的库函数,具有很强的数据处理能力,编程中对8051单片机的寄存器和存储器的分配均由编译器自动管理,而汇编程序短小精干、执行速度快,因而混合编程时通常主程序应用C51编写,与硬件有关的程序应用汇编语言编写,所以程序中涉及到C51程序调用汇编程序。107C51程序调用汇编程序主要有下列情况:外围设备的驱动程序用汇编语言编写,但主程序采用C51程序完成。如一些板卡的驱动程序一般用汇编语言编写。较为复杂的程序需要采用结构性较强的C51语言开发,但是部分程序要求较高的处理速度而必须使用更精练的汇编语言编写。如高速数据采集中的A/D转换程序。程序中的部分代码因时序要求严格而使用汇编语言编写。如串行接口芯片的读写。108在实际程序开发中,有些程序以汇编语言为主体,如果涉及到复杂的数学运算,往往需要借助于C语言工具所提供的库函数和强大的数据处理能力,这就要求汇编程序中调用C51程序。因此,在单片机应用程序的开发过程中,有必要掌握C51和汇编语言的混合编程方法。要实现混合编程,必须掌握两种编程语言之间函数名的转换规则、参数的传递和函数返回规则、C51程序中调用汇编语言以及汇编程序中调用C51程序的方法。1094.4.1函数名的转换混合编程中,需要将欲调用的每种语言编写的程序用一段单独的程序表示,即C51中用函数表示,汇编中用子程序表示。互相调用时,函数名称需要变化,即函数名的转换。C51与汇编中函数名的转换规则如表4-6所示。110表4-6C51与汇编中函数名的转换规则111C51中函数说明汇编中符号名

温馨提示

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

评论

0/150

提交评论