65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (4投票s)

2021 年 5 月 30 日

CPOL

7分钟阅读

viewsIcon

14665

downloadIcon

195

一个 C++ 脚本, 包含用于管理 ESP32 板功耗降低和唤醒技术的函数

引言

本文介绍了如何通过让板子进入睡眠模式来降低功耗,以及如何唤醒它。

我找到了许多关于这个主题的文章,并感谢那些允许我开发我即将描述的程序的作者。

我也相信这是一个征求意见和批评的机会,因为我知道我不是这个领域的专家;所以如果读者发现不准确或错误,我深表歉意。

背景

ESP32Power.h 脚本包含一组用于进入所谓的 sleep 模式以降低功耗的函数,以及用于设置板子如何唤醒的函数。这里考虑的睡眠模式主要是 lightdeep - 它们不仅在电流的降低程度上有差异,而且在唤醒后的行为上也有差异;实际上,从深度睡眠唤醒几乎相当于重新启动,也就是说,程序会被重新初始化,只有 RTC (实时时钟) 内存中的数据会被保留。

下表总结了 ESP32 板降低功耗的可能方式,以及与本脚本实现的功能相关联的部分。

调制解调器睡眠模式 在处理 WIFI 和/或蓝牙时自动进行
浅睡眠模式 已实现
深度睡眠模式 已实现
休眠模式 (非完全)已实现

唤醒类型总结如下表

定时器 已实现
触摸板 已实现
外部唤醒 (ext0) 已实现
外部唤醒 (ext1) 已实现
ULP 协处理器唤醒 未实现
GPIO 唤醒 已实现
UART 唤醒 已实现但尚未测试

Using the Code

sketch 使用一个调度器(参见 Code Project 文章 Arduino scheduler)和三个事件

  • 启动 2 秒后,显示帮助信息,该事件将被禁用
  • 每两秒,该事件会检查串口以管理请求
  • 每分钟,会显示一次时间

命令 h 显示帮助,以下是一些睡眠命令的示例

  • d7 deep sleep 7 秒
  • lg15 light sleep,可通过 GPIO 引脚或 15 秒后结束
  • dideep 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_SLEEPPW_DEEP_SLEEP 之一
  • touchPintouchPins 可以用于触摸的引脚(s)
  • Threshold 触摸的“灵敏度”
  • pinpins 是 GPIO 的引脚(s) 或 Ext1 中断唤醒的引脚(s)
  • mode 导致唤醒的状态变化

touchPinspins 是以 -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 引脚,第一行包含可用于 Ext0Ext1 唤醒的 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 的引脚,然后将空闲连接器插入该连接器;在此模式下,我们有两个触摸引脚(T0T3)。

GPIO 唤醒

所有引脚都可以用于 GPIO 唤醒,但仅限于 light sleep,两个函数

  • pw_wakeByGPIO(dl, pins, mode = GPIO_INTR_HIGH_LEVEL)
  • pw_wakeByGPIO(dl, pin, mode = GPIO_INTR_HIGH_LEVEL)

将板子置于睡眠状态,当 pinpins 中的一个达到 mode 指定的状态或当 dl 大于 0 时由定时器唤醒。

默认 modeHIGH(当引脚连接到电源时)。

测试方法要唤醒,请将其中一个电源(例如,名为 3V3 的引脚)连接到名为 D13D4 的引脚之一。

中断唤醒

此中断可以是单个引脚状态的改变 (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 的引脚)连接到名为 D13D4 的引脚之一。

从深度睡眠唤醒

与浅睡眠模式不同,浅睡眠模式会保留内存内容,而在深度睡眠模式下,只有 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 日:初始版本
© . All rights reserved.