汇编程序设计

题目1

编写一子程序,将内部RAM 40H ~ 4FH的内容复制到50H~5FH。

答案

MAIN:
MOV R0, #40H
MOV R1, #50H
MOV R2, #10H
LOOP:
MOV A, @R0
MOV @R1, A
INC R0
INC R1
DJNZ R2, LOOP
RET

好的,我来给出这道题的详细解析:

这个子程序的目的是将内部RAM的40H4FH的内容复制到50H5FH。程序的思路是:

  1. 首先使用两个寄存器R0和R1分别指向源地址40H和目标地址50H。
  2. 然后使用寄存器R2作为计数器,初始值为10H(16个字节)。
  3. 进入循环,每次循环做以下事情:
    • 将源地址(R0)指向的内存内容通过累加器A复制到目标地址(R1)
    • 源地址和目标地址分别自增(INC指令)
    • 计数器R2减1(DJNZ指令),如果计数器不为0就跳转到LOOP标签继续循环
  4. 当计数器R2减到0时,循环结束,使用RET指令返回

现在我们来逐行解释汇编代码:

MAIN:
MOV R0, #40H ; 将源地址40H存入寄存器R0
MOV R1, #50H ; 将目标地址50H存入寄存器R1
MOV R2, #10H ; 将计数器初值10H存入寄存器R2
LOOP:
MOV A, @R0 ; 将源地址指向的内存内容复制到累加器A
MOV @R1, A ; 将累加器A的内容复制到目标地址指向的内存
INC R0 ; 源地址自增
INC R1 ; 目标地址自增
DJNZ R2, LOOP ; 计数器减1,如果不为0则跳转到LOOP继续循环
RET ; 子程序返回

这个子程序非常简洁高效地完成了内存复制的功能。其关键是合理利用了寄存器资源:

  • R0和R1用作指针寄存器,指向源地址和目标地址
  • R2用作计数器,控制循环次数
  • A用作数据传送的中间媒介

同时灵活使用了DJNZ指令,避免了使用单独的DEC和CJNE指令来控制循环,使得代码更加简洁。

题目2

编写一子程序,将外部数据存储器空间2000H~2007H单元的内容复制到内部RAM 30H~37H单元。

答案

MAIN:
MOV DPTR, #2000H
MOV R0, #30H
MOV R1, #08H
LOOP:
MOVX A, @DPTR
MOV @R0, A
INC DPTR
INC R0
DJNZ R2, LOOP
RET

好的,我来给出这道题的详细解析:

这个子程序的目的是将外部数据存储器2000H2007H的内容复制到内部RAM的30H37H。程序的思路是:

  1. 首先使用DPTR(数据指针寄存器)指向源地址2000H。DPTR是一个16位寄存器,用于访问外部数据存储器。
  2. 然后使用寄存器R0指向目标地址30H。
  3. 使用寄存器R1作为计数器,初始值为08H(8个字节)。
  4. 进入循环,每次循环做以下事情:
    • 使用MOVX指令,将DPTR指向的外部存储器内容通过累加器A复制到R0指向的内部RAM
    • DPTR和R0分别自增
    • 使用DJNZ指令,将R1减1,如果R1不为0则跳转到LOOP标签处继续循环
  5. 循环结束后,使用RET指令返回主程序

需要注意的是:

  1. 访问外部数据存储器需要使用MOVX指令,而访问内部RAM则使用MOV指令
  2. DPTR是一个16位寄存器,由DPH和DPL两个8位寄存器组成。这里使用MOV DPTR, #2000H直接对16位寄存器赋值
  3. 这里使用R1作为循环计数器,而题目1中使用的是R2,这取决于程序的具体需求,任何一个寄存器都可以用作计数器

题目3

编写一个能将任何无符号8位二进制数转换为其等效BCD码的子程序。入口参数为内部RAM单元20H,出口参数为内部RAM单元30H和31H。其中30H存放百位数,31H存放十位数和个位数。

答案

MAIN:
MOV A, 20H
MOV B, #10
DIV AB
MOV 31H, B
MOV B, #10
DIV AB
MOV 30H, A
MOV A, B
SWAP A
ORL A, 31H
MOV 31H, A
RET

题目4

编写一个能将R2中的两位十进制数(压缩BCD码形式)转换为二进制数的子程序,转换结果扔保存到R2中。

答案

MAIN:
MOV A,R2
ANL A, #F0H
SWAP A
MOV B, #10
MUL AB
MOV R0, A
MOV A, R2
ANL A, #0FH
ADD A, R0
MOV R2, A
RET

好的,我来给出这道题的详细解析:

这个子程序的目的是将R2中的两位十进制数(压缩BCD码形式)转换为二进制数,转换结果仍保存到R2中。程序的思路是:

  1. 首先将R2的内容复制到累加器A中。
  2. 使用ANL指令与F0H进行按位与操作,取出高4位(十位数)。
  3. 使用SWAP指令交换高4位和低4位,现在低4位是十位数。
  4. 将立即数10复制到寄存器B中。
  5. 使用MUL指令将A和B相乘,结果的高8位在A中,低8位在B中。此时A中是十位数乘以10的结果。
  6. 将A的内容复制到R0中暂存。
  7. 再次将R2的内容复制到A中。
  8. 使用ANL指令与0FH进行按位与操作,取出低4位(个位数)。
  9. 使用ADD指令将A(个位数)与R0(十位数乘以10)相加,得到最终的二进制结果。
  10. 将结果保存到R2中。
  11. 使用RET指令返回主程序。

举个例子,假设R2初始值为12H(表示十进制数12),经过转换后,R2的值将变为0CH(表示二进制数12)。

这个程序巧妙地利用了BCD码的特点,通过位操作和乘法、加法等运算,高效地实现了BCD码到二进制的转换。需要注意的是,由于MUL指令的限制,这个程序只适用于两位BCD码,如果需要处理更多位数,需要进行相应的扩展。

题目5

编写一子程序,统计在内部RAM 20H~4FH单元中有多少个单元的内容为20H,统计结果保存在寄存器B中。

答案

MAIN:
MOV R0, #20H
MOV R1, #48
MOV B, #00H
ANS:
INC B
INC R0
DJNZ R1, LOOP
RET
LOOP:
CJNE @R0, #20H, ANS
INC R0
DJNZ R1, LOOP
RET

好的,我来给出这道题的详细解析:

这个子程序的目的是统计内部RAM的20H~4FH单元中,内容为20H的单元数量,并将结果保存在寄存器B中。

程序的思路是利用两个循环:外层循环用来遍历20H~4FH的每一个单元,内层循环用来判断当前单元的内容是否为20H,如果是则将计数器B加1。具体步骤如下:

  1. 将20H存入寄存器R0,作为起始地址
  2. 将48存入寄存器R1,作为循环计数器。因为20H~4FH共48个单元
  3. 将0存入寄存器B,作为计数器初值
  4. 开始外层循环:
    • 比较当前单元内容(@R0)与20H是否相等,如果不等则跳转到ANS处,将B加1,R0加1
    • 如果相等,则直接将R0加1
    • 将R1减1,如果R1不为0则继续循环,否则结束循环
  5. 内层循环ANS:
    • 将B加1
    • 将R0加1
    • 将R1减1,如果R1不为0则跳转到LOOP处继续外层循环,否则结束循环
  6. 循环结束后,B中保存了内容为20H的单元数量

需要注意的是,内层循环的两个分支最终都会将R0加1,所以R0始终指向当前正在判断的单元。而R1则始终表示剩余需要判断的单元数量。

这个程序的时间复杂度是O(n),其中n为单元数量,空间复杂度是O(1)。

题目6

内部RAM 30H单元中存放的是两位十进制数(压缩BCD码),编写将该十进制数转换为对应ASCII码的子程序,转换结果存放到内部40H和41H单元。要求十位数的ASCII码在40H单元,个位数的ASCII码在41H单元。

答案

MAIN:
MOV A, 30H
ANL A, #F0H
SWAP A
ADDC A, #30H
MOV 40H, A
MOV A, 30H
ANL A, #0FH
ADDC A, #30H
MOV 41H, A
RET

好的,我来给出这道题的详细解析:

这个子程序的目的是将内部RAM的30H单元中存放的两位十进制数(压缩BCD码)转换为对应的ASCII码,并将转换结果存放到内部40H和41H单元。其中,十位数的ASCII码存放在40H单元,个位数的ASCII码存放在41H单元。程序的思路是:

  1. 将30H单元的内容复制到累加器A中。
  2. 使用ANL指令与F0H进行按位与操作,取出高4位(十位数)。
  3. 使用SWAP指令交换高4位和低4位,现在低4位是十位数。
  4. 使用ADDC指令将A的内容与30H相加,得到十位数的ASCII码。因为数字09的ASCII码是30H39H,所以只需要将十位数加上30H即可得到对应的ASCII码。
  5. 将A的内容复制到40H单元中,存储十位数的ASCII码。
  6. 再次将30H单元的内容复制到A中。
  7. 使用ANL指令与0FH进行按位与操作,取出低4位(个位数)。
  8. 使用ADDC指令将A的内容与30H相加,得到个位数的ASCII码。
  9. 将A的内容复制到41H单元中,存储个位数的ASCII码。
  10. 使用RET指令返回。

这样,子程序就完成了将两位十进制数转换为对应ASCII码的任务。例如,如果30H单元中存放的是34H(表示十进制数34),经过转换后,40H单元中将存放33H(表示字符'3'),41H单元中将存放34H(表示字符'4')。

题目7

某十进制数的十位和个位的ASCII码存在R6和R7中,编写将ASCII码转换为对应十进制数的子程序,转换结果存放到R6中(十位数在高4位,个位数在低4位)。

答案

MAIN:
MOV A, R6
SUBB A, #30H
SWAP A
MOV R6, A
MOV A, R7
SUBB A, #30H
ADDC A, R6
MOV R6, A
RET

好的,我来给出这道题的详细解析:

这个子程序的目的是将存储在R6和R7中的十进制数的ASCII码转换为对应的十进制数,并将结果存放在R6中,其中十位数存在高4位,个位数存在低4位。

程序的思路是先将R6和R7中的ASCII码减去30H,得到对应的十进制数,然后将R6中的结果左移4位(即交换高低4位),最后将R7中的结果加到R6中。具体步骤如下:

  1. 将R6中的内容移到累加器A中
  2. 将A的内容减去30H,得到十位数的十进制值
  3. 交换A的高4位和低4位,将十位数的值移到高4位
  4. 将A的内容存回R6中
  5. 将R7中的内容移到累加器A中
  6. 将A的内容减去30H,得到个位数的十进制值
  7. 将A的内容加到R6中(带进位),此时R6的高4位是十位数,低4位是个位数
  8. 将A的内容存回R6中
  9. 返回

举个例子,假设R6中存的是'3'的ASCII码33H,R7中存的是'5'的ASCII码35H:

  1. 执行第1步后,A=33H
  2. 执行第2步后,A=03H
  3. 执行第3步后,A=30H
  4. 执行第4步后,R6=30H
  5. 执行第5步后,A=35H
  6. 执行第6步后,A=05H
  7. 执行第7步后,A=35H,因为R6的低4位是0,不影响加法结果
  8. 执行第8步后,R6=35H,即十进制数35

所以这个子程序可以正确地将十位和个位的ASCII码转换为对应的十进制数,并将结果按照要求存储在R6中。

题目8

设y=sin(x),x为0~60的整数。假设已将正弦函数制成了一个表,表中依序存放y值,y值是用两个字节表示的定点小数。编写用查表法由x值求y值的子程序,设程序入口为寄存器R4,出口为R2和R3,R2中存放y值的高字节,R3中存放y值的低字节。程序中的表格不需写出具体数值。表格需占用程序存储器多少个字节?

答案

MAIN:
MOV DPTR, #TABLE
MOV A, R4
RL A
PUSH ACC
MOVC A, @A+DPTR
MOV R2, A
POP ACC
INC A
MOVC A, @A+DPTR
MOV R3, A
RET
ORG 2000H
TABLE: DW

占用程序存储器122个字节
好的,我来给出这道题的详细解析:

这个子程序的目的是通过查表法,根据输入的x值(0~60的整数)求出对应的sin(x)值,并将结果存放在R2和R3寄存器中。其中,R2存放y值的高字节,R3存放y值的低字节。程序的思路是:

  1. 将表格的起始地址存入DPTR寄存器。
  2. 将R4中的x值复制到累加器A中。
  3. 使用RL指令将A左移一位,相当于乘以2。这是因为每个y值占用两个字节,所以要将x值乘以2才能得到对应的偏移地址。
  4. 将左移后的结果压入堆栈,用于后续计算低字节地址。
  5. 使用MOVC指令从程序存储器中取出y值的高字节,并存入R2寄存器。
  6. 从堆栈中弹出之前的结果,加1得到y值的低字节地址。
  7. 使用MOVC指令从程序存储器中取出y值的低字节,并存入R3寄存器。
  8. 返回主程序。

表格部分使用ORG指令将起始地址设置为2000H,并使用DW伪指令预留出存储空间。

关于表格占用的存储空间:由于x的取值范围是0~60,共61个整数,每个y值占用两个字节,所以表格总共需要占用61*2=122个字节的程序存储器空间。

题目9

在一音频处理设备中,需将音频信号用12位模/数转换器转换为12位无符号二进制数,并用软件将转换结果进行对数压缩,压缩结果为8位无符号二进制数。我们已经模/数转换值与对应的压缩结果制成一个表,表中依序存放单字节压缩结果。假设用模/数转换结果存于内部RAM 31H和30H单元(31H单元存高4位,30H单元存低8位),对数压缩结果保存到内部RAM 40H单元。要求:1)编写由模/数转换值计算压缩结果的子程序;2)计算表格占用的程序存储器空间。

答案

MAIN:
MOV DPTR, #TABLE
MOV A, 30H
ADD A, DPL
MOV DPL, A
MOV A,31H
ADD A, DPH
MOV DPH, A
MOVC A, @A+DPTR
MOV 40H, A
RET
ORG 2000H
TABLE: DW

占用4K字节

好的,我来给出这道题的详细解析:

这个子程序的目的是根据12位无符号二进制数的模/数转换值,查表得到对应的8位无符号二进制数的对数压缩结果,并将结果保存到内部RAM的40H单元。

程序的思路是利用模/数转换值作为表的索引,从表中取出对应的压缩结果。因为模/数转换值是12位的,而表中的索引是16位的,所以需要将12位的模/数转换值转换为16位的表索引。具体步骤如下:

  1. 将表的起始地址存入DPTR寄存器
  2. 将30H单元(模/数转换值的低8位)的内容加到DPL中,作为表索引的低8位
  3. 将31H单元(模/数转换值的高4位)的内容加到DPH中,作为表索引的高8位
  4. 利用MOVC指令从程序存储器中取出表索引对应的压缩结果
  5. 将压缩结果保存到40H单元
  6. 返回

需要注意的是,表格是存放在程序存储器中的,所以需要使用MOVC指令来访问。另外,表格的起始地址是在程序的2000H位置,这个地址需要根据实际情况来设置。

关于表格占用的程序存储器空间:因为模/数转换值是12位的,所以一共有2^12=4096个可能的值。每个值在表中对应一个字节的压缩结果,所以表格总共占用4096个字节,即4K字节。

这个程序的时间复杂度是O(1),因为无论模/数转换值是多少,都只需要一次表查找操作。空间复杂度也是O(1),因为只使用了固定的几个寄存器和RAM单元。

题目10

编写一平均滤波子程序,对系统采集的16个无符号8位二进制数求平均值。该数组从地址30H开始,计算出的平均值保存到地址2FH。分别考虑带四舍五入和不带四舍五入两种情况。

答案

MAIN:
MOV R0, 30H
MOV R1, 16
MOV R2, #00H
CLR C
LOOP1:
ADDC A, @R0
INC R0
JC LOOP2
DJNZ R1, LOOP1
MOV R3, A
MOV A, R2
RL A
RL A
RL A
RL A
MOV R2, A
MOV A, R3
RR A
RR A
RR A
RR A
ADD A, R2
MOV R2, A
LCALL SPE ; 假如带四舍五入
MOV 2FH, R2
RET
LOOP2:
CLR C
INC R2
LJMP LOOP1
SPE:
MOV A, R3
ANL A, #08H
CJNE A, #08H, NSPE
MOV A, R2
ADD A, #16H
MOV R2, A
RET
NSPE:
RET

好的,我来给出这道题的详细解析:

这个子程序的目的是对系统采集的16个无符号8位二进制数求平均值,并将结果保存到地址2FH。程序需要考虑带四舍五入和不带四舍五入两种情况。

程序的主要思路是:

  1. 将16个数相加,得到总和。
  2. 将总和右移4位,即除以16,得到平均值。
  3. 如果需要四舍五入,则判断第5位是否为1,如果为1则将平均值加1。

具体步骤如下:

  1. 将30H存入寄存器R0,作为数组的起始地址。
  2. 将16存入寄存器R1,作为循环计数器。
  3. 将0存入寄存器R2,用于存储进位。
  4. 清除进位标志C。
  5. 开始循环LOOP1:
    • 将当前数组元素(@R0)与A相加,结果存入A。
    • 将R0加1,指向下一个元素。
    • 如果有进位(C=1),则跳转到LOOP2。
    • 将R1减1,如果R1不为0则继续循环,否则跳出循环。
  6. 循环结束后,A中为低8位和,R2中为高8位和。
  7. 将A存入R3,暂存低8位和。
  8. 将R2移到A中,然后左移4位,即将高8位和的低4位移到高4位。
  9. 将A存回R2。
  10. 将R3移到A中,然后右移4位,即将低8位和的高4位移到低4位。
  11. 将R2与A相加,得到平均值的高8位。
  12. 如果需要四舍五入,则调用子程序SPE。
  13. 将R2存入2FH,即将平均值存入目标地址。
  14. 返回。

子程序LOOP2:

  1. 清除进位标志C。
  2. 将R2加1,记录进位。
  3. 跳转回LOOP1继续循环。

子程序SPE(四舍五入):

  1. 将R3移到A中,然后与08H进行与运算,提取第5位。
  2. 将A与08H比较,如果不等则跳转到NSPE返回。
  3. 如果相等,则将R2加16H(即0.5),再存回R2。
  4. 返回。

子程序NSPE(不四舍五入):

  1. 直接返回。

这样,子程序就完成了对16个无符号8位二进制数求平均值的任务,并根据需要进行了四舍五入。程序的时间复杂度是O(n),其中n为数组长度,空间复杂度是O(1)。

题目11

请编写一子程序,将保存在外部数据存储器中的数据块传送到内部数据存储器中,数据块在外部数据存储器中的起始地址位于R2(高字节)和R3(低字节)中,要传送的字节数位于30H单元,要传送到的内部数据存储器的起始地址位于R1中。

答案

MAIN:
MOV DPH, R2
MOV DPL, R3
LOOP:
MOVX A, @DPTR
MOV @R1, A
INC R1
INC DPTR
DJNZ 30H, LOOP
RET

好的,我来给出这道题的详细解析:

这个子程序的目的是将外部数据存储器中的一块数据传送到内部数据存储器中。需要注意的是,外部数据存储器的地址是16位的,而内部数据存储器的地址是8位的。程序的思路如下:

  1. 将外部数据存储器的起始地址设置为DPTR寄存器的值。其中,起始地址的高字节在R2中,低字节在R3中。

    MOV DPH, R2
    MOV DPL, R3
  2. 开始传送数据的循环:

    LOOP:
    MOVX A, @DPTR ; 从外部数据存储器中读取一个字节的数据到累加器A
    MOV @R1, A ; 将累加器A中的数据写入内部数据存储器
    INC R1 ; 内部数据存储器的地址加1
    INC DPTR ; 外部数据存储器的地址加1
    DJNZ 30H, LOOP ; 如果30H单元的值不为零,就跳转到LOOP标号继续循环

    在循环中,每次从外部数据存储器中读取一个字节的数据,并写入内部数据存储器。然后,内部数据存储器的地址和外部数据存储器的地址都加1,准备传送下一个字节的数据。循环次数由30H单元的值决定,每次循环都将30H单元的值减1,直到它变为零为止。

  3. 数据传送完成后,返回主程序:

    RET

这个子程序利用了DPTR寄存器和MOVX指令来访问外部数据存储器,利用@R1来访问内部数据存储器,利用DJNZ指令来控制循环次数。代码简洁明了,容易理解。