一、 实验要求
实验目的:
- 熟练掌握算术运算汇编指令的使用
- 熟练掌握子程序设计的基本方法
- 熟练掌握程序的调试方法
实验内容:
- 编程实现两个数:#8888H和#79H的乘除运算
- 结合实验1的代码,将加减乘除四则运算写成四个子程序,注意现场保护。
二、 实验设计
1.整体思路
(1)DIV:
- 初始化被除数的高位、低位和除数的寄存器,以及商的高位、低位和余数的寄存器。
- 设置一个计数器R0,用于循环执行16次,以处理16位的被除数。
- 进入循环,从被除数的高位开始,将被除数的高位和除数相减,并检查进位标志。
- 如果进位标志为0,表示没有借位,说明被除数可以整除除数,直接跳转到RESULT标签。
- 如果进位标志为1,表示有借位,说明被除数不能整除除数,余数增加1,并跳转至ENDING标签。
- 在RESULT标签中,将被除数的高位和低位分别右环移一位,得到新的被除数。
- 商的高位增加1。
- 在ENDING标签中,计数器R0减1,如果计数器不等于0,则继续执行循环。
- 循环结束后,程序结束。
(2)MUL:
- 在START标签中,首先从NUM1和NUM2内存地址读取被除数和除数。
- 使用MUL指令将被除数的低位与除数相乘,结果保存在R6和R5寄存器中,其中R6存储乘法的低位结果,R5存储乘法的高位结果。
- 接着再次从NUM1和NUM2内存地址读取被除数和除数的高位。
- 再次使用MUL指令将被除数的低位与除数相乘,结果保存在R4和A寄存器中,其中R4存储乘法的高位结果,A寄存器存储乘法的低位结果。
- 将累加器A中的值与R5相加,进位标志也参与运算,结果保存在R5中作为中位的值。
- 最终,R4存储计算结果的高位,R5存储计算结果的中位,R6存储计算结果的低位,即商和余数。
(3)FOUR:
- 首先定义了两个数据变量NUM1和NUM2,并给它们赋初值。
- 然后定义了保存现场寄存器的子程序(SAVE),将需要保护的寄存器值依次压入栈中,并为新的返回地址腾出空间。
- 接着定义了恢复现场寄存器的子程序(RESTORE),将之前保存的寄存器值从栈中弹出,恢复到相应的寄存器中。
- 进入主程序(START)后,依次调用加法子程序、减法子程序、乘法子程序和除法子程序,并在每一次调用子程序前后分别执行保存和恢复现场寄存器的操作。
- 程序最后结束。
2.流程图
DIV | MUL | FOUR |
![]() | ![]() | ![]() ![]() |
3.主要模块设计思路及分析
(1)DIV:
- 初始化模块:在START标签中,将被除数的高位、低位和除数的寄存器初始化,以及商的高位、低位和余数的寄存器初始化。
- 商的计算模块:在RESULT标签中,通过右环移操作更新被除数的高位和低位,同时商的高位增加1。
- 循环模块:使用计数器R0和DJNZ指令来控制循环次数,确保循环执行16次。
(2)MUL:
- 数据读取模块:使用MOV和MOVC指令从指定内存地址读取乘数1和乘数2的数据,分别存入R0和B寄存器中。
- 低位乘法模块:使用MUL指令将R0和B寄存器中的值相乘,结果保存在R6和A寄存器中。
- 高位乘法模块:再次使用MUL指令将R0和B寄存器中的值相乘,结果保存在R4和A寄存器中。
- 加法模块:使用ADDC指令将A寄存器中的值与R5寄存器中的值相加,进位标志也参与运算,结果保存在R5寄存器中。
(3)FOUR:
现场保护子程序(SAVE):
- 通过PUSH指令将PSW寄存器、B寄存器、DPL寄存器、DPH寄存器和新的返回地址依次压入栈中。
- 将栈指针SP的值移动到累加器A中,并为新的返回地址腾出空间(4个字节)。
- 将新的返回地址压入栈中,将栈指针SP的值移回SP寄存器。
- 保存现场寄存器的值完成。
现场恢复子程序(RESTORE):
- 通过POP指令将之前保存的寄存器值从栈中依次弹出。
- 恢复到相应的寄存器中,如数据指针高位DPH、数据指针低位DPL、B寄存器和PSW寄存器。
- 现场恢复完成。
加法子程序(ADD_NUMS):
- 将NUM1和NUM2的低位相加,结果保存在R0中,在此之前需要设置DPTR指向NUM1。
- 将NUM1和NUM2的高位相加,结果保存在R1中。
- 加法过程中使用了MOV和ADDC指令实现低位和高位的相加,并且考虑进位标志。
- 最终结果保存在R1和R2中。
减法子程序(SUB_NUMS):
- 将NUM1和NUM2的低位相减,结果保存在R0中,在此之前需要设置DPTR指向NUM1。
- 将NUM1和NUM2的高位相减,结果保存在R1中。
- 减法过程中使用了MOV和SUBB指令实现低位和高位的相减,并且考虑借位标志。
- 最终结果保存在R1和R2中。
乘法子程序(MUL_NUMS):
- 将NUM1和NUM2的低位相乘,结果保存在R0中,在此之前需要设置DPTR指向NUM1。
- 将NUM1和NUM2的高位相乘,结果保存在R4和寄存器A中。
- 乘法过程中使用了MOV和MUL指令实现低位和高位的相乘。
- 最终结果保存在R4、R5和R6中。
除法子程序(DIV_NUMS):
- 设置被除数的初始值和除数的值。
- 使用R0作为循环计数器,执行16次循环,将一个16 bit的被除数除以一个8 bit的除数,通过右移和减法来实现除法运算。
- 商的高位增加1,余数逢低位增加1的情况下进行修正。
- 循环结束后,最终结果保存在R0和R1中。
三、实现效果
DIV | MUL | FOUR |
![]() | ![]() | ![]() ![]() ![]() ![]() |
四、 总结
1.在编写除法运算的时候,第一次编写的时候出现了死循环,最后我的解决方式是通过计算的位数限定了循环的结果,死循环的问题就解决了。
2.在计算乘法运算的时候,前几次结果出现了错误,最后的解决方式是将低位的进位和中位的低位进行相加,结果最后显示正确。
3.通过这次实验,我深刻体会到了汇编语言的高效性和灵活性,以及8051单片机所具有的强大的功能。同时,我也意识到写汇编代码需要耐心和细心,需要对指令和操作数的含义非常清楚。
附录:
(1)DIV:
ORG 0000h
START:
MOV R1, #88H ; 将被除数的高位存入R1
MOV R2, #88H ; 将被除数的低位存入R2
MOV R3, #79H ; 将除数存入R1
MOV R4, #0 ; 将商的高位定义为0
MOV R5, #0 ; 将商的低位定义为0
MOV R6, #0 ; 将余数定义为0
MOV R0, #16 ; 由于需要将一个16 bit的被除数除以一个8 bit的除数,因此需要执行16次循环,才能将所有的被除数都除尽。
; 并且为了避免出现死循环,在这里设置了一个计数器,用来计算循环共执行16次
LOOP:
MOV A, R1 ; 用A暂时存放被除数的高位
SUBB A, R3 ; 用A减除数
JNC RESULT ; 如果进位标志为"0",即没有借位的情况下,直接跳转至RESULT
INC R6 ; 余数增1
SJMP ENDING
RESULT:
CLR C ; 进位标志清"0"
MOV A, R1 ; 把被除数的高位存放至A
RRC A ; 把经过进位标志位的累加器A右环移
MOV R1, A ; 把结果存回被除数的高位
MOV A, R2 ; 把被除数的低位存放至A
RRC A ; 把经过进位标志位的累加器A右环移
MOV R2, A ; 把结果存回被除数的低位
INC R4 ; 商的高位增1
ENDING:
DJNZ R0, LOOP ; 计数器控制循环
END
(2)MUL:
ORG 0000h
NUM1: DB 88H,88H ; data: 88 88
; addr: 0H 1H
NUM2: DB 79H ; data: 79
; addr: 2H
START:
MOV DPTR, #0H ; 用于读取数据
MOV A, #1H
MOVC A, @A+DPTR ; 读取程序储存器地址1H数据88(低位)
MOV R0, A ; 使用R0暂时储存数据
MOV A, #2H
MOVC A, @A+DPTR ; 读取程序储存器地址2H数据79
MOV B, A
MOV A, R0
MUL AB ; 低位相乘
MOV R6, B ; 将低位保存到R6,作为低位的值
MOV R5, A ; 将高位保存到R5
MOV A, #0H
MOVC A, @A+DPTR ; 读取程序储存器地址1H数据88(高位)
MOV R0, A ; 使用R0暂时储存数据
MOV A, #2H
MOVC A, @A+DPTR ; 读取程序储存器地址2H数据79
MOV B, A
MOV A, R0
MUL AB ; 低位相乘
MOV R4, A ; 将寄存器A(高位结果)的值保存到寄存器R4中,作为高位的值
MOV A, B ; 将寄存器B(低位结果)的值移回累加器A
ADDC A, R5 ; 将累加器A中的值与寄存器R5中的值相加,进位标志也参与运算
MOV R5, A ; 将累加器A中的结果保存到寄存器R5中,作为中位的值
END
(3)FOUR:
ORG 0000h
NUM1: DB 88H, 88H ; data: 88 88
; addr: 0H 1H
NUM2: DB 79H ; data: 79
; addr: 2H
SAVE: ;现场保护子程序,保存现场寄存器的值,并开启中断
PUSH PSW ; 将程序状态字PSW压入栈中
PUSH B ; 将寄存器B的值压入栈中
PUSH DPL ; 将数据指针低位DPL的值压入栈中
PUSH DPH ; 将数据指针高位DPH的值压入栈中
MOV A,SP ; 将栈指针SP的值移动到累加器A中
ADD A,#04h ; 为新的返回地址腾出空间(4个字节)
PUSH ACC ; 将新的返回地址压入栈中
MOV SP,A ; 将栈指针SP的值移回SP寄存器
RESTORE: ; 现场恢复子程序,恢复现场寄存器的值,并关闭中断
POP ACC ; 将返回地址弹出栈中
MOV SP,ACC ; 将返回地址移回栈指针SP中
POP DPH ; 恢复数据指针高位DPH的值
POP DPL ; 恢复数据指针低位DPL的值
POP B ; 恢复寄存器B的值
POP PSW ; 恢复程序状态字PSW的值
ADD_NUMS: ; 加法子程序,将NUM1和NUM2的值相加,结果保存在R1和R2中
MOV DPTR, #0H ; 加法模块 —— 低位
MOV A, #1H
MOVC A, @A+DPTR ; 读取程序存储器地址 2H 数据 77(低位)
MOV R0, A ; 使用 R0 暂时存储数据
MOV A, #2H
MOVC A, @A+DPTR ; 读取程序存储器地址 5H 数据 99(低位)
ADDC A, R0 ; 低位相互加
MOV R0, A
MOV A, #0H
MOVC A, @A+DPTR
MOV R1, A ;读取最终的高位结果
ADDC A, R0
MOV R2, A ; 读取最终的低位结果
SUB_NUMS: ; 减法子程序,将NUM1和NUM2的值相减,结果保存在R1和R2中
MOV DPTR, #0H ; 加法模块 —— 低位
MOV A, #1H
MOVC A, @A+DPTR ; 读取程序存储器地址 2H 数据 77(低位)
MOV R0, A ; 使用 R0 暂时存储数据
MOV A, #2H
MOVC A, @A+DPTR ; 读取程序存储器地址 5H 数据 99(低位)
SUBB A, R0 ; 低位相互加
MOV R0, A
MOV A, #0H
MOVC A, @A+DPTR
MOV R1, A ;读取最终的高位结果
SUBB A, R0
MOV R2, A ; 读取最终的低位结果
MUL_NUMS: ; 乘法子程序,将NUM1和NUM2的值相乘,低位结果保存在R6中,中位结果保存在R5中,高位结果保存在R6中
MOV DPTR, #0H ; 用于读取数据
MOV A, #1H
MOVC A, @A+DPTR ; 读取程序储存器地址1H数据88(低位)
MOV R0, A ; 使用R0暂时储存数据
MOV A, #2H
MOVC A, @A+DPTR ; 读取程序储存器地址2H数据79
MOV B, A
MOV A, R0
MUL AB ; 低位相乘
MOV R6, B ; 将低位保存到R6,作为低位的值
MOV R5, A ; 将高位保存到R5
MOV A, #0H
MOVC A, @A+DPTR ; 读取程序储存器地址1H数据88(高位)
MOV R0, A ; 使用R0暂时储存数据
MOV A, #2H
MOVC A, @A+DPTR ; 读取程序储存器地址2H数据79
MOV B, A
MOV A, R0
MUL AB ; 低位相乘
MOV R4, A ; 将寄存器A(高位结果)的值保存到寄存器R4中,作为高位的值
MOV A, B ; 将寄存器B(低位结果)的值移回累加器A
ADDC A, R5 ; 将累加器A中的值与寄存器R5中的值相加,进位标志也参与运算
MOV R5, A ; 将累加器A中的结果保存到寄存器R5中,作为中位的值
DIV_NUMS: ; 除法子程序,将NUM1除以NUM2,商保存在R0中,余数保存在R1中
MOV R1, #88H ; 将被除数的高位存入R1
MOV R2, #88H ; 将被除数的低位存入R2
MOV R3, #79H ; 将除数存入R1
MOV R4, #0 ; 将商的高位定义为0
MOV R5, #0 ; 将商的低位定义为0
MOV R6, #0 ; 将余数定义为0
MOV R0, #16 ; 由于需要将一个16 bit的被除数除以一个8 bit的除数,因此需要执行16次循环,才能将所有的被除数都除尽。
; 并且为了避免出现死循环,在这里设置了一个计数器,用来计算循环共执行16次
LOOP:
MOV A, R1 ; 用A暂时存放被除数的高位
SUBB A, R3 ; 用A减除数
JNC RESULT ; 如果进位标志为"0",即没有借位的情况下,直接跳转至RESULT
INC R6 ; 余数增1
SJMP ENDING
RESULT:
CLR C ; 进位标志清"0"
MOV A, R1 ; 把被除数的高位存放至A
RRC A ; 把经过进位标志位的累加器A右环移
MOV R1, A ; 把结果存回被除数的高位
MOV A, R2 ; 把被除数的低位存放至A
RRC A ; 把经过进位标志位的累加器A右环移
MOV R2, A ; 把结果存回被除数的低位
INC R4 ; 商的高位增1
ENDING:
DJNZ R0, LOOP ; 计数器控制循环
START:
CALL SAVE ; 执行现场保护子程序,保存现场寄存器的值
CALL ADD_NUMS ; 执行加法子程序,将NUM1和NUM2的值相加,结果保存在R0中
CALL SUB_NUMS ; 执行减法子程序,将NUM1和NUM2的值相减,结果保存在R0中
CALL MUL_NUMS ; 执行乘法子程序,将NUM1和NUM2的值相乘,结果保存在R0和R1中
CALL DIV_NUMS ; 执行除法子程序,将NUM1除以NUM2,商保存在R0中,余数保存在R1中
CALL RESTORE ; 执行现场恢复子程序,恢复现场寄存器的值
END ; 结束