电源管理
Categories:
2 分钟阅读
概述
XWOS有两套电源管理框架,分别用于MP系统和UP系统。
XWOS的电源管理框架只提供了基本流程,最终SOC如何休眠由BSP中的回调函数实现: 用户可以自行决定是否保持SDRAM的刷新、是否保持某些GPIO的输出、或则是否进入最低功耗的待机等等。
XWOS多核系统中,CPU不唯一,每个CPU的休眠与唤醒流程相同且相互独立运行。 XWOS单核系统中,单一CPU的休眠与唤醒流程与多核系统任意CPU的流程相同。
流程
每个CPU的电源管理流程
Photo: xwos.tech / CC-BY-SA-4.0
图中,左列为休眠流程,右列为唤醒流程,箭头代表了可以进行 电源管理阶段 转换的方向。 电源管理领域 将电源管理分为四个阶段(图中绿色的框):
正在运行 ( XWOS_PM_STAGE_RUNNING )
所有CPU正常运行,可以使用下面CAPI进入休眠的流程:
xwer_t xwos_pm_suspend(void);
此CAPI是操作系统抽象层 OSAL 中的CAPI,实际调用的是:
- 多核系统:
xwer_t xwmp_pm_suspend(void); - 单核系统:
xwer_t xwup_pm_suspend(void);
冻结线程 —— 解冻线程 ( XWOS_PM_STAGE_FREEZING - XWOS_PM_STAGE_THAWING )
一旦开始休眠流程,内核会中断所有线程的 等待 和 睡眠 态,并为所有线程设置 可冻结 标志, 然后依次调度每个线程,直到它们运行到 冻结点 进行冻结。
这个过程耗时比较长,在执行过程中如果遇到唤醒事件,内核可在唤醒事件中断中切换为解冻流程: 解冻已冻结的线程,并取消未冻结的线程的 可冻结 标志。
全部线程已冻结 ( XWOS_PM_STAGE_ALLFRZ )
这是一个比较短暂的状态。当最后一个线程完成冻结时,CPU处于调度器服务中断中,内核会将滴答定时器关闭。 从此时开始,内核会将上下文(Context)切换为 电源管理 (XWOS_SKD_CONTEXT_PWRMNT) 。
如果此刻出现唤醒事件,待CPU从调度器服务中断中退出后,会立即进入唤醒事件中断。 休眠流程切换为唤醒流程,并重新打开滴答定时器。 按照 调度器的中断 中的约束,唤醒事件中断的优先级低于调度器服务中断, 因此唤醒事件中断一定会在调度器服务中断退出后才开始执行, 滴答定时器一定会先关闭,然后再打开,顺序不会错乱。
若未出现唤醒中断,CPU从调度器服务中断中退出后,会切换回线程上下文,此时因所有线程已经冻结, CPU只可能运行在 空闲任务 中。
正在暂停 —— 正在恢复 ( XWOS_PM_STAGE_SUSPENDING - XWOS_PM_STAGE_RESUMING )
内核会在 空闲任务 中执行用户的 suspend() 回调函数。
此时,如果出现唤醒事件,系统会在唤醒事件中断中将状态从 正在暂停 切换为 正在恢复 ,
并执行用户的 resume() 回调函数。
suspend()回调函数用于在休眠之前让用户关闭设备、配置SDRAM刷新模式、配置GPIO等;resume()回调函数用于在唤醒之前执行与suspend()回调函数相反的操作;
这两个回调函数是每CPU独立的,每个CPU只能执行自己的回调函数。
执行这两个函数时,内核会关闭CPU总中断开关。
如果正在执行 suspend() 回调函数时出现了唤醒事件,唤醒事件中断会挂起,等待 suspend() 返回,
内核才会打开CPU总中断开关,然后执行唤醒事件的中断函数。
示例,WeActMiniStm32H750的 resume() 与 suspend() 回调函数:
void stm32hal_resume(void)
{
xwds_pm_resume(&stm32xwds); /* 恢复所有设备 */
}
void xwosac_pmcb_resume(void * arg)
{
xwsq_t ctx;
xwirq_t irq;
XWOS_UNUSED(arg);
xwos_skd_get_context_lc(&ctx, &irq); /* 获取上下文以及中断号,用于调试 */
stm32hal_resume();
}
void stm32hal_suspend(void)
{
xwds_pm_suspend(&stm32xwds); /* 暂停所有设备 */
/* 设置休眠方式为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();
}
void xwosac_pmcb_suspend(void * arg)
{
xwsq_t ctx;
xwirq_t irq;
XWOS_UNUSED(arg);
xwos_skd_get_context_lc(&ctx, &irq); /* 获取上下文以及中断号,用于调试 */
stm32hal_suspend();
}
已经暂停 ( XWOS_PM_STAGE_SUSPENDED )
此阶段为休眠流程的最后一个阶段,也是唤醒流程的第一个阶段。此阶段提供给用户的回调函数:
sleep():休眠SOCwakeup():唤醒SOC
当最后一个CPU的 空闲任务 执行完上一阶段的 suspend() 回调函数后,
内核将电源管理步骤切换到此阶段,并执行 sleep() 回调函数。
SOC系统在 sleep() 回调函数内部进入低功耗状态,
此时时钟停止,代码停止运行, sleep() 回调函数不会返回。
XWOS将 sleep() 回调函数设计在空闲任务中执行,是因为中断优先级的问题有可能导致系统无法唤醒,
例如,ARM Cortex-M的内核单片机。
如果在一个高优先级的中断中执行了休眠指令( WFI ),低优先级的唤醒中断无法把系统唤醒。
当唤醒事件出现, wakeup() 回调函数在唤醒事件中断中执行。
回调函数 sleep() 与 wakeup() 之间没有锁保护,
因此 sleep() 回调函数需要设计成能被 wakeup() 回调函数打断。
示例,WeActMiniStm32H750的 wakeup() 与 sleep() 回调函数:
void stm32hal_wakeup(void)
{
LL_LPM_EnableSleep(); /* 清除DEEPSLEEP位 */
SystemClock_Config(); /* 从STOP模式恢复后,需要重新配置时钟 */
}
void xwosac_pmcb_wakeup(void * arg)
{
XWOS_UNUSED(arg);
stm32hal_wakeup();
}
void stm32hal_sleep(void)
{
}
void xwosac_pmcb_sleep(void * arg)
{
XWOS_UNUSED(arg);
stm32hal_sleep();
/* 位置1 */
armv7m_wfi(); /* 通过 WFI 指令进入STOP模式 */
/* 位置2 */
}
分为两种情况讨论:
xwosac_pmcb_sleep()中的WFI指令执行后出现唤醒事件: 唤醒流程执行完毕后,代码会回到xwosac_pmcb_sleep()中 位置2 ;xwosac_pmcb_sleep()中的WFI指令执行之前出现唤醒事件: 唤醒事件中断会打断xwosac_pmcb_sleep()函数, 然后进入唤醒事件中断函数中,随着唤醒流程执行,会调用到xwosac_pmcb_wakeup()函数, 函数LL_LPM_EnableSleep()会清除Cortex-M的DEEPSLEEP位,接下来的唤醒流程也会将滴答定时器重新打开, 当再次回到xwosac_pmcb_sleep()的 位置1 时,WFI指令只会使CPU进入ARMv7-m的SLEEP模式, 只是短暂地暂停了一下CPU时钟,即将到来的滴答定时器中断可使系统恢复正常。