3.4 算术运算类指令

89C51单片机的算术运算类指令共有24条,可以完成加、减、乘、除等各种操作,全部指令都是8位数运算指令。如果需要做16位数的运算则需编写相应的程序来实现。

算术运算类指令大多数要影响到程序状态字寄存器PSW中的溢出标志OV、进位(借位)标志CY、辅助进位标志AC和奇偶标志位P。利用进位(借位)标志CY,可进行多字节无符号整数的加、减运算,利用溢出标志可对带符号数进行补码运算,辅助进位标志则用于BCD码运算的调整。

3.4.1 加法指令

        ADD  A,# data            ;A ← (A) + data
        ADD  A,direct            ;A ← (A) + (direct)
        ADD  A,Rn                ;A ← (A) + (Rn)
        ADD  A,@Ri               ;A ← (A) + ((Ri))

这组指令的功能是把源操作数所指出的内容与累加器A的内容相加,其结果存放在A中。源操作数的寻址方式分别为立即寻址、直接寻址、寄存器寻址和寄存器间接寻址。运算结果对程序状态字PSW中的CY、AC、OV和P的影响情况如下。

进位标志CY:在加法运算中,如果D7位向上有进位,则CY=1;否则,CY=0。

半进位标志AC:在加法运算中,如果D3位向上有进位,则AC=1;否则,AC=0。

溢出标志OV:在加法运算中,如果D7、D6位只有一个向上有进位时,OV=1;如果D7、D6位同时有进位或同时无进位时,OV=0。

奇偶标志P:当A中“1”的个数为奇数时,P=1;为偶数时,P=0。

例3-8 设(A)=94H,(30H)=8DH,执行指令ADD A,30H,操作如下:

结果(A)= 21H,(CY)= 1,(AC)= 1,(OV)= 1,(P)= 0

参加运算的两个数,可以是无符号数(0~255),也可以是有符号数(-128~+1 2 7)。用户可以根据标志位CY或OV来确定运算结果或判断结果是否正确。无符号数用CY位表示进位、溢出(不考虑 OV位),有符号数用 OV位表示溢出(不考虑CY位)。

上例中,若把94H、8DH看做无符号数相加,结果中CY=1,表示运算结果发生了溢出(结果超出了8位),此时溢出的含义是向高位产生进位,所以确定结果时不能只看累加器A的内容,而应该把CY的值加到高位上,才可得到正确的结果。即结果为121H,若把94H、8DH看做有符号数(补码表示的),结果中OV=1,它表示运算结果发生了溢出,A中的值是个错误的结果。因为两个负数相加,结果却为正数,很显然是错误的。

两个正数相加或两个负数相加时,若发生溢出,将改变结果的符号位,所得结果都是错误的,OV=1正好指出了这一类错误。

无论编程人员把参加运算的两个数看做是无符号数还是有符号数,计算机在每次运算后,都会按规则自动设置标志位CY、OV、AC、P,对于编程人员来说,应能根据这些标志来了解当前运算结果所处的状态,以确定程序的走向。

3.4.2 带进位加法指令

        ADDC  A,# data           ;A ← (A) + data + (CY)
        ADDC  A,direct           ;A ← (A) + (direct)+ (CY)
        ADDC  A,Rn               ;A ← (A) + (Rn)+ (CY)
        ADDC  A,@Ri              ;A ← (A) + ((Ri)) + (CY)

这组指令的功能是把源操作数所指出的内容与累加器A的内容相加、再加上进位标志CY的值,其结果存放在A中。源操作数的寻址方式分别为立即寻址、直接寻址、寄存器寻址和寄存器间接寻址。运算结果对PSW标志位的影响与ADD指令相同。

需要说明的是,这里所加的进位标志CY的值是在该指令执行之前已经存在的进位标志值,而不是执行该指令过程中产生的进位标志值。

例3-9 设(A)=AEH,(R1)=81H,(CY)=1,

执行指令ADDC A,R1,则操作如下:

结果(A)= 30H,(CY)= 1,(OV)= 1,(AC)= 1,(P)= 0

带进位加法指令主要用于多字节数的加法运算。因低位字节相加时可能产生进位,而在进行高位字节相加时,要考虑低位字节向高位字节的进位,因此必须使用带进位的加法指令。

例3-10 设有两个无符号16位二进制数,分别存放在30H、31H单元和40H、41H单元中(低8位先存),写出两个16位数的加法程序,将和存入50H、51H单元(设和不超过16位)。

由于不存在16位数的加法指令,所以只能先加低8位,后加高8位,而在加高8位时要连低8位相加的进位一起相加,编程如下:

        MOV   A,30H                ;取一个加数的低字节送A中
        ADD   A,40H                ;两个低字节数相加
        MOV   50H,A                ;结果送50H单元
        MOV   A,31H                ;取一个加数的高字节送A中
        ADDC  A,41H                ;高字节数相加,同时加低字节产生的进位
        MOV   51H,A                ;结果送51H单元

3.4.3 带借位减法指令

        SUBB  A,# data           ;A ← (A)- data -  (CY)
        SUBB  A,direct           ;A ← (A)-  (direct)-  (CY)
        SUBB  A,Rn               ;A ← (A)-  (Rn)-  (CY)
        SUBB  A,@Ri              ;A ← (A)-  ((Ri))-  (CY)

这组指令的功能是将累加器A中的数减去源操作数所指出的数和进位位CY,其结果存放在累加器A中。源操作数的寻址方式分别为立即寻址、直接寻址、寄存器寻址和寄存器间接寻址。运算结果对程序状态字PSW中各标志位的影响情况如下。

借位标志CY:在减法运算中,如果D7位向上需借位,则CY=1;否则,CY=0。

半借位标志AC:在减法运算中,如果D3位向上需借位,则AC=1;否则,AC=0。

溢出标志OV:在减法运算中,如果D7、D6位只有一个向上需借位时,OV=1;如果D7、D6位同时需借位或同时无借位时,OV=0。

奇偶标志P:当A中“1”的个数为奇数时,P=1;为偶数时,P=0。

减法运算只有带借位减法指令,而没有不带借位的减法指令。若要进行不带借位的减法运算,应该先用指令将CY清零,然后再执行SUBB指令。

需强调的一点是,减法运算在计算机中实际上是变成补码相加,下面举例说明。

例3-11 设(A)=DBH,(R4)=73H,(CY)=1。

执行指令SUBB A,R4 则操作如下:

结果(A)= 67H,(CY)= 0,(AC)= 0,(OV)= 1

由上述两式可见两种算法的最终结果是一样的。在此例中,若DBH和73H是两个无符号数,则结果67H是正确的;反之,若为两个带符号数,则由于产生溢出(OV=1),使得结果是错误的,因为负数减正数其结果不可能是正数,OV=1,就指出了这一错误。

3.4.4 加1指令

        INC  A               ;A ← (A) + 1
        INC  direct          ;direct ← (direct)+ 1
        INC  Rn              ;Rn ← (Rn) + 1
        INC  @Ri             ;Ri ← ((Ri)) + 1
        INC  DPTR            ;DPTR ← (DPTR) + 1

这组指令的功能是将操作数所指定单元的内容加1。本组指令除“INC A”指令影响P标志外,其余指令均不影响PSW标志。

加1指令常用来修改操作数的地址,以便于使用间接寻址方式。

3.4.5 减1指令

        DEC  A               ;A ← (A) - 1
        DEC  direct          ;direct ← (direct)- 1
        DEC  Rn              ;Rn ← (Rn) - 1
        DEC  @Ri             ;Ri ← ((Ri)) - 1

这组指令的功能是将操作数所指定单元的内容减1。除“DEC A”指令影响P标志外,其余指令均不影响PSW标志。

3.4.6 乘、除法指令

89C51单片机有乘、除法指令各一条,它们都是一字节指令,执行时需 4 个机器周期。

1.乘法指令

        MUL  AB              ;BA← (A)× (B)

这条指令的功能是把累加器A和寄存器B中的两个8位无符号数相乘,所得16位乘积的低8位放在A中,高8位放在B中。

乘法指令执行后会影响3 个标志:若乘积小于FFH(即B的内容为零),则OV=0,否则OV=1。CY总是被清零,奇偶标志P仍按A中1的奇偶性来确定。

例3-12 已知(A)=80H,(B)=32H,

执行指令 MUL AB

结果(A)= 00H,(B)= 19H,OV= 1,CY= 0,P= 0

2.除法指令

        DIV  AB              ;A← (A)/(B)之商,B← (A)/(B)之余数

这条指令的功能是对两个8位无符号数进行除法运算。其中被除数存放在累加器A中,除数存放在寄存器B中。指令执行后,商存于累加器A中,余数存于寄存器B中。

除法指令执行后也影响3个标志:若除数为零(B=0)时,OV=1,表示除法没有意义;若除数不为零,则OV=0,表示除法正常进行。CY总是被清零,奇偶标志P仍按A中1的奇偶性来确定。

例3-13 已知(A)=87H(135D),(B)=0CH(12D),

执行指令 DIV AB

结果(A)= 0BH,(B)= 03H,OV= 0,CY= 0,P= 1

3.4.7 十进制调整指令

        DA  A

该指令的功能是对A中刚进行的两个BCD码的加法结果进行修正。该指令只影响进位标志CY。

有时希望计算机能存储十进制数,而且能进行十进制数的运算,这时就要用BCD码来表示十进制数。

所谓BCD码就是采用4位二进制编码表示的十进制数。4位二进制数共有16个编码, BCD码是取它前 10 个的编码 0000~1001 来代表十进制数的 0~9,这种编码称为8421BCD码,简称BCD码。一个字节可以存放2位BCD码(称为压缩的BCD码)。

如果两个BCD码数相加,结果也是BCD码,则该加法运算称为BCD码加法。在89C51单片机中没有专门的BCD码加法指令,要进行BCD码加法运算,也要用加法指令ADD或ADDC,然而计算机在执行ADD或ADDC指令进行加法运算时,是按照二进制规则进行的,对于4位二进制数是按逢16进位;而BCD码是逢10进位,两者存在进位差。因此用ADD或ADDC指令进行BCD码相加时,可能会出现错误。例如:

在上述3组运算中,(a)的运算结果是正确的,因为8的BCD码就是1000;(b)的运算结果是错误的,因为13的BCD码应是00010011,但运算结果却是1101,BCD码中没有这个编码;(c)的运算结果也是错误的,因为 17 的BCD码应是00010111,而运算结果是00010001。

由此可知,当运算结果大于16或在10~16之间时,都将出现错误结果,因此要对结果进行修正,这就是所谓的十进制调整问题。

使用DA A指令可修正这种错误,它能对运算结果自动进行调整。实际上,计算机在遇到十进制调整指令时,中间结果的修正是由ALU硬件中的十进制调整电路自动进行的。因此,用户不必考虑它是怎样调整的。使用时只需在上述加法指令后面紧跟一条DA A指令即可。

在执行DA A指令之后,若CY=1,则表明相加后的和已等于或大于十进制数100。

例3-14 试编写程序,实现95+59的BCD码加法,并将结果存入30H、31H单元。

              MOV  A,# 95H       ;95的BCD码数送A中
              ADD  A,# 59H       ;A与59的BCD码相加,结果存在A中
              DA  A               ;对相加结果进行十进制调整
              MOV  30H,A         ;A中的和 (十位、个位的BCD码)存入30H
              MOV  A,# 00H       ;A清零
              ADDC  A,# 00H      ;加进位 (百位的BCD码)
              DA  A               ;BCD码相加之后,必须使用调整指令
              MOV  31H,A         ;存进位

第一次执行DA A指令的结果:(A)= 54H,CY= 1

最终结果(31H)= 01H,(30H)= 54H

需要指出的是,DA A指令只能用在加法指令的后面。如果要进行BCD码减法运算,也应该进行调整,但在89C51单片机中没有十进制减法调整指令,也不像有的微处理器有加减标志,因此要用适当的方法来进行十进制减法运算。

为了进行十进制减法运算,可用加减数的补数来进行,2位十进制数是对100取补的,例如:减法60-30=30,也可以改为补数相加为

60+(100-30)=130

丢掉进位后,就得到正确的结果。

在实际运算时,不可能用9位二进制数来表示十进制数100,因为CPU是8位的。为此,可用8位二进制数10011010(9AH)来代替。因为这个二进制数经过十进制调整后就是100000000。因此,十进制无符号数的减法运算可按以下步骤进行:

(1)求减数的补数,即9AH-减数;

(2)被减数与减数的补数相加;

(3)对第二步的和进行十进制调整,就得到所求的十进制减法运算结果。

这里用“补数”而没有用“补码”,这是为了和带有符号位的补码相区别。由于现在操作数都是正数,没有必要再加符号位,故称“补数”更为合适一些。

例3-15 编写程序实现十进制减法,计算87-38。

              CLR   C             ;减法之前,先清CY位,即CY= 0
              MOV   A,   # 9AH   ;9AH送A中
              SUBB  A,   # 38H   ;做减法,计算38的补数送A中
              ADD   A,   # 87H   ;38的补数与87做加法
              DA    A             ;对相加结果进行调整

丢掉进位,取调整结果的低8位,即得结果为十进制数49,显然是正确的结果。