第 3 章 MCS - 51 单片机指令系统与程序设计
3.1 MCS - 51 指令系统与指令的执行
3.1.1 指令系统概述
MCS - 51 单片机的指令系统是一套规定好的指令集合,用于控制单片机完成各种操作。这些指令可以分为不同的类型,包括数据传送、算术运算、逻辑运算、控制转移和位操作等指令。指令系统是单片机软件编程的基础,熟悉指令系统对于编写高效、准确的程序至关重要。MCS - 51 指令系统具有丰富的指令种类,能够满足各种应用场景的需求,例如工业控制、智能仪器仪表等领域。
3.1.2 指令的时序
- 时钟周期:是单片机最基本的时间单位,由外接晶体振荡器的频率决定。例如,若外接晶振频率为 ( f_{osc} = 12 \text{MHz} ),则时钟周期 ( T_{clock} = \frac{1}{f_{osc}} = \frac{1}{12 \times 10^{6}} \text{s} = 0.0833 \text{μs} )。
- 状态周期:一个状态周期由两个时钟周期组成。即 ( T_{state} = 2T_{clock} )。
- 机器周期:MCS - 51 单片机的一个机器周期由 6 个状态周期组成,也就是 12 个时钟周期。所以,当晶振频率为 ( 12 \text{MHz} ) 时,机器周期 ( T_{machine} = 12T_{clock} = 1 \text{μs} )。
- 指令周期:执行一条指令所需要的时间,以机器周期为单位。不同类型的指令,其指令周期不同。例如,单字节单周期指令的执行时间为 1 个机器周期,双字节单周期指令同样为 1 个机器周期,而乘法和除法指令则需要 4 个机器周期。
3.1.3 指令的执行过程
- 取指令阶段:程序计数器(PC)中存放着当前要执行指令的地址。在取指令阶段,PC 的值被送到地址总线上,然后从程序存储器中读取该地址对应的指令代码,并将其送入指令寄存器(IR)。同时,PC 的值自动加 1,指向下一条指令的地址。
- 指令译码阶段:存放在指令寄存器中的指令代码被送到指令译码器,指令译码器对指令进行分析,识别出指令的操作码和操作数,确定该指令要执行的操作。
- 执行指令阶段:根据指令译码的结果,单片机内部的各个功能部件协同工作,完成指令所规定的操作。例如,若指令是加法运算,运算器会从相应的寄存器或存储单元中取出操作数,进行加法运算,并将结果存放到指定的寄存器或存储单元中。
3.2 符号指令的寻址方式
3.2.1 寄存器寻址
寄存器寻址是指操作数存放在寄存器中,指令中直接给出寄存器的名称。例如,指令 MOV A, R0
,表示将寄存器 R0 中的内容传送到累加器 A 中。在 MCS - 51 单片机中,可用于寄存器寻址的寄存器有工作寄存器 R0 - R7、累加器 A、寄存器 B、程序状态字寄存器 PSW 等。这种寻址方式的优点是操作速度快,因为寄存器位于单片机内部,访问速度比访问存储器快。
3.2.2 立即寻址
立即寻址是指在指令中直接给出操作数。例如,指令 MOV A, #30H
,其中 #30H
就是立即数,表示将十六进制数 30H 传送到累加器 A 中。立即数前面通常用 #
符号表示。立即寻址主要用于给寄存器或存储单元赋初值,其特点是操作数直接包含在指令中,执行速度较快。
3.2.3 直接寻址
直接寻址是指在指令中直接给出操作数的存储单元地址。例如,指令 MOV A, 30H
,表示将内部数据存储器地址为 30H 的单元中的内容传送到累加器 A 中。直接寻址可以访问内部数据存储器的低 128 字节(地址范围 00H - 7FH)以及特殊功能寄存器(SFR)。这种寻址方式适用于对特定存储单元进行操作的情况。
3.2.4 寄存器间接寻址
寄存器间接寻址是指操作数的地址存放在寄存器中,指令中给出的是寄存器的名称。例如,指令 MOV A, @R0
,表示将以寄存器 R0 的内容为地址的内部数据存储器单元中的内容传送到累加器 A 中。可用于寄存器间接寻址的寄存器有 R0、R1 和数据指针 DPTR。其中,R0 和 R1 用于访问内部数据存储器低 128 字节(地址范围 00H - 7FH),DPTR 用于访问外部数据存储器(64KB 地址空间)。寄存器间接寻址为访问数组、表格等数据结构提供了方便。
3.2.5 变址寻址
变址寻址是以程序计数器 PC 或数据指针 DPTR 作为基址寄存器,以累加器 A 作为变址寄存器,两者内容相加形成操作数的地址。例如,指令 MOVC A, @A + DPTR
,表示以 DPTR 的内容为基地址,A 的内容为偏移量,将程序存储器中相应地址单元的内容传送到累加器 A 中。变址寻址常用于访问程序存储器中的表格数据,通过改变累加器 A 的值,可以灵活地访问表格中的不同元素。
3.2.6 相对寻址
相对寻址是在转移指令中使用的一种寻址方式。它以当前程序计数器 PC 的值为基准,加上指令中给出的相对偏移量,形成转移目标地址。例如,指令 SJMP rel
,其中 rel
就是相对偏移量。执行该指令时,PC 当前值加上 rel
的值,得到的结果就是转移后的目标地址。相对寻址主要用于实现程序的短距离转移,通常在条件转移指令中使用,方便根据程序运行的条件跳转到不同的地址继续执行程序。
3.2.7 位寻址
位寻址是对内部数据存储器的位寻址区(20H - 2FH)和特殊功能寄存器中的可寻址位进行操作。例如,指令 SETB 20H.0
,表示将内部数据存储器 20H 单元的第 0 位置 1。位寻址为处理逻辑判断和标志位操作提供了便利,在一些需要进行位控制的应用中非常有用,如工业控制中的开关量控制。
3.3 常用指令
3.3.1 数据传送类指令
- 通用数据传送指令:
- MOV:用于在寄存器、存储单元之间传送数据。例如,
MOV A, R1
将寄存器 R1 的内容传送到累加器 A;MOV 30H, A
将累加器 A 的内容传送到内部数据存储器 30H 单元。 - MOVX:用于单片机与外部数据存储器之间的数据传送。例如,
MOVX A, @DPTR
从外部数据存储器中以 DPTR 为地址的单元读取数据到累加器 A;MOVX @DPTR, A
将累加器 A 的数据写入外部数据存储器以 DPTR 为地址的单元。 - MOVC:用于从程序存储器中读取数据到累加器 A,常与变址寻址方式配合使用。如
MOVC A, @A + PC
和MOVC A, @A + DPTR
。
- MOV:用于在寄存器、存储单元之间传送数据。例如,
- 堆栈操作指令:
- PUSH:将数据压入堆栈。例如,
PUSH ACC
将累加器 A 的内容压入堆栈,堆栈指针 SP 自动加 1。 - POP:从堆栈中弹出数据。例如,
POP ACC
从堆栈中弹出数据到累加器 A,堆栈指针 SP 自动减 1。
- PUSH:将数据压入堆栈。例如,
- 数据交换指令:
- XCH:字节交换指令。例如,
XCH A, R2
将累加器 A 和寄存器 R2 的内容进行交换。 - XCHD:半字节交换指令。例如,
XCHD A, @R0
将累加器 A 的低 4 位和以 R0 内容为地址的内部数据存储器单元的低 4 位进行交换。
- XCH:字节交换指令。例如,
3.3.2 加减运算指令
- 加法指令:
- ADD:不带进位加法指令。例如,
ADD A, R3
将累加器 A 的内容与寄存器 R3 的内容相加,结果存放在累加器 A 中,同时根据运算结果设置程序状态字寄存器 PSW 中的进位标志 CY、辅助进位标志 AC 和溢出标志 OV 等。 - ADDC:带进位加法指令。例如,
ADDC A, 30H
将累加器 A 的内容与内部数据存储器 30H 单元的内容以及进位标志 CY 相加,结果存放在累加器 A 中,并更新 PSW 中的标志位。
- ADD:不带进位加法指令。例如,
- 减法指令:
- SUBB:带借位减法指令。例如,
SUBB A, R4
将累加器 A 的内容减去寄存器 R4 的内容以及借位标志 CY,结果存放在累加器 A 中,同时更新 PSW 中的标志位。由于 MCS - 51 单片机没有不带借位的减法指令,若要进行不带借位减法,需先将 CY 清零。
- SUBB:带借位减法指令。例如,
- 增量和减量指令:
- INC:增量指令。例如,
INC A
将累加器 A 的内容加 1;INC 40H
将内部数据存储器 40H 单元的内容加 1。 - DEC:减量指令。例如,
DEC R5
将寄存器 R5 的内容减 1;DEC @R1
将以 R1 内容为地址的内部数据存储器单元的内容减 1。
- INC:增量指令。例如,
3.3.3 逻辑运算及移位类指令
- 逻辑与指令:
- ANL:用于对两个操作数进行按位与操作。例如,
ANL A, #0FH
将累加器 A 的内容与立即数 0FH 进行按位与操作,结果存放在累加器 A 中,常用于屏蔽某些位(将不需要的位清零)。
- ANL:用于对两个操作数进行按位与操作。例如,
- 逻辑或指令:
- ORL:用于对两个操作数进行按位或操作。例如,
ORL A, R6
将累加器 A 的内容与寄存器 R6 的内容进行按位或操作,结果存放在累加器 A 中,可用于置某些位为 1。
- ORL:用于对两个操作数进行按位或操作。例如,
- 逻辑异或指令:
- XRL:用于对两个操作数进行按位异或操作。例如,
XRL A, 50H
将累加器 A 的内容与内部数据存储器 50H 单元的内容进行按位异或操作,结果存放在累加器 A 中,常用于对某些位取反。
- XRL:用于对两个操作数进行按位异或操作。例如,
- 移位指令:
- RL:循环左移指令。例如,
RL A
将累加器 A 的内容循环左移一位,最高位进入最低位和 CY。 - RR:循环右移指令。例如,
RR A
将累加器 A 的内容循环右移一位,最低位进入最高位和 CY。 - RLC:带进位循环左移指令。例如,
RLC A
将累加器 A 的内容连同 CY 一起循环左移一位,CY 进入最低位,A 的最高位进入 CY。 - RRC:带进位循环右移指令。例如,
RRC A
将累加器 A 的内容连同 CY 一起循环右移一位,CY 进入最高位,A 的最低位进入 CY。
- RL:循环左移指令。例如,
3.3.4 位操作指令
- 位传送指令:
- MOV C, bit:将指定的位(bit)传送到进位标志 CY 中。例如,
MOV C, 20H.3
将内部数据存储器 20H 单元的第 3 位传送到 CY 中。 - MOV bit, C:将进位标志 CY 的值传送到指定的位(bit)。例如,
MOV 30H.1, C
将 CY 的值传送到内部数据存储器 30H 单元的第 1 位。
- MOV C, bit:将指定的位(bit)传送到进位标志 CY 中。例如,
- 位状态控制指令:
- CLR C:将进位标志 CY 清零。
- CLR bit:将指定的位(bit)清零。例如,
CLR 25H.2
将内部数据存储器 25H 单元的第 2 位清零。 - SETB C:将进位标志 CY 置 1。
- SETB bit:将指定的位(bit)置 1。例如,
SETB 40H.0
将内部数据存储器 40H 单元的第 0 位置 1。
- 位逻辑运算指令:
- ANL C, bit:将进位标志 CY 与指定的位(bit)进行逻辑与操作,结果存放在 CY 中。
- ANL C, /bit:将进位标志 CY 与指定位(bit)的反进行逻辑与操作,结果存放在 CY 中。
- ORL C, bit:将进位标志 CY 与指定的位(bit)进行逻辑或操作,结果存放在 CY 中。
- ORL C, /bit:将进位标志 CY 与指定位(bit)的反进行逻辑或操作,结果存放在 CY 中。
- 位条件转移指令:
- JC rel:若进位标志 CY 为 1,则程序转移到相对地址
rel
处执行;否则,顺序执行下一条指令。 - JNC rel:若进位标志 CY 为 0,则程序转移到相对地址
rel
处执行;否则,顺序执行下一条指令。 - JB bit, rel:若指定的位(bit)为 1,则程序转移到相对地址
rel
处执行;否则,顺序执行下一条指令。 - JNB bit, rel:若指定的位(bit)为 0,则程序转移到相对地址
rel
处执行;否则,顺序执行下一条指令。 - JBC bit, rel:若指定的位(bit)为 1,则将该位清零,并程序转移到相对地址
rel
处执行;否则,顺序执行下一条指令。
- JC rel:若进位标志 CY 为 1,则程序转移到相对地址
3.3.5 伪指令
- ORG(Origin):规定程序或数据块的起始地址。例如,
ORG 0100H
表示从地址 0100H 开始存放程序或数据。 - END:标志源程序的结束,汇编程序遇到 END 后不再继续汇编。
- DB(Define Byte):定义字节数据。例如,
DB 30H, 40H, 'A'
定义了三个字节的数据,分别为十六进制数 30H、40H 和字符A
的 ASCII 码。 - DW(Define Word):定义字数据(16 位)。例如,
DW 1234H
定义了一个 16 位的数据 1234H,在存储器中按高字节在前、低字节在后的顺序存放。 - EQU(Equate):给一个符号赋一个值或地址。例如,
COUNT EQU 30H
将符号 COUNT 赋值为 30H,之后在程序中可以使用 COUNT 代替 30H。 - BIT:定义位地址符号。例如,
FLAG BIT 20H.0
将内部数据存储器 20H 单元的第 0 位定义为符号 FLAG,在程序中可以使用 FLAG 来操作该位。
3.4 汇编语言程序设计方法
以下为你展示《单片机原理及应用》中 3.4 汇编语言程序设计方法里各类型程序设计的代码示例,并对每行代码进行注释:
3.4.1 顺序程序设计
ORG 0000H ; 程序起始地址设为 0000H
START:
MOV A, #30H ; 将立即数 30H 传送到累加器 A
MOV B, #20H ; 将立即数 20H 传送到寄存器 B
ADD A, B ; 累加器 A 中的值与寄存器 B 中的值相加,结果存于 A
MOV 40H, A ; 将累加器 A 的结果存到内部数据存储器 40H 单元
SJMP $ ; 程序结束,原地踏步,$ 表示当前指令地址
END ; 汇编结束标志
3.4.2 分支程序设计
假设比较两个数大小,将较大的数存放到指定单元。
ORG 0000H ; 程序起始地址设为 0000H
START:
MOV A, #30H ; 将立即数 30H 传送到累加器 A
MOV B, #40H ; 将立即数 40H 传送到寄存器 B
CJNE A, B, COMPARE ; 比较 A 和 B 的值,若不相等则跳转到 COMPARE 处执行
EQUAL:
MOV 50H, A ; 若 A 和 B 相等,将 A 的值存到 50H 单元
SJMP END_PROGRAM ; 跳转到程序结束处
COMPARE:
JNC GREATER ; 如果 A >= B(无借位),跳转到 GREATER 处执行
MOV 50H, B ; 若 A < B,将 B 的值存到 50H 单元
SJMP END_PROGRAM ; 跳转到程序结束处
GREATER:
MOV 50H, A ; 若 A >= B,将 A 的值存到 50H 单元
END_PROGRAM:
SJMP $ ; 程序结束,原地踏步
END ; 汇编结束标志
3.4.3 循环程序设计
以累加 1 到 100 的和为例。
ORG 0000H ; 程序起始地址设为 0000H
START:
MOV A, #00H ; 初始化累加器 A 为 0
MOV R0, #01H ; 初始化寄存器 R0 为 1,用于计数
LOOP:
ADD A, R0 ; 将 R0 的值加到累加器 A
INC R0 ; R0 的值加 1
CJNE R0, #65H, LOOP ; 比较 R0 是否等于 100(64H + 1 = 65H),若不等则继续循环
MOV 40H, A ; 将累加结果存到内部数据存储器 40H 单元
SJMP $ ; 程序结束,原地踏步
END ; 汇编结束标志
3.4.4 多重循环程序设计
以延时程序为例,通过双重循环实现较长时间的延时。
ORG 0000H ; 程序起始地址设为 0000H
START:
MOV R1, #100 ; 外层循环计数器 R1 初始化为 100
OUTER_LOOP:
MOV R2, #250 ; 内层循环计数器 R2 初始化为 250
INNER_LOOP:
DJNZ R2, INNER_LOOP ; R2 减 1,若不为 0 则继续内层循环
DJNZ R1, OUTER_LOOP ; R1 减 1,若不为 0 则继续外层循环
SJMP $ ; 程序结束,原地踏步
END ; 汇编结束标志
3.4.5 子程序设计
设计一个简单的延时子程序,并在主程序中调用。
ORG 0000H ; 程序起始地址设为 0000H
START:
ACALL DELAY ; 调用延时子程序
SJMP $ ; 程序结束,原地踏步
; 延时子程序
DELAY:
MOV R3, #250 ; 延时子程序中,初始化寄存器 R3 为 250
DELAY_LOOP:
DJNZ R3, DELAY_LOOP ; R3 减 1,若不为 0 则继续循环
RET ; 返回主程序
END ; 汇编结束标志
以上代码基于 MCS - 51 单片机汇编语言编写,不同单片机的指令集可能略有差异,实际应用中需根据具体单片机型号进行调整。