0. 寻址方式
寻址方式在汇编中是很重要的,汇编所有的操作都是和和内存或者寄存器打交道的,在80C51里面一共7种寻址方式。
1. 立即寻址:
这个没什么好说的,就是往寄存器或者内存里面写立即数,在80C51汇编里面立即数前面带一个#(这个和Intel其他汇编和AT&T的都是不一样的)。
MOV A, #00H ;把数字0放入寄存器A中
2. 直接寻址:
在80C51汇编中,如果数字前面不带#,就表明这个是一个地址,而且是绝对地址
MOV A, 20H ;把20H对应的内存的一个字节的内容传送到A中
MOV C, 20H ;把20H对应的内存的一个字的内容传送到C中(位寻址)
(注意P口也是内存地址,可能会出判断题判断MOV P0, #02H,是否合法类似的。)
3. 寄存器寻址:
在80C51中,RX表示寄存器(要记住寄存器是有4组的)
比如(R0) = 44H
MOV A, R0 ;把44H放入A寄存器中
4. 寄存器间接寻址:
在8086中我们很熟悉这个东西,比如在8086这种寻址方式就是 mov ax, [bx],但是80c51这个东西就很特别,在80c51中,如果要使用寄存器间接寻址:
:片内RAM(采用Ri(R0和R1,只能这两个),SP)
:片外RAM(采用Ri,DPTR)
比如(R0) = 44H (44H) = 30H
MOV A, @R0 ;把R0里面的内容对应的内存(44H)的内容(30H)放入A寄存器中
5. 变址寻址:
这种方式是拿来访问ROM的(而且也只能访问ROM)
MOVC A, @A+DPTR ;把A+DPTR的内存对应的内容放入A中
6. 相对寻址:
这个是专门针对相对跳转指令来说的,这些指令等下看。要注意的是这个相对转移的偏移值的大小是-128~127的。
目的地址 = PC + rel = 当前指令存储地址 + 指令的字节数 + rel
(要注意的是80C51的PC值是不断+1的(80C51有个部件叫PC Incremter),也就是8位8位移动(因为80C51是个8位的芯片,不能像80386那样移动四个字节或者两个字节),也就是说80C51看到跳转指令的时候是让自己再获取PC对应的操作数(8位),然后继续+1的)
7. 位寻址:
可位寻址的地方就是上一个文章所说的那个用户低128位的那16字节地址(20H~30H),以及特殊寄存器的一些可以叫出名字的一些地方。
位寻址的四种表示方式:
1. 直接操作地址:00H
2. 点操作符(有点像C的结构体的操作) 20H.1 PSW.6
3. 位名字,如IT0
4. 自定义: NAME BIT PSW.5 ;把PSW.5这个位地址给一个名字NAME
1. 汇编指令
书上废话太多,总结一下。
0. 传输指令(MOV)
格式(目的:源)
A :#data,direct,@Ri,Rn
Rn :#data,direct,A (R寄存器不能操作R寄存器)
direct :#data,direct,@Ri,Rn,A
@Ri :#data,direct,A (R寄存器不能操作R寄存器)
DPTR :#data16 (注意这个是MOV指令,不是MOVC或者MOVX,MOV只能是16位操作数到DPTR)
1. 特殊传输指令:
0. MOVC:(查表指令)
格式(目的:源)
A :@A+DPTR,@A+PC(都是在ROM里面查东西)
1. MOVX:(外部内存操作,扩展的时候非常需要用到)
格式(目的:源)
A :@A+Ri,@DPTR(读外部内容)
@Ri :A (写外部内容,注意这个只能寻址到256位,如果RAM寻址比8位还高,还需要给P2口提供高8位)
@DPTR :A (写外部内容,外部64KB的内容都可以访问到,DPH由P2输出,DPL由P0输出)
2. PUSH:(压栈指令):
PUSH direct
3. POP: (弹栈指令):
POP direct (注意PUSH和POP只能操作内存,有一种情况是需要我们压入A的内容,则要使用POP/PUSH ACC)
4. XCH:(字节交换指令):
格式(目的:源)
A :direct,@Ri,Rn
5. XCHD:(半字节交换指令):
格式(目的:源)
A :@Ri(这个指令比较奇葩,只能操作@Ri,是把Ri的内容指向的内存的低四位和A的低四位互换)
6. SWAP:(自交换):
SWAP A(不解释了。。)
2. 算术指令:
0. 不带进位加法指令(ADD)
格式(目的:源)
A :#data,direct,@Ri,Rn
1. 十进制调整指令(DA)
DA A
2. 带进位的加法指令(ADDC)
格式(目的:源)
A :#data,direct,@Ri,Rn
3. INC(自增指令)
格式(目的:源)
INC direct,@Ri,Rn,A, DPTR
4. SUBB(带借位的减法指令)
格式(目的:源)
A :#data,direct,@Ri,Rn(注意减法指令没有不带借位的)
5. DEC(自减指令)
格式(目的:源)
DEC direct,@Ri,Rn,A(没了DPTR)
6. MUL(无符号相乘指令,一定要记得是无符号的!)
MUL AB (A*B)(低8位在A上,高8位在B上,如果A溢出,OV = 1,B有结果,否则0V = 0,结果只在A中,进位CY总是会被清零,P的值按A的值来决定)
7. DIV(无符号除法指令)
DIV AB (A/B)(A是商,B是余数,OV的值只与除数是否为0有关,如果除数是0,那么OV = 1,否则0V = 0,CY会被清零,P的值按A的值来决定)。
2. 逻辑运算指令:
1. ANL(按位与),ORL(按位或),XRL(按位异或)
格式(目的:源)
A :#data,direct,@Ri,Rn
direct :#data,A
C :bit(可位寻址)
bit(可位寻址) :C
2. CLR(清零),CPL(取反),RL(循环左移),RLC(带CY位的循环左移),RR(循环右移),RRC(带CY位的循环右移)
操作A和位,注意带CY位的循环移动是把最高位放到CY位上,然后最低位是根据上一个CY位来的。
3. 跳转指令:
这些指令相当好记,因为只用记英文就知道这些指令是干嘛的了。
1. LJMP(long jump,长转移指令):
可以跳转到一个16位的绝对地址
2. AJMP(短转移指令):
可以跳转到一个11位的绝对地址
3. SJMP(相对转移指令):
rel是目标地址相对PC的偏移值(这里应该这样理解,处理器一看到SJMP指令,要先指向SJMP指令的下一条指令,所以先PC + 2(当然了其实一个一个加的,先遇到SJMP的8位操作码,然后再识别8位操作数,问题就是这个8位的操作数,这个8位的操作数实际上是编译的时候已经算好的了,是按照目标地址 - (SJMP的汇编地址 + 2)来算的 )
比如当我执行SJMP $的时候,$在这里是助记符而已,表明的当前地址,所以在编译的时候,会在SJMP的目的操作数上替换成-2,所以等SJMP执行时就自动跳转到本身了。
4. JMP(散转移指令):
这个指令是为了实现跳转表的,跳转表就是我们经常写的C的那个switch在汇编层实现的东西,具体格式就是JMP @A+DPTR(A + DPTR里面的内容是要跳转到的位置的据对地址)。
5. 条件转移指令(JZ,JNZ,CJNE,DJNZ,JC,JNC,JB,JNB,JBC):
这些指定都加多了一些条件,然后进行的都是相对转移(比如JZ,就是JUMP IS ZERO(当结果为0的时候就进行转移(和8086看ZF位不一样,80C51是看的A的值是否为0,如果A的内容为0,则转移,否则不转移)),JNZ刚好相反)
CJNE是比较不相等才转移(注意这个指令只能用于无条件数,有条件数是不适用的,因为它只看CY)。
格式:
CJNE A, #data, rel
CJNE A, direct, rel
CJNE Rn, #data, rel
CJNE @Ri, #data, rel
DJNZ是最常用的拿来做循环的指令(曾几何时我没用这个指令被老师说我程序写的太复杂),这个指令很容易拿来实现for,这个指令最大的特点就是可以边比较边把寄存器或者目的地址内容减1,直到减到0为止。
格式:
DJNZ Rn, rel
DJNZ direct, rel
JB,JBC,JNB指令都是看对应的位地址的情况然后跳转,JB是目的位地址是1则跳,否则不跳,JNB功能刚好和JB的相反,JBC除了能比较位为1然后跳转意外,还能把对应的位地址清零
JC和JNC则是看的是CY标记位。
ACALL和LCALL这两个指令就不用说了,一个短调用一个是长调用,这两个命令都要和RET指令一起用。RETI是和中断一起用的,RETI除了会把压入栈的地址弹出来外还会把80C51内部的挂起的中断请求给撤销掉,以响应下次中断(和8086的IRQ是一样的)。
2. C51
C51虽然长得和C差不多,写的时候和C也差不多,但是毕竟不是正统的CS的产物,有一点奇奇怪怪的东西还是需要注意一下的。
首先C51的整形(unsigned int和int都是16位的(并不是32位),而且我建议大家写程序的时候不要去定义一个什么宏uchar来表示什么unsigned char,这种做法简直是在侮辱编辑器,要是keli写的不够爽可以换VScode或者Notepad++)。而unsigned long 和long都是32位的,float是32位的,同时C51有bit型,这个是C没有的东西。
特殊功能寄存器型(sfr,sfr16),分别可以指向8位特殊寄存器(当然必须是可寻址的那些了),16位特殊寄存器。
比如sfr FUCK = 0x90; 我们就定义了FUCK这个变量指向0x90这个地址对应的特殊功能寄存器(当然我们知道这个是P1,是8位的)
sbit可以定义一个可位寻址,这个可以定义内部RAM的位寻址空间以及特殊功能寄存器,
比如可以sbit papa = P0^0,这样papa这个变量就是P0^0了。
C51另一个特色是可以自定义存储类型,这个在外部拓展写代码的时候是很重要的。
比如我可以定义一个指针unsigned char xdata *ptr = 0x7fff,这个指针指向外部内存0x7ffff地址。
另外我们也可以在C51中定义存储模式,C51定义了三种存储模式,SMALL(默认存储类型是data,存在片内RAM,速度快),COMPACT(pdata(紧凑模式),存储于片外分片RAM),LARGE(xdata大模式,存储于片外64KB的RAM,速度慢)。
注意在C51里面,中断子程序的写法是这样的void Fun()interrupt m uisng n(没有返回值和没有参数,并且后面带着interrupt(使用哪个中断(中断从0-4分别为外部中断0,定时器中断0,外部中断1,定时器中断1,串口中断))和using(80c51一共4个工作寄存器组))。
3. 一些题目
选了一些题目,有一些是书上作业,有一些是老师布置过的题目
1. 将R1的内容传送到R0(注意80c51MOV指令不能寄存器对寄存器)
MOV A, R1
MOV R0, A
2. 将内部RAM单元10H的内容送到外部RAM单元1000H(注意1000H已经比256要大了,不能用@Ri,要用DPTR)
MOV A,10H
MOV DPTR,#1000H
MOVX @DPTR, A
3. 将外部ROM内容1000H的内容传送到R5(注意MOVC的格式只有@A + DPTR或者@A + PC,不能少A)
MOV DPTR,#1000H
MOV A, #00H
MOVC A, @A+DPTR
MOV R5 ,A
4. 将外部RAM单元2000H的内容传送到外部RAM单元2001H(注意MOVX的格式)
MOV DPTR, #2000H
MOVX A, @DPTR
MOV DPTR, #2001H
MOVX @DPTR, A
5. 现外部RAM 2000H和内部RAM20H有内容,设计程序实现将这两个单元内容相加,将其结果的十位和各位送到外部RAM的2000H,百位送到F0位
MOV R0, #2000H
MOVX A, @R0 ;注意是外部内容
ADD A, 20H
DA A ;DA一定要放在ADD之后
MOVX @R0, A ;A包括了十位和个位
MOV F0,C
6. 屏蔽20H的高4位(注意逻辑指令是可以直接操作内存的,不要傻乎乎地转A)
ANL 20H, 0FH
7. 将E0H的低4位取反,高4位不变(注意这个世界有异或运算这种东西)
XRL E0H, 0FH
8. 将31H:30H和11H:10H的内容实现16位数相加((31H:30H) + (11H:10H)),把结果存在30H中(51是高端字节序)
MOV A, 31H ;高端字节序,低位在高字节
ADD A, 11H
MOV A, 30H
ADDC A, 10H
MOV 30H, A
(PS如果是减法,由于减法是没有不带借位减法的,所以在第一步必须CLR C,其他只要把ADD和ADDC换成SUBB就可以了)
9. 设X变量在内存10H内,Y在内存20H内,编写程序实现:(完整实现) Y = { 80H x>0; 50 x ==0 ; 0FFH x<0 }
ORG 0000H
LJMP MAIN
ORG 0100H
MAIN:
MOV A,10H
CLR C
JB ACC.7 LESS ;注意这里不要用SUBB,题目没有告诉我们这是无符号数,SUBB的话要看OV和CY
GREAT:
JZ EQUAL
MOV Y,#80H
AJMP OUT
EQUAL:
MOV Y,#50H
AJMP OUT
LESS:
MOV Y,0FFH
OUT:
...
10. 在内部RAM21H单元开始存储了有一组单字节的不带符号数,数据长度是30H,要求求出最大的数放入BIG单元
MOV R6, #30H ;R6存放有多少个数
MOV R1, #21H ;数组开始
MOV R0, #00H ;R0放的是最大的数
LOOP:
CLR C
MOV A, R0
SUBB A, @R1
JNC CONTINUE
MOV A, @R1
MOV R0, A
CONTINUE:
INC R1
DJNZ R6,LOOP
MOV R3, BIG ;R3指向了BIG单元
MOV A, R0
MOV @R3, A