电源管理
Categories:
2 分钟阅读
概述
XWOS有两套电源管理框架,分别用于MP系统和UP系统。
XWOS的电源管理框架只提供了基本流程,最终SOC如何休眠由BSP中的回调函数实现: 用户可以自行决定是否保持SDRAM的刷新、是否保持某些GPIO的输出、或则是否进入最低功耗的待机等等。
MP系统的电源管理
电源管理领域
MP系统中,CPU不唯一,XWOS中使用 电源管理领域(PMDM) 来统一管理所有CPU的休眠与唤醒。
流程
图中,左列为休眠流程,右列为唤醒流程,箭头代表了可以进行 电源管理阶段 转换的方向。 电源管理领域 将电源管理分为四个阶段(图中绿色的框):
正在运行 ( XWMP_PMDM_STAGE_RUNNING
)
所有CPU正常运行,可以使用下面CAPI进入休眠的流程:
xwer_t xwos_pm_suspend(void);
此CAPI是操作系统抽象层 OSAL 中的CAPI,实际调用的是:
xwer_t xwmp_pmdm_suspend(void);
冻结调度器 —— 解冻调度器 ( XWMP_PMDM_STAGE_FREEZING - XWMP_PMDM_STAGE_THAWING
)
一旦开始休眠流程,PMDM会通知所有CPU冻结线程。当调度器中线程全部冻结后,调度器会向PMDM报告 已经暂停 的状态。 调度器冻结线程的过程是一个比较复杂的过程,这个阶段又分为几个子阶段(图中蓝色的框):
冻结线程 —— 解冻线程 ( XWMP_SKD_WKLKCNT_FREEZING - XWMP_SKD_WKLKCNT_THAWING
)
调度器会中断所有线程的 等待 和 睡眠 态,并为所有线程设置 可冻结 标志, 然后依次调度每个线程,直到它们运行到 冻结点 进行冻结,这个过程耗时比较长, 在执行过程中如果遇到唤醒事件,调度器会在唤醒事件中断中切换为解冻流程: 解冻已经冻结的线程,并取消未冻结的线程的 可冻结 标志。
全部线程已经冻结 ( XWMP_SKD_WKLKCNT_ALLFRZ
)
最后一个线程完成冻结时,CPU处于调度器服务中断中,调度器会将滴答定时器关闭。 如果此刻出现唤醒事件,待CPU从调度器服务中断中退出后,会立即进入唤醒事件中断。 休眠流程切换为唤醒流程,并重新打开滴答定时器。 按照 调度器的中断 中的约束,唤醒事件中断的优先级低于调度器服务中断, 因此唤醒事件中断一定会在调度器服务中断退出后才开始执行, 滴答定时器一定会先关闭,然后再打开,顺序不会错乱。
调度器已经暂停 ( XWMP_SKD_WKLKCNT_SUSPENDED
)
CPU从上一步骤的调度器服务中断中退出后,会切换回线程上下文,此时因所有线程已经冻结, CPU只可能运行在 空闲任务 中。 CPU会在空闲任务中向PMDM报告调度器 已经暂停 的状态。此过程不能在调度器服务中断中进行, 因为调度器服务中断是最高优先级中断,不能被唤醒事件中断打断,有可能会导致无法唤醒的问题。
正在暂停 —— 正在恢复 ( XWMP_PMDM_STAGE_SUSPENDING - XWMP_PMDM_STAGE_RESUMING
)
当最后一个CPU报告了 已经暂停 的状态后,会在最后一个CPU的 空闲任务 中执行用户的 suspend()
回调函数。
此时,如果出现唤醒事件,电源管理的阶段会从 正在暂停 切换为 正在恢复 ,并执行用户的 resume()
回调函数。
suspend()
回调函数用于在休眠之前让用户关闭设备、配置SDRAM刷新模式、配置GPIO等;resume()
回调函数用于在唤醒之前执行与suspend()
回调函数相反的操作;- 执行这两个函数时,PMDM会自动锁定 resume-suspend锁 ,并关闭当前CPU的总中断开关。
resume-suspend锁 是自旋锁,如果CPU-1正在执行
suspend()
回调函数,此时出现了唤醒事件,会分下面两种情况进行处理:- 唤醒事件中断也绑定在CPU-1上,唤醒事件中断会挂起,直到
suspend()
回调函数返回, PMDM解锁 resume-suspend锁 并打开CPU总中断开关,才被处理,然后执行resume()
回调函数; - 唤醒事件中断绑定在CPU-2上,CPU-2会立即进入唤醒事件中断,但一直会 自旋 等待CPU-1释放 resume-suspend锁 ,
直到获得 resume-suspend锁 后,才会执行
resume()
回调函数;
- 唤醒事件中断也绑定在CPU-1上,唤醒事件中断会挂起,直到
- 示例WeActMiniStm32H750的
resume()
与suspend()
回调函数:
/* XWOS/xwbd/WeActMiniStm32H750/bm/xwac/xwds/pm.c */
void stm32cube_pm_resume(void)
{
...
/* 恢复stm32cube中的所有设备 */
xwds_pm_resume(&stm32cube_ds);
}
void stm32cube_pm_suspend(void)
{
...
/* 暂停stm32cube:
+ 暂停所有设备
+ 配置GPIO */
xwds_pm_suspend(&stm32cube_ds);
/* 设置休眠方式为STOP模式:
STOP模式下寄存器与内部RAM数据不丢失,
因此休眠方式为SuspendToRAM,唤醒后运行状态可恢复。*/
LL_PWR_SetRegulModeDS(LL_PWR_REGU_DSMODE_LOW_POWER);
LL_PWR_EnableFlashPowerDown();
LL_PWR_CPU_SetD1PowerMode(LL_PWR_CPU_MODE_D1STOP);
LL_PWR_CPU_SetD2PowerMode(LL_PWR_CPU_MODE_D2STOP);
LL_PWR_CPU_SetD3PowerMode(LL_PWR_CPU_MODE_D3STOP);
LL_LPM_EnableDeepSleep();
/* 设置ARMv7-m的DEEPSLEEP位 */
LL_LPM_EnableDeepSleep();
}
已经暂停 ( XWMP_PMDM_STAGE_SUSPENDED
)
此阶段为休眠流程的最后一个阶段,也是唤醒流程的第一个阶段。此阶段提供给用户的回调函数:
sleep()
:休眠SOCwakeup()
:唤醒SOC
当最后一个CPU的 空闲任务 执行完上一阶段的 suspend()
回调函数后,
PMDM将电源管理步骤切换到此阶段,并执行 sleep()
回调函数。
SOC系统在 sleep()
回调函数内部进入低功耗状态,
此时时钟停止,代码停止运行, sleep()
回调函数不会返回。
XWOS将 sleep()
回调函数设计在空闲任务中执行,是因为中断优先级的问题有可能导致系统无法唤醒。
例如基于ARM-m的单片机,如果在一个高优先级的中断中执行了休眠指令( WFI
),低优先级的唤醒中断无法把系统唤醒。
当唤醒事件出现, wakeup()
回调函数在唤醒事件中断中执行。
回调函数 sleep()
与 wakeup()
之间没有锁的保护,
因此 sleep()
回调函数需要设计成能被 wakeup()
回调函数打断。
示例,WeActMiniStm32H750的 wakeup()
与 sleep()
回调函数:
/* XWOS/xwbd/WeActMiniStm32H750/bm/xwac/xwds/pm.c */
void stm32cube_pm_wakeup(void)
{
LL_LPM_EnableSleep(); /* 清除DEEPSLEEP位 */
SystemClock_Config(); /* 从STOP模式恢复后,需要重新配置时钟 */
}
void stm32cube_pm_sleep(void)
{
/* 位置1 */
cm_wfi();
/* 位置2 */
}
分为两种情况讨论:
stm32cube_pm_sleep()
中的WFI
指令执行后出现唤醒事件: 唤醒流程执行完毕后,代码会回到stm32cube_pm_sleep()
中 位置2 ;stm32cube_pm_sleep()
中的WFI
指令执行之前出现唤醒事件: 唤醒事件中断会打断stm32cube_pm_sleep()
函数, 然后执行stm32cube_pm_wakeup()
函数, 函数LL_LPM_EnableSleep()
会清除ARMv7-m的DEEPSLEEP位,接下来的唤醒流程也会将滴答定时器重新打开, 当再次回到stm32cube_pm_sleep()
的 位置1 时,WFI
指令只会使CPU进入ARMv7-m的SLEEP模式, 只是短暂地暂停了一下CPU时钟,即将到来的滴答定时器中断可使系统恢复正常。
UP内核的电源管理
流程
图中,左列为休眠流程,右列为唤醒流程,箭头代表了 电源管理阶段 切换的方向。 UP系统电源管理分为五个阶段:
正在运行 ( XWUP_PM_STAGE_RUNNING
)
调度器正常运行,可以使用下面CAPI进入休眠的流程:
xwer_t xwup_skd_suspend(void);
正在冻结线程 —— 正在解冻线程 ( XWUP_PM_STAGE_FREEZING - XWUP_PM_STAGE_THAWING
)
一旦开始休眠流程,调度器会中断所有线程的 等待 和 睡眠 态,并通知所有线程冻结。这个过程耗时比较长, 在执行过程中如果遇到唤醒事件,调度器可在唤醒事件中断中切换为解冻流程:解冻已冻结的线程,并将调度器状态逐步恢复至 正在运行 。
调度器中全部线程已经冻结 ( XWUP_PM_STAGE_ALLFRZ
)
最后一个线程完成冻结时,CPU处于调度器服务中断中,调度器会将滴答定时器关闭。 如果此刻出现唤醒事件,待CPU从调度器服务中断中退出后,会立即进入唤醒事件中断, 休眠流程切换为唤醒流程,并重新打开滴答定时器。 按照 调度器的中断 中的约束,唤醒事件中断的优先级低于调度器服务中断, 因此唤醒事件中断一定会在调度器服务中断退出后才开始执行,滴答定时器一定会先关闭,然后再打开,顺序不会错乱。
正在暂停 —— 正在恢复 ( XWUP_PM_STAGE_SUSPENDING - XWUP_PM_STAGE_RESUMING
)
当全部线程冻结后,调度器只能调度 空闲任务 ,调度器会在 空闲任务 中执行用户的 suspend()
回调函数。
此时,如果出现唤醒事件,系统会在唤醒事件中断中将状态从 正在暂停 切换为 正在恢复 ,并执行用户的 resume()
回调函数。
suspend()
回调函数用于在休眠之前让用户关闭设备、配置SDRAM刷新模式、配置GPIO等;resume()
回调函数用于在唤醒之前执行与suspend()
回调函数相反的操作;- 执行这两个函数时系统会关闭CPU总中断开关。如果正在执行
suspend()
回调函数时出现了唤醒事件, 唤醒事件中断会挂起,直到suspend()
回调函数,系统打开CPU总中断开关时才被处理,然后执行resume()
回调函数;
已经暂停 ( XWUP_PM_STAGE_SUSPENDED
)
此阶段为休眠流程的最后一个阶段,也是唤醒流程的第一个阶段。此阶段提供给用户的回调函数:
sleep()
:休眠SOCwakeup()
:唤醒SOC
当 空闲任务 执行完上一阶段的 suspend()
回调函数后,电源管理步骤切换到此阶段,并执行 sleep()
回调函数。
SOC系统在 sleep()
回调函数内部进入休眠状态,此时时钟停止,代码停止运行, sleep()
回调函数不会返回。
XWOS将 sleep()
回调函数设计在空闲任务中执行,是因为中断优先级的问题有可能导致系统无法唤醒。
例如基于ARM-m的单片机,如果在一个高优先级的中断中执行了休眠指令( WFI
),低优先级的唤醒中断无法把系统唤醒。
当唤醒事件出现, wakeup()
回调函数在唤醒事件中断中执行。
回调函数 sleep()
与 wakeup()
之间没有锁的保护,
因此 sleep()
回调函数需要设计成能被 wakeup()
回调函数打断。