I.MX6ULL裸机开发之汇编基础

I.MX6ULL裸机开发之汇编基础

I.MX6ULL是一款Cortex-A7芯片,这个芯片一上电SP指针还没初始化好,也就是说C语言运行的环境还没准备好,所以肯定不能直接运行C语言代码的。必须使用汇编语言设置好C语言运行环境,比如初始化DDR、设置SP指针等,只有把C语言运行环境设置好了,才可以运行C代码。C语言中的函数调用涉及到出栈入栈,也就是需要对堆栈进行操作,所谓的堆栈就是一段内存,这段内存由SP指针进行访问,所以需要初始化SP指针; 对于有些芯片还需要初始化DDR,是因为芯片本身不带由RAM或者芯片内部自带的RAM不开放给用户,用户代码需要在DDR中运行,所以一开始要初始化DDR。

汇编语法

GNU汇编语法使用于所有的架构,并不是ARM独享,GNU汇编由一系列的语句组成,每一条语句由三个可选部分:

label: instruction @comment

label标号,表示地址位置,有些指令前面可能有标号,这样可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意label后面的 “:” ,任何以 “:” 结尾的标识符都会识别为一个标号
instruction指令,也就是汇编指令或者伪指令。
@符号,表示后面的是注释,和C语言里面 “/" "/” 一样。在GNU汇编中也可以使用 "/" "/"来注释.
例如:

add:
	MOVS R0,#0X12 @设置R0=0X12

"add:"就是标号,"MOVS R0,#0X12"就是指令,@设置R0=0X12就是注释。
注意:在ARM指令、伪指令、伪操作、寄存器名等都可以全部使用大写或者全部使用小写,但是不能大小写混用。
用户可以使用 .section 来定义一个段,每个段以段名开始,以下一个段名或者文件结尾结束。汇编系统预定义了一些段名:

①.text:代码段
②.data:初始化数据段
③.bss:未初始化数据段
④.rodata:只读数据段
定义一个testsetcion段

.section .testsection @定义一个testsection段

汇编程序的默认入口是 _start,不过也可以在链接脚本中使用 ENTRY 来指明它的入口点

.global _start

_start:
	ldr r0,=0x12 @r0=0x12

.global是一个伪操作,表示_start是一个全局标号,类似C语言里面的全局变量一样。常见的伪操作有:
①.byte 定义单字节数据,比如:.byte 0x12
②.short 定义双字节数据,比如:.short 0x1234
③.long 定义一个4字节数据,比如:.long 0x12345678
④.equ 赋值语句,格式为:.equ 变量名,表达式;比如:equ num,0x12;表示num=0x12
⑤.align 数据字节对齐,比如:.align 4表示4字节对齐
⑥.end 表示源文件结束
⑦.global 定义一个全局符号,格式为:.global symbol,比如:global _start
GNU汇编也支持函数,格式如下:

函数名:
	函数体
	返回语句

GNU汇编函数返回语句不是必须的。

常用汇编指令

处理内部数据传输指令
使用处理器做最多的事情就是处理器内部来回的传递数据,常见的操作有:
①将数据从一个寄存器传递到另一个寄存器
②将数据从一个寄存器传递到特殊寄存器,如CPSR和SPSR寄存器
③立即数传递到寄存器
数据传输常用的指令有三个:MOV、MRS和MSR

指令目的描述
MOVR0R1将R1里面的数据复制到R0中
MRSR0CPSR将特殊寄存器CPSR里面的数据复制到R0中
MSRCPSRR1将R1里面的数据复制到特殊寄存器CPSR中

MOV指令
MOV指令用于将数据从一个寄存器拷贝到另一个寄存器,或者将一个立即数传递到寄存器里面。

MOV R0, R1		@将寄存器R1中的数据传递到R0,即R0=R1
MOV R0, #0X12	@将立即数0X12传递到R0寄存器,即R0=0X12

MRS
MRS指令用于特殊寄存器(如CPSR和SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用MRS指令。

MRS R0,CPSR		@特殊寄存器CPSR里面的数据传递到R0,即R0=CPSR

MSR
MSR指令和MRS刚好相反,MSR指令用来将普通寄存器的数据传递到特殊寄存器中,也就是写特殊寄存器,写特殊寄存器只能使用MSR。

MSR CPSR, R0		@将R0中的数据复制到特殊寄存器CPSR中,即CPSR=R0

存储器访问指令

ARM不能直接访问存储器,比如RAM中的数据,I.MX6UL中的寄存器就是RAM类型的,使用汇编来配置I.MX6UL寄存器的时候需要借助寄存器访问指令,一般先将要配置的值写入到Rx(x=0~12)寄存器中,然后借助存储器访问指令将Rx中的数据写入到I.MX6UL寄存器中。读取寄存器的方式也是一样的,只不过是过程相反。常用的存储器访问指令有两种:LDR和STR。

指令描述
LDR Rd, [Rn, #offset]从寄存器Rn+offset的位置读取数据存放到Rd中
STR Rd, [Rn, #offset]将Rd中的数据写入到存储器中的Rn+offset的位置

LDR指令

LDR主要用于从存储加载数据到寄存器Rx中,LDR也可以将一个立即数加载到寄存器Rx中,LDR加载立即数的时候需要用 “=” 而不是 “#”。在嵌入式开发中,LDR最常用的就是读取CPU的寄存器的值,比如在I.MX6UL有寄存器GPIO1_GDIR,其地址为0X0209C004,如果要读取这个寄存器的值:

LDR R0, =0X0209C004		@将寄存器地址0X0209C004加载到R0中
LDR R1, [R0]		@读取地址0X0209C004中的数据到R1寄存器中

STR指令
LDR是从存器读取数据,STR就是将数据写入到存储器中,同样以I.MX6UL寄存器GPIO1_GDIR为例,要配置寄存器GPIO1_GDIR的值为0X20000002:

LDR R0, =0X0209C004				@将寄存器地址0X0209C004加载到R0中
LDR R1, =0X20000002				@R1保存要写入到寄存器的值
STR R1, [R0]					@将R1中的值写入到R0所保存的地址中

如果要按照字节、半字进行操作的话,可以在指令 “LDR” 后面加上B或者H;比如按字节操作的指令就是LDRB和STRB,按半字操作的指令就是LDRH和STRH。

压栈和出栈指令

在编程中通常会在A函数中调用B函数,当B函数执行完之后,再回到A函数继续执行。要想跳回A函数后,代码还能接着继续运行,那就必须再调用B函数之前,保存当前处理器的状态(就是保存R0到R15这些寄存器的值),当B函数执行完成后,再用之前保存的寄存器值(就是恢复R0到R15寄存器的值)即可。保存R0~R15寄存器的操作就叫做现场保护,恢复R0-R15寄存器的操作就叫做恢复现场。
在进行现场保护的时候,需要进行压栈(入栈)操作;恢复现场就要进行出栈操作。压栈的指令为PUSH,出栈的指令为POP,PUSH和POP是一种多存储和多加载指令,也就是一次可以操作多个寄存器数据,它们利用当前的栈指针SP来生成地址。简而言之,在跳转之前,压栈和出栈指令的作用就是用来保护现场和跳转之后恢复现场的。

指令描述
PUSH< reg list>将寄存器列表存入栈中
POP< reg list>从栈中恢复寄存器列表

例如将R0-R3和R12这5个寄存器压栈,当前的SP指针指向0X80000000,处理器的堆栈是向下增长的:

PUSH {R0-R3,R12}

压栈完成后的堆栈如图
R0-R3和R125个寄存器压栈
此时SP指针指向了0X7FFFFFEC,如果此时再将LR寄存器进行压栈

PUSH {LR}

压栈完成后的堆栈如图
LR寄存器压栈
出栈操作,出栈就是从栈顶,也就是SP当前执行的位置开始,地址依次减小来提取堆栈中的数据。

POP {LR}					@先恢复LR
POP {R0~R3,R12}			@再恢复R0~R3,R12

PUSH和POP另一种写法是:“STMFD SP!”“LDMFD SP!”,因此上面的汇编代码还可以写成:

STMFD SP!, {R0~R3, R12}		@入栈
STMFD SP!, {LR}

LDMFD SP!, {LR}				@出栈,先恢复LR
LDMFD SP!, {R0~R3, R12}		@再恢复R0~R3, R12

STMFD可以分为两部分:STM和FD,同理,LDMFD也可以分为LDM和FD。STM和LDM不同于STR和LDR,STR和LDR每次只能读写一个数据,而STM和LDM是多存储多加载,可以连续读写多个连续的数据。
FD是Full Descending的缩写,即满递减的意思。根据ATPCS规则,ARM使用的FD类型的堆栈,SP指向最后一个入栈的数值,堆栈是由高地址向下增长的,也就是前面说的向下增长的堆栈,因此最常用的指令就是STMFD和LDMFD。STM和LDM的指令寄存器列表中的编号小的对应低地址,编号高的对应高地址。

跳转指令

指令描述
B < label >跳转到label,如果跳转范围超过了±2KB,可以指定B.W< label > 使用32位版本的跳转指令,这样可以得到较大范围的跳转
BX < Rm >间接跳转,跳转到Rm中的地址处,并且切换指令集
BL < label >跳转到标号地址,并将返回地址保存再LR中
BLX < Rm >结合BX和BL的特点,跳转到Rm指定的地址,并将返回地址保存在LR中,并切换指令集

B指令
B指令是最简单的跳转指令,B指令会将PC寄存器的值设置为跳转目标地址,一旦执行B指令,ARM处理器会立即跳转到指定的目标地址。如果要调用的函数不需要再返回原来的执行处,那就可以使用B指令

_start:
	ldr sp,=0X80200000		@设置栈指针地址
	b main					@跳转到C语言的main函数中,并且不会再返回

上述代码就是典型的汇编中初始化C运行环境,然后跳转到C文件的main函数中运行。上述代码只是初始化了SP指针,有些处理器还需要其他的初始化,比如初始化DDR等。因为跳转到C文件后不需要再回到汇编,所以可以使用B指令来完成跳转。
BL指令
BL指令和B指令,在跳转之前会在寄存器LR(R14)中保存当前PC寄存器的值,所以当跳转完成后,可以通过将LR寄存器中的值重新加载到PC中,来继续执行跳转之前的代码,这是子程序调用的一个基本常用手段。比如Cortex-A处理器的irq中断服务函数都是汇编写的,只要用汇编来实现现场的保护和恢复、获取中断号等。但是具体的中断处理过程都是C函数,所以就会存在汇编调用C函数的问题,而且当C语言的中断处理函数执行完成之后,还需要回到汇编irq的中断服务函数,来继续处理其他工作,一般是恢复现场。这时候就不能使用B指令进行跳转了,要使用BL指令。

PUSH {R0, R1}			@保存R0,R1
CPS #0X13				@进入SVC模式,允许其他中断再次进入

BL system_irqhandler	@跳转到C语言的中断处理函数中

CPS #0X12				@进入IRQ模式
POP {R0, R1}
STR R0, [R1, #0X10]		@中断执行完成,写EOIR

算术运算指令

指令计算公式备注
ADD Rd, Rn, RmRd = Rn + Rm加法运算,指令为ADD
ADD Rd, Rn, #immedRd = Rn + #immed加法运算,指令为ADD
ADC Rd, Rn, RmRd = Rn + Rm + 进位带进位的加法运算,指令为ADC
ADC Rd, Rn, #immedRd = Rn + #immed + 进位带进位的加法运算,指令为ADC
SUB Rd, Rn, RmRd = Rn - Rm减法运算,指令为SUB
SUB Rd, #immedRd = Rd - #immed减法运算,指令为SUB
SUB Rd, Rn, #immedRd = Rn - #immed减法运算,指令为SUB
SBC Rd, Rn, #immedRd = Rn - #immed - 借位带借位的减法运算,指令为SBC
SBC Rd, Rn, RmRd = Rn - Rm - 借位带借位的减法运算,指令为SBC
MUL Rd, Rn, RmRd = Rn * Rm乘法运算(32位),指令为MUL
UDIV Rd, Rn, RmRd = Rn / Rm无符号除法运算,指令为UDIV
SDIV Rd, Rn, RmRd = Rn / Rm有符号除法运算,指令为SDIV

逻辑运算指令

指令计算公式备注
AND Rd, RnRd = Rd & Rn按位与
AND Rd, Rn, #immedRd = Rn & #immed按位与
AND Rd, Rn, RmRd = Rn & Rm按位与
ORR Rd, RnRd = Rd | Rn按位或
ORR Rd, Rn, #immedRd = Rd | #immed按位或
ORR Rd, Rn, RmRd = Rn | Rm按位或
BIC Rd, RnRd = Rd & (~Rn)位清除
BIC Rd, #immedRd = Rd & (~#immed)位清除
BIC Rd, Rn, RmRd = Rd & (~Rm)位清除
ORN Rd, Rn, #immedRd = Rd | (~#immed)按位或非
ORN Rd, Rn, RmRd = Rn | (~Rm)按位或非
EOR Rd, RnRd = Rd ^ Rn按位异或
EOR Rd, Rn, #immedRd = Rn ^ #immed按位异或
EOR Rd, Rn, RmRd = Rn ^ Rm按位异或
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

__不高兴

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值