ARM的A系列处理器有9种运行模型:User、FIQ、IRQ、Supervisor(SVC)、Abort、Undef,Monitor,Hyp和System,其中User是非特权模式,其余6中都是特权模式。这9个运行模式可以通过软件、中断或者异常来进行切换。大多数的程序都运行在User模式,是不能访问系统所有资源的,要想访问这些受限的资源就必须进行模式切换。但用户模式不能直接进行切换,需要借助异常来完成模式切换,当要切换模式的时候,应用程序可以产生异常,在异常的处理过程中完成处理器模式切换。当中断或者异常发生以后,处理器就会进入到相应的异常模式种,每一种模式都有一组寄存器供异常处理程序使用,目的是为了保证在进入异常模式以后,用户模式下的寄存器不会被破坏。
STM32只有特权模式和非特权模式,但Cortex-A 就有 9 种运行模式。
ARM架构有16个32位的通用寄存器,R0~R14可以用作通用的数据存储,R15是程序计数器PC,用来保存将要执行的指令。另外还有“当前程序状态寄存器CPSR”和“备份程序状态寄存器SPSR”,SPSR是CPSR寄存器的备份。
Cortex-A7 9种模式对应的寄存器如下图,蓝色背景的是各个模式所独有的寄存器。在所有的模式中,低寄存器组(R0 ~ R7)共享同一组物理寄存器,但一些高寄存器组在不同的模式有自己独有的寄存器。
R0~R15 通用寄存器,可以分为以下三类:
①、未备份寄存器,即R0~R7。
②、备份寄存器,即R8~R14。
③、程序计数器PC,即R15。
方法1.在子函数中编写:
MOV PC, LR //寄存器 LR 中的值赋值给 PC,实现跳转
方法2.在子函数入口将LR入栈:
PUSH {LR} //将LR寄存器压栈
在子函数的最后面出栈:
POP{PC} @将上面压栈的 LR 寄存器数据出栈给 PC 寄存器
②、当异常发生后,该异常模式对应的R14寄存器被设置成该异常模式将要返回的地址,R14也可以当作普通寄存器使用。
CPSR是当前程序状态寄存器,被所有模式共用,该寄存器包含了条件标志位、中断禁止位、当前处理器模式标志等一些状态位以及一些控制位。所有的处理器模式都共用一个CPSR会导致冲突,为此除了User和Sys两个模式,其他7个模式都各配备了一个专用的物理状态寄存器,叫做SPSR(备份程序状态寄存器)。当特定的异常中断发生时,SPSR寄存器用来保存当前程序状态寄存器(CPSR)的值,当异常退出以后可以用SPSR中保存的值来恢复CPSR。因为User和Sys不是异常模式,所以并没有配备SPSR,因此不能在User和Sys模式下访问SPSR,会导致不可预知的结果。SPSR和CPSR的寄存器结构相同。
J | T | 描述 |
---|---|---|
0 | 0 | ARM |
0 | 1 | Thumb |
1 | 1 | ThumbEE |
1 | 0 | Jazelle |
M[4:0] | 处理器模式 |
---|---|
10000 | User模式 |
10001 | FIQ模式 |
10010 | IRQ模式 |
10011 | Supervisor(SVC)模式 |
10110 | Monitor(MON)模式 |
10111 | Abort(ABT)模式 |
11010 | Hyp(HYP)模式 |
11011 | Undef(UND)模式 |
11111 | System(SYS)模式 |
在Cortex-A芯片上电后,需要用户写程序,使用汇编语言初始化SP指针,使其指向栈顶,并且需要初始化DDR等操作(因为芯片可能没有向用户开放的ram),最后再进入C语言的main。由于我们编写的是ARM汇编,使用GCC交叉编译器编译,所以汇编代码要符合GNU语法,GNU语法的格式是:label:instruction @ comment。
可以使用“.section”伪操作来定义一个段,可以自定义段名称,也可以使用汇编系统预定义的段名:
段名 | 含义 |
---|---|
.text | 表示代码段 |
.data | 初始化的数据段 |
.bss | 未初始化的数据段 |
.rodata | 只读数据段 |
汇编程序的默认入口标号是_start,也可以在链接脚本中使用ENTRY来指明其它的入口点。下面代码中.global 是伪操作,表示_start 是一个全局标号,类似 C 语言里面的全局变量。
.global _start
_start:
ldr r0, =0x12 @r0=0x12
还有其他常用伪操作:
名称 | 举例 |
---|---|
.byte | 定义单字节数据,比如.byte 0x12 |
.short | 定义双字节数据,比如.short 0x1234 |
.long | 定义一个 4 字节数据,比如.long 0x12345678 |
.equ | 赋值语句,比如.equ num, 0x12,表示num=0x12 |
.align | 数据字节对齐,比如:.align 4 表示 4 字节对齐 |
.end | 表示源文件结束 |
.global | 定义一个全局符号,比如:.global _start |
处理器内部数据传输命令:
MOV R0,R1 @将寄存器R1中的数据传递给R0,即R0=R1
MOV R0, #0X12 @将立即数0X12传递给R0寄存器,即R0=0X12
MRS R0, CPSR @将特殊寄存器CPSR里面的数据传递给R0,即R0=CPSR
MSR CPSR, R0 @将R0中的数据复制到CPSR中,即CPSR=R0
存储器访问指令:
LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
LDR R1, [R0] @读取地址 0X0209C004 中的数据到 R1 寄存器中
LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
LDR R1, =0X20000002 @R1 保存要写入到寄存器的值,即 R1=0X20000002
STR R1, [R0] @将 R1 中的值写入到 R0 中所保存的地址中
压栈和出栈指令:
通常会在A函数中调用B函数,B函数执行完以后再回到A函数继续执行。必须在跳到B函数前保存当前处理器状态(R0 ~ R15寄存器值,即保护现场),B函数执行完后再恢复R0 ~ R15即可(即恢复现场)。保护现场需要进行压栈操作(PUSH指令),恢复现场要进行出栈操作(POP指令)。PUSH和POP是多存储和多加载指令,利用当前的栈指针SP来生成地址,能一次操作多个寄存器。
跳转指令:
//设置栈顶指针后跳转到C语言
_start:
ldr sp,=0X80200000 @设置栈指针
b main @跳转到 main 函数
//在汇编中使用bl执行C语言的中断服务函数
push {r0, r1} @保存r0,r1
cps #0x13 @进入SVC模式,允许其他中断再次进去
bl system_irqhandler @加载C语言中断处理函数到r2寄存器中
cps #0x12 @进入IRQ模式
pop {r0, r1}
str r0, [r1, #0X10] @中断执行完成,写EOIR
参考正点原子嵌入式linux开发指南