ESP32 板的功耗降低和唤醒技术






4.50/5 (4投票s)
一个 C++ 脚本,
引言
本文介绍了如何通过让板子进入睡眠模式来降低功耗,以及如何唤醒它。
我找到了许多关于这个主题的文章,并感谢那些允许我开发我即将描述的程序的作者。
我也相信这是一个征求意见和批评的机会,因为我知道我不是这个领域的专家;所以如果读者发现不准确或错误,我深表歉意。
背景
ESP32Power.h 脚本包含一组用于进入所谓的 sleep
模式以降低功耗的函数,以及用于设置板子如何唤醒的函数。这里考虑的睡眠模式主要是 light
和 deep
- 它们不仅在电流的降低程度上有差异,而且在唤醒后的行为上也有差异;实际上,从深度睡眠唤醒几乎相当于重新启动,也就是说,程序会被重新初始化,只有 RTC (实时时钟) 内存中的数据会被保留。
下表总结了 ESP32 板降低功耗的可能方式,以及与本脚本实现的功能相关联的部分。
调制解调器睡眠模式 | 在处理 WIFI 和/或蓝牙时自动进行 |
浅睡眠模式 | 已实现 |
深度睡眠模式 | 已实现 |
休眠模式 | (非完全)已实现 |
唤醒类型总结如下表
定时器 | 已实现 |
触摸板 | 已实现 |
外部唤醒 (ext0 ) | 已实现 |
外部唤醒 (ext1 ) | 已实现 |
ULP 协处理器唤醒 | 未实现 |
GPIO 唤醒 | 已实现 |
UART 唤醒 | 已实现但尚未测试 |
Using the Code
该 sketch
使用一个调度器(参见 Code Project 文章 Arduino scheduler)和三个事件
- 启动 2 秒后,显示帮助信息,该事件将被禁用
- 每两秒,该事件会检查串口以管理请求
- 每分钟,会显示一次时间
命令 h
显示帮助,以下是一些睡眠命令的示例
d7
d
eep sleep 7 秒lg15
l
ight sleep,可通过 GPIO 引脚或 15 秒后结束di
从d
eep sleep 通过 Ext0 引脚唤醒
硬件
板子是 ESP32 DEVKIT TV1,带有
- 一个连接到名为
VIM
的引脚的Female Female 连接器 - 一个连接到名为
3V3
的引脚的Female Female 连接器 - 一个连接到名为
D15
的引脚的Female Male 连接器 - 一个空闲的 Male Male 连接器
唤醒类型
ESP32Power.h 脚本包含用于处理功耗降低的函数、变量和常量。
特别是,它包含
- 用于设置唤醒模式并进入功耗降低状态的函数(这些将在专门的段落中进行解释)
- 一个
stub
函数,该函数在板子从深度睡眠唤醒时被调用 - 两个获取时钟的函数
- 一个显示唤醒类型的函数
所有唤醒模式都可以与定时器唤醒相关联;事实上,由所有唤醒类型调用的函数 pw_EnterSleep(dl, delayType)
也可以插入定时器唤醒(见下文)。
void pw_EnterSleep(uint32_t dl,int delayType) { // dl is milliseconds
pw_sleepTime = dl;
if (dl > 0) esp_sleep_enable_timer_wakeup(dl*1000); // enable also timer
if (delayType == PW_LIGHT_SLEEP) esp_light_sleep_start();
else if (delayType == PW_DEEP_SLEEP) {
pw_Clock = pw_getTime2()/1000;
esp_deep_sleep_start();
} else
delay(dl); // -1 and unknown
}
在接下来的段落中,我将使用一些变量
dl
睡眠时间,以毫秒为单位delayType
睡眠模式,是常量PW_LIGHT_SLEEP
、PW_DEEP_SLEEP
之一touchPin
、touchPins
可以用于触摸的引脚(s)Threshold
触摸的“灵敏度”pin
、pins
是 GPIO 的引脚(s) 或Ext1
中断唤醒的引脚(s)mode
导致唤醒的状态变化
touchPins
和 pins
是以 -1
结尾的数组,例如
...
int8_t GPIOpins[] = {GPIO_NUM_13,GPIO_NUM_4, -1};
int8_t touchPins[] = {T0,T3,-1}; // Touch pins GPIO 4 GPIO 15
int8_t Ext1Pins[] = {GPIO_NUM_13,GPIO_NUM_4, -1};
...
以下是一个设置触摸唤醒功能的示例
void pw_wakeByTouch(uint32_t dl,int delayType, int8_t touchPins[],
int Threshold = 40) { // *** wakeup by touch pad ***
for (int8_t i=0;touchPins[i] != -1;i++)
touchAttachInterrupt(touchPins[i], pw_callback, Threshold);
esp_sleep_enable_touchpad_wakeup();
pw_EnterSleep(dl,delayType);
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TOUCHPAD);
}
请注意,最后一条禁用唤醒源的语句 **仅** 用于 light sleep
。
定时器唤醒
有两个用于设置基于定时器的中断的函数
pw_wakeByTimer(dl, delayType)
pw_EnterSleep(dl, delayType)
它们在形式上相似,第一个仅用于定时器唤醒,如果需要深度睡眠,则会发生(轻微)休眠。第二个也被所有类型的唤醒调用。
触摸板唤醒
两个函数
pw_wakeByTouch(dl, delayType, touchPins, Threshold = 40) // 引脚数组
pw_wakeByTouch(dl, delayType, touchPin, Threshold = 40) // 单个引脚
将板子置于睡眠状态,当 touchPin(s)
被激活或当 dl
大于 0
时由定时器唤醒。
如果省略 Threshold
,则默认值为 40
。
以下是与触摸相关的 GPIO 引脚,第一行包含可用于 Ext0
和 Ext1
唤醒的 RTC 引脚。
GPIO | 0 | 2 | 4 | 12 | 13 | 14 | 15 | 25 | 26 | 27 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
触摸 | T1 | T2 | T0 | T5 | T4 | T6 | T3 | T7 | T9 | T8 |
测试方法:将一个连接器从电源之一(例如,从名为 3V3
的引脚)拔出,插入名为 D4
的引脚,然后将空闲连接器插入该连接器;在此模式下,我们有两个触摸引脚(T0
和 T3
)。
GPIO 唤醒
所有引脚都可以用于 GPIO 唤醒,但仅限于 light sleep
,两个函数
pw_wakeByGPIO(dl, pins, mode = GPIO_INTR_HIGH_LEVEL)
pw_wakeByGPIO(dl, pin, mode = GPIO_INTR_HIGH_LEVEL)
将板子置于睡眠状态,当 pin
或 pins
中的一个达到 mode
指定的状态或当 dl
大于 0
时由定时器唤醒。
默认 mode
是 HIGH
(当引脚连接到电源时)。
测试方法:要唤醒,请将其中一个电源(例如,名为 3V3
的引脚)连接到名为 D13
或 D4
的引脚之一。
中断唤醒
此中断可以是单个引脚状态的改变 (Ext0
)
pw_wakeByExt0(dl, delayType, pin, mode = HIGH)
或一组引脚 (Ext1
)
pw_wakeByExt1(dl, delayType, pins, mode = ESP_EXT1_WAKEUP_ANY_HIGH)
mode
可以是 ESP_EXT1_WAKEUP_ANY_HIGH
(默认值),即当 **一个** 引脚变为 HIGH
时发生唤醒,或者 ESP_EXT1_WAKEUP_ALL_LOW
,即 **所有** 引脚变为 LOW
时发生唤醒。
脚本包含一个用于设置 Ext1
引脚的函数
...
inline uint64_t setExt1(uint64_t a, int pin) {return bitSet(a, pin % 40);}
...
void pw_wakeByExt1(uint32_t dl,int delayType, int8_t pins[],
esp_sleep_ext1_wakeup_mode_t mode = ESP_EXT1_WAKEUP_ANY_HIGH) {
uint64_t Ext1 = 0;
for (int8_t i=0; pins[i] != -1;i++)
Ext1 = setExt1(Ext1, pins[i]); // 0,2,4,12-15,25-27,32-39
esp_sleep_enable_ext1_wakeup(Ext1,mode);
pw_EnterSleep(dl,delayType);
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_EXT1);
}
测试 Ext0:要唤醒,请将其中一个电源(例如,名为 3V3
的引脚)连接到名为 D13
的引脚。
测试 Ext1:要唤醒,请将其中一个电源(例如,名为 3V3
的引脚)连接到名为 D13
或 D4
的引脚之一。
从深度睡眠唤醒
与浅睡眠模式不同,浅睡眠模式会保留内存内容,而在深度睡眠模式下,只有 RTC 内存会被保留,CPU 和所有数字外设都会断电。RTC 控制器、RTC 外设和 ULP 协处理器保持供电。
因此,在唤醒时,程序会执行一次准重置,即从 setup()
函数开始重新执行,但是 RTC 内存中的内容会被保留,并且通过函数 esp_sleep_get_wakeup_cause()
,可以知道是发生了硬件重置(例如板子连接到电源时)还是简单的唤醒。
void setup() {
Serial.begin(9600);
delay(100);
if ((int) esp_sleep_get_wakeup_cause() > 0) {
Serial.println(pw_wakeup_reason());
events.begin(cron,pw_Clock);
}
}
Stub 函数
void RTC_IRAM_ATTR esp_wake_deep_sleep(void) {
esp_default_wake_deep_sleep();
pw_Clock = pw_getTime()/1000 - pw_Clock; // milliseconds
}
待解决或未解决的问题
本段部分反映了我对 ESP32 板的知识不足。
脚本有一个用于 UART 唤醒的函数,但尚未测试,ULP 唤醒尚未实现。
通过定时器从深度睡眠唤醒的调用通过关闭 RTC 外设和 RTC 快速内存来实现部分休眠。在这种情况下,我无法从 stub
函数访问 RTC 内存,尽管该函数保留了其内容。未实现关闭 RTC 低内存,因为它会导致存储数据丢失。
睡眠时间未被检查;我发现并检查出,对于 32 位计数器来说,最大睡眠时间约为 71 分钟,并且值为微秒,因此似乎计数器可以是 64 位,请参阅 本文。
最后,当深度唤醒不是通过定时器时,我使用 RTC 控制定时器,并使用两个相同的函数(感谢 此帖子)。
...
#define RTC_CTNL_SLOWCLK_FREQ 160000
...
uint64_t pw_getTime2(void) {
SET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE_M);
while (GET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_VALID_M) == 0) { }
uint64_t now = READ_PERI_REG(RTC_CNTL_TIME0_REG);
now |= ((uint64_t) READ_PERI_REG(RTC_CNTL_TIME1_REG)) << 32;
return now * 100 / (RTC_CTNL_SLOWCLK_FREQ / 10000); // scale RTC_CTNL_SLOWCLK_FREQ
// to avoid overflow;
}
RTC_IRAM_ATTR uint64_t pw_getTime(void) {
SET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE_M);
while (GET_PERI_REG_MASK(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_VALID_M) == 0) { }
uint64_t now = READ_PERI_REG(RTC_CNTL_TIME0_REG);
now |= ((uint64_t) READ_PERI_REG(RTC_CNTL_TIME1_REG)) << 32;
return now * 100 / (RTC_CTNL_SLOWCLK_FREQ / 10000); // scale RTC_CTNL_SLOWCLK_FREQ
// to avoid overflow;
}
为什么是两个函数?因为我无法从正常操作中调用存储在 RTC 内存中的函数,只能从 stub
函数中调用。另一个让我感到困惑的事实是,我使用的频率是 160kHz,这比 150kHz 的偏差要小。
已知问题
在尝试了触摸中断后,同一引脚上用于触摸的 GPIO 唤醒将无法工作(即使在深度睡眠之后)。
在通过触摸结束的浅睡眠后,esp_sleep_get_touchpad_wakeup_status
函数会崩溃,因此我无法知道是哪个触摸引脚引起了中断。
历史
- 2021 年 5 月 30 日:初始版本