低功耗模式
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的选择有关。
- DLPS (Deep Low Power State)系统休眠模式,较低的功耗,以及较快的进出时间,同时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 |
√ |
√ |
× |
√ |
√ |
× |
× |
-- |
PowerPown |
√ |
× |
× |
× |
× |
× |
× |
-- |
特性与限制
RTL87x2G DLPS模式有如下特点与限制:
系统可以快速进出DLPS模式。
进入DLPS的时间小于1ms
蓝牙唤醒事件:退出DLPS的时间大约4ms
其他唤醒事件:退出DLPS的时间大约3ms
唤醒事件 可以将系统从DLPS状态唤醒。
CPU断电会导致SWD断开连接,因此在使用JLink进行在线debug时,需要禁止DLPS模式。
原理
系统在大部分时间处于空闲状态,此时可以让系统进入DLPS模式以降低功耗。在DLPS模式下高速clock,CPU,Peripherals等模块会掉电,掉电前需要保存必要的数据用以恢复系统。当有事件需要处理时,系统会退出DLPS模式,并将高速clock,CPU,Peripherals等模块重新上电并恢复到进DLPS前的状态,然后响应唤醒事件。
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状态
Platform 唤醒
PAD唤醒信号
PAD具有DLPS唤醒功能,详情可参见相关硬件手册。可以调用以下API使能某个Pin的唤醒功能。当该Pin的电平与唤醒电平相同时,会将系统从DLPS状态唤醒。
void System_WakeUpPinEnable(uint8_t Pin_Num, uint8_t Polarity, uint8_t DebounceEn);
例如希望P3_2为高电平时唤醒系统,可以做以下配置。
System_WakeUpPinEnable(P3_2, PAD_WAKEUP_POL_HIGH, PAD_WAKEUP_DEB_DISABLE);
调用System_WakeUpPinEnable时,将DebounceEn设置为1可以使能wakeup debounce功能。如果使能了wakeup 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 handler是一个系统中断,其触发条件是System_WakeUpPinEnable 使能PAD唤醒,且当前pin脚的电平状态与唤醒电平一致。系统默认在进入DLPS后关闭system handler,退出DLPS后恢复system handler。因此如果是PAD唤醒DLPS,系统会在system handler恢复后,进入system handler中断。
当debouce wakeup disable时,系统从DLPS醒来后,会触发system handler,用户可以在system handler ISR里面调用System_WakeUpInterruptValue来查询是哪个Pin唤醒了系统。之后需要调用Pad_ClearWakeupINTPendingBit来清除该Pin的唤醒状态。
uint8_t System_WakeUpInterruptValue(uint8_t Pin_Num); void Pad_ClearWakeupINTPendingBit(uint8_t Pin_Num);
当debouce wakeup enable时,系统从DLPS醒来后,会触发system handler。但是用户无法确定是哪个pin唤醒的DLPS,只能在System handler ISR里面调用System_WakeupDebounceStatus获取debounce唤醒状态,之后调用System_WakeupDebounceClear清除掉debounce唤醒状态。
uint8_t System_WakeupDebounceStatus(uint8_t Pin_Num); void System_WakeupDebounceClear(uint8_t Pin_Num);
Debounce wakeup开启后,只有pin维持在唤醒电平状态的时间超过debounce time,才会唤醒DLPS,可以防止意外唤醒。但是缺点是,开启debounce wake up后,无法检测具体是哪个pin在唤醒DLPS。
备注
虽然debounce相关的API里面的参数有pin num,但这里只是为了确保不同IC系列的接口兼容性,RTL87x2G并未支持debounce wakeup enable下对不同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 wake-up的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模式退出流程
系统从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}。
设置了唤醒功能的PAD需要设定为{SW mode, Input mode, Pull Up/Pull Down}。Pull Up/Pull Down状态要与wakeup信号的极性相反。
退出DLPS时要把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来注册并实现vender 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使用示例
Pad wake up without debounce
注册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);
}
Pad wake up with debounce
注册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 wake-up的SW实现
RTC HW唤醒DLPS,需要RTC到期后,系统从DLPS退出,执行上电及restore动作,最后再触发RTC中断。这样导致RTC中断产生的时间被延迟了,其时间为DLPS退出的时间。
如果需要更高精度的RTC中断,可以采用SW唤醒DLPS,退出DLPS后,RTC到期执行中断。因为SW唤醒DLPS,会考虑到DLPS退出的延迟而提前唤醒DLPS,因此RTC中断会更为准确。
具体做法如下。
取消调用上面的接口,以关闭RTC中断HW唤醒。
在callback中增加next_wakeup_time指针参数,计算出下次唤醒的时间(单位32.15us),将计算得到的值更新到next_wakeup_time中,由platform触发下次唤醒。
uint32_t RTC_tick; // unit: 32.15us POWER_CheckResult RTC_Check_GT(uint32_t *next_wakeup_time) //unit 31.25us { uint32_t wakeup_count = RTC_GetCompValue(RTC_COMP_INDEX) - RTC_GetCounter(); if(wakeup_count > 0) { *next_wakeup_time = wakeup_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);
DLPS Mode APIs
power_mode_set
函数 |
int32_t power_mode_set(POWERMode mode) |
---|---|
功能 |
设置Platform power mode |
参数 |
功耗模式,POWERMode枚举值
|
power_mode_get
函数 |
POWERMode power_mode_get(void) |
---|---|
功能 |
获取Platform power mode |
返回值 |
功耗模式,POWERMode枚举值
|
bt_power_mode_set
函数 |
void bt_power_mode_set(BtPowerMode mode) |
---|---|
功能 |
设置BT power mode |
参数 |
功耗模式,BtPowerMode枚举值
|
bt_power_mode_get
函数 |
BtPowerMode bt_power_mode_get(void) |
---|---|
功能 |
获取BT power mode |
返回值 |
功耗模式,BtPowerMode枚举值
|
power_mode_pause
函数 |
int32_t power_mode_pause(void) |
---|---|
功能 |
ctrl stack加1,此时ctrl stack大于0,不允许进入DLPS |
参数 |
无 |
power_mode_resume
函数 |
int32_t power_mode_resume(void) |
---|---|
功能 |
ctrl stack减1,如果ctrl stack为0,则允许进入DLPS |
参数 |
无 |
power_get_refuse_reason
函数 |
uint32_t *power_get_refuse_reason() |
---|---|
功能 |
获取阻止进入DLPS的callback function |
返回值 |
阻止进入DLPS的callback function地址 |
power_get_wakeup_reason
函数 |
PowerModeWakeupReason power_get_wakeup_reason() |
---|---|
功能 |
获取DLPS的唤醒原因 |
返回值 |
DLPS唤醒原因 |
power_get_error_code
函数 |
PowerModeErrorCode power_get_error_code(void) |
---|---|
功能 |
获取不进入DLPS的原因 |
返回值 |
不进入DLPS的原因 |
power_get_statistics
函数 |
void power_get_statistics(uint32_t *wakeup_count, uint32_t *last_wakeup_clk, uint32_t *last_sleep_clk) |
---|---|
功能 |
获取DLPS debug信息 |
参数 |
Wakeup_count:进出DLPS mode的总次数 last wakeup clock:系统上次唤醒DLPS的时间,单位31.25us last sleep clock:系统上次进入DLPS的时间,单位31.25us |
power_register_excluded_handle
函数 |
bool power_register_excluded_handle(void **handle, PowerModeExcludedHandleType type) |
---|---|
功能 |
SW timer或者task移除DLPS wake list,timer必须是one shot timer |
参数 |
handle:SW timer/task的handler type:SW timer还是task type |
power_unregister_excluded_handle
函数 |
bool power_unregister_excluded_handle(void **handle, PowerModeExcludedHandleType type) |
---|---|
功能 |
已移除的SW timer或者task重新加入DLPS wake list |
参数 |
handle:SW timer/task的handler type:SW timer还是task type |
power_check_cb_register
函数 |
int32_t power_check_cb_register(POWERCheckFunc func) |
---|---|
功能 |
系统注册查询callback。在Idle task里,系统每次试图进入DLPS前会回调该callback,根据callback的返回值决定是否允许进入DLPS状态。 返回值:
|
参数 |
func:进入DLPS前的查询callback |
DLPS_IORegUserDlpsEnterCb
函数 |
__STATIC_INLINE void DLPS_IORegUserDlpsEnterCb(DLPS_IO_EnterDlpsCB func) |
---|---|
功能 |
用于注册进DLPS时的vendor callback,APP自定义的IO保存动作需要在vendor callback中实现 |
参数 |
func:进入DLPS的vendor callback |
备注
DLPS enter callbackfunction不允许有较长时间的delay动作否则会影响DLPS唤醒时间。
同时也不允许调用OS接口,因为此时已关闭OS调度和中断。
DLPS_IORegUserDlpsExitCb
函数 |
__STATIC_INLINE void DLPS_IORegUserDlpsExitCb(DLPS_IO_ExitDlpsCB func) |
---|---|
功能 |
用于注册出DLPS时的vendor callback,APP自定义的IO恢复动作需要在vendor callback中实现 |
参数 |
LPS_IO_ExitDlpsCB func:退出DLPS的vendor callback |
System_WakeUpPinEnable
函数 |
void System_WakeUpPinEnable(uint8_t Pin_Num, uint8_t Polarity, uint8_t DebounceEn) |
---|---|
功能 |
用于配置PAD的wakeup功能 |
参数 |
|
System_WakeUpDebounceTime
函数 |
void System_WakeUpDebounceTime(uint8_t time) |
---|---|
功能 |
用于设置debounce时间 |
参数 |
time:debounce时间 单位:ms |
System_WakeUpInterruptValue
函数 |
uint8_t System_WakeUpInterruptValue(uint8_t Pin_Num) |
---|---|
功能 |
询某个PAD是否是唤醒系统的PAD,若返回true,则该PAD即为唤醒系统的PAD |
参数 |
Pin_Num:待查询的PAD |