低功耗模式
概述
RTL87x2G支持四种功耗模式。
- CPU Active在Power active的模式下,如果程序没有进入到idle task,CPU处于Active状态,CPU Clock会处于高速状态,默认是40M Clock。
- CPU Sleep在Power active的模式下,程序进入idle task,CPU进入sleep状态,此时CPU clock会自动降速。如果CPU工作模式下处于40M Clock,CPU sleep时clock降低至625KHz。如果有使用 PLL clock,slow clock与PLL Clock source的选择有关。
- DLPSDLPS 为系统休眠模式,较低的功耗,以及较快的进出时间,同时RAM内容保持。
- Power Down极致低功耗模式。此模式下RAM内容不保持,从Power Down模式退出执行重启流程,较DLPS模式耗时更长。只有LPC和PAD(关闭debounce)可以唤醒Power Down。
不同功耗模式下各个模块使用情况如下,此文档将详细介绍DLPS模式。
Mode |
PAD |
RAM |
Bluetooth |
32K clock |
RTC |
Peripheral |
CPU |
CPU Clock |
---|---|---|---|---|---|---|---|---|
CPU Active |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
40MHz |
CPU Sleep |
√ |
√ |
√ |
√ |
√ |
√ |
√ |
625KHz |
DLPS |
√ |
√ |
× |
√ |
√ |
× |
× |
– |
PowerDown |
√ |
× |
× |
× |
× |
× |
× |
– |
特性与限制
RTL87x2G DLPS模式有如下特点与限制:
系统可以快速进出DLPS模式。
进入DLPS的时间小于1ms。
蓝牙唤醒事件:退出DLPS的时间大约4ms。
其他唤醒事件:退出DLPS的时间大约3ms。
DLPS唤醒事件 可以将系统从DLPS状态唤醒。
CPU断电会导致SWD断开连接,因此在使用JLink进行在线debug时,需要禁用DLPS模式。
原理
系统在大部分时间处于空闲状态,此时可以让系统进入DLPS模式以降低功耗。在DLPS模式下高速clock,CPU,Peripherals等模块会掉电,掉电前需要保存必要的数据用以恢复系统。当有事件需要处理时,系统会退出DLPS模式,并将高速clock,CPU,Peripherals等模块重新上电并恢复到进DLPS前的状态,然后响应唤醒事件。上述过程在系统注册的Callback函数里面自动处理,用户无须自行实现。
DLPS模式的进入与退出
DLPS进入条件
只有当同时满足以下条件时,系统才会进入DLPS模式。
系统执行在idle task,其余task均处在block状态或suspend状态,且没有中断发生。
蓝牙模块检查是否满足进入DLPS的条件:
Non-Link mode (当没有连接时,BT存在以下三种状态)
状态
说明
Standby State
系统不会收发数据,蓝牙会进入sleep状态,且不会将系统从DLPS状态唤醒。
Advertising State
(connectable or un-connectable)
若Adv_Interval * 0.625ms >= 15ms,允许进入DLPS,否则不允许进入。
若Advertising Type为Direct Advertising (High duty cycle),不允许进入DLPS。
对于多广播,依然要满足条件Adv_Interval * 0.625ms >= 15ms后才能进入DLPS。
Scanning State
若(Scan Interval-Scan Window) * 0.625ms >= 15ms,允许进入DLPS,否则不允许进入。
Link mode (有以下两种角色)
角色
说明
Master Role
若Connection Interval * 1.25ms >= 15ms,允许进入DLPS
Slave Role
若Connection Interval * ( 1 + Slave Latency ) * 1.25ms >= 15ms,允许进入DLPS
备注
对于Multi-link mode,设备同时广播和连接,只有当当前时间到下次广播或者连接到期时间隔大于或者等于15ms,才允许进入DLPS。
平台模块检查是否满足条件:
Peripheral,APP注册的check callback执行后均返回true。
OS中的SW timer周期或者task delay时间大于或者等于20ms。
备注
如果蓝牙模块允许进入DLPS模式,但平台模块不允许,则蓝牙模块进入DLPS模式,而平台模块保持active状态。
如果蓝牙模块不允许进入DLPS模式,则整个系统将仍然处于active状态。
DLPS唤醒事件
系统可以由以下事件唤醒退出DLPS模式。
BT 唤醒
- BT事件发生:
BT处于advertising状态,且advertising anchor到来。
BT处于connection状态,且connection anchor到来。
BT处于scanning状态,且scaning anchor到来。
Platform 唤醒
PAD唤醒信号
PAD具有DLPS唤醒功能,可以调用
System_WakeUpPinEnable()
使能某个Pin的唤醒功能。当该Pin的电平与唤醒电平相同时,会将系统从DLPS状态唤醒。例如希望P3_2为高电平时唤醒系统,可以做以下配置。
System_WakeUpPinEnable(P3_2, PAD_WAKEUP_POL_HIGH, PAD_WAKEUP_DEB_DISABLE);
调用
System_WakeUpPinEnable()
时,将DebounceEn设置为1可以使能debounce唤醒功能。如果使能了debounce唤醒功能,则可以调用System_WakeUpDebounceTime()
设置debounce time。如下设置debounce time为8ms(默认没有配置时,default value是0ms)。System_WakeUpDebounceTime(P3_2, 8); System_WakeUpPinEnable(P3_2, PAD_WAKEUP_POL_HIGH, PAD_WAKEUP_DEB_ENABLE);
System irq是一个系统中断,其触发条件是
System_WakeUpPinEnable()
使能PAD唤醒,且当前pin脚的电平状态与唤醒电平一致。系统默认在进入DLPS后关闭system中断,退出DLPS后恢复system中断。因此如果是PAD唤醒DLPS,系统会在system中断恢复后,进入system中断的handler。当debounce唤醒被关闭时,系统从DLPS醒来后,会触发system中断,用户可以在system中断handler里面调用
System_WakeUpInterruptValue()
来查询是哪个Pin唤醒了系统。之后需要调用Pad_ClearWakeupINTPendingBit()
来清除该Pin的唤醒状态。当debounce唤醒被打开时,系统从DLPS醒来后,会触发system中断。但是用户无法确定是哪个Pin唤醒的DLPS,只能在System中断handler里面调用
System_WakeupDebounceStatus()
获取debounce唤醒状态,之后调用System_WakeupDebounceClear()
清除掉debounce唤醒状态。Debounce唤醒开启后,只有pin维持在唤醒电平状态的时间超过debounce time,才会唤醒DLPS,可以防止意外唤醒。但是缺点是,开启debounce唤醒后,无法检测具体是哪个pin在唤醒DLPS。备注
虽然debounce相关的API里面的参数有pin num,但这里只是为了确保不同IC系列的接口兼容性,RTL87x2G并未支持debounce唤醒使能下对不同pin唤醒的检测。具体的使用示例,参考 DLPS使用示例 , 上面涉及到的相关接口的具体说明,详见 DLPS Mode APIs。
RTC中断(demo工程:
samples\io_sample\rtc\rtc_dlps\proj\rtl87x2g\mdk
)Hardware唤醒需要额外调用以下接口:
RTC_WKConfig(RTC_COMP_WK_INDEX, ENABLE) // enable Comparator wake up RTC_SystemWakeupConfig(ENABLE);
Software唤醒实现方式参考:RTC唤醒的SW实现
SW Timer timeout或者task delay事件。
LPC中断(demo工程:
samples\io_sample\lpc\voltage_detection_dlps\proj\rtl87x2g\mdk
)LPC唤醒需要调用以下API使能。
LPC_WKCmd(ENABLE);
AON Q-Decoder中断(demo工程:
samples\io_sample\aon_qdec\aon_qdec_dlps\proj\rtl87x2g\mdk
)AON Q-Decoder唤醒需要调用以下API使能。
AON_QDEC_INITMask(AON_QDEC, AON_QDEC_X_WAKE_AON_MASK, DISABLE);
DLPS模式进入流程
- 前置条件
某个模块如果希望进入DLPS前能够询问自己,需要预先向Power Manager注册Callback函数。当Power Manager执行Callback函数时,各模块再根据Callback函数的结果告知Power Manager是否允许进入DLPS。 蓝牙模块是基于平台模块的,因此只有蓝牙模块允许进入DLPS,才会继续检查平台模块。
- 进入流程
系统进入到idle task。
询问蓝牙模块是否能进入DLPS模式。
蓝牙模块状态保存和进入DLPS模式。
询问平台模块是否能进入DLPS模式。
平台模块状态保存和进入DLPS模式。
DLPS模式退出流程
系统从DLPS模式醒来后,首先会恢复power和高速clock,随后恢复CPU NVIC和Peripherals。只有平台模块完全退出DLPS后,才会检查其他模块是否需要退出DLPS。
- 系统恢复系统退出DLPS模式后,会触发Reset异常进入Reset Handler。在Reset Handler中会检查重启的原因。如果重新上电,系统会走重启流程。如果从DLPS模式唤醒,系统会走DLPS恢复流程,Platform首先恢复power和高速clock,最后进行OS的restore。
- 平台完全退出DLPSPlatform恢复的最后阶段是在timer task中完成的,此时Platform完全退出DLPS,会完成CPU NVIC,peripherals的恢复及用户自定义退出callback函数的执行。
- 检查其他系统模块是否需要从DLPS中退出和恢复判断是否蓝牙事件唤醒DLPS。如果是,蓝牙模块退出DLPS并恢复状态;否则蓝牙模块继续保持低功耗状态。
硬件状态保存与恢复
CPU NVIC
系统进入DLPS后CPU会掉电,因此需要在进入DLPS前保存NVIC寄存器,并在退出DLPS后恢复NVIC寄存器。这样可以确保中断配置可以得到保存,并在DLPS退出后及时恢复中断配置。
SDK中默认CPU_DLPS_Enter和CPU_DLPS_Exit自动实现了CPU NVIC寄存器的保存和恢复,用户无须自行实现。
PAD
PAD在DLPS模式下不会掉电,因此不需要保存其状态。但是为了防止漏电,在进DLPS时需要对PAD做如下设置。
系统没有使用到的PAD,包括package没有出引脚的PAD必须设为{SW mode, Input mode, Pull Down}。这些是PAD的默认设置,因此用户无须更改。
系统使用到的PAD必须设为{SW mode, Input mode, Pull Up/Pull Down}。Pull Up还是Pull Down取决于外围电路。电压是VDD,需要Pull Up;电压是GND,需要Pull Down。
对于PAD的外围电路电压介于VDD和GND之间,需要把PAD设为{SW mode,Shut down mode,Pull None}。这样配置可以确保进入DLPS后,引脚与外围电路隔离以最小化漏电。
设置了唤醒功能的PAD需要设定为{SW mode, Input mode, Pull Up/Pull Down}。Pull Up/Pull Down状态要与唤醒信号的极性相反。如果一个引脚设置为拉低唤醒,则需要在进入DLPS前Pull Up。
退出DLPS时要把PAD设置恢复成原来的状态,确保PAD实现应用所需的功能,以避免产生问题。
外设
外设进DLPS时会掉电,所以进出DLPS时需要保存和恢复相关设定。退出DLPS时需要先重新使能外设模块并打开其时钟,再恢复相关设定。
外挂Sensor
外挂Sensor在进出DLPS时的处理分两种情况。
如果Sensor不掉电,则不用恢复。
如果Sensor掉电,则需要注册application callback函数,并在其中重新初始化Sensor。
状态保存流程
CPU,PINMUX和peripherals的状态会由系统自动保存。PAD状态如何设定取决于APP,因此需要在APP中调用 DLPS_IORegUserDlpsEnterCb()
注册Vendor Callback函数,并在该Vendor Callback函数中根据需要设定PAD状态。如果有外挂sensors需要在进DLPS前进行保存操作,也应该放在同样的vendor Callback函数中实现。
恢复流程
退出DLPS时,系统会自动恢复CPU,PINMUX和peripherals。出DLPS时,PAD如何设定取决于APP,因此需要在APP中调用 DLPS_IORegUserDlpsExitCb()
注册Vendor Callback函数,在Vendor Callback函数里恢复PAD设定。如果有外部sensor在出DLPS后需要恢复的操作,也应该放在同样的Vendor Callback函数里实现。
外设DLPS相关设定
每个APP project会有一个独立的board.h文件,其中包含如下硬件相关的DLPS设定。
/* if use user define DLPS enter/DLPS exit callback function */
#define USE_USER_DEFINE_DLPS_EXIT_CB 1
#define USE_USER_DEFINE_DLPS_ENTER_CB 1
/* if use any peripherals below, #define it 1 */
#define USE_ADC_DLPS 0
#define USE_GPIOA_DLPS 0
#define USE_I2C0_DLPS 0
#define USE_I2C1_DLPS 0
#define USE_IR_DLPS 0
#define USE_KEYSCAN_DLPS 0
#define USE_SPI0_DLPS 0
#define USE_SPI1_DLPS 0
#define USE_UART0_DLPS 0
#define USE_UART1_DLPS 0
#define USE_ENHTIM_DLPS 0
如果APP中使用了某个外设,且需要在进出DLPS时,系统能自动保存、恢复其状态,则需要将该外设对应的USE_XXX_DLPS宏设定为‘1’。对于部分外设比如GDMA,需要在DLPS exit callback function里重新调用init函数。
APP还需要在PwrMgr_Init()调用如下API来注册IO DLPS Callback函数,系统会在该Callback函数里自动完成相关外设的保存和恢复。
DLPS_IORegister();
如果需要在进出DLPS时做一些APP自定义的操作,则需要设定以下两处:
在board.h中将USE_USER_DEFINE_DLPS_EXIT_CB与USE_USER_DEFINE_DLPS_ENTER_CB配置为1。
在APP中调用如下API来注册并实现vendor callback函数。
void DlpsExitCallback(void) { //do something here } void DlpsEnterCallback(void) { //do something here } DLPS_IORegUserDlpsExitCb(DlpsExitCallback); DLPS_IORegUserDlpsEnterCb(DlpsEnterCallback);
在上面的例子中,在进入和退出DLPS的过程中会分别执行DlpsEnterCallback()和DlpsExitCallback(),APP可以在这两个函数中完成自定义操作,如PAD设置、外设设备的操作等。
DLPS使用示例
关闭Debounce下的PAD唤醒
注册DLPS check, enter and exit vendor callback函数,拉低P3_2以唤醒DLPS。
bool DLPS_Check(void)
{
return true;
}
void EnterDlpsSet(void) //DLPS Enter
{
Pad_Config(P3_2, PAD_SW_MODE, PAD_IS_PWRON,
PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_LOW);
System_WakeUpPinEnable(P3_2, PAD_WAKEUP_POL_LOW,
PAD_WAKEUP_DEB_DISABLE);
}
void ExitDlpsInit(void) //DLPS Exit
{
Pad_Config(P3_2, PAD_PINMUX_MODE, PAD_IS_PWRON,
PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_LOW);
}
void pwr_mgr_init(void)
{
#if DLPS_EN
power_check_cb_register (DLPS_Check);
DLPS_IORegUserDlpsEnterCb(EnterDlpsSet); //DLPS Enter CB
DLPS_IORegUserDlpsExitCb(ExitDlpsInit); //DLPS Exit CB
DLPS_IORegister();
bt_power_mode_set(BTPOWER_DEEP_SLEEP);
power_mode_set(POWER_DLPS_MODE);
#endif
}
定义System中断handler以检测哪一个pin唤醒DLPS。
void System_Handler(void)
{
APP_PRINT_INFO0("System_Handler");
NVIC_DisableIRQ(System_IRQn);
if (System_WakeUpInterruptValue(P3_2) == SET)
{
APP_PRINT_INFO0("P3_2 Wake up");
Pad_ClearWakeupINTPendingBit(P3_2);
System_WakeUpPinDisable(P3_2);
}
NVIC_ClearPendingIRQ(System_IRQn);
}
打开Debounce下的PAD唤醒
注册DLPS check, enter 和exit vendor callback函数,拉低P3_2以唤醒DLPS,设置8ms debounce。
bool DLPS_Check(void)
{
return true;
}
void EnterDlpsSet(void)
{
Pad_Config(P3_2, PAD_SW_MODE, PAD_IS_PWRON,
PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_LOW);
System_WakeUpPinDisable(P3_2);
System_WakeUpDebounceTime(P3_2, 8);
System_WakeUpPinEnable(P3_2, PAD_WAKEUP_POL_LOW, PAD_WAKEUP_DEB_ENABLE);
}
void ExitDlpsInit(void)
{
Pad_Config(P3_2, PAD_PINMUX_MODE, PAD_IS_PWRON,
PAD_PULL_UP, PAD_OUT_DISABLE, PAD_OUT_LOW);
}
void pwr_mgr_init(void)
{
#if DLPS_EN
power_check_cb_register(DLPS_Check);
DLPS_IORegUserDlpsEnterCb(EnterDlpsSet);
DLPS_IORegUserDlpsExitCb(ExitDlpsInit);
DLPS_IORegister();
bt_power_mode_set(BTPOWER_DEEP_SLEEP);
power_mode_set(POWER_DLPS_MODE);
#endif
}
当debounce唤醒DLPS使能后,System中断handler不能判断具体是哪个pin唤醒的DLPS,只能判断是否存在debounce唤醒DLPS。因此只需要clear debounce status。
void System_Handler(void)
{
APP_PRINT_INFO0("System_Handler");
NVIC_DisableIRQ(System_IRQn);
if(System_WakeupDebounceStatus(P3_2) == SET)
{
System_WakeupDebounceClear(P3_2);
DBG_DIRECT("debounce Wake up");
}
NVIC_ClearPendingIRQ(System_IRQn);
}
RTC唤醒的SW实现
RTC HW唤醒DLPS,需要RTC到期后,系统从DLPS退出,执行上电后restore动作,最后再触发RTC中断。这样导致RTC中断产生的时间被延迟了,其时间为DLPS退出的时间。
如果需要更高精度的RTC中断,可以采用SW唤醒DLPS,退出DLPS后,RTC到期执行中断。因为SW唤醒DLPS,会考虑到DLPS退出的延迟而提前唤醒DLPS,因此RTC中断会更为准确。
具体做法如下。
取消调用RTC唤醒使能的接口,以关闭RTC中断HW唤醒。
在callback中增加next_wake_up_time指针参数,计算出下次唤醒的时间(单位32.15us),将计算得到的值更新到next_wake_up_time中,由platform触发下次唤醒。
uint32_t RTC_tick; // unit: 32.15us POWER_CheckResult RTC_Check_GT(uint32_t *next_wake_up_time) //unit 31.25us { uint32_t wake_up_count = RTC_GetCompValue(RTC_COMP_INDEX) - RTC_GetCounter(); if(wake_up_count > 0) { *next_wake_up_time = wake_up_count * RTC_tick; return POWER_CHECK_PASS; } else { return POWER_CHECK_FAIL; } }
注册DLPS check callback。
RTC_tick = (RTC_PRESCALER_VALUE + 1); power_check_cb_register(RTC_Check_GT);