ESP32 – Stromverbrauch im Deep-Sleep

Wer mit dem ESP32 Batterie betetriebene Anwendungen bauen will kommt um den Deep-Sleep Modus nicht herum. Im Netz finden sich Posts dazu, dass der ESP32 trotz Deep-Sleep zu viel Strom frist. Hier ein paar Experimente dazu … 

Zunächst einmal zum Setup meines Versuchsaufbaus:
Es besteht aus einem ESP32 WROOM mit 16MB Flash und
der Stromversorgung aus 2 AA Batterien und einem TPS610986. Der TPS610986 verwandelt alles zwischen 0,7 – 4,5V in eine Ausgangsspannung von 3,3V. Er eignet sich durch einen sehr niedrigen Eigenverbrauch von 300 nA, einer guten Effizienz von 88 -93% und einem zweiten schaltbaren Ausgang besonder gut für Batterie Anwendungen.

Damit sind die Stromfresser wie USB, LEDs etc. schon einmal außen vor und wir können uns auf die Programmierung des ESPs konzentrieren.

Im einfachsten Fall schicken wir unseren ESP immer in den Deep-Sleep:

#include <Arduino.h>

void setup(void)
{
  esp_deep_sleep(1000000 * 10); // sleep for 10 sec.
}

void loop(void)
{
}

Im Deep-Sleep kommen kommen wir so auf einen Stromverbrauch von ~5µA. Bei 2x 1,5V mit 2000mA ergibt sich daraus eine Laufzeit von:

2000mAh / 0,005mA = 40000h ~ 46 Jahre

unter idealen Bedingungen wie 0% Selbstentladung. Damit lässt sich auf jeden Fall was anfangen.

Nun hab ich als Verbraucher einen One-Wire Temperaturfühler DS18B20 angeschlossen Datenleitung auf GPIO12 und VCC auf VSUB des TPSTPS610986. Mode zum Schalten von VSUB hängt an GPIO2.

Das ganze mit dem Beispiel Code von Paul Stoffregens OneWire Bibliothek getestet. Die einzige Anpassung ist, dass GPIO2 auf HIGH gesetzt und damit der Sensor mit Strom versorgt wird. Es läuft und benötigt ~50mA während des Betriebs (inkl. der Konsolenausgaben die man aus Effizienzgründen im Produktionscode natürlich nicht haben sollte, da sie unnötig Strom fressen – dazu später mehr).

Die Spitzen sind die Zeitpunkte der Messungen an denen etwas mehr Strom verbraucht wird.

Betriebsdauer:

2000mAh / 50mA = 40h ~ 1,6 Tage

Das ist doch etwas wenig. Also fangen wir an einen Deep-Sleep nach jeder Messung einzufügen. Dafür modifizieren wir den Beginn der loop-Funktion wie folgt:

bool firstRun = true;

void loop(void) {
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;
  
  if ( !ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    if(firstRun) {
        ds.reset_search();
        firstRun = false;
        delay(250);
        return;
    }
    digitalWrite(2, LOW);
    esp_deep_sleep(1000000 * 10);

  }

Betriebsdauer:

10 * 2,2 mA + 2 * 50mA / 12 = 10,1mA
2000mAh / 10,1mA = 198 ~ 8,25 Tage

Schon besser. Aber HALT!! wieso 2,2 mA im DeepSleep die Eingangmessung kam auf 5µA! was stimmt hier nicht?

Nach längerem Messen habe ich festgestellt, dass GPIO2 zwar auf 0 gezogen wird, aber im Deep-Sleep floated und hier die Sensoren wieder mit Strom versorgt. Im IDF bin ich über folgenden Befehl gestolpert:

gpio_deep_sleep_hold_en(void)
“When the chip is in Deep-sleep mode, all digital gpio will hold the state before sleep, and when the chip is woken up, the status of digital gpio will not be held. Note that the pad hold feature only works when the chip is in Deep-sleep mode, when not in sleep mode, the digital gpio state can be changed even you have called this function.”

Klingt doch genau nach der Lösung des Problems!

bool firstRun = true;

void loop(void) {
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;
  
  if ( !ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    if(firstRun) {
        ds.reset_search();
        firstRun = false;
        delay(250);
        return;
    }
    digitalWrite(2, LOW);
    gpio_hold_en(GPIO_NUM_2);
    gpio_deep_sleep_hold_en();
    esp_deep_sleep(1000000 * 10);

  }

Entgegen der Beschreibung ließ sich GPIO2 nach dem ersten Deep-Sleep nicht mehr auf HIGH setzen. Deshalb musste die Setup Funktion auch noch angepasst werden.

void setup(void) {
  gpio_deep_sleep_hold_dis();
  gpio_hold_dis(GPIO_NUM_2);
  Serial.begin(115200);
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);
}

Das Ergebnis ist sehr unerwartet:

Der Stromfluss sinkt zwar im vergleich zu vorher ab ist mit durchschnittlich 100µA aber immer noch hoch und stark schwankend.

Also weiter in der API lesen und weiter unten finden sich Befehle zum Konfigurieren und Setzen der GPIOs im RTC bzw. im ULP (Ultra Low Power) Prozessor der auch im Deep-Sleep noch arbeitet.

rtc_gpio_init, rtc_gpio_set_direction und rtc_gpio_set_level

Damit sieht das Programm dann wie folgt aus:

#include <OneWire.h>
#include "driver/rtc_io.h"

OneWire  ds(12);  // on pin 10 (a 4.7K resistor is necessary)


void setup(void) {
  rtc_gpio_init(GPIO_NUM_2);
  rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY);
  rtc_gpio_set_level(GPIO_NUM_2, 1);
  Serial.begin(115200);
}

bool firstRun = true;

void loop(void) {
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;
  
  if ( !ds.search(addr)) {
    Serial.println("No more addresses.");
    Serial.println();
    if(firstRun) {
        ds.reset_search();
        firstRun = false;
        delay(250);
        return;
    }
    rtc_gpio_set_level(GPIO_NUM_2, 0);
    esp_deep_sleep(1000000 * 10);

  }
 // Ab hier wie im Beispiel

Und wir landen im Mittel bei 10µA während des Deep-Sleeps. Das sieht doch vielversprechend aus!

Betriebsdauer:

10 * 0,01mA + 2 * 50mA / 12 = 8,3mA
2000mAh / 8,3mA = 240h ~ 10 Tage

Nun macht es für die wenigsten Prozesse Sinn die Temperatur alle 10 Sekunden zu erfassen. Wenn wir nun auf alle 2 Minuten erweitern ergibt sich:

118 * 0,01mA + 2 * 50mA / 120 = 0,8mA
2000mAh / 0,8mA = 2500h ~ 104 Tage

Nun kann man die DeepSleep Phasen noch erweitern, je nach dem was die Anwendung erlaubt. OneWire ist ein Protokoll das recht langsam arbeitet und somit der Prozessor sehr lange auf die Übertragung warten muss. Nun kann man die Taktrate des ESP32 einfach senken.

TaktgeschwindigkeitAusführungszeitStromaufnahme
240MHz~ 2Sek50mA
160MHz~ 2Sek35mA
80MHz~ 2Sek 27mA

Damit kommt man auf folgende Betriebsdauer:

118 * 0,01mA + 2 * 27mA / 120 = 0,45mA
2000mAh / 0,45mA = 4444h ~ 185 Tage

Durch Anpassung der Taktfrequenz hat man einfach 163 Tage gewonnen ohne viel dafür zu tun. Natürlich muss der Rest der Anwendung auch mit der Taktrate zu rande kommen.

Wer noch mehr Energie sparen und sich nicht scheut Assembler zu schreiben kann die Sensor Auswertung in den ULP Prozessor verlagern und den ESP32 nur aufwecken wenn sich z.B. der Sensorwert geändert hat. Ein gut kommentiertes Beispiel für den DS18B20 findet sich z.B. bei https://github.com/fhng/ESP32-ULP-1-Wire.

Update:

Rechenfehler beseitigt, natürlich ergeben 2 * 2000mAh Batterien in reihe keine 4000mAh. Danke Jörg.

Und wer sich für ULP interessiert dem sei die Seite von Jörg ans Herz gelegt. Er hat einen klasse Einsteiger Guide auf seiner Webseite.

6 Gedanken zu „ESP32 – Stromverbrauch im Deep-Sleep“

    1. Hallo Jonas,

      ok das ist wirklich nicht ganz offensichtlich muss ich zugeben. Gemessen wurde an BAT+ mit einem SIGLENT SDM3055 das in Reihe zur Spannungsquelle hing.

      Viele Grüße
      Steve

  1. Hallo Steve,

    ich bin im Internet auf diesen Blog gestossen weil ich auch das Problem mit dem hohen Stromverbrauch im DEEP-SLEEP bei dem ESP32 habe. Ich habe schon das Board ESP32 DEVKIT I verbannt und einen ESP32 WROOM auf einem Breakout PCB genommen. Dieser Wechsel hat schon sehr viel gebracht (von 50mA auf nur noch 2,2mA). Trotz ist das immer noch zu viel. Es handelt sich um eine Wetterstation mit 1Wire, I²C, Analoge Pins. Ich würde gerne die Taktfrequenz herabsetzen aber finde leider nichts wirkliches dazu bzw. bin noch ziemlicher Anfänger. Das oben beschriebene finde ich auch interessant aber da verstehe ich nur Bahnhof. Ich hatte immer gedacht das in der Loop nichts stehen darf für DEEP-SLEEP Mode. Für ein paar Erklärungen wäre ich Dir sehr dankbar.
    VG Thomas

    1. Wo sich die Taktfrequenz einstellen lässt hängt von deiner verwendeten Umgebung und Framework ab. Bei PlatformIO geht es z.b. hier.

      Die loop ist beim ESP32 nur ein kleiner Teil der läuft. Um WLAN, Bluetooth usw am laufen zu halten hat der ESP ein OS genauer ein freeRTOS laufen. Dieses kümmert sich darum wie die Aufgaben auf die 2 Prozessoren verteilt werden. Die loop die man für Arduino sieht ist nur einer der Prozesse die normalerweise laufen. Dieses OS kontrolliert also was gerade ausgeführt wird und eben auch dem entsprechend was Energie verbraucht. Vor dem Deep-Sleep sollte man darauf achten alles auszuschalten was man nicht braucht z.B. WLAN, BT, Analog Wandler, etc. Auch I2C verbraucht im Ruhezustand Energie, da es 2 Pull-up Widerstände an den Datenleitungen hat. Diese sollte man auch über einen Pin abschalten können wenn man wirklich alles an Sparpotential raus holen will.

      Ich hoffe das hat etwas weiter geholfen.

  2. Hallo Steve,

    vielen Dank für Deinen Beitrag. Ich bin über den Suchbegriff “gpio_hold_en” hier gelandet.
    Sehr interessant ist der Ruhestromverbrauch der Wandlerschaltung. Gibt es den Chip irgendwo zu bastlerüblichen Versandkosten? Oder sogar als Breakoutboard?
    Widerspruch muss ich bei der Berechnung der Laufzeit erheben. Du gehst davon aus, dass sich bei einer Reihenschaltung von zwei Zellen die Kapazität verdoppelt. Die bleibt aber bei 2Ah. Es erhöht sich die elektr. Energie von 3Wh auf 6Wh.
    Beim Einsatz von Wandlerschaltungen ist es einfacher mit der elektr. Arbeit zu rechnen und gleich den Wirkungsgrad einzubeziehen.

    Daraus ergibt sich:
    (2 * 1,5V * 2Ah * 0.9) / (3,3V * 5µA) = 327273h => 37Jahre

    Immer noch viel, aber ein deutlicher Unterschied, der sich auch durch die folgenden Berechnungen zieht.

    Gruß
    Jörg

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.