Den ESP32 schlafen legen, um Strom zu sparen


Mein letztes Projekt, das ich hier heranziehen will, um die Schlafmodi des ESP32 an einem praktischen Beispiel zu erklären, ging darum, eine Mikrowelle an einer Funksteckdose mit einem CYD ferngesteuert an- und auszuschalten (siehe Screenshot rechts).

Bei meiner mittlerweile betagten Mikrowelle lässt sich keine genaue Zeit mehr einstellen. Aber das könnt ihr in dem Artikel dazu nachlesen. Nur noch so viel: CYD steht für "Cheap Yellow Display" und ist ein ESP32 mit Touchscreen und ein bisschen mehr auf einer Platine.

Deep oder Light Sleep?

Mein eigentlicher Plan war eigentlich, das CYD an eine Powerbank anzuschließen. Dabei sollte es natürlich nicht immer laufen, sondern ich wollte in den 23 Minuten und 40 Minuten pro Tag, wo es nicht gebraucht wird, den Bildschirm ausschalten und den ESP32 in einen Schlafmodus versetzen, in dem er so gut wie keinen Strom verbrauchen würde, den sogenannten Deep Sleep. In diesem Schlafmodus soll der ESP32 nur so 10 bis 150 µA ziehen. Laut Datenblatt. Warum das am Ende dann doch nicht so richtig funktioniert, erkläre ich euch später.

Ich wollte meinen CYD On/Off-Timer nach 30 Sekunden Nichtstun in den Schlafmodus schicken und dann mit einem Tap auf den Bildschirm wieder aufwecken. Aber ich war da ein bisschen naiv und ging zu optimistisch an die Sache.

Schnell stellte sich heraus, dass der extrem stromsparende Deep Sleep immer so eine Art Reset macht, wenn er wieder aufwacht. Was natürlich unschön ist. Dann kann ich im Prinzip auch einen Schalter zwischen Netzteil und CYD löten. Dann habe ich Null Stromverbrauch und auch jedesmal einen Reset. Der Deep Sleep war also schnell vom Tisch.

Es gibt aber noch einen Light Sleep Modus. Mit dem kann man im Prinzip da weitermachen, wo man aufgehört hat. Denn der Speicherinhalt bleibt erhalten. Das kostet natürlich ein wenig Strom und so läuft der ESP32 im Light Sleep noch so mit 1 bis 5 mA - auf dem Datenblatt. Später sollte sich herausstellen: Das gilt nicht für das CYD.

Kurzer Einschub für Interessierte: Deep Sleep mit Restaurierung nach Reset

Ein kurzer Einschub für Interessierte, der ein wenig tiefer geht und den man nicht unbedingt gelesen haben muss:

Man kann im Prinzip schon aus dem Deep Sleep so erwachen, dass es da weitergeht, wo man aufgehört hat. Dazu muss man sich den Zustand mit allen veränderbaren Variablen in den sogenannten RTC-Speicher kopieren. Der heißt so, weil da auch die Real Time Clock dran angeschlossen ist. Dieser Speicherbereich geht auch im Deep Sleep nicht verloren. Beim ESP32-WROOM32 (also "classic") hat man da 8 kBytes von, was genügen sollte.

Bei der Startroutine (setup() im Arduino Framework) muss man dann prüfen, ob man neu startet oder aufwacht, weil man beim Schlafen gehen zum Beispiel eine bestimmte Variable, etwa DeepSleepActive, gesetzt hat. Und wenn das der Fall ist, die normalen Variablen wieder aus denen im RTC-Speicher hinterlegten wiederherstellen und dann an die Stelle im Programm springen, wo es vor dem Deep Sleep verlassen worden ist.

Das ist eine Heidenarbeit für ein paar Milliampere gegenüber den Light Sleep und nur selten den Aufwand wert. Zudem bietet das viel Potential, um Fehler zu machen und ist nicht leicht zu testen. Aber wenn man wirklich wenig super stromsparend sein muss, führt wohl kein Weg um den Deep Sleep und man muss in den sauren Apfel beißen. Außer beim CYD. Da läuft das anders, wie ihr später seht.

Light Sleep, Deep Sleep oder doch nur "Power safe"?

Von der Programmierung her unterscheidet sich Deep Sleep und Light Sleep gar nicht so sehr von einander: Man definiert vorher einen Aufweckmechanismus. Dann startet man den Sleep und wenn das Aufwachereignis eintritt, wird bei Light an dieser Stelle im Code weitergemacht, bzw. bei Deep Sleep ein Reset durchgeführt.

Aufweckmechanismus

Als Aufweckmechanismus kommen drei Arten in Frage:

1. Ein Timer:
esp_sleep_enable_timer_wakeup(uint64_t time_in_us)
Der ESP32 schläft die in Mikrosekunden angegebene Zeit und wacht danach von selbst wieder auf. Ein Anwendungsfall wäre, alle 10 Millisekunden (time_in_us = 10000) aufzuwachen und nachzuschauen, ob gerade das Display niedergedrückt (Touch) ist. Man kann die Zeitspanne natürlich auch verlängern, dann werden halt nur längere Tap erkannt.

Der Vorteil dabei ist, dass der ESP32 auf jeden Fall aufwacht und dann voll da ist. Man kann damit also alle Konditionen überprüfen. Das ist bei einer externen Aufweckquelle nicht der Fall.

2. Flankenwechsel an einem Pin:
esp_sleep_enable_ext0_wakeup(gpio_num_t gpio, int level)
Damit sind Light und Deep Sleep möglich. Darin verweilt der ESP32, bis der Hardware-Interrupt eintritt, nämlich, dass ein Pin HIGH oder LOW wird. Dieser Pin muss ein RTC-GPIO sein, damit es funktioniert. Anwendung wäre zum Beispiel, wenn ein Pin als Input / Pullup definiert ist und ich ihn mit einem Taster auf Low ziehe und diesen Pin als Wakeup-Pin übergebe: Beim Druck auf den Taster wacht der ESP32 dann auf und macht weiter (bzw. einen Reset im Deep Sleep).

Man kann auch
esp_sleep_enable_gpio_wakeup()
benutzen. Dann muss es kein RTC-GPIO sein, sondern es funktioniert jeder GPIO-Pin, allerdings nur im Light-Mode.

Kleiner Einschub für das CYD: Das CYD kann einen Interrupt auf Pin 36 (XPT2046_IRQ) senden, wenn der Touchscreen gedrückt wird. Das kann auch ein Hardware-Interrupt sein, wenn man die entsprechende Library benutzt. Ich benutze aber die Bitbang-Library für das Touchdisplay, damit das CYD überhaupt gescheit funktioniert und sich nichts überschneidet - es ist kompliziert, lest meine Artikel für mehr Infos. Die aktualisiert den Pin 36 nur softwaremäßig, also nicht, wenn der ESP32 im Sleep-Modus ist, denn dann läuft das Programm für den Pin36-Update ja nicht.

esp_sleep_enable_ext1_wakeup(uint64_t mask, esp_sleep_ext1_wakeup_mode_t mode)
ermöglicht übrigens, gleich auf mehrere Pins zu reagieren, falls man das braucht. Und dann gibt es da noch die speziellen
esp_sleep_enable_touchpad_wakeup() und esp_sleep_enable_uart_wakeup()
Befehle, um bei Aktivität des Touchpads oder der seriellen Schnittstelle zu reagieren. Was im Prinzip aber auch nichts anderes als ein Hardware-Interrupt mit Flankenwechsel ist.

3. Über den Ultra Low Power (ULP) Coprozessor:
esp_sleep_enable_ulp_wakeup()
Die Programmierung des ULP, der in den ESP32 steckt, ist eine Sache für sich (7,8) und nicht ganz einfach. Aber vom Prinzip her ist der ULP ein kleiner Neben-Prozessor, der sehr wenig Energie benötigt und der weiterlaufen kann, wenn die große MCU in Deep Sleep ist, also sozusagen abgeschaltet. Im ULP kann dann eine Routine laufen, die Checks machen kann und dann ggf. die MCU wieder aufwecken kann.

Abschalten, was geht

Bevor wir den ESP32 wirklich ins Bett schicken, machen wir aus, was geht. Sinnbildlich gesprochen machen wir den Fernseher und überall das Licht aus, eben alles, was Strom braucht. Hier ein Beispiel für mein CYD-Projekt:

ESP32 schlafen schicken

Nachdem sozusagen der Wecker gestellt ist, schicken wir den ESP32 jetzt ins Bett, entweder in einen leichten Schlaf mit
esp_light_sleep_start()
oder einen tiefen Schlaf mit
esp_deep_sleep_start()
Beim leichten Schlaf schicken wir den ESP32 einfach nur ins Bett. Beim Aufwachen, wenn der Wecker klingelt, ist er dann wieder ganz da ist und macht da weiter, wo er aufgehört hatte. Beim tiefen Schlaf zieht sich der ESP32 komplett aus und bekommt ein Schlafmittel, das bewirkt, dass er alles vergisst. Beim Aufwachen startet er komplett von vorne. Er macht eine Art Reset.

Schlaf gut und träum süß


Nachdem alles mögliche ausgeschaltet ist, sollte ein ESP32 nur noch wenig Milliampere oder sogar beim Deep Sleep nur ein Bruchteil eines Milliamperes Strom ziehen.

Man muss allerdings beim Light Sleep aufpassen, was man alles vorher abschaltet, damit es weiter gehen kann. Beim Deep Sleep ist das egal, der macht eh einen Reset. Aber beim Light Sleep muss man daran denken, alles wieder korrekt herzustellen: WiFi und Bluetooth wieder zu initialisieren und die Verbindungen wieder herzustellen, das LVGL-Subsystem wieder auf die Beine zu stellen (hat bei mir nicht funktioniert), den Bildschirm-Controller und Bildschirm selbst wieder anzuschalten, die Hintergrundbeleuchtung wieder anzuschalten etc. pp. Dabei sollte man sich immer fragen, wieviel Strom man spart und ob es die Stromersparnis den Aufwand aufwiegt.

Ich habe mit dem CYD ein bisschen experimentiert und mir eine Routine geschrieben, in dem ich wählen kann, wie tief ich meinen CYD schlafen schicke. Dabei gibt es drei Abstufungen: Für light und deep Sleep braucht es einen Hardware-IRQ. Der an Pin 36 funktioniert mit BitBang ja leider nicht als Hardware. Man müsste darum durchmessen, wo die Folien der resistiven Touchscreens hinführen. Die werden bei einem Druck ja irgendwie kurzgeschlossen. Das müsste man dann einem freien Pin verbinden und als Hardware-IRQ benutzen. Aber da habe ich drauf verzichtet. Für mich kommt zu Zeit eigentlich nur deep=0 in Frage.

void enter_powersave() { int deep = 0; debug_print ("Entering Sleep / Power Save Mode "); debug_println(deep); // CPU Frequenz reduzieren vor Sleep // bringt nichts, macht er wohl schon von selbst setCpuFrequencyMhz(80); // 1. LVGL stoppen if (deep > 1) lv_deinit(); // 2. Display ausschalten if (deep > 1) { tft.fillScreen(TFT_BLACK); delay(10); } // bringt nichts und zeigt nach light aufwachen nichts mehr an setTftBrightness(0); delay(10); if (deep > 1) { tft.writecommand(0x28); delay(10); } // Display off // bringt nichts und zeigt nach light aufwachen nichts mehr an tft.writecommand(0x10); delay(10); // Display-Controller Sleep mode // 3. ggf. Hardware-IRQ-Aufweck-Ereignis setzen if (deep > 0 && Pin_WakeUpIRQ != 0) { pinMode(Pin_WakeUpIRQ, INPUT_PULLUP); // alternativer Pin, der auf GND gezogen werden muss zum Aufwachen esp_err_t ret = esp_sleep_enable_ext0_wakeup((gpio_num_t) Pin_WakeUpIRQ, 0); // LOW triggert Wakeup if (ret != ESP_OK) { debug_println ("Konnte Aufwach-IRQ nicht setzen!"); debug_println("Error: " + String(ret));} } // 4. Andere Peripherals ausschalten // eigentlich schon generell aus WiFi.mode(WIFI_OFF); btStop(); Serial.end(); //stürzt ab? adc_power_off(); // deep sleep macht nach Aufwachen einen Reset! if (deep == 2) { esp_deep_sleep_start(); // wenn wieder aufgeweckt: RESET } else if (deep == 1) { esp_light_sleep_start(); // bringt nochmal was, von 0.5W (50% TftBrightness) auf 0.1W } else if (deep == 0) { delay (1000); //erstmal Touchscreen wieder loslassen while (!xptTouched()) { // warten bis zum nächsten Touch delay(50); } } //restlichen Dinge wieder an Serial.begin(115200); //stürzt ab adc_power_on(); // Ab hier nur noch für LIGHT, deep macht immer Reset /////////////////////// debug_print ("Exiting Sleep / Power Save Mode "); debug_println(deep); // Display wieder aktivieren setTftBrightness(TftBrightness); tft.writecommand(0x11); // Wake Display from sleep delay(120); // Warten bis Display bereit refreshLVGL(); reset_sleep_timer(); }
Ich habe mit den 3 unterschiedlichen Schlaftiefen herumexperimentiert und bin bei der bei mir eingesetzten 2.8" CYD-Version auf folgende Verbrauchswerte gekommen: Man sieht: das CYD lässt sich gar nicht richtig "herunterfahren". Um die 70 mA sind viel zu viel im Light bzw. Deep Sleep. Der Stromverbrauch zwischen light und deep Sleep ist in dieser Relation dermaßen marginal, dass man genausogut den light sleep benutzen kann.

Beim Design des CYD scheint einfach nicht an einen Deep Sleep gedacht worden zu sein. Ich habe da ein paar Verdachtsmomente, die zu überprüfen mir allerdings nicht sonderlich sinnvoll erscheint, weil ich eh nicht viel dran ändern kann:

Mein Fazit

Die ESP32 Sleep Modes sind toll, um einen ESP32 lange an einem Akku oder einer Powerbank zu betreiben, wenn er nur hin und wieder benutzt wird. Dazu muss das Board aber auch entsprechend designt sein.

Für das CYD ist das allerdings nichts, denn hier ist das Board einfach nicht auf Energie sparen ausgelegt.

Darum betreibe ich mein Mikrowellen-Steuer-CYD auch an einem USB-Ladegerät. Zusammen mit der Funksteckdose sind das trotzdem unter 1 Watt im Powersave-Modus. Das läppert sich zwar zu unter 3 Euro Stromkosten im Jahr, aber das ist mir der extra Komfort wert.

Demo und Video

Hier ein kleines Demonstrations-Video für die On/Off-Timer-App für das CYD gemacht, dass die Dialoge und Timer in Aktion zeigt:




Quellen, Literaturverweise und weiterführende Links