Cortex M处理器休眠指令行为详解:
在嵌入式开发中,经常会用到wfi或wfe让处理器进入休眠模式,等待中断唤醒,既可以节约功耗,也能避免处理器抢占系统带宽,降低数据传输的效率,这里详细分析一下这两条指令的具体行为以及使用的场景
ARM特殊寄存器以及指令:
PRIMASK 寄存器:写1时,屏蔽掉除了NMI和Hard fault之外的所有中断。如果有外部中断,则将中断挂起,而不会执行。写0时,取消掉屏蔽效果。效果和 cpsid i 和 cpsie i 一样
mov r0, #1
msr PRIMASK, r0
cpsid i // 这行代码和上面两行代码的效果完全相同
mov r0, #1
msr PRIMASK, r0
cpsie i // 这行代码和上面两行代码的效果完全相同
BASEPRI 寄存器:往内部写入一个中断优先级,屏蔽比该优先级低的所有中断。如果写0,则不屏蔽中断(因为0是最高的中断优先级)
事件寄存器:Cortex M处理器内部不可见的一个1bit的寄存器,如果产生任一事件,则该寄存器置1,一直保持为1直到通过wfe清除(后文会介绍wfe指令)
SEVONPEND :是系统控制寄存器(SCR)中的一个位,如果该位为1,当中断挂起时(无需执行),会让事件寄存器置1
WFI:
wait for interrupt,顾名思义是等待中断唤醒,执行之后会处理器会立刻进入休眠模式(无条件休眠),直到被任一中断唤醒为止
while(1){
__asm("wfi");
}
复杂情况分析:
必须要在NVIC中使能对应中断,否则,处理器永远不能从wfi 指令中唤醒
不使能PRIMASK或BASEPRI
1:如果中断优先级小于当前优先级(如中断嵌套),处理器不会被唤醒
中断被PRIMASK或BASEPRI屏蔽
1:中断优先级大于当前优先级,即使中断被特殊寄存器屏蔽,处理器也会被唤醒,但中断只会被挂起,而不会被执行
2:如果中断被挂起但没有被执行,再次使用wfi处理器也不会进入休眠
3:中断优先级小于当前优先级,处理器不会被唤醒
小结
WFI的行为比较简单,一般在中断驱动的应用中才会用到(线程模式休眠,中断模式下执行任务)
WFE:
WFE:wait for event,即等待事件唤醒
事件: 事件源有很多,如:异常进入和退出,置位SEVONPEND后中断处于挂起状态(注意这里是挂起的状态而不是挂起的动作),SEV指令,调试请求等
执行之后会处理器首先会检查事件寄存器的值,如果为0则进入休眠模式,如果为1,则清除事件寄存器而不进入休眠(条件休眠)
因为这种特性,为保证处理器进入休眠模式,通常这样使用
while(condition){
__asm("sev"); // 发送事件,让事件寄存器置1
__asm("wfe"); // 清除事件寄存器
__asm("wfe"); // 进入休眠
}
// 其实上述代码也不是百分百保险,如果在两条wfe指令之间又产生了一个事件,那么处理器还是无法进入休眠
// 这是处理器本身的设计缺陷,无法通过软件的方式弥补
复杂情况分析:
wfe的使用不一定需要在NVIC中使能对应中断,具体情况见下列分析
不使能PRIMASK或BASEPRI
SEVONPEND为1
1:由于SEVONPEND为1,无论中断优先级如何,只要挂起就会唤醒处理器(甚至无需使能NVIC)。但如果优先级小于当前优先级时,不会处理中断(使能NVIC的情况)
SEVONPEND为0
1:必须在NVIC中使能对应中断,并且只有中断优先级大于当前优先级时,才会进入中断处理程序并唤醒处理器(异常进入是事件源)。否则,不会唤醒
中断被PRIMASK或BASEPRI屏蔽
SEVONPEND为1
1:无需使能NVIC,由于SEVONPEND为1,所以只要有外部中断被挂起,就会唤醒处理器
SEVONPEND为0
2:由于中断被屏蔽,即使使能NVIC,无论中断的优先级如何,也不会唤醒处理器
小结
WFE可以让CPU休眠,等待外设处理完任务之后再发送中断唤醒CPU,避免CPU空跑浪费功耗,和因为高频率的轮询抢占总线带宽导致其他任务效率低下,在嵌入式应用中比较常用