进入和退出流程
进入休眠模式
进入睡眠前先完成唤醒源配置,再执行 WFI 或 WFE。两条指令只负责让内核停止取指,具体进入普通睡眠还是深度睡眠由 SCB->SCR 的 SLEEPDEEP 位决定:
SLEEPDEEP=0:进入 Sleep,CPU 时钟停止,外设和中断系统仍可运行。SLEEPDEEP=1:进入芯片定义的深度睡眠,例如 Stop 或 Standby,实际掉电范围由电源控制器配置决定(看电源管理 (睡眠,停止,待机三个模式+电源域管理))
// 普通睡眠:WFI 等待中断
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
__WFI();
// 等待事件
__WFE();WFI(Wait For Interrupt)用于等待中断,通常用于中断驱动的空闲流程;WFE(Wait For Event)用于等待事件,适合通过事件机制唤醒内核。进入前应确认目标中断或事件已经配置完成,并清理可能影响判断的历史标志。
唤醒条件
WFI:等待中断
多数情况下,中断(包括NMI和SYSTICK定时中断)可以将cortex-M3或M4处理器从休眠中唤醒,不过,有些休眠模式可能会关掉NVIC或外设的时钟信号,还需要仔细查看微控制器的参考手册以免中断(或其中一部分)无法唤醒处理器。
若利用WFI或退出时进入休眠模式,要想将处理器唤醒,则需要使能中断请求,且中断优先级要大于当前的等级:
- BASEPRI(阈值屏蔽寄存器):
- 设置一个非零优先级阈值(如 0x50),所有优先级数值 该阈值(即优先级同级或更低)的中断均被屏蔽。
- 作用:根据 ARM 架构规范,若中断优先级被 BASEPRI 屏蔽,NVIC 会将其视为不可用或不高于当前执行级别,在大多数实现下无法产生合法的 WFI 唤醒信号(或直接被 NVIC 丢 弃,无法唤醒内核)。
唤醒之后是进入中断还是从睡眠位置继续执行?由 ‘PRIMASK’ 控制:
- PRIMASK(1 位开关) :
- 置 1 时(
__disable_irq()),屏蔽除 NMI 和 HardFault 以外的所有具有可编程优先级的异常(相当于把当前运行优先级提至0)。
- 置 1 时(
1. 裸机休眠唤醒流程
void BareMetal_Sleep(void)
{
// 置位 PRIMASK=1。
// 阻止 CPU 跳入 ISR,防止在判断条件到执行 WFI 之间因发生中断导致"错过唤醒";
// 硬件上 PRIMASK 不屏蔽唤醒事件,只要 NVIC 使能的中断到来,仍可唤醒 CPU。
__disable_irq();
if (System_Can_Sleep()) {
__DSB();
// 进入低功耗状态,等待唤醒事件。
__WFI();
__ISB();
}
// 唤醒后 CPU 继续向下顺序执行(此时 PRIMASK=1 仍起效,不会被 ISR 打断)。
// 必须在此处优先恢复 PLL/高频时钟和供电轨,确保后续外设 ISR 运行时硬件时钟已就绪。
Clock_And_Power_Restore();
// 清零 PRIMASK=0,放行中断。
// 刚才唤醒系统的挂起中断在此处立即触发,跳入对应的 ISR 执行。
__enable_irq();
}2. RTOS (FreeRTOS Tickless) 休眠唤醒流程
void RTOS_Tickless_Sleep(void)
{
// 此时系统已在临界区内,低功耗会先进临界区防跳转,BASEPRI != 0(阻断了普通外设中断)
// 先置位 PRIMASK=1。
// 用全局执行锁把代码彻底锁死,消除后面解开 BASEPRI 时可能产生的竞态窗口。
__disable_irq();
// 清零 BASEPRI=0。
// 硬件规则决定了非零 BASEPRI 会彻底阻断低优先级中断的 WFI 唤醒信号;
// 借由前一步 PRIMASK 的保护,可安全清零 BASEPRI,允许所有外设中断产生唤醒。
__set_BASEPRI(0);
// 最后一次确认是否有任务被唤醒,防止在停调度器到此处的缝隙中有超时到期。
if (eTaskConfirmSleepModeStatus() != eAbortSleep) {
// 配置专用低功耗定时器(如 LPTIM),作为超时唤醒源。
Config_LowPower_Timer();
__DSB();
// 进入低功耗模式。中断到来仅唤醒核心,因 PRIMASK=1 不会立刻跳入 ISR。
__WFI();
__ISB();
}
// 唤醒后主流程优先执行。
// 此时中断依然不能跳,必须先恢复系统主时钟,避免外设 ISR 在降频/异常时钟下运行出错。
Restore_System_Clock();
// 根据低功耗定时器的实际流逝时间,把休眠期间跳过的系统 Tick 一次性补上。
vTaskStepTick(Get_Sleep_Ticks());
// 将 BASEPRI 设回原临界区阈值,恢复 RTOS 的临界区受控状态。
__set_BASEPRI(configMAX_SYSCALL_INTERRUPT_PRIORITY);
// 清零 PRIMASK=0,正式解开跳转。
// 挂起的唤醒中断立刻执行 ISR(如按键/串口等),ISR 退出后由内核调度器完成任务切换。
__enable_irq();
}WFE:等待事件
SEVONPEND = 0:- 必须抢占成功才能唤醒 :中断使能且优先级高于当前等级(及
BASEPRI)。 - 注意 :若
PRIMASK = 1,执行级别被拉至最高,抢占不成立,无法唤醒(会睡死)。
- 必须抢占成功才能唤醒 :中断使能且优先级高于当前等级(及
SEVONPEND = 1:- 只要挂起就能唤醒 :任何中断只要 Pending 从 0 变 1,无视优先级、
BASEPRI和PRIMASK,必定唤醒。
- 只要挂起就能唤醒 :任何中断只要 Pending 从 0 变 1,无视优先级、
唤醒后的执行规则
- 进 ISR :
PRIMASK = 0且优先级高于当前等级(及BASEPRI)。 - 不进 ISR(继续往下跑) :
PRIMASK = 1,或优先级不足,或中断在 NVIC 未使能。