ARM笔记

ARM基础

零散知识

stm32f103是crotex-m3内核,armv7结构,哈佛结构
s5p6818是crotex-a53内核,armv8架构,哈佛结构

指令集

精简指令集:RISC指令,一条指令占32位内存空间,大多数指令都是单周期。
复杂指令集:CISC指令,指令宽度不固定,指令周期不固定。

注: arm是精简指令集RISC

ARM处理器工作状态

ARM7、ARM9、ARM11 有7个基本工作模式:

用户模式User
非特权模式,大部分任务执行在这种模式

快速中断模式FIQ
当一个高优先级中断产生时将进入这种模式

外部中断模式IRQ
当一个低优先级中断产生时将会进入这种模式

管理模式SVC
当复位或软中断指令执行时将回进入这种模式

中止模式ABT
当存取异常时将会进入这种模式

未定义指令模式UND
当执行未定义指令时会进入这种模式

系统模式SYS
使用和User模式相同寄存器集的特权模式

Cortex-A系列

Cortex-A系列新增Monitor 模式。
Monitor : 是为了安全而扩展出的用于执行安全监控代码的模式;
也是一种特权模式

ARM中的寄存器

ARM7,ARM9,ARM11 有37个32-Bits长的寄存器。
Cortex体系结构下有40个32-Bits长的寄存器
Cortex-A(ARM-v7)多出3个寄存器,Monitor 模式 r13_mon , r14_mon, spsr_mon
ARM笔记_第1张图片
R13:栈指针(sp),sp中存放的值在退出调用函数时必须与进入调用函数时的值相同
R14:链接寄存器(lr),当调用一个函数时或执行中断时,下一条指令的地址(返回值的地址)被自动保存到链接寄存器中。在函数返回时或中断结束时有效
PC:程序计数器,用于存放下一条指令所在单元的地址的地方。取址就是根据PC中存放的的指令的地址来取址。
CPSR:程序状态寄存器,
SPSR:程序状态寄存器,当异常出现时,SPSR用来保存CPSR的值。当异常结束时,再恢复CPSR的值,即程序运行状态

程序状态寄存器

在这里插入图片描述
条件位
[31:28]位,即N,Z,C,V这四位被称为条件标志位,几乎所有指令都会测试CPSR中的条件标志位来决定指令是否执行。
N:符号位
Z:结果为零设置为1,繁殖为0
C:一般有四种情况
1. 对于加法,包括比较指令CMN,如果加法运算产生无符号溢出,C设置为1,反之为0
2. 对于减法,包括比较指令CMP,如果 减法运算产生一个借位,C设置为1,反之为0
3. 对于包含移位操作的非加法运算,C设置为移位器移出的最后一位
4. 对于其他的非减法运算,C通常保持不变
V:一般有两种情况
1. 在两个二进制补码的有符号整型的加法和减法指令运算是,如果发生符号一处,则设置为1
2. 对于非减法和加法指令,V通常是不变的

中断禁止位
[7]:I:为1表示禁止IFQ
[6]:F:为1表示禁止FIQ

处理器模式位
[4:0]
1. 10000:User mode
2. 10001:FIQ mode
3. 10011:SVC mode
4. 10111:Abort mode
5. 11011:Undfined mode
6. 11111:System mode
7. 10010:IRQ

异常处理

中断向量表

异常类型 优先级 工作模式 异常向量地址 说明
复位 RESET 1 管理模式 0x00000000 复位引脚有效时进入
未定义指令 UND 6 未定义指令中止模式 0x00000004
软件中断 SWI 6 管理模式 0x00000008 用户定义的中断指令
指令预取终止 PABT 5 中止模式 0x0000000C 预取指令不存在或不允许访问时进入
数据访问中止 DABT 2 中止模式 0x00000010 当前数据访问呢指令的目标地址不存在或不允许访问时进入
外部中断请求 IRQ 4 外部中断模式 0x00000018 外部中断发生时进入
快速中断请求 FIQ 3 快速中断模式 0x0000001C 快速中断发生时进入

注: 中断向量表的地址可能不同,但是顺序一定相同

产生异常时

  1. 拷贝CPSR到 SPSR_< mode >
  2. 设置相应的CPSR
    1. 改变处理器状态进入ARM态
    2. 改变处理器模式,进入相应异常模式
    3. 设置中断禁止位禁止相应中断(如果需要的话)
  3. 保存返回地址到 LR_< mode >
  4. 设置PC为相应的异常向量

从异常返回时

  1. 从CPSR_< mode >恢复到CPSR
  2. 从 LR_< mode > 恢复到PC

三级流水线

目的
提高效率

内容
程序计数器PC保存写一次要取得指令地址。除非有其他情况,否则处理器在每次取指令后总是递增PC,使他能够按顺序取得下一条指令。

一条指令的执行分为三个阶段:取址、译码、执行
取址,即将指令从存储器中取出
译码,即解码指令中用到的寄存器
执行,即处理指令,并将结果写入寄存器

三级流水线如下图:
ARM笔记_第2张图片
注:

  1. PC指向的是被取址的地址,而不是正在执行的指令的地址,PC-8为正在执行的指令的地址
  2. 当一条指令读取pc寄存器的时候,是出于译码阶段,此时,pc指向的是下一条指令的地址

四种栈

根据栈顶指针的位置,栈分为两种:
满堆栈:栈顶指针指向最后插入元素的堆栈
空堆栈:栈顶指针指向限一个要放入元素位置的堆栈

根据堆的生成方式,栈分为两种:
递增堆栈:当栈由低地址向高地址生成时,称为递增堆栈
递减堆栈:当栈由高地址向低地址生成时,称为递减堆栈

上面两种方式混合成四种堆栈:
ARM笔记_第3张图片
分别是:空递增堆栈、满递增堆栈、空递减堆栈、满递减堆栈。

注:ARM是满减栈

汇编指令

常用关键字

关键字 含义 示例
.text 表示下面的程序属于代码段
.global 将本文件中的某个程序标号定义为全局的
.include 头文件包含
.arm/.code32 表示以下为arm指令
.thumb/.code16 声明一下为thumb指令
.data 定义一个数据码,可读写
.word 在存储器中分配4个字节,用指定的数据进行初始化 a: .word 2 @分配4个字节空间,并使用2进行初始化
.short 在存储器中分配2个字节,用指定的数据进行初始化
.byte 在存储器中分配1个字节,用指定的数据进行初始化
.string 分配一段字符内存单元 str1: .string “abcdef”
.space 用value填充size个字节的内存单元 buf: .space 16, 1 ;buf: .space(80)

指令格式

一般格式如下:
< opcode > {< cond >} {S} < Rd >, < Rn > {, < op2 > }
其中<>为不可省略,{}为可省略,opcode 、cond与S之间没有分隔符。{S}与Rd之间用空格分开

项目 含义 备注
< opcode > 指令的操作代码 即助记符,如MOV、B等
{cond} 条件域 可不加即可省略条件,如EQ、NE等
{S} 指令执行时是否更新CPSR 可省略
Rd 目的寄存器 Rd可以为任意通用寄存器
Rn 第一个源操作数 Rn可以为任意通用寄存器,且可以与Rd相同
op2 第二个源操作数 可为寄存器或任意移位寄存器

基本指令

助记符 指令功能 描述 注意
MOV 数据传输指令
LDR 存储器到寄存器的数据传送指令 LDR R1, =123456 此时立即数前加=,不是#
MVN 数据取反传送指令 MVN R1, R2 -> R1 = ~R2
CMP 比较指令 CMP R1, R2 -> CPSR = R1 - R2
CMN 比较反值指令 CMN R1, R2 -> CPSR = R1 + R2
ADD 加法指令
ADC 带进位加法指令 ADC R1, R2, R2 -> R1 = R2 + R3
SUB 减法指令
SBC 带借位减法指令 SBC R1, R2, R3 -> R1 = R2 - R3 - !C
AND 逻辑与指令
ORR 逻辑或指令
B 跳转指令
BL 带返回的跳转指令 BL 是另一个跳转指令,但跳转之前,会在寄存器R14 中保存PC 的当前内容,

条件指向及标志位

ARM指令可以通过添加适当的条件码前缀来达到条件执行的目的。

常用条件码如下:

条件码 助记符后缀 标志 含义
0000 EQ Z置位 相等
0001 NE Z清零 不相等
1011 LT N不等于V 带符号数小于
1100 GT Z清零且N等于V 带符号数大于
1101 LE Z置位或N不等于V 带符号数小于或等于

条件码为CPSR中的N、Z、V、C四位.

其他

机器码
汇编指令经过编译后处理器能够只从的机器指令称为机器码

指令编码
在这里插入图片描述
cond:指令的条件码
I:用于区别operand2是立即数(I=1)还是寄存器(I=0)
opcode:指令操作码
S:操作是否影响cpsr,S=0不影响,S=1影响
Rn:包含第一个操作数的寄存器编码
Rd:目标寄存器编码
operand2:第二操作数,有三种方式:立即数方式,寄存器方式,寄存器移位方式。每种方式下的编码格式不一样

立即数轮回

在MOV指令中,后12位即[11:0]是用来表示立即数的。但是立即数的范围远大于12位,所以[11:0]存储的并不是完整的立即数,而是立即数的一部分。
[11:8]:常数
[7:0]:右移位数的一半。

所以,汇编语言的立即数是通过常数右移得来的。

注:

  1. 当立即数数值在0和0xFF范围时,零immmed_8=< immediate >,rotate_imm=0.、
  2. 其他情况下,汇编编译器选择使rotate_imm数值最小的编码方式。所以0x3f0的正确表示法是第一种。

ARM寻址方式

立即寻址
立即寻址也称为立即数寻址,操作数本身就在指令中给出,只要取出指令也就取到了操作数
例:MOV R1, #0x123

寄存寻址
寄存器寻址就是将寄存器中存储的数值作为操作数。是执行效率最高的一种方式。
例:MOV R1, R2

寄存器间接寻址
寄存器间接寻址就是将寄存器中的值作为操作数的地址,而操作数本山存放在存储器中。用于间接寻址的寄存器必须使用[]括起来。
例:
LDR R1, [R2]
STR R1, [R2]

基址加变址寻址
基址加变址寻址就是将寄存器(一般称作基址寄存器)的内容与指令中给出地址偏移量相加,从而得到一个操作数的有效地址。
例:
LDR R1, [R2 + #4]
STR R1, [R2 + #4]
LDR R1, [R2 + #4]!(加!表示,R2中的内容会改变)
STR R1, [R2 + #4]!(加!表示,R2中的内容会改变)

相对寻址
与基址加变址寻址方式类似,相对寻址以程序计数器(PC值)的当前值作为基址,指令中的地址标号作为偏移量,将两者相加后得到操作数的有效地址。
例:
B stop

堆栈寻址

块拷贝寻址

特殊指令

分支指令注意事项

在这里插入图片描述
分支指令的编码如上图。
其中[23:0]表示偏移量,共计24位,通过换算其可以表示16Mbyte也就是可以跳转的范围是上下8M。
但是,事实上他的跳转范围为上下32Mbyte,也就是64Mbyte,这是什么原因呢?

原因是汇编中指令都是4字节对齐的,所以4字节指令的低两位是没用的,所以跳转指令[32:0]是不包括低两位的。所以换算范围的时候需要乘以4。

LDR伪指令

mov指令可以给寄存器赋一个立即数,但是因为指令长度限制,只能赋值一些特殊的立即数,所以使用起来很不方便。

ldr伪指令没有这个限制。如果使用ldr伪指令时,后面跟的立即数没有超过8位,那么在实际汇编的时候该ldr伪指令是被转换为mov指令的。

LDR伪指令与LDR指令是有区别的。
LDR的伪指令格式为ldr Rn, =expr ,其中Rn为寄存器,expr为立即数。

ARM笔记_第4张图片
LDR汇编指令会被编译器优化。运行时会将立即数作为指令读取到PC中,然后使用 LDR , R1, [PC] 将PC中的值放入寄存器中。而这个指令的位置在为 正在执行指令的地址 + 8
在汇编语言中,会将常量编译在可执行指令后面,通过LDR方式读取,这个存储常量的位置叫做 数据池

移位指令

LSL
左移指令,将无符号操作数左移,低位补零
LSL R1, R0, #3 ----->将R0中数据左移3位放入R1中,但不影响R0中数据。
LSL R1, #3 ----->将R1中数据左移3位,R1中数据改变。

LSLS指令同LSL相同,但会将CPSR中的C设置为移位数据的最高位。
ARM笔记_第5张图片
上图中执行完 LSL R0, #1指令后,CPSR中的C位并没有变化

ARM笔记_第6张图片
上图中,执行完 LSLS R0, #1 后,CPSR中的C位变为1,此时,R0中数据为0x0000003C,最高位为0。
ARM笔记_第7张图片
在上图中,再次经过 LSLS R0, #1 后,CPSR中的值变为0。

LSR
同LSL相似,将无符号操作数右移,高位补0。
同时,若不加S,不影响CPSR中的C位。

ROR
同LSR类似,循环右移,将低位移出位补至高位。
同时,不加S,不影响CPSR中的C位。

ROL
同LSL类似,循环左移,将高位移出位补至低位位。
同时,不加S,不影响CPSR中的C位。

ASR
同LSR类似,将操作数右移,将最高位补至最高位。
同时,不加S,不影响CPSR中的C位。
ARM笔记_第8张图片
ARM笔记_第9张图片
** RRX**
循环右移,使用S补高位。

加载/存储指令

load/store架构规定,存储器之间不能直接拷贝,需要通过寄存器做中转

LDR
从存储器将一个32位的字数据加载至目的寄存器

若想加载半字数,使用LDRH
若想加载字节数据,使用LDRB
若想添加条件位,例LDREQB

使用示例:

指令 含义
LDR R0, [R1] 将R1指向的存储器中的字数据读取寄存器R0
LDR R4, [R1 + R2] 将R1+R2指向的存储器中的字数据读取至寄存器R4
LDR R2, [R1 + #4] 将R1+4指向的存储器中的字数据读取至寄存器R2
LDR R1, [R2 + #4]! 将R2+4指向的存储器中的字数据读取至寄存器R1,并将新地址写入R1
LDR R1, [R2 + R3]! 将R2+R3指向的存储器中的字数据读取至寄存器R1,并将新地址写入R2
LDR R1, [R2], R3 将R2指向的存储器中的字数据读取至寄存器R1,并将新地址R2+R3写入R1
LDR R0, [R1, R2, LSL#2]! 将由R1+R24指向的存储器中的数据读至寄存器R0,并将R1+R24写入R1,将R2*4写入R2
LDR R0, [R1], R2, LSL#2 将R1指向的存储器中的字数据读取至寄存器R1,并将R2*4写入R1

注:不能跨字节读取
ARM笔记_第10张图片

STR
类似于LDR。

批量数据加载/存储指令

批量数据加载指令

LDM{条件} {类型} 基址寄存器 {!}, 寄存器列表 {^}

  1. 其中 {!} 为可选后缀,若选用后缀,则当前数据传送完毕后将最后的地址写入基址寄存器。
  2. 其中 {^} 为可选后缀,当指令为LDM且寄存器列表中包含R15,选用该后缀时表示:除了正常的传送数据外,还将SPSR复制到CPSR。同时该后缀还表示传入或传出的是用户模式下的寄存器,而不是当前模式下的寄存器。
  3. 其中类型可选如下表:
类型 含义
IA 传送后地址增加
IB 传送前地址增加
DA 传送后地址减小
DB 传送前地址减小
  1. 其中参数列表的写法如下表:
写法 含义
{R1, R2, R3, R4, R7} R1, R2, R3, R4, R7寄存器,可以是不连续的寄存器
{ R1-R3} R1寄存器到R3寄存器
{R1-R3, R7} R1, R2, R3, R7寄存器

批量数据存储指令
STM同LDM相似。

堆栈指令

注:ARM是满减栈
ARM堆栈操作通过传送指令来完成。
STMFD:入栈(STMDB)
SLMFD:出栈(LDMIA)

指令操作的是R13寄存器,也就是栈指针。

例:
STMFD SP!, {R0-R4}
SLMFD SP!, {R0-R4}
==注:需添加 ! ==

SWP

作用:在寄存器和存储器之间,由一次存储器读和一次存储器写组成的原子操作。完成一个字节或字的交换
原子操作:不可被打断的操作

例:
swp r1, r2, [r0]
将R0指向的内容放入R1,再将R2内容放到R0所指向的空间

PSR传送指令

共包括两条指令:MSR和MRS,主要用于修改CPSR的内容。

MRS用于将CPSR或SPSR中的值传送到另一个寄存器
MSR用于将一个寄存器中的值传送到CPSR或SPSR

注:特权模式可以直接使用PSR传送指令切换到非特权模式,而非特权模式不能使用PSR传送指令直接切换到特权模式,该操作由ARM处理器来完成

SWI指令(软件中断)

格式:swi swi_number
==注:==swi_number范围是0~255

在执行swi时处理器都做了哪些事?

  1. 切换到svc模式
  2. cpsr被分到svc模式下的spsr中
  3. 保存swi下一条指令的地址到svc模式下的lr(r14)中,swi处理结束后从svc模式下lr保存的地址重新取址译码执行
  4. 这是pc到中断向量表相应的入口

异常返回指令

从SWI返回和Undef异常返回
MOVS pc, lr

** 从FIQ、IRQ和预取异常(Perfect Abort)返回**
SUBS pc , lr, #4

从数据异常(Data Abort)返回
SUBS pc, lr, #8

如果LR之前被压栈
使用LDMFD sp!, {sp}^

软中断

ARM笔记_第11张图片

.text
.global _start
_start:
	@中断向量表,中断向量表位置
	b reset
	nop
	b swi_handle
	nop
	nop
	nop
	nop
	nop
	@中断向量表顺序为什么固定?中断向量表为什么一般位于程序开始位置?
	@从设计角度出发,一旦发生异常,从固定的位置固定顺序找中断入口比较合适。

reset:
	ldr sp, =bufend				@设置栈,目的之保护和恢复现场
	@为什么在svc模式下设置栈?
	@因为在不同模式下设置的栈是私有的,swi软中端进入的是svc模式,所以在svc模式下设置栈

	@模式切换
	mrs r0, cpsr
	bic r0, #0x1f
	orr r0, #0x10
	msr cpsr, r0
	@为什么要切换模式?
	@在没有异常产生的情况下,代码应该在非特权模式下运行,即user模式,唯一的非特权模式
	
	ldr r1, =0x11111111
	ldr r2, =0x22222222
	ldr r3, =0x33333333
	ldr r4, =0x44444444
	
	swi 88
	@swi执行会切换到哪个模式下运行?
	@会切换到svc模式下运行
	@在执行swi时处理器都做了哪些事?
	@ 1 切换到svc模式
	@ 2 cpsr被分到svc模式下的spsr中
	@ 3 保存swi下一条指令的地址到svc模式下的lr(r14)中,swi处理结束后从svc模式下lr保存的地址重新取址译码执行
	@ 4 设置pc到中断向量表相应的入口
	@ swi number的范围是0~255
	
	nop
	b stop

swi_handle:
	stmfd sp!, {r0-r12, r14}	@寄存器入栈
	@保护现场,将寄存器值备份到栈空间。因为r0-r12是user与svc模式共有的,svc模式下执行程序可能会修改,所以要备份。而r14寄存器在程序执行过程中,可能会调用bl这种影响r14寄存器的指令,所以也要备份
	
	sub r0, lr, #4				@找到swi指令
	ldr r1, [r0]				@获取指令机器码
	bic r1, #0xff000000			@获取swi number
	cmp r1, #88					@比较机器码
	bleq func					@跳转到相应的函数
	nop
	nop
	ldmfd sp!, {r0-r12, pc}^	@出栈操作
	@入栈时是r14,退栈时给pc赋值,将swi下一条指令地址,给pc,重新取址译码执行
	
func:
	mov r0, #0x12
	mov pc, lr					@函数返回
	@注意通过lr给pc赋值,在返回时从pc处重新取址译码执行
	
stop:

.data
	buf:
		.space (128)
	bufend:
.end

汇编和C语言混合变编程

C语言和汇编混合编程要解决的问题:

  1. 参数传递
  2. 返回值
  3. 调用

返回值:

  1. 若返回值是半精度浮点型,则使用r0的低16位
  2. 小于4个字节的i基本数据类型,扩充为1个字,使用r0返回
  3. 返回字大小的基本数据类型,使用r0返回
  4. 返回双字大小的基本类型数据和64位容器化向量,使用r和r1返回
  5. 128位容器化向量使用0-r3返回

参数:
基本标准规定了在核心寄存器(ro-r3)和堆栈中传递参数。对于带有少量参数的子例程,仅使用寄存器。

添加设置:
ARM笔记_第12张图片

汇编调用C语言

设置: 取消勾选
ARM笔记_第13张图片
汇编代码:

.text
.global _start
_start:
ldr sp, =bufend

mov r0, #2
mov r1, #3
bl fun
nop
nop
nop
b main

.data
	buf:
		.space (128)
	bufend:
.end

C语言代码:

int fun(int a, int b)
{
 return a + b;
}

int main()
{
 return 0;
}

C语言转汇编:
ARM笔记_第14张图片
调用C语言函数过程示意图:ARM笔记_第15张图片

C语言调用汇编

汇编代码:

.text
.global _start
_start:
.global myadd

ldr sp, =bufend
b main

myadd:
	ldr r2, [r0]			@使用R0作为第一个参数
	ldr r3, [r1]			@使用R1作为第二个参数
	add r0, r2, r3	@使用R0作为返回值
	mov pc, lr

.data
	buf:
		.space (128)
	bufend:
.end

C语言代码:

extern int myadd(int, int);//声明是外部的函数
int main()
{
	int a = 10;
	int b = 30;
	myadd(a, b);
	return 0;
}

你可能感兴趣的:(笔记,arm,嵌入式)