ARM汇编指令

ARM指令是通用指令,指的是所有的ARM芯片都支持。
1.ARM汇编程序
一个ARM汇编程序,大体是由下列的三类指令组成:
1)ARM汇编指令
ARM汇编指令由ARM公司定的。
汇编语言是CPU执行效率最高的一门语言。
一条汇编指令是唯一对应一条机器指令(二进制码)。

汇编指令由两个部分组成:

		汇编指令 = 操作码 + 操作数
			操作码:表示一种操作,一种运算
				比如:
					ADD 表示加法运算
					MOV 表示移动(赋值)
					SUB 表示减法运算
			操作数:表示要操作的数据或者说参与运算的数据
				比如:	
					MOV R0,#3   ;R0 = 3

2)伪指令
伪指令是由编译器产商提供,比如:
Keil环境有Keil的伪指令
GNU环境下有GNU的伪指令

	伪指令-->假指令,所以不是可执行指令。	
	
伪指令的作用一般是:

	仅在编译过程中起控制作用,不产生可执行目标代码
	与机器指令代码无一一对应的关系,只能被编译器识别,编译完成后,目标程序
	中不再出现伪指令。

指令和伪指令到底有什么区别?

	指令是在执行阶段发挥作用的,由CPU来执行
	伪指令是在编译阶段发挥作用的,由对应的编译器来解释,CPU并不会执行。

3)宏指令(等同于C语言中的宏定义)
同样的还是由编译器产商提供。

基本格式:
	label 指令 ;
		其中:
			label   是一个标签,要顶格写,标志着当前行的位置(地址)
			
			指令	可以是汇编指令或者伪指令	
			;		行注释
			label 指令 ; 任意都可以省略。
		
		例子:
			loop 
				MOV R0,#3
	label EQU 数值		;等同于#define label 数值
	比如:
		#define PI 3.14 ---> PI EQU 3.14

2.编写一个简单的启动文件

    ;.s文件中,;号到这一行的末尾都是注释
	;在Keil中所有的指令不能顶格写
	;所有的标号or标签必须顶格写
	
    IMPORT main
    EXPORT Is_Multi         ;设定为外部可访问
    
Stack_Size EQU 0x200		;EQU用来定义一个宏,是一条宏指令
							;Stack_Size就是一个宏名
							;定义一个堆栈空间的大小为0x200
	;在linux进程中也好还是在STM32中也好,都会对程序进行分段
	;“段”:就是一块内存区域,用来存放代码或者数据或者地址等
	;在编写代码之前必须将内存划分为相应的区域
	;在Keil中定义一个段使用伪指令:Area
	;语法格式:AREA 段名,段属性1,段属性2....
	;段属性:	NOINIT	表示这段内存不需要初始化
	;			CODE	表示代码段
	;			DATA	表示数据段
	;			READWRITE 表示可读可写
	;			用CODE 和 READWRITE 去修饰一个段是互相冲突的
	;			ALIGN	用来设置对齐方式的
	;			如:ALIGN = 3,表示存储空间是2^3(8)字节对齐
	;			字节对齐的主要原因是为了提高访问效率
	;汇编程序中,必须要有的段有三个:堆栈段、RESET段、代码段。
	
	;定义堆栈段
	AREA Stack,NOINIT,READWRITE,ALIGN=3
	;现在仅仅只是把段的属性设置完成,并没有真正的把这个段开辟出来

Stack_Start					;这是一个标签,表示Stack这个段从此地址处开始
	SPACE Stack_Size		;SPACE x伪指令,表示开辟(占用)x个字节的区域
Stack_End					;这是一个标签,表示Stack这个段到此地址处结束
							;Stack这个段是从Stack_Start到Stack_End之间的Stack_Size个字节的内存空间
							;Stack段实际上是处于SRAM中
							;Stack_Start-->栈底指针 Stack_End-->栈顶指针 
      
	PRESERVE8				;表示之后的代码无特殊说明的话,都是以8字节对齐
	THUMB					;表示之后的代码无特殊说明的话,处理器都是Thumb状态
							;M4只支持THUMB指令
	      
	;定义RESET段
	;用来保存Cortex-M4中断向量表(本质上是函数指针数组)
	AREA RESET,DATA,READONLY	;只读数据段,段名必须为RESET
__Vectors						;__Vectors这是一个标号,表示RESET段从此处开始
	DCD	Stack_End				;DCD:开辟四个字节的空间   开辟了四个字节的空间用来保存栈顶元素的地址
								;中断向量表下标为0的那个元素里面必须是保存堆栈区域栈顶地址
	DCD __start					;中断向量表下标为1的那个元素里面必须是保存复位(重启)中断服务函数地址							
	
	SPACE 0x400					;后面可能还有中断处理函数需要定义,所以先开一块0x400个字节的
								;空间预留出来
	str                         ;char str[] = {"abc"};
    DCB 'a','b','c','\0'
    
array                           ;int array[] = {1,2,3,4,5}; 
    DCD 1,2,3,4,5

	;定义代码段,用来存储用户编写的代码
	AREA |.text|,CODE,READONLY	;设置代码段的属性为只读
	
__start	PROC					;PROC : process过程-->函数
								;PROC 与 ENDP 搭配使用,两者之间就是
	B main 						;跳转到main函数中去执行
	
	...
	...

   	B .                         ;相当于while(1)
	ENDP						;ENDP表示__start函数在此处结束

	END							;表示代码段在此处结束
								;段的结束有两种方式:
								;	1.	遇上了END
								;	2.	遇上了下一个段的AREA                    
    

3.中断向量表
中断向量表就是一个保存了各个中断处理程序地址的数组。

比如:
		0号中断的处理函数为:
		void isr_0(void)
		{
				
		}
	
		1号中断的处理函数为:
		void isr_1(void)
		{
			
		}		
		....

中断函数不是CPU主动调用的,而是被动调用的。

中断向量表是一个函数指针数组:
	如:
		void *isr_addr[256];
		isr_addr[0] = isr_0;
		isr_addr[1] = isr_1;
		...
		isr_addr[n] = isr_n;
		...
	当产生了0号中断的时候,CPU就会立即找到在固定位置(0x0800 0000)
的中断向量表,然后以中断编号为下标,找到中断向量表中对应的元素,
将元素中的函数地址提取出来,然后CPU就跳转到这个地址上去执行中断
处理函数。

4. ARM指令格式

<opcode>{<cond>}{S} <Rd>,<Rn>{,<operand2>}
	其中 <> 表示必须要有的,{}内表示可选的。
	opcode:operator code
	操作码:指令助记符,表示某种操作/表示某条指令,可以大写可以小写
			如:
				MOV ADD SUB ....
	cond:condition条件
		表示该指令执行的条件,如果省略不写,则表示该指令无条件(必须)执行的。
			比如:	
				MOV R0,#3
				MOVEQ R0,#3		;这条语句在执行前会先去判断xPSR.Z是否为1
								;如果为1表示上一条指令的执行结果为0
								;则会将3-->R0					
			if(R1 == R2)		;CMP R1,R2
			{					;MOVEQ R0,#250
				R0 = 250;
			}
	S:Status 表示该指令的执行结果是否影响xPSR的标志位
		如:
			MOV R0,#0
			MOVS R0,#0	
	Rd:Register Destination
		结果寄存器,用来保存运算的结果
		结果只能保存在寄存器中。
	Rn:第一个运算操作数寄存器
	operand2:第二个运算操作数
		第二个操作数的形式多种多样:
		1)	立即数	立即数就是一个常数
			一条ARM指令唯一对应一条二进制码。
			operand2在指令中占12bit:
			分为常数部分8bit(存放在低8bit) + 循环右移常数4bit(存放在高4bit)
				
			常数部分取值范围为0-255,假设为x。
			循环右移取值范围为0-15,假设为y。
			立即数T = x 循环右移 (2*y)
			
			一个数如果循环左移偶数次位(不超过30次),如果其二进制
			形式中所有的为1的bit都能存放在低8位的话,那么这个数
			就是立即数。
		2)	RM	第二个操作数也可以是一个寄存器
			比如:
				ADD R3,R1,R2
		3)	RM,shift	第二个操作数也可以是寄存器+移位来表示
			移位:
				LSL #n		Logic Shift Left	逻辑左移n位(n必须为立即数)
				LSR #n		Logic Shift Right 	逻辑右移n位
				ASR #n		算术右移n位
				ROR #n		循环右移n位
				RRX			带扩展的循环右移1位
				
				算术移位:将操作数当成是一个有符号的数
					算术左移:左边的高位直接干掉,右边空出来的直接补0
					算术右移:左边的地位直接干掉,右边空出来的高位全部补符号位
					
				逻辑移位:将操作数当成是一个无符号的数
					无论是左移还是右移直接补0
					
				RRX:带扩展的循环右移1位
					带着xPSR.C位进行循环右移
					移除出去的那个bit(最低位)会补充到xPSR.C位中
					去,而原来的xPSR.C位中的值会补充到位移的数
					据的最高位上去。

5. 寻址方式
 立即数寻址

立即寻址指令举例如下: 
	SUBS R0,R0,#1   ;R0 减 1 ,结果放入 R0 ,并且影响标志位
	MOV R0,#0xFF000 ; 将立即数 0xFF000 装入 R0 寄存器 

 寄存器寻址

寄存器寻址指令举例如下: 
	MOV R1,R2    ; 将 R2 的值存入 R1
	SUB R0,R1,R2 ; 将 R1 的值减去 R2 的值,结果保存到 R0

 寄存器间接寻址

寄存器间接寻址指令举例如下: 
	LDR R1,[R2]    ; 将 R2 指向的存储单元的数据读出保存在 R1 中 
	SWP R1,R1,[R2] ; 将寄存器 R1 的值和 R2 指定的存储单元的内容
					 交换,将R2的数值作为一个地址,将此地址处的
					 数值与R1中的内容交换

 寄存器偏移寻址

寄存器移位寻址指令举例如下:
	MOV R0,R2,LSL #3     ;R2 的值左移 3 位,结果放入R0 ,即是
						  R0=R2×8 
	ANDS R1,R1,R2,LSL R3 ;R2 的值左移 R3 位,然后和R1相“与”操
					      作,结果放入R1

 基址变址寻址

基址寻址指令举例如下: 
	LDR R2,[R3,#0x0C]   ; 读取 R3+0x0C 地址上的存储单元的内容,
						  放入 R2 
	STR R1,[R0,#-4]! 	; 先 R0=R0-4 ,然后把 R1 的值寄存到 R0 
						  指定的存储单元 

 多寄存器寻址

多寄存器寻址指令举例如下: 
	LDMIA R1!,{R2-R7,R12} ; 将 R1 指向的单元中的数据读出到R2 
							~R7、R12 中 (R1自动加1) 
	STMIA R0!,{R2-R7,R12} ; 将寄存器 R2 ~ R7 、 R12 的值保存
							到 R0 指向的存储单元中(R0自动加1)

 堆栈寻址

堆栈是一个按特定顺序进行存取的存储区,操作顺序为“后进先出” 。堆栈
寻址是隐含的,它使用一个专门的寄存器 ( 堆栈指针 ) 指向一块存储区
域 ( 堆栈 ) ,指针所指向的存储单元即是堆栈的栈顶。

存储器堆栈可分为两种: 
 向上生长:向高地址方向生长,称为递增堆栈
 向下生长:向低地址方向生长,称为递减堆栈


堆栈指针指向最后压入的堆栈的有效数据项,称为满堆栈;堆栈指针指向下一个待压入数据的空位置,称为空堆栈。

所以可以组合出四种类型的堆栈方式:

满递增:堆栈向上增长,堆栈指针指向内含有效数据项的最高地址。
	    指令如 LDMFA 、 STMFA 等;
空递增:堆栈向上增长,堆栈指针指向堆栈上的第一个空位置。
		指令如 LDMEA 、 STMEA 等;
满递减:堆栈向下增长,堆栈指针指向内含有效数据项的最低地址。
		指令如 LDMFD 、 STMFD 等;
空递减:堆栈向下增长,堆栈指针向堆栈下的第一个空位置。
		指令如 LDMED 、 STMED 等。 

 块拷贝寻址
多寄存器传送指令用于将一块数据从存储器的某一位置拷贝到另一位置。

如:
STMIA R0!,{R1-R7}; 将 R1 ~ R7 的数据保存到存储器中。存储指针
					在保存第一个值之后增加, 增长方向为向上增长。
STMIB R0!,{R1-R7}; 将 R1 ~ R7 的数据保存到存储器中。存储指针
					在保存第一个值之前增加, 增长方向为向上增长。
STMDA R0!,{R1-R7}; 将 R1 ~ R7 的数据保存到存储器中。存储指针
					在保存第一个值之后增加, 增长方向为向下增长。
STMDB R0!,{R1-R7}; 将 R1 ~ R7 的数据保存到存储器中。存储指针
					在保存第一个值之前增加, 增长方向为向下增长。

 相对寻址
相对寻址是基址寻址的一种变通。由程序计数器 PC提供基准地址,指令中的地址码字段作为偏移量,两者相加后得到的地址即为操作数的有效地址。

6. ARM指令
(1)ARM存储访问指令
用来在存储器与寄存器之间传递数据。

把数据从存储器放到寄存器称之为加载Loader。
把数据从寄存器放到存储器称之为存储Store。

1)单寄存器操作

	指令格式:
		LDR{cond}{S}{B/H} Rd,<存储器地址>
		STR{cond}{B/H} Rd,<存储器地址>
			
		注意:
			1.	B:byte一个字节,加载/存储一个字节的数据。
			  	H:Half半字(两个字节),加载/存储两个字节的数据。
			   	没有B或者H的话,默认表示加载/存储四个字节的数据。
				
				S:signed 有符号的
				如果带有S的话,表示将存储器中的数据当成是一个有
	符号的数据进行加载。
				如果不带S的话,则表示将其当成是一个无符号的数据进行加载。
				如果加载的数据是有符号的,则高位补符号位
				如果加载的数据是无符号的,则高位直接补0
				
			2.	存储器地址确定方式
				a.寄存器间接寻址
					存储地址直接在寄存器中表示	
					比如:
						STR R1,[R0]
						
				b.寄存器偏移寻址
					将寄存器表示的地址偏移后,才是最终操作的地址。
				即:
					表示法	 访问的存储地址  访问后地址寄存器的值
				 [Rn,偏移量]   Rn + 偏移量		Rn
				 [Rn,偏移量]!  Rn + 偏移量	Rn = Rn + 偏移量
				 [Rn],偏移量	  Rn		Rn = Rn + 偏移量
					  
				偏移量可以有多种表现形式:
					1.立即数
					2.寄存器
							MOV R2,#4
							STR R7,[R0,R2]-->STR R7,[R0,#4]
					3.寄存器+移位来表示	
						比如:
							MOV R2,#1
							STR R7,[R0,R2,LSL #2];R7-->[R0 + (R2 << 2)]

2)多寄存器操作
在一个连续的存储器地址上,进行多个寄存器的存取。
加载:Load
存储:Store
多个:Multi
LDM:多寄存器加载
STM:多寄存器存储

指令格式:	
	LDM{cond}<模式> Rn{!},reglist
	STM{cond}<模式> Rn{!},reglist
		
	a. Rn是一个寄存器,里面存储的是存储器的地址
		表示将多个寄存器的值存储至存储器的哪个位置上去或者存储器
某地址上的连续的多个字节加载到多寄存器上来。
	
	b.	无论是哪种模式,低地址都是对应低编号的寄存器
		模式:
			IA:Increment After  每次传输数据之后,地址会自动增加(+4字节)
			DB:Decrement Before 每次传输数据之前,地址就已经减少(-4字节)
			IB:Increment Before	每次传输数据之前,地址就已经增加(+4字节)
			DA:Decrement After	每次传输数据之后,地址会自动减少(-4字节)
		ARM Cortex-M4只支持IA/DB.
			
	c.	! 决定了完成操作后,地址寄存器Rn的值是否随着地址的增减而改变
			如果指令带有!,则地址的变化会被记录在Rn寄存器中。
			
	d.	reglist:register list寄存器列表
			可以包含多个寄存器,并且用 , 隔开。
			
	现场保护和现场恢复:
		例子:	
			MOV R0,#0x20000000
			MOV R1,#0x0A
			MOV R2,#0x0B
			MOV R3,#0x0C
			STMIA R0!,{R1-R3}	;将R1,R2,R7的值保存到R0指向的
								 地址中去
								;现场保护:把寄存器的值保存到
								 指定的存储器地址中去							
			MOV R1,#0x11
			MOV R2,#0x22
			MOV R3,#0x33
			LDMDB R0!,{R1-R3}	;现场恢复:从指定的存储器地址中
								 恢复原先保存的数据

3)堆栈指令
堆栈指令就是按照特定的入栈和出栈模式,来对堆栈段进行多寄存器操作。
堆栈指令的规则也是低地址对应低编号的寄存器。

PUSH reglist  入栈操作(现场保护)
POP reglist   出栈操作(现场恢复)

栈有四种不同的属性:

	A:Add 递增堆栈,栈地址会增加
	D:Dec 递减堆栈,栈地址会减少
	E:Empty 空的,表示SP指向的地址空间没有数据
	F:Full  满的,表示SP指向的地址空间存储了数据

四种属性互相结合可以得到四种不同类型的堆栈:

	EA:空递增堆栈
	 	SP指向的永远是一个没有存储数据的空间(指向栈顶元素后面的那块空间)。
		入栈:	PUSH x	
			   x --> [SP]
		  		 SP++
		   
		出栈:	POP x
				SP--
		 	  [SP] --> x
		
	 FA:满递增堆栈
		SP指向的永远是一个存储了数据的空间(指向栈顶元素)
		入栈:	PUSH x	
		  		 SP++
				x --> [SP]
				
	    出栈:	POP x
				[SP] --> x
				SP--
		
	 FD:满递减堆栈
		入栈:	PUSH x	
				SP--
				x --> [SP]
				
		出栈:	POP x
				[SP] --> x
				SP++
					
	 ED:空递减堆栈

ARM采用的是满递减堆栈。

现场保护和现场恢复改进:

STMIA R0!,{R1-R3}	;现场保护:把寄存器的值保存到指定的存储器地址中去
				     -->PUSH {R1-R3}
				
LDMDB R0!,{R1-R3}	;现场恢复:从指定的存储器地址中恢复原先保存的数据
				     -->POP {R1-R3}

(2)ARM数据处理指令
只是对寄存器中的数据进行操作。
1) 数据传送指令

		MOV{cond}{S} Rd,operand2	;operand2-->Rd
		MVN{cond}{S} Rd,operand2	;~operand2-->Rd

2)算术运算指令

a.	加法
	ADD{cond}{S} Rd,Rn,operand2	;Rn + operand2 -> Rd
	ADC{cond}{S} Rd,Rn,operand2	;带进位的加法 Rn + operand2 + xPSR.C -> Rd
								ADC带进位的加法常用与64位加法中。
			
	假设两个64位的x和y要做加法运算:
			x的低32位-->R0	y的低32位-->R2
			x的高32位-->R1	y的高32位-->R3		
	汇编实现:
			ADDS R4,R0,R2
			ADC R5,R1,R3
			
b.	减法
		SUB{cond}{S} Rd,Rn,operand2		;Rn - operand2 -> Rd
		SBC{cond}{S} Rd,Rn,operand2		;带借位的减法Rn - operand2 - !xPSR.C -> Rd

c.	逆向减法
		RSB{cond}{S} Rd,Rn,operand2		;逆向减法 operand2 - Rn --> Rd
		RBC{cond}{S} Rd,Rn,operand2		;逆向带借位的减法
										;operand2 - Rn - !xPSR.C -> Rd

3)逻辑运算指令(按位操作)

	AND{cond}{S} Rd,Rn,opearnd2			;按位与		Rn & opearnd2 --> Rd
	ORR{cond}{S} Rd,Rn,opearnd2			;按位或		Rn | opearnd2 --> Rd		
	EOR{cond}{S} Rd,Rn,opearnd2			;按位异或	Rn ^ opearnd2 --> Rd	
	BIC{cond}{S} Rd,Rn,opearnd2			;按位清零	Rn & (~opearnd2) --> Rd
		
	例子:
		BIC R2,R1,#0xF	;R2 = R1 & (~0xF)	
						;将R1的低4bit清零后赋值给R2

4)比较指令
不需要加S位,就可以直接影响xPSR中的状态标志位。并且,比较指令的运算是没有结果的,只影响xPAS,因此比较指令没有Rd。

比较指令:
	CMP/CMN/TST/TEQ
			
	a.	CMP Rn,operand2 	
		比较Rn和operand2的大小
		利用Rn - operand2,对xPSR状态标志位的影响来判断
		例子:
			CMP R0,R1
			MOVEQ R2,#3
				
	b.	CMN Rn,operand2 	
		比较Rn和operand2的大小
		利用Rn - (-operand2)对xPSR状态标志位的影响来判断
		例子:
			CMN R0,#1		;把R0与-1进行比较
				
	c.	TST Rn,operand2 
		用来测试特定的bit位是否为1
		利用Rn & operand2对xPSR状态标志位的影响来判断
		例子:	
			TST R1,#1		;相当于R1 & 0x01
				
	d.	TEQ Rn,operand2 
		相等判断,利用Rn ^ operand2来进行判断
		如果两个数相等,则xPAR.Z == 1
		例子:	
			TEQ R1,R2
			MOVEQ R3,#3

3)乘法指令
略。

4)分支指令
用来实现代码的跳转。

1)	直接向PC寄存器赋值
		程序计数器,作用就是保存下一条要执行的指令的地址。
	例子:	
		MOV R0,#0x08000000
		ADD R0,R0,#0x400
		ADD R0,R0,#0x8			;R0->0x08000408
		MOV PC,R0
			
2)	分支指令	
		B lable				;label标号,标记着一个地址
							;B lable实际上就是将lable标志的地址赋值给PC
		
		BL lable			;BL与B之间的最大的区别在于B是不带返回的跳转
							;BL是带返回的跳转

		int main()
		{
			A
			BL SUM			;跳转到SUM函数中去执行代码
			C
			D
		}
	
		int SUM()
		{
			E
			F
		}
		所以通过两者之间的一个比较,B一般用于做分支跳转(if)或者循环跳转(for..)
		BL用于跳转到函数中去执行。	

5)杂项指令

	MRS Rd,xPSR
	程序状态寄存器的值赋值给Rd
	
	MSR xPSR,Rd
	将通用寄存器Rd的值赋值给程序状态寄存器	

6)伪指令
伪指令是编译器厂商提供的一些指令,机器不识别,但是可以表示程序或者编译器的某种想要的操作。
编译器会把伪指令变成一条或者多条合适的机器指令。

1)	NOP : no operation
	空操作指令,其实就是什么都不干,但是会消耗时间。
	NOP翻译成机器指令可能就是MOV R0,R0

2)	LDR{cond} Rd,=expr
	用来将expr的值传送给Rd
	效果等用于MOV,但是expr不受立即数的约束。
		
	MOV R0,#0x12345678   非法
	LDR R0,=0x12345678   合法

3)	DCD/DCB	
		DCD  向当前地址存入一个word(4个字节)
		DCB  向当前地址存入一个byte(1个字节)	

7.ARM程序设计
1)if…else…的实现

if(a > b)
{
	a = 1;
}
else
{
	b = 2;
}
		
//翻译成汇编:(a-->R0   b-->R1)
	CMP R0,R1
	MOVGT R0,#1
	MOVLE R1,#2

2)循环的实现

for(i = 1,sum = 0;i <= 10;i++)
{
	sum += i;
}
		
//i-->R0  sum-->R1
		
	MOV R0,#1
	MOV R1,#0
		
For_Start				;For_Start表示for循环开始的地方
	CMP R0,#10
	ADDLE R1,R0,R1
	ADDLE R0,R0,#1
	BLE For_Start		;条件满足才跳转回循环开始的地方
//优化:
	MOV R0,#1
	MOV R1,#0
	
For_Start				;For_Start表示for循环开始的地方
	CMP R0,#10
	BGT For_End
	ADD R1,R0,R1
	ADD R0,R0,#1
	B For_Start			;条件满足才跳转回循环开始的地方		
For_End

3)函数的实现
过程调用(函数调用):

1)	参数的传递只能用R0,R1,R2,R3这四个寄存器(最多只能传四个参数)
	如果超过四个参数,后续的只能是存放在栈空间。
			
2)	函数的返回值只能通过R0返回。如果是64bit的返回,则可以用R1(高),R0(低)
	超过64bit的返回值则不支持。
		
3)	过程调用必须使用现场保护和现场恢复。
	
4)	函数返回
	将要返回的值传送到R0,然后将LR传送给PC。	
Func1 PROC
	PUSH {R4-R12,LR}	;函数执行前进行现场保护(假设传递了四个参数)

...						;函数的代码(内容)

	MOV R0,<返回值>		;如果有返回值的话就将返回值存至R0中
	POP {R4-R12,LR}		;函数执行完后进行现场恢复
	MOV PC,LR			;跳转回主调函数
	ENDP
5)	汇编文件与C文件共存
	a.	在汇编文件中调用C文件中的函数
		1.	在汇编文件中
			IMPORT 函数名		;要引入某个函数到汇编文件中来,
								 相当于C中的函数声明
			
	    2.	编写一个C函数(比如main函数)
			将其.c文件添加到工程中去
			
		3.	只需要在代码段添加
				BL main
	b.	在C文件中调用汇编文件中的函数
		1.	在汇编文件中使用
				EXPORT 过程标号		;表示将某个过程标号,设定为外部可访问
					
		2.	在C文件中需要一个函数声明
				
		3.	在C文件中调用汇编文件中的函数

现场保护:建议除了传递参数的那个寄存器外,其余的寄存器都进行保护。因为传递参数的寄存器本身就是给另外一个函数使用的,所以不需要保护。

比如:
	传递1个参数-->R0
	PUSH {R1-R12,LR}

为什么LR也需要保护呢?

因为过程跳转后,在过程中有可能又发生了跳转,就是在调用函数内部又
调用了一个函数,此时LR就会发生改变。

现场恢复:保护了什么就恢复什么。

//求斐波那契数列的前20项			
int main()
{
	int a = 1,b = 1,an = 0,i;
	for(i = 1;i < 19;i++)
	{
		an = a + b;
		a = b;
		b = an;
	}
}
	
//汇编的实现:
	MOV R0,#1		;R0-->a
	MOV R1,#1		;R1-->b
	MOV R2,#0		;R2-->an
	MOV R3,#1		;R3-->i
	
For_Start1	
	CMP R3,#19
	BGE For_End1
	ADD R2,R0,R1 
	MOV R0,R1
	MOV R1,R2
	ADD R3,R3,#1
	B For_Start1

For_End1
  • 21
    点赞
  • 111
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_60265426

都是好兄弟

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值