ARM开发技术

ARM

ARM( Advanced RISC Machines)有 3 种含义,它是一个公司的名称,是一类微处理器的通称,还是一种技术的名称。主要出售芯片设计技术的授权。

哈佛体系结构

程序和数据空间独立的体系结构,重新设计区分了数据总线和指令总线。

ARM9 处理器系列

采用了 5 级指令流水线。到 ARM7 为止的 ARM 处理器使用简单的 3 级流水线。取指令、译码、执行。

ARM11 处理器系列

8 级流水线, ARMv6 架构。在 Cortex-A8 中有一条 13 级的流水线。

Cortex 处理器系列

ARMv7 架构,采用了 Thumb-2 (32位)技术。

ARM处理命名规则

后缀名称:
T -> Thumb
D -> 调试[JTAG]
M -> 长乘法
I -> 嵌入式ICE单元 [支持设置断点]
E -> 增强型DSP指令
J -> 支持Java字节码

在命名方式上,基于 ARMv7 架构的 ARM 处理器已经不再延用过去的数字命名方式,而是冠以 Cortex 的代号。基于 v7A 的称为“ Cortex-A 系州”,基于v7R 的称为“ Cortex-R 系列”,基于 v7M 的称为“ Cortex-M3”。

ARM 微处理器结构

ARM 内核采用 RISC 体系结构。 ARM 体系结构的主要特征如下:
( 1) 采用大量的寄存器,它们都可以用于多种用途。
( 2) 采用 Load/Store 体系结构。
( 3) 每条指令都条件执行。
( 4) 采用多寄存器的 Load/Store 指令。
( 5) 能够在单时钟周期执行的单条指令内完成一项普通的移位操作和一项普通的 ALU 操作。
( 6) 通过协处理器指令集来扩展 ARM 指令集,包括在编程模式中增加了新的寄存器和数据类型。
( 7) 如果把 Thumb 指令集也当做 ARM 体系结构的一部分,那么在 Thumb 体系结构中还可以高
密度 16 位压缩形式表示指令集。

中断控制器

ARM 内核只提供快速中断( FIQ)和标准中断( IRQ)两个中断向量。但各个半导体厂家在设计芯片时加入了自己定义的中断控制器,以便支持诸如串行口、外部中断、时钟中断等硬件中断。

ARM 的基本数据类型

ARM 采用的是 32 位架构, ARM 的基本数据类型有以下 3 种。
Ø Byte:字节,8bit。
Ø Halfword:半字,16bit(半字必须与 2 字节边界对齐)。
Ø Word:字,32bit(字必须与 4 字节边界对齐)。
注意:
所有数据类型指令的操作数都是字类型的,如“ ADD r1, r0,# 0x1”中的操作数“ 0x1”就是以字类型数据处理的。
ARM 指令编译后是 4 个字节(与字边界对齐)。 Thumb 指令编译后是 2 个字节(与半字边界对齐)。

存储器大/小端

在大端模式下,一个字的高地址放的是数据的低位,而在小端模式下,数据的低位放在内存中的低地址。即低字节放在低地址中。

ARM有几种工作模式?

ARM有7个基本工作模式: 用户模式(user)、系统模式(system)、数据访问中止模式(abort)、未定义指令中止模式(undef)、快速中断模式(FIQ)、外部中断模式(IRQ)、管理模式(SVC)。
cortex-A系列特有:M (monitor) : 安全模式

记忆方法:USA UFI(不是UFO) SM(你懂的)


除用户模式外的其他 7 种处理器模式称为特权模式( Privileged Modes)。在特权模式下,程序可以访问所有的系统资源,也可以任意地进行处理器模式切换。
非特权模式 : 用户模式
特权模式 : S,A,U,F,I,S,M
异常模式 : abt,udef,fiq,irq,svc

ARM有哪几个异常类型?

(1)复位异常 -> SVC 模式 [开发板复位]
(2)未定义异常 -> UDF 模式 [执行未定义的指令]
(2)软中断异常 -> SVC 模式 [通过SWI指令产生]
(4)取数据终止 -> ABT 模式 [没有取到数据]
(5)取指令终止 -> ABT 模式 [没有取指令]
(6)IRQ中断 -> IRQ 模式 [硬件触发的IRQ中断]
(7)FIQ中断 -> FIQ 模式 [硬件触发的FIQ中断]
处理器模式可以通过软件控制进行切换,也可以通过外部中断或异常处理过程进行切换。

协处理器( CP15)

ARM 处理器支持 16 个协处理器。

存储管理单元( MMU)

MMU 作为转换器,将程序和数据的虚拟地址(编译时的连接地址)转换成实际的物理地址,即在物理主存中的地址。虚拟地址由编译器和连接器在定位程序时分配;物理地址用来访问实际的主存硬件模块(物理上程序存在的区域)。

高速缓冲存储器( Cache)

Cache 经常与写缓存器( write buffer)一起使用。写缓存器是一个非常小的先进先出( FIFO)存储器,位于处理器核与主存之间。使用写缓存的目的是,将处理器核和 Cache 从较慢的主存写操作中解脱出来。当 CPU 向主存储器做写入操作时,它先将数据写入到写缓存区中,由于写缓存器的速度很高,这种写入操作的速度也将很高。写缓存区在 CPU 空闲时,以较低的速度将数据写入到主存储器中相应的位置。

当进行数据写操作时Cache未命中,根据Cache执行的操作不同,将Cache分为两类( C )
A:数据Cache和指令Cache
B:统一Cache和独立Cache
C:写通Cache和写回Cache
D:读操作分配Cache和写操作分配Cache

ARM核有多少个寄存器?

ARM 处理器有如下 40 个 32 位长的寄存器。
( 1) 33 个通用寄存器。
( 2) 6 个状态寄存器: 1 个 CPSR( Current Program Status Register,当前程序状态寄存器), 6 个SPSR( Saved Program Status Register,备份程序状态寄存器)。
( 3) 1 个 PC( Program Counter,程序计数器)。

当前处理器的模式决定着哪组寄存器可操作,任何模式都可以存取下列寄存器。
( 1) 相应的 R0~R12。
( 2) 相应的 R13( Stack Pointer, SP,栈指向)和 R14( the Link Register, LR,链路寄存器)。
( 3) 相应的 R15( PC)。
( 4) 相应的 CPSR。
特权模式(除 System 模式外)还可以存取相应的 SPSR。
通用寄存器根据其分组与否可分为以下两类。
( 1) 未分组寄存器( Unbanked Register),包括 R0~R7。
( 2) 分组寄存器( Banked Register),包括 R8~R14。

哪种模式使用的寄存器最少?

用户模式/系统模式(不用SPSR)。

未分组寄存器

未分组寄存器包括 R0~R7。顾名思义,在所有处理器模式下对于每一个未分组寄存器来说,指的都是同一个物理寄存器。未分组寄存器没有被系统用于特殊的用途,任何可采用通用寄存器的应用场合都可以使用未分组寄存器。但由于其通用性,在异常中断所引起的处理器模式切换时,其使用的是相同的物理寄存器,所以也就很容易使寄存器中的数据被破坏。切换模式时要保护。

分组寄存器

R8~R14 是分组寄存器,它们每一个访问的物理寄存器取决于当前的处理器模式。
对于分组寄存器 R8~R12 来说,每个寄存器对应两个不同的物理寄存器。一组用于除 FIQ 模式外的所有处理器模式,而另一组则专门用于 FIQ 模式。这样的结构设计有利于加快 FIQ 的处理速度。不同模式下寄存器的使用,要使用寄存器名后缀加以区分。例如,当使用 FIQ 模式下的寄存器时,寄存器 R8 和寄存器 R9 分别记为 R8_fiq、 R9_fiq;当使用用户模式下的寄存器时,寄存器 R8 和 R9 分别记为 R8_usr、 R9_usr等。在 ARM 体系结构中, R8~R12 没有任何指定的其他的用途,所以当 FIQ 中断到达时,不用保存这些通用寄存器,也就是说, FIQ 处理程序可以不必执行保存和恢复中断现场的指令,从而可以使中断处理过程非常迅速。所以 FIQ 模式常被用来处理一些时间紧急的任务,如 DMA 处理。
对于分组寄存器 R13 和 R14 来说,每个寄存器对应 6 个不同的物理寄存器。其中的一个是用户模式和系统模式公用的,而另外 5 个分别用于 5 种异常模式。访问时需要指定它们的模式。
R13 寄存器在 ARM 处理器中常用做堆栈指针,称为 SP。
寄存器 R14 又被称为连接寄存器( Link Register, LR)。每一种处理器模式用自己的 R14 存放当前子程序的返回地址。当通过 BL 或 BLX 指令调用子程序时, R14被设置成该子程序的返回地址。在子程序返回时,把 R14 的值复制到程序计数器( PC)。

程序状态寄存器

每一种处理器模式下都有一个专用的物理寄存器做备份程序状态寄存器( Saved Program StatusRegister, SPSR)。当特定的异常中断发生时,这个物理寄存器负责存放当前程序状态寄存器的内容。当异常处理程序返回时,再将其内容恢复到当前程序状态寄存器。

cpsr (反应CPU状态)

N Z C V …. I F T mode

N : 计算结果为负则置1
Z : 记录结果为0,则置1
C : 加法计算最高位产生进位,则置1,减法运算没有产生借位,则置1
V : 有符号数溢出[两个整数相加,结果为负数,则置1
T : 置1(Thumb状态),否则(ARM 状态)
F :置1(禁用FIQ)
I : 置1(禁用IRQ)
mode : 模式位

总结:
(1)所有模式通用 r0-r7 ,pc,CPSR [10]
(2)除了快中断模式,其他模式共用 : r8-r12 ,快中断模式的r8-r12私有的 [5 + 5 = 10]
(3)异常模式特有的寄存器 : sp,lr,spsr [5 * 3 = 15]
(4)用户模式和系统模式共用同一套寄存器(所有的寄存器) : sp,lr [2]

一个模式最多可以访问的寄存器:r0-r12,sp,lr,pc,cpsr,spsr
注意:在Thumb状态下,r8-r12是不可见,其他寄存器映射ARM状态的寄存器

ARM异常处理

1.ARM 异常类型
(1)复位异常 -> SVC 模式 [开发板复位]
(2)未定义异常 -> UDF 模式 [执行未定义的指令]
(2)软中断异常 -> SVC 模式 [通过SWI指令产生]
(4)取数据终止 -> ABT 模式 [没有取到数据]
(5)取指令终止 -> ABT 模式 [没有取指令]
(6)IRQ中断 -> IRQ 模式 [硬件触发的IRQ中断]
(7)FIQ中断 -> FIQ 模式 [硬件触发的FIQ中断]

2.异常产生的时候
(1)ARM 核自动完成的事情

<1>CPSR拷贝到异常模式的SPSR

<2>设置CPSR的相应位
进入ARM状态
设为对应的异常模式
禁止中断[中断产生的时候]

<3>将PC保存到异常模式的LR

<4>修改PC的值到异常向量表


异常向量表:一块内存,内存的中存放的是一些跳转指令,跳转异常处理函数

异常向量表可以存放的位置:
在非CortexA系列异常,异常向量表可以存放在0x0000,0000 或 0xffff,0000

在CoctexA系列异常向量表可以存放在任何位置,只需将异常向量表所在起始位置告诉CP15[协处理器]的C12寄存器

(2)程序员需要的事情

异常进入:保护通用寄存器
异常退出:恢复现场[恢复保护的寄存器,恢复LR[异常]到PC , 恢复SPSR[异常]到CPSR]

ARM核是如何让FIQ快速处理?

<1>FIQ模式有私有的r8-r12,如果只是用私有的寄存器,不需要保护通用寄存器

<2>FIQ异常在异常向量表最顶端,可以直接写FIQ异常处理函数,不需要跳转

ARM 处理器的寻址方式

ARM 指令的寻址方式分为数据处理指令寻址方式和内存访问指令寻址方式。

数据处理指令寻址方式可以分为以下几种?

( 1) 立即数寻址方式。
( 2) 寄存器寻址方式。
( 3) 寄存器移位寻址方式。

判断一个数是不是合法的立即数,最好的方法就是拿编译器验证,不是,编译器就会报错。但是在面试或者是笔试的时候,可能会遇到判断立即数的题目,那有没有好的方法判断呢?

1.首先将这个数转换为32bits 16进制形式,例如218 = 0xda = 0x000000da
2.除零外,仅有一位数字的如0x00100000,为合法立即数该数字可为0-F
3.除零外,仅有两位数字,并且相邻(包括首位,如0x1000000A)的为合法立即数。
4.除零外,仅有三位数字,并且相邻(包括中间有0相间,例如0x10800000,包括首尾相邻,如:0x14000003),这三位数中最高位取值仅能为1,2,3,最低位取值仅能为4,8,C,中间位0-f,这样的数为合法立即数。
5.除去以上三种,其他基本上都是非法立即数。

寄存器寻址方式

寄存器的值可以被直接用于数据操作指令,这种寻址方式是各类处理器经常采用的一种方式,也是一种执行效率较高的寻址方式,如:
MOV R2,R0 ;R0 的值送 R2
ADD R4,R3,R2 ;R2 加 R3,结果送 R4
CMP R7,R8 ; 比较 R7 和 R8 的值

#寄存器移位寻址方式
寄存器的值在被送到 ALU 之前,可以事先经过桶形移位寄存器的处理。预处理和移位发生在同一周期内,所以有效地使用移位寄存器,可以增加代码的执行效率。
下面是一些在指令中使用了移位操作的例子:
ADD R2,R0,R1,LSR #5
MOV R1,R0,LSL #2
RSB R9,R5,R5,LSL #1
SUB R1,R2,R0,LSR #4
MOV R2,R4,ROR R0

内存访问指令寻址方式

内存访问指令的寻址方式可以分为以下几种。
( 1) 字及无符号字节的 Load/Store 指令的寻址方式。
( 2) 杂类 Load/Store 指令的寻址方式。
( 3) 批量 Load/Store 指令的寻址方式。
( 4) 协处理器 Load/Store 指令的寻址方式。

如果在数据处理指令前使用 S 前缀,指令的执行结果将会影响 CPSR 中的标志位。

当 PC 作为目标寄存器且指令中 S 位被设置时,指令在执行跳转操作的同时,将当前处理器模式的 SPSR寄存器的内容复制到 CPSR 中。这种指令“ MOVS PC LR”可以实现从某些异常中断中返回。

汇编加法程序设计

.globl _start
_start:
mov r0,#9 //9 存入 r0
mov r1,#15 //15 存入 r1
add r1,r1,r0 //r1 与 r0 相加存入 r1
stop:
b stop
.globl指示告诉汇编器,_start这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号(在第 5.1 节 “目标文件”详细解释)。_start就像C程序的main函数一样特殊,是整个程序的入口,链接器在链接时会查找目标文件中的_start符号代表的地址,把它设置为整个程序的入口地址,所以每个汇编程序都要提供一个_start符号并且用.globl声明。如果一个符号没有用.globl声明,就表示这个符号不会被链接器用到。

指令助记符{条件}{S} Rd,Rn,{operand2}

@指令助记符号 ADD,SUB …
@条件 只有在条件满足,指令才会执行 eq,ne,gt,lt,ge,le
@S 指令执行结果影响CPSR的N,Z,C,V
@Rd 目标寄存器[R0-R15]
@Rn 第一个操作数寄存器 [r0-r15]
@operand2 第二个操作数

如何判断一个数是否为合法立即数?

立即数
表示方法 : # + 数字
合法立即数 : 如果能从[0-255]直接找到一个数,将这个数循环右移偶数为得到当前的数字,则数字是合法立即数 ,否则为非法立即数。

寄存器移位

移位方式: lsl[逻辑左移] ,lsr[逻辑右移] , asr[算术右移:需要关心最高位是否是符号位]
例如:
ADD r0,r1,r2,lsl #3 //r0 = r1 + (r2 << 3)

算术运算指令 [+, - , *]

1.加法指令
ADD Rd,Rn,operand2
例如:
ADD r0,#5,#6//错误
ADD r0,r1,#6 //r0 = r1 + 6
2.减法指令
SUB Rd,Rn,operand2
SUB r0,r1,#6 //r0 = r1 - 6
3.乘法指令
MUL Rd,Rn,寄存器
例如:
MUL r1,r2,r3 //r1 = r2 * r3
注意:Rd和Rn不能一样

数据装载指令

1.MOV Rd,operand2
MOV r0,#5 //r0 = 5
2.装载任意数据到寄存器
LDR Rd,=数据

LDR r0,=0x123456 //r0 = 0x123456

伪指令:是给编译器使用的,编译器最终会将这条指令翻成ARM核所能识别的指令

3.MVN Rd,operand2
MVN r0,#0x10 //r0 = ~ 0x10

按位操作 [按位与,按位或,异或,位清除]

(1)按位与
AND Rd,Rn,operand2
AND r0,r1,#5 //r0 = r1 & 5
(2)按位或
ORR Rd,Rn,operand2
ORR r0,r1,#5 //r0 = r1 | 5
(3)异或
EOR Rd,Rn,operand2
EOR r0,r1,#5 //r0 = r1 ^ 5
(4)位清除
BIC Rd,Rn,operand2 //Rd = Rn & ~ operand2
//r0:0xff
BIC r0,r0,#3 //r0 = 0xff & ~3 = 0xfc

比较指令

CMP Rd,operand2
注意:自动影响CPSR的N,Z,C,V eq,ne,gt,lt,ge,le
练习: a 的值10
if(a >= 5)
a ++ ;
else
a –;

mov r0,#10
cmp r0,#5
addge r0,r0,#1
sublt r0,r0,#1

跳转指令

B 标签
BL 标签 [会将PC的值保存到Lr寄存器]
思考:如何实现长跳转?
直接给PC赋值 :ldr pc,=lable

内存访问指令

  1. ldr / str
    ldr r0,[r1] //r0 = r1
    //前索引
    ldr r0,[r1,#4] //r0 =
    (r1 + 4)
    //后索引
    ldr r0,[r1],#4 //r0 = r1,r1 = r1 + 4 ; data = p ++;
    //自动索引
    ldr r0,[r1,#4]! //r0 = *(r1 + 4); r1 = r1 + 4

str r0,[r1] //r1 = r0
str r0,[r1,#4] //
(r1 + 4) = r0
str r0,[r1],#4 //r1 = r0 ,r1 = r1 + 4
str r0,[r1,#4]! //
(r1 + 4) = r0,r1 = r1 + 4

2.ldm{mode} / stm{mode}
mode:
IA (increase after) : 先操作数据,后增加地址
IB (increase before) : 先增加地址,后操作数据
DA (decrement after) : 先操作数据,后减少地址
DB (decrement before) : 先减少地址,后操作数据

//进栈/出栈 的方式
FD (full decrement)
ED (empty decrement)
FA (full add)
EA (empty add)

中断异常进入的时候,保护通用寄存器
sub lr,lr,#4
stm{mode} sp!,{r0-r12,lr}
bl c_lang

//@^表示将SPSR拷贝到CPSR
ldm{mode} sp!,{r0-r12,pc}^

多寄存器操作指令

STM/LDM{cond}{mode} 基地寄存器{!} {寄存器列表}{^}

STM : 将列表中的寄存器写入内存
LDM : 从内存中读取数据到寄存器列表

@cond eq,ne,gt,lt,ge,le
@mode ia,ib,da,db,fd,fa,ed,ea

@基地址寄存器 r0-r13
@! 最后更新基地址寄存器
@寄存器列表 {r0,r4,r5-r7}

@^ 如果寄存器列表没有pc,此时操作的是用户模式的寄存器;如果是LDM指令,并且有pc,此时恢复CPSR

注意:
1.大地址对应大寄存器
2.寄存器列表从小到大
3.堆栈操作的时候,怎么进栈,怎么出栈 stmfd / ldmfd

ARM 一些伪指令

(1)LDR 的几种写法
LDR r0,=0x123456 //r0 = 0x12345678
LDR r0,=label //r0 = label
LDR r0,[r1] // r0 = r1
LDR r0,lable // r0 =
label

(2)ADR :小范围的地址装载

ADR r0,label //r0 = label

ARM异常处理

<1>告诉异常向量表的位置
ldr r0,=0x20000
mcr p15,0,r0,c12,c0,0

<2>编写异常向量表,在异常向量表中存放跳转指令
例如:
b handler_swi / ldr pc,=handler_swi

<3>异常处理

  1. 保存通用的寄存器[r0-r12]
  2. 处理
  3. 异常返回,恢复通用的寄存器,恢复PC,恢复CPSR

ldr pc,_handler_swi

_handler_swi:

.word handler_swi

ldr pc,=handler_swi

irq:
handler_swi:
软中断处理
获取软中断号,存放到r0

软中断实验

_start:
异常向量表

reset:
mov r0,#10
mov r1,#20
@告诉ARM核心异常向量表位置
ldr r0,=0x20000
mcr p15,0,r0,c12,c0,0
@切换到用户模式[MRS/MSR]
SWI 0x80
mov r2,#30
stop:
b stop
software_interrupt:
保护通用的寄存器
ldr sp,=0x38000
stmfd sp!,{r0-r12,lr}
获得软中断号,存放在r0
恢复现场

ARM 核的异常返回

(1)IRQ 和 FIQ 异常
指令1[执行] <- IRQ异常到达
指令2
指令3
指令4 <- pc
最终:lr 保存指令3的地址
返回: pc <- lr - 4

(2)软中断异常
swi 0x80 <- 产生软中断异常
指令2
指令3 <-pc
指令4
最终:lr 保存指令2的地址
返回: pc <- lr

(3)预取指令终止异常 :取指令的时候,没有取到指令
指令1 <-执行的时候,没有取到指令
指令2
指令3 <- pc
指令4
最终:lr 保存指令2地址
返回: pc <- lr - 4 [重新取没有取到指令]

(4)预取数据终止异常 : 取数据的时候,没有取到数据
指令1 <-执行结束后,产生数据终止异常
指令2
指令3
指令4 <- pc
最终:lr 保存指令3地址
返回: pc <- lr - 8

(5)未定义指令异常 : 指令ARM核不能识别指令
指令1 <-执行的时候,ARM核不能识别指令
指令2
指令3 <- pc
指令4
最终:lr 保存指令2的地址
返回: pc <-lr

GCC C语言内联汇编格式

asm(
“汇编指令1\n”
“汇编指令2\n”

:输出列表
:输入列表
:修改列表
);

(1)输出列表 : 将寄存器的值赋值给”C”变量

:”=r”(C变量)

(2)输入列表 : 将”C”变量值赋值给寄存器
:”r”(C变量)

注意:
“=r”(C变量) : 表示C变量可以写
“+r”(C变量) : 表示C变量可以读、可以写
“r” (C变量) : 表示C变量只读

汇编指令中如何引用C变量?
在输出列表和输入列表中,对C变量做编号,第一个C变量为%0,第二个C变量%1,….

(3)修改列表 : 发生变化的寄存器
好处,编译器会这些寄存器先做一个压栈保护