[001] [ARM-Cortex-M3/4] 内部寄存器

ARM
Cortex-M3/4
寄存器组
R0~R12
堆栈指针R13(SP,
Stack Pointer)
连接寄存器R14(LR,
Link Register)
程序计数器R15(PC,
Program Counter)
特殊功能寄存器组
程序状态寄存器
(xPSR 或曰 PSPs)
`APSR`位域
整数运算状态标志
(N-Z-C-V位)
饱和运算状态
标志 (Q位)
SIMD运算状态
标志 (GE位)
`IPSR`位域
`EPSR`位域
中断屏蔽寄存器组
`PRIMASK`
`FAULTMASK`
`BASEPRI`
控制寄存器
(CONTROL)

1 寄存器组

在这里插入图片描述
在Keil调试界面也可以看到:
在这里插入图片描述

1.1 R0~R12

R0~R12为通用目的寄存器,其中:R0-R7为低组寄存器,字长32位,由于指令中可用的空间有限,绝大多数16位指令只能访问低组寄存器;R8-R12为高组寄存器,字长32位,只有很少的16位Thumb指令可以访问它们。

注意:32位Thumb-2指令可用访问所有通用寄存器,R0~R12复位后的初始值未知。

1.2 堆栈指针R13(SP, Stack Pointer)

CM3/4处理器内核中有两个堆栈指针:

  • 主堆栈指针MSP(SP_main):缺省的堆栈指针,它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用,MSP的初始值在复位时从SRAM中的第一个字中取出(向下生长的满栈)。
  • 进程栈指针PSP(SP_process):用于常规的应用程序代码(不处于异常服用例程中时),PSP初始值未定义。

裸机一般只用MSP,用到OS时才会使用PSP。
当引用R13时,引用到的是当前正在使用的那一个,另一个必须用MRS/MSR指令来访问。

堆栈指针的选择由特殊寄存器CONTROL(后面会说到)决定。

堆栈指针访问堆栈时的操作:

堆栈由一块连续的内存和一个栈顶指针组成,用于实现LIFO的缓冲区。典型应用:在数据处理前先保存寄存器的值,任务处理完后再从堆栈中恢复先前保护的值。
OS中上下文切换时的保护现场与恢复现场,就是用堆栈来保存和恢复数据的。

在这里插入图片描述

  • PUSH:把若干寄存器的值压入堆栈中
  • POP:从堆栈中弹出若干的寄存器的值

在执行PUSHPOP时,SP地址寄存器的值由硬件自动调整,以避免后续操作破坏先前的数据。同时PUSHPOP操作必须是4字节对齐的,而且R13的最低两位被硬件连接到0,因此总是读出0。

ARM堆栈是向下生长的满栈,在PUSH新数据时,SP指针先减一个单元(弹栈时SP增加一个单元,即向高地址方向移动)。

通常在进入一个子程序后,第一件事就是把寄存器的值先PUSH压栈,在程序退出后POP弹出之前压入栈中的寄存器。此外,PUSHPOP还能一次操作多个寄存器:

PUSH {R0-R7, R12, R14} 	; 保存寄存器列表(压入R0-R7,R12,R14); 执行处理
POP {R0-R7, R12, R14} 	; 恢复寄存器列表(弹出R0-R7,R12,R14)
BX R14 					; 返回到主调函数

注意:在寄存器列表中,不管寄存器的序号是以什么顺序给出的,汇编器都将把它们升序排序。然后先push序号大的寄存器,所以也就先pop序号小的寄存器。(不按升序写,有些汇编器会报错)

1.3 连接寄存器R14(LR, Link Register)

  • 一般用于在调用子程序时存储其返回地址,当子程序结束时,将LR的值加载到PC中,子程序即返回调用程序处并继续执行。汇编中使用BLBLX指令调用子程序时,LR值会自动被设置为该子程序返回地址。下面两种方式实现子程序返回操作:
MOV	PC, LR	; 把LR值传到PC,程序返回(这条语句相对于C语言return)
BX 	LR		; 跳转到LR保存的原函数地址处

注意:若某函数需要调用另外一个函数时(嵌套调用),它需要先将LR的值保存到栈中,否则,当执行了函数调用后,LR的当前值会丢失。例如:

main 		;主程序
…
BL func1 	; 跳转到func1(跳转前PC=func1, LR=main的下一条指令地址)
…

Func1
… 			; func1 的代码
BX LR 		; 函数返回(如果func1要使用LR,必须在使用前PUSH,否则返回时程序就可能跑飞了)
  • 当异常中断发生时,LR会自动更新为该异常返回地址,之后会在异常处理结束时触发异常返回。

此外,LR也可以当做通过寄存器来使用。

Cortex-M处理器的返回地址总是偶数(由于指令会对齐到半字上,因此第0位始终为0),但LR的第0位为可读写,有些跳转/调用操作需要将LR(或正使用的任何寄存器)的第0位(LSB)置1以表示Thumb状态(0表示ARM状态)。

这是历史遗留问题。现在的ARM处理器已经都采用Thumb指令集了,但为了兼容老的ARMThumb状态并存的处理器,需要允许LRbit[0]可读写。

1.4 程序计数器R15(PC, Program Counter)

表示当前指令地址,写入新值即可跳转,汇编一般写成PC,由于CM3内部使用了指令流水线,读PC时返回的值是当前指令的地址+4。例如:

0x1000: MOV R0, PC 		; R0 = 0x1004

因为指令至少半字(两个字节)对齐,所以PC的最低位(LSB)总为0,但是使用跳转/读存储器的指令更新PC时,需要将PC的LSB置1表示Thumb状态,否则就会由于试图使用不支持的ARM指令而触发fault异常(LSB为0表示ARM状态)。对于高级编程语言(C/C++等),编译器会自动将跳转目标的LSB置位。

ARM汇编可以使用大写、小写或者大小写混合的寄存器名称:
在这里插入图片描述

2 特殊功能寄存器组

在这里插入图片描述
特殊功能寄存器有预定义的功能,必须通过专用的指令MSR/MRS来访问,而且它们也没有与之相关联的访问地址。

  • MRS(Move to Register From Special Register):加载特殊功能寄存器的值到通用寄存器。
  • MSR(Move to Special Register From Register):存储通用寄存器的值到特殊功能寄存器。
MRS <reg>, <special_reg>	; 读特殊功能寄存器的值到通用寄存器
MSR <special_reg>, <reg>	; 写通用寄存器的值到特殊功能寄存器

2.1 程序状态寄存器(xPSR 或曰 PSPs)

Cortex-A7中为CPSR(当前程序状态寄存器),与M3/4有所出入

在这里插入图片描述

注意GE[3:0]仅在Cortex-M4和CortexA/R系列存在,CM3中没有。

程序状态寄存器xPSR包括以下三个状态寄存器:

  • 应用PSR(APSR):记录算术逻辑单元ALU(arithmetic and logic unit)标志
  • 执行PSR(EPSR):执行状态
  • 中断PSR(IPSR):正服务的中断号

ARM汇编器使用PSR同时访问以上三个状态寄存器,同时它们也支持单独访问:

MRS R0 PSR	; 读组合程序状态寄存器值
MSR PSR R0 	; 写组合程序状态寄存器值
-----------------------------------
MRS R0, APSR	; 将状态标志读入到RO
MRS R0, IPSR	; 读取异常/中断状态
MSR APSR, R0	; 写状态标志

汇编代码无法使用MRS/MSR直接访问EPSRIPSR为只读。

此外,还可以组合访问:

IAPSR = IPSR + APSR
IEPSR = IPSR + EPSR
EAPSR = EPSR + APSR

CMSIS-Core还提供了C函数接口(MDK的ACM6编译器可F12跳转查看,在cmsis-armclang中可查看):

uint32_t __get_IPSR(void);
uint32_t __get_APSR(void);
uint32_t __get_xPSR(void);

PSR组合状态寄存器中的位域:

2.1.1 APSR位域

在这里插入图片描述

2.1.1.1 整数运算状态标志 (N-Z-C-V位)
  • N
    负标志(Negative or less than flag)
    当两个补码表示的符号整数运算时,N = 1 表示结果为负数,N = 0 为正数。
  • Z
    零标志(Zero flag)
    Z = 1 表示运算结果为0,Z = 0 表示运算结果不为0;对于CMP比较指令,Z = 1 表示进行比较的两个数大小相等。
  • C
    进位或非借位标志(Carry/borrow flag)
    1.在加法指令中(包括比较指令CMN),当结果产生了进位,则C = 1 ,表示无符号数运算发生上溢出;其他情况C = 0
    2.在减法指令中(包括比较指令CMP),当结果发生错位,则C = 0 ,表示无符号数运算发生下溢出;其他情况C = 1
    3.对于包含移位操作的非加/减法运算指令,C为最后一次被溢出的位的数值;
    4.对于其他非加/减法运算指令,C位的值通常不受影响。
  • V
    溢出标志(oVerflow flag)
    对与加/减法运算指令,当操作数与运算结果为二进制的补码表示的带符号数时,V = 1表示符号位溢出,其他指令不影响V位。

ALU标志运算示例:
在这里插入图片描述
此外,很多16位指令会影响这4个ALU标志,对于32位指令,指令编码中的一个位定义了是否应该更新APSR标志,注意:部分指令不会更新V标志和C标志,例如:MULS(乘法)指令只会修改N标志和Z标志。
除条件跳转或条件执行代码,APSR的进位标志也可以用于将加法和减法的运算扩大为超过32位,例如,将两个64位整数相加时,可以将低32位加法运算结果的进位标志作为高32位加法的一个输入:

// 计算 Z = X + Y,其中X, Y, Z均为64位
Z[31:O] = X[31:0] + Y[31:0]; 				//低字相加,更新进位标志
Z[63:32] = X[63:32] + Y[63:32] + Carry;	 	//高字相加
2.1.1.2 饱和运算状态标志 (Q位)

用于指示增强的DSP指令是否发生溢出(饱和),该位被设置后,以及在软件写APSR清除Q位前,它会一直保持置位状态,饱和运算/调整运算不会清除该位。因此,可以在饱和运算/调整运算结束时,利用该位确定是否发生了饱和,而无须每步都检查饱和状态。

饱和算术运算对于数字信号处理非常有用,有些情况下,保存计算结果的目的寄存器的位宽度可能会不够,这样就会导致上溢或下溢。 若使用一般的数据运算指令,结果的MSB就会丢失,从而导致结果产生严重畸变。 饱和算术运算并非只是将MSB去掉,而是将结果强制置为最大值(上溢的情形)或最小值(下溢的情形),以降低信号畸变的影响。
实际触发饱和的最大值和最小值取决于所使用的指令。 多数情况下,饱和运算指令的助记符前都带有Q,如QADD16。 若产生了饱和,Q位就会置位,否则 Q位的数值 就不会改变。
Cortex-M3处理器提供了一些饱和调整指令,而除这些指令外,Cortex-M4还支持一整套饱和运算指令。

有符号和无符号的饱和:
在这里插入图片描述

2.1.1.3 SIMD运算状态标志 (GE位)

在Cortex-M4中,大于等于标志(GE)在APSR中占用4位,而Cortex-M3处理器中则不存在。许多SIMD指令(single instruction multiple data,单指令流多数据流)都会更新该标志,其中,多数情况下,每个位表示SIMD运算的每个字节为正或溢出。对于具有16位数据的SIMD指令,第0和1位由低半字的 结果控制,第2和3位则由高半字的结果控制。

2.1.2 IPSR位域

在这里插入图片描述
表示处理器正在处理的异常编号,共8位:
在这里插入图片描述

2.1.3 EPSR位域

在这里插入图片描述

2.2 中断屏蔽寄存器组

在这里插入图片描述
三个寄存器常用于控制异常或中断的使能和除能。每个异常都有优先级,数值越小优先级越高,这些特殊寄存器可基于优先级屏蔽异常,只有在特权访问模式才可以对它们操作,用户模式写操作会被忽略,读返回0。

可以用MRS/MSR指令来读写它们。

2.2.1 PRIMASK

PRIMASK只有1位,缺省值为0,被置1后,它会阻止除NMI和HardFault异常之外的所有异常(包括中断),实际上,它是将当前异常优先级提升为0(可编程中断/异常最高优先级)。

在RT-Thread中,就是通过其来开关中断以保护临界资源(中断锁)。
为了快速关中断,CM3还专门设置了一条CPS(修改处理器状态)指令,用法如下:

CPSID I 	;PRIMASK=1 关中断
CPSIE I 	;PRIMASK=0 开中断

MSR/MRS指令操作:

MOVS R0, #1			; 向PRIMASK写1禁止所有中断
MSR	PRIMASK, R0

MOVS R0, #0			; 向PRIMASK写0使能中断
MSR	PRIMASK, R0

MRS R0, PRIMASK		; 将PRIMASK读入R0

CMSIS-Core提供的C函数接口

void __disable_irq(); 					// PRIMASK=1 关中断
void __enable_irq(); 					// PRIMASK=0 开中断
void __set_PRIMASK(uint32_t priMask);	// 设置PRIMASK
uint32_t __get_PRIMASK(void);			// 读取PRIMASK

PRIMASK被置位时,所有的错误事件都会触发HardFault异常,而不论相应的可配置错误异常是否使能。

2.2.2 FAULTMASK

FAULTMASK只有1位,与PRIMASK类似,缺省值为0,被置1后,只有NMI能够响应,HardFault异常也被屏蔽掉,实际上是将异常优先级提升到-1,这样可以使用HardFault的一些特殊特性:

  • 旁路MPU。
  • 忽略用于设备/存储器探测的数据总线错误。

这样FAULTMASK可在配置错误处理执行期间,阻止其他异常或中断处理的执行。(不可以在NMIHardFault中设置它)

PRIMASK不同,FAULTMASK在异常返回时会被自动清除(从NMI退出除外)。由于这个特点,FAULTMASK就有了 个很有趣的用法:若要在低优先级的异常处理中触发一个高优先级的异常(NMI除外),但想在低优先级处理完成后再处理器高优先级,可以:

  • 设置FAULTMASK禁止所有中断和异常(NMI除外)
  • 设置高优先级中断或异常的挂起状态
  • 退出处理

由于在FAULTMASK置位时,挂起的高优先级异常处理无法执行,高优先级的异常就会在FAULTMASK被清除前继续保待挂起状态,低优先级处理完成后才会将其清除。 因此,可以强制让高优先级处理在低优先级处理结束后开始执行。

CPS指令:

CPSID F 	;FAULTMASK=1 关异常
CPSIE F 	;FAULTMASK=0 开异常

MSR/MRS指令:

MOVS R0, #1				; 向FAULTMASK写1禁止所有中断
MSR	FAULTMASK, R0

MOVS R0, #0				; 向FAULTMASK写0使能中断
MSR	FAULTMASK, R0

MRS R0, PRIMASK			; 将FAULTMASK读入R0

CMSIS-Core提供的C函数:

void __disable_fault_irq(); 				// FAULTMASK=1 关异常
void __enable_fault_irq(); 					// FAULTMASK=0 开异常
void __set_FAULTMASK(uint32_t faultMask);	// 设置FAULTMASK
uint32_t __get_FAULTMASK(void);				// 读取FAULTMASK

FAULTMASK专门留给OS用

2.2.3 BASEPRI

BASEPRI寄存器会根据优先级屏蔽异常或中断,最多有9位,其位宽取决于设计表达优先级的位数,大多数CM3/4的MCU都有8/16个可编程的异常优先级,此时BASEPRI位宽就为3/4位,如STM32F103中,有4位用于表达优先级:
在这里插入图片描述
可以通过NVICSCB寄存器来配置优先级。

当需要禁止优先级低于某特定等级的中断时,将屏蔽优先级写入到BASEPRI寄存器即可。例如,若要屏蔽优先级小于等于0x60的所有异常:

  • CMSIS-Core C函数
void __set_BASEPRI(uint32_t basePri);
__set_CONTROL(0x60);		// 禁止优先级在0x60~0xFF的中断
// 读取BASEPRI
int32_t __get_BASEPRI(void);
// 取消屏蔽
__set_CONTROL(0);	// 写0即可
  • 汇编
MOVS RO, #0x60		; 禁止优先级在0x60~0xFF的中断
MSR BASEPRI, R0	

MRS R0, BASEPRI		; 读取BASEPRI

MOVS RO, #0			;取消屏蔽
MSR BASEPRI, R0	

此外,BASEPR寄存器可用过别名BASEPR_MAX访问,当使用这个名称时,会得到一个条件写操作,处理器会自动比较当前值与新的数值,只有新的优先级更高才会允许修改。(修改更低的优先级使用BASEPR名称)

  • CMSIS-Core C函数
// 当BASEPRI被屏蔽/禁用或添加新值到BASEPRI时,可调用以下函数
void __set_BASEPRI_MAX(uint32_t basePri);
  • 汇编
MOVS RO, #0x60		; 禁止优先级在0x60~0xFF的中断
MSR BASEPR_MAX, R0	

MOVS RO, #0xF0		; 优先级低于上次0x60(越小越高), 写操作不起作用
MSR BASEPR_MAX, R0	

MOVS RO, #0x40		; 禁止优先级在0x40~0xFF的中断, 写操作修改成功
MSR BASEPR_MAX, R0

BASEPR寄存器格式与优先级寄存器宽度有关,若表达优先级的位数为3位,则BASEPR可被设为:0x00, 0x20, 0x40,… ,0xC0和0xE0。

2.3 控制寄存器(CONTROL)

CONTROL仅在特权级下才允许写操作,而读操作特权和非特权访问都可以。
在这里插入图片描述

  • CONTROL[0]:定义线程模式中的特权等级
    0=特权级的线程模式,1=用户级的线程模式。
    一但进入用户级,唯一返回特权级途径就是触发一个软中断,再有中断服务例程改写此位。(handler模式永远都是特权级的)
  • CONTROL[1]:定义栈指针的选择
    0=选择主堆栈指针MSP),1=选择进程堆栈指针PSP。
    特权级线程可使用MSP和PSP,用户级线程仅可使用PSP,handler模式只允许使用MSP(此时不应该往该位写1)。因此,只有特权级的线程模式下才可以写此位。
  • CONTROL[2]:异常处理机制使用该位确定异常产生是浮点单元中的寄存器是否需要保存
    当该位为1时,当前的上下文使用浮点指令,则需要保存浮点寄存器。该位会在执行浮点指令时自动置位,在异常入口被硬件清除。
    该位仅存在具有FPU的Cortex-M4的中
    在这里插入图片描述
    复位后,CONTROL值全为0,即处于特权级的线程模式,使用主堆栈指针MSP。此时可以写CONTROL寄存器,但是当bit[0]被置1后,处于用户级的线程模式就不能访问CONTROL了。此时需利用异常机制,在异常处理期间清除该位,返回特权级(异常服务函数永久处于特权级):
    在这里插入图片描述

CONTROL[0]bit[0]bit[1]的组合:
在这里插入图片描述
一般不使用RTOS,则无须修改CONTROL值,整个应用运行在特权级的线程模式,且只使用MSP:
在这里插入图片描述
操作CONTROL寄存器:

  • CMSIS-Core C函数
uint32_t __get_CONTROL(void);
void __set_CONTROL(uint32_t control);
  • 汇编
MRS RO, CONTROL		; 将CONTROL读入RO
MSR CONTROL, R0		; 将RO写入CONTROL

可以通过检查IPSRCONTROL寄存器数值确认是否为特权级:

int in_privileged(void)
{
	if(__get_IPSR() != 0)	// 判断是否在异常服务例程中(特权级)
		return true;
	else 
		if(__get_CONTROL() & 0x1 == 0)	// 仅特权级可使用MSP
			return true;
		else
			return false;
}

参考:计算机原理与应用 第二章——ARM处理器_南瑾与春风的博客-程序员秘密

END

  • 14
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柯西的彷徨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值