Cortex-M4的寄存器
在处理器内核(core)中有一系列的寄存器用于程序控制和数据处理。 使用C语言写程序时,基本不用考虑处理器的寄存器,因为编译器已经帮忙搞定了。 但是一个嵌入式操作系统就不得不深入到寄存器的细节。
1 Register Bank
在ARM的架构中,处理器不能直接操作内存中数据,必须先把内存中的数据装载进寄存器,处理完后再写回内存。 如果core的寄存器数量充足的话,可以把一些常用的数据临时保存在寄存器中一段时间, 而不用每次都从内存中读取数据,可以有效的提高系统的工作效率。 Cortex-M4有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位指令访问。
R13也被标记为SP,是栈空间指针(stack pointer)。它被PUSH和POP指令用来访问栈空间内存。 物理上存在两个栈空间指针:Main Stack Pointer(MSP,以后翻译为主栈指针,一些文档中称之为SP_main)和 Process Stack Pointer(PSP,进程栈指针,SP_process)。 其中,MSP是默认的栈指针,在系统复位后或者运行在Handler模式下时,通过R13访问的是MSP, 而PSP只能在Thread模式下使用。至于使用的是哪个栈指针由CONTROL寄存器控制,但在同一时间只有一个栈指针工作。 MSP和PSP都是32位的寄存器,但它们的低两位总是0,向其中写1是没有效果的。 因为Cortex-M的栈空间是32位对齐的,PUSH和POP指令也总是32位的。
R14也记为LR,链接寄存器(Link Register)。它被用于在调用函数或者执行子程序时记录返回地址,在调用函数执行完后, CPU根据LR中记录的值返回到原来的程序中。在发生函数调用时,LR的值会被自动更新,如果被调用的函数在执行过程中仍需要调用其他函数, 需要先把LR的值压栈,否则返回地址将丢失。处理中断函数时,LR会被写入一个特别的值EXC_RETURN,具体使用方法在中断处理时讲述。 虽然在Cortex-M4中任何函数的返回地址都一定是偶数,但LR的第0位仍然是可以写1的,标识着Thumb状态以满足一些指令的需要。
R15又称为程序计数器(Program Counter, PC),这是一个可读写的寄存器。 读操作将返回当前指令地址+4,之所以会加4是由于流水线的设计以及对ARM7TDMI的兼容设计。 写操作将导致程序执行地址的跳转。因为指令必须半字或者整字对齐,所以PC的最低位总为0。 但是通过跳转等语句更新PC时,应当保证新PC值的最低位为1,以标识Thumb状态。 使用C语言编程是,不需要考虑这一点,因为编译器已经帮我们完成了这一操作。
2 特殊寄存器
除了上述Register Bank中涉及到的16个寄存器外, Cortex-M4还有一些反应系统状态、控制内核工作模式、异常中断屏蔽掩码等特殊功能的寄存器。 这些特殊的寄存器并没有分配地址映射,可以通过MSR或者MRS读写这些寄存器。
2.1 状态寄存器
系统的状态寄存器(Program Status Register)一共涉及到三个寄存器,分别为: Application PSR(APSR)、Execution PSR(EPSR)和Interrupt PSR(IPSR)。 如表1所示,这三个寄存器可以组合为一个寄存器一起访问,标记为xPSR。
表1 Cortex-M4的状态寄存器
31 | 30 | 29 | 28 | 27 | 26:25 | 24 | 23:20 | 19:16 | 15:10 | 9 | 8 | 7 | 6 | 5 | 4:0 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
APSR | N | Z | C | V | Q | GE* | ||||||||||
IPSR | Exception Number | |||||||||||||||
EPSR | ICI/IT | T | ICI/IT | |||||||||||||
xPSR | N | Z | C | V | Q | ICI/IT | T | GE* | ICI/IT | Exception Number |
GE:只有基于ARMv7E-M架构的处理器才有,例如Cortex-M4。
在ARM的汇编器中,用PSR标识三个状态寄存器的组合。 我们可以单独的访问每个状态寄存器,也可以通过PSR访问他们的组合,参考如下的代码。 但是,需要注意的是,EPSR是不能够直接通过MRS和MSR读写的,而IPSR则是一个只读的寄存器。
MRS r0, PSR ; 读组合寄存器的值到r0中
MRS r0, APSR ; 读应用寄存器的值到r0中
MSR APSR, r0 ; 写r0的值到应用寄存器中
MSR PSR, 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通常用于在处理有实时性要求(time critical)的程序时关闭所有中断,实时程序处理完毕后清除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寄存器主要用来选择栈空间指针寄存器,控制系统的访问权限(privileged/unprivileged)。 我们选用的STM32F407有浮点运算单元(Floating Point Unit,FPU),因此还有一位标识着当前的上下文中是否使用了FPU。 CONTROL寄存器只能够在系统具有privileged级权限下被修改,它可以在任何情况下被读取,表2中列举了CONTROL寄存器的位定义。
表2 CONTROL寄存器的位定义
Bit | 功能 |
---|---|
nPRIV(bit 0) | 定义了Thread模式下系统的访问权限。 nPRIV=0(默认)时,系统具有privileged级的权限, nPRIV=1时,系统具有unprivileged的权限。 |
SPSEL(bit 1) | 定义了栈空间指针寄存器。 SPESEL=0(默认)时,系统使用MSP(Main Stack Pointer), SPSEL=1时,使用PSP(Process Stack Pointer)。 |
FPCA(bit 2) | FPCA(Floating Point Contex Active)用于标识上下文中是否使用了浮点运算单元。 FPCA=0(默认)时,上下文中没有使用过浮点运算单元,因此在上下文切换时不需要保存浮点运算寄存器的值。 FPCA=1时,使用过,需要保存浮点运算寄存器的值。 执行浮点指令时,FPCA会被自动地置1,当系统产生中断或者异常时会由硬件清除。 |
系统复位后, 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做了简化,不执行这一操作也没有影响。
2.4 浮点运算寄存器
F407还提供了一个浮点运算单元(Floating Point Unit, FPU), 它包含32个32位用于数据处理的寄存器和一个状态和控制寄存器(FPSCR)。
用于数据处理的寄存器可以作为单精度浮点数单独访问被标记为S0~S1。 也可以成对的访问,此时则是用作双精度的浮点数标记为D0~D15。S0和S1一起组合成D0,以此类推。 虽然Cortex-M4只支持单精度的浮点数,我们仍然可以使用浮点数指令进行双精度浮点数的转换。
为了描述一些浮点运算行为,指示浮点运算状态,FPSCR定义了一系列的操作和状态位。 默认情况下,浮点运算的行为兼容IEEE 754单精度运算标准。
此外,除了上述的这些数据和状态寄存器外,FPU还需要借助一些系统寄存器。 为了省电,默认系统上电时FPU是关闭的。 在执行任何浮点数操作之前,需要操作CPACR(Coprocessor Access Control Register)打开FPU。
具体的使用方法和更多的关于浮点运算单元的内容我们将在介绍F407的浮点运算时予以详细介绍。