文章目录
前言
学习cortex-m3和m4系列芯片的寄存器,看这篇文章就够了。
一、寄存器组
cortex-m3与cortex-m4处理器的寄存器组中有16个寄存器,其中13个为32位通用目的寄存器,其他3个则有特殊用途。
1.R0-R12
寄存器R0-R12为通用目的寄存器,前8个(R0-R7)也被称为低寄存器。由于指令中可用的空间有限,许多16位指令只能访问低寄存器。高寄存器(R8-R12)则可以用于32位指令和几个16位指令,如MOV。R0-R12的初始值是未定义的。
2.R13,栈指针(SP)
R13 为栈指针,可通过 PUSH 和 POP 操作实现栈存储的访问。物理上存在两个栈指针:主栈指针(MSP,有些 ARM 文献也称其为 SP main)为默认的栈指针,在复位后或处理器处于处理模式时,其会被处理器选择使用。另外一个栈指针名为进程栈指针(PSP,有些ARM 文献也称其为 SP_process),其只能用于线程模式。栈指针的选择由特殊寄存器 CONTROL 决定。对于一般的程序,这两个寄存器只会有一个可见。
MSP 和 PSP 都是 32位的,不过栈指针(MSP或PSP)的最低两位总是为0,对这两位的写操作不起作用。对于 ARM Cortex-M 处理器,PUSH 和 POP 总是 32位的,栈操作的地址也必须对齐到32位的字边界上。
大多情况下,若应用不需要嵌入式 OS,PSP 也没必要使用。许多简单的应用可以完全依
赖于 MSP,一般在用到嵌人式 OS时才会使用 PSP,此时 OS内核同应用任务的栈是相互独立
的。PSP的初始值未定义,而MSP的初始值则需要在复位流程中从存储器的第一个字中
取出。
3.R14,链接寄存器(LR)
R14 也被称作链接寄存器(LR),用于函数或子程序调用时返回地址的保存。在函数或子程序结束时,程序控制可以通过将1R的数值加载程序计数器(PC)中返回调用程序处并继续执行。当执行了函数或子程序调用后,LR的数值会自动更新。若某函数需要调用另外一个函数或子程序,则它需要首先将 LR的数值保存在栈中,否则,当执行了函数调用后,LR的当前值会丢失。
在异常处理期间,LR也会被自动更新为特殊的 EXC RETURN(异常返回)数值,之后该数值会在异常处理结束时触发异常返回。本书第8章将会对这方面进行更加深入的介绍。尽管 Cortex-M 处理器中的返回地址数值总是偶数(由于指令会对齐到半字地址上,因此,第0位为 0),LR的第0位为可读可写的,有些跳转/调用操作需要将 LR(或正使用的任何寄存器)的第0 位置1以表示 Thumb 状态。
4.R15,程序计数器(PC)
R15 为程序计数器(PC),是可读可写的,读操作返回当前指令地址加4(由于设计的流水线特性及同 ARM7TDMI处理器兼容的需要)。写PC(例如,使用数据传输/处理指令)会引起跳转操作。
由于指令必须要对齐到半字或字地址,PC的最低位(LSB)为0。不过,在使用一些跳转/读存储器指令更新 PC 时,需要将新 PC 值的 LSB 置1以表示 Thumb 状态,否则就会由于试图使用不支持的 ARM 指令(如 ARM7TDMI中的 32 位 ARM 指令)而触发错误异常。对于高级编程语言(包括C和C++),编译器会自动将跳转目标的LSB 置位。
多数情况下,跳转和调用由专门的指令实现,利用数据处理指令更新 PC的情况较为少
见。不过,在访问位于程序存储器中的字符数据时,PC的数值非常有用,因此,会经常发现存
储器读操作将PC作为基地址寄存器,而地址偏移则由指令中的立即数生成。
5.程序中使用的寄存器名
对于多数汇编工具,在访问寄存器组中的寄存器时可以使用多种名称。在一些汇编工具中,如 ARM 汇编(被 DS-5 Professional 和 Keil MDK-ARM 支持),可以使用大写、小写或者大小写混合。
二、特殊寄存器
除了寄存器组中的寄存器外,处理器中还存在多个特殊寄存器。这些寄存器表示处理器状态、定义了操作状态和中断/异常屏蔽。在使用C等高级编程语言开发简单的应用时,需要访问这些寄存器的情形不多。不过,在开发嵌入式 OS或需要高级中断屏蔽特性时,就要访问它们。
特殊寄存器未经过存储器映射,可以使用 MSR 和 MRS等特殊寄存器访问指令来进行访问。
MRS<reg>,<special reg>;将特殊寄存器读入寄存器
MSR<special reg>,<reg>;写入特殊寄存器
CMSIS-Core也提供了几个用于访问特殊寄存器的C函数。不要把特殊寄存器和其他微控制器架构中的“特殊功能寄存器(SFR)”搞混淆了,它们一般指的是用于 I/O控制的寄存器。
1.程序状态寄存器
程序状态寄存器包括以下三个状态寄存器:
- 应用PSR(APSR)
- 执行PSR(EPSR)
- 中断PSR(IPSR)
这三个寄存器可以通过一个组合寄存器访问,该寄存器在有些文献中也被称作 xPSR。对于 ARM 汇编器,在访问xPSR 时使用的是 PSR。例如:
MRS rO, PSR;读组合程序状态字
MSR PSR, rO;写组合程序状态字
还可以单独访问每个 PSR 。例如:
MRS rO, APSR;将标志状态读入 RO
MRS rO, IPSR;读取异常/中断状态
MSR APSR, rO;写标志状态
注意
- 软件代码无法直接使用 MRS(读出为 0)或 MSR 直接访问 EPSR。
- IPSR 为只读的,可以从组合PSR(xPSR)中读出。
注意,APSR 和 EPSR 的一些位域在 ARMv6-M 架构(如 Cortex-M0)中是不可用的,且它们和ARM7TDMI等经典的 ARM 处理器之间也存在很大的差异。若和 ARM7的当前程序状态寄存器(CPSR)相比较,可能会发现 ARM7 中的某些位域已经不存在了。由于 CortexM3 中没有 ARM7 中定义的操作模式,因此模式位(M)也就给去掉了。Thumb(T)位被移到了第 24 位,中断状态(1和F)位被新的中断屏蔽寄存器(PRIMASK)代替,已经从 PSR 中分离出来。
2.PRIMASK、FAULTMASK 和 BASEPRI 寄存器
PRIMASK、FAULTMASK 和 BASEPRI寄存器都用于异常或中断屏蔽,每个异常(包括中断)都具有一个优先等级,数值小的优先级高,而数值大的则优先级低。这些特殊寄存器可基于优先等级屏蔽异常,只有在特权访问等级才可以对它们进行操作(非特权状态下的写操作会被忽略,而读出则会返回 0)。它们默认全部为0,也就是屏蔽(禁止异常/中断)不起作用。
PRIMASK寄存器为1位宽的中断屏蔽寄存器。在置位时,它会阻止不可屏蔽中断(NMI)和 HardFault 异常之外的所有异常(包括中断)。实际上,它是将当前异常优先级提升为0,这也是可编程异常/中断的最高优先级。
PRIMASK 最常见的用途为,在时间要求很严格的进程中禁止所有中断,在该进程完成后,需要PRIMASK 清除以重新使能中断。
FAULTMASK 和 PRIMASK 非常类似,不过它还能屏蔽 HardFault 异常,它实际上是将异常优先级提升到了-1。错误处理代码可以使用FAULTMASK以免在错误处理期间引发其他的错误(只有几种)。例如,FAULTMASK 可用于旁路 MPU 或屏蔽总线错误(这些都是可配置的),这样,错误处理代码执行修复措施也就更加容易了。与PRIMASK 不同,FAULTMASK 在异常返回时会被自动清除。
为使中断屏蔽更加灵活,ARMv7-M架构还支持BASEPRI,该寄存器会根据优先等级屏蔽异常或中断。BASEPRI的宽度取决于设计实际实现的优先级数量,这是由微控制器供应商决定的。大多数 Cortex-M3 或 Cortex-M4 微控制器都有8个(3 位宽)或 16 个可编程的异常优先级,此时,BASEPRI的宽度就相应地为3位或4位。BASEPRI为0时就不会起作用当被设置为非0数值时,它就会屏蔽具有相同或更低优先级的异常(包括中断),而更高优先级的则还可以被处理器接受。
CMSIS-Core 提供了多个C函数用于访问 PRIAMSK、FAULTMASK及 BASEPRI 寄存器(注意,这些寄存器只能在特权等级下访问)。
X=_get_BASEPRI();//读BASEPRI寄存器
X=_get_PRIMARK();//读PRIMASK寄存器
X =_get_FAULTMASK();//读FAULTMASK寄存器
_set_BASEPRI(x);//设置 BASEPRI 的新数值
_set_PRIMASK(x);//设置 PRIMASK的新数值
_set_FAULTMASK(x);//设置 FAULTMASK 的新数值
_disable_irq();//设置PRIMASK,禁止IRQ
_enable_irg();//清除PRIMASK,使能 IRO
还可以使用汇编代码访问这些异常屏蔽寄存器:
MRS rO, BASEPRI;将BASEPRI寄存器读入RO
MRS rO,PRIMASK;将PRIMASK寄存器读入RO
MRS rO,FAULTMASK;将FAULIMASK寄存器读入RO
MSR BASEPRI,rO;将R0写人BASEPRI 寄存器
MSR PRIMASK, rO;将R0写人PRIMASK寄存器
MSR FAULTMASK, rO;将R0写人FAULTMASK寄存器
另外,利用修改处理器状态(CPS)指令,可以很方便地设置或清除PRIMASK 和FAULTMASK 的数值。
CPSIE i;使能中断(清除PRIMASK)
CPSID i;禁止中断(设置 PRIMASK)
CPSIE f;使能中断(清除 FAULTMASK)
CPSID f;禁止中断(设置 FAULTMASK)
注意 FAULTMASK和BASEPRI寄存器在ARMv6-M中不存在(如 Cortex-MO)。
3.CONTROL 寄存器
CONTROL 寄存器定义了:
- 栈指针的选择(主栈指针/进程栈指针)。
- 线程模式的访问等级(特权/非特权)。
另外,对于具有浮点单元的 Cortex-M4 处理器,CONTROL, 寄存器中有一位表示当前上
下文(正在执行的代码)是否使用浮点单元。
注意 对于 ARMv6-M,nPRIV和非特权等级的实现是和设计实现相关的,而且在最初的Cortex-M0 和 Cortex-M1 产品上是不可用的,其在 Cortex-M0+上则是可选的。
CONTROL寄存器只能在特权访问等级进行修改操作,而读取操作则在特权和非特权访问等级都可以。
复位后,CONTROL寄存器默认为0,这也就意味着处理器此时处于线程模式、具有特权访问权限以及使用主栈指针。通过写 CONTROL寄存器,特权线程模式的程序可以切换栈指针的选择或进人非特权访问等级。不过,nPRIV(CONTROL的第0位)置位后,运行在线程模式的程序就不能访问CONTROL 寄存器了。
运行在非特权等级的程序无法再切换回特权访问等级,这样就提供了一个基本的安全模型。例如,嵌入式系统中可能会具有运行在非特权访问等级的不受信任的应用,这些应用的访问权限就需要受到限制,以免不可靠的程序引起整个系统的崩溃。
若有必要将处理器在线程模式切换回特权访问等级,则需要使用异常机制。在异常处理期间,处理程序可以清除 nPRIV 位。在返回到线程模式后,处理器就会进人特权访问等级。
若使用嵌入式 OS,每次上下文切换时都可以重新编程 CONTROL,寄存器,以满足应用间不同的特权访问等级需要。
nPRIV 和 SPSEL,的设置共有4种组合方式,其中3种在实际应用中较为常见。
对于未使用嵌入式 OS 的多数简单应用,无须修改 CONTROL寄存器的数值。整个应用可以运行在特权访问等级并且只使用 MSP。
要利用 C语言访问 CONTROL,寄存器,可以使用符合 CMSIS 的设备驱动库提供的以下函数:
x=_get_CONTROL();//读取 CONTROL 寄存器的当前值
_set_CONTROL(x);//设置 CONTROL 寄存器的数值为x
在修改 CONTROL 寄存器的值时需要注意以下两点:
- 对于具有浮点单元(FPU)的 Cortex-M4 处理器,或具有 FPU 的 ARMv7-M 处理器,由于浮点单元的存在,FPCA 位会自动置位。若程序中包含浮点运算但FPCA 位被意外清除,而且接下来产生了一个中断,那么浮点单元寄存器中的数据将不能在异常入口流程保存,且可能会被中断处理覆盖。在这种情况下,继续执行被中断的任务时,程序可能无法继续正确地处理。
- 在修改了 CONTROL, 寄存器后,从架构来看,应该使用指令同步屏障(ISB)指令(或符合 CMSIS的设备驱动库中的–ISB()函数),以确保本次修改对接下来的代码能起到作用。由于 Cortex-M3、Cortex-M4、Cortex-M0+、Cortex-M0 以及 Cortex-M1 的流水线非常简单,不使用 ISB 指令也不会引起什么问题。
要用汇编访问 CONTROL 寄存器,可以使用 MRS 和 MSR 指令:
MRS r0,CONTROL ;将 CONTROL 寄存器读入 RO
MSR CONTROL,rO ;将 R0 写入 CONTROL 寄存器
可以通过检查 CONTROL 和 IPSR 的数值来确定当前是否为特权等级。
int in_privileged(void)
{
if( _get_IPSR()!=0)return 1;//True
else
if(( _get_CONTROL()&0x1)==0)return 1;//True
else return 0; //False
}
4.浮点寄存器
Cortex-M4具有可选的浮点单元,其提供了浮点数据处理用的一些寄存器以及浮点状态和控制寄存器(FPSCR)。
1.S0~S31 和 D0~D15
S0~S31(S)都为 32位寄存器,而且每个都可以通过浮点指令访问,或者利用符号 DO~D15(D代表双字/双精度)成对访问。例如,S0和S1成对组成 D0,而S3和 S2 则成对组成D1。尽管 Cortex-M4中的浮点单元不支持双精度浮点运算,在传输双精度数据时仍可使用浮点指令。
2.浮点状态和控制寄存器(FPSCR)
由于下面的几个原因,FPSCR 中包含多个位域:
- 定义一些浮点运算动作。
- 提供浮点运算结果的状态信息。
浮点控制默认被配置为符合 IEEE 754单精度运算。在普通应用中,浮点运算控制的设置也无须修改。
注意 软件可以利用 FPSCR 中的异常位检测浮点运算中的异常。
3.经过存储器映射的浮点单元控制寄存器
除了浮点单元寄存器组和 FPSCR,浮点单元还往系统中引人了一些经过存储器映射的寄存器,如用于使能和禁止浮点单元的协处理器访问控制寄存器(CPACR)。浮点单元默认被禁止以降低功耗,在使用任何浮点指令前,都必须通过编程 CPACR 寄存器来使能浮点单元。
在使用符合CMSIS的设备驱动库的C编程环境中:
SCB->CPACR|=0xF<<20;//使能对FPU的全访问
在汇编语言编程环境中,可以使用下面的代码:
LDR RO,=0xE000ED88 ;R0 被设置为 CPACR 的地址
LDR R1,= 0x00F00000 ;R1=0xF<<20
LDR R2 [RO] ;读取 CPACR的当前值
ORRS R2,R2,R1 ;置位
STR R2,[RO] ;将修改后的数值写回 CPACR
三. 应用程序状态寄存器
APSR 中包含下面的几组状态标志:
- 整数运算的状态标志(N-Z-C-V位)
- 饱和运算的状态标志(Q位)
- SIMD运算的状态标志(GE位)
1.整数状态标志
整数状态标志和其他许多处理器架构中的 ALU 状态标志类似,它们受普通数据处理指令的影响,在控制条件跳转和条件执行时非常有用。另外,APSR 标志之一,也就是C(进位)位,也可用于加法和减法运算中。
Cortex-M 处理器中共存在4个整数标志。
对于 ARMv7-M 和 ARMv7E-M架构,多数 16 位指令会影响这4个 ALU 标志。对于多数 32 位指令,指令编码中的一个位定义了是否应该更新 APSR 标志。注意,部分指令不会更新 V标志和℃标志。例如,MULS(乘法)指令只会修改 N标志和乙标志。
除条件跳转或条件执行代码,APSR的进位标志也可用于将加法和减法运算扩大为超过32位。例如,在将两个 64位整数相加时,可以将低 32位加法运算的进位标志作为高 32 位加法的一个输人:
//计算Z=X+ Y,其中X、Y和Z都是 64 位的
Z[31:0]=x[31:0]+ Y[31:0];//低字相加,更新进位标志
Z[63:32]=X[63:32]+ Y[63:32]+ Carry;//高字相加
ARM 处理器都具有 N-Z-C-V 标志,其中包括 Cortex-M0 处理器。
2.Q状态标志
Q表示饱和算术运算或饱和调整运算过程中产生了饱和,其存在于ARMv7-M(如Cortex-M3 和 Cortex-M4 处理器),但在 ARMv6-M(如 Cortex-M0 处理器)中不可用。在该位被设置后,以及软件写 APSR 清除 Q位之前,它会一直保持置位状态,饱和算术/调整运算不会清除该位。因此,可以在饱和算术/调整运算流程结束时,利用该位确定是否产生了饱和,而无须在每步都检查饱和状态。
饱和算术运算对于数字信号处理非常有用,有些情况下,保存计算结果的目的寄存器的位宽度可能会不够,这样就会导致上溢或下溢。若使用一般的数据运算指令,结果的 MSB 就会丢失,从而导致结果产生严重畸变。饱和算术运算并非只是将 MSB 去掉,而是将结果强制置为最大值(上溢的情形)或最小值(下溢的情形),以降低信号畸变的影响。
实际触发饱和的最大值和最小值取决于所使用的指令。多数情况下,饱和运算指令的助记符前都带有 Q,如 QADD16。若产生了饱和,Q位就会置位,否则 Q位的数值就不会改变。
Cortex-M3 处理器提供了一些饱和调整指令,而除这些指令外,Cortex-M4 还支持一整套饱和运算指令。
3.GE位
在 Cortex-M4 中,“大于等于”(GE)位域在 APSR 中占用4位,而 Cortex-M3 处理器中则不存在。许多 SIMD指令都会更新该标志,其中,多数情况下,每个位表示 SIMD 运算的每个字节为正或溢出。对于具有 16 位数据的 SIMD指令,第0和1位由低半字的结果控制,第2和3位则由高半字的结果控制。
SEL 指令,使用 GE 标志,其会基于每个 GE 位复用两个源寄存器的字节数值。在组合使用 SIMD指令和 SEL,指令时,可以在 SIMD 处理中建立简单的条件数据选择,以提高性能。
若需要其他的处理,还可以将 APSR 中的 GE 位读到通用目的寄存器中。
总结
希望通过这篇文章可以对cortex-m3和m4内部寄存器有深入的理解。
参考书籍:《ARM Cortex-M3与Cortex-M4权威指南》