低功耗开发指南
低功耗开发指南*
第一章 概述*
PMU(Power Manager Unit)
,是电源管理单元。主要功能有:芯片电源控制、芯片休眠唤醒、OSC 时钟控制。
1.1 功能特性*
- 支持5V或者3V供电,满足锂电池、干电池、USB供电的应用
- VBAT3电源支持1.8V~3.6V宽电压供电范围
- VBAT5电源支持3.3V~5.5V宽电压供电范围
- 支持BUCK供电,输出1.2V~2.5V
- 内部集成1.8V LDO(VDD18),1.2V LDO(VDD12),数字逻辑供电LDO(VDD09)
- 独立的Audio LDO
- 独立的RF LDO
- 低功耗LDO供电,提供Deep Sleep模式下的电源
- 支持逻辑核心供电的动态调压,0.9V~1.1V范围,满足低功耗和高性能要求
1.2 Tickless机制*
低功耗模式使用 Tickless 机制。
当用户任务都被挂起或者阻塞时,最低优先级的空闲任务会得到执行。 那么系统的睡眠模式就放在空闲任务里面实现。但是,为了实现低功耗最优设计,我们还不能直接把睡眠模式直接放在空闲任务里。
进入空闲任务后,首先要计算可以执行低功耗的最大时间,也就是求出下一个要执行的高优先级任务还剩多少时间。 然后就是把低功耗的唤醒时间设置为这个求出的时间,到时间后系统会从低功耗模式被唤醒,继续执行多任务。这个就是所谓的 tickless 模式。
整个低功耗进入流程,由系统的 PM 框架自动处理。
当有任何定时器相关的使用,并且时间到期后,会由RTC唤醒。处理完成后,再由 PM 判断,在合适的时间点重新进入休眠。
因此,用户开发的任务,需要注意以下几个要求:
- 任务不能一直占用cpu,而不释放,否则会导致系统永远无法进入休眠。比如一个while(1)循环,然后里面没有任何 sleep,阻塞等操作;
- 用户开发的任务,如果做了 sleep 5秒的操作,那么系统进入休眠后,会在5秒后被RTC唤醒,继续执行用户sleep完成后的操作;
- 用户开发的任务,如果希望在休眠后,由外部时间触发唤醒后,才能继续执行,可以使用信号量的方法。文档最后提供了一个示例;
- 用户希望进入低功耗,那么必须自己没有占用休眠锁。什么时候系统进入低功耗,会由PM框架自动判断;
- cpu工作中,只有 “work mode” 和 “deep sleep mode” 两种模式切换;
第二章 电源控制*
- Flash LDO 主要用于给 Flash 以及 Flash 的 IO 电压供电,典型为 1.8V,同时作为 SARADC 的参考电压。
-
Bypass LDO 与 DCDC 并联,开机先 Bypass LDO 供电。系统起来后,再开启 DCDC,然后关闭 Bypass LDO。
-
DIG LDO 给工作状态下的数字逻辑供电,默认电压 1.0V。
-
Lower Power LDO 主要给休眠状态下唤醒逻辑数字部分供电,以及给 SRAM 的 Retention 内核电压供电,默认电压 0.9V。
-
5V LDO 将锂电池或 USB 的 5V 的供电降到 3V,保证 VBAT3 后的电源电压一致。5V LDO 默认常开。它有两种模式:正常工作模式、低功耗模式。默认处于正常工作模式,在干电池供电场景下,系统启动后应该将它配置为低功耗模式,降低整机功耗。
-
BUCK,内置一个BUCK模块,用于高效的从VBAT3电源转到1.2V左右。
第三章 工作模式*
工作模式 | 英文描述 | 供电情况 | 模块状态 | 备注 |
---|---|---|---|---|
关机 | Shutdown Mode | 完全掉电 | ||
船运模式 | Ship Mode | 仅 VBAT3/VBAT5 供电,5V LDO 打开,其他全关 | 仅 AO 3V 逻辑工作,AO 域关闭 | |
深度休眠模式 | Deep Sleep Mode | VBAT3 有电,LP LDO 打开,其他 LDO 关闭 | AO 3V 逻辑工作,AO 域工作,32K 时钟关闭,唤醒源模块工作,sram大部分区域断电 | 可配置唤醒源 |
正常工作模式 | Work Mode | 所有模块正常工作 |
重要
进入 Deep Sleep Mode 的时候,GPIO 如果没有设置为唤醒源,那么会变成高阻态。如果是 GPIO 唤醒源,那么是一个上拉或者下拉输入状态
3.1 工作模式转换*
下图为芯片各种工作模式之间转换图
WFI
Wait For Interrupt
调了wfi会进cpu idle,会被任何中断唤醒
3.2 wake_lock 机制*
wake_lock 机制:在某些应用场景下,为了性能考虑,需要一直维持 Work Mode状 态。此时通过 pm_wakelock_acquire / pm_wakelock_release 接口在应用层禁止/允许进入Deep Sleep Mode。
// 应用层禁止Deep Sleep Mode
int pm_wakelock_acquire(pm_wakelock_t *lock);
// 应用层允许进入Deep Sleep Mode
int pm_wakelock_release(pm_wakelock_t *lock);
3.3 ship mode 模式使用*
3.4 支持的唤醒源*
唤醒源 | 数据结构 | 注意事项 |
---|---|---|
无条件唤醒 | .always | 无条件唤醒指的是休眠后立马唤醒 |
IO 唤醒 | .io | 标准的脉冲唤醒 |
RTC 唤醒 | .rtc | 休眠后可以通过 RTC 唤醒,唤醒触发条件为 RTC 中断 |
LPC唤醒 | .lpc | 休眠后可以通过 LPC 唤醒,LPC 接收外部 IO 电压输入,根据外部电压和参考电压值高低来唤醒芯片。比如低电压检测到并触发唤醒芯片。查看芯片手册,有写支持LPC的管脚就支持 |
ON_OFF按键唤醒 | .onoff | 在船运模式下,只能通过 ON-OFF 按键唤醒 |
蓝牙唤醒 | .ble | 休眠后可以通过 BT 唤醒,唤醒触发条件为蓝牙中断,其实就是RTC唤醒 |
第三章 低功耗处理流程简析*
void system_init(void)---->pm_lowpower_init()
void pm_lowpower_init(void)
{
// 设置低功耗唤醒后,启动的代码地址
fastboot_cfg.fb_boot_addr = (uint32_t)&reset_handler & 0x3ffff;
gx_hal_pmu_set(&pmu_dev, GX_HAL_PMU_CMD_FASTBOOT, &fastboot_cfg);
// 设置空闲线程钩子函数
// 钩子函数为:是否进入低功耗的检测函数
// 注意:空闲线程是一个线程状态永远为就绪态的线程,因此设置的钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态, 例如rt_thread_delay() , rt_sem_take() 等可能会导致线程挂起的函数都不能使用
chip_os_task_idle_sethook(_pm_lowpower_process);
}
在 _pm_lowpower_process里,最终调用 PendSV_Handler 进入deep sleep。
我们查看 apus\os\rt-thread\libcpu\risc-v\t-head\e9xx\context_gcc.S的汇编代码,会发现,PendSV_Handler调用了 do_suspend 来进入deep sleep的。
这里的基本原理是这样的:在进入休眠前,先把cpu和当前任务的上下文全部在任务堆栈里保存下来。然后进入休眠。deep sleep模式下的休眠,内存里的数据是保持的。当唤醒后,先进入 reset_handler 里,通过还原任务堆栈保存的现场数据,然后调到在睡眠前的下一个运行位置,继续执行代码。
第四章 低功耗应用开发示例*
测试环境:
- 使用 P0_4 做 GPIO 按键,支持 GPIO 中断(工作的时候触发)和 GPIO 低功耗唤醒(休眠的时候触发)
- 创建了两个任务,每个任务会被信号量阻塞住而进入挂起状态
- GPIO 低功耗唤醒的时候,会释放信号量,使两个任务能够在低功耗恢复后重新运行
- 在运行过程中,需要触发 GPIO 中断,在里面释放睡眠锁,以允许应用层进入Deep Sleep Mode;同时,两个任务也因为信号量阻塞住而进入挂起状态的时候,整个系统会自动进入休眠状态
相当于我们实现了个按键,按一次,进入休眠;再按一次,从休眠状态恢复,如此循环。
用户在开发的时候,可以注册低功耗事件回调,以在低功耗进入或者恢复后,实现自己的业务恢复逻辑:
typedef enum {
PM_STATUS_HALT = 0, // 停机
PM_STATUS_WAKEUP, //唤醒
PM_STATUS_SUSPEND, // 开始休眠流程
PM_STATUS_RESTORE, //中止休眠流程
PM_STATUS_ENTER_SLEEP, //完成休眠准备,准备休眠掉电
PM_STATUS_UNKOWN
}pm_status_e;
测试代码:
#include <stdio.h>
#include <gx_gpio.h>
#include <pad_def.h>
#include <pm.h>
#define TASK_STACK_SIZE 2048
static pm_wakelock_t wakelock;
static chip_os_task_t *task;
static chip_os_sem_t main_task_sem;
static chip_os_sem_t task_sem;
static int gpio_callback (unsigned int port)
{
printf("ISR port = %d\n", port);
pm_wakelock_release(&wakelock);
return 1;
}
static int wake_gpio_callback (unsigned int port)
{
printf("wake port = %d\n", port);
pm_wakelock_acquire(&wakelock);
chip_os_sem_give(&main_task_sem);
chip_os_sem_give(&task_sem);
return 1;
}
static void _pm_notify_handler(pm_status_e pm_status)
{
if(pm_status == PM_STATUS_HALT) {
printf("PM_STATUS_HALT\n");
} else if(pm_status == PM_STATUS_WAKEUP) {
printf("PM_STATUS_WAKEUP\n");
} else if(pm_status == PM_STATUS_SUSPEND) {
printf("PM_STATUS_SUSPEND\n");
}else if(pm_status == PM_STATUS_RESTORE) {
printf("PM_STATUS_RESTORE\n");
}else if(pm_status == PM_STATUS_ENTER_SLEEP) {
printf("PM_STATUS_ENTER_SLEEP\n");
}else {
printf("PM_STATUS_UNKOWN\n");
}
}
static void task_entry(void)
{
while (1)
{
printf("task_entry run\n");
chip_os_sem_take(&task_sem, CHIP_OS_TIME_FOREVER);
}
}
int main(void)
{
printf("Apus Low Power Sample\n");
pm_wakelock_init(&wakelock, "lowpower sample");
chip_os_sem_init(&main_task_sem, "main_task_sem", 0);
chip_os_sem_init(&task_sem, "task_sem", 0);
gx_gpio_init();
gx_gpio_set_mode(P0_4, GX_GPIO_MODE_INPUT);
gx_gpio_enable_trigger(P0_4, GX_GPIO_TRIGGER_EDGE_FALLING, gpio_callback);
gx_gpio_wakeup_enable(P0_4, GX_GPIO_LEVEL_LOW, wake_gpio_callback);
pm_notify_set(_pm_notify_handler);
rt_thread_mdelay(3000);
printf("I am awake1 and do something\n");
rt_thread_mdelay(3000);
printf("I am awake2 and do something\n");
task = chip_os_task_create("my task", (void *)task_entry, NULL, 10, 10, TASK_STACK_SIZE);
chip_os_task_startup(task);
while (1)
{
printf("main run\n");
chip_os_sem_take(&main_task_sem, CHIP_OS_TIME_FOREVER);
}
return 0;
}
开机,会先进入一次休眠。然后被sleep到期后唤醒。然后在所有的应用任务都进入挂起状态时,PM管理系统会判断到,自动再次进入休眠。日志如下:
Hello Apus: v1.2.0-rc5-1712043059, Bank: 0x2000
Chip Label: GX8302B-66c2660ec983b2ce
Build Target: low_power_sample-rtthread-gx8302b_dev
Apus Low Power Sample
msh >I am awake1 and do something
PM_STATUS_SUSPEND
STATUS_WAKEUPER_SLEEP
I am awake2 and do something
main run
task_entry run
PM_STATUS_SUSPEND
PM_STATUS_ENTER_SLEEP
按下按键,触发低功耗唤醒事件。同时,两个任务也进入运行状态。日志如下:
e port = 4
PM_STATUS_WAKEUP
main run
task_entry run
再按一次按键,模拟主动要求进入低功耗的行为。可以看到,系统马上进入休眠。日志如下:
ISR port = 4
PM_STATUS_SUSPEND
PM_STATUS_ENTER_SLEEP