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:

4000mAh / 0,005mA = 80000h ~ 91 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:

4000mAh / 50mA = 80h ~ 3,3 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
4000mAh / 10,1mA = 396 ~ 16,6 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
4000mAh / 8,3mA = 481h ~ 20 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
4000mAh / 0,8mA = 5000h ~ 208 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 einfach 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
4000mAh / 0,45mA = 8888 ~ 370 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.

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.