低功耗管理(Low Power Management)也可以称为功耗管理(Power Management),本⽂档中会简称为PM。
Telink低功耗解惑
我查阅多连接SDK开发手册时,低功耗管理章节看了两三遍也没太明白,有以下几个问题一直没得以解决,比较困惑,麻烦大神给点指
进入低功耗好像只有cpu_sleep_wakeup()这个API可以设置进入低功耗的时间,而且还只有200+秒钟,而且只有timer和pad两种唤醒方式;那么
- 等到有master发送连接请求了,再退出sleep,等上5秒之后,如果主机还没发送需要的信号,slave又重新进入sleep,如果发送了就不进入sleep,又应该如何设置呢?
- 如果想设置更长的睡眠时间怎么设置呢?
- 如果用串口唤醒的话,怎么设置呢?
- 以及比如下面这情况,是通过cpu_sleep_wakeup()API设置的吗?,通过这个API设置的tick和这里的sleep时间有关吗?这情况和这个API有什么关系呢?
回答:
(1)(4) 在Telink BLE Multiple Connection SDK中,如果当前有BLE任务,广播/扫描/连接,睡眠由BLE stack进行管控,用户不需要介入,但是用户可以在app_process_power_management()使用blc_pm_setSleepMask()配置当前是否暂时关闭sleep,比如当前正在按键或进行OTA。
(3) 如果用串口唤醒的话,建议配置UART_Rx_GPIO为低电平唤醒API cpu_set_gpio_wakeup(),在回调BLT_EV_FLAG_SLEEP_ENTER里将UART_Rx_GPIO配置为GPIO模式,在回调BLT_EV_FLAG_SUSPEND_EXIT里将UART_Rx_GPIO配置为UART模式。
当没有BLE任务(广播关闭/扫描关闭/连接断开),BLE stack不进行睡眠管控,程序在main_loop()循环转,这时用户可以使用API cpu_sleep_wakeup()自行管理睡眠时间,
(2)当期望睡眠时间大于268秒可以使用API cpu_long_sleep_wakeup_32k_rc()自行管理睡眠时间。
如果只打开BLE_APP_PM_ENABLE,协议栈在任务空闲时只会进入suspend。如果同时打开BLE_APP_PM_ENABLE和PM_DEEPSLEEP_RETENTION_ENABLE,协议栈在任务空闲时会进入suspend或deepsleep retention,会根据任务空闲的时间来选择进入哪个睡眠模式,空闲时间长进入deepsleep retention,空闲时间短进入suspend,空闲时间的门限是API blc_pm_setDeepsleepRetentionThreshold()设置的,默认配置为95ms。
注:
对于uart的接收GPIO作为串口唤醒;在回调BLT_EV_FLAG_SUSPEND_ENTER里将UART_Rx_GPIO配置为GPIO模式,在回调BLT_EV_FLAG_SUSPEND_EXIT里将UART_Rx_GPIO配置为UART模式。
bls_app_registerEventCallback(BLT_EV_FLAG_SUSPEND_EXIT, &task_suspend_exit);
bls_app_registerEventCallback(BLT_EV_FLAG_SUSPEND_ENTER, &task_suspend_enter);
在回调函数 task_suspend_exit中UART_Rx_GPIO配置为UART模式,task_suspend_enter中UART_Rx_GPIO配置为GPIO模式。
空闲时间的门限是API blc_pm_setDeepsleepRetentionThreshold()设置的,默认配置为95ms。
这个95ms的值,不是唯一固定的,你要看你的SDK中,power management initialization中,是否有规定了adv和conn的门限值。
低功耗唤醒源
MCU 的suspend/deepsleep/deepsleep retention 在硬件上有2 个唤醒源:TIMER、GPIO PAD。
- 唤醒源PM_WAKEUP_TIMER 来⾃硬件32k timer(32k RC timer or 32k Crystal timer)。32k timer 在SDK 中已经被正确初始化,user 在使⽤时不需要任何配置,只需要在cpu_sleep_wakeup() 中设置该唤醒源即可。
- 唤醒源PM_WAKEUP_PAD 来⾃GPIO 模块,除MSPI 4 个管脚外所有的GPIO(PAx/PBx/PCx/PDx)的⾼低电平都具有唤醒功能。
配置GPIO PAD 唤醒sleep mode 的API:
typedef enum{
Level_Low=0,
Level_High =1,
} GPIO_LevelTypeDef;
void cpu_set_gpio_wakeup (GPIO_PinTypeDef pin, GPIO_LevelTypeDef pol, int en);
- pin 为GPIO 定义。
- pol 为唤醒极性定义:Level_High 表⽰⾼电平唤醒,Level_Low 表⽰低电平唤醒。
- en: 1 表⽰enable,0 表⽰disable。
举例说明:
cpu_set_gpio_wakeup (GPIO_PC2, Level_High, 1); //GPIO_PC2 PAD 唤醒打开, ⾼电平唤醒
cpu_set_gpio_wakeup (GPIO_PC2, Level_High, 0); //GPIO_PC2 PAD 唤醒关闭
cpu_set_gpio_wakeup (GPIO_PB5, Level_Low, 1); //GPIO_PB5 PAD 唤醒打开, 低电平唤醒
cpu_set_gpio_wakeup (GPIO_PB5, Level_Low, 0); //GPIO_PB5 PAD 唤醒关闭
低功耗模式的进⼊和唤醒
设置MCU 进⼊睡眠和唤醒的API 为:
int cpu_sleep_wakeup (SleepMode_TypeDef sleep_mode, SleepWakeupSrc_TypeDef wakeup_src, unsigned int wakeup_tick);
- 第⼀个参数sleep_mode:设置sleep mode,有以下4 个选择,分别表⽰suspend mode、deepsleep mode、deepsleep retention 16K Sram、deepsleep retention 32K Sram。
typedef enum {
SUSPEND_MODE = 0,
DEEPSLEEP_MODE = 0x80,
DEEPSLEEP_MODE_RET_SRAM_LOW16K = 0x43,
DEEPSLEEP_MODE_RET_SRAM_LOW32K = 0x07,
}SleepMode_TypeDef;
- 第⼆个参数wakeup_src:设置当前的suspend/deepsleep 的唤醒源,参数只能是PM_WAKEUP_PAD、PM_WAKEUP_TIMER 中的⼀个或者多个。如果wakeup_src 为0,那么进⼊低功耗sleep mode 后,⽆法被唤醒。
- 第三个参数“wakeup_tick”:当wakeup_src 中设置了PM_WAKEUP_TIMER 时,需要设置wakeup_tick来决定timer 在何时将MCU 唤醒。如果没有设置PM_WAKEUP_TIMER 唤醒,该参数⽆意义。
wakeup_tick 的值是⼀个绝对值,按照本⽂档前⾯介绍的System Timer tick 来设置,当System Timer tick 的值达到这个设定的wakeup_tick 后,sleep mode 被唤醒。wakeup_tick 的值需要根据当前的System Timer tick的值,加上由需要睡眠的时间换算成的绝对时间,才可以有效地控制睡眠时间。如果没有考虑当前的SystemTimer tick,直接对wakeup_tick 进⾏设置,唤醒的时间点就⽆法控制。
由于wakeup_tick 是绝对时间,必须在32bit 的System Timer tick 能表⽰的范围之内,所以这个API 能表⽰的最⼤睡眠时间是有限的。⽬前的设计是最⼤睡眠时间为32bit 能表⽰的最⼤System Timer tick 对应时间的7/8。System Timer tick 最⼤能表⽰⼤概268s,那么最⻓sleep 时间时间为268*7/8=234 s,即下⾯delta_Tick不能超过234 s, 若需要更⻓的睡眠时间,user 可以调⽤⻓睡眠函数,具体可参考4.2.7 章节。
cpu_sleep_wakeup(SUSPEND_MODE, PM_WAKEUP_TIMER, clock_time() + delta_tick);
cpu_sleep_wakeup (SUSPEND_MODE , PM_WAKEUP_PAD, 0);
cpu_sleep_wakeup (SUSPEND_MODE , PM_WAKEUP_PAD | PM_WAKEUP_TIMER,
clock_time() + 50* CLOCK_16M_SYS_TIMER_CLK_1MS);
deepsleep mode
cpu_sleep_wakeup (DEEPSLEEP_MODE, PM_WAKEUP_PAD, 0);
cpu_sleep_wakeup (DEEPSLEEP_MODE_RET_SRAM_LOW32K , PM_WAKEUP_TIMER, clock_time() + 8*
↪ CLOCK_16M_SYS_TIMER_CLK_1S);
cpu_sleep_wakeup (DEEPSLEEP_MODE_RET_SRAM_LOW32K , PM_WAKEUP_PAD | PM_WAKEUP_TIMER,clock_time()
↪ + 10* CLOCK_16M_SYS_TIMER_CLK_1S);
低功耗运行流程
- no sleep
- 如果没有sleep mode,MCU 的运⾏流程为在while(1) 中循环,反复执⾏“Operation Set A” - >“Operation Set B”。
- suspend
- 如果调⽤cpu_sleep_wakeup 函数进⼊suspend mode,当suspend 被唤醒后,相当于cpu_sleep_wakeup 函数的正常退出,MCU 运⾏到“Operation Set B”。
- suspend 是最⼲净的sleep mode,在suspend 期间所有的Sram 数据能保持不变,所有的数字/模拟寄存器状态也保持不变(只有⼏个特殊的例外);suspend 唤醒后,程序接着原来的位置运⾏,⼏乎不需要考虑任何sram和寄存器状态的恢复。suspend 的缺点是功耗偏⾼。
- deepsleep
- 如果调⽤cpu_sleep_wakeup 函数进⼊deepsleep mode,当deepsleep 被唤醒后,MCU 会重新回到Runhardware bootloader。
- 可以看出,deepsleep wake_up 跟Power on 的流程是⼏乎⼀致的,所有的软硬件初始化都得重新做。MCU 进⼊deepsleep 后,所有的Sram 和数字/模拟寄存器(只有⼏个模拟寄存器例外)都会掉电,所以功耗很低,MCU 电流⼩于1uA。
- deepsleep retention
- 如果调⽤cpu_sleep_wakeup 函数进⼊deepsleep retention mode,当deepsleep retention 被唤醒后,MCU会重新回到Run software bootloader。
- deepsleep retention 是介于suspend 和deepsleep 之间的⼀种sleep mode。
- suspend 因为要保存所有的sram 和寄存器状态⽽导致电流偏⾼;deepsleep retention 不需要保存寄存器状态,Sram 只保留前16K(或32K)不掉电,所以功耗⽐suspend 低很多,只有2uA 左右。
- deepsleep wake_up 后需要把所有的流程重新运⾏⼀遍,⽽deepsleep retention 可以跳过“Run hardware bootloader”这⼀步,这是因为Sram 的前16K(32K)上数据是不丢的,不需要再从flash 上重新拷⻉⼀次。但由于Sram 上retention area 有限,“run software bootloader”⽆法跳过,必须得执⾏;由于deepsleep retention ⽆法保存寄存器状态,所以system initilization 必须执⾏,寄存器的初始化需要重新设置。deepsleep retention wake_up 后的user initilization 可以做⼀些优化改进,和MCU power on/deepsleep wake_up 后的user initilization 做区分处理,参考本⽂档后⾯的介绍。
API
int pm_is_MCU_deepRetentionWakeup(void);
return 值为1,表⽰deepsleep retention wake_up;return 值为0,表⽰power on 或deepsleep wake_up。
void bls_pm_setSuspendMask (u8 mask);
u8 bls_pm_getSuspendMask (void);
mask可以有多个选择
#define SUSPEND_DISABLE 0
#define SUSPEND_ADV BIT(0)
#define SUSPEND_CONN BIT(1)
#define DEEPSLEEP_RETENTION_ADV BIT(2)
#define DEEPSLEEP_RETENTION_CONN BIT(3)
SUSPEND_DISABLE 表⽰sleep disable,不允许MCU 进⼊suspend 和deepsleep retention。
SUSPEND_ADV 和DEEPSLEEP_RETENTION_ADV 分别⽤于控制Advertising state 时MCU 进⼊suspend 和deepsleep retention。
SUSPEND_CONN 和DEEPSLEEP_RETENTION_CONN 分别⽤于控制Conn state Slave role 时MCU 进⼊suspend和deepsleep retention。
SDK 低功耗sleep mode 的设计上,deepsleep retention 是suspend 的替代模式,⽬的是降低sleep mode 的功耗。
以Conn state slave role 为例,SDK ⾸先得看到bltPm.suspend_mask 中SUSPEND_CONN 是否⽣效,才可以进⼊suspend。在可以进⼊suspend 的基础上,根据实际情况再结合bltPm.suspend_mask 中DEEPSLEEP_RETENTION_CONN 是否⽣效,才能决定此时suspend mode 是否被切换为deepsleep retention mode。
所以如果user 希望MCU 进⼊suspend,打开SUSPEND_ADV/SUSPEND_CONN 即可;如果希望MCU 进⼊deepsleep retention mode,必须同时打开SUSPEND_CONN 和DEEPSLEEP_RETENTION_CONN。
该API 最常⽤的3 种情况如下:
bls_pm_setSuspendMask(SUSPEND_DISABLE);
MCU 不允许进⼊sleep mode。
bls_pm_setSuspendMask(SUSPEND_ADV | SUSPEND_CONN);
MCU 在Advertising state 和Conn state Slave role 只允许进⼊suspend,但是不允许进⼊deepsleep retention
bls_pm_setSuspendMask(SUSPEND_ADV | DEEPSLEEP_RETENTION_ADV
|SUSPEND_CONN | DEEPSLEEP_RETENTION_CONN);
MCU 在Advertising state 和Conn state Slave role 允许进⼊suspend 和deepsleep retention,具体进⼊哪种?sleep mode 由当前sleep 的时间⻓度决定。
阈值:
blc_pm_setDeepsleepRetentionThreshold(43, 43);
设置43ms
以⼀个10ms connection interval * (99 + 1) = 1s 的⻓连接为例进⾏说明:
在Conn state slave role 时,由于应⽤层的任务、⼿动latency 的设置等,会导致MCU suspend 时可能出现10ms、20ms、50ms、100ms、1s 等时间值。根据43ms 的阈值设置,MCU 会⾃动将50ms、100ms、1s 等suspend 切换为deepsleep retention,⽽10ms、20ms 等suspend 还是维持suspend,这样的处理可以保证⼀个最优的功耗。
connection latency ⽣效时的Sleep 时序
if(conn_latency != 0)
{latency_use = bls_calculateLatency();T_wakeup = T_brx + (latency_use +1) * conn_interval;
}
else
{T_wakeup = T_brx + conn_interval;
}
当BLE slave 经过connection parameters update(连接参数更新)流程,conn_latency ⽣效后,sleep wake_up的时间为
T_wakeup = T_brx + (latency_use +1) * conn_interval;
下图所⽰为⼀个conn_latency ⽣效时的sleep 时序,此时latency_use= 2。
conn_latency 没有⽣效时,sleep 的时间最⻓不超过1 个connection interval (⼀般都⽐较⼩)。由于conn_latency的⽣效,sleep 的时间可能会出现⼀个⽐较⼤的值,如1s、2s 等,系统功耗可以变得⾮常低。⻓sleep 期间使⽤功耗更⼩的deepsleep retention mode 才变得有意义。
应⽤层定时唤醒
应⽤层定时唤醒API:
void bls_pm_setAppWakeupLowPower(u32 wakeup_tick, u8 enable);
- wakeup_tick 为定时唤醒的System Timer tick 值;
- enable 为1 时打开该唤醒功能,enable 为0 时关闭。
以Conn state Slave role 为例:
当user 使⽤bls_pm_setAppWakeupLowPower 设置了应⽤层定时唤醒的app_wakeup_tick,SDK 在进⼊sleep 前,会检查app_wakeup_tick 是否在T_wakeup 之前。
- 如果app_wakeup_tick 在T_wakeup 之前,如下图所⽰,就会在app_wakeup_tick 触发sleep 提前唤醒;
- 如果app_wakeup_tick 在T_wakeup 之后,MCU 还是会在T_wakeup 唤醒。