51单片机C常见关键字转汇编的模板

零.前言

因为明天就要考试了,但是我自己的我自己的51单片机还停留在C语言开发的层面,但是考试要求用汇编,所以花一点儿时间来学习"编译自动机"(其实是自己把常见的关键字iffor[]给弄成汇编版的,便于写代码)。
本身由于以前接触过逆向,所以有一点儿汇编基础,以及能熟练的使用C语言给51单片机编程,所以这篇文章的重点,是在C转汇编上,且为了应付考试,只选择比较好记的方式,而不讲究代码效率。

一.结构

在汇编中,由于代码是按地址顺序执行的,所以为了方便,我习惯将每一个功能,比如iffor都给封装成一个函数,这样逻辑清晰又好记。
我有一个习惯,累加器不用来进行数据存储,仅用于运算和临时变量,不做储存;储存的话,习惯用工作寄存器Rn去储存。

二.判断语句

在C语言中,我们的if是这样的:

if (condition)
{
    
    }
else
{
    
    }

CJNE是一个适用范围比较大的指令,所以我们就以它为基础,写一个判断语句:

JUDGE:		CJNE R0, #01H, JFALSE
			MOV R1, #0FFH
			RET
JFALSE:		MOV R1, #00H
			RET

这条命令就是判断R0是否为0x01,若是,则给R1赋值为0xFF,若不是,则赋值0。等同:

if (R0 == 0x01)
{
    
    
	R1 = 0XFF;
	return;
}
else
{
    
    
	R1 = 0x00;
	return;
}

举个例子,这是一个判断R0的值,并点灯的程序:
灯亮:
在这里插入图片描述
灯不亮:
在这里插入图片描述

三.循环语句

我们对一个C语言的分析,我将其写成比较好看的形式:

R2 = 5;
while(--R2 > 0)
{
    
    
	//express
}

这是一个很基础的循环,那么我们可以用DJNZ来模拟这个功能:

REPEATINIT: MOV R2, #05H
REPEATBEGIN:DJNZ R2, REPEATEXPRE
			RET
REPEATEXPRE:INC A
			SJMP REPEATBEGIN

这段代码会执行++A 4次,因为是先减。
抓换成C语言代码为:

R2 = 5;				// REPEATINIT
while(--R2 > 0)		// REPEATBEGIN
{
    
    
	++A;			// REPEATEXPRE
}
return;				// RET

这是一个每次让P2+4的代码:
在这里插入图片描述
当然还有一种死循环语句:

while(--R5);

这种代码可以用以下代码代替:

DJNZ R5, $

四.下标[]

在汇编中,其实一段数的地址本身就是连续的可以用[基址]+偏移指针的方式来模拟数组,所以一般来说,我们在这里面多用这种方式来查表:
基址使用DPTR来代替,比如:

MOV DPTR,#TAB

这样可以通过MOVC A,@A+DPTR (A=0、1、2……len)来将TAB中的内容赋值给A。

举例:对P2循环赋值数组DB中的内容(就不写延时了,通过调试步进查看代码效果)
也就是这样亮:
●●●●●●●● FFH
○●●●●●●○ 7EH
○○●●●●○○ 7CH
○○○●●○○○ 18H
○○○○○○○○ 00H
但是我们循环是从高减到0,所以索引因该倒着排。
当然,可以用数再减此时的循环变量的值可以得到正向索引。
完整代码:

ORG  0000H
MOV DPTR, #TAB
LJMP  MAIN
ORG  0100H

MAIN:		LCALL REPEATINIT
			SJMP MAIN
REPEATINIT:	MOV R0, #06
REPEATBEGIN:DJNZ R0, REPEATEXPRE
			RET
REPEATEXPRE:MOV A, R0
			DEC A
			MOVC A, @A+DPTR
			MOV P2, A
			SJMP REPEATBEGIN

TAB:		DB 00H,18H,3CH,7EH,0FFH
END

效果:
看红点儿就行了,二极管导通截止需要时间,所以显示是有延迟的:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
……

五.判断语句的嵌套

对于判断语句的嵌套,除了多个if叠加,还有一个就是switch,这种方式比较局限,需要这几种方案的值是连续的,比如switch(i) i=0、1、2……,因为单片机的代码是连续存放的,所以函数地址也可以由[基址]+偏移指针的方式得到。
比如通过修改R0来修改P2的状态:

ORG  0000H
MOV R0, #0
LJMP  MAIN
ORG  0100H

MAIN:		LCALL SwitchInit
			SJMP MAIN
SwitchInit: MOV DPTR, #Switch
			CLR A
			MOV A, R0
			MOV B, #3
			MUL AB
			ADD A, #1
Switch:		JMP @A+DPTR
			LJMP FUN0
			LJMP FUN1
			LJMP FUN2
FUN0:		MOV P2, #0H
			RET
FUN1:		MOV P2, #0FH
			RET
FUN2:		MOV P2, #0F0H
			RET

如何理解?首先我们把偏移量×3了,是因为LJMP占用三个机器码:
在这里插入图片描述
所以我们只需要将偏移量A×3+1,就可以得到我们跳转语句所在的地址。
该代码等同于:

void SwitchInit(int A)
{
    
    
	switch(3*A+1)
	{
    
    
		case &FUN0: FUN0();
		case &FUN1: FUN1();
		case &FUN2: FUN2();
	}
}
// SwitchInit并不会反回到CALL时入栈的地址,而是在FUN里通过RET,将PC指向入栈前地址(听不懂这句话也没关系)。

六.循环语句的嵌套

对于刚才写的一重循环

R2 = 5;				// REPEATINIT
while(--R2 > 0)		// REPEATBEGIN
{
    
    
	++A;			// REPEATEXPRE
}
return;				// RET

我们可以给它生个级:

R1 = 5;				// REINIT
while(--R1 > 0)		// RE1BEG
{
    
    
	R2 = 5;			// express
	while(--R2 > 0) // RE2BEG
	{
    
    
		// express
	}
}
return;				// RET

比如,写个流水灯:

ORG  0000H
LJMP  MAIN
ORG  0100H

MAIN:   MOV A, #01H
LOOP:   MOV P2, A
	    RL A
	    LCALL  REINIT
	    SJMP LOOP
REINIT: MOV R1,#20
RE1BEG: DJNZ R1,RE1EXP
		RET	;RE1END
RE1EXP: MOV R2, #200
		;More express
RE2BEG: DJNZ R2, RE2EXP
		SJMP RE1BEG ;RE2END
RE2EXP: NOP
		SJMP RE2BEG

当然,若没有RE2EXP的话,可以直接原地跳:

REINIT: MOV R1,#20
RE1BEG: DJNZ R1,RE1EXP
		RET	;RE1END
RE1EXP: MOV R2, #200
		;More_express
RE2BEG: DJNZ R2, $
		SJMP RE1BEG ;RE2END

嗯,看起来很啰嗦。

七.要背的一些寄存器和溢出值算法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/u011017694/article/details/112427089
今日推荐