深入理解用于中断控制的特殊寄存器
之前在介绍 Cortex-M3 特殊寄存器的文章中提到了中断/异常屏蔽寄存器,具体可参见下文:
资深嵌入式工程师的自我修养 —— Cortex-M3 特殊寄存器
其中包括 PRIMASK、FAULTMASK 和 BASEMASK 寄存器。这些寄存器都用于异常或中断的屏蔽,本文就来详细理解下这些寄存器的操作细节。
PRIMASK
在许多应用中,可能都需要暂时禁止所有中断以执行一些时序关键的任务,此时就可以使用 PRIMASK 寄存器。需要注意的是 PRIMASK 寄存器只能在特权等级下访问。
PRIMASK 用于禁止除了 NMI 和 HardFault 外的所有异常,它实际上是将当前优先级改为 0(最高的可编程等级)。如果用 C 编程,可以使用 CMSIS-Core 提供的函数来设置和清除 PRIMASK:
void __enable_irq(); // 清除 PRIMASK
void __disable_irq(); // 设置 PRIMASK
void __set_PRIMASK(uint32_t priMask); // 设置 PRIMASK 为特定值
uint32_t __get_PRIMASK(void); // 读取 PRIMASK 的数值
对于汇编编程,可以利用 CPS(修改处理器状态)指令修改 PRIMASK 寄存器数值。
CPSIE I ; 清除 PRIMASK (使能中断)
CPSID I ; Set PRIMASK (禁止中断)
PRIMASK 寄存器还可通过 MRS 和 MSR 指令访问,例如:
MOVS R0, #1
MSR PRIMASK, R0 ;将 1 写入 PRIMASK 禁止所有中断
以及:
MOVS R0, #0
MSR PRIMASK, R0 ;将 0 写入 PRIMASK 以使能中断
当 PRIMASK 置位时,所有的错误事件都会触发 HardFault 异常
FAULTMASK
从行为来说,FAULTMASK 和 PRIMASK 很类似,只是它实际上会将当前优先级修改为 -1,这样即使是 HardFault 也会被屏蔽。当 FAULTMASK 置位时,只有 NMI 异常处理才能执行。
从用法来说,FAULTMASK 用于将配置的错误处理(如 MemManage、总线错误和使用错误)的优先级提升到 -1(这里指的是在某个错误处理运行期间,置位 FAULTNASK,此时这个错误处理的优先级就被提升到了 -1 ,并且在此期间其他错误处理都不会响应),这样,这些处理就可以使用 HardFault 的一些特殊性。其中包括:
-
旁路 MPU
-
忽略用于设备/内存探测的数据总线错误
将优先级提升到 -1 后,FAULTMASK 可在可配置的错误处理执行期间,阻止其他异常或中断处理的执行。(关于错误处理的更深入概念会在以后的文章中详细说明)
FAULTMASK 寄存器只能在特权状态访问,不过不能在 NMI 和 HardFault 处理中设置。若在 C 语言编程中使用符合 CMSIS 的设备驱动,则可以使用下面的 CMSIS-Core 函数来设置和清除 FAULTMASK:
void __enable_fault_irq(void); // 清除 FAULTMASK
void __disable_fault_irq(void); // 设置 FAULTMASK 以禁用中断
void __set_FAULTMASK(uint32_t faultMask);
uint32_t __get_FAULTMASK(void);
对于汇编语言,可以利用 CPS 指令修改 FAULTMASK 的当前状态:
CPSIE F ; 清除 FAULTMASK
CPSID F ; 设置 FAULTMASK
还可以利用 MRS 和 MSR 指令访问 FAULTMASK 寄存器:
MOVS R0, #1
MSR FAULTMASK, R0 ; 将 1 写入 FAULTMASK 禁止所有中断
以及
MOVS R0, #0
MSR FAULTMASK, R0 ; 将 0 写入 FAULTMASK 使能中断
FAULTMASK 会在退出异常处理时被自动清除,从 NMI 处理中退出时除外。由于这个特性,FAULTMASK 就有了一个有趣的用法:若要在低优先级的异常处理中触发一个高优先级的异常(NMI 除外),但此时想在低优先级处理完成后再进行高优先级的处理,可以按如下步骤:
-
设置 FAULTMASK 禁止所有中断和异常(NMI异常除外)
-
设置高优先级中断或异常的挂起状态
-
退出当前处理
由于在 FAULTMASK 置位时,挂起的高优先级异常处理无法执行,高优先级的异常就会在 FAULTMASK 被清除前继续保持挂起状态,低优先级处理完后才会将其清除。因此,可以强制让高优先级处理在低优先级处理结束后才开始执行。
BASEPRI
有些情况下,可能只想禁止优先级低于某个特定等级的中断,此时,就可以使用 BASEPRI 寄存器。要实现这个目的,只需简单地将所需的屏蔽优先级写入 BASEPRI 寄存器。例如,要屏蔽优先级小于等于 0x60 的所有异常,则可以将这个数值写入 BASEPRI:
__set_BASEPRI(0x60); // 使用 CMSIS-Core 函数禁用优先级为 0x60~0xFF 的中断
若要使用汇编语言,则可写为:
MOVS R0, #0x60
MSR BASEPRI, R0 ; 禁用优先级为 0x60~0xFF 的中断
当然你也可以读出 BASEPRI 的值:
x = __get_BASEPRI(void); // 读出 BASEPRI 的值
或使用汇编实现:
MRS R0, BASEPRI
如果要取消屏蔽,只需要往 BASEPRI 写 0 即可:
__set_BASEPRI(0x0); // 取消 BASEPRI 屏蔽
或使用汇编:
MOVS R0, #0x0
MSR BASEPRI, R0 ; 取消 BASEPRI 屏蔽
BASEPRI 还可以通过另一个名称访问,即 BASEPRI_MAX。它们实际上是同一个寄存器,不过当使用这个名称访问时,会得到一个条件写操作。BASEPRI 和 BASEPRI_MAX 在硬件上是一个寄存器,不过在汇编中的编码不同。在使用 BASEPRI_MAX 时,处理器会自动比较当前值和新的数值,只有在新的优先级更高时才会允许修改。例如下面的指令序列:
MOVS R0, #0x60
MSR BASEPRI_MAX, R0 ; 禁止优先级为 0x60,0x61... 的中断
MOVS R0, #0xF0
MSR BASEPRI_MAX, R0 ; 由于该优先级低于 0x60,所以本次操作将会被忽略
MOVS R0, #0x40
MSR BASEPRI_MAX, R0 ; 本次写操作会将屏蔽值修改为 0x40
综上,要修改为更低的屏蔽值或禁止屏蔽,应该使用 BASEPRI。BASEPRI或 BASEPRI_MAX 寄存器无法在非特权状态被设置。
最后,与其他优先级寄存器类似,BASEPRI 寄存器的格式受到实际的优先级寄存器宽度影响,如果优先级仅实现了 3 位,则 BASEPRI 可被设置为 0x00、0x20、0x40 ... 0xC0 和 0xE0。