Cortex-M4/7的寄存器
ARM架构中,处理器不能直接操作内存(存储器)中数据,必须先把内存中的数据加载到寄存器组中的寄存器里,处理完后,若有必要,再写回内存。这种方式一般被称作“加载-存储架构”。如果core的寄存器数量充足的话,在进行数据处理时,寄存器组中可以临时存储一些数据变量,而无需每次更新到系统内存及在使用时将它们读回,从而可以有效的提高系统的工作效率。
1.Register Bank
Cortex-M4/7有16个用于数据处理和程序控制的寄存器,称为Register Bank,依次被标记为R0~R15。
R0~R12是32位通用寄存器(general purpose register)。其中前8个寄存器R0~R7被称为low register,因为访问空间的限制,大多数16位指令只能够访问low registers。而high register(R8~R12)则能被32位和几个特殊的16位指令(如MOV(move))访问, R0~R12的初始值是未定义的。
R13,栈指针SP(stack pointer,堆栈指针) ,32位。
- 通过PUSH和POP指令访问栈空间内存(即系统的RAM区)。物理上存在两个栈空间指针:主栈指针Main Stack Pointer(MSP, 或SP_main)和进程栈指针Process Stack Pointer(PSP, 或SP_process)。其中MSP是默认的栈指针,系统复位或运行在处理(Handler)模式下时,通过R13访问的是MSP, 而PSP只能在线程(Thread)模式下使用。
- 至于使用的是哪个栈指针由CONTROL寄存器控制/配置(仅在Thread Mode下才可设置CONTORL寄存器),但在同一时间只有一个栈指针工作。 MSP和PSP都是32位的寄存器,但它们的低两位总是0,对这两个bit位的写操作不起作用。对于ARM Cortex-M处理器,PUSH和POP指令总是32位的,栈操作的地址也必须对齐到32位的字边界上。
- 一般用到嵌入式OS时才会使用PSP(用于thread级app的SP),此时OS内核同应用任务的栈是相互独立的。PSP得初始值未定义,而MSP(用于OS内核的SP)的初始值则需要在复位流程中从存储器的第一个字中取出。
- 在编译器中,可以通过r13(R13)或sp(SP)来访问堆栈(具体是MSP和PSP由当时环境决定);也可以通过指定的MRS、MSR指令来访问MSP和PSP。
-
注:
Cortex-M0中堆栈方向是向低地址方向增长,为满堆栈机制。堆栈操作是通过PUSH和POP来完成操作的。(压栈:栈顶地址减小;出栈:栈顶地址增大)
栈一般放在ARM的 RAM高位区,如某MCU中RAM地址为0x20000000-0x20007fff,共32KByte。栈大小设为4KByte的话,其地址一般就放在0x20007000-0x20007fff,其中0x20007000为绝对栈顶,0x20007ffc为绝对栈底,sp总是指向相对栈顶。第一个PUSH数据被存在绝对栈底(此时绝对栈底也是相对栈顶)。实际上,除了POP指令可以从栈顶中取数据外;MOV指令也可从任意位置取数据,但不会影响栈结构(即不影响其sp)。
由于ARM寄存器均是32bit,故PUSH和POP指令均是32bit访问,故sp指针总是至少4Byte对齐(低2bit永远为0)。有时编译器也会分配8Byte对齐的栈,这是由于double浮点类型需要占用8Byte,为了处理方便,故将栈设为8Byte对齐。
在一般函数(无参无局部变量无返回值)嵌套调用时,关于sp的操作。在执行BL FunctionA指令时,LR记录的是BL FunctionA的下一条顺序指令,在进入FunctionA后执行的第一条操作便是PUSH {LR}即将下一条顺序指令压入栈中,然后才开始执行FunctionA函数体。函数体执行结束之后,使用POP {PC}指令将栈顶数据弹到PC中,即可返回继续执行BL FunctionA的下一条顺序指令。函数嵌套调用与函数返回关系如下图:
R14,链接寄存器LR (Link Register)。
- 它被用于在调用函数或者执行子程序时,记录返回地址,在函数或子程序结束时,程序控制可以通过将LR的数值加载到程序计数器(PC)中,从而返回到调用程序处并继续执行。
- 在发生函数或子程序调用时,LR的值会被自动更新,如果被调用的函数在执行过程中仍需要调用其他函数, 则需要先把LR的值压栈,保存,否则返回地址将丢失。
- 异常处理(中断也是一种异常)期间,LR会被写入一个特别的值EXC_RETURN(异常返回),之后该数值会在异常处理结束时触发异常返回。
- 虽然在Cortex-M处理器中,任何函数的返回地址都一定是偶数(由于指令会对齐到半字地址上,因此第0位为0),但LR的第0位仍然是可读可写的,有些跳转/调用操作需要将LR(或正在使用的任何寄存器)的第0位,置1以表示Tbumb状态。
R15,程序计数器PC (Program Counter),可读可写。
- 读操作返回当前指令地址+4(由于流水线特性的设计以及对ARM7TDMI的兼容设计)。 写操作(例如,使用数据传输/处理指令)将导致程序执行地址的跳转。
- 因为指令必须半字或者整字对齐,所以PC的最低位(LSB)总为0。 但是通过跳转等语句更新PC时,需要将PC的最低位置1,以标识Thumb状态,否则会由于使用不支持的ARM指令(如ARM7TDMI中的32位ARM指令)而触发错误异常。对于C语言编程,不需要考虑这一点,因为编译器已经帮我们完成了这一操作。(对于高级编程语言,包括C和C++编译器会自动将跳转目标的LSB置位)
- 多数情况下,跳转和调用由专门的指令实现,利用数据处理指令更新PC的情况较少。不过,在访问位于程序存储器中的字符数据时,PC数值非常有用,因此会经常发现存储器读操作将PC作为基地址寄存器,而地址偏移则由指令中的立即数生成。
2.特殊寄存器
除了上述Register Bank中涉及到的16个寄存器外,Cortex-M4/7还有一些反应系统状态、控制内核工作模式、异常中断屏蔽掩码等特殊功能的寄存器。 这些特殊的寄存器并没有分配地址映射,可以通过MSR或者MRS读写这些寄存器。
2.1.状态寄存器PSR(Program Status Register)
APSR(Application PSR)、EPSR(Execution PSR)和Interrupt PSR(IPSR)。另外这三个寄存器可以组合为一个寄存器一起访问,标记为xPSR。
注:GE只有基于ARMv7E-M架构的处理器才有,例如Cortex-M4/7
在ARM的汇编器中,用PSR标识三个状态寄存器的组合。 我们可以单独的访问每个状态寄存器,也可以通过PSR访问他们的组合,参考如下的代码。 但是,需要注意的是,EPSR是不能够直接通过MRS(Move from Spential register to general Register,加载特殊寄存器的值到通用寄存器)和MSR(Move from general Register to Spential register,存储通用寄存器的值到特殊寄存器)读写的,而IPSR则是一个只读的寄存器。
MRS r0, PSR ; 读组合寄存器的值到r0中
MSR PSR, r0 ; 写r0的值到组合寄存器中
MRS r0, APSR ; 读应用寄存器的值到r0中
MSR APSR, r0 ; 写r0的值到应用寄存器中
状态寄存器的位定义如下:
N: 负数标识位(Negative flag)
Z: 零标识位(Zero flag)
C: 进位或者无借位标识位(Carry or NOT borrow flag)
V: 溢出标识位(Overflow flag)
Q: Sticky saturation flag
GE: 大于等于标识位(Greater-Than or Equal flags for each byte lane)
ICT/IT: Interrupt-Continuable Instruction(ICT)bits, IF-THEN instruction status bit for conditional execution.
T: Thumb状态标识位,始终为1;尝试清除该位将触发一个错误异常。
Exception Number: 异常代号,标志着正在处理的异常。
2.2.异常/中断掩码寄存器(PRIMASK, FAULTMASK, 和BASEPRI)
- 仅在privileged的情况下访问。每个异常/中断都有一个优先级,取值越小优先级越高。默认情况下,这些寄存器的值都为0,所有的中断都是关闭的。
- PRIMASK只有最低位有定义,用于打开或屏蔽除复位、NMI(Non-Maskable Interrupt, 不可屏蔽中断)以及硬件错误异常(HardFault Exception)外的所有异常和中断。 同时会把当前正在处理的中断或者异常的优先级提升到level 0,可屏蔽中断的最高优先级。
- PRIMASK通常用于执行一些对时序要求严格的任务时,暂时关闭所有中断,实时程序处理完毕后清除PRIMASK,重新开启中断。
- FAULTMASK与PRIMADSK类似,并且它可以关闭硬件异常(HardFault Exception),同时把当前异常的优先级提升至level -1。 和PRIMASK不同的是,当中断服务程序返回后FAULTMASK将会自动清除。
- 为了提供灵活的中断屏蔽机制,ARMv7-M架构提供了BASEPRI寄存器,可以根据优先级屏蔽中断或者异常。 BASEPRI寄存器的可用位长由具体的CPU厂商实现,Cortex-M4通常有8个或者16个优先级,因此通常为3位或者4位。 BASEPRI中的值为0时,没有作用。当BASEPRI非零时,将屏蔽具有相同和更低优先级的中断。(中断优先级号低于阈值不屏蔽,等于或高于阈值屏蔽)
- CMSIS-Core定义了一系列的C语言函数用来访问这三个中断屏蔽寄存器:
x = __get_BASEPRI();
x = __get_PRIMASK();
x = __get_FAULTMASK();
__set_BASEPRI(x);
__set_PRIMASK(x);
__set_FAULTMASK(x);
__disable_irq(); // 置位PRIMASK,关闭中断
__enable_irq(); // 清除PRIMASK,开启中断
也可以通过汇编语句访问这些寄存器:
MRS r0, BASEPRI ; 读BASEPRI值到r0
MRS r0, PRIMASK ; 读PRIMASK的值到r0
MRS r0, FAULTMASK ; 读FAULTMASK的值到r0
MSR BASEPRI, r0 ; 写r0的值到BASEPRI
MSR PRIMASK, r0 ; 写r0的值到PRIMASK
MSR FAULTMASK, r0 ; 写r0的值到FAULTMASK
另外,更改处理器状态(Change Processor State)指令提供了用于修改
PRIMASK和FAULTMASK的简单指令:
CPSIE i ; 清除PRIMASK,使能中断
CPSID i ; 置位PRIMASK,关闭中断
CPSIE f ; 清除FAULTMASK,使能中断
CPSID f ; 置位FAULTMASK,关闭中断
2.3.控制寄存器(CONTROL)
- CONTROL寄存器主要用来选择栈空间指针寄存器。
- CONTROL寄存器只能够在系统具有privileged级权限下被修改,它可以在任何情况下被读取。
系统复位后,CONTROL的值为0,标识着系统以MSP作为栈空间指针寄存器,而且具有privileged级的访问权限。priveleged的应用程序可以通过写CONTROL寄存器,切换栈空间指针和系统的访问权限。
但是一旦nPRIV置位后,系统将不能对CONTROL寄存器进行写访问。也就是说,在unpriveleged级权限下的系统不能自由地变更系统的访问权限和栈空间指针。这对于系统的安全性而言是有必要的。
如果必须切换回privileged的权限,则需要借助中断机制。在中断服务程序中清除nPRIV位,中断服务程序返回后系统就具有了privileged的权限。
对于使用CMSIS接口的处理器,我们可以通过如下的C语言代码访问CONTROL寄存器:
x = __get_CONTROL();
__set_CONTROL(x);
或者汇编的形式:
MRS r0, CONTROL ; 读CONTROL中的数据到r0中
MSR CONTROL, r0 ; 写r0的数据到CONTROL中
在对CONTROL寄存器进行写操作时,有两点需要注意:
- 对于有FPU的Cortex-M4处理器,执行浮点数指令时,FPCA位会被自动置位。 如果写操作清除了该位,紧接着产生了中断的话,浮点运算单元中的数据将不会被保存,在中断处理过程中可能修改相关的寄存器,导致中断返回时数据不能正常恢复。
- 修改了CONTROL寄存器后,需要执行一个Instruction Synchronization Barrier(ISB)指令, 以保证修改能够应用到以后需要执行的代码中。 而Cortex-M4做了简化,不执行这一操作也没有影响。
3.常用汇编指令
ldr:通常都是作加载指令的,但是它也可以作伪指令,通常有两种不同的表示:
1)ldr pc, =MyHandleIRQ 表示将MyHandleIRQ地址放入pc寄存器中,相当于PC=MyHandleIRQ 。
例如:
ldr r0,=label
//用于加载立即数或一个地址值到指定寄存器中
//如果label是立即数: ldr r0,=0x123 ;将0x123存入r0中
//如果label是个标识符: ldr r0,=label_1 ;将label_1所指向的地址值存入r0中
2)ldr pc,MyHandleIRQ 表示将 MyHandleIRQ地址中的值放入pc寄存器中,相当于PC=*(MyHandleIRQ )。
例如:
ldr r0,[r1] //将r1中的值存到r0中
ldr r1,[r2,#16] //将(r2+16)地址中的内容存到r1中
ldr r1,[r2],#4 //将r2地址中的内容存到r1中,同时r2=r2+4
3)LDMIA R1!,{R0,R4-R12}
LDMIA 中的 IA表示每次访问后递增地址,LD表示加载(load)
R1后面的感叹号“!”表示会自动调节 R1里面存的指针
该代码段表示:任务栈R1的存储地址由低到高,将R1存储地址里面的内容手动加载到 CPU 寄存器 R0,R4-R12里面;
4)STMDB R1!, {R0,R4-R12}
与3)指令相反,ST表示存储(store),D表示decrease,B表示before,
该代码段表示:R1的存储地址由高到低递减,将R0,R4-R12里的内容存储到R1任务栈里面。