51单片机原理以及接口技术(四)--80C51的程序设计

本文介绍了80C51单片机的汇编语言程序设计,涵盖了程序编制的方法和技巧,包括程序编制的步骤、算法优化、程序总体设计及流程图绘制等内容。同时,文章详细解释了80C51单片机汇编语言的语句格式,以及源程序的编辑和汇编过程。此外,还提供了基本程序结构、常用程序举例,帮助读者理解并掌握80C51单片机的汇编语言程序设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单片机应用系统是合理的硬件与完善的软件的有机组合。软件就是各种指令依某种规律组合形成的程序。程序设计(或软件设计)的任务是利用计算机语言对系统预完成的任务进行描述和规定。
80C51单片机的程序设计主要采用两种语言,一种是汇编语言,另一种是高级语言(如C51)。采用高级语言进行程序设计,对系统硬件资源的分配比用汇编语言简单,且程序的阅读和修改比较容易,适于编写较大一点的程序。汇编语言生成的目标程序占存储空间少、运行速度快,具有效率高、实时性强的优点,适于编写短小高效的程序。
由于汇编语言是面向机器的语言,对单片机的硬件资源操作直接方便、概念清晰,尽管对编程人员的硬件知识要求较高,但对于学习和掌握单片机的硬件结构极为有利。所以,这里我们仅对汇编语言进行介绍。

点击此处访问小编的个人小站

4.1 程序编制的方法和技巧

4.1.1 程序编制的步骤

  一、预完成任务的分析
  首先,要对单片机应用系统预完成的任务进行深入的分析,明确系统的设计任务、功能要求和技术指标。其次,要对系统的硬件资源和工作环境进行分析。这是单片机应用系统程序设计的基础和条件。
  二、进行算法的优化
  算法是解决具体问题的方法。一个应用系统经过分析、研究和明确规定后, 对应实现的功能和技术指标可以利用严密的数学方法或数学模型来描述,从而把一个实际问题转化成由计算机进行处理的问题。同一个问题的算法可以有多种,结果也可能不尽相同,所以,应对各种算法进行分析比较,并进行合理的优化。比如,用迭代法解微分方程,需要考虑收敛速度的快慢(即在一定的时间里能否达到精度要求)。而有的问题则受内存容量的限制而对时间要求并不苛刻。对于后一种情况,速度不快但节省内存的算法则应是首选。
  三、程序总体设计及流程图绘制
  经过任务分析、算法优化后,就可以进行程序的总体构思,确定程序的结构和数据形式,并考虑资源的分配和参数的计算等。然后根据程序运行的过程, 勾画出程序执行的逻辑顺序,用图形符号将总体设计思路及程序流向绘制在平面图上,从而使程序的结构关系直观明了,便于检查和修改。
通常,应用程序依功能可以分为若干部分,通过流程图可以将具有一定功能的各部分有机地联系起来,并由此抓住程序的基本线索,对全局可以有一个完整的了解。清晰正确的流程图是编制正确无误的应用程序的基础和条件,所以,绘制一个好的流程图,是程序设计的一项重要内容。
流程图可以分为总流程图和局部流程图。总流程图侧重反映程序的逻辑结构和各程序模块之间的相互关系。局部流程图反映程序模块的具体实施细节。对于简单的应用程序,可以不画流程图。但当程序较为复杂时,绘制流程图是一个良好的编程习惯。
  常用的流程图符号有开始和结束符号、工作任务符号、判断分支符号、程序连接符号、程序流向符号等,如图 4.1 所示。
图 4.1 常用程序流程图符号
  此外,还应编制资源分配表,包括数据结构和形式、参数计算、通信协议、各子程序的入口和出口说明等。

4.1.2 编制程序的方法和技巧

  一、采用模块化程序设计方法
单片机应用系统的程序一般由包含多个模块的主程序和各种子程序组成。每一程序模块都要完成一个明确的任务,实现某个具体的功能,如发送、接收、延时、打印、显示等。采用模块化的程序设计方法,就是将这些不同的具体功能程序进行独立的设计和分别调试,最后将这些模块程序装配成整体程序并进行联调。
  模块化的程序设计方法具有明显的优点。把一个多功能的、复杂的程序划分为若干个简单的、功能单一的程序模块,有利于程序的设计和调试,有利于程序的优化和分工,提高了程序的阅读性和可靠性,使程序的结构层次一目了然。所以,进行程序设计的学习,首先要树立起模块化的程序设计思想。
  二、尽量采用循环结构和子程序
采用循环结构和子程序可以使程序的长度减少,占用内存空间减少。对于多重循环,要注意各重循环的初值和循环结束条件,避免出现程序无休止循环的“死循环”现象。对于通用的子程序,除了用于存放子程序入口参数的寄存器外,子程序中用到的其它寄存器的内容应压入堆栈进行现场保护,并要特别注意堆栈操作的压入和弹出的平衡。对于中断处理子程序除了要保护程序中用到的寄存器外,还应保护标志寄存器。这是由于在中断处理过程中难免对标志寄存器中的内容产生影响,而中断处理结束后返回主程序时可能会遇到以中断前的状态标志为依据的条件转移指令,如果标志位被破坏,则程序的运行就会发生混乱。

4.1.3 汇编语言的语句格式

  80C51 单片机汇编语言的语句行由 4 个字段组成,汇编程序能对这种格式正确地进行识别。这 4 个字段的格式为:
  [标号:]操作码 [操作数] [;注释]
  括号内的部分可以根据实际情况取舍。每个字段之间要用分隔符分隔,可以用作分隔符的符号有空格、冒号、逗号、分号等。如:
  LOOP:MOV A,# 7FH ;A←7FH

一、标号

  标号是语句地址的标志符号,用于引导对该语句的非顺序访问。有关标号的规定为:
(1)标号由 1~8 个 ASCII 字符组成。第一个字符必须是字母,其余字符可以是字母、数字或其它特定字符;
(2)不能使用该汇编语言已经定义了的符号作为标号。如指令助记符、寄存器符号名称等;
(3)标号后边必须跟冒号。

二、操作码

  操作码用于规定语句执行的操作。它是汇编语句中惟一不能空缺的部分。它用指令助记符表示。

三、操作数

  操作数用于给指令的操作提供数据或地址。在一条汇编语句中操作数可能是空缺的,也可能包括一项,还可能包括两项或三项。各操作数间以逗号分隔。操作数字段的内容可能包括以下几种情况:
(1)工作寄存器名;
(2)特殊功能寄存器名;
(3)标号名;
(4)常数;
(5)符号“$”,表示程序计数器 PC 的当前值;
(6)表达式。

四、注释

  注释不属于汇编语句的功能部分,它只是对语句的说明。注释字段可以增加程序的可读性,有助于编程人员的阅读和维护。注释字段必须以分号“;”开头,长度不限,当一行书写不下时,可以换行接着书写,但换行时应注意在开头使用分号“;”。

五、数据的表示形式

  80C51 汇编语言的数据可以有以下几种表示形式:

  • 二进制数,末尾以字母 B 标识。如:1000 1111B
  • 十进制数,末尾以字母 D 标识或将字母 D 省略。如:88D,66;
  • 十六进制数,末尾以字母 H 标识。如:78H,0A8H(应注意的是,十六进制数以字母 A~F 开头时应在其前面加上数字“0”)
  • ASCII 码,以单引号括起来标识。如:‘AB’,‘1245’。

4.2 源程序的编辑和汇编

由于通用微型计算机的普及,现在单片机应用系统的程序设计都借助于通用微型计算机来完成。首先,在微机上利用各种编辑软件编写单片机的汇编语言源程序,然后使用交叉汇编程序对源程序进行汇编,并将获得的目标程序经仿真器或通用编程器写到单片机或程序存储器中,进而完成应用程序的调试。

4.2.1 源程序的编辑与汇编

一、源程序的编辑

  源程序的编写要依据 80C51 汇编语言的基本规则,特别要用好常用的汇编命令(即伪指令)。例如下面的程序段:
  ORG    0040H
  MOV  A, #7FH
  MOV  R1, #44H
  END
  这里的 ORG 和 END 是两条伪指令,其作用是告诉汇编程序此汇编源程序的起止位置。编辑好的源程序应以“ . ASM ”扩展名存盘,以备汇编程序调用。

二、源程序的汇编

  将汇编语言源程序转换为单片机能执行的机器码形式的目标程序的过程叫汇编。汇编常用的方法有两种,一是手工汇编,二是机器汇编。
  手工汇编时,把程序用助记符指令写出后,通过手工方式查指令编码表, 逐个把助记符指令翻译成机器码,然后把得到的机器码程序(以十六进制形式) 键入到单片机开发机中,并进行调试。由于手工汇编是按绝对地址进行定位的, 所以,对于偏移量的计算和程序的修改非常不便。通常只在程序较小或开发条件有限制时才使用。
  机器汇编是在常用的个人计算机 PC 上,使用交叉汇编程序将汇编语言源程序转换为机器码形式的目标程序。此时汇编工作由计算机完成,生成的目标程序由 PC 机传送到开发机上,经调试无误后,再固化到单片机的程序存储器ROM 中。机器汇编与手工汇编相比具有极大的优势,所以是汇编工作的首选。
  源程序经过机器汇编后,形成的若干文件中含有两个主要文件,一个是列表文件,另一个是目标码文件。因汇编软件的不同,文件的格式及信息会有一些不同,但主要信息如下:
  列表文件主要信息为:
  地 址  目标码  汇编程序
           ORG  0040H
  0040H  747F  MOV  A,#7FH
  0042H  7944  MOV  R1,#44H
           END
  目标码文件主要信息为:
  首地址  末地址  目标码
  0040H  0044H  747F7944
  该目标码文件由 PC 机的串行口传送到开发机后,接下来的任务就是仿真调试了。

4.2.2 伪指令

我举一个例子就是,我们在读报告的时候有时候会在页脚写上“下转第三页”,就是跳过第二页直接看第三页,但是我们并不把“下转第三页”这五个字读出来,那么“转第三页”这五个字就相当于伪指令怎么样是不是通俗易懂!!!

  伪指令是汇编程序能够识别并对汇编过程进行某种控制的汇编命令。它不是单片机执行的指令,所以没有对应的可执行目标码,汇编后产生的目标程序中不会再出现伪指令。标准的 80C51 汇编程序定义了许多伪指令,下面仅对一些常用的进行介绍。

一、起始地址设定伪指令 ORG

  格式为:
  ORG 表达式
  该指令的功能是向汇编程序说明下面紧接的程序段或数据段存放的起始地址。表达式通常为 16 进制地址,也可以是已定义的标号地址。如:
      ORG  8000H
  START:MOV  A,#30H
  此时规定该段程序的机器码从地址 8000H 单元开始存放。
  在每一个汇编语言源程序的开始,都要设置一条 ORG 伪指令来指定该程序在存储器中存放的起始位置。若省略 ORG 伪指令,则该程序段从 0000H 单元开始存放。在一个源程序中,可以多次使用 ORG 伪指令规定不同程序段或数据段存放的起始地址,但要求地址值由小到大依序排列,不允许空间重叠。

二、汇编结束伪指令 END

  格式为: END
  该指令的功能是结束汇编。
  汇编程序遇到 END 伪指令后即结束汇编。处于 END 之后的程序,汇编程序将不处理。

三、字节数据定义伪指令 DB

  格式为: [标号:] DB 字节数据表
  功能是从标号指定的地址单元开始,在程序存储器(ROM)中定义字节数据
  字节数据表可以是一个或多个字节数据、字符串或表达式。该伪指令将字节数据表中的数据根据从左到右的顺序依次存放在指定的存储单元中,一个数据占一个存储单元。例如:
  DB “how are you?”
  把字符串中的字符以 ASCII 码的形式存放在连续的 ROM 单元中。又如:
  DB –2,–4,–6,8,10,18
  把 6 个数转换为十六进制表示(FEH,FCH,FAH,08H,0AH,12H),并连续地存放在 6 个 ROM 单元中。
  该伪指令常用于存放数据表格。如要存放显示用的十六进制的字形码,可以用多条 DB 指令完成:
  DB 0C0H,0F9H,0A4H,0B0H
  DB 99H,92H,82H,0F8H
  DB 80H,90H,88H,83H
  DB 0C6H,0A1H,86H,84H

四、字数据定义伪指令 DW

  格式为: [标号:] DW 字数据表
  功能是从标号指定的地址单元开始,在程序存储器中定义字数据。
  该伪指令将字或字表中的数据根据从左到右的顺序依次存放在指定的存储单元中。应特别注意:16 位的二进制数,高 8 位存放在低地址单元,低 8 位存放在高地址单元
  例如:
        ORG 1400H
  DATA:  DW 24AH,3CH
  汇编后,(1400H)=32H,(1401H)= 4AH,(1402H)=00H,(1403H)=3CH。

五、空间定义伪指令 DS

  格式为:[标号:] DS 表达式
  功能是从标号指定的地址单元开始,在程序存储器中保留由表达式所指定的个数的存储单元作为备用空间,并都填以零值。
  例如:
    ORG 3000H
BUF: DS   50
  汇编后,从地址 3000H 开始保留 50 个存储单元作为备用单元。

六、赋值伪指令 EQU

  格式为:符号名 EQU 表达式
  功能是将表达式的值或特定的某个汇编符号定义为一个指定的符号名。例如:

     LEN  EQU	10 
     SUM  EQU	21H
  BLOCK   EQU	22H
          CLR	A
          MOV	R7,#LEN 
          MOV	R0,#BLOCK
    LOOP:ADD	A,@R0 
          INC	R0
          DJNZ R7,LOOP 
          MOV	SUM,A
          END

  该程序的功能是,将 BLOCK 单元开始存放的 10 个无符号数进行求和,并将结果存入 SUM 单元中。

七、位地址符号定义伪指令 BIT

  格式为: 符号名 BIT 位地址表达式
  功能是将位地址赋给指定的符号名。其中,位地址表达式可以是绝对地址, 也可以是符号地址。
  例如:
    ST BIT P1.0
    将P1.0 的位地址赋给符号名 ST,在其后的编程中就可以用ST 来代替P1.0。

4.3 基本程序结构

4.3.1 顺序程序

  顺序程序是指无分支、无循环结构的程序。其执行流程是依指令在存储器中的存放顺序进行的。

一、数据传送

  例 内部 RAM 的 2AH~2EH 单元中存储的数据如图 4.2 所示。试编写程序实现图 4.3 所示的数据传送结果。
数据传送
方法一:
    MOV A,2EH ;2 字节,1 个机器周期
    MOV 2EH,2DH ;3 字节,2 个机器周期
    MOV 2DH,2CH ;3 字节,2 个机器周期
    MOV 2CH,2BH ;3 字节,2 个机器周期
    MOV 2BH,#00H ;3 字节,2 个机器周期
方法二:
    CLR A ;1 字节,1 个机器周期
    XCH A,2BH ;2 字节,1 个机器周期
    XCH A,2CH ;2 字节,1 个机器周期
    XCH A,2DH ;2 字节,1 个机器周期
    XCH A,2EH ;2 字节,1 个机器周期
  以上两种方法均可以实现所要求的传送任务。方法一使用 14 个字节的指令代码,执行时间为 9 个机器周期;方法二仅用了 9 个字节的代码,执行时间也减少到了 5 个机器周期。实际应用中应尽量采用指令代码字节数少、执行时间短的高效率程序,即注意程序的优化。

二、查表程序

  例 有一变量存放在片内 RAM 的 20H 单元,其取值范围为:00H~05H。要求编制一段程序,根据变量值求其平方值,并存入片内 RAM 的 21H 单元。
程序如下:

  在程序存储器的一片存储单元中建立起该变量的平方表。用数据指针 DPTR 指向平方表的首址,则变量与数据指针之和的地址单元中的内容就是变量的平方值。程序流程图如图 4.4 所示。
  采样 MOVC A,@A+PC 指令也可以实现查表功能,且不破坏 DPTR 的内容,从而可以减少保护 DPTR 的内容所需的开销。但表格只能存放在 MOVC A,@A+PC 指令后的 256 字节内,即表格存放的地点和空间有一定的限制。

三、简单运算

  由于 80C51 指令系统中只有单字节加法指令,因此对于多字节的相加运算必须从低位字节开始分字节进行。除最低字节可以使用 ADD 指令外,其它字节相加时要把低字节的进位考虑进去,这时就应该使用 ADDC 指令。
  例 双字节无符号数加法。
  设被加数存放在内部 RAM 的 51H、50H 单元,加数存放在内部 RAM 的61H、60H 单元,相加的结果存放在内部 RAM 的 51H、50H 单元,进位存放在位寻址区的 00H 位中。实现此功能的程序段如下:

		MOV	  R0,  #50H	;被加数的低字节地址
		MOV	  R1,  #60H	;加数的低字节地址
		MOV	  A,   @R0	    ;取被加数低字节
		ADD	  A,   @R1	    ;加上加数低字节
		MOV   @R0, A	    ;保存低字节相加结果
		INC	  R0	        ;指向被加数高字节
		INC	  R1	        ;指向加数高字节
		MOV	  A,   @R0	    ;取被加数高字节
		ADDC  A,   @R1	    ;加上加数高字节(带进位加)
		MOV	  @R0, A	    ;存高字节相加结果
		MOV   00H, C	    ;保存进位

4.3.2 分支程序

  通常情况下,程序的执行是按照指令在程序存储器中存放的顺序进行的, 但根据实际需要也可以改变程序的执行顺序,这种程序结构就属于分支结构。分支结构可以分成单分支、双分支和多分支几种情况。
  单分支结构如图 4.5 所示。若条件成立,则执行程序段 A,然后继续执行该指令下面的指令;如条件不成立,则不执行程序段 A,直接执行该指令的下条指令。
  双分支结构如图 4.6 所示。若条件成立,执行程序段 A;否则执行程序段 B。
  多分支结构如图 4.7 所示。先将分支按序号排列,然后按照序号的值来实现多分支选择。分支程序在单片机系统中有较多的应用,具体的实现上存在着许多技巧, 可以通过阅读一些典型的程序逐渐增加这方面的知识。

一、单分支程序

  例 求双字节补码。
  设在内部 RAM 的 addr1 和 addr+1 单元存有一个双字节数(高位字节存于高地址单元)。编写程序将其读出取补后再存入 addr2 和 addr2+1 单元。
  首先对低字节取补,然后判其结果是否为全“0”。若是,则高字节取补,否则高字节取反。程序段如下:

START: MOV	  R0,#addr1	;原码低字节地址送 R0
		MOV	  R1,#addr2	;补码低字节地址送 R1
		MOV	  A, @R0	    ;原码低字节送 A
		CPL	  A	            ;A 内容取补
		INC   A
		MOV	  @R1,A		;存补码低字节
		INC	  R0	        ;调整地址,指向下一单元
		INC	  R1
		JZ	  ZERO          ;(A)=0 时转 ZERO
        MOV	  A,@R0	    ;原码高字节送 A
        CPL   A
        MOV	  @R1,A	    ;高字节反码存入 addr2+1 单元
        SJMP  LOOP1
ZERO:  MOV   A,@R0	    ;高字节取补存入 addr2+1 单元
        CPL	  A	
        INC   A
        MOV   @R1,A	
LOOP1:RET	
二、双分支程序

  例设变量 x 以补码的形式存放在片内 RAM 的 30H 单元,变量 y 与 x 的关系是:当 x > 0 时,y = x;当 x = 0 时,y = 20H;当 x < 0 时,y = x + 5。编制程序,根据 x 的大小求 y 并送回原单元。程序段如下:

	START: MOV	A,30H
			JZ	NEXT
			ANL	A,#80H	;判断符号位
			JZ	LP
			MOV	A,#05H 
			ADD	A,30H 
			MOV	30H,A 
			SJMP	LP
	NEXT:  MOV 30H,#20H 
	LP:    SJMP $
三、多分支程序

  例根据 R7 的内容转向相应的处理程序。
  设R7 的内容为 0~N,对应的处理程序的入口地址分别为 PP0~PPN。程序段如下:

	START: MOV	 DPTR,#TAB   ;置分支入口地址表首址
			MOV	 A,R7	      ;分支转移序号送 A
			ADD	 A,R7	      ;分支转移序号乘以 2
			MOV	 R3,A	      ;暂存于 R3
			MOVC A,@A+DPTR
			XCH	 A,R3	      ;取高位地址
			INC  A
			MOVC A,@A+DPTR	  ;取低位地址
			MOV	 DPL,A	      ;处理程序入口地址低 8 位送 DPL
			MOV	 DPH,R3	  ;处理程序入口地址高 8 位送 DPL
			CLR	 A	
			JMP  @A+DPTR
	 TAB:  DW   PP0	
			DW	 PP1	
			DW	 PPN

4.3.3 循环程序

  在程序设计中,经常需要控制一部分指令重复执行若干次,以便用简短的程序完成大量的处理任务。这种按某种控制规律重复执行的程序称为循环程序。循环程序有先执行后判断和先判断后执行两种基本结构,如图 4.8 所示。
  图 4.8(a)为“先执行后判断”的循环程序结构图。其特点是一进入循环, 先执行循环处理部分,然后根据循环控制条件判断是否结束循环。若不结束, 则继续执行循环操作;若结束,则进行结束处理并退出循环。
  图 4.8(b)为“先判断后执行”的循环程序结构图。其特点是将循环的控制部分放在循环的入口处,先根据循环控制条件判断是否结束循环。若不结束, 则继续执行循环操作;若结束,则进行结束处理并退出循环。
在这里插入图片描述

一、先执行后判断

  例 50ms 延时程序。
  若晶振频率为 12MHz,则一个机器周期为 1μs。执行一条 DJNZ 指令需要2 个机器周期,即 2μs。采用循环计数法实现延时,循环次数可以通过计算获得,并选择先执行后判断的循环结构。程序段如下:

	DEL: MOV    R7,#2001 μs 
	DEL1:MOV    R6,#1231 μs
	      NOP	            ;1 μs
	DEL2:DJNZ   R6,DEL2    ;2 μs,计(2×123)μs
	      DJNZ   R7,DEL1    ;2 μs,计 [(2×12322)×200+1] μs,约 50ms 
	      RET

  例 无符号数排序程序。在片内 RAM 中,起始地址为 30H 的 8 个单元中存放有 8 个无符号数。试对这些无符号数进行升序排序。
  数据排序常用的方法是冒泡排序法。这种方法的过程类似水中气泡上浮, 故称冒泡法。执行时从前向后进行相邻数的比较,如数据的大小次序与要求的顺序不符就将这两个数互换,否则不互换。对于升序排序,通过这种相邻数的互换,使小数向前移动,大数向后移动。从前向后进行一次冒泡(相邻数的互换),就会把最大的数换到最后。再进行一次冒泡,就会把次大的数排在倒数第二的位置。
  设 R7 为比较次数计数器,初始值为 07H,位地址 00H 为数据互换标志位。程序段如下:

	START: CLR	 00H     	;互换标志清 0
			MOV	 R7,#07H	;各次冒泡比较次数
			MOV	 R0,#30H	;数据区首址
	LOOP:  MOV	 A,@R0	    ;取前数
			MOV	 2BH,A	    ;暂存
			INC  R0
			MOV	 2AH,@R0    ;取后数
			CLR  C
			SUBB A,@R0	    ;前数减后数
			JC   NEXT
			MOV	 @R0,2BH	;前数小于后数,不互换
			DEC  R0
			MOV	 @R0,2AH   ;两数交换
			INC	 R0	        ;准备下一次比较
			SETB 00H	    ;置互换标志
	NEXT:  DJNZ R7,LOOP	;进行下一次比较
			JB	 00H,START	;进行下一轮冒泡
			SJMP $	
二、先判断后执行

  例将内部 RAM 中起始地址为 data 的数据串传送到外部 RAM 中起始地址为 buffer 的存储区域内,直到发现‘$ ’字符停止传送。由于循环次数事先不知道,但循环条件可以测试到,所以,采用先判断后执行的结构比较适宜。程序段如下:

			MOV	R0,#data	
			MOV	DPTR,#buffer	
	LOOP0: MOV	A,@R0	
			CJNE	A,#24H,LOOP1	;判断是否为‘ $ ’字符
			SJMP	LOOP2	        ;是‘ $ ’字符,转结束
	LOOP1: MOVX	@DPTR,A        ;不是‘ $ ’字符,执行传送
			INC  R0
			INC DPTR	
			SJMP	LOOP0	        ;传送下一数据
	LOOP2:

4.3.4 子程序及其调用

  一、子程序的调用
  在实际应用中,经常会遇到一些带有通用性的问题,如数值转换、数值计算等,在一个程序中可能要使用多次。这时可以将其设计成通用的子程序供随时调用。利用子程序可以使程序结构更加紧凑,使程序的阅读和调试更加方便。
  子程序的结构与一般的程序并无多大区别,它的主要特点是,在执行过程中需要由其它程序来调用,执行完后又需要把执行流程返回到调用该子程序的主程序。
  子程序调用时要注意两点,一是现场的保护和恢复,二是主程序与子程序的参数传递。
  二、现场保护与恢复
  在子程序执行过程中常常要用到单片机的一些通用单元,如工作寄存器
R0~R7、累加器 A、数据指针 DPTR 以及有关标志和状态等。而这些单元中的内容在调用结束后的主程序中仍有用,所以需要进行保护,称为现场保护。在执行完子程序,返回继续执行主程序前恢复其原内容,称为现场恢复。保护与恢复的方法有以下两种:
  1.在主程序中实现
  其特点是结构灵活。示例如下:

	PUSH	PSW	;保护现场
	PUSH	ACC	;
	PUSH	B	;
	MOV	    PSW,#10H	;换当前工作寄存器组
	LCALL	addr16	;子程序调用
	POP	    B	;恢复现场
	POP	    ACC	;
	POP	    PSW	;

  2.在子程序中实现
  其特点是程序规范、清晰。示例如下:

	SUB1:  PUSH	PSW	;保护现场
	PUSH	ACC	;
	PUSH	B	;
	MOV	    PSW,#10H	;换当前工作寄存器组
	POP	    B	;恢复现场
	POP  	ACC	;
	POP  	PSW	;
	RET

  应注意的是,无论哪种方法保护与恢复的顺序都要对应,否则程序将会发生错误。
  三、参数传递
  由于子程序是主程序的一部分,所以,在程序的执行时必然要发生数据上的联系。在调用子程序时,主程序应通过某种方式把有关参数(即子程序的入口参数)传给子程序。当子程序执行完毕后,又需要通过某种方式把有关参数(即子程序的出口参数)传给主程序。在 80C51 单片机中,传递参数的方法有三种。
  1.利用累加器或寄存器
在这种方式中,要把预传递的参数存放在累加器 A 或工作寄存器 R0~R7 中,即在主程序调用子程序时,应事先把子程序需要的数据送入累加器 A 或指定的工作寄存器中,当子程序执行时,可以从指定的单元中取得数据,执行运算。反之,子程序也可以用同样的方法把结果传送给主程序。
  例 编写程序,实现 c=a2+b2。设 a、b、c 分别存于内部 RAM 的 30H、31H、32H 三个单元中。程序段如下:

	START:MOV	  A,30H	    ;取 a
	       ACALL  SQR	        ;调用查平方表
	       MOV	  R1,A	        ;a2 暂存于 R1 中
	       MOV	  A,31H	    ;取 b
	       ACALL  SQR	        ;调用查平方表
	       ADD	  A,R1	        ;a2+b2 存于 A 中
	       MOV	  32H,A	    ;存结果
	       SJMP	  $	
	SQR:  MOV	  DPTR,#TAB	;子程序
	       MOVC	  A,@A+DPTR	;
	       RET		
	TAB:  DB	  0149162536496481

  2.利用存储器
  当传送的数据量比较大时,可以利用存储器实现参数的传递。在这种方式中,事先要建立一个参数表,用指针指示参数表所在的位置。当参数表建立在内部 RAM 时,用 R0 或 R1 作参数表的指针。当参数表建立在外部 RAM 时, 用 DPTR 作参数表的指针。
  例 将 R0 和 R1 指向的内部 RAM 中两个 3 字节无符号整数相加,结果送到由 R0 指向的内部 RAM 中。入口时,R0 和 R1 分别指向加数和被加数的低位字节;出口时,R0 指向结果的高位字节。低字节在高地址,高字节在低地址。程序段如下:

	NADD: MOV    R7,#3	;三字节加法
	       CLR	  C	        ;
	NADD1:MOV    A,@R0	;取加数低字节
	       ADDC   A,@R1	;被加数低字节加 
	MOV    @R0,  A     	;
		   DEC	  R0
		   DEC	  R1
		   DJNZ   R7,NADD1 
		   INC	  R0
		   RET

  3.利用堆栈
  利用堆栈传递参数是在子程序嵌套中常采用的一种方法。在调用子程序前,用 PUSH 指令将子程序中所需数据压入堆栈。进入执行子程序时,再用 POP 指令从堆栈中弹出数据。
  例 把内部 RAM 中 20H 单元中的 1 个字节十六进制数转换为 2 位 ASCII码,存放在 R0 指示的两个单元中。程序段如下:

	MAIN:  MOV	A,20H	;
			SWAP  A
			PUSH ACC  ;参数入栈
			ACALL	HEASC	
			POP ACC
			MOV	@R0,A	        ;存高位十六进制数转换结果
			INC	R0	            ;修改指针
			PUSH	20H	        ;参数入栈
			ACALL	HEASC	
			POP	 ACC
			MOV  @R0,A    	    ;存低位十六进制数转换结果
	HEASC: MOV	R1,SP	        ;借用 R1 为堆栈指针
			DEC	R1	
			DEC	R1	            ;R1 指向被转换数据
			XCH	A,@R1	        ;取被转换数据
			ANL	A,#0FH	        ;取 1 位十六进制数
			ADD	A,#2	        ;偏移量调整,所加值为 MOVC 与 DB间字节数
			MOVC A,@A+PC       ;查表
			XCH	A,@R1	        ;1 字节指令,存结果于堆栈
			RET	                ;1 字节指令
	ASCTAB:DB  30H,31H,32H,33H,34H,35H,36H,37H 
	        DB	38H,39H,41H,42H,43H,44H,45H,46H

  一般说来,当相互传递的数据较少时,采用寄存器传递方式可以获得较快的传递速度。当相互传递的数据较多时,宜采用存储器或堆栈方式传递。如果是子程序嵌套,最好采用堆栈方式。

4. 4 常用程序举例

4.4.1 算术运算程序

  一般说来,单片机应用系统的任务就是对客观实际的各种物理参数进行测试和控制。所以,数据的运算是避免不了的。尽管数据运算并不是 80C51 单片机的优势所在,但运用一些编程技巧和方法,对于大部分测控应用中的运算,80C51 单片机还是能够胜任的。
一、多字节数的加、减运算
  80C51 单片机的指令系统提供的是字节运算指令,所以在处理多字节数的加减运算时,要合理地运用进位(借位)标志。
  例 多字节无符号数的加法。
  设两个 N 字节的无符号数分别存放在内部 RAM 中以 DATA1 和 DATA2 开始的单元中。相加后的结果要求存放在 DATA2 数据区。
  程序段如下:

		MOV	R0,#DATA1	    ;
		MOV	R1,#DATA2	    ;
		MOV	R7,#N	        ;置字节数
		CLR	C	            ;
LOOP:  MOV	A,@R0	;
		ADDC   A,@R1	    ;求和
		MOV	@R1,A	        ;存结果
		INC	R0	            ;修改指针
		INC	R1	            ;
		DJNZ	R7,LOOP	;

  二、多字节数乘法运算
  例 双字节无符号数的乘法。
  设双字节的无符号被乘数存放在 R3、R2 中,乘数存放在 R5、R4 中,R0 指向积的高位。算法及流程图如图 4.9 所示。
图 4.9 双字节无符号数乘法的算法与流程图
  程序段如下:

	MULTB: MOV	     R7,#04	;结果单元清 0
	LOOP:  MOV	     @R0,#00H	;
			DJNZ	 R7,LOOP  	;
			ACALL 	 BMUL	    ;
			SJMP     $
	BMUL:  MOV	     A,R2	    ;
			MOV	     B,R4	    ;
			MUL	     AB	        ;低位乘
			ACALL	 RADD	    ;
			MOV	     A,R2	    ;
			MOV	     B,R5	    ;
			MUL	     AB	        ;交叉乘
			DEC	     R0	        ;
			ACALL    RADD	    ;
			MOV	     A,R4	    ;
			MOV	     B,R3	    ;
			MUL	     AB	        ;交叉乘
			DEC	     R0	        ;
			DEC	     R0	        ;
			ACALL	 RADD    	;
			MOV	     A,R5	    ;
			MOV	     B,R3	    ;
			MUL	     AB	        ;高字节乘
			DEC	     R0	        ;
			ACALL	 RADD	    ;
			DEC	     R0	
			RET
	RADD:  ADD	     A,@R0	    ;
			MOV	     @R0,A	    ;
			MOV	     A,B	    ;
			INC	     R0	        ;
			ADDC	 A,@R0	    ;
			MOV	     @R0,A	    ;
			INC	     R0	        ;
			MOV	     A,@R0
			ADDC	 A,#00H
			MOV      @R0,A
			RET

4.4.2 码型转换程序

  单片机能识别和处理的是二进制码,而输入输出设备(如 LED 显示器、微型打印机等)则常使用 ASCII 码或 BCD 码。为此,在单片机应用系统中经常需要通过程序进行二进制码与 BCD 码或 ASCII 码的相互转换。
  由于二进制数与十六进制数有直接的对应关系,所以,为了书写和叙述方便,下面将用十六进制数代替二进制数。
  一、十六进制数与 ASCII 码间的转换
  十六进制数与 ASCII 码的对应关系如表 4.1 所示。由表可见,当十六进制数在 0~9 之间时,其对应的 ASCII 码值为该十六进制数加 30H;当十六进制数在 A~F 之间时,其对应的 ASCII 码值为该十六进制数加 37H。
表 4.1 十六进制数与 ASCII 码的关系表
  例 将 1 位十六进制数(即 4 位二进制数)转换成相应的 ASCII 码。
  设十六进制数存放在 R0 中,转换后的 ASCII 码存放于 R2 中。实现程序如下:

HASC:  MOV    A, R0	;取 4 位二进制数
		ANL	   A, #0FH	;屏蔽掉高 4 位
		PUSH   ACC	    ;4 位二进制数入栈
		CLR    C	    ;清进(借)位位
		SUBB   A,#0AH	;用借位位的状态判断该数在 09 还是A~F 之间
		POP	   ACC	    ;弹出原 4 位二进制数
		JC	   LOOP	    ;借位位为 1,跳转至 LOOP
		ADD	   A,#07H	;借位位为 0,该数在 A~F 之间,加 37H
LOOP:  ADD	   A,#30H	;该数在 09 之间,加 30H
		MOV	    R2,   A	    ;ASCII 码存于R2 
	    RET

  例 将多位十六进制数转换成 ASCII 码。
  设地址指针 R0 指向十六进制数低位,R2 中存放字节数,转换后地址指针R0 指向 ASCII 码的高位。R1 指向要存放的 ASCII 码的低位地址。实现程序如下:

	HTASC: MOV	  A,@R0	;取低 4 位二进制数
			ANL	  A,#0FH	;
			ADD	  A,#15	;偏移量修正
			MOVC  A,@A+PC	;查表
			MOV	  @R1,A	;存 ASCII 码
			INC	  R1	    ;
			MOV	  A ,@R0	;取十六进制高 4 位
			SWAP  A
			ANL	  A,#0FH	;
			ADD	  A,#06H	;偏移值修正
			MOVC  A,@A+PC	;
			MOV	  @R1,A
			INC	  R0	    ;指向下一单元
			INC	  R1	    ;
			DJNZ  R2,HTASC	;ASCII 码存于 R2 RET
	ASCTAB:DB	30H,31H,32H,33H,34H,35H,36H,37H
	        DB	38H,39H,41H,42H,43H,44H,45H,46H

  二、BCD 码与二进制数之间的转换
  在计算机中,十进制数要用 BCD 码来表示。通常,用 4 位二进制数表示一位 BCD 码,用 1 个字节表示 2 位 BCD 码(称为压缩型 BCD 码)。
  例 双字节二进制数转换成 BCD 码。
  设(R2R3)为双字节二进制数,(R4R5R6)为转换完的压缩型 BCD 码。

  实现程序如下:

	DCDTH: CLR	 A	       ;
			MOV	 R4,A	   ;R4 清 0
			MOV	 R5,A	   ;R5 清 0
			MOV	 R6,A	   ;R6 清 0
			MOV	 R7,#16   ;计数初值
	LOOP:  CLR	 C	       ;
			MOV	 A,R3	   ;
			RLC	 A	       ;
			MOV	 R3,A	   ;R3 左移一位并送回
			MOV	 A,R2	   ;
			RLC	 A	       ;
			MOV	 R2,A	   ;R2 左移一位并送回
			MOV	 A,R6	   ;
			ADDC A,R6	   ;
			DA	 A	       ;
			MOV	 R6,A	   ;(R6)乘 2 并调整后送回
			MOV	 A,R5	   ;
			ADDC A,R5	   ;
			DA	 A	       ;
			MOV	 R5,A	   ;(R5)乘 2 并调整后送回
			MOV  A,R4	   ;
			ADDC A,R4	   ;
			DA	 A	       ;
			MOV	 R4,A	   ;(R4)乘 2 并调整后送回
			DJNZ R7,LOOP  ;

本 章 小 结

  汇编语言的源程序结构紧凑、灵活,汇编成的目标程序效率高,具有占存储空间少、运行速度快、实时性强等优点。但因它是面向机器的语言,所以它缺乏通用性,编程复杂繁琐,但应用相当广泛。
  在进行程序设计时,首先需要对单片机应用系统预完成的任务进行深入的分析,明确系统的设计任务、功能要求、技术指标。然后,要对系统的硬件资源和工作环境进行分析和熟悉。经过分析、研究和明确规定后,利用数学方法或数学模型来对其进行描述,从而把一个实际问题转化成由计算机进行处理的问题。进而,对各种算法进行分析比较,并进行合理的优化。
  模块化的程序设计方法具有明显的优点,所以,进行程序设计的学习,开始时就应该建立起模块化的设计思想。采用循环结构和子程序可以使程序的容量大大减少,提高程序的效率,节省内存。
  80C51 汇编语言的语句行由 4 个字段组成,汇编程序能对这种格式正确地识别。伪指令是汇编程序能够识别的汇编命令,它不是单片机执行的指令,没有对应的机器码,仅用来对汇编过程进行某种控制。
  汇编语言程序设计是实践性较强的一种单片机应用技能,它需要较多的编程训练和实际应用经验的积累。本章仅列出了一些最为基本的程序段示例以供参考。

思考题及习题

  1. 80C51 单片机汇编语言有何特点?
  2. 利用 80C51 单片机汇编语言进行程序设计的步骤如何?
  3. 常用的程序结构有哪几种?特点如何?
  4. 子程序调用时,参数的传递方法有哪几种?
  5. 什么是伪指令?常用的伪指令功能如何?
  6. 设被加数存放在内部 RAM 的 20H、21H 单元,加数存放在 22H、23H 单元,若要求和存放在 24H、 25H 中,试编写出 16 位数相加的程序。
  7. 编写一段程序,把外部 RAM 中 1000H~1030H 单元的内容传送到内部 RAM 的30H~60H 单元中。
  8. 编写程序,实现双字节无符号数加法运算,要求 (R1R0)+(R7R6)→(61H60H)。
  9. 若 80C51 的晶振频率为 6MHz,试计算延时子程序的延时时间。DELAY:MOV R7,#0F6HLP:MOV R6,#0FAH DJNZ R6,$ DJNZ R7,LP RET
  10. 在内部 RAM 的 21H 单元开始存有一组单字节不带符号数,数据长度为 30H,要求找出最大数存入 IG 单元。
  11. 编写程序,把累加器 A 中的二进制数变换成 3 位 BCD 码,并将百、十、个位数分别存放在内部 RAM 的 50H、51H、52H 单元中。
  12. 编写子程序,将 R1 中的 2 个十六进制数转换为 ASCII 码后存放在 R3 和 R4 中。
  13. 编写程序,求内部 RAM 中 50H~59H 十个单元内容的平均值,并存放在 5AH 单元。

单片机的C 语言轻松入门 随着单片机开发技术的不断发展,目前已有越来越多的人从普遍使用汇编语言到逐渐使 用高级语言开发,其中主要是以C 语言为主,市场上几种常见的单片机均有其C 语言开发 环境。这里以最为流行的80C51 单片机为例来学习单片机的C 语言编程技术。 本书共分六章,每章一个专题,以一些待完成的任务为中心,围绕该任务介绍C 语言 的一些知识,每一个任务都是可以独立完成的,每完成一个任务,都能掌握一定的知识,等 到所有的任务都完成后,即可以完成C 语言的入门工作。 第1 章 C 语言概述及其开发环境的建立 学习一种编程语言,最重要的是建立一个练习环境,边学边练才能学好。Keil 软件是目 前最流行开发80C51 系列单片机的软件,Keil 提供了包括C 编译器、宏汇编、连接器、库 管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境 (μVision)将这些部份组合在一起。 在学会使用汇编语言后,学习C 语言编程是一件比较容易的事,我们将通过一系列的 实例介绍C 语言编程的方法。图1-1 所示电路图使用89S52 单片机作为主芯片,这种单片 机性属于80C51 系列,其内部有8K 的FLASH ROM,可以反复擦写,并有ISP 功能,支 持在线下载,非常适于做实验。89S52 的P1 引脚上接8 个发光二极管,P3.2~P3.4 引脚上接 4 个按钮开关,我们的任务是让接在P1 引脚上的发光二极管按要求发光。 1.1 简单的C 程序介绍 例1-1: 让接在P1.0 引脚上的LED 发光。 /************************************************* 平凡单片机工作室 http://www.mcustudio.com Copyright 2003 pingfan's mcustudio All rights Reserved 作者:周坚 dddl.c 单灯点亮程序 *************************************************/ 图1-1 接有LED 的单片机基本电路 P1.0 EA/VPP VCC XTAL2 XTAL1 GND RST +5V +5V + R1 E1 10K 10U 27P CY 27P PZ1 1K D8 D1 89××× #include “reg51.h” sbit P1_0=P1^0; void main() { P1_1=0; } 这个程序的作用是让接在P1.0 引脚上的LED 点亮。下面来分析一下这个C 语言程序包 含了哪些信息。 1)“文件包含”处理。 程序的第一行是一个“文件包含”处理。 所谓“文件包含”是指一个文件将另外一个文件的内容全部包含进来,所以这里的程序 虽然只有4 行,但C 编译器在处理的时候却要处理几十或几百行。这里程序中包含REG51.h 文件的目的是为了要使用P1 这个符号,即通知C 编译器,程序中所写的P1 是指80C51 单 片机的P1 端口而不是其它变量。这是如何做到的呢? 打开reg51.h 可以看到这样的一些内容: /*------------------------------------------------------------------------- REG51.H Header file for generic 80C51 and 80C31 microcontroller. Copyright (c) 1988-2001 Keil Elektronik GmbH and Keil Software, Inc. All rights reserved. --------------------------------------------------------------------------*/ /* BYTE Register */ sfr P0 = 0x80; sfr P1 = 0x90; sfr P2 = 0xA0; sfr P3 = 0xB0; sfr PSW = 0xD0; sfr ACC = 0xE0; sfr B = 0xF0; sfr SP = 0x81; sfr DPL = 0x82; sfr DPH = 0x83; sfr PCON = 0x87; sfr TCON = 0x88; sfr TMOD = 0x89; sfr TL0 = 0x8A; sfr TL1 = 0x8B; sfr TH0 = 0x8C; sfr TH1 = 0x8D; sfr IE = 0xA8; sfr IP = 0xB8; sfr SCON = 0x98; sfr SBUF = 0x99; /* BIT Register */ /* PSW */ sbit CY = 0xD7; sbit AC = 0xD6; sbit F0 = 0xD5; sbit RS1 = 0xD4; sbit RS0 = 0xD3; sbit OV = 0xD2; sbit P = 0xD0; /* TCON */ sbit TF1 = 0x8F; sbit TR1 = 0x8E; sbit TF0 = 0x8D; sbit TR0 = 0x8C; sbit IE1 = 0x8B; sbit IT1 = 0x8A; sbit IE0 = 0x89; sbit IT0 = 0x88; /* IE */ sbit EA = 0xAF; sbit ES = 0xAC; sbit ET1 = 0xAB; sbit EX1 = 0xAA; sbit ET0 = 0xA9; sbit EX0 = 0xA8; /* IP */ sbit PS = 0xBC; sbit PT1 = 0xBB; sbit PX1 = 0xBA; sbit PT0 = 0xB9; sbit PX0 = 0xB8; /* P3 */ sbit RD = 0xB7; sbit WR = 0xB6; sbit T1 = 0xB5; sbit T0 = 0xB4; sbit INT1 = 0xB3; sbit INT0 = 0xB2; sbit TXD = 0xB1; sbit RXD = 0xB0; /* SCON */ sbit SM0 = 0x9F; sbit SM1 = 0x9E; sbit SM2 = 0x9D; sbit REN = 0x9C; sbit TB8 = 0x9B; sbit RB8 = 0x9A; sbit TI = 0x99; sbit RI = 0x98; 熟悉80C51 内部结构的读者不难看出,这里都是一些符号的定义,即规定符号名与地 址的对应关系。注意其中有 sfr P1 = 0x90; 这样的一行(上文中用黑体表示),即定义P1 与地址0x90 对应,P1 口的地址就是0x90 (0x90 是C 语言中十六进制数的写法,相当于汇编语言中写90H)。 从这里还可以看到一个频繁出现的词:sfr sfr 并标准C 语言的关键字,而是Keil 为能直接访问80C51 中的SFR 而提供了一个新 的关键词,其用法是: sfrt 变量名=地址值。 2)符号P1_0 来表示P1.0 引脚。 在C 语言里,如果直接写P1.0,C 编译器并不能识别,而且P1.0 也不是一个合法的C 语言变量名,所以得给它另起一个名字,这里起的名为P1_0,可是P1_0 是不是就是P1.0 呢?你这么认为,C 编译器可不这么认为,所以必须给它们建立联系,这里使用了Keil C 的关键字sbit 来定义,sbit 的用法有三种: 第一种方法:sbit 位变量名=地址值 第二种方法:sbit 位变量名=SFR 名称^变量位地址值 第三种方法:sbit 位变量名=SFR 地址值^变量位地址值 如定义PSW 中的OV 可以用以下三种方法: sbit OV=0xd2 (1)说明:0xd2 是OV 的位地址值 sbit OV=PSW^2 (2)说明:其中PSW 必须先用sfr 定义好 sbit OV=0xD0^2 (3)说明:0xD0 就是PSW 的地址值 因此这里用sfr P1_0=P1^0;就是定义用符号P1_0 来表示P1.0 引脚,如果你愿意也可以 起P10 一类的名字,只要下面程序中也随之更改就行了。 3)main 称为“主函数”。 每一个C 语言程序有且只有一个主函数,函数后面一定有一对大括号“{}”,在大括号 里面书写其它程序。 从上面的分析我们了解了部分C 语言的特性,下面再看一个稍复杂一点的例子。 例1-2 让接在P1.0 引脚上的LED 闪烁发光 /************************************************* 平凡单片机工作室 http://www.mcustudio.com Copyright 2003 pingfan's mcustudio All rights Reserved 作者:周坚 ddss.c 单灯闪烁程序 *************************************************/ #include "reg51.h" #define uchar unsigned char #define uint unsigned int sbit P10=P1^0; /*延时程序 由Delay 参数确定延迟时间 */ void mDelay(unsigned int Delay) { unsigned int i; for(;Delay>0;Delay--) { for(i=0;i<124;i++) {;} } } void main() { for(;;) { P10=!P10; //取反P1.0 引脚 mDelay(1000); } } 程序分析:主程序main 中的第一行暂且不看,第二行是“P1_0=!P1_0;”,在P1_0 前有 一个符号“!”,符号“!”是C 语言的一个运算符,就像数学中的“+”、“-”一样,是一种 运算任号,意义是“取反”,即将该符号后面的那个变量的值取反。 注意:取反运算只是对变量的值而言的,并不会自动改变变量本身。可以认为C 编译 器在处理“!P1_0”时,将P1_0 的值给了一个临时变量,然后对这个临时变量取反,而不 是直接对P1_0 取反,因此取反完毕后还要使用赋值符号(“=”)将取反后的值再赋给P1_0, 这样,如果原来P1.0 是低电平(LED 亮),那么取反后,P1.0 就是高电平(LED 灭),反之, 如果P1.0 是高电平,取反后,P1.0 就是低电平,这条指令被反复地执行,接在P1.0 上灯就 会不断“亮”、“灭”。 该条指令会被反复执行的关键就在于main 中的第一行程序:for(;;),这里不对此作详细 的介绍,读者暂时只要知道,这行程序连同其后的一对大括号“{}”构成了一个无限循环语 句,该大括号内的语句会被反复执行。 第三行程序是:“mDelay(1000);”,这行程序的用途是延时1s 时间,由于单片机执行指 令的速度很快,如果不进行延时,灯亮之后马上就灭,灭了之后马上就亮,速度太快,人眼 根本无法分辨。 这里mDelay(1000)并不是由Keil C 提供的库函数,即你不能在任何情况下写这样一行 程序以实现延时。如果在编写其它程序时写上这么一行,会发现编译通不过。那么这里为什 么又是正确的呢?注意观察,可以发现这个程序中有void mDelay()这样一行,可见, mDelay 这个词是我们自己起的名字,并且为此编写了一些程序行,如果你的程序中没有这 么一段程序行,那就不能使用mDelay(1000)了。有人脑子快,可能马上想到,我可不可 以把这段程序也复制到我其它程序中,然后就可以用mDelay(1000)了呢?回答是,那当然 就可以了。还有一点需要说明,mDelay 这个名称是由编程者自己命名的,可自行更改,但 一旦更改了名称,main()函数中的名字也要作相应的更改。 mDelay 后面有一个小括号,小括号里有数据(1000),这个1000 被称之“参数”,用它 可以在一定范围内调整延时时间的长短,这里用1000 来要求延时时间为1000 毫秒,要做到 这一点,必须由我们自己编写的mDelay 那段程序决定的,详细情况在后面循环程序中再作 分析,这里就不介绍了。 1.2 Keil 工程的建立 要使用Keil 软件,首先要正确安装Keil 软件,该软件的Eval 版本可以直接去 http://www.keil.com 下载,安装时选择Eval Vision,其它步骤与一般Windows 程序安装类似, 这里就不再赘述了。安装完成后,将Ledkey.dll 文件复制到Keil 安装目录下的C51\BIN 文 件夹下,这是作者提供的键盘与LED 实验仿真板,可与Keil 软件配合,在计算机上模拟LED 和按键的功能。 启动μVison,点击“File
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

果果小师弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值