L1.1 与 L1.2 子状态:从定义到实战

大家好,我是你们的低功耗设计讲师。这节课咱们聊聊 L1.1 和 L1.2 子状态。说白了,这两个东西就是 CPU 在浅睡眠和深睡眠之间的两个“中间档”。你在做低功耗产品时,不可能总是一下子跳到最深休眠,得给系统留点喘气的空间。

我刚开始做嵌入式低功耗时,总想着“要么全睡,要么全醒”。结果发现电池撑不住,系统响应也慢。后来才明白,L1.1 和 L1.2 这两个子状态,才是真正省电又灵活的妙招。咱们一个个来看。

L1.1 子状态:浅度睡眠,快速唤醒

官方定义: L1.1 是 ACPI 规范中定义的处理器电源状态下的一个子状态。在这个状态下,CPU 核心时钟和大多数 PLL(锁相环)都关了,但缓存(L1/L2)仍然保持供电和上下文。换句话说,芯片内部的关键状态还留着,只是不跑指令了。

注意: 我曾经在某个项目中把 L1.1 误当成完全睡眠,结果唤醒后缓存内容全丢了。后来翻手册才发现,L1.1 下缓存是自保活的,但前提是供电没断。如果你在硬件设计时把缓存的电源域也切了,那就不是 L1.1 了,直接跳到 L2 了。

进入条件(硬性要求):

  • 所有 CPU 核心执行完 HLT 指令或类似的空转指令。
  • 中断控制器(如 APIC)处于活动状态,能响应唤醒事件。
  • 系统软件(比如 OS 的 idle 线程)确认功耗策略允许 L1.1。
  • 缓存一致性协议处于洁净状态,没有未完成的缓存行写回。

退出条件(如何醒来):

  • 任意使能的中断(包括定时器、外设中断、IPI等)到达。
  • 从收到中断到恢复第一条指令,典型延迟在几十微秒到几百微秒之间。
  • 我一般建议客户:如果你的中断响应要求 < 100µs,用 L1.1 很合适;要是 < 10µs,那你得考虑 C0 状态或者特殊硬件加速。

典型场景举例:

  • USB 设备轮询: 键盘、鼠标这类设备,大部分时间空闲,但一旦用户操作就要立刻响应。L1.1 下功耗比 C0 降低 70% 以上,唤醒延迟又足够短。
  • 周期性传感器采集: 例如温度传感器,每 100ms 采集一次。期间 CPU 可在 L1.1 里等着,中断来了再唤醒处理数据。
  • 音频播放的空闲空隙: 播放流媒体时,解码器处理完一个数据块后短暂等待下一个 DMA 中断,这时进入 L1.1 能省不少电。

L1.2 子状态:更深一层,门控更狠

L1.2 比 L1.1 多做了两件事:关闭了 L1 缓存的部分电压域(有些设计连 L2 也关),并且 把 PLL 和大多数内部时钟全关了。这意味着唤醒时需要重新建立时钟稳定,延迟增加不少。

对比项 L1.1 L1.2
缓存供电 全保持 部分保持/丢失
PLL 关闭 关闭
内部时钟 大部分关闭 全部关闭
唤醒延迟(典型) 50~200 µs 200~1000 µs
功耗节约 约 60-80% 相比 C0 约 85-95% 相比 C0
退出后恢复复杂度 低(直接执行) 中(需重建上下文)

进入条件: 除了 L1.1 的条件外,额外要求:

  • 系统软件确认没有正在进行的 DMA 操作会访问缓存区域。
  • 缓存内容可以被安全清空或写回(如果硬件不负责自恢复)。
  • 唤醒事件必须能够通过低功耗唤醒控制器传递,而不是直接由中断控制器发。

退出过程:

  1. 唤醒信号唤醒电源管理控制器。
  2. 重新建立 PLL 锁相,稳定时钟(需要几十到几百微秒)。
  3. 恢复缓存状态(如果硬件不支持自保活,则需要软件从内存重新加载)。
  4. 跳转至中断服务程序。
避坑指南: 我曾经在一个物联网网关项目里,把 L1.2 的阈值设得太低,结果每次中断都触发退出再进入,反而比一直跑 C0 还耗电。后来加了迟滞时间——进入 L1.2 前必须空闲至少 5ms,功耗才降下来。记住:频繁进出更费电。

典型场景举例:

  • 可穿戴设备的深层空闲: 手表不亮屏、无操作时,可以长时间待在 L1.2,只保留 RTC 或加速度传感器中断。我的上一个可穿戴方案就是靠这个子状态把待机电流从 2mA 降到 150µA。
  • 物联网传感器节点(每分钟上报一次): 大多数时间处于睡眠,每隔 60 秒由定时器唤醒采集数据并上报。L1.2 非常适合这种长周期场景。
  • SoC 内部非实时模块的空闲: 比如 GPU 没任务时,可以进入 L1.2;而 CPU 自己还在 L1.1 等待用户输入。不同类型核心可以用不同子状态。
L1.1 与 L1.2 状态切换流程与关键指标 L1.1 ↔ L1.2 状态切换与电平判断 C0 运行态 L1.1 浅睡眠 缓存保持 · 唤醒 <200µs L1.2 深睡眠 缓存部分丢失 · 唤醒 >200µs 空闲 < 阈值 空闲 ≥ 阈值 超时降级 中断唤醒(快速) 中断唤醒(慢速) 💡 个人经验:判断阈值很关键 我曾在一个项目里用 2ms 判断:空闲 <2ms → L1.1;≥2ms → L1.2 既保证了响应速度,又最大化节能。别盲目调高阈值,否则会一直留在 L1.1 浪费电。

进入/退出流程中的一个关键细节:仲裁与锁定

你想想看,如果多个核心都想进入不同的子状态,怎么协调?我记得有一次调试多核 SoC 的低功耗,一个核要进 L1.1,另一个要进 L1.2,结果整个系统因为电源域不一致死锁了。后来加上一个仲裁器,强制所有核心必须协商一致才能切换电源域。这就是业界常说的 「Last Core」原则:最后一个申请睡眠的核心负责正式下电。

实用忠告: 在你写低功耗驱动时,一定要检查硬件是否支持「异步退出」。如果某个核还在 L1.2 时另一个核被中断唤醒了,电源控制器能否独立恢复那个核的缓存?有些老芯片不行,只能全系统退出。这一点在项目选型阶段就要确认。

再补两句避坑

  • 定时器配置: 如果你用 L1.2,确保唤醒定时器用的是独立运行的实时时钟(RTC),而不是依赖 PLL 的普通定时器。PLL 都关了,定时器也就停了。我曾经犯过这错误,醒来后发现时间慢了半秒。
  • 中断极性: L1.1/L1.2 下中断控制器可能处于低功耗模式,有些中断(比如边沿触发)可能会丢失。建议统一用电平触发,并且保持至少几个时钟周期的宽度。嗯,这点生产测试时最容易忽略。
  • 测量功耗时的陷阱: 你想测量 L1.2 的实际电流,不能只测 1 秒。因为唤醒瞬间电流会飙高,你的万用表可能取到平均值。正确的做法:让系统在 L1.2 里待至少 10 秒,然后用示波器抓电流波形,取平稳区间的值。我每次都这样干,从没出过偏差。