进入和退出流程

← 低功耗知识地图 | ← 主页


进入休眠模式

进入睡眠前先完成唤醒源配置,再执行 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. 裸机休眠唤醒流程

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,必定唤醒。

唤醒后的执行规则

  • 进 ISR :PRIMASK = 0 且优先级高于当前等级(及 BASEPRI)。
  • 不进 ISR(继续往下跑) :PRIMASK = 1,或优先级不足,或中断在 NVIC 未使能。