MCS51单片机学习笔记(三)—— 指令系统

本文中所用到的完整Xmind脑图下载链接

1.汇编指令基本格式

标号区段: 操作码区段 操作数区段 ;注释区段

2.程序 = 指令集 = 操作码 + 操作数

这里写图片描述

2.1.数据(操作数)

2.1.1.数据来源

这里写图片描述
数据主要来源于 指令中包含的的立即数,单片机内部的寄存器,内部存储器(RAM+ROM),I/O输入输出设备

2.1.2.寻址方式

CPU通过地址寻找数据,主要有7种方式:

这里写图片描述
其中:

  • 内部RAM的低128字节(00H~7FH)可以直接寻址,寄存器寻址,寄存器间接寻址,除了这三种之外,位寻址区(20H~2FH)可以进行位寻址;
  • 程序存储区64K都必须通过PC或DPTR指针使用基址变址寻址
  • 外部RAM可以使用R0,R1,DPTR寄存器间接寻址,要注意R0和R1寻址范围为00-FF,DPTR寻址范围0000-FFFF;

2.2.操作码(指令系统)

这里写图片描述

2.2.1.数据传送类指令

这些指令中A寄存器最为重要,除了MOV指令,其余指令必须使用A寄存器!

这里写图片描述
重要指令应用——利用MOVC进行查表程序
①在ROM中建立数据表,因为A是变址寄存器,所以表的范围是256个字节,将数据表首地址存入PC或者DPTR;
②PC中的内容为PC当前值,CPU在读取本指令时,PC的值已经加一指向下一条指令了,这个要注意;、
③MOVC @A+PC和 MOVC @A+DPTR的区别在于查表范围:
利用@A+PC只能查找指令后面256字节内,而@A+DPTR可以查找到64k空间任意一个;
④将要查表的数据偏移量送入A中,查表,再将得到的数据写回A中;
再来用一个例子说明一下:
在程序存储器2000H开始存储0~9的平方表,然后查表获得3的平方存入内部30H单元中。

  • 使用DPTR作为基址寄存器
    ORG  0000H
    SJMP MAIN
    ORG  0030H
MAIN:
    MOV  DPTR,#2000H    ;表首地址
    MOV  A,#03H         ;查找3的平方
    MOVC A,@A+DPTR      ;查表
    MOV  30H,A          ;存入RAM的30H单元中
    SJMP $             ;程序暂停

    ;定义数据表
    ORG 2000H
    DB  0,1,4,9,16,25,36,49,64,81

    END

复位后可以看到寄存器情况:

这里写图片描述
程序执行完成的情况:
这里写图片描述
  • 使用PC作为基址寄存器
    ORG  0000H
    JMP MAIN

;因为数据表在2000H,A+PC只能寻找后面256个字节
    ORG  1FF0H      
MAIN:
;1FF0
    MOV  A,#03H         ;查找3的平方 
;1FF2    
    ADD  A,#0BH         ;修正PC值      
;1FF4
    MOVC A,@A+PC        ;查表     
    MOV  30H,A          ;存入RAM的30H单元中
    SJMP $             ;程序暂停

    ;定义数据表
    ORG 2000H
    DB  0,1,4,9,16,25,36,49,64,81

    END

因为@A+PC只能寻找到指令之后的256字节,所以指令外置距离表首地址不能太远,此处放在1FF0H处,最关键的是要修正PC值,因为执行到MOVC指令的位置时,PC变成了1FF4,执行该指令时PC自动加一变为1FF5,所以2000H - 1FFF5H = 000BH,又因为是寻找3的平方,所以偏移量03H,加起来A应该是0BH + 03H;
运算到MOVC指令时:

这里写图片描述
执行MOVC指令之后
这里写图片描述

2.2.2.算术运算类指令

这里写图片描述
重要指令应用

2.2.2.1.利用ADD+ADDC进行16位加法

因为51单片机只能做8位无符号数的算术运算,运算范围只有0~255,所以要进行扩展运算,跨展运算的关键是进位Cy,比如两个16位数相加,先将两个数的低字节不带进位相加,运算结果产生的进位保存在Cy中,然后将两个数的高字节带进位相加,这样就完成了16位加法,同样可以以8位为基础进行32位运行,关键要把握好进位Cy;

  • 举例说明:
    1067H存在R1R0中,30A0H存在R3R2中,计算R1R0+R3R2,并将结果存放在R5R4中。
    这里写图片描述
    ORG  0000H      ;起始地址
    SJMP MAIN
    ORG  0030H
MAIN:
    MOV R0,#67H     ;第一个加数的低字节  
    MOV R1,#10H     ;第一个加数的高字节
    MOV R2,#0A0H    ;第二个加数的低字节
    MOV R3,#30H     ;第二个加数的高字节

    MOV A,R0
    ADD A,R2        ;不带进位相加低字节
    MOV R4,A        ;结果低字节放在R4中

    MOV  A,R1
    ADDC A,R3       ;带进位计算高字节
    MOV  R5,A       ;结果高字节放在R5中     
    SJMP $

    END             ;汇编程序结束

结果如下:

这里写图片描述

2.2.2.2.BCD加法

BCD编码即用四位二进制数去表示一个十进制数,在这里比如要计算68+89 = 157,有两种方式,第一种是直接用十进制立即数计算:

    MOV A,#68
    ADD A,#89

这里实际将68转化为二进制44H存入A中,再将89转为二进制59H进行相加,最后将加法结果9DH存入A中,这样的方法用来计算十进制太不方便,所以多采用BCD码加法运算,也就是第二种运算;
用二进制BCD码68H来表示十进制数68,用二进制BCD码89H来表示十进制数89,这样就很方便了,但是最后出来的结果是F1H,当然这个结果不是错误的,我们要对它进行BCD码调整,方法如下:

  • 低4位大于9或者AC等于1(低4位向高4位有进位),则A+06H调整;
  • 高4位大于9或者Cy等于1(高4位有进位),则A+60H调整;

据此我们对运算结果F1H进行十进制调整,首先来看低4位,1< 9,但是在8(1000)+ 9(1001)的过程中,最高位都是1,AC置1,低4位向高4位进位,所以加06H调整,变为F7H;然后是高四位,F > 9,直接加60H进行调整,结果变为57H,Cy置1,所以最后得到的结果为1-57,也就是十进制157,显然这样的结果更加直观,方便;
在程序中可以直接调用DA A指令进行调整,如下:

    ORG  0000H      ;起始地址
    SJMP MAIN
    ORG  0030H
MAIN:    
    MOV A,#68H      ;BCD表示十进制68
    ADD A,#89H      ;BCD表示十进制89
    DA  A           ;十进制调整
    SJMP $

    END             ;汇编程序结束

结果如下:

这里写图片描述

2.2.2.逻辑运算及移位指令

这里写图片描述
逻辑与、或、异或运算经常用于 I/O口的操作

  • 与0相与为0,与1相与不变
  • 与1相或为1,与0相或不变
  • 与1异或取反,与0异或不变

2.2.3.控制转移类指令

这里写图片描述
控制转移类指令用于程序流程控制,实现程序的分支和循环,比如C语言中的if…else,switch….case(条件转移),for,while循环等(条件转移),这些指令是通过修改PC值实现的,特别要注意的是,使用时 要注意指令跳转范围

  • 重要指令应用

2.2.3.1.利用JMP指令的散转分支程序

在程序存储器中定义一个表用于存放多个指针(跳转指令,一般采用绝对转移指令AJMP,占两个字节空间),然后通过JMP @A+DPTR指令中A值的不同实现程序向多个方向的转移;
比如让程序根据R0中存放的不同键值执行不同键值的处理程序:

这里写图片描述
    ORG  0000H      ;起始地址
    SJMP MAIN
    ORG  0030H
MAIN:    
    MOV DPTR,#KEYTAB    ;DPTR指向表首地址
    MOV A,R0            ;从R0中取键值
    RLC A               ;因为转移表中AJMP占两个字节,相差2,所以进行乘2处理
    JMP @A+DPTR         ;程序散转到按键处理程序

;状态转移表
KEYTABLE:
    AJMP KEY0IDL        ;按键0处理程序
    AJMP KEY1IDL        ;按键1处理程序
    AJMP KEY2IDL        ;按键3处理程序
;存放按键处理程序
KEY0IDL:
    SJMP $     
KEY1IDL:
    SJMP $
KEY2IDL:
    SJMP $

    END             ;汇编程序结束

2.2.3.2.利用JNZ指令的while循环程序(条件:A是否为0)

通过JZ/JNZ判断A是否为0可以实现分支操作if(true)或if(false),如果分支过后又跳转到原来的程序地址去,就形成了循环结构,也就是do{ }while(true/false)结构;
举例:将外部RAM30H处的数据传送到片内40H起,直到数据为0(典型的while判0结构)

    ORG  0000H      ;起始地址
    SJMP MAIN
    ORG  0030H
MAIN:    
    MOV R0,#30H     ;设置片外RAM指针
    MOV R1,#40H     ;设置片内RAM指针
LOOP:
    MOVX A,@R0      ;取数据
    MOV  @R1,A      ;存数据
    INC  R1         ;指针后移
    INC  R0
    JNZ  LOOP       ;A不为0则继续执行
    SJMP $

    END             ;汇编程序结束

2.2.3.3.利用CJNE指令的while循环程序(条件:AB是否相等)

CJNE指令可以比较两个数的大小,如果相等则继续,如果不等则转移;因为CJNZ判断之后如果A>B,则Cy = 0,如果A < B, 则Cy = 1,所以如果想比较大还是小,则在转移之后判断位Cy就可以,此处暂且不论;
举例:将数据00H~0FH写入片内RAM的30H~3FH单元中;
分析:因为数据增长到0FH结束,地址指针增长到3FH结束,所以不能采用JZ判0指令作为循环条件,故采用CJNE指令;

    ORG  0000H      ;起始地址
    SJMP MAIN
    ORG  0030H
MAIN:    
    MOV A,#00H      ;起始数据
    MOV R0,30H      ;起始RAM指针
LOOP:
    MOV @R0,A       ;写入数据
    INC A           ;数据递增
    INC R0          ;指针后移
    CJNE A,#10H,LOOP
                    ;如果数据不为10H,则继续执行
    SJMP $
    END             ;汇编程序结束

2.2.3.3.利用DJNZ指令的for循环程序(条件:减一后是否为0)

DJNZ指令可以构造出for循环结构,首先指定一个寄存器或者一块内存单元作为计数器,放入计数初值,然后每次利用DJNZ指令减一,若不为0则继续执行,若为0则循环结束,跳出,执行后面的语句;
举例:编写循环统计片内RAM中30H单元开始的20个数据中0的个数,将统计结果放在寄存器R7中;

    ORG  0000H      ;起始地址
    SJMP MAIN
    ORG  0030H
MAIN:    
    MOV  R0,#30H    ;片内RAM指针
    MOV  R1,#20     ;循环计数器
    MOV  R7,#0      ;计数0的个数

LOOP:
    MOV  A,@R0      ;取数据
    JNZ  NEXT       ;判0
    INC  R7
NEXT:
    INC  R0
    DJNZ R1,LOOP

    SJMP $
    END             ;汇编程序结束

2.2.4.位操作类指令

这里写图片描述

2.2.4.1.位寻址与片内RAM寻址的区别

    MOV  A,30H
    MOV  C,30H

这两行代码中,指令都是MOV,都是30H这个地址,但是,8051的指令系统中,寻址方式根据指令而变

  • 第一行代码中30H指的是片内RAM30H单元,执行后将30H单元中的8bit数据传送到A中;
  • 第二行代码中30H指的是位寻址区30H单元(在片内RAM的20H~2FH是位寻址区,总共16*8 = 128个位地址(00H~7FH)),将位地址30H单元中的1bit数据传送到C中(PSW.7 = Cy);

2.2.4.2.利用CJNE+JC/JNC判断与某个数的大小

因为CJNE可以判断两个数的大小,若相等则执行后续语句,若不相等则跳转,同时,若第一个数>第二个数,则将Cy置1,反之则Cy = 0,所以,在CJNE进行是否相等判断后,进而可以利用JC/JNC再次对Cy进行判断;
比如想要在R0的值等于10H,大于10H,小于10H分别执行不同的程序

    ORG  0000H      ;起始地址
    SJMP MAIN
    ORG  0030H
MAIN:    
    MOV   A,R0      ;取数据
    CJNE  A,#10H,L1
    MOV   R1,#00H   ;R0=10H时,R1=00H
    AJMP  L3
L1:
    JC    L2
    MOV   R1,#10H   ;R0>10H,R1=10H
    AJMP  L3
L2:
    MOV   R1,#20H   ;R0<10H,R1=20H
L3:    
    SJMP $
    END             ;汇编程序结束

2.2.4.3.JB/JNB判0指令的两种用法

  • 通过判断ACC.7是0还是1来判断正负数
    MOV  A,R0   ;取数据
    JZ   ZERO   ;首先判断是否为0
    JB   ACC.7,PRON
                ;判断A最高位,为0则为正数
    SJMP PROP   ;否则为负数
  • 判断I/O口电平是高还是低(按键程序)
    MOV  P1,#0FFH   ;读取按键前先要写1
L1:
    JNB  P1.0,KEY1  
    JNB  P1.1,KEY2 
    SJMP L1         ;P1.0=1,P1.1=1等待
KEY1:
    MOV  P2,#0FFH
    SJMP L1
KEY2:
    MOV  P2.#00H
    SJMP L1

2.2.5.伪指令

这里写图片描述
上面我们所说的程序都是源程序,这些指令必须经过编译器编译链接成机器代码才可以被机器执行,这个翻译过程称为汇编,在汇编源程序的时候需要一些指令对汇编源程序进行相应的控制和说明,比如要 指定数据和程序存放的起始地址,为数据分配内存空间等等,这些指令并不会被翻译成目标代码,更不会被机器执行,所以称为伪指令。


猜你喜欢

转载自blog.csdn.net/mculover666/article/details/80913708