1
第 4章 80C51的程序设计
4.1 程序编制的方法和技巧
4.2 源程序的编辑和汇编
4.3 基本程序结构
4.4 常用程序举例
2
4.1 程序编制的方法和技巧
4.1.1程序编制的步骤
一、预完成任务的分析
首先,要对单片机应用系统预完成的
任务进行深入的分析,明确系统的设计任
务、功能要求和技术指标。其次,要对系
统的硬件资源和工作环境进行分析。这是
单片机应用系统程序设计的基础和条件。
3
二、进行算法的优化
算法是解决具体问题的方法 。一个应用系统经
过分析、研究和明确规定后,对应实现的功能和技
术指标可以利用严密的数学方法或数学模型来描述,
从而把一个实际问题转化成由计算机进行处理的问
题。 同一个问题的算法可以有多种,结果也可能不
尽相同,所以,应对各种算法进行分析比较,并进
行合理的优化。 比如,用迭代法解微分方程,需要
考虑收敛速度的快慢(即在一定的时间里能否达到
精度要求)。而有的问题则受内存容量的限制而对
时间要求并不苛刻。对于后一种情况,速度不快但
节省内存的算法则应是首选。
4
三、程序总体设计及流程图绘制
经过任务分析、算法优化后,就可以进
行程序的 总体构思,确定 程序的结构 和 数
据形式,并考虑 资源的分配 和 参数的计算
等。然后根据程序运行的过程,勾画出 程
序执行的逻辑顺序,用图形符号将总体设
计思路及程序流向绘制在平面图上,从而
使程序的结构关系直观明了,便于检查和
修改。
5
?清晰正确的流程图是编制正确无误的应用
程序的基础和条件。所以,绘制一个好的流
程图,是程序设计的一项重要内容。
?流程图可以分为 总流程图 和 局部流程图 。
总流程图侧重反映程序的逻辑结构和各程序
模块之间的相互关系。局部流程图反映程序
模块的具体实施细节。对于简单的应用程序,
可以不画流程图。 但是当程序较为复杂时,
绘制流程图是一个良好的编程习惯。
6
常用的流程图符号有:开始和结束符号、工
作任务符号、判断分支符号、程序连接符号、
程序流向符号等
开 始 或 结 束 符 号
工 作 任 务 符 号
判 断 分 支 符 号
程 序 连 接 符 号
程 序 流 向 符 号
程 序 流 向 符 号
7
4.1.2 编制程序的方法和技巧
一、采用模块化程序设计方法
?应用系统的程序一般由包含多个模块的主程序和各种
子程序组成。 各程序模块都要完成一个明确的任务,
实现某个具体的功能,如:发送、接收、延时、打印
和显示等。采用模块化的程序设计方法,就是将这些
不同的具体功能程序进行独立的设计和分别调试,最
后将这些模块程序装配成整体程序并进行联调。
?模块化的程序设计方法 具有明显的优点。把一个多功
能的复杂的程序划分为若干个简单的、功能单一的程
序模块,有利于程序的设计和调试,有利于程序的优
化和分工,提高了程序的阅读性和可靠性,使程序的
结构层次一目了然。
8
二、尽量采用循环结构和子程序
采用循环结构和子程序可以使程序的长度减少、
占用内存空间减少。
?多重循环,注意各重循环的初值和循环结束条
件,避免出现,死循环”现象;
?通用的子程序,除了用于存放子程序入口参数
的寄存器外,子程序中用到的其它寄存器的内
容应压入堆栈进行现场保护,并要特别注意堆
栈操作的压入和弹出的平衡;
?中断处理子程序 除了要保护程序中用到的寄存
器外,还应保护标志寄存器。
9
4.1.3 汇编语言的语句格式
语句行由四个字段组成,
[标号,]操作码 [操作数 ] [;注释 ]
括号内的部分可以根据实际情况取舍。每个
字段之间要用分隔符分隔,可以用作分隔符的
符号有空格、冒号、逗号、分号等。
如,LOOP,MOV A,# 7FH ; A←7FH
10
一、标号
标号是语句地址的标志符号,用于引导对该语句的
非顺序访问。 有关标号的规定 为:
? 标号由 1~ 8个 ASCII字符组成。第一个字符必须是字
母,其余字符可以是字母、数字或其他特定字符;
? 不能使用该汇编语言已经定义了的符号作为标号。如
指令助记符、寄存器符号名称等;
? 标号后边必须跟冒号。
二、操作码
操作码用于规定语句执行的操作。它是汇编语句中
唯一不能空缺的部分。它由指令助记符表示。
11
三、操作数
操作数用于给指令的操作提供数据或地址。在一条
汇编语句中操作数可能是空缺的,也可能包括一项,
还可能包括两项或三项。各操作数间以逗号分隔。操
作数字段的内容可能包括以下几种情况:
( 1)工作寄存器名;
( 2)特殊功能寄存器名;
( 3)标号名;
( 4)常数;
( 5)符号,$”,表示程序计数器 PC的当前值;
( 6)表达式。
12
四、注释
注释不属于汇编语句的功能部分,它只是对语句的
说明。注释字段可以增加程序的可读性,有助于编程
人员的阅读和维护。注释字段必须以分号“;”开头,
长度不限,当一行书写不下时,可以换行接着书写,
但换行时应注意在开头使用分号“;”。
五、数据的表示形式
80C51汇编语言的数据可以有以下几种表示形式:
? 二进制数,末尾以字母 B 标识。如,1000 1111B;
? 十进制数,末尾以字母 D 标识或将字母 D省略。如,88D,66;
? 十六进制数,末尾以字母 H 标识。如,78H,0A8H(但应注意的是,
十六进制数以字母 A~ F开头时应在其前面加上数字,0”。);
ASCII码,以单引号括起来标识。如:‘ AB’,‘ 1245’
13
4.2 源程序的编辑和汇编
一、源程序的编辑
源程序的编写要依据 80C51汇编语言的基本规则,
特别要用好常用的汇编命令(即伪指令),例如下
面的程序段:
ORG 0040H
MOV A,#7FH
MOV R1,#44H
END
这里的 ORG和 END是两条伪指令,其作用是告诉
汇编程序此汇编源程序的起止位置。编辑好的源程
序应以,,ASM”扩展名存盘,以备汇编程序调用。
4.2.1 源程序的编辑与汇编
14
二、源程序的汇编
将汇编语言源程序转换为单片机能执行的机器码形
式的目标程序的过程叫汇编 。常用的方法有两种:
?手工汇编时,把程序用助记符指令写出后,通过手
工方式查指令编码表,逐个把助记符指令翻译成机器
码,然后把得到的机器码程序(以十六进制形式)键
入到单片机开发机中,并进行调试。
?机器汇编是在常用的个人计算机 PC上,使用交叉汇
编程序将汇编语言源程序转换为机器码形式的目标程
序。汇编工作由计算机完成,生成的目标程序由 PC
机传送到开发机上,经调试无误后,再固化到单片机
的程序存储器 ROM中。
15
源程序经过机器汇编后,形成的若干文件
中含有两个主要文件,一是列表文件, 另一个
是目标码文件 。因汇编软件的不同,文件的格
式及信息会有一些不同。但主要信息如下:
列表文件主要信息为:
地 址 目标码 汇编程序
ORG 0040H
0040H 747F MOV A,#7FH
0042H 7944 MOV R1,#44H
END
目标码文件主要信息为:
首地址 末地址 目标码
0040H 0044H 747F7944
16
伪指令 是汇编程序能够识别并对汇编过程进行
某种控制的汇编命令。它不是单片机执行的指令,
所以没有对应的可执行目标码,汇编后产生的目
标程序中不会再出现伪指令。
一、起始地址设定伪指令 ORG
格式为:
ORG 表达式
该指令的 功能 是向汇编程序说明下面紧接的程序
段或数据段存放的起始地址。表达式通常为 16进
制地址,也可以是已定义的标号地址。
4.2.2 伪指令
17
ORG 8000H
START,MOV A,#30H
… …
此时规定该段程序的机器码从地址 8000H单元开
始存放。
在每一个汇编语言源程序的开始,都要设置一条
ORG伪指令来指定该程序在存储器中存放的起始位置。
若省略 ORG伪指令,则该程序段从 0000H单元开始存
放。在一个源程序中,可以多次使用 ORG伪指令规定
不同程序段或数据段存放的起始地址,但 要求地址值
由小到大依序排列,不允许空间重叠。
18
二、汇编结束伪指令 END
格式为:
END
该指令的 功能 是结束汇编。
汇编程序遇到 END伪指令后即结束汇编。处于
END之后的程序,汇编程序将不处理。
19
三、字节数据定义伪指令 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。
20
? 该伪指令常用于存放数据表格。如要存放显
示用的十六进制的字形码,可以用多条 DB
指令完成:
DB 0C0H,0F9H,0A4H,0B0H
DB 99H,92H,82H,0F8H
DB 80H,90H,88H,83H
DB C6H,A1H,86H,84H
21
四、字数据定义伪指令 DW
[标号,] DW 字数据表
功能 是从标号指定的地址单元开始,在程序存储器中
定义字数据。该伪指令将字或字表中的数据根据从左
到右的顺序依次存放在指定的存储单元中。应特别注
意,16位的二进制数,高 8位存放在低地址单元,低 8
位存放在高地址单元。例如:
ORG 1400H
DATA,DW 324AH,3CH
… …
汇编后,( 1400H) =32H,( 1401H) = 4AH,
( 1402H) =00H,( 1403H) =3CH。
22
五、空间定义伪指令 DS
[标号,] DS 表达式
功能 是从标号指定的地址单元开始,在程序存
储器中保留由表达式所指定的个数的存储单元
作为备用的空间,并都填以零值。例如:
ORG 3000H
BUF,DS 50
… …
汇编后,从地址 3000H开始保留 50个存储单元
作为备用单元。
23
六、赋值伪指令 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单元中。
24
七、位地址符号定义伪指令 BIT
格式为:
符号名 BIT 位地址表达式
功能 是将位地址赋给指定的符号名。其中,
位地址表达式可以是绝对地址,也可以是符
号地址。
例如:
ST BIT P1.0
将 P1.0的位地址赋给符号名 ST,在其后的编
程中就可以用 ST来代替 P1.0。
25
4.3 基本程序结构
4.3.1 顺序程序
顺序程序是指无分支、无循环结构的程序。其执行流程是
依指令在存储器中的存放顺序进行的。
一、数据传送
例 内部 RAM的 2AH~ 2EH单元中存储的数据如图所示。
试编写程序实现图示的数据传送结果。
7 8 H
5 6 H
3 4 H
1 2 H
0 0 H
2 E H
2 D H
2 C H
2 B H
2 A H
A C C
7 8 H
5 6 H
3 4 H
1 2 H
0 0 H
2 E H
2 D H
2 C H
2 B H
2 A H
A C C
0 0 H
26
方法一:
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个机器周期
27
方法二:
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个机器周期。实际应用中应尽量采用指令
代码字节数少、执行时间短的高效率程序,即 注意
程序的优化 。
28
例 有一变量存放在片内 RAM的
20H单元,其取值范围为,00H~
05H。要求编制一段程序,根据变量
值求其平方值,并存入片内 RAM的
21H单元。程序如下:
ORG 1000H
START,MOV DPTR,#2000H
MOV A,20H
MOVC A,@A+DPTR
MOV 21H,A
SJMP $
ORG 2000H
TABLE,DB 00,01,04,09,16,25
END。
→# 2 0 0 0 H D P T R
→( 2 0 H ) A
→(A+DPTR) A
→A 2 1 H
开始
结束
29
?在程序存储器的一片存储单元中建立起该变
量的平方表。用数据指针 DPTR指向平方表的
首址,则变量与数据指针之和的地址单元中
的内容就是变量的平方值。
?采样 MOVC A,@A+PC指令也可以实现查
表功能,且不破坏 DPTR的内容,从而可以减
少保护 DPTR的内容所需的开销。但表格只能
存放在 MOVC A,@A+PC指令后的 256字节
内,即表格存放的地点和空间有一定限制。
30
三、简单运算
由于 80C51指令系统中只有单字节加法指令,因
此对于多字节的相加运算必须从低位字节开始分
字节进行。除最低字节可以使用 ADD指令外,
其他字节相加时要把低字节的进位考虑进去,这
时就应该使用 ADDC指令。
例 双字节无符号数加法。
设被加数存放在内部 RAM的 51H,50H单元,加
数存放在内部 RAM的 61H,60H单元,相加的结
果存放在内部 RAM的 51H,50H单元,进位存放
在位寻址区的 00H位中。
31
程序段如下:
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 ;保存进位 。
32
4.3.2 分支程序
分支结构可以分成单分支、双分支和多分支几
种情况,
条 件 成 立?
下 条 指 令
程 序 段 A
Y
N
条 件 成 立?
程 序 段 A
Y
N
程 序 段 B
K=0?
分 支 0
Y
K=1?
Y
分 支 1
K=2?
Y
分 支 2
N
N
N
33
一、单分支程序
例 求双字节补码。
设在内部 RAM的 addr1和 addr+1单元存
有一个双字节数(高位字节存于高地址单
元)。编写程序将其读出取补后再存入
addr2和 addr2+1单元。
首先对低字节取补,然后判其结果是否为
全,0”。若是,则高字节取补,否则高字节
取反。
34
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
35
二、双分支程序
例 设变量 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 $
36
三、多分支程序
例 根据 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
37
4.3.3 循环程序
按某种控制规律重复执行的程序称为循环程序。循环程序
有 先执行后判断 和 先判断后执行 两种基本结构,
置 循 环 初 值
开 始
结 束
循 环 处 理
循 环 修 改
结 束 处 理
循 环 结 束?
Y
N
置 循 环 初 值
开 始
结 束
循 环 处 理
循 环 修 改
结 束 处 理
循 环 结 束?
Y
N
38
一、先执行后判断
例 50ms延时程序。
若晶振频率为 12MHz,则一个机器周期为 1μs。执行一条
DJNZ指令需要 2个机器周期,即 2μs。采用循环计数法实
现延时,循环次数可以通过计算获得,并选择先执行后判
断的循环结构。程序段如下:
DEL,MOV R7,#200 ; 1 μs
DEL1,MOV R6,#123 ; 1 μs
NOP ; 1 μs
DEL2,DJNZ R6,DEL2 ; 2μs,计( 2× 123) μs
DJNZ R7,DEL1 ; 2μs,
RET
共计 [( 2× 123+ 2+ 2) × 200+1] μs,即 50.001ms
39
例 无符号数排序程序。在片内 RAM中,起始地址
为 30H的 8个单元中存放有 8个无符号数。试对这些
无符号数进行升序排序。
?数据排序常用的方法是冒泡排序法。执行时从前向
后进行相邻数的比较,如数据的大小次序与要求的
顺序不符就将这两个数互换,否则不互换。对于升
序排序,通过这种相邻数的互换,使小数向前移动,
大数向后移动。从前向后进行一次冒泡(相邻数的
互换),就会把最大的数换到最后。再进行一次冒
泡,就会把次大的数排在倒数第二的位置。
?设 R7为比较次数计数器,初始值为 07H,位地址
00H为数据互换标志位。
40
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 $
41
二、先判断后执行
例 将内部 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,… …
42
4.3.4 子程序及其调用
一、子程序的调用
?在实际应用中,经常会遇到一些带有通用性的问题,
例如:数值转换、数值计算等,在一个程序中可能
要使用多次。这时可以将其设计成通用的子程序供
随时调用。
?子程序的结构与一般的程序并无多大区别,它的主
要特点是,在执行过程中需要由其它程序来调用,
执行完后又需要把执行流程返回到调用该子程序的
主程序。
子程序调用时要注意两点:一是现场的保护和恢复;
二是主程序与子程序的参数传递。
43
二、现场保护与恢复
在子程序执行过程中常常要用到单片机的
一些通用单元,如工作寄存器 R0~R7、累加
器 A、数据指针 DPTR,以及有关标志和状态
等。而这些单元中的内容在调用结束后的主
程序中仍有用,所以需要进行保护,称为 现
场保护 。在执行完子程序,返回继续执行主
程序前恢复其原内容,称为 现场恢复 。保护
与恢复的方法有以下两种:
?在主程序中实现;
?在子程序中实现。
44
1、在主程序中实现
示例如下:
PUSH PSW ;保护现场
PUSH ACC ;
PUSH B ;
MOV PSW,#10H ;换当前工作寄存器组
LCALL addr16 ;子程序调用
POP B ;恢复现场
POP ACC ;
POP PSW ;
… …
其特点是结构灵活。
45
2、在子程序中实现
示例如下:
SUB1,PUSH PSW ;保护现场
PUSH ACC ;
PUSH B ;
… …
MOV PSW,#10H ;换当前工作寄存器组
… …
POP B ;恢复现场
POP ACC ;
POP PSW ;
RET
其特点是程序规范、清晰。
注意,无论哪种方法保护与恢复的 顺序要对应。
46
三、参数传递
由于子程序是主程序的一部分,所以,在程序的执行时必
然要发生数据上的联系。在调用子程序时,主程序应通过某
种方式把有关参数(即子程序的入口参数)传给子程序,当
子程序执行完毕后,又需要通过某种方式把有关参数(即子
程序的出口参数)传给主程序。在 80C51单片机中,传递参
数的方法有三种:
1、利用累加器或寄存器
在这种方式中,要把预传递的参数存放在累加器 A或工作
寄存器 R0~R7中。即在主程序调用子程序时,应事先把子程
序需要的数据送入累加器 A或指定的工作寄存器中,当子程
序执行时,可以从指定的单元中取得数据,执行运算。反之,
子程序也可以用同样的方法把结果传送给主程序。
47
例 编写程序,实现 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 0,1,4,9,16, 25,36,49,64,81
48
2、利用存储器
当传送的 数据量比较大 时,可以利用存储器实现
参数的传递。在这种方式中,事先要建立一个参数
表,用指针指示参数表所在的位置。当参数表建立
在内部 RAM时,用 R0或 R1作参数表的指针。当参
数表建立在外部 RAM时,用 DPTR作参数表的指针。
例 将 R0和 R1指向的内部 RAM 中两个 3字节无
符号整数相加,结果送到由 R0指向的内部 RAM中。
入口时,R0和 R1分别指向加数和被加数的低位字
节;出口时,R0指向结果的高位字节。低字节在
高地址,高字节在低地址。
49
实现程序:
NADD,MOV R7,#3 ;三字节加法
CLR C ;
NADD1,MOV A,@R0 ;取加数低字节
ADDC A,@R1 ;被加数低字节加 A
MOV @R0,A ;
DEC R0
DEC R1
DJNZ R7,NADD1
INC R0
RET
50
3、利用堆栈
利用堆栈传递参数是在 子程序嵌套中 常采
用的一种方法。在调用子程序前,用 PUSH
指令将子程序中所需数据压入堆栈,进入执
行子程序时,再用 POP指令从堆栈中弹出数
据。
例 把内部 RAM中 20H单元中的 1个字节十
六进制数转换为 2位 ASCII码,存放在 R0指示
的两个单元中。
51
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 ;存低位十六进制数转换结果
SJMP $
HEASC,MOV R1,SP ;借用 R1为堆栈指针
DEC R1
DEC R1 ; R1指向被转换数据
XCH A,@R1 ;取被转换数据
ANL A,#0FH ;取一位十六进制数
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
52
一般说来:
?当相互传递的数据较少时,采用寄存器
传递方式可以获得较快的传递速度;
?当相互传递的数据较多时,宜采用存储
器或堆栈方式传递;
?如果是子程序嵌套时,最好是采用堆栈
方式。
53
4.4 常用程序举例
4.4.1 算术运算程序
一、多字节数的加、减运算
80C51单片机的指令系统提供的是字节运算指
令,所以在处理多字节数的加减运算时,要合理地
运用进位(借位)标志。
例 多字节无符号数的加法。
设两个 N字节的无符号数分别存放在内部 RAM中以
DATA1和 DATA2开始的单元中。相加后的结果要
求存放在 DATA2数据区。
54
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 ;
55
二、多字节数乘法运算
例 双字节无符号数的乘法。
设双字节的无符号被乘数存放在 R3,R2中,乘数存放在
R5,R4中,R0指向积的高位。
R 2
R 3
R 4R 5
R 2 R 4R 2 R 4
R 2 R 5
R 2 R 5
R 3 R 4 R 3 R 4
R 3 R 5R 3 R 5
L
L
L
×
+
积,
R 0
L
开 始
结 束
R 2 * R 4 累 加 到 结 果 单 元
R 2 * R 5 累 加 到 结 果 单 元
R 3 * R 4 累 加 到 结 果 单 元
R 3 * R 5 累 加 到 结 果 单 元
56
例 多字节无符号数的减法。
设两个 N字节的无符号数分别存放在内部 RAM
中以 DATA1和 DATA2开始的单元中。相减后的结
果要求存放在 DATA2数据区 。
MOV R0,#DATA1 ;
MOV R1,#DATA2 ;
MOV R7,#N ;置字节数
CLR C ;
LOOP,MOV A,@R0 ;
SUBB A,@R1 ;求差
MOV @R1,A ;存结果
INC R0 ;修改指针
INC R1 ;
DJNZ R7,LOOP ;
57
主程序段如下:
MULTB,MOV R7,#04 ;结果单元清 0
LOOP,MOV @R0,#00H ;
DJNZ R7,LOOP ;
DEC R0
ACALL BMUL ;
SJMP $
另有 2段子程序:
BMUL
RADD(在 BMUL中被调用)
58
先看子程序段:
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
59
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
60
一、十六进制数与 ASCII码间的转换
十六进制数与 ASCII码的对应关系如表所示。当十六进
制数在 0~ 9之间时,其对应的 ASCII码值为该十六进制数加
30H;当十六进制数在 A~ F之间时,其对应的 ASCII码值为该
十六进制数加 37H。
4.4.2 码型转换
61
例 将 1位十六进制数转换成相应的 ASCII码。
设十六进制数存放在 R0中,转换后的 ASCII 码存放
于 R2中。实现程序如下:
HASC,MOV A,R0 ;取 4位二进制数
ANL A,#0FH ;屏蔽掉高 4位
PUSH ACC ; 4位二进制数入栈
CLR C ;清进(借)位位
SUBB A,#0AH ; 用借位位的状态判断该数在 0~ 9还是 A~ F之间
POP ACC ;弹出原 4位二进制数
JC LOOP ;借位位为 1,跳转至 LOOP
ADD A,#07H ; 借位位为 0,该数在 A~ F之间,加 37H
LOOP,ADD A,#30H ;该数在 0~ 9之间,加 30H
MOV R2,A ; ASCII码存于 R2
RET
62
例 将多位十六进制数转换成 ASCII码。
设地址指针 R0指向十六进制数低位,R2中
存放字节数,转换后地址指针 R0指向 十六进制
数 的高位。 R1指向要存放的 ASCII码的 高 位地
址。实现程序如下:
63
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 ; 字节数 存于 R2
RET
ASCTAB,DB 30H,31H,32H,33H,34H,35H,36H,37H
DB 38H,39H,41H,42H,43H,44H,45H,46H
64
二,BCD码与二进制数之间的转换
在计算机中,十进制数要用 BCD码来表示。通常,用
四位二进制数表示一位 BCD码,用 1个字节表示 2位
BCD码(称为压缩型 BCD码)。
例 双字节二进制数转换成 BCD码。
设( R2R3)为双字节二进制数,( R4R5R6)为
转换完的压缩型 BCD码。
十进制数 B与一个 8位的二进制数的关系可以表示为:
只要依十进制运算法则,将 bi( i= 7,6,… …, 1,
0)按权相加,就可以得到对应的十进制数 B。(逐次
得到,b7× 20; b7× 21+ b6× 20; b7× 22+ b6× 21
+ b5× 20 ; … )。
65
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 ;
66
思考题及习题
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)。
67
9、若 80C51的晶振频率为 6MHz,试计算延时子程序的延时时
间。。
DELAY,MOV R7,#0F6H
LP,MOV R6,#0FAH
DJNZ R6,$
DJNZ R7,LP
RET
10、在内部 RAM 的 21H单元开始存有一组单字节不带符号数,
数据长度为 30H,要求找出最大数存入 BIG单元。
11、编写程序,把累加器 A中的二进制数变换成 3位 BCD码,并
将百、十、个位数分别存放在内部 RAM的 50H,51H,52H中。
12、编写子程序,将 R1中的 2个十六进制数转换为 ASCII码后存
放在 R3和 R4中。
13、编写程序,求内部 RAM中 50H~59H十个单元内容的平均
值,并存放在 5AH单元。
第 4章 80C51的程序设计
4.1 程序编制的方法和技巧
4.2 源程序的编辑和汇编
4.3 基本程序结构
4.4 常用程序举例
2
4.1 程序编制的方法和技巧
4.1.1程序编制的步骤
一、预完成任务的分析
首先,要对单片机应用系统预完成的
任务进行深入的分析,明确系统的设计任
务、功能要求和技术指标。其次,要对系
统的硬件资源和工作环境进行分析。这是
单片机应用系统程序设计的基础和条件。
3
二、进行算法的优化
算法是解决具体问题的方法 。一个应用系统经
过分析、研究和明确规定后,对应实现的功能和技
术指标可以利用严密的数学方法或数学模型来描述,
从而把一个实际问题转化成由计算机进行处理的问
题。 同一个问题的算法可以有多种,结果也可能不
尽相同,所以,应对各种算法进行分析比较,并进
行合理的优化。 比如,用迭代法解微分方程,需要
考虑收敛速度的快慢(即在一定的时间里能否达到
精度要求)。而有的问题则受内存容量的限制而对
时间要求并不苛刻。对于后一种情况,速度不快但
节省内存的算法则应是首选。
4
三、程序总体设计及流程图绘制
经过任务分析、算法优化后,就可以进
行程序的 总体构思,确定 程序的结构 和 数
据形式,并考虑 资源的分配 和 参数的计算
等。然后根据程序运行的过程,勾画出 程
序执行的逻辑顺序,用图形符号将总体设
计思路及程序流向绘制在平面图上,从而
使程序的结构关系直观明了,便于检查和
修改。
5
?清晰正确的流程图是编制正确无误的应用
程序的基础和条件。所以,绘制一个好的流
程图,是程序设计的一项重要内容。
?流程图可以分为 总流程图 和 局部流程图 。
总流程图侧重反映程序的逻辑结构和各程序
模块之间的相互关系。局部流程图反映程序
模块的具体实施细节。对于简单的应用程序,
可以不画流程图。 但是当程序较为复杂时,
绘制流程图是一个良好的编程习惯。
6
常用的流程图符号有:开始和结束符号、工
作任务符号、判断分支符号、程序连接符号、
程序流向符号等
开 始 或 结 束 符 号
工 作 任 务 符 号
判 断 分 支 符 号
程 序 连 接 符 号
程 序 流 向 符 号
程 序 流 向 符 号
7
4.1.2 编制程序的方法和技巧
一、采用模块化程序设计方法
?应用系统的程序一般由包含多个模块的主程序和各种
子程序组成。 各程序模块都要完成一个明确的任务,
实现某个具体的功能,如:发送、接收、延时、打印
和显示等。采用模块化的程序设计方法,就是将这些
不同的具体功能程序进行独立的设计和分别调试,最
后将这些模块程序装配成整体程序并进行联调。
?模块化的程序设计方法 具有明显的优点。把一个多功
能的复杂的程序划分为若干个简单的、功能单一的程
序模块,有利于程序的设计和调试,有利于程序的优
化和分工,提高了程序的阅读性和可靠性,使程序的
结构层次一目了然。
8
二、尽量采用循环结构和子程序
采用循环结构和子程序可以使程序的长度减少、
占用内存空间减少。
?多重循环,注意各重循环的初值和循环结束条
件,避免出现,死循环”现象;
?通用的子程序,除了用于存放子程序入口参数
的寄存器外,子程序中用到的其它寄存器的内
容应压入堆栈进行现场保护,并要特别注意堆
栈操作的压入和弹出的平衡;
?中断处理子程序 除了要保护程序中用到的寄存
器外,还应保护标志寄存器。
9
4.1.3 汇编语言的语句格式
语句行由四个字段组成,
[标号,]操作码 [操作数 ] [;注释 ]
括号内的部分可以根据实际情况取舍。每个
字段之间要用分隔符分隔,可以用作分隔符的
符号有空格、冒号、逗号、分号等。
如,LOOP,MOV A,# 7FH ; A←7FH
10
一、标号
标号是语句地址的标志符号,用于引导对该语句的
非顺序访问。 有关标号的规定 为:
? 标号由 1~ 8个 ASCII字符组成。第一个字符必须是字
母,其余字符可以是字母、数字或其他特定字符;
? 不能使用该汇编语言已经定义了的符号作为标号。如
指令助记符、寄存器符号名称等;
? 标号后边必须跟冒号。
二、操作码
操作码用于规定语句执行的操作。它是汇编语句中
唯一不能空缺的部分。它由指令助记符表示。
11
三、操作数
操作数用于给指令的操作提供数据或地址。在一条
汇编语句中操作数可能是空缺的,也可能包括一项,
还可能包括两项或三项。各操作数间以逗号分隔。操
作数字段的内容可能包括以下几种情况:
( 1)工作寄存器名;
( 2)特殊功能寄存器名;
( 3)标号名;
( 4)常数;
( 5)符号,$”,表示程序计数器 PC的当前值;
( 6)表达式。
12
四、注释
注释不属于汇编语句的功能部分,它只是对语句的
说明。注释字段可以增加程序的可读性,有助于编程
人员的阅读和维护。注释字段必须以分号“;”开头,
长度不限,当一行书写不下时,可以换行接着书写,
但换行时应注意在开头使用分号“;”。
五、数据的表示形式
80C51汇编语言的数据可以有以下几种表示形式:
? 二进制数,末尾以字母 B 标识。如,1000 1111B;
? 十进制数,末尾以字母 D 标识或将字母 D省略。如,88D,66;
? 十六进制数,末尾以字母 H 标识。如,78H,0A8H(但应注意的是,
十六进制数以字母 A~ F开头时应在其前面加上数字,0”。);
ASCII码,以单引号括起来标识。如:‘ AB’,‘ 1245’
13
4.2 源程序的编辑和汇编
一、源程序的编辑
源程序的编写要依据 80C51汇编语言的基本规则,
特别要用好常用的汇编命令(即伪指令),例如下
面的程序段:
ORG 0040H
MOV A,#7FH
MOV R1,#44H
END
这里的 ORG和 END是两条伪指令,其作用是告诉
汇编程序此汇编源程序的起止位置。编辑好的源程
序应以,,ASM”扩展名存盘,以备汇编程序调用。
4.2.1 源程序的编辑与汇编
14
二、源程序的汇编
将汇编语言源程序转换为单片机能执行的机器码形
式的目标程序的过程叫汇编 。常用的方法有两种:
?手工汇编时,把程序用助记符指令写出后,通过手
工方式查指令编码表,逐个把助记符指令翻译成机器
码,然后把得到的机器码程序(以十六进制形式)键
入到单片机开发机中,并进行调试。
?机器汇编是在常用的个人计算机 PC上,使用交叉汇
编程序将汇编语言源程序转换为机器码形式的目标程
序。汇编工作由计算机完成,生成的目标程序由 PC
机传送到开发机上,经调试无误后,再固化到单片机
的程序存储器 ROM中。
15
源程序经过机器汇编后,形成的若干文件
中含有两个主要文件,一是列表文件, 另一个
是目标码文件 。因汇编软件的不同,文件的格
式及信息会有一些不同。但主要信息如下:
列表文件主要信息为:
地 址 目标码 汇编程序
ORG 0040H
0040H 747F MOV A,#7FH
0042H 7944 MOV R1,#44H
END
目标码文件主要信息为:
首地址 末地址 目标码
0040H 0044H 747F7944
16
伪指令 是汇编程序能够识别并对汇编过程进行
某种控制的汇编命令。它不是单片机执行的指令,
所以没有对应的可执行目标码,汇编后产生的目
标程序中不会再出现伪指令。
一、起始地址设定伪指令 ORG
格式为:
ORG 表达式
该指令的 功能 是向汇编程序说明下面紧接的程序
段或数据段存放的起始地址。表达式通常为 16进
制地址,也可以是已定义的标号地址。
4.2.2 伪指令
17
ORG 8000H
START,MOV A,#30H
… …
此时规定该段程序的机器码从地址 8000H单元开
始存放。
在每一个汇编语言源程序的开始,都要设置一条
ORG伪指令来指定该程序在存储器中存放的起始位置。
若省略 ORG伪指令,则该程序段从 0000H单元开始存
放。在一个源程序中,可以多次使用 ORG伪指令规定
不同程序段或数据段存放的起始地址,但 要求地址值
由小到大依序排列,不允许空间重叠。
18
二、汇编结束伪指令 END
格式为:
END
该指令的 功能 是结束汇编。
汇编程序遇到 END伪指令后即结束汇编。处于
END之后的程序,汇编程序将不处理。
19
三、字节数据定义伪指令 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。
20
? 该伪指令常用于存放数据表格。如要存放显
示用的十六进制的字形码,可以用多条 DB
指令完成:
DB 0C0H,0F9H,0A4H,0B0H
DB 99H,92H,82H,0F8H
DB 80H,90H,88H,83H
DB C6H,A1H,86H,84H
21
四、字数据定义伪指令 DW
[标号,] DW 字数据表
功能 是从标号指定的地址单元开始,在程序存储器中
定义字数据。该伪指令将字或字表中的数据根据从左
到右的顺序依次存放在指定的存储单元中。应特别注
意,16位的二进制数,高 8位存放在低地址单元,低 8
位存放在高地址单元。例如:
ORG 1400H
DATA,DW 324AH,3CH
… …
汇编后,( 1400H) =32H,( 1401H) = 4AH,
( 1402H) =00H,( 1403H) =3CH。
22
五、空间定义伪指令 DS
[标号,] DS 表达式
功能 是从标号指定的地址单元开始,在程序存
储器中保留由表达式所指定的个数的存储单元
作为备用的空间,并都填以零值。例如:
ORG 3000H
BUF,DS 50
… …
汇编后,从地址 3000H开始保留 50个存储单元
作为备用单元。
23
六、赋值伪指令 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单元中。
24
七、位地址符号定义伪指令 BIT
格式为:
符号名 BIT 位地址表达式
功能 是将位地址赋给指定的符号名。其中,
位地址表达式可以是绝对地址,也可以是符
号地址。
例如:
ST BIT P1.0
将 P1.0的位地址赋给符号名 ST,在其后的编
程中就可以用 ST来代替 P1.0。
25
4.3 基本程序结构
4.3.1 顺序程序
顺序程序是指无分支、无循环结构的程序。其执行流程是
依指令在存储器中的存放顺序进行的。
一、数据传送
例 内部 RAM的 2AH~ 2EH单元中存储的数据如图所示。
试编写程序实现图示的数据传送结果。
7 8 H
5 6 H
3 4 H
1 2 H
0 0 H
2 E H
2 D H
2 C H
2 B H
2 A H
A C C
7 8 H
5 6 H
3 4 H
1 2 H
0 0 H
2 E H
2 D H
2 C H
2 B H
2 A H
A C C
0 0 H
26
方法一:
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个机器周期
27
方法二:
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个机器周期。实际应用中应尽量采用指令
代码字节数少、执行时间短的高效率程序,即 注意
程序的优化 。
28
例 有一变量存放在片内 RAM的
20H单元,其取值范围为,00H~
05H。要求编制一段程序,根据变量
值求其平方值,并存入片内 RAM的
21H单元。程序如下:
ORG 1000H
START,MOV DPTR,#2000H
MOV A,20H
MOVC A,@A+DPTR
MOV 21H,A
SJMP $
ORG 2000H
TABLE,DB 00,01,04,09,16,25
END。
→# 2 0 0 0 H D P T R
→( 2 0 H ) A
→(A+DPTR) A
→A 2 1 H
开始
结束
29
?在程序存储器的一片存储单元中建立起该变
量的平方表。用数据指针 DPTR指向平方表的
首址,则变量与数据指针之和的地址单元中
的内容就是变量的平方值。
?采样 MOVC A,@A+PC指令也可以实现查
表功能,且不破坏 DPTR的内容,从而可以减
少保护 DPTR的内容所需的开销。但表格只能
存放在 MOVC A,@A+PC指令后的 256字节
内,即表格存放的地点和空间有一定限制。
30
三、简单运算
由于 80C51指令系统中只有单字节加法指令,因
此对于多字节的相加运算必须从低位字节开始分
字节进行。除最低字节可以使用 ADD指令外,
其他字节相加时要把低字节的进位考虑进去,这
时就应该使用 ADDC指令。
例 双字节无符号数加法。
设被加数存放在内部 RAM的 51H,50H单元,加
数存放在内部 RAM的 61H,60H单元,相加的结
果存放在内部 RAM的 51H,50H单元,进位存放
在位寻址区的 00H位中。
31
程序段如下:
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 ;保存进位 。
32
4.3.2 分支程序
分支结构可以分成单分支、双分支和多分支几
种情况,
条 件 成 立?
下 条 指 令
程 序 段 A
Y
N
条 件 成 立?
程 序 段 A
Y
N
程 序 段 B
K=0?
分 支 0
Y
K=1?
Y
分 支 1
K=2?
Y
分 支 2
N
N
N
33
一、单分支程序
例 求双字节补码。
设在内部 RAM的 addr1和 addr+1单元存
有一个双字节数(高位字节存于高地址单
元)。编写程序将其读出取补后再存入
addr2和 addr2+1单元。
首先对低字节取补,然后判其结果是否为
全,0”。若是,则高字节取补,否则高字节
取反。
34
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
35
二、双分支程序
例 设变量 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 $
36
三、多分支程序
例 根据 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
37
4.3.3 循环程序
按某种控制规律重复执行的程序称为循环程序。循环程序
有 先执行后判断 和 先判断后执行 两种基本结构,
置 循 环 初 值
开 始
结 束
循 环 处 理
循 环 修 改
结 束 处 理
循 环 结 束?
Y
N
置 循 环 初 值
开 始
结 束
循 环 处 理
循 环 修 改
结 束 处 理
循 环 结 束?
Y
N
38
一、先执行后判断
例 50ms延时程序。
若晶振频率为 12MHz,则一个机器周期为 1μs。执行一条
DJNZ指令需要 2个机器周期,即 2μs。采用循环计数法实
现延时,循环次数可以通过计算获得,并选择先执行后判
断的循环结构。程序段如下:
DEL,MOV R7,#200 ; 1 μs
DEL1,MOV R6,#123 ; 1 μs
NOP ; 1 μs
DEL2,DJNZ R6,DEL2 ; 2μs,计( 2× 123) μs
DJNZ R7,DEL1 ; 2μs,
RET
共计 [( 2× 123+ 2+ 2) × 200+1] μs,即 50.001ms
39
例 无符号数排序程序。在片内 RAM中,起始地址
为 30H的 8个单元中存放有 8个无符号数。试对这些
无符号数进行升序排序。
?数据排序常用的方法是冒泡排序法。执行时从前向
后进行相邻数的比较,如数据的大小次序与要求的
顺序不符就将这两个数互换,否则不互换。对于升
序排序,通过这种相邻数的互换,使小数向前移动,
大数向后移动。从前向后进行一次冒泡(相邻数的
互换),就会把最大的数换到最后。再进行一次冒
泡,就会把次大的数排在倒数第二的位置。
?设 R7为比较次数计数器,初始值为 07H,位地址
00H为数据互换标志位。
40
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 $
41
二、先判断后执行
例 将内部 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,… …
42
4.3.4 子程序及其调用
一、子程序的调用
?在实际应用中,经常会遇到一些带有通用性的问题,
例如:数值转换、数值计算等,在一个程序中可能
要使用多次。这时可以将其设计成通用的子程序供
随时调用。
?子程序的结构与一般的程序并无多大区别,它的主
要特点是,在执行过程中需要由其它程序来调用,
执行完后又需要把执行流程返回到调用该子程序的
主程序。
子程序调用时要注意两点:一是现场的保护和恢复;
二是主程序与子程序的参数传递。
43
二、现场保护与恢复
在子程序执行过程中常常要用到单片机的
一些通用单元,如工作寄存器 R0~R7、累加
器 A、数据指针 DPTR,以及有关标志和状态
等。而这些单元中的内容在调用结束后的主
程序中仍有用,所以需要进行保护,称为 现
场保护 。在执行完子程序,返回继续执行主
程序前恢复其原内容,称为 现场恢复 。保护
与恢复的方法有以下两种:
?在主程序中实现;
?在子程序中实现。
44
1、在主程序中实现
示例如下:
PUSH PSW ;保护现场
PUSH ACC ;
PUSH B ;
MOV PSW,#10H ;换当前工作寄存器组
LCALL addr16 ;子程序调用
POP B ;恢复现场
POP ACC ;
POP PSW ;
… …
其特点是结构灵活。
45
2、在子程序中实现
示例如下:
SUB1,PUSH PSW ;保护现场
PUSH ACC ;
PUSH B ;
… …
MOV PSW,#10H ;换当前工作寄存器组
… …
POP B ;恢复现场
POP ACC ;
POP PSW ;
RET
其特点是程序规范、清晰。
注意,无论哪种方法保护与恢复的 顺序要对应。
46
三、参数传递
由于子程序是主程序的一部分,所以,在程序的执行时必
然要发生数据上的联系。在调用子程序时,主程序应通过某
种方式把有关参数(即子程序的入口参数)传给子程序,当
子程序执行完毕后,又需要通过某种方式把有关参数(即子
程序的出口参数)传给主程序。在 80C51单片机中,传递参
数的方法有三种:
1、利用累加器或寄存器
在这种方式中,要把预传递的参数存放在累加器 A或工作
寄存器 R0~R7中。即在主程序调用子程序时,应事先把子程
序需要的数据送入累加器 A或指定的工作寄存器中,当子程
序执行时,可以从指定的单元中取得数据,执行运算。反之,
子程序也可以用同样的方法把结果传送给主程序。
47
例 编写程序,实现 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 0,1,4,9,16, 25,36,49,64,81
48
2、利用存储器
当传送的 数据量比较大 时,可以利用存储器实现
参数的传递。在这种方式中,事先要建立一个参数
表,用指针指示参数表所在的位置。当参数表建立
在内部 RAM时,用 R0或 R1作参数表的指针。当参
数表建立在外部 RAM时,用 DPTR作参数表的指针。
例 将 R0和 R1指向的内部 RAM 中两个 3字节无
符号整数相加,结果送到由 R0指向的内部 RAM中。
入口时,R0和 R1分别指向加数和被加数的低位字
节;出口时,R0指向结果的高位字节。低字节在
高地址,高字节在低地址。
49
实现程序:
NADD,MOV R7,#3 ;三字节加法
CLR C ;
NADD1,MOV A,@R0 ;取加数低字节
ADDC A,@R1 ;被加数低字节加 A
MOV @R0,A ;
DEC R0
DEC R1
DJNZ R7,NADD1
INC R0
RET
50
3、利用堆栈
利用堆栈传递参数是在 子程序嵌套中 常采
用的一种方法。在调用子程序前,用 PUSH
指令将子程序中所需数据压入堆栈,进入执
行子程序时,再用 POP指令从堆栈中弹出数
据。
例 把内部 RAM中 20H单元中的 1个字节十
六进制数转换为 2位 ASCII码,存放在 R0指示
的两个单元中。
51
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 ;存低位十六进制数转换结果
SJMP $
HEASC,MOV R1,SP ;借用 R1为堆栈指针
DEC R1
DEC R1 ; R1指向被转换数据
XCH A,@R1 ;取被转换数据
ANL A,#0FH ;取一位十六进制数
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
52
一般说来:
?当相互传递的数据较少时,采用寄存器
传递方式可以获得较快的传递速度;
?当相互传递的数据较多时,宜采用存储
器或堆栈方式传递;
?如果是子程序嵌套时,最好是采用堆栈
方式。
53
4.4 常用程序举例
4.4.1 算术运算程序
一、多字节数的加、减运算
80C51单片机的指令系统提供的是字节运算指
令,所以在处理多字节数的加减运算时,要合理地
运用进位(借位)标志。
例 多字节无符号数的加法。
设两个 N字节的无符号数分别存放在内部 RAM中以
DATA1和 DATA2开始的单元中。相加后的结果要
求存放在 DATA2数据区。
54
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 ;
55
二、多字节数乘法运算
例 双字节无符号数的乘法。
设双字节的无符号被乘数存放在 R3,R2中,乘数存放在
R5,R4中,R0指向积的高位。
R 2
R 3
R 4R 5
R 2 R 4R 2 R 4
R 2 R 5
R 2 R 5
R 3 R 4 R 3 R 4
R 3 R 5R 3 R 5
L
L
L
×
+
积,
R 0
L
开 始
结 束
R 2 * R 4 累 加 到 结 果 单 元
R 2 * R 5 累 加 到 结 果 单 元
R 3 * R 4 累 加 到 结 果 单 元
R 3 * R 5 累 加 到 结 果 单 元
56
例 多字节无符号数的减法。
设两个 N字节的无符号数分别存放在内部 RAM
中以 DATA1和 DATA2开始的单元中。相减后的结
果要求存放在 DATA2数据区 。
MOV R0,#DATA1 ;
MOV R1,#DATA2 ;
MOV R7,#N ;置字节数
CLR C ;
LOOP,MOV A,@R0 ;
SUBB A,@R1 ;求差
MOV @R1,A ;存结果
INC R0 ;修改指针
INC R1 ;
DJNZ R7,LOOP ;
57
主程序段如下:
MULTB,MOV R7,#04 ;结果单元清 0
LOOP,MOV @R0,#00H ;
DJNZ R7,LOOP ;
DEC R0
ACALL BMUL ;
SJMP $
另有 2段子程序:
BMUL
RADD(在 BMUL中被调用)
58
先看子程序段:
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
59
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
60
一、十六进制数与 ASCII码间的转换
十六进制数与 ASCII码的对应关系如表所示。当十六进
制数在 0~ 9之间时,其对应的 ASCII码值为该十六进制数加
30H;当十六进制数在 A~ F之间时,其对应的 ASCII码值为该
十六进制数加 37H。
4.4.2 码型转换
61
例 将 1位十六进制数转换成相应的 ASCII码。
设十六进制数存放在 R0中,转换后的 ASCII 码存放
于 R2中。实现程序如下:
HASC,MOV A,R0 ;取 4位二进制数
ANL A,#0FH ;屏蔽掉高 4位
PUSH ACC ; 4位二进制数入栈
CLR C ;清进(借)位位
SUBB A,#0AH ; 用借位位的状态判断该数在 0~ 9还是 A~ F之间
POP ACC ;弹出原 4位二进制数
JC LOOP ;借位位为 1,跳转至 LOOP
ADD A,#07H ; 借位位为 0,该数在 A~ F之间,加 37H
LOOP,ADD A,#30H ;该数在 0~ 9之间,加 30H
MOV R2,A ; ASCII码存于 R2
RET
62
例 将多位十六进制数转换成 ASCII码。
设地址指针 R0指向十六进制数低位,R2中
存放字节数,转换后地址指针 R0指向 十六进制
数 的高位。 R1指向要存放的 ASCII码的 高 位地
址。实现程序如下:
63
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 ; 字节数 存于 R2
RET
ASCTAB,DB 30H,31H,32H,33H,34H,35H,36H,37H
DB 38H,39H,41H,42H,43H,44H,45H,46H
64
二,BCD码与二进制数之间的转换
在计算机中,十进制数要用 BCD码来表示。通常,用
四位二进制数表示一位 BCD码,用 1个字节表示 2位
BCD码(称为压缩型 BCD码)。
例 双字节二进制数转换成 BCD码。
设( R2R3)为双字节二进制数,( R4R5R6)为
转换完的压缩型 BCD码。
十进制数 B与一个 8位的二进制数的关系可以表示为:
只要依十进制运算法则,将 bi( i= 7,6,… …, 1,
0)按权相加,就可以得到对应的十进制数 B。(逐次
得到,b7× 20; b7× 21+ b6× 20; b7× 22+ b6× 21
+ b5× 20 ; … )。
65
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 ;
66
思考题及习题
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)。
67
9、若 80C51的晶振频率为 6MHz,试计算延时子程序的延时时
间。。
DELAY,MOV R7,#0F6H
LP,MOV R6,#0FAH
DJNZ R6,$
DJNZ R7,LP
RET
10、在内部 RAM 的 21H单元开始存有一组单字节不带符号数,
数据长度为 30H,要求找出最大数存入 BIG单元。
11、编写程序,把累加器 A中的二进制数变换成 3位 BCD码,并
将百、十、个位数分别存放在内部 RAM的 50H,51H,52H中。
12、编写子程序,将 R1中的 2个十六进制数转换为 ASCII码后存
放在 R3和 R4中。
13、编写程序,求内部 RAM中 50H~59H十个单元内容的平均
值,并存放在 5AH单元。