第四章
80C51单片微机的程序设计
4.1概述
4.1.1 汇编语言格式
1,计算机语言 ——机器语言、汇编语言与高级语言
程序就是为计算某一算式或完成某一工作的若干指令的有
序集合。计算机的全部工作概括起来,就是执行这一指令序
列的过程。这一指令序列称为程序。为计算机准备这一指令
前的过程称为程序设计。
⑴ 机器语言
在计算机中,所有的数符都是用二进制代码来表示的,指
令也是用二进制代码来表示。这种用二进制代码表示的指令
系统称为机器语言系统,简称为机器语言。直接用机器语言
编写的程序称为手编程序或机器语言程序。
⑵ 汇编语言
在程序设计自动化的第一阶段,就是用英文字符来代替机
器语言,这些英文字符被称为助记符。用这种助记符表示指令系
统的语言称为汇编语言或符号语言,用汇编语言编写的程序称为
汇编语言程序。
具有以下几个特点:
① 助记符指令与机器指令是一一对应的,所以用汇编语言
编写的程序效率高,占用存储空间小,运行速度快,而且能反映
计算机的实际运行情况,所以用汇编语言能编写出最优化的程序。
② 汇编语言是“面向机器”的语言,编程比使用高级语言
困难。
③ 汇编语言能直接访问存储器、输入与输出接口及扩展的
各种芯片 (比如 A/D,D/A等 ),也可直接处理中断,因此汇编语言
能直接管理和控制硬件设备。
④ 汇编语言通用性差,汇编语言和机器语言一样,都面
向一台具体的机器,不同的单片微机具有不同的指令系统,并
且不能通用。
但是,计算机不能直接识别在汇编语言中出现的字母、
数字和符号,需要将其转换成用二进制代码表示的机器语言程
序,才能够识别和执行。通常把这一转换(翻译)工作称为汇
编。汇编可以由程序员通过查指令表把汇编指令程序转换为机
器语言程序,这个过程称为人工汇编。目前基本上由专门的程
序来进行汇编,这种程序称为汇编程序。经汇编程序汇编而得
到的机器语言程序,计算机能够识别和执行,因此这一机机器
语言程序称为目的程序或目标程序,而汇编语言程序称为源程
序。这三者之间的关系如 图 4–1所示。
⑶ 高级语言
比如 BASIC,FORTRAN,COBOL及 PASCAL等,都是
一些参照数学语言而设计的、近似于人们日常用语的语言。这种
语言不仅直观、易学、易懂,而且通用性强,易于移植到不同类
型的机器中去。计算机也不能直接识别和执行高级语言,需要将
其转换为机器语言才能识别和执行。对于高级语言,这一转换工
作通常称为编译或者解释。进行编译或者解释的专用程序称为编
译程序或者解释程序。
由于高级语言不受具体机器的限制, 而且使用了许多数学
公式和习惯用语, 从而简化了程序设计的过程, 因此是一种面向
问题或者面向过程的语言 。 近年来高级语言发展很快, 相继出现
了许多面向工程设计, 自动控制, 人工智能等方面的语言, 比如,
APT,PROLOG,LISP,PL/M以及 C语言等 。 而 80C51系列单
片微机作为工业标准地位, 从 1985年开始就有 C语言编译器, 简
称 C51。, 面向控制, 这一使用的特点, 仍以汇编语言为主进行
讲解 。
⒉ 汇编语言
⑴ 汇编语言语句的种类和格式
① 汇编语言语句的种类
汇编语言语句有三种基本类型:指令语句、伪指令语句
和宏指令语句。
指令语句:每一个指令语句都在汇编时产生一个目标代码,
对应着机器的一种操作 。
例如,MOVA,#0
伪指令语句:主要是为汇编语言服务的, 在汇编时没有目标
代码与之对应 。
例如,ONE EQU 1
宏指令语句:用以代替汇编语言源程序中重复使用的程序段
的一种语句,由汇编程序在汇编时产生相应的目标代码。
② 汇编语言语句的格式
指令语句和伪指令语句的格式是类似的。
指令语句的格式为:
【 标号 ( 名字 ) 】,助记符 (操作码 ) 【 操作数 ( 参数 ) 】 ;
【 注释 】
伪指令语句的格式:
名字 定义符 参数 ;注释
两种语句都由四个部分组成。其中每一部分称为域也称为字段,
各字段之间用一个空格或字段定界符分隔,常用的字段定界符
有冒号“:”,逗号“,”和分号“;”。其中方括号括起来
的是可选择部分。
标号 (也称为名字 )域:
用来说明指令的地址。标号可以作为 LJMP,AJMP、
LCALL及 ACALL等指令的操作数。
? 在指令语句中, 标号位于一个语句的开头位置, 由字母和
数符组成, 字母打头, 冒号,,, 结束 。 在 80C51单片微机的汇
编语言中, 标号中的字符个数一般不超过 8个, 若超过 8个, 则
以前面的 8个为有效, 后面字符不起作用 。
? 不能使用本汇编语言中已经定义了的符号作标号, 比如指
令助记符 (如 ADD), 伪指令 (如 END) 及寄存器符号名称 (如 PC) 。
? 一条语句可以有标号, 也可以没有标号, 标号的有无取决
于程序中的其它语句是否需要访问该条语句 。
伪指令语句与指令语句主要不同是在其名字后面没有冒号。
操作码域:
是指令的助记符或定义符, 用来表示指令的性质, 规定这
个指令语句的操作类型 。
伪指令语句中的定义符规定这个指令语句的伪操作功能 。
对于标号缺省的语句, 操作码域作为一行的开始 。 但在书写
时, 应与上一行的操作码对齐 。
操作数域:
给出的是参与运算或进行其它操作的数据或这些数据的地址。
? 操作数与操作码之间用空格“”分隔,若有两个操作数,
这两个操作数之间必须用逗号“,”分开。操作数域若是数据的
直接或间接地址,则必须满足寻址方式的规定。
? 对于操作数域出现的常数:若采用十六进制数表示, 其末
尾必须加, H‖说明;若十六进制数以 A,B,C,D,E,F开头,
其前面必须添一个, 0‖进行引导说明, 例如; 0F0H,否则在机
器汇编时会出错 。 若采用二进制数表示, 其末尾必须用, B‖说
明 。 若采用十进制数表示, 可以不加后缀或加, D‖说明 。
80C51的操作数可以是寄存器寻址、直接地址等七种寻址方式。
注释域由分号“;”引导开始,是说明语句功能、性质
以及执行结果的文字。使用注释可以使文件编制显得更加清楚,
便于人们阅读程序,简化软件的维护。对机器不起作用。注释
的长度不限,一行不够可换行接着写,但换行的开头仍以分号
“;”引导。
例:把片外存储器 2200H单元中的数送入片内 30H单元中 。
标号域 操作码域 操作数域 注释域
BEGIN,MOV DPTR,#2200H ; (DPTR)=2200H
MOV R0,#70H ; (R0)=70H
MOVX A,@DPTR ; (A)=((DPTR))
MOV @R0,A ; ((R0))=(A)
为了便于编程和对汇编语言程序进行汇编, 各种汇编程
序都提供一些特殊的指令, 供人们编程使用 。 这些指令通常称
为伪指令, 由伪指令确定的操作称为伪操作 。 伪指令又称汇编
程序控制译码指令 。, 伪, 体现在汇编时不产生机器指令代码,
不影响程序的执行, 仅指明在汇编时执行一些特殊的操作 。
4.1.2 伪指令语句
⒈ ORG(ORiGIN) 汇编起始地址伪指令
指令格式为,ORG <表达式>
其含义是向汇编程序说明,下述程序段的起始地址由表
达式指明。表达式通常为 16进制地址码。
? 一般规定, 在由 ORG伪指令定位时, 其地址应当由小到大,
不能重叠 。
? 它的有效范围一直到下一条 ORG伪指令出现为止 。
? 跟在 ORG伪指令后面的程序段或数据段是绝对地址还是浮
动地址段, 依赖于 ORG右边的表达式性质 。
例如:
ORG 1000H
START,MOVA,# 12H
ORG伪指令通知汇编程序,从 START开始的程序段,其
起始地址由 1000H开始。由于 1000H是立即数型地址码,所以还
隐含地指明该程序段是绝对地址段。
假定 ORG右边的表达式是浮动程序段中定义的标号 RELOCA,

ORG RELOCA
SUBROU,……
表明 SUBROU起始于 RELOCA(它是相对地址 )浮动地址的程序
段。
⒉ END(END of assembly) 汇编结束伪指令
汇编结束伪指令一般有以下两种格式:
主程序模块:<标号> END <表达式>
子程序模块,<标号> END
其含义是用以通知汇编程序, 该程序段汇编至此结束 。
因此, 在设计的每一个程序中必须要有 END语句, 而且只能有
一条 。 但 END语句应设置在整个程序 ( 包括伪指令在内 ) 的后
面 。
当源程序为主程序时,END伪指令中可有标号,这个标
号应是主程序第一条指令的符号地址。若源程序为子程序,则
在 END伪指令中不需要带标号。
只有主程序模块才具有<表达式>项,且<表达式>的
值等于该程序模块的入口地址。子程序模块没有该项。
⒊ EQU( EQUate) 赋值伪指令
指令格式为:<标号> EQU <表达式>
其作用是把表达式赋值于标号, 这里的标号和表达式是
必不可少的 。 例如:
LOOP EQU 2002H
是向汇编程序表明, 标号 LOOP的值为 2002H。 又如:
LOOP1 EQU LOOP
LOOP已赋值为 2002H,则相当于 LOOP1= LOOP,即
LOOPI也为 2002H,在程序中 LOOP和 LOOP1可以互换使用 。
用 EQU语句给一个标号赋值以后,在整个源程序中该标
号的值是固定的,不能更改。若需更改,需用伪指令 DL重新定
义。
⒋ DL定义标号值伪指令
指令格式为,<标号 > DL <表达式 >
其含义也是说明标号等值于表达式 。 同样, 标号和表达式
是必不可少的 。 例如;
COUNT DL3000H ; S定义标号 COUNT的值为 3000H
COUNT DL COUNT十 1 ;重新定义 COUNT的值为; 3000H+ l
DL和 EQU的功能都是将表达式值赋予标号,但两者有差
别:可用 DL语句在同一源程序中给同一标号赋予不同的值,即
可更改已定义的标号值;而用 EQU语句定义的标号,在整个源程
序中不能更改。
⒌ DB(Define Byte) 定义字节伪指令
指令格式为:<标号> DB <表达式或表达式表>
其含义是将表达式或表达式表所表示的数据或数据串存入从
标号开始的连续存储单元中 。 标号为可选项, 它表示数据存储单
元地址 。 表达式或表达式表是指一个字节或用逗号分开的字节数
据 。 可以是用引号括起来的字符串 。 字符串中的字符按 ASCII码
存于连续的 ROM中 。 例如:
ORG 2000H
TABLE DB 73H,04,100,32,00,- 2,” ABC‖
表示字节串数据存入由 TABLE标号为起始地址的连续存
储器单元中。即从 2000H存储单元开始依次连续存放数据为:
73H,04H,64H,20H,00H,FEH,41H,42H,43H。
若不采用 ORG伪指令专门规定数据区的起始地址,则数
据区的起始地址即根据 DB命令前一条指令的地址确定。这时 DB
所定义的数据字节的起始地址为 DB命令前一条指令的地址加上
该指令的字节数。
⒍ DW(Define Word) 定义字伪指令
其指令格式为:<标号> DW <表达式或表达式表>
其含义是把字或字串值存入由标号开始的连续存储单元
中, 且把字的高字节数存入低地址单元, 低字节数存入高地址
单元 。 按顺序连续存放 。
DW 100H,3456H,814
表示按顺序存入 01H,00H,34H,56H,03H,2EH
注,DB和 DW定义的数表,数的个数不得超过 80个。若数据的
数目较多时,可以使用多个定义命令。一般以 DB来定义数据,
以 DW来定义地址。
⒎ DS(Define Storage) 定义存储区伪指令
存储区说明伪指令的指令格式为,<标号 > DS <表达式 >
通知汇编程序, 在目标代码中, 以标号为首地址保留表达式值
的若干存储单元以备源程序使用 。 汇编时, 对这些单元不赋值 。
例如:
BASE DS 100H
通知汇编程序, 从标号 BASE开始, 保留 100H个存储单元,
以备源程序另用 。
注意,对于 80C51单片微机,DB,DW,DS等伪指令只
能应用于程序存储器,而不能对数据存储器使用。
⒏ BIT 位定义伪指令
用于给字符名称赋予位地址。
命令格式为,<字符名称 > BIT <位地址 >
其中,位地址可以是绝对地址,也可以是符号地址。
例,ABC BIT P3.1
把 P3.1位地址赋值给 ABC,在后面的编程中,ABC即可
作为位地址 P3.1使用。
除了一般的汇编程序之外,还有一些高性能的汇编程序,
可在汇编时进行表达式赋值、条件汇编和宏汇编。这样为用户
编程带来了很大的方便。
? 表达式赋值可允许汇编语言程序的指令操作数域使用表达式,
例如:,ADD A,#ALFA*BETA/ 2‖,其中 ALFA和 BETA是两
个已定义的标号。
? 条件汇编可使用户在汇编时根据需要对源程序进行汇编, 这
样有利于程序的调试 。 特别是为用户系统 ( 或大的应用 ) 程序的
调试带来方便 。
? 宏汇编允许用户在编写源程序时使用宏指令 。 一条宏指令往
往包括若干条汇编语言指令, 这样在使用宏指令之后可使源程序
缩短, 简化程序设计 。
在使用宏指令之前,要先对相应的寄存器赋值,否则将会得
出错误的结果。
例 1,伪指令应用
ORG 8100H
BUFFER DS 10H
DW ―AB‖
DW 100H,1ACH,-814
说明,① 从 8100H至 810FH为缓冲区空间
② (8110H)=41H (?A‘)
(8111H)=42H (?B‘)
③ 8112H单元起存放 01H,00H,01H,ACH,FCH、
D2H
4.2 80C51汇编语言程序设计
汇编语言程序设计, 就是采用汇编指令来编写计算机程序 。
要对应用中需使用的寄存器, 存储单元, I/O端口等先要作出具
体安排 。 在实际编程中, 如何正确选择指令, 寻址方式和合理使
用工作寄存器, 包括数据存储器单元, 如何对扩展的 I/O端口进
行操作等, 都是基本的汇编语言程序设计技巧 。
程序结构一般采用以下三种基本控制结构, 即顺序结构,
分支结构和循环结构来组成, 再加上使用广泛的子程序及中断服
务子程序, 共有五种基本结构 。
顺序结构是按照逻辑操作顺序,从某一条指令开始逐条顺序
执行,直至某一条指令为止。
例 2,数据传送和交换 。
将 R0与 R7内容互换, R4与内存 20H单元内容互换 。
XCHR,MOV A,R0
XCH A,R7
XCH A,R0 ; R0与 R7内容互换
MOV A,R4
XCH A,20H
XCH A,R4 ; R4与 20H单元内容互换
4.2.1 顺序结构程序设计
设被加数存放于片内 RAM的 20H( 低位字节 ), 21H
( 高位字节 ), 加数存放于 22H( 低位字节 ) 和 23H( 高位字
节 ), 运算结果的和数存放于 20H( 低位字节 ) 和 21H( 高位字
节 ) 中 。 实现 16位相加 。 其程序段如下:
START,PUSH ACC ;将 A中内容进栈保护
MOV R0,# 20H ;将 20H地址送 R0
MOV R1,# 22H ;将 22H地址值送 R1
MOV A,@ R0 ;被加数低字节内容送 A
ADD A,@ R1 ;低字节数相加
MOV @ R0,A ;低字节数和存 20H中
INC R0 ;指向被加数高位字节
例 3,不带符号多字节加法 。
INC R1 ;指向加数高位字节
MOV A,@ R0 ;被加数高位字节送 A
MOV A,@ R0 ;被加数高位字节送 A
ADDC A,@ R1 ;高字节数带进位相加
MOV @ R0,A ;高字节数和存 21H中
CLR A
ADDCA,#00H
MOV 10H,A ;进位暂存于 10H中
POP ACC ;恢复 A原内容
这里将 A原内容进栈保护, 如果原 R0,R1 内容有用, 亦
需进栈保护 。 如果相加结果高字节的最高位产生进位且有意义时,
应对标志 CY位检测并处理之 。
注意:对于对带符号数 原码 的减法运算,只要先将减数原码的符
号位取反,即可把减法运算按加法运算的原则来处理。
对于带符号数的加法运算, 首先要进行两数符号的判定,
若两数符号相同, 则进行两数相加, 并以被加数符号为结果的符
号 。
如果两数符号不同,则进行两数相减。如果相减结果为正,
则该数即为最后结果,并以被减数符号为结果的符号。如果两数
相减的结果为负,则应将其差数取补,并把被减数的符号取反后
作为结果的符号。
已知两个 16位的带符号数分别存放在工作寄存器 R2(高字
节 ), R3(低字节 ) 和 R4(高字节 ),R5(低字节 ) 中,二个数的
D15位为符号位。请编写多字节带符号数的加法和减法运算程序。
减法入囗地址为标号 BSUB,加法入口地址为 BADD。运算结果存
入 R6(高字节 )和 R7(低字节 ) 。
BSUB:MOV A,R4 ;取减数高字节
CPL ACC.7 ;减数 原码 符号取反以进行加法
MOV R4,A
例 4,带符号双字节二进制数加减法程序
BADD:MOV A,R2 ;取被加数高字节
MOV C,ACC.7
MOV F0,C ;被加数符号保存在用户标志 F0中
XRL A,R4 ;判别二个数的符号位是否相同?
MOV C,ACC.7 ;两数同号 CY=0,两数异号 CY=1
MOV A,R2 ;被加 (减 ) 数符号位清为, 0‖
CLR ACC.7
MOV R2,A ;取被加 (减 ) 数的数值部分
MOV A,R4 ;加 (减 ) 数的符号位清为, 0‖
CLR ACC.7
MOV R4,A ;取加 (减 ) 数的数值部分
JC JIAN ;两数异号转 JIAN
JIA,MOV A,R3 ;两数同号, 进行加法
ADD A,R5 ;低字节相加
MOV R7,A ;存和的低字节
MOV A,R2 ;高字节相加
ADDCA,R4
MOV R6,A ;存和的高字节
JB ACC.7,QAZ ;符号位为, 1‖,转溢出处理
QWE:MOV C,F0 ; 以被加数的符号为结果符号
MOV ACC.7,C
MOV R6,A
RET
JIAN:MOV A,R3 ;低字节相减
CLR C
SUBB A,R5
MOV R7,A ;存差的低字节
MOV A,R2
SUBB A,R4 ;高字节相减
MOV R6,A ;存差的高字节
JNB ACC.7,QWE;判差的符号, 为 0转 QWE
BMP:MOV A,R7 ;若差的符号为 1,则取补
CPL A ;低字节取反加 1
ADD A,#01H
MOV R7,A
MOV A,R6 ;高字节取补
CPL A
ADDCA,#00H
MOV R6,A
CPL F0 ;被减数符号取反
SJMP QWE
QAZ,…… ;溢出处理
多字节乘法的基础是加法 。 分别相乘后对应字节相加 (个
位, 十位, 百位等分别相加, 并考虑低字节向高字节的进位 )。
选用工作寄存器暂存中间积 。
分析:设被乘数低字节 ( addrl) 用 A表示, 高字节 ( addr2)
用 B表示;乘数低字节 ( addr3) 用 L表示, 高字节 ( addr4) 用
M表示 。
例 5,双字节乘法 。
双字节相乘的过程如下:
工作寄存器用来存放部分积, R2存放 (HAL+ LBL+
LAM),R3存放 (HBL+CY + HAM+ LBM),R4存放 (HBM 十
CY)。 双字节乘法程序段如下:
START,PUSH PSW ; PSW,A,B入 栈
PUSH ACC
PUSH B
MOV PSW,# 18H ;选用工作寄存器组 3
MOV R0,# addrl ;被乘数低字节地址送 R0
MOV R1,# addr3 ;乘数低字节地址送 R1
MOV A,@ R0 ;被乘 数低字节内容送 A
MOV B,@ R1 ;乘数低字节内容送 B
MUL AB ; (1) A× L
MOV @ R0,A ;积的最低字节存入 addrl中
MOV R2,B ; HAL送 R2中
INC R0 ;指向被乘数高字节
MOV A,@ R0 ;被乘数高字节送 A
MOV B,@ R1 ;乘数低字节送 B
MUL AB ; (2) B× L
ADD A,R2 ; HAL十 LBL
MOV R2,A ; HAL十 LBI之和送 R2
MOV A,B ; HBL送 A
ADDC A,# 00H ; HBL+ CY
MOV R3,A ; HBL送 R3
DEC R0 ;指向 ddrl
INC R1 ;指向 ddr4
MOV A,@ R 0 `; A送累加器 A
MOV B,@ R1 ; M送 B
MUL AB ; (3) M× A
ADD A,R2 ; LAM+ ( R2)
MOV R2,A ; LAM+ HAL十 LBL之和送 R2
MOV A,B ; HAM送 A
ADDC A,R3 ; HAM十 HBL+ CY
MOV R3,A ; HAM+ HBL+ CY之和送 R3
MOV R4,# 0 ;清 R4
JNC LOOP ; CY= 0,转 LOOP
INC R4 ; CY= 1, 则( R4) ← R4+ l
LOOP,INC R0 ;指向 addr2
MOV A,@R0 ; B送 A
MOV B,@ R1 ; M送 B
MUL AB ; (4) M× B
ADD A,R3 ; HAM十 HBL+ LBM
MOV R3,A ; HAM+ HBL+ LBM之和送 R3
MOV A,B ; HBM送 A
ADDCA,R4 ; R4+ HBM= RES3
MOV @ R1,A ; RES3存入 addr4中
MOV @ R0,R2 ; RES1存入 addr2中
DEC R1 ;指向 addr3
MOV @R1,R3 ; RES2存入 addr3中
POP B ; B,A,PSW出栈
POP ACC
POP PSW
对于带符号数的乘法,其原则为:原码相乘,乘积的符号位为
被乘数与乘数符号位的, 异或,
除法指令 DIV AB是条单字节除法, 对于多字节无符号数
的除法, 可以依照, 移位相减, 的基本方法来进行 。 除法运算
是按位进行的, 每一位是一个循环, 每个循环中做三件事, 一
是被除数左移一位, 二是余数减除数, 最后根据是否够减来置
商位为 1 或 0 。 对于 16位的被除数, 要循环 16次才能完成除法运
算 。 若除数为零, 则除法无法进行, 这时置溢出标志为 1。
对于被除数的移位,最简单的办法是把被除数向余数单
元左移,把被除数左移后空出的低位存放商数,当除法完成后,
被除数已全部移到余数单元并逐次被减而得到余数,而被除数
单元中内容已成为商数。
对相关内存单元作以下分配, 即:
R7(高字节 )R6—程序执行前为被除数, 执行后为最终商数;
R5(高字节 )R4—存除数;
例 6,双字节无符号数除法
R3(高字节 )R2—存放每次相除后的余数, 程序执行后为
最终余数;
R1—循环次数计数器 (被除数的位数 ) ;
F0—溢出标志。
双字节无符号数除法程序如下:
CLR F0 ;清溢出标志
MOV A,R5
JNZ ZERO ;除数不为零, 转 ZERO
MOV A,R4
JZ OVER;除数为零,转溢出处理
ZERO,CLRA ;余数单元清 0
MOV R2,A
MOV R3,A
MOV A,R7
JNZ START ;被除数高字节不为零, 开始除法运算
MOV A,R6
JNZ START ;被除数高字节为零, 低位字节不为;零, 开始除法运算
RET ;被除数为零, 则商和余数均为零, 结束 。
START,MOV R1,#10H ;设循环计数器
LOOP,CLRC ;进行一位除法运算
MOV A,R6 ;被除数左移一位
RLC A
MOV R6,A
MOV A,R7
RLC A
MOV R7,A
MOV A,R2 ;移出的被除数高位移入余数单元
RLC A
MOV R2,A
MOV A,R3
RLC A
MOV R3,A
MOV A,R2 ;余数低字节减除数低字节
SUBB A,R4
JC NEXT ;余数小于除数, 则继续下一位除法
MOV R0,A
MOV A,R3 ;再比较余数与除数的高字节
SUBB A,R5
JC NEXT ;余数小于除数, 则继续下一位除法
INC R6 ;余数大于除数, 则商加 1
MOV R3,A ;相减结果送入余数单元
MOV A,R0
MOV R2,A
NEXT,DJNZ R1,LOOP; 16次循环未结束, 则继续

OVER,SETB F0 ;除数为零, 溢出标志为, 1‖
RET
线性表可以有不同的存储结构,而最简单最常用的是用一
组连续的存储单元顺序存储线性表的各个元素,这种方法称为线
性表的顺序分配。
实际应用场合会接触到许多非线性的参数, 如在自动检测
系统或智能仪器仪表中, 其误差往往难以从理论上建立准确的误
差模型, 用硬件来修正很难实现或代价很高, 这时可以通过软件
校准的方法, 只要测试出系统的输入所对应的输出值, 然后将系
统的输入, 输出数据列成校准表格, 通过查表方法将复杂的非线
性变成简单的查表 。
对于虽然可以通过建立数学模型来用软件编程实现的一些
运算,如求平方数,开根号等,但由于八位机的局限,编程相当
麻烦,这时可在程序存储器中存入平方表、开根号表等,采用查
表方式,可以很方便的求得结果。
例 7,查表
查表就是根据变量 x,在表格中查找对应的 y值, 使 y= f
( x) 。 y与 x的对应关系可有各种形式, 而表格也可有各种结构 。
一般表格常量设置在程序存储器的某一区域内 。 在 80C51指令集
中, 设有两条查表指令:
MOVC A,@ A+ DPTR ;远程查表
MOVC A,@A+PC ;近程查表
设有一个巡回检测报警装置,需对 4 路输入进行控制,每路设
有一个最大额定值,为双字节数。控制时需根据检测的路号找
出该路对应的最大额定值。设 R2用于寄存检测路号,查找到的
对应的最大额定值存放于 31H和 32H单元中。查找最大允许额定
值子程序如下:
地址 机器码 源程序 注释
ORG 0000H
0000 EA MOV A,R2 ;检测路号送 A
0001 2A ADD A,R2 ; ( R2) × 2
0002 F531 MOV 31H,A ;距表首址偏移量
0004 2408 ADD A,#08H ;偏移量
0006 83 MOVC A,@A+PC ;查表, 读取第一个字节内容
0007 C531 XCH A,31H ;第一字节存入 31H单元
0009 2404 ADD A,#04H ;偏移量
000B 83 MOVC A,@A+PC ;查表, 读取第二字节
000C F532 MOV 32H,A ;第二字节存入 32H单元
000E 22 RET
000F 1230 DW 1230H,1540H ;最大额定值表:路号; 0,路号 1
0011 1540
0013 2340 DW 2340H,2430H ;路号 2,路号 3
0015 2430
END
说明:两个偏移量 (08H,04H) 分别为当前 PC地址 (查表
指令地址 +1) 与表首址和 (表首址 +1) 之间的偏差。通过计算得到。
设表中有 1024个元素, 每个元素为两个字节, 则表格总长
为 2048个字节 。 现按 R4和 R5的内容从表格中查出对应的数据元
素值, 送存 R4和 R5中 。 其程序如下:
TBDP1,MOV DPTR,addrl6 ;表格首地址值送 DPTR
MOV A,R5 ;查表参数低位字节送 A
CLR C ;清 CY
RLC A ;带进位左移一位
XCH A,R4 ;将查表参数 R4内容送 A
RLC A ;带进位左移一位
XCH A,R4 ; R4与 R5内容互换
例 8,查表
ADD A,DPL ; DPL+查表参数低位字节
MOV DPL,A ;调整 DPL,DPH
MOV A,DPH ; DPH送 A
ADDC A,R4 ; DPH+查表参数高位字节
MOV DPH,A ;相加和存 DPH
CLR A ;清 A
MOVC A,@A+ DPTR ;查表, 读第一字节
MOV R4,A ;第一字节存入 R4
CLR A ;清 A
INC DPTR ; ( DPTR) + 1
MOVC A,@ A+ DPTR ;查表, 读第二字节
MOV R5,A ;第二字节存入 R5
RET
TBDP2,DW … ;数据表
DW …
主要特点是程序执行流程中必然包含有条件判断。符合条
件要求和不符合条件要求的有不同的处理路径。编程的主要方法
和技术是合理选用具有逻辑判断功能的指令。在程序设计时,往
往借助程序框图(判断框)来指明程序的走向。
一般情况下, 每个分支均需单独一段程序, 在程序的起始地址
赋予一个地址标号, 以便当条件满足时转向指定地址单元去执行,
条件不满足时仍顺序往下执行 。
4.2.2 分支结构程序设计
⒈ 单分支结构
当程序仅有两个出口, 两者选一, 称为单分支结构 。 通
常用条件判跳指令来选择并转移 。 在 80C51指令系统中, 可实现
单分支程序转移的指令有位条件转移指令, 如,JC,JNC,JB、
JNB和 JBC等, 还有一些条件转移指令, 如,JZ,JNZ,DJNZ
等 。
这类单分支结构程序有三种典型的形式(见 图 4–2):
在 图 4–2( a) 中, 当条件满足时执行分支程序 1,否则执
行分支程序 2。
在 图 4–2( b) 中, 当条件满足时跳过程序段 1,从程序段
2开始继续顺序执行;否则, 顺序执行程序段 1和程序段 2。
在 图 4–2( c)中,当条件满足时程序顺序执行程序段 2;
否则,重复执行程序段 1,直到条件满足为止。以程序段 1重复
执行的次数或某个参数作为判跳条件,当重复次数或参数值达
到条件满足时,停止重复,程序顺序往下执行。这是分支结构
的一种特殊情况,这实际是循环结构程序。
当条件不满足, 不是转向程序段 1的起始地址, 重复执行
程序段 1,而是转向判跳指令本身 。 这种方式常用于状态检测 。
例如:
LOOP,JB Pl.1,LOOP;当 P1.1引脚电平为 1时, 等待 。
由于条件判跳指令均属相对寻址方式, 其相对偏移量 rel
是个带符号的 8位二进制数, 常以补码形式出现, 可正可负, 其
寻址范围为+ 127~- 128个字节单元之间, 因此, 它可向高地
址方向转移, 也可向低地址方向转移 。
设对 addrl,addrl+ 1的双字节读数取补后存入 addr2和
addr2十 1单元中, 其中高位字节在高地址单元中 。 8位微机对双
字节数取补需分二次进行 。 首先对低字节数取补, 然后判其结果
是否为全, 0‖。 若为, 0‖,则高字节数取补;否则, 高位字节数
取反 。 双字节数取补程序段如下:
START,MOV R0,# addrl ;原码低字节地址码送 R0
MOV R1,# addr2 ;补码低字节地址码送 R1
MOV A,@ R0 ;原码低字节内容送 A
CPL A
INC A ; A内容取反加 l,即取补
MOV @ R1,A ;低字节补码存 addr2单元
例 9,求双字节补码程序
INC R0 ;指向原码高字节
INC R1 ;指向补码高字节
JZ LOOP1 ;当( A)= 0,转 LOOP1
MOV A,@ R0 ;原码高字节送 A
CPL A ;高字节内容取反
MOV @ R1,A ;字节反码存 (addr2十 1)单元
SJMP LOOP2 ;转 LOOP2,结束
LOOP1,MOV A,@ R0 ;低字节补码为 0
CPL A ;对高字节数取补
INC A
MOV @R1,A ;高字节补码存 (addr2+1)单元
LOOP2,…
END ;结束
上述程序采用判 0指令 JZ进行分支的选择。
Y=a2+ b (当 b≥10时 )
Y=a2- b (当 b<10时 )
ORG 0000H
START,MOV A,#a
MOV B,A
MUL AB ; (B)(A)= a2
MOV R0,A ; (R1)(R0)= a2
MOV R1,B
MOV A,#b
CJNE A,#0AH,MMN ; b≠ 10则转移
例 10、试编写计算下式的程序
MM,ADD A,R0 ; b≥ 10,a2+b=Y
MOV R0,A
MOV A,#00H
ADDC A,R1
MOV R1,A
SJMP MMNN
MMN,JNC MM ;无借位 ( 即 b>10) 转 MM
MOV R3,A ; R3←b
MOV A,R0
CLR C
SUBB A,R3 ; (R1)( R0)← a2-b
MOV R0,A
MOV A,R1
SUBB A,#00H
MOV R1,A
MMNN,MOVY0,R0 ; (Y1)(Y0)← 结果
MOV Y1,R1
HERE,AJMP HERE
注,Y1,Y0需用位定义伪指令赋值。
⒉ 多分支选择结构
当程序的判别部分有两个以上的出口流向时, 称为多分
支结构 。
多分支结构通常有两种形式, 参见 图 4–3。
分支结构程序允许嵌套, 即一个程序的分支又由另一个
分支程序所组成, 从而形成多级 分支程序结构 。 汇编语言本身
并不限制这种嵌套的层次数, 但过多的嵌套层次将使程序的结
构变得复杂和臃肿, 以致造成逻辑上的混乱, 应尽力避免 。
80C51设有两条多分支选择指令:
散转指令 JMP @ A+ DPTR
散转指令由数据指针 DPTR决定多分支转移程序的首地址,
由累加器 A中内容动态地选择对应的分支程序 。 因此, 可从多达
256个分支中选一 。
比较指令 CJNE A,direct,rel( 共有 4条 ) 。
比较两个数的大小, 必然存在大于, 等于, 小于三种情况,
这时就需从三个分支中选一 。
另外,还可以使用查地址表的办法、查转移指令表的办法
或通过堆栈来实现多分支程序转移。
设分支转移序号在 R3中, 分支程序入口地址放在 BRTAB表中 。
MOV DPTR,#BRTAB ;分支入口地址表首地址
MOV A,R3
RL A ; × 2
MOV R1,A
INC A ;取低位地址
MOVC A,@A+DPTR
PUSH A ;低位地址入栈
MOV A,R1
MOVC A,@A+DPTR ;取高位地址, 并入栈
PUSH A
RET ;分支入口地址出栈送入 PC
BRTAB,DW BR0
注,RET指令与两条 PUSH指令配对, 压栈时先, 低, 后, 高, 。
而 DW伪指令在字节存放时,先, 高, 后, 低, 。
例 11,通过堆栈操作实现分支程序转移
由 40H单元中动态运行结果值来选择分支程序
( 40H) = 0,转处理程序 0
( 40H) = 1,转处理程序 1
( 40H) = n,转处理程序 n
其程序段如下:
START,MOV DPTR,addr16 ;多分支转移指令表首址送; DPTR
MOV A,40H ; 40H单元内容送 A
CLR C ;清 CY
RLC A ; A内容左移一位
例 12,通过查转移指令表实现多分支程序转移
JNC TABLE ;若 CY= 0,转 TABEL
INC DPH ;若 CY= l,DPH内容十 1
TABEL,JMP @A十 DPTR ;多分支转移
ADDR16,AJMP LOOP0 ;转分支程序 0
AJMP LOOP1 ;转分支程序 1

AJMP LOOPn ;转分支程序 n
由于选用绝对转移指令 AJMP,每条指令占用两个字节,
因此, 要求 A中内容为偶数, 在程序中将选择参量 ( A中内容 ) 左
移一位 。 如果最高位为 1,则将它加到 DPH中, 这样分支量可达
0~ 127中选一 。
根据 AJMP指令的转移范围,要求分支程序段和各处理程序
入口均位于 2KB范围内。如果要求不受此限制,可选用长跳转指
令 LJMP,但它需占用三个字节,因此在程序上需作一定的修改。
修改后的程序段如下:
START,MOV DPTR,# addrl6 ;分支程序段首址送 DPTR
MOV A,40H ;选择参量送 A
MOV B,# 03H ;乘数 3送入 B
MUL AB ;参量 × 3
MOV R7,A ;乘积低 8位暂存 R7中
MOV A,B ;乘积高 8位送 A
ADD A, DPH ;乘积高 8位加到 DPH中
MOV DPH,A
MOV A,R7
JMP @ A+ DPTR ;多分支选择
ADDR16,LJMP LOOP0 ;转分支程序 0
LJMP LOOP1 ;转分支程序 1

LJMP LOOPn ;转分支程序 n
统计的数值分别存入 20H~ 29H中 。
CLR A ;结果单元 20H~ 29H清零
MOV R0,#10
MOV R1,#20H
LP,MOV @R1,A
INC R1
DJNZ R0,LP
MOV R0,#100 ; 100个数的计数器
READ,MOV A,P1 ;读入 P1
例 13,对从 P1口输入的 100个 0~ 9的数进行概率统计。
CHK0,CJNE A,#0,CHK1 ;比较, 不为, 0‖,继续比较
INC 20H ;是, 0‖,则, 0‖计数单元加 1
SJMP END0 ;是否全部统计完?
CHK1,CJNE A,#1,CHK2
INC 21H ;是, 1‖,则, 1‖计数单元加 1
SJMP END0
CHK2,CJNE A,#2,CHK3
INC 22H ;是, 2‖,则, 2‖计数单元加 1
SJMP END0
CHK3,CJNE A,#3,CHK4
INC 23H ;是, 3‖, 则, 3‖ 计数单元加
1
SJMP END0
CHK4,CJNE A,#4,CHK5
INC 24H ;是, 4‖,则, 4‖计数单元加 1
SJMP END0
CHK5,CJNE A,#5,CHK6
INC 25H ;是, 5‖,则, 5‖计数单元加 1
SJMP END0
CHK6,CJNE A,#6,CHK7
INC 26H ;是, 6‖,则, 6‖计数单元
加 1
SJMP END0
CHK7,CJNE A,#7,CHK8
INC 27H ;是, 7‖,则, 7‖计数单元
加 1
SJMP END0
CHK8,CJNE A,#8,CHK9
INC 28H ;是, 8‖,则, 8‖计数单元加 1
SJMP END0
CHK9,CJNE A,#9,ERR
INC 29H ;是, 9‖,则, 9‖计数单元加 1
END0,DJNZ R0,READ ;判是否全部统计完?
HERE,SJMP HERE
ERR,… ;非 0~ 9,出错
循环是强制 CPU重复多次地执行一串指令的基本程序结构。
从本质上看,循环程序结构只是分支程序中的一个特殊形式,
循环结构如 图 4–4,图 4-5所示。
⒈ 循环程序的四个部分
( 1) 循环初始化
在进入循环程序体之前所必要的准备工作:需给用于循
环过程的工作单元设置初值,如循环控制计数初值的设置、地
址指针的起始地址的设置、为变量预置初值等,有些情况下还
要进行现场保护。
4.2.3 循环 结构程序设计
( 2) 循环体
这是循环结构程序的核心部分,完成实际的处理工作,
是需反复循环执行的部分。
( 3)循环控制
控制循环程序的循环与结束部分, 通过循环变量和结束
条件进行控制 。 在重复执行循环体的过程中, 不断修改循环变
量, 直到符合结束条件, 就结束循环程序的执行 。 在循环过程
中, 除不断修改循环变量外, 还需修改地址指针等有关参数 。
循环控制部分的实现方法主要有循环计数控制法和条件控制法 。
循环次数不确定的情况:比如当计算结果达到给定的精度要
求或找到某一个给定值或某个特定标志 (如故障标志 )时, 满足条
件就结束循环 。 采用条件控制法 。 循环次数己知的情况:比如
传送 100个数, 循环次数设为 100。 采用计数控制法 。
(4) 结束部分
对循环程序执行的结果进行分析, 处理和存放 。 有些情
况下需恢复现场 。
图 4–4是计数循环结构形式 。 由图 4-4可见, 主机对循环
程序的初始化和结束部分均只执行一次, 而对循环体和循环控
制部分则常需重复执行多次, 不管条件如何, 它至少执行一次
循环体, 当循环计数回, 0‖时, 结束循环 。 循环体和循环控制
这两部分是循环程序的主体, 是循环程序设计的重点 。
图 4–5是条件循环结构形式 。 条件循环先检查控制条件是
否成立, 决定循环程序是否执行 。 当循环结束条件一开始就已
成立, 则循环体可能一次也不执行 。 这是两种不同结构的本质
区别 。
⒉ 计数控制循环结构
计数循环程序的特点是循环次数已知,必须在初始化部分
设定计数的初值,循环控制部分依据计数器的值决定循环次数。
一般均设置为减,1‖计数器,每循环一次自动减,1‖,直到回 0
时结束循环。
80C51设有功能强的循环转移指令:
DJNZ Rn,rel ;以工作寄存器作控制计数器
DJNZ direct,rel ;以直接寻址单元作控制计数器
这两条基本指令可派生出很多条不同控制计数器的循环转移指
令, 大大扩充了应用范围和多重循环层次 。
有些情况可以不采用单片微机内的定时器 /计数器作定时,
而是采用软件延时的办法, 执行一段循环程序, 而循环程序执行
的时间即为延时时间 。 延时子程序如下:
DELAY,MOV R2 # data ;预置计数循环控制常数
DELAY1,DJNZ R2,DELAY1 ; 当 ( R2) ≠0,转向本身
RET
根据 R2的不同初值可实现 3~ 513个机器周期的延时 ( 第
一条为单周期指令, 第二条为双周期指令 ) 。
例 l4,软件延时
⒊ 条件控制循环结构
根据控制循环结束的条件,决定是否继续循环程序的执
行。所谓的结束条件可以是搜索到某个参数 (比如回车符, CR‖),
也可以是发生的某种变化 (如故障引起电路电平变化 ) 等,什么
时侯结束循环是不可预知的。
一般常用比较转移指令或条件判跳指令进行控制和实现。
在浮点表示法中, 数的小数点位置不是固定的, 而是浮动的 。
浮点数分为阶码和尾数两个部分, 在数的表示中都有各自的符号
位 。 所谓规格化数即尾数的最高位是有效数字 1而不是 0。 要使浮
点数规格化, 只要移动尾数的小数点即可实现 。
要求将未规格化尾数左移直到最高位为 l,并从阶码中减
去尾数左移的位数 。 阶码减 1,直到检测到最高位为 1为止 。 每次
操作相同, 故应采用循环结构的程序 。 但循环次数是不定的, 与
尾数的数值有关 。 因此, 采用最高位是否为, 1‖为循环结束条件,
并需检查尾数是否为, 0‖,以免发生, 死循环, 。
设浮点数阶码地址存于 R0中,尾数字节数存于 R7中。
例 15,浮点尾数规格化
START,MOV A,R0 ;阶码地址送 A
CLR C ;清 CY
SUBB A,R7 ; ( R0) - (R7)
MOV R1,A ;尾数低位字节地址存 R1
MOV R6,R7 ;将尾数字节数送 R6
MOV A,# 00H ;清 A
LOOP1,XRL A,@R1 ;尾数字节内容与 A比较
INC R1 ;以检查尾数是否为全 0
JNZ LOOP2
DJNZ R6,LOOP1
SJMP LOOP5 ;若尾数为全 0,转结束
LOOP2,DEC R1 ;指向尾数最高位字节
MOV A,@R1 ;尾数最高字节内容送 A
JB ACC,6,LOOP5 ;尾数最高位为 1则转结束
MOV A,R7 ;计算尾数长度 ( 位数 )
MOV B,# 08H
MUL AB
MOV R2,A
LOOP3,MOV A,R7 ;计算出尾数低位字节地址存入 R1中
MOV R6,A
MOV A,R0
CLR C
SUBB A,R7
MOV R1,A
CLR C
LOOP4,MOV A,@ R1 ;尾数低位宇节内容送 A
RLC A ;带进位位左移一位
MOV @ R1,A ;移位后仍存原单元
INC R1 ;指向下一尾数单元
DJNZ R6,LOOP4 ;实现每一尾数字节左移一位
DEC @R0 ;阶码减 1
JB ACC,6,LOOP5 ;当尾数最高位为 1时转结束
DJNZ R2,LOOP3 ;当尾数最高位不为 1且 ( R2);- 1≠0 时, 转 LOOP3 继续
LOOP5,SJMP LOOP5 ;结束
END
把内部 RAM中起始地址为 DATA的数据串传送到外部 RAM
以 BUFFER为首地址的区域,直到发现,$‖字符的 ASCII码为止,
数据串的最大长度在内存 20H中。
MOV R0,#DATA ;数据区首地址
MOV DPTR,#BUFFER ;数据区长度指针
LOOP,MOV A,@R0
CJNE A,#24H, LOOP2 ;判是否为, $‖符 (24H)
SJMP LOOP1 ;是, $‖符, 则结束
LOOP2,MOV A,@R0 ;不是, $‖符, 则传送
MOVX @DPTR,A
INC R0
例 16
INC DPTR
DJNZ 20H,LOOP ;数据串未查完, 继续
LOOP1,RET
DATA,… ;数据串
注:本题中循环控制条件有两个,一个是条件循环控制,以找到
ASCII码, $‖符为循环结束条件,这是主要的结构;笫二个是计
数循环结构,万一找不到 ASCII码, $‖符,则由数据串的最大长
度作为计数循环控制。
⒋ 循环嵌套结构
循环嵌套就是在循环内套循环的结构形式, 也称多重循环 。
循环的执行过程是从内向外逐层展开的。内层执行完全部循环后,
外层则完成一次循环,逐次类推。层次必须分明,层次之间不能
有交叉,否则将产生错误。
设二进制数低字节地址指针为 R0,二进制数长度指针为
R7,BCD码低字节地址指针为 R1。
设计方法:将二进制数从高位开始逐次移入结果寄存器 ( 从最
低位开始移入 ) 。
START,PUSH PSW ;保护现场
PUSH ACC
PUSH B
MOV A,R0 ;将 R0,R1内容暂存于 R5,R6中
MOV R5,A
MOV A,R1
例 17,多字节二进制整数转换成 BCD码
MOV R6,A
MOV A,R7 ;二进制数的字节数加 1后暂存 R3中
INC A
MOV R3,A
CLR A
LOOP1,MOV @ R1,A ;存放 BCD码的单元清 0
INC R1
DJNZ R3,LOOP1
MOV A,R7 ;求二进制数的总长 ( 位数 ) 存 R3中
MOV B,# 08H
MUL AB
MOV R3,A
LOOP2,MOV A,R5
MOV R0,A
MOV A,R7
MOV R2,A
LOOP3,MOV A,@ R0 ;二进制数左移一位后存入原单元
RLC A
MOV @ R0,A
INC R0 ;指向下一单元
DJNZ R2,LOOP3 ;未全部移位完, 则转 LOOP3
MOV A,R6
MOV R1,A
MOV A,R7 ;字节数送 R2,并加 1
MOV R2,A
INC R2
LOOP4,MOVA,@ R1 ;结果单元内容 × 2+ CY,进行调整;后存入原单元
ADDC A,@ R1
DA A
MOV @ R1,A
INC R1
DJNZ R2,LOOP4 ;按字节相加未完,转 LOOP4
DJNZ R3,LOOP ;全部处理完了吗
POP B ;出栈,恢复原单元的内容
POP A
POP PSW
END ; 结束
⒈ 子程序及其调用
子 程序是一段由专门的子程序调用指令 CALL调用而以子
程序返回指令 RET结束的程序段。
将那些需多次应用的、但完成的运算或操作相同的程序段,
编制成一个子程序,并尽量使其标准化,存放于某存储区域。
调用子程序的程序称为主程序或调用程序。
绝对调用指令,ACALL addrll。 被调用的子程序首地址
距调用指令的距离在 2KB范围内 。
4.2.4 子程序设计
长调用指令,LCALL addr16。 子程序可设置在 64 KB的
任何存储器区域 。
调用指令自动将断点地址 ( 当前 PC值 ) 压入堆栈保护,
以便于程序执行完毕, 正确返回原程序, 从断点处继续往下执行 。
返回指令,RET。 设置在子程序的末尾, 表示子程序执行完
毕 。 它的功能是自动将断点地址从堆栈弹出送 PC,从而实现程
序返回原程序断点处继续往下执行 。
子程序的第一条指令地址, 通常称为子程序首地址或入口
地址, 往往采用标号 (可用助记符 )加以表示, 调用 ( 转子 ) 指令
的下一条指令地址, 通常称为返回地址或断点 。
子程序与主程序之间的关系如 图 4- 6所示 。
这里子程序的入口地址为一个,addr。在某些情况下可以为多
个,addrl,addr2,…,根据具体要求转入不同的入口。
在子程序的执行过程中, 可能出现在子程序中再次调用其它子程
序的情况 。 像这种子程序调用子程序的现象通常称为子程序嵌套 。
在编写子程序时需要注意以下几点:
⑴ 子程序入口条件
在凋用子程序之前,必须先将数据或参数送到主程序与子程序
的某一共享存储单元或寄存器中,调用子程序后,子程序从共享
存储单元或寄存器中取得数,在返回主程序之前,子程序还必须
把计算结果送到共享存储单元或寄存器中。这样在返回主程序之
后,主程序才可能从共享存储单元或寄存器中得到执行子程序后
的结果。
⑵ 保护现场与恢复现场
在调用子程序时, 单片微机只是自动保护断点地址, 但由
调用程序转入子程序执行时, 往往会破坏主程序或调用程序的有
关寄存器 ( 如工作寄存器和累加器等 ) 的内容, 也很可能破坏程
序状态字 PSW中的标志位, 从而在子程序返回后引起出错 。 因
此, 必要时应将这些单元内容保护起来, 即保护现场 。 对于
PSW,A,B等可通过压栈指令进栈保护 。 工作寄存器采用选择
不同工作寄存器组的方式来达到保护的目的 。 一般主程序选用工
作寄存器组 0,而子程序选用工作寄存器的其它组 。
当子程序执行完后, 即返回主程序时, 应先将上述内容送
回到来时的寄存器中去, 这后一过程称为恢复现场 。 对于 PSW、
A,B等内容可通过弹栈指令来恢复 。
在编写子程序时,还应注意保护(压栈)和恢复(弹出)
的顺序,即先压入者后弹出。
⑶ 子程序的特性
对于通用子程序, 为便于各种用户程序的选用, 要求在子
程序编制完成后提供一个说明文件,一般包含如下内容:
? 子程序名 。 标明子程序功能的名字 。
? 子程序功能 。 简要说明子程序能完成的主要功能 。
? 子程序入口条件和出口结果 。 说明当主程序或调用程序调用本
子程序时应设置哪些参量, 说明子程序执行结果及其存储单元 。
? 子程序所用的寄存器, 存储单元, 标志位等, 提示主程序或调
用程序是否需要在调用本子程序前对此进行保护 。
? 子程序嵌套 。 指明本子程序需调用哪些子程序 。
⒉ 常用子程序举例
入口参数,BCD码高位字节地址放入指针 R0、位数 (n- l)存于 R2
中。
出口参数:二进制数存放于 R3,R4中, 其中高位字节在 R3中,
低位字节在 R4中 。
处理方法,A= a3× 103十 a2× 102+ al× 101+ a0× 100
子程序如下:
BCDB,PUSH PSW ;现场保护
PUSH ACC
PUSH B
例 18,四位 BCD码整数 (放在高 4位,低 4位为 0)转换成二进
制整数子程序
MOV R3,# 00H
MOV A,@ R0
MOV R4,A ; BCD码千位送 R4
LOOP,MOV A,R4
MOV B,#10
MUL AB
MOV R4,A
MOV R5,B ;存中间结果
MOV B,#10 ;( R3), ( R4) × 10送 R3,R4
XCH A,R3
MUL AB
ADD A,R5
MOV R3,A
XCH A,R4
INC R0
ADD A,@R0
XCH A,R4 ;( R3R4) + (( R0)) 送 (R3)(R4)
MOV A,R3
ADDC A,#0
MOV R3,A
DJNZ R2,LOOP ;循环 n- l次
POP B ;恢复现场
POP ACC
POP PSW
RET ;返回
为了保证采集数据的稳定和不受千扰影响, 除了采用硬件
滤波电路外, 还常常应用软件来进行数字滤波 。 中值滤波就是连
续输入三个检测信号值, 从中选择一个中间值为有效信号 。
入口条件:三次采集数据分别存储在内部存储器的 20H、
21H和 22H中 。
出口结果:中间值在 R0寄存器中。
使用资源:累加器 A,R0及内存 20H,21H和 22H。
FILLE,PUSH PSW ; PSW及 ACC保护入栈
PUSH ACC
MOV A,20H ;取第一个数据
CLR C
例 19,中值数字滤波子程序
SUBB A,21H ;与第二个数据比较
JNC LOB1 ;第一个数据比第二个数据大, 转; LOB1
MOV A,20H ;第一个数据比第二个数据小, 交换;二个数的位置
XCH A,21H
MOV 20H,A
LOB1,MOVA,22H
CLR C
SUBB A,20H ;第三个数据与前二个数据中的较大;数比较
JNC LOB3 ;第三个数据大于前二个数据中的较;大数, 转 LOB3
MOV A,22H
CLR C
SUBB A,21H ;第三个数据与前二个数据中的较小;数比较
JNC LOB4
MOV A,21H
MOV R0,A
LOB2,POP ACC ;恢复 ACC和 PSW
POP PSW
RET
LOB3,MOV A,20H
MOV R0,A
AJMP LOB2
LOB4,MOV A,22H
MOV R0,A
AJMP LOB2
把外部 RAM 30H~ 3FH共 16个单元中的 ASCII码依次转换
为十六进制数, 并拼装压缩存入内部 60H~ 67H共 8个单元中 。
ASCTOH,MOV R0,#30H ;设 ASCII码地扯指针
MOV R1,#60H ;设十六进制数地址指针
MOV R7,#08H ;拼装的十六进制数字节个数 。
AB,ACALLTRAN ;调用转换子程序 。
SWAP A
MOVX @R1,A
INC R0
例 20,码制转换
ACALL TRAN ;调用转换子程序 。
XCHD A,@R1
XCHD A,@R1
INC R0
INC R1
DJNZ R7,AB
HALT,SJMP HALT
TRAN,CLR C ; ASCII码数转换为十六进制数子程序
MOVX A,@R
SUBB A,#30H
CJNE A,#0AH,BB
AJMP BC
BB,JC DONE
BC,SUBB A,#07H ; ≥0AH,则再减 07H(共减 37H)
DONE,RET
注:转换算法 —把欲转换的 ASCII码数减 30H,若小于 0则为非十
六进制数, 若为 0~ 9,即为转换结果 。 若 ≥0AH,则应再减
07H(―41H‖—―A‖), 若在 0AH~ 0FH间, 即为转换结果, 若小
于 0AH或大于 0FH,均为非十六进制数 。
标号为 TRAN的子程序实现 ASCII码数转换为十六进制数,
调用前应把欲转换的 ASCII码数放在以 R0间接寻址的单元中,
转换结果在累加器 A中 。
⒈ 数据排序程序
数据的排序, 其算法很多, 常用的有插入排序法, 冒泡排
序法, 快速排序法, 选择排序法, 堆积排序法, 二路归并排序法
及基数排序法等 。
冒泡法是一种相邻数互换的排序方法, 其过程类似水中气
泡上浮 。 执行时从前向后进行相邻数比较, 若数据的大小次序与
要求的顺序不符时 (逆序 ),就将二数互换, 正序时不交换, 假定
是升序排序, 则通过这种相邻数互换方法, 使小数向前移, 大数
向后移, 为此从前向后进行一次冒泡 ( 相邻数互换 ), 就会把最
大数换到最后, 再进行一次冒泡, 就会把次大数排在倒数第二,
直至冒泡结束 。
假定原始数据为 5FH,38,7,13,44,D8H,22
4.2.5 程序设计举例
冒泡过程,5FH,38,7,13,44,D8H,22( 逆序, 则两数互换 )
38,5FH,7,13,44,D8H,22( 逆序, 则两数互换 )
38,7,5FH,13,44,D8H,22( 逆序, 则两数互换 )
38,7,13,5FH,44,D8H,22( 逆序, 则两数互换 )
38,7,13,44,5FH,D8H,22( 正序 )
38,7,13,44,5FH,D8H,22( 逆序, 则两数互换 )
38,7,13,44,5FH,22,D8H(第一次冒泡结束 )
第二次冒泡结束,7,13,38,44,22,5FH,D8H
第三次冒泡结束,7,13,38,22,44,5FH,D8H
第四次冒泡结束,7,13,22,38,44,5FH,D8H
第五次冒泡结束,7,13,22,38,44,5FH,D8H
说明:
⑴ 每次冒泡都从向后排定了一个大数 ( 升序 ), 每次冒
泡所需进行 的比较次数都递减, 例如有 n个数排序, 则第一次
冒泡需比较 ( n-1) 次, 第二次冒泡则需 ( n-2) 次, 依此类推,
但实际编程中为了简化程序, 往往把各次比较次数都固定为
( n-1) 次 。
⑵ 对于 n个数,理论上说应进行( n-1)次冒泡才能完成
排序,但实际上往往不到( n-1)次就已排好序。判定排序是否
完成的最简单方法是每次冒泡中是否有互换发生,如果有互换
发生,说明排序还没完成,否则就表示已排好序,为此控制排
序结束常不使用计数方法,而使用设置互换标志的方法,以其
状态表示在一次冒泡中有无数据互换进行。
在一组测量数据中, 挑选出大于标准 m的数值作为合格的产
品, 而那些小于 m 的数值作为不合格产品, 则被剔除掉 。
设数据组为 X1,X2,…, X10共十个 。
ORG 0000H
0000H C2 00 PX,CLR 00 ;设交换过标志
0002H 7B 09 MOV R3,#09H ;10个数据比较, 第一次;比较两个数据, 比较次数为 ( n-1) 次
0004H 78 50 MOV R0,#50H ;十个单元无符号数存;放首址
0006H E6 MOV A,@R0
例 21
0007H 08 PX1,INC R0
0008H F9 MOV R1,A
0009H 96 SUBB A,@R0 ; DX- DX+1
000AHE9 MOV A,R1
000BH40 06 JC PX2 ; DX<DX+1则转 PX2,不交换
000DH D2 00 SETB 00H ; DX>DX+1量交换标志位, 20H.0=1
000FHC6 XCH A,@R0 ; DX与 DX+1交换
0010H 18 DEC R0
0011H C6 XCH A,@R0
0012H 08 INC R0
0013H E6 PX2,MOV A,@R0 ;A< DX+1
0014H DB F1 DJNZ R3,PX1 ;比较九次
0016H 20 00 E7 JB 00H,PX ;有交换则再比较一遍
0019H 80 FE END0,SJMP END0
执行结果( 50H)中为最小数,( 59H)中为最大数。
⒉ 查找最大值程序
已知采样值存放在外部 RAM 1000H~ 100FH中, 试编程找出
其中的最大值存入内部 RAM区的 20H中 。
ORG 0000H
MOV R0,#10H ;采样值数据区长度
MOV DPTR,#1000H ;采样值存放首址
MOV 20H,#00H ;最大值单元初始值设为最小数
LP,MOVX A,@DPTR ;取采样值
CJNE A,20H,CHK ;数值比较
SJMP LP1 ;共相等, 则转移
CHK,JC LP1 ; A值小, 转移
例 22
MOV 20H,A ; A值大, 则送 20H
LOB3,MOV A,20H
MOV R0,A
AJMP LOB2
LOB4,MOV A,22H
MOV R0,A
AJMP LOB2
LP1,INC DPTR
DJNZ R0,LP ;继续
HERE,SJMP HERE ;结束
注,20H中始终存放两个数比较后的大值, 比较结束后存放的即
是最大值 。
*若要寻找最小值,只要在初始化时,把可能的最
大值放入最小值存放单元,比较转移用的标志位由 C改
NC即可。
3.数据搜索程序
在数据区中寻找关键字,称为数据搜索。常用的方法有
两种,即顺序搜索和对分搜索。所谓顺序搜索就是把关键字与
数据区中的数据逐个比较,相等者即为找到的关键字。所谓对
分搜索是按对分原则进行取数与关键字比较,但前提是数据区
中的数已排好序,这样搜索一次后,搜索的数据区范围缩小一
半,搜索速度快
已知数据区内有 16个数, 从内部 RAM30H开始存放, 要搜索的关
键字在 20H中, 若数据区中搜索到关键字, 则在 21H中记录关键
字在数据区中的序号, 若数据区中没有搜索到关键字, 则置用户
标志 F0为 1。
ORG 0000H
MOV R0,#30H ;数据区首址
MOV R1,#16 ;数据区长度
MOV 20H,#KEY ;关键字送 20H单元
CLR F0 ;清用户标志位
MOV 21H,#01 ;序号置 1
例 23
LP,MOV A,@R0 ;取数
CJNE A,20H,LP1
HERE,SJMP HERE ;找到关键字, 结束
LP1,INC 21H ;序号加 1
INC R0 ;数据区地址指针加 1
DJNZ R1,LP ;继续
SETB F0 ;未搜索到关键字, 则置位用户标志
SJMP HERE
END
图 4–1 汇编过程示意图
图 4–2 单分支结构示意图
图 4–3 多分支结构示意图
图 4–4 计数循环结构示意图
图 4–5 条件循环结构示意图
图 4–6 子程序调用与返回过程示意图