首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

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的值到组合寄存器中

状态寄存器的位定义如下:

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寄存器进行写操作时,有两点需要注意:
  1. 对于有FPU的Cortex-M4处理器,执行浮点数指令时,FPCA位会被自动置位。 如果写操作清除了该位,紧接着产生了中断的话,浮点运算单元中的数据将不会被保存,在中断处理过程中可能修改相关的寄存器, 导致中断返回时数据不能正常恢复。
  2. 修改了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的浮点运算时予以详细介绍。

参考书目

  1. Joseph Yiu. The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors. 3rd, Elsevier, 2014.



Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1