stm32f103是crotex-m3内核,armv7结构,哈佛结构
s5p6818是crotex-a53内核,armv8架构,哈佛结构
精简指令集:RISC指令,一条指令占32位内存空间,大多数指令都是单周期。
复杂指令集:CISC指令,指令宽度不固定,指令周期不固定。
注: arm是精简指令集RISC
用户模式User
非特权模式,大部分任务执行在这种模式
快速中断模式FIQ
当一个高优先级中断产生时将进入这种模式
外部中断模式IRQ
当一个低优先级中断产生时将会进入这种模式
管理模式SVC
当复位或软中断指令执行时将回进入这种模式
中止模式ABT
当存取异常时将会进入这种模式
未定义指令模式UND
当执行未定义指令时会进入这种模式
系统模式SYS
使用和User模式相同寄存器集的特权模式
Cortex-A系列新增Monitor 模式。
Monitor : 是为了安全而扩展出的用于执行安全监控代码的模式;
也是一种特权模式
ARM7,ARM9,ARM11 有37个32-Bits长的寄存器。
Cortex体系结构下有40个32-Bits长的寄存器
Cortex-A(ARM-v7)多出3个寄存器,Monitor 模式 r13_mon , r14_mon, spsr_mon
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 | 快速中断发生时进入 |
注: 中断向量表的地址可能不同,但是顺序一定相同
产生异常时
从异常返回时
目的
提高效率
内容
程序计数器PC保存写一次要取得指令地址。除非有其他情况,否则处理器在每次取指令后总是递增PC,使他能够按顺序取得下一条指令。
一条指令的执行分为三个阶段:取址、译码、执行
取址,即将指令从存储器中取出
译码,即解码指令中用到的寄存器
执行,即处理指令,并将结果写入寄存器
根据栈顶指针的位置,栈分为两种:
满堆栈:栈顶指针指向最后插入元素的堆栈
空堆栈:栈顶指针指向限一个要放入元素位置的堆栈
根据堆的生成方式,栈分为两种:
递增堆栈:当栈由低地址向高地址生成时,称为递增堆栈
递减堆栈:当栈由高地址向低地址生成时,称为递减堆栈
上面两种方式混合成四种堆栈:
分别是:空递增堆栈、满递增堆栈、空递减堆栈、满递减堆栈。
注: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]:右移位数的一半。
所以,汇编语言的立即数是通过常数右移得来的。
注:
立即寻址
立即寻址也称为立即数寻址,操作数本身就在指令中给出,只要取出指令也就取到了操作数
例: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。
mov指令可以给寄存器赋一个立即数,但是因为指令长度限制,只能赋值一些特殊的立即数,所以使用起来很不方便。
而ldr伪指令没有这个限制。如果使用ldr伪指令时,后面跟的立即数没有超过8位,那么在实际汇编的时候该ldr伪指令是被转换为mov指令的。
LDR伪指令与LDR指令是有区别的。
LDR的伪指令格式为ldr Rn, =expr ,其中Rn为寄存器,expr为立即数。
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设置为移位数据的最高位。
上图中执行完 LSL R0, #1指令后,CPSR中的C位并没有变化
上图中,执行完 LSLS R0, #1 后,CPSR中的C位变为1,此时,R0中数据为0x0000003C,最高位为0。
在上图中,再次经过 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位。
** 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 |
STR
类似于LDR。
批量数据加载指令
LDM{条件} {类型} 基址寄存器 {!}, 寄存器列表 {^}
类型 | 含义 |
---|---|
IA | 传送后地址增加 |
IB | 传送前地址增加 |
DA | 传送后地址减小 |
DB | 传送前地址减小 |
写法 | 含义 |
---|---|
{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 r1, r2, [r0]
将R0指向的内容放入R1,再将R2内容放到R0所指向的空间
共包括两条指令:MSR和MRS,主要用于修改CPSR的内容。
MRS用于将CPSR或SPSR中的值传送到另一个寄存器
MSR用于将一个寄存器中的值传送到CPSR或SPSR
注:特权模式可以直接使用PSR传送指令切换到非特权模式,而非特权模式不能使用PSR传送指令直接切换到特权模式,该操作由ARM处理器来完成
格式:swi swi_number
==注:==swi_number范围是0~255
在执行swi时处理器都做了哪些事?
从SWI返回和Undef异常返回
MOVS pc, lr
** 从FIQ、IRQ和预取异常(Perfect Abort)返回**
SUBS pc , lr, #4
从数据异常(Data Abort)返回
SUBS pc, lr, #8
如果LR之前被压栈
使用LDMFD sp!, {sp}^
.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语言和汇编混合编程要解决的问题:
返回值:
参数:
基本标准规定了在核心寄存器(ro-r3)和堆栈中传递参数。对于带有少量参数的子例程,仅使用寄存器。
.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;
}
汇编代码:
.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;
}