5.15 LPMGR设备
5.15 LPMGR设备
📚 本节导读
学习时长: 约 40 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言基础、MCU 低功耗模式基础知识、5.1 设备驱动框架、4.5 时间管理
🎯 学习目标
- 理解 OneOS 低功耗管理框架的分层设计
- 掌握四种睡眠模式的区别与选择
- 掌握设备挂起/恢复的注册与回调机制
- 理解 Tickless 机制在低功耗场景下的作用
- 掌握低功耗通知机制的注册与使用
一、概述
LPMGR(Low Power Manager)是 OneOS 提供的低功耗管理框架,负责在系统空闲时协调各外设进入低功耗状态,并在唤醒时恢复。LPMGR 设备驱动位于 drivers/lpmgr/ 目录下,核心头文件为 lpmgr.h 和 lpm.h,实现文件为 lpmgr.c 和 lpm.c。
LPMGR 框架的特点:
- 多级睡眠模式:支持 Idle / LightSleep / DeepSleep / Standby / Shutdown 多级模式
- 设备协调机制:外设驱动通过注册 suspend/resume 回调参与功耗管理
- Tickless 补偿:唤醒后自动补偿睡眠期间的系统时钟
- 通知机制:支持注册睡眠/唤醒通知回调
- 请求/释放模式:多个模块可独立请求睡眠模式,由框架综合决策
1.1 使用场景
一句话核心:LPMGR 适用于电池供电、对功耗敏感、有大量空闲时间的嵌入式设备(如穿戴、传感器节点、遥控器),让 CPU 和外设在空闲时尽量进入低功耗状态,同时保证唤醒后功能与时钟不丢。
与"裸机只做 WFI / 每个模块自己管功耗"对比:
| 维度 | LPMGR 统一管理 | 各模块自己管 / 裸机 WFI |
|---|---|---|
| 外设协调 | 统一挂起/恢复所有注册外设 | 各模块自行处理,易漏 |
| 睡眠模式决策 | 引用计数 + 取最浅请求,自动综合 | 需手动判断所有约束 |
| Tickless | 自动补偿系统时钟 | 需自行处理时钟偏差 |
| 可移植性 | 跨厂商统一框架 | 绑定具体芯片 |
| 唤醒恢复 | 统一 resume + 通知 | 需逐个恢复 |
结论:系统中有多个外设需要统一进出低功耗、且要保证定时器精度时用 LPMGR;极简、单外设场景可手动处理。
典型应用场景:
| 场景 | 谁在请求/使用 | 实际工作情况 |
|---|---|---|
| 电池传感器节点 | 传感器任务 + 无线任务 | 采样间隙进入 DEEP 睡眠,无线任务请求 IDLE(要保持 RF 时钟)时只能浅睡 |
| 穿戴设备 | UI 任务 + 通信任务 | 屏幕点亮时请求 NONE,待机时全部 release 进 DEEP,抬手唤醒 |
| 遥控器 | 按键中断 + RF 发送 | 平时 DEEP,按键中断唤醒后发送,再回 DEEP |
| 需要时钟准的采集任务 | 定时采集任务 + LPMGR | 深度睡眠靠 LPCE 唤醒,Tickless 补偿让 os_tick_get 依然准确 |
选型边界表(何时不该用它):
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 极简单外设、无统一功耗需求 | 手动 WFI / 关外设 | 引入 LPMGR 框架反而复杂 |
| 需要保持时钟一直运行 | 普通 tick | 深度睡眠停主时钟,若业务不能容忍 tick 跳变则不适合深度睡眠 |
| 实时性要求极高 | 保持运行 / IDLE | 深度睡眠唤醒延迟(ms 级)可能不满足实时响应 |
结合源码的使用注意点:
- 先
os_lpmgr_init再 request:gs_lpmgr未初始化时 request/release 会解引用空指针崩溃; - request/release 必须成对:漏 release 会让计数一直 >0,系统永远进不了更深睡眠;
- 外设必须注册 suspend/resume:未注册外设在睡眠时不挂起,唤醒后状态可能错乱;
- suspend/resume 里不能阻塞:它们在低功耗流程中被调用,做阻塞/申请内存会导致系统卡死或睡眠失败。
二、睡眠模式
2.1 模式定义
typedef enum
{
SYS_SLEEP_MODE_NONE = 0, /* 无睡眠(正常运行) */
SYS_SLEEP_MODE_IDLE, /* 空闲模式(仅 CPU 暂停,外设运行) */
SYS_SLEEP_MODE_LIGHT, /* 轻度睡眠(部分外设关闭) */
SYS_SLEEP_MODE_DEEP, /* 深度睡眠(大部分外设关闭) */
SYS_SLEEP_MODE_MAX, /* 模式数量(用于数组大小) */
} lpmgr_sleep_mode_e;| 模式 | 功耗 | 唤醒延迟 | 典型场景 |
|---|---|---|---|
| NONE | 正常运行 | — | 系统繁忙 |
| IDLE | 极低 | ~0 μs | CPU 空闲时自动进入 |
| LIGHT | 低 | ~10 μs | 短暂空闲 |
| DEEP | 很低 | ~100 μs | 长时间空闲 |
| Standby | 极低 | ~1 ms | 电池供电待机 |
| Shutdown | 最低 | ~10 ms | 关机前最低功耗 |
2.2 模式决策机制
多个模块通过 os_lpmgr_request(mode) 和 os_lpmgr_release(mode) 声明自己允许进入的最深睡眠模式。框架取所有请求中最浅(最保守)的模式作为最终睡眠模式——因为必须满足每个模块的约束,只要有一个模块不允许深度睡眠,系统就不能深度睡眠:
模块 A 请求: DEEP ─┐ 含义:A 允许深度睡眠
模块 B 请求: IDLE ─┼─→ 最终: IDLE(B 只允许空闲,不许深度睡眠,故取最浅的)
模块 C 请求: LIGHT ─┘关键理解:请求模式 = "我允许系统进入的最深睡眠"。IDLE 比 DEEP 更"浅"(功耗更高)。上面例子里 B 请求 IDLE,意味着 B 需要外设持续工作、不允许深度睡眠,所以即使 A 请求了 DEEP,最终也只能进 IDLE——取所有请求中最浅的那个,即所有模块都能接受的最深睡眠(见 5.1
lpmgr_check_sleep_mode的数值演算)。
对比:若没有模块请求 NONE(禁止睡眠)且所有模块都请求 DEEP,则最终可进入 DEEP(最深、最省电)。请求越多、越深,系统越能深度睡眠;只要有一个模块请求较浅的模式,就"一票否决"降到该浅模式。
三、核心数据结构
3.1 LPMGR 设备结构体 os_lpmgr_dev
来源:drivers/lpmgr/lpmgr.h
struct os_lpmgr_dev
{
struct os_device parent;
/* modes */
lpmgr_sleep_mode_e modes[SYS_SLEEP_MODE_MAX];
lpmgr_sleep_mode_e sleep_mode; /* current sleep mode */
uint8_t mode_change_falg;
/* if the mode has timer, the corresponding bit is 1*/
os_tick_t sleep_tick;
os_err_t (*sleep)(lpmgr_sleep_mode_e mode);
os_clockevent_t *lpce;
uint32_t mult; /* time(ns) = tick * mult >> shift */
uint32_t shift;
uint32_t mult_t; /* tick = time(ns) * mult_t >> shift_t */
uint32_t shift_t;
};字段详解(源码注释为英文原版,下表补充说明,不属于源码):
| 字段 | 类型 | 含义 |
|---|---|---|
parent | struct os_device | 首成员继承自标准设备,使 os_lpmgr_dev 可作普通设备注册(设备名 "lpm",见 OS_LPMGR_DEVICE_NAME) |
modes[] | lpmgr_sleep_mode_e[] | 各模式的请求计数数组,modes[i] 表示第 i 种模式被多少模块 request 过(引用计数) |
sleep_mode | lpmgr_sleep_mode_e | 当前综合决策出的睡眠模式(由 lpmgr_check_sleep_mode 计算) |
mode_change_falg | uint8_t | 模式变化标志(框架内部使用) |
sleep_tick | os_tick_t | 与睡眠相关的 tick 数 |
sleep | os_err_t (*)(mode) | HAL 层睡眠回调函数指针(真正执行 WFI/STOP 等) |
lpce | os_clockevent_t * | 低功耗定时器(LPCE,深度睡眠时的唤醒源) |
mult/shift | uint32_t | 换算系数:time(ns) = tick * mult >> shift |
mult_t/shift_t | uint32_t | 反向换算系数:tick = time(ns) * mult_t >> shift_t |
关键理解 ——
modes[]引用计数与sleep_mode决策:modes[i]是"有多少模块请求了模式 i"的计数。sleep_mode则是框架从modes[]推导出的最终睡眠模式。多个模块并发 request/release 时,计数增减,最终lpmgr_check_sleep_mode从最低功耗模式向上找到第一个有请求的模式(详见 5.1)。
关键理解 ——
mult/shift换算:深度睡眠时系统主时钟可能停止,睡眠时长以纳秒计。mult/shift用于把 tick 数换算成纳秒(nsec = tick * mult >> shift),mult_t/shift_t反向换算,避免浮点运算(详见 5.3 的 Tickless 计算)。
3.2 外设低功耗操作接口 os_lpmgr_device_ops
来源:drivers/lpmgr/lpmgr.h
struct os_lpmgr_device_ops
{
os_err_t (*suspend)(void *priv, lpmgr_sleep_mode_e mode);
void (*resume)(void *priv, lpmgr_sleep_mode_e mode);
};函数指针详解(源码无注释,下表为文档补充说明,不属于源码):
| 函数指针 | 说明 |
|---|---|
suspend | 挂起外设:进入睡眠前调用,返回非 OS_SUCCESS 则框架放弃本次睡眠并回滚 |
resume | 恢复外设:唤醒后调用,恢复外设到睡眠前状态 |
关键理解 —— 配套的设备链表节点
os_lpmgr_device:注册的外设以os_lpmgr_device结构挂在全局链表gs_register_dev_list上:
struct os_lpmgr_device
{
const struct os_lpmgr_device_ops *ops;
void *priv;
os_list_node_t list;
};list 节点挂到 gs_register_dev_list(lpmgr.c 里的 static 全局变量),suspend/resume 时框架遍历该链表逐一调用(见 5.4 lpmgr_device_suspend)。
3.3 通知结构体 lpmgr_notify 与事件枚举
来源:drivers/lpmgr/lpmgr.h
struct lpmgr_notify
{
void (*notify)(os_lpmgr_sys_e event, lpmgr_sleep_mode_e mode, void *data);
void *data;
os_list_node_t list;
};字段详解(源码无注释,下表为文档补充说明,不属于源码):
| 字段 | 含义 |
|---|---|
notify | 通知回调:睡眠/唤醒时调用,event 区分事件、mode 为本次睡眠模式、data 为注册时传入的私有数据 |
data | 回调私有数据(原样传回 notify) |
list | 链表节点,挂到全局通知链表 gs_lpmgr_notify_list |
事件枚举(lpmgr.h,与项目逐字一致):
typedef enum
{
SYS_ENTER_SLEEP = 0,
SYS_EXIT_SLEEP,
} os_lpmgr_sys_e;| 事件 | 含义 |
|---|---|
SYS_ENTER_SLEEP | 即将进入睡眠(挂起外设前通知) |
SYS_EXIT_SLEEP | 已退出睡眠(恢复外设后通知) |
注意:
lpmgr_notify本身只登记回调,调用方需保证lpmgr_notify是持久对象(如static或全局),因为框架把&cb_info->list挂进链表,函数返回后若对象销毁,链表将悬挂野指针。示例 7.3 中g_notify就是static的。
四、API 详解
本章给出 LPMGR 对外 API 的签名与参数含义,方便速查;核心函数的完整源码、分步详解见第五章。API 分五类:初始化、模式请求、外设注册、通知、核心入口。
4.1 初始化
函数签名:
void os_lpmgr_init(os_err_t (*sleep)(lpmgr_sleep_mode_e mode));| 参数 | 说明 |
|---|---|
sleep | HAL 层睡眠回调函数指针,各模式调 HAL 进入对应睡眠(见第八章适配) |
os_lpmgr_init会创建gs_lpmgr设备、打开 LPCE 低功耗定时器、注册 Tickless 补偿回调。必须在系统启动早期(设备层前)调用,且之后才能用os_lpmgr_request/release。
4.2 请求/释放睡眠模式
函数签名:
void os_lpmgr_request(lpmgr_sleep_mode_e sleep_mode);
void os_lpmgr_release(lpmgr_sleep_mode_e sleep_mode);| 参数 | 说明 |
|---|---|
sleep_mode | 声明"允许进入的最深睡眠"模式(SYS_SLEEP_MODE_NONE~DEEP) |
| 函数 | 说明 |
|---|---|
os_lpmgr_request | 请求:把对应模式计数 +1(上限 255) |
os_lpmgr_release | 释放:把对应模式计数 -1(下限 0) |
请求/释放是引用计数式的,必须成对调用。框架取所有请求中最浅的模式作为最终睡眠模式(详见 5.1/5.2)。
4.3 外设注册
函数签名:
os_err_t os_lpmgr_device_register(void *priv, const struct os_lpmgr_device_ops *ops);
os_err_t os_lpmgr_device_register_high_prio(void *priv, const struct os_lpmgr_device_ops *ops);
void os_lpmgr_device_unregister(void *priv, const struct os_lpmgr_device_ops *ops);| 参数 | 说明 |
|---|---|
priv | 外设私有数据(传入 suspend/resume 回调的第一个参数) |
ops | os_lpmgr_device_ops(suspend/resume 两个回调) |
| 函数 | 说明 |
|---|---|
os_lpmgr_device_register | 注册外设,suspend 时按注册顺序调用(尾插链表) |
os_lpmgr_device_register_high_prio | 高优先级注册,suspend 时优先调用(头插链表) |
os_lpmgr_device_unregister | 注销外设(从链表移除并释放节点) |
4.4 通知注册
函数签名:
os_err_t os_lpmgr_notify_register(struct lpmgr_notify *cb_info);
os_err_t os_lpmgr_notify_unregister(struct lpmgr_notify *cb_info);| 参数 | 说明 |
|---|---|
cb_info | lpmgr_notify 结构(notify 回调 + data + 链表节点),必须为持久对象(static/全局) |
注册后,进入睡眠前收到
SYS_ENTER_SLEEP通知、唤醒后收到SYS_EXIT_SLEEP通知(见 3.3 和 5.4)。
4.5 核心入口
函数签名:
void os_low_power_manager(void);空闲任务自动调用:在系统空闲钩子中被调用,检查当前
sleep_mode,若允许深度睡眠(> IDLE)则执行完整睡眠流程(见 5.3lpmgr_start)。应用一般无需手动调用。
五、核心源码详解
说明:以下源码均取自
drivers/lpmgr/lpmgr.c,与项目源码逐字一致(含原始英文注释)。
5.1 睡眠模式决策 lpmgr_check_sleep_mode
一句话定位:根据各模式的请求计数,从最低功耗模式起向上找第一个有请求的模式作为最终睡眠模式。
完整源码(drivers/lpmgr/lpmgr.c,与项目一致):
static void lpmgr_check_sleep_mode(struct os_lpmgr_dev *lpmgr)
{
lpmgr_sleep_mode_e index;
lpmgr->sleep_mode = SYS_SLEEP_MODE_IDLE;
for (index = SYS_SLEEP_MODE_NONE; index < SYS_SLEEP_MODE_MAX; index++)
{
if (lpmgr->modes[index])
{
lpmgr->sleep_mode = index;
break;
}
}
}函数签名:
static void lpmgr_check_sleep_mode(struct os_lpmgr_dev *lpmgr);| 参数 | 说明 |
|---|---|
lpmgr | LPMGR 设备指针,读其 modes[],写其 sleep_mode |
分步详解:
第 1 步:先默认取 IDLE(无条件执行)
lpmgr->sleep_mode = SYS_SLEEP_MODE_IDLE;详细解释:先把 sleep_mode 默认置为 SYS_SLEEP_MODE_IDLE(空闲)。为什么默认 IDLE 而非 NONE:系统空闲时总可以进入 IDLE(CPU 停止,无副作用),只有当有人请求了更浅的模式(NONE,禁止睡眠)时才会被覆盖。
第 2 步:从 NONE 起找第一个有请求的模式(条件 命中则 break)
for (index = SYS_SLEEP_MODE_NONE; index < SYS_SLEEP_MODE_MAX; index++)
{
if (lpmgr->modes[index])
{
lpmgr->sleep_mode = index;
break;
}
}
}详细解释:关键理解:modes[index] 是"有多少模块请求模式 index"的计数。从 NONE(index=0,最浅)开始向上(往更深)找第一个 modes[index] > 0 的模式,它就是最终睡眠模式。
为什么从 NONE 向上找(而不是从 DEEP 向下):每个模块请求的模式含义是"我允许系统进入的最深睡眠"。如果 A 请求 IDLE、B 请求 DEEP,说明 A 不允许深度睡眠(A 需要外设持续工作),系统只能满足最保守的 A——取所有请求中最浅的模式。NONE 有请求(有人禁止任何睡眠)优先级最高,因此从 index=0 向上找到的第一个有请求的模式,正好是"所有请求中最浅的那个",即所有模块都能接受的最深睡眠。
数值演算(假设 modes 数组:NONE=0, IDLE=1, LIGHT=0, DEEP=0):
| 计算项 | 代入 | 结果 |
|---|---|---|
默认 sleep_mode | —— | SYS_SLEEP_MODE_IDLE |
| 遍历 NONE(index=0) | modes[0] = 0 | 跳过 |
| 遍历 IDLE(index=1) | modes[1] = 1(有请求) | 命中,sleep_mode = IDLE,break |
最终 sleep_mode | —— | SYS_SLEEP_MODE_IDLE |
数值演算(含 NONE 请求的场景)(假设 NONE=1, IDLE=1, LIGHT=0):
| 计算项 | 代入 | 结果 |
|---|---|---|
| 遍历 NONE(index=0) | modes[0] = 1(有人禁止睡眠) | 命中,sleep_mode = NONE,break |
最终 sleep_mode | —— | SYS_SLEEP_MODE_NONE(不允许任何睡眠) |
lpmgr_check_sleep_mode() 条件走向总结:
lpmgr_check_sleep_mode(lpmgr) 条件走向总结:
├── lpmgr->sleep_mode = SYS_SLEEP_MODE_IDLE(默认)
├── for index = NONE(0) 到 MAX-1 循环
│ ├── modes[index] == 0(该模式无人请求)
│ │ └── 继续下一个 index
│ └── modes[index] != 0(有人请求该模式)
│ ├── lpmgr->sleep_mode = index
│ └── break 跳出循环
└── 返回,sleep_mode = 第一个被请求的模式(所有请求中最浅的)5.2 请求/释放 os_lpmgr_request / os_lpmgr_release
完整源码(drivers/lpmgr/lpmgr.c,与项目一致):
void os_lpmgr_request(lpmgr_sleep_mode_e sleep_mode)
{
os_ubase_t level;
struct os_lpmgr_dev *lpmgr = gs_lpmgr;
if (sleep_mode > (SYS_SLEEP_MODE_MAX - 1))
return;
os_spin_lock_irqsave(&gs_device_lock, &level);
if (lpmgr->modes[sleep_mode] < 255)
lpmgr->modes[sleep_mode]++;
lpmgr_check_sleep_mode(lpmgr);
os_spin_unlock_irqrestore(&gs_device_lock, level);
}
void os_lpmgr_release(lpmgr_sleep_mode_e sleep_mode)
{
os_ubase_t level;
struct os_lpmgr_dev *lpmgr = gs_lpmgr;
if (sleep_mode > (SYS_SLEEP_MODE_MAX - 1))
return;
os_spin_lock_irqsave(&gs_device_lock, &level);
if (lpmgr->modes[sleep_mode] > 0)
lpmgr->modes[sleep_mode]--;
lpmgr_check_sleep_mode(lpmgr);
os_spin_unlock_irqrestore(&gs_device_lock, level);
}函数签名:
void os_lpmgr_request(lpmgr_sleep_mode_e sleep_mode);
void os_lpmgr_release(lpmgr_sleep_mode_e sleep_mode);| 参数 | 说明 |
|---|---|
sleep_mode | 要请求/释放的睡眠模式(SYS_SLEEP_MODE_NONE~DEEP),超出范围则忽略 |
注意:
os_lpmgr_request/release返回void(无返回值),用gs_lpmgr全局设备指针,必须在os_lpmgr_init之后调用(否则gs_lpmgr == NULL,解引用崩溃)。
分步详解(以 os_lpmgr_request 为例,release 仅在计数增减方向不同):
第 1 步:参数校验(条件 sleep_mode 越界)
if (sleep_mode > (SYS_SLEEP_MODE_MAX - 1))
return;详细解释:sleep_mode 超过 SYS_SLEEP_MODE_MAX - 1(即大于 DEEP)说明是非法模式,直接返回,不操作 modes[](防止数组越界)。这是对枚举值做边界保护。
第 2 步:加锁并修改引用计数(无条件执行,但计数有上下限)
os_spin_lock_irqsave(&gs_device_lock, &level);
if (lpmgr->modes[sleep_mode] < 255)
lpmgr->modes[sleep_mode]++;详细解释:用自旋锁 os_spin_lock_irqsave 保护 modes[](可能被多任务/中断并发访问)。request 使 modes[sleep_mode] +1(上限 255,防止 uint8_t 计数溢出回绕)。release 则反之:if (modes[sleep_mode] > 0) modes[sleep_mode]--;(下限 0,防止计数下溢成负数)。
第 3 步:重新计算最终睡眠模式(无条件执行)
lpmgr_check_sleep_mode(lpmgr);
os_spin_unlock_irqrestore(&gs_device_lock, level);
}详细解释:计数改变后调用 lpmgr_check_sleep_mode(lpmgr)(见 5.1)重新推导 sleep_mode。最后 os_spin_unlock_irqrestore 释放自旋锁。
关键理解 —— 引用计数机制:modes[] 是引用计数,不是"当前模式"标志。多个模块可各自 request/release,计数随之增减,框架总是取"所有请求中最浅的模式"。例如模块 A 和 B 都 request(DEEP),则 modes[DEEP] = 2;A release(DEEP) 后 modes[DEEP] = 1,因 B 仍请求 DEEP,最终仍可深度睡眠。只有当计数减到 0 才失效。
os_lpmgr_request() 条件走向总结:
os_lpmgr_request(sleep_mode) 条件走向总结:
├── sleep_mode > (SYS_SLEEP_MODE_MAX - 1)(越界)
│ └── 直接 return,不修改计数
└── sleep_mode 合法
├── os_spin_lock_irqsave 加锁
├── modes[sleep_mode] < 255(未到上限)
│ └── modes[sleep_mode]++
├── modes[sleep_mode] == 255(已达上限)
│ └── 不增加(防溢出)
├── lpmgr_check_sleep_mode(lpmgr) 重新决策
└── os_spin_unlock_irqrestore 解锁
os_lpmgr_release与 request 的唯一区别:第 2 步是if (modes[sleep_mode] > 0) modes[sleep_mode]--;(减 1,下限 0),其余逻辑完全相同。
5.3 核心入口 os_low_power_manager 与 lpmgr_start
一句话定位:空闲任务调用,若允许深度睡眠则调用 lpmgr_start 执行完整睡眠流程。
完整源码(drivers/lpmgr/lpmgr.c,与项目一致):
void os_low_power_manager(void)
{
OS_ASSERT(gs_lpmgr != NULL);
os_schedule_lock();
if (gs_lpmgr->sleep_mode > SYS_SLEEP_MODE_IDLE)
lpmgr_start(gs_lpmgr);
os_schedule_unlock();
}分步详解:
第 1 步:锁调度,按需深度睡眠(条件 sleep_mode > IDLE)
OS_ASSERT(gs_lpmgr != NULL);
os_schedule_lock();
if (gs_lpmgr->sleep_mode > SYS_SLEEP_MODE_IDLE)
lpmgr_start(gs_lpmgr);
os_schedule_unlock();
}详细解释:空闲任务在 os_schedule 空闲钩子里调用本函数。关键理解:先 os_schedule_lock(禁止调度,避免睡眠过程中任务切换)。若 sleep_mode > IDLE(允许深度睡眠)才调用 lpmgr_start 进入深度睡眠;否则(IDLE)直接返回(只做 WFI,由空闲任务流程处理)。最后 os_schedule_unlock。
lpmgr_start(完整睡眠流程核心):
函数签名:
static os_err_t lpmgr_start(struct os_lpmgr_dev *lpmgr);| 参数/返回值 | 说明 |
|---|---|
lpmgr | LPMGR 设备指针 |
OS_SUCCESS | 完成一次睡眠 + 唤醒流程 |
OS_BUSY | 预挂起失败 / 睡眠时间太短不值得 / 挂起外设失败,放弃本次睡眠 |
完整源码(drivers/lpmgr/lpmgr.c,与项目逐字一致):
static os_err_t lpmgr_start(struct os_lpmgr_dev *lpmgr)
{
os_err_t ret = OS_SUCCESS;
os_ubase_t level;
uint64_t nsec = 0;
ret = pre_gettick_dev_suspend(SYS_SLEEP_MODE_NONE);
if (ret != OS_SUCCESS)
{
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
return OS_BUSY;
}
os_spin_lock_irqsave(&gs_device_lock, &level);
os_tick_t timeout_tick = os_tickless_get_sleep_ticks();
if (timeout_tick != OS_TICK_MAX)
{
nsec = ((uint64_t)timeout_tick * lpmgr->mult >> lpmgr->shift);
if (nsec < lpmgr->lpce->min_nsec || nsec < BSP_USING_MINSLEEP_MS * 1000000)
{
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
os_spin_unlock_irqrestore(&gs_device_lock, level);
return OS_BUSY;
}
ret = lpmgr_enter_sleep_call(lpmgr->sleep_mode);
if (ret != OS_SUCCESS)
{
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
lpmgr_exit_sleep_call(lpmgr->sleep_mode);
os_spin_unlock_irqrestore(&gs_device_lock, level);
return OS_BUSY;
}
if (nsec > lpmgr->lpce->max_nsec)
nsec = lpmgr->lpce->max_nsec;
gs_lp_sleep_nsec = nsec;
os_clockevent_start_oneshot(lpmgr->lpce, nsec);
}
else
{
gs_lp_sleep_nsec = 0;
os_clockevent_stop(lpmgr->lpce);
ret = lpmgr_enter_sleep_call(lpmgr->sleep_mode);
if (ret != OS_SUCCESS)
{
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
lpmgr_exit_sleep_call(lpmgr->sleep_mode);
os_spin_unlock_irqrestore(&gs_device_lock, level);
return OS_BUSY;
}
}
gs_lp_sleep_state = LP_SLEEP_STATUS_ACTIVE;
os_spin_unlock_irqrestore(&gs_device_lock, level);
lpmgr->sleep(lpmgr->sleep_mode);
OS_ASSERT(gs_lp_sleep_state != LP_SLEEP_STATUS_ACTIVE);
gs_lp_sleep_state = LP_SLEEP_STATUS_NONE;
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
lpmgr_exit_sleep_call(lpmgr->sleep_mode);
return OS_SUCCESS;
}分步详解:
第 1 步:预挂起设备(条件 预挂起失败则回滚返回)
ret = pre_gettick_dev_suspend(SYS_SLEEP_MODE_NONE);
if (ret != OS_SUCCESS)
{
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
return OS_BUSY;
}详细解释:先挂起"预挂起设备"(pre_gettick_dev_suspend,即 gs_register_pre_dev_list 链表上的设备,如 DMA/时钟等必须在睡眠前停的)。若失败:恢复已预挂起的设备并返回 OS_BUSY,放弃本次睡眠。
第 2 步:计算最长睡眠时长并判断是否值得(条件 timeout_tick != OS_TICK_MAX 且有足够时长)
os_spin_lock_irqsave(&gs_device_lock, &level);
os_tick_t timeout_tick = os_tickless_get_sleep_ticks();
if (timeout_tick != OS_TICK_MAX)
{
nsec = ((uint64_t)timeout_tick * lpmgr->mult >> lpmgr->shift);
if (nsec < lpmgr->lpce->min_nsec || nsec < BSP_USING_MINSLEEP_MS * 1000000)
{
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
os_spin_unlock_irqrestore(&gs_device_lock, level);
return OS_BUSY;
}
}详细解释:Tickless 核心计算:
os_tickless_get_sleep_ticks()返回距离下一个软件定时器到期的 tick 数;OS_TICK_MAX表示无定时器到期(可无限睡),否则有确定的可睡眠时长。- 用
nsec = timeout_tick * mult >> shift把 tick 换算成纳秒(mult/shift由os_lpmgr_init用calc_mult_shift算出,见 3.1 字段)。 - 关键判断(是否值得睡):若
nsec小于低功耗定时器最小分辨率lpce->min_nsec,或小于配置的最小睡眠时间BSP_USING_MINSLEEP_MS(ms),说明睡眠时间太短——唤醒/挂起开销大于省电收益,返回OS_BUSY放弃。
数值演算 —— tick → 纳秒换算(假设 timeout_tick = 1000 tick、mult = 15258、shift = 15,即 mult>>shift ≈ 1000/1000000,把 1 tick≈0.466ms):
| 计算项 | 代入 | 结果 |
|---|---|---|
timeout_tick * mult | 1000 * 15258 | 15258000 |
>> shift | 15258000 >> 15 | 15258000 / 32768 ≈ 465.6 |
nsec | —— | 约 465600 ns = 0.466 ms |
与 min_nsec 比较 | 若 min_nsec = 1ms | 0.466ms < 1ms → 太短,返回 OS_BUSY |
第 3 步:挂起外设并启动唤醒定时器(条件 timeout_tick != OS_TICK_MAX,且挂起成功)
ret = lpmgr_enter_sleep_call(lpmgr->sleep_mode);
if (ret != OS_SUCCESS)
{
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
lpmgr_exit_sleep_call(lpmgr->sleep_mode);
os_spin_unlock_irqrestore(&gs_device_lock, level);
return OS_BUSY;
}
if (nsec > lpmgr->lpce->max_nsec)
nsec = lpmgr->lpce->max_nsec;
gs_lp_sleep_nsec = nsec;
os_clockevent_start_oneshot(lpmgr->lpce, nsec);
}
else
{
gs_lp_sleep_nsec = 0;
os_clockevent_stop(lpmgr->lpce);
ret = lpmgr_enter_sleep_call(lpmgr->sleep_mode);
if (ret != OS_SUCCESS)
{
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
lpmgr_exit_sleep_call(lpmgr->sleep_mode);
os_spin_unlock_irqrestore(&gs_device_lock, level);
return OS_BUSY;
}
}详细解释:lpmgr_enter_sleep_call(见 5.4)通知 SYS_ENTER_SLEEP 并逐一挂起外设,失败则回滚(恢复外设 + 通知 SYS_EXIT_SLEEP)并返回 OS_BUSY。成功后:
- 有超时分支(
timeout_tick != OS_TICK_MAX):nsec截断到lpce->max_nsec(防溢出),存gs_lp_sleep_nsec,启动低功耗定时器在nsec后触发唤醒(保证最长睡眠不超过下一个定时器到期时间)。 - 无超时分支(
else):gs_lp_sleep_nsec = 0(表示无唤醒定时器,可能睡到外部事件),os_clockevent_stop停定时器,直接lpmgr_enter_sleep_call挂起外设。
第 4 步:执行 HAL 睡眠并恢复(无条件执行)
gs_lp_sleep_state = LP_SLEEP_STATUS_ACTIVE;
os_spin_unlock_irqrestore(&gs_device_lock, level);
lpmgr->sleep(lpmgr->sleep_mode);
OS_ASSERT(gs_lp_sleep_state != LP_SLEEP_STATUS_ACTIVE);
gs_lp_sleep_state = LP_SLEEP_STATUS_NONE;
pre_gettick_dev_resume(SYS_SLEEP_MODE_NONE);
lpmgr_exit_sleep_call(lpmgr->sleep_mode);
return OS_SUCCESS;
}详细解释:
- 置
gs_lp_sleep_state = LP_SLEEP_STATUS_ACTIVE(标记"正在睡眠"),先解锁再调lpmgr->sleep(mode)——因为 HAL 睡眠(WFI/STOP)会阻塞,不能在持锁状态下睡。 lpmgr->sleep(mode)让 HAL 执行真正睡眠,阻塞直到被中断唤醒。- 唤醒后:中断里
os_lpmgr_irq_entry(OS_IRQ_HOOK)会把gs_lp_sleep_state改为UPDATED并做时间补偿(见 5.5)。所以OS_ASSERT(gs_lp_sleep_state != ACTIVE)断言必真(若仍 ACTIVE 说明没走中断钩子,异常)。 - 把状态置回
NONE,恢复预挂起设备、调lpmgr_exit_sleep_call(恢复外设 + 通知SYS_EXIT_SLEEP)。
lpmgr_start() 条件走向总结:
lpmgr_start(lpmgr) 条件走向总结:
├── pre_gettick_dev_suspend() 失败
│ ├── pre_gettick_dev_resume() 回滚
│ └── return OS_BUSY
├── pre_gettick_dev_suspend() 成功,加锁
│ ├── 若 timeout_tick != OS_TICK_MAX(有定时器到期)
│ │ ├── nsec = timeout_tick * mult >> shift(tick→ns)
│ │ ├── nsec < lpce->min_nsec 或 < BSP_USING_MINSLEEP_MS(太短不值得)
│ │ │ ├── pre_gettick_dev_resume();解锁
│ │ │ └── return OS_BUSY
│ │ ├── lpmgr_enter_sleep_call() 失败(有外设挂不起)
│ │ │ ├── pre_gettick_dev_resume();lpmgr_exit_sleep_call();解锁
│ │ │ └── return OS_BUSY
│ │ └── 成功:nsec 截断到 max_nsec;gs_lp_sleep_nsec = nsec;
│ │ os_clockevent_start_oneshot(lpce, nsec) 启动唤醒定时器
│ └── 若 timeout_tick == OS_TICK_MAX(无定时器到期,可无限睡)
│ ├── gs_lp_sleep_nsec = 0;os_clockevent_stop(lpce)
│ └── lpmgr_enter_sleep_call()(失败则回滚并 return OS_BUSY)
│ ├── gs_lp_sleep_state = ACTIVE;解锁
│ ├── lpmgr->sleep(mode) ← HAL 真正睡眠,阻塞至唤醒
│ ├── OS_ASSERT(state != ACTIVE);state = NONE
│ ├── pre_gettick_dev_resume();lpmgr_exit_sleep_call()
│ └── return OS_SUCCESS5.4 挂起/恢复调用链 lpmgr_enter_sleep_call / lpmgr_exit_sleep_call
完整源码(drivers/lpmgr/lpmgr.c,与项目一致):
static int lpmgr_enter_sleep_call(lpmgr_sleep_mode_e mode)
{
int ret = OS_SUCCESS;
/* Notify app will enter sleep mode */
lpmgr_notify_call(SYS_ENTER_SLEEP, mode);
/* Suspend all peripheral device */
ret = lpmgr_device_suspend(mode);
if (ret != OS_SUCCESS)
{
lpmgr_device_resume(mode);
lpmgr_notify_call(SYS_EXIT_SLEEP, mode);
return OS_FAILURE;
}
return OS_SUCCESS;
}
static int lpmgr_exit_sleep_call(lpmgr_sleep_mode_e mode)
{
lpmgr_device_resume(mode);
lpmgr_notify_call(SYS_EXIT_SLEEP, mode);
return OS_SUCCESS;
}分步详解:
第 1 步:进入睡眠:通知 + 挂起(条件 挂起失败则回滚)
lpmgr_notify_call(SYS_ENTER_SLEEP, mode);
ret = lpmgr_device_suspend(mode);
if (ret != OS_SUCCESS)
{
lpmgr_device_resume(mode);
lpmgr_notify_call(SYS_EXIT_SLEEP, mode);
return OS_FAILURE;
}详细解释:先 lpmgr_notify_call(SYS_ENTER_SLEEP, mode) 通知所有注册的通知回调。然后 lpmgr_device_suspend(mode) 逐一遍历 gs_register_dev_list 调用每个外设的 suspend。关键点:若有外设 suspend 失败,lpmgr_device_suspend 返回非 0,此时调用 lpmgr_device_resume 回滚已挂起的外设,并通知 SYS_EXIT_SLEEP,返回失败(放弃本次睡眠)。
第 2 步:退出睡眠:恢复 + 通知(无条件执行)
lpmgr_device_resume(mode);
lpmgr_notify_call(SYS_EXIT_SLEEP, mode);
}详细解释:唤醒后逐一 lpmgr_device_resume 恢复外设,通知 SYS_EXIT_SLEEP。
lpmgr_enter_sleep_call() 条件走向总结:
lpmgr_enter_sleep_call(mode) 条件走向总结:
├── lpmgr_notify_call(SYS_ENTER_SLEEP, mode) 通知将进入睡眠
├── ret = lpmgr_device_suspend(mode) 逐一挂起外设
│ ├── ret == OS_SUCCESS(所有外设挂起成功)
│ │ └── return OS_SUCCESS
│ └── ret != OS_SUCCESS(某外设挂起失败)
│ ├── lpmgr_device_resume(mode) 回滚已挂起外设
│ ├── lpmgr_notify_call(SYS_EXIT_SLEEP, mode) 通知取消进入
│ └── return OS_FAILURElpmgr_device_suspend(逐一挂起,失败即停):
static int lpmgr_device_suspend(lpmgr_sleep_mode_e mode)
{
int ret = OS_SUCCESS;
struct os_lpmgr_device *device_lpm;
os_list_for_each_entry(device_lpm, &gs_register_dev_list, struct os_lpmgr_device, list)
{
ret = device_lpm->ops->suspend(device_lpm->priv, mode);
if (ret != OS_SUCCESS)
break;
}
return ret;
}5.5 中断唤醒与时间补偿 os_lpmgr_irq_entry
一句话定位:OS_IRQ_HOOK 注册的中断钩子,睡眠期间中断发生时计算实际睡眠时长并补偿系统时钟。
函数签名:
static void os_lpmgr_irq_entry(int irq);| 参数 | 说明 |
|---|---|
irq | 触发本次中断的中断号;gs_lpce_irq 是低功耗定时器自身的中断号,用于区分"被 LPCE 唤醒"还是"被其他外设中断提前唤醒" |
完整源码(drivers/lpmgr/lpmgr.c,与项目一致):
static void os_lpmgr_irq_entry(int irq)
{
os_ubase_t level;
struct os_lpmgr_dev *lpmgr = gs_lpmgr;
os_spin_lock_irqsave(&gs_device_lock, &level);
if (gs_lp_sleep_state == LP_SLEEP_STATUS_ACTIVE)
{
if (gs_lp_sleep_nsec != 0)
{
if (irq != gs_lpce_irq)
{
gs_lp_sleep_nsec = os_clockevent_read(lpmgr->lpce);
os_clockevent_stop(lpmgr->lpce);
}
os_lpmgr_update(gs_lp_sleep_nsec);
}
gs_lp_sleep_state = LP_SLEEP_STATUS_UPDATED;
}
if (gs_lp_sleep_state != LP_SLEEP_STATUS_NONE)
{
/* period trig 1ms, avoid sleep forever */
os_clockevent_start_oneshot(lpmgr->lpce, max(1000000ULL, lpmgr->lpce->min_nsec));
}
os_spin_unlock_irqrestore(&gs_device_lock, level);
}
OS_IRQ_HOOK(os_lpmgr_irq_entry);分步详解:
第 1 步:若在睡眠中,计算睡眠时长并补偿(条件 睡眠状态 ACTIVE)
if (gs_lp_sleep_state == LP_SLEEP_STATUS_ACTIVE)
{
if (gs_lp_sleep_nsec != 0)
{
if (irq != gs_lpce_irq)
{
gs_lp_sleep_nsec = os_clockevent_read(lpmgr->lpce);
os_clockevent_stop(lpmgr->lpce);
}
os_lpmgr_update(gs_lp_sleep_nsec);
}
gs_lp_sleep_state = LP_SLEEP_STATUS_UPDATED;
}详细解释:若处于 ACTIVE(睡眠中)且设了唤醒定时器(gs_lp_sleep_nsec != 0):若唤醒不是因为低功耗定时器自身(irq != gs_lpce_irq,即被其他中断提前唤醒),读 os_clockevent_read 算实际睡眠时长并停定时器。然后 os_lpmgr_update(gs_lp_sleep_nsec) 用实际睡眠纳秒数补偿系统时钟(把 tick 往前推进,见 os_kernel_update_ns)。
第 2 步:重启周期定时器防睡死(条件 状态非 NONE)
if (gs_lp_sleep_state != LP_SLEEP_STATUS_NONE)
{
os_clockevent_start_oneshot(lpmgr->lpce, max(1000000ULL, lpmgr->lpce->min_nsec));
}详细解释:关键点:防止意外"睡死",重新启动低功耗定时器在至少 1ms 后触发一次,保证系统不会因错过唤醒而永久沉睡。
os_lpmgr_irq_entry() 条件走向总结:
os_lpmgr_irq_entry(irq) 条件走向总结:
├── gs_lp_sleep_state == LP_SLEEP_STATUS_ACTIVE(睡眠中)
│ ├── gs_lp_sleep_nsec != 0(设了唤醒定时器)
│ │ ├── irq != gs_lpce_irq(被其他中断提前唤醒)
│ │ │ ├── gs_lp_sleep_nsec = os_clockevent_read() 算实际睡眠时长
│ │ │ └── os_clockevent_stop() 停 LPCE
│ │ ├── irq == gs_lpce_irq(被 LPCE 自身唤醒)
│ │ │ └── 用预设的 gs_lp_sleep_nsec
│ │ └── os_lpmgr_update(gs_lp_sleep_nsec) 补偿系统时钟
│ └── gs_lp_sleep_nsec == 0(无唤醒定时器,睡到外部事件)
│ └── 不补偿(无法精确计时)
│ └── gs_lp_sleep_state = LP_SLEEP_STATUS_UPDATED
├── gs_lp_sleep_state != LP_SLEEP_STATUS_NONE(曾进入睡眠)
│ └── os_clockevent_start_oneshot(lpce, max(1ms, min_nsec)) 防睡死
└── 解锁返回六、睡眠流程
┌──────────────────────────────────────────────────────────────┐
│ 低功耗管理睡眠流程 │
│ │
│ 1. 空闲任务调用 os_low_power_manager() │
│ ↓ │
│ 2. 检查 sleep_mode > IDLE? │
│ ├── NO → 仅执行 WFI/WFE,不进入深度睡眠 │
│ └── YES → 继续 │
│ ↓ │
│ 3. 计算最长睡眠时间(Tickless),时间太短则放弃 │
│ ↓ │
│ 4. pre_gettick_dev_suspend() → 预挂起设备 │
│ ↓ │
│ 5. lpmgr_enter_sleep_call() → 通知 SYS_ENTER_SLEEP │
│ └→ lpmgr_device_suspend() → 逐一挂起所有注册的外设 │
│ ↓ │
│ 6. lpmgr->sleep(mode) → HAL 层执行睡眠(WFI/WFE) │
│ ↓ ← 中断唤醒 │
│ 7. lpmgr_exit_sleep_call() → 通知 SYS_EXIT_SLEEP │
│ └→ lpmgr_device_resume() → 逐一恢复所有注册的外设 │
│ ↓ │
│ 8. pre_gettick_dev_resume() → 恢复预挂起设备 │
│ ↓ │
│ 9. os_lpmgr_update() → 补偿系统时钟 │
│ ↓ │
│ 10. 恢复正常运行 │
└──────────────────────────────────────────────────────────────┘七、代码示例
7.1 外设驱动注册低功耗回调
#include <oneos_config.h>
#include <lpmgr.h>
typedef struct { os_device_t *uart_dev; } my_uart_priv_t;
static my_uart_priv_t g_uart_priv;
static os_err_t uart_suspend(void *priv, lpmgr_sleep_mode_e mode)
{
os_kprintf("UART suspend for mode %d\r\n", mode);
if (mode >= SYS_SLEEP_MODE_DEEP)
hal_uart_disable_clock();
return OS_SUCCESS;
}
static void uart_resume(void *priv, lpmgr_sleep_mode_e mode)
{
os_kprintf("UART resume from mode %d\r\n", mode);
if (mode >= SYS_SLEEP_MODE_DEEP)
{
hal_uart_enable_clock();
hal_uart_reconfigure();
}
}
static const struct os_lpmgr_device_ops uart_lpm_ops = {
.suspend = uart_suspend,
.resume = uart_resume,
};
void uart_lpmgr_init(void)
{
os_lpmgr_device_register(&g_uart_priv, &uart_lpm_ops);
}7.2 应用层请求/释放睡眠模式
void app_power_management(void)
{
/* 应用需要保持外设运行,不允许深度睡眠 */
os_lpmgr_request(SYS_SLEEP_MODE_IDLE);
os_kprintf("Requested IDLE mode (no deep sleep)\r\n");
do_network_communication();
/* 任务完成,允许深度睡眠 */
os_lpmgr_release(SYS_SLEEP_MODE_IDLE);
os_kprintf("Released IDLE restriction\r\n");
}7.3 睡眠/唤醒通知
static void my_sleep_notify(os_lpmgr_sys_e event, lpmgr_sleep_mode_e mode, void *data)
{
switch (event)
{
case SYS_ENTER_SLEEP:
os_kprintf("[App] About to enter sleep mode %d\r\n", mode);
break;
case SYS_EXIT_SLEEP:
os_kprintf("[App] Exited sleep mode %d\r\n", mode);
break;
}
}
static struct lpmgr_notify g_notify = { .notify = my_sleep_notify, .data = "MyApp" };
void register_sleep_notify(void)
{
os_lpmgr_notify_register(&g_notify);
}7.4 初始化低功耗管理
#include <lpmgr.h>
static os_err_t hal_sleep(lpmgr_sleep_mode_e mode)
{
switch (mode)
{
case SYS_SLEEP_MODE_IDLE:
__WFI();
break;
case SYS_SLEEP_MODE_LIGHT:
hal_clock_disable_peripheral();
__WFI();
hal_clock_enable_peripheral();
break;
case SYS_SLEEP_MODE_DEEP:
hal_prepare_stop_mode();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
hal_restore_from_stop();
break;
default: break;
}
return OS_SUCCESS;
}
void system_lpmgr_init(void)
{
os_lpmgr_init(hal_sleep);
os_kprintf("LPMGR initialized\r\n");
}7.5 Shell 命令调试
power_request 1 # 请求 IDLE 模式(禁止深度睡眠)
power_release 1 # 释放 IDLE 模式限制
power_status # 查看当前低功耗状态输出示例:
| Power Management Mode | Counter | Timer |
+-----------------------+---------+-------+
lpmgr current sleep mode: LightSleep Mode
| no | priv | ops |
+-------+------------+------------+
| 0 | 0x20001234 | 0x08004567 |
total register dev num: 1八、LPMGR 的适配(接入新 MCU 的低功耗模式)
8.1 先认识适配层:LPMGR 是怎么"长"到芯片上的
drivers/lpmgr/ 下是跨平台统一的框架层(管理模式决策、外设挂起/恢复、Tickless 补偿、通知),不碰具体低功耗寄存器。真正进入/退出某芯片睡眠模式的是各厂商 HAL 驱动,例如 ST 的 drivers/hal/st/drivers/drv_lpmgr.c、GD 的 drv_lpmgr.c、nRF 的 drv_lpmgr.c。换 MCU 就是换这份 drv_lpmgr.c。
适配全景图:
┌──────────────────────────────────────────────────────────┐
│ 应用层 / 外设驱动 │
│ os_lpmgr_request/release、os_lpmgr_device_register │
│ os_lpmgr_notify_register(声明约束 / 挂起恢复 / 通知) │
└──────────────────────────────┬───────────────────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ LPMGR 框架层(drivers/lpmgr/lpmgr.c) │
│ os_low_power_manager / lpmgr_start / Tickless 补偿 │
│ 模式决策、外设协调、时间补偿(不改) │
└──────────────────────────────┬───────────────────────────┘
│ os_lpmgr_init(lpm_sleep) 注册睡眠回调
▼
┌──────────────────────────────────────────────────────────┐
│ 厂商驱动适配层(drv_lpmgr.c)★需实现★ │
│ 实现 lpm_sleep(mode):switch 各模式调 HAL 进入/退出睡眠 │
└──────────────────────────────┬───────────────────────────┘
│ HAL 库(HAL_PWREx_Enter*Mode 等)
▼
┌──────────────────────────────────────────────────────────┐
│ 硬件(MCU 的 SLEEP / STOP / STANDBY / SHUTDOWN) │
└──────────────────────────────────────────────────────────┘适配的关键洞察:厂商驱动只需实现一个 lpm_sleep(mode) 回调(按模式调 HAL 进入对应睡眠),然后 os_lpmgr_init(lpm_sleep) 把它注册给框架。框架层、外设协调、Tickless 都不需要改。
8.2 以 ST 为例,看一整套适配代码
以 drivers/hal/st/drivers/drv_lpmgr.c 为例(与项目逐字一致,去除无关的空行/注释后展示关键结构):
① 实现 lpm_sleep —— 各模式调 HAL 进入睡眠:
static os_err_t lpm_sleep(lpmgr_sleep_mode_e mode)
{
switch (mode)
{
case SYS_SLEEP_MODE_NONE:
break;
case SYS_SLEEP_MODE_IDLE:
break;
case SYS_SLEEP_MODE_LIGHT:
HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
break;
case SYS_SLEEP_MODE_DEEP:
/* Enter STOP 2 mode */
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
// HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
// HAL_PWR_EnterSTANDBYMode();
// HAL_PWREx_EnterSHUTDOWNMode();
// HAL_PWR_EnterSTANDBYMode();
/* Re-configure the system clock */
// SystemClock_Config();
break;
default:
OS_ASSERT(0);
}
return OS_SUCCESS;
}要点:
lpm_sleep的switch覆盖框架支持的每种模式,每个 case 调用对应的 HAL 进入睡眠 API(如HAL_PWR_EnterSLEEPMode、HAL_PWREx_EnterSTOP2Mode)。注意:LIGHT/DEEP这些深度睡眠会停主时钟,唤醒后可能需要重新配置系统时钟(注释里的SystemClock_Config())。
② 初始化并注册给框架:
static int drv_lpmgr_hw_init(void)
{
/* Enable power clock */
__HAL_RCC_PWR_CLK_ENABLE();
/* system lpmgr module */
os_lpmgr_init(lpm_sleep);
return 0;
}
OS_INIT_CALL(drv_lpmgr_hw_init, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_MIDDLE);要点:
drv_lpmgr_hw_init里先使能电源时钟(__HAL_RCC_PWR_CLK_ENABLE),再调os_lpmgr_init(lpm_sleep)把睡眠回调注册给框架(gs_lpmgr在os_lpmgr_init内创建)。用OS_INIT_CALL在OS_INIT_LEVEL_PRE_DEVICE(设备层之前)初始化——保证系统一开始就有 LPMGR 可用。
8.3 接口契约:接入 LPMGR 需要实现什么
| 层 | 接口/回调 | 职责 | 对应 8.2 ST 代码 |
|---|---|---|---|
| 框架入口 | os_lpmgr_init(sleep) | 初始化框架并注册睡眠回调 | drv_lpmgr_hw_init 里的 os_lpmgr_init(lpm_sleep) |
| 睡眠回调 | lpm_sleep(mode) | 按模式调 HAL 进入/退出睡眠 | lpm_sleep |
| 初始化时机 | OS_INIT_CALL | 在设备层之前初始化 | OS_INIT_CALL(drv_lpmgr_hw_init, OS_INIT_LEVEL_PRE_DEVICE, OS_INIT_SUBLEVEL_MIDDLE) |
| 低功耗定时器 | LPMGR_TIMER_DEVICE_NAME | 深度睡眠的唤醒源(LPCE),由 os_lpmgr_init 打开 | 板级需提供 LPCE 时钟源 |
补充:
os_lpmgr_init内部会os_device_open_s(LPMGR_TIMER_DEVICE_NAME)打开低功耗定时器(LPCE),因此板级必须提供可用的 LPCE 定时器设备,且要求lpce->prescaler_mask == 1(见lpmgr.c的断言)。这是适配时容易漏的一点。
8.4 适配新 MCU 的标准流程
第 1 步:确认 MCU 的低功耗模式能力——查数据手册确认支持哪些睡眠模式(SLEEP/STOP/STANDBY),以及对应的唤醒源(EXTI、RTC、低功耗定时器等)。
第 2 步:新建厂商 drv_lpmgr.c——在对应厂商 HAL 目录(如 drivers/hal/my_chip/drivers/)新建,实现 lpm_sleep(mode),每个 case 调该芯片 HAL 的睡眠 API。
第 3 步:提供低功耗定时器(LPCE)——确认板级有可作唤醒源的 LPCE 定时器,设备名匹配 LPMGR_TIMER_DEVICE_NAME,且 prescaler_mask == 1。
第 4 步:初始化注册——在 drv_lpmgr_hw_init 里使能时钟 + os_lpmgr_init(lpm_sleep),用 OS_INIT_CALL 指定在设备层前初始化。
第 5 步:测试验证——用 power_request/power_release/power_status 命令(见 7.5)验证各模式能正常进出,并用示波器/功耗仪确认电流变化。
8.5 适配检查清单(快速自查)
| 检查项 | 说明 |
|---|---|
lpm_sleep 覆盖所有模式? | 遗漏的模式会走到 default: OS_ASSERT(0) 断言崩溃 |
| LPCE 定时器可用? | 无 LPCE 时 os_lpmgr_init 里 os_device_open_s 返回空、断言失败 |
LPCE prescaler_mask == 1? | 不满足则 os_lpmgr_init 断言失败 |
| 深度睡眠唤醒后时钟恢复? | STOP/STANDBY 停主时钟,需在唤醒路径重配 SystemClock_Config |
| 初始化层级在设备层之前? | OS_INIT_LEVEL_PRE_DEVICE 保证系统一启动就有 LPMGR |
| 外设都注册了 suspend/resume? | 未注册外设睡眠时不会挂起,可能损坏数据 |
九、注意事项
- suspend 返回值:如果某外设的
suspend返回失败,框架会回滚已挂起的外设并放弃本次睡眠 - 最小睡眠时间:通过
BSP_USING_MINSLEEP_MS宏配置最小睡眠时间,太短不值得进入深度睡眠 - 低功耗定时器:深度睡眠时系统主时钟停止,需使用低功耗定时器(LPCE)作为唤醒源
- 中断唤醒:任何中断都可以唤醒系统,
os_lpmgr_irq_entry通过OS_IRQ_HOOK注册为中断钩子 - 时间补偿:唤醒后自动补偿系统 tick,保证定时器准确性
- request/release 必须成对:漏 release 会让计数一直 >0,系统永远进不了更深睡眠
📝 本节小结
核心要点回顾
本节围绕 LPMGR 设备展开,核心要点如下:
- 低功耗管理框架:LPMGR 协调 CPU 和外设进入/退出低功耗状态,框架层在
drivers/lpmgr/,厂商适配在drivers/hal/*/drivers/drv_lpmgr.c。 - 多级睡眠模式:NONE/IDLE/LIGHT/DEEP,功耗和唤醒延迟逐级增加(NONE 最浅、DEEP 最深)。
- 引用计数决策:
os_lpmgr_request/release用modes[]引用计数声明"允许进入的最深睡眠",lpmgr_check_sleep_mode取所有请求中最浅(最保守)的模式——有一个模块不允许深度睡眠就"一票否决"。 - 外设参与管理:外设通过
os_lpmgr_device_register注册 suspend/resume 回调(挂到gs_register_dev_list链表),睡眠前逐一挂起、唤醒后逐一恢复,挂起失败则回滚。 - Tickless 机制:
lpmgr_start用timeout_tick * mult >> shift算可睡眠纳秒,太短则放弃;唤醒后os_lpmgr_irq_entry用实际睡眠时长补偿系统时钟,保证定时器精度。 - 通知机制:
os_lpmgr_notify_register注册睡眠/唤醒通知回调(SYS_ENTER_SLEEP/SYS_EXIT_SLEEP)。 - 硬件适配:只需实现
lpm_sleep(mode)(各模式调 HAL 进入睡眠)+os_lpmgr_init(lpm_sleep)+ 提供 LPCE 定时器,框架层不用改。 - Shell 调试:
power_request/power_release/power_status便于调试。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
| 误以为取"最低功耗"模式 | lpmgr_check_sleep_mode 是取所有请求中最浅的模式(满足最保守的模块),不是取最深 |
| request/release 不成对 | 漏 release 会让计数 >0,系统永远进不了更深睡眠;必须成对 |
未 os_lpmgr_init 就 request | gs_lpmgr == NULL,request/release 解引用空指针崩溃 |
| 外设未注册 suspend/resume 就睡眠 | 外设必须先用 os_lpmgr_device_register 注册回调,否则睡眠时外设数据丢失 |
| 忽略 Tickless 时间补偿 | 睡眠期间定时器不走,系统需靠时间补偿保证 os_tick_get 准确 |
| 在 suspend/resume 里做阻塞操作 | 挂起/恢复回调在低功耗流程中执行,不能阻塞或申请内存 |
lpm_sleep 漏实现某模式 | 走到 default: OS_ASSERT(0) 断言崩溃 |
| 无 LPCE 定时器 | os_lpmgr_init 里 os_device_open_s 返回空、断言失败;必须提供可用 LPCE 且 prescaler_mask == 1 |
下一步
掌握了 LPMGR 低功耗管理后,接下来学习 CAN 总线设备:
- 5.16 CAN设备:了解 OneOS 的 CAN 消息收发、过滤器配置、波特率设置与状态监控。