入坑单片机(7):第7章汇编语言程序设计:算术运算程序设计
这一章的主要内容有1个伪指令和7种典型结构的程序设计:

其中伪指令可以视为对指令集的补充,但它不是我们要学的伪指令大约有起止标识ORG与END、赋值EQU、定义单字节数据DB、定义双字节数据DW、位地址赋值BIT。伪指令是写给汇编编译器的,不生成具体的汇编指令。
程序设计是为了解决某一个问题,把指令按照一定的意图有序地组合在一起。程序设计的过程包括以下步骤:
分析题目要求
确定方案画流程图
由步骤2确定变量并分配内存
由步骤2从指令集中选用合适的指令设计源程序
调试完善
我们先来看个第七章思考题第1题
1、已知a、b为8位无符号二进制数,分别存在data和data+1单元,编写程序计算5a+b

我们的思路是:考虑到5a+b的大小可能溢出8位,所以我们用两个单元来存储数据,先放低8位,再放溢出的进位到高8位。
MUL AB是乘法指令,乘积的高8位存储在寄存器B中,低8位在累加器A中,若乘积大于255,即寄存器内容非0时,溢出标志OV=1;若乘积小于255,B内容=0,OV=0。乘法指令结束时,Cy总是被清零的。
乘完后A中内容是5a的高8位,B中内容是5a的低8位。然后加法指令ADD A,31H,它把(31H)也就是b相加,再把A也就是低8位的内容存储,程序至此分段,下一段是处理进位溢出。
首先清零A,因为A已经完成了存储低8位的中转站任务,下一步得开始高8位的任务,首先清零:MOV A #00H同义于CLR A。ADDC A,源操作数是(A)+源操作数+(Cy)→(A),先把A清零再ADDC A,B,B中可能有溢出量,溢出量就是乘积时的高8位,我们把它连同ADD A,31H产生的进位Cy一同相加作为高8位,再存储。

将才这个题的目的在于热身一下,主流应该是有固定模式的课本例题:

这个多字节加法就是单字节加法的有限次重复操作,
定式为初始化+循环调用单字节加法子程序:
;单字节加法子程序dan_add,这个子程序请先背过
;入口条件:R0指出被加数所在单元的地址;R1指出加数所在单元的地址
;出口条件:R0指出和所在单元的地址,进位在CY中
dan_add:MOV A,@R0
ADDC A,@R1
MOV @R0,A
;——————————以上三句是一个指令句群,用来执行单字节相加功能
INC R0
INC R1
;——————————以上两句也是个指令句群,执行步进功能
RET
main:MOV R0,#20H
MOV R1,#30H
MOV R5,#03H;字节数即单字节加法的循环次数
CLR C
;——————————main函数执行初始化功能,包括装入运算数、设置控制数
duo_add:LCALL dan_add
DJNZ R5,duo_add
;——————————以上两句为1个句群,执行多次进行单字节加法功能
CLR A
ADDC A,#00H
MOV @R0,A
;——————————以上三句为1个句群,执行多字节加法时最高位的进位处理功能
RET
所以,这个程序的记忆方法是
dan-add{单字节相加+步进}
main{装入运算数、控制数}
duo_add{循环调用dan_add+处理最高位进位}
我们再来看下一个例题:

它和加法的相似,
;单字节减法子程序dan_sub,这个子程序请先背过
;入口条件:R0指出被减数所在单元的地址;R1指出减数所在单元的地址
;出口条件:R0指出和所在单元的地址,进位在CY中
dan_sub:MOV A,@R0
SUBB A,@R1
MOV @R0,A
;——————————以上三句是一个指令句群,用来执行单字节相减功能
INC R0
INC R1
;——————————以上两句也是个指令句群,执行步进功能
RET
main:MOV R0,#20H
MOV R1,#30H
MOV R5,#03H;字节数即单字节加法的循环次数
CLR C
;——————————main函数执行初始化功能,包括装入运算数、设置控制数
duo_sub:LCALL dan_sub
DJNZ R5,duo_sub
;——————————以上两句为1个句群,执行多次进行单字节加法功能
;减法与加法的不同之处在于无需考虑最高为的进位问题
RET
’所以我建议的记忆方法为:
dan-sub{单字节相减+步进}
main{装入运算数、控制数}
duo_sub{循环调用dan_sub}
再来看一个例题:

它与二进制的多字节加法基本一样。原因是十进制数在计算机中以压缩BCD码的形式存放,一个十进制数存储占半个字节,一个字节的存储单元可以存储2位。
所以2位十进制数加法就是单字节加法:
;单字节加法子程序shi_dan_add,这个子程序请先背过
;入口条件:R0指出被加数所在单元的地址;R1指出加数所在单元的地址
;出口条件:R0指出和所在单元的地址,进位在CY中
shi_dan_add:MOV A,@R0
ADDC A,@R1
DA A;转10进制
MOV @R0,A
;——————————以上四句是一个指令句群,用来执行单字节相加功能
INC R0
INC R1
;——————————以上两句也是个指令句群,执行步进功能
RET
main:MOV R0,#20H
MOV R1,#30H
MOV R5,#03H;字节数即单字节加法的循环次数
CLR C
;——————————main函数执行初始化功能,包括装入运算数、设置控制数
duo_add:LCALL shi_dan_add
DJNZ R5,duo_add
;——————————以上两句为1个句群,执行多次进行单字节加法功能
CLR A
ADDC A,#00H
MOV @R0,A
;——————————以上三句为1个句群,执行多字节加法时最高位的进位处理功能
RET
所以,这个程序的记忆方法是
shi_dan-add{单字节相加+转10进制+步进}
main{装入运算数、控制数}
duo_add{循环调用dan_add+处理最高位进位}
我们再来看下一个例题:

对于二进制的计算机实现减法,精妙之处在于正数补码与原码相同while负数补码=其反码+1,所以x-y=x+[-y]的补码=x+100-y,100D它存储是1001,1001+1=(1001,1010)B=9AH,∴
所以2位十进制数减法也能转化为单字节加法:
;单字节减法子程序dan_sub,这个子程序请先背过
;入口条件:R0指出被减数所在单元的地址;R1指出减数所在单元的地址
;出口条件:R0指出和所在单元的地址,进位在CY中
dan_sub:MOV A,@R0
SUBB A,@R1
ADD A,@R0
DA A;这是因为DA放在SUBB后边不起作用,所以要放在ADD后边
MOV @R0,A
;——————————以上三句是一个指令句群,用来执行单字节相减功能
INC R0
INC R1
;——————————以上两句也是个指令句群,执行步进功能
CLR C;课本201页是CPL C,我觉得应该是印错了,取反没道理,应该是把减法改补码相加时加法溢出位清零。
;——————————该句执行修正功能
RET
main:MOV R0,#20H
MOV R1,#30H
MOV R5,#03H;字节数即单字节加法的循环次数
CLR C
;——————————main函数执行初始化功能,包括装入运算数、设置控制数
duo_sub:LCALL dan_add
DJNZ R5,duo_sub
;——————————以上两句为1个句群,执行多次进行单字节加法功能
;减法与加法的不同之处在于无需考虑最高为的进位问题
RET
’所以我建议的记忆方法为:
dan-sub{单字节相减+步进+修正}
main{装入运算数、控制数}
duo_sub{循环调用dan_sub}
再来看一个例题:

首先只会单字节乘法是不够。乘法指令实现(A)x(B),乘积的高8位存储在寄存器B中,低8位在累加器A中。A、B都是8位寄存器,对于两个8位无符号二进制数相乘,结果是两个字节。多字节乘法与多字节加法、减法的不同之处在于,用乘法分配律得到两次两字节乘单字节得到两个三字节的中间乘积再相乘,才得最后的四字节乘积。这一下就多了很多道手续,我觉得太难了,不知道考不考。我只摆个简单的课本76页两字节乘单字节。
对于乘法运算,我建议你画Rn示意图。两字节乘单字节其实就是两次单字节乘单字节:
对于低8位、中8位、高8位,分别对应一段指令句群:
MOV A,R3;两字节因数低8位
MOV B,R1;单字节因数
MUL AB
MOV R6,A
MOV R5,B
;——————以上为一段指令句群,执行最后乘积低8位的计算,低8位乘积计算实为单字节乘单字节的子程序
MOV A,R2;两字节因数高8位
MOV B,R1;单字节因数
MUL AB
ADD A,R5;
;——————以上四句为一段指令句群,执行最后乘积中8位的计算,中8位乘积计算包括本位的单字节乘单字节,还需ADD低8位乘积的进位
CLR A
ADDC A,B
;——————以上两句为一段指令句群,用以执行加法、乘法最后结果最高位进位的计算
MOV R4,A;将最高位存入R4
对于多字节除法,就更加困难了:

除法比乘法更加困难主要原因是这个商和余数处理并不方便。除法指令实现累加器A内容除以寄存器B的内容,执行后商在A中,余数在B中。方便的除法是除数为2^n时,这样除法就可以采用移位的方法实现。盲猜这个位移的考察优先级更高,摆个位移的吧,假设被除数(R5)除以8.
DIV2:CLR C;清零进位;因为RRC是连带进位位的循环右移指令
;————————————该据单独作一个指令句群,执行子程序的内部初始化功能
MOV A,R5;装入被除数的高8位
RRC A;当(A)为偶数时,右移1位相当于除以2
MOV R5,A;商的高8位保存进R5
;————————————以上三句为一个指令句群,执行高8位除以2的功能
MOV A,R6;商的低8位
RRC A
MOV R6,A
;————————————以上三句为一个指令句群,执行低8位除以2的功能
DJNZ R4,DIV2
;————————————该句单独作一个指令句群,执行循环控制功能
RET
至此我们可以总结出一些编程规律:
1、常见的功能都有相应的定式指令句群来执行
2、先存后算。对于算术程序设计,不变的核心就是累加器A

加减的比重大于乘除大于其他
3、分层算
结构化设计,源程序划分为若干子程序,子程序又划分为若干指令句群。从功能或框图上看对应着将实现题目要求的功能拆解为51单片机基本功能的组合
4、分布算
有限次重复操作在编程中总是常见的