Režimy spánku modulu ESP32 (Arduino IDE)
Když začneme pracovat s mikrokontroléry, často se soustředíme hlavně na to, co všechno dokáží – připojit se k Wi-Fi, číst senzory, ovládat relé nebo zobrazovat data. Dříve nebo později ale překvapivě narazíme na opačný problém: Jak to udělat, aby zařízení většinu času vlastně „nic nedělalo“! Proč? Třeba proto, aby zbytečně neplýtvalo energií. A právě v tu chvíli přichází ke slovu režimy spánku, které jsou pro moderní mikrokontroléry, jako je ESP32, naprosto klíčové.
ESP32 je zajímavý mimo jiné tím, jak širokou škálu úsporných režimů nabízí. Nejde jen o jednoduché „zapnuto/vypnuto“ – máme k dispozici několik úrovní spánku, které se liší tím, co všechno zůstává aktivní, jak rychle se z nich probudíme a kolik energie při tom spotřebujeme.
V tomto článku se tedy na spánkové režimy trochu podíváme z pohledu bastlíře. Nebudeme se snažit o úplnou akademickou přesnost, ale spíše o praktické pochopení toho, co se uvnitř ESP32 děje a kdy se který režim vyplatí použít.
Budeme vycházet z prostředí Arduino IDE, které je mezi DIY vývojáři velmi rozšířené.
- Čtenáře hledající článek o problematice režimů spánku modulu ESP32 v MicroPythonu si dovolíme odkázat na článek: Režimy spánku ESP32 v MicroPythonu, který je taktéž součástí tohoto webu.
Přehled spánkových režimů ESP32
Než se pustíme do praktických ukázek, udělejme si rychlý přehled toho, co nám vlastně ESP32 nabízí. I když se často mluví jen o „sleep“, ve skutečnosti jde o několik různých režimů, které se zásadně liší chováním i spotřebou.
- Active mode (běžný běh)
Tohle je stav, ve kterém ESP32 normálně pracuje – běží procesor, Wi-Fi, Bluetooth i vše ostatní. Spotřeba je zde nejvyšší a žádné úspory se nekonají. - Modem-sleep
Procesor zůstává aktivní, ale vypne se Wi-Fi/Bluetooth část, když ji zrovna nepotřebujeme. Hodí se ve chvíli, kdy například čekáme na data, ale nechceme držet rádiovou část neustále zapnutou. - Light sleep (lehký spánek)
Zde už uspáváme i procesor, ale většina paměti a periferií zůstává zachována. Probouzení je velmi rychlé a program pokračuje v podstatě tam, kde skončil. Je to dobrý kompromis mezi úsporou energie a rychlou odezvou. - Deep sleep (hluboký spánek)
Nejčastěji používaný režim u bateriově napájených projektů. Většina čipu je vypnutá, běží jen vybrané části (RTC – reálný čas a několik málo obvodů). Spotřeba padá na minimum, po probuzení se ESP32 chová jako po resetu – program běží od začátku. - Hibernation (hibernace)
Ještě „tvrdší“ varianta deep sleep, kde se vypíná téměř všechno. Spotřeba je úplně minimální, ale možnosti probuzení jsou velmi omezené.
Když se na to podíváme prakticky: čím hlubší spánek zvolíme, tím méně energie spotřebujeme, ale tím víc „zapomeneme“, co jsme předtím dělali, a tím je i složitější návrat zpět.
- UPOZORNĚNÍ:
- Je třeba ale zmínit, že ne všechny výše uvedené režimy zde budeme využívat. Je zde totiž problém, jak moc lze k jednotlivým režimům v Arduino IDE přistupovat. Arduino core, které se v prostředí Arduino IDE využívá pro obsluhu ESP32, totiž ve skutečnosti používá ESP-IDF (oficiální framework od Espressifu), takže většina režimů tam existuje, ale ne všechny mají komfortní „arduino-style“ obsluhu nebo smysluplné využití v naší hobby praxi.
Z pohledu Arduino IDE nemá smysl řešit úplně všechny režimy, které ESP32 nabízí na papíře. Reálně se zaměříme na dva, které nám dávají smysl:
- light sleep, kdy se zařízení jen „lehce uspí“ a rychle probouzí,
- deep sleep, který je ideální pro bateriové projekty a používá se nejčastěji.
Režimy jako modem-sleep sice ESP32 podporuje, ale stará se o ně automaticky Wi-Fi/Bluetooth knihovna a běžně je přímo neřídíme. A hibernace sice existuje, ale v Arduino prostředí pro ni prakticky nemáme důvod, deep sleep ji ve většině případů plně nahrazuje.
Jinými slovy: i když má ESP32 režimů spánku celou řadu, v praxi si v Arduino IDE vystačíme s malou, ale dobře použitelnou podmnožinou.
„Hej, Šípková Růženko, vstávej!“ – jak ESP32 probudit
Spánek by sám o sobě nebyl moc užitečný, kdybychom neměli spolehlivý způsob, jak zařízení znovu probudit. ESP32 naštěstí nabízí několik možností, jak mu dát signál, že má vstát a znovu začít pracovat.
- Časovač (timer wakeup)
Nejčastější varianta. Řekneme: „Usni na 10 sekund,“ a ESP32 se po uplynutí této doby samo probudí. Typický scénář: každou minutu změřit teplotu, odeslat data a zase usnout. - Externí pin (ext0/ext1 wakeup)
Probuzení signálem z GPIO pinu. Může to být tlačítko, pohybový senzor, kontakt na dveřích apod. - Dotykové piny (touch wakeup)
ESP32 má kapacitní dotykové vstupy, které můžeme využít jako „neviditelné tlačítko“. Stačí se dotknout vodiče nebo dotykové plošky a zařízení se probudí. - ULP koprocesor
Trochu pokročilejší, ale velmi zajímavá možnost. Malý pomocný procesor může běžet i během deep sleep (například monitorovat analogovou hodnotu nebo senzor) a probudit hlavní jádro jen tehdy, když se něco skutečně stane – Tomu se ale dnes věnovat nebudeme 😒 - Reset (napájení/EN pin)
Klasika – úplné „tvrdé“ probuzení. Použije se při zapnutí nebo resetu zařízení.
I spánek má svá pravidla
Než začneme režimy spánku ve svém projektu „jen tak zapínat“, je dobré se na chvíli zastavit a promyslet si, co přesně od něj očekáváme. Režimy spánku totiž nejsou jen nějaké univerzální „tlačítko na úsporu energie“. Dost zásadním způsobem ovlivňují způsob, jakým náš program funguje.
U light sleep je situace poměrně přímočará. Můžeme si ho představit jako takovou úspornější pauzu. Procesor se na chvíli uspí, ale po probuzení pokračujeme víceméně tam, kde jsme skončili. Paměť zůstává zachovaná, struktura programu se nemění a pohled „Arduino stylu“ (tj. setup() + nekonečný loop()) zůstává nedotčen. Pokud tedy potřebujeme krátké přestávky a rychlou reakci, je light sleep relativně bezproblémový.
Úplně jiný svět ale začíná u deep sleep. Tady už totiž neplatí představa, že program po probuzení „plynule naváže“. Modul ESP32 se po probuzení chová jako po resetu – znovu se spustí setup() a celý program běží od začátku. To má zásadní důsledky:
- stav programu se ztratí (pokud si ho sami nějak „vtipně“ neuložíme),
loop()se nemusí vůbec dostat ke slovu- a celkově musíme začít uvažovat jinak než u klasického Arduino sketch.
Typická chyba, na kterou dříve nebo později narazíme, vypadá například takto: Vložíme esp_ do setup() a očekáváme, že se „později“ bude něco dít v loop(). Jenže k tomu nikdy nedojde – zařízení se uspí dřív, než se do loop() vůbec dostane. A po probuzení znovu začínáme od setup(), takže se situace jen a jen opakuje.
Z toho plyne důležitý závěr: při použití deep sleep už nepíšeme program jako nekonečný cyklus, ale spíš jako krátký scénář typu:
probuď se → něco udělej → znovu usni
Jinými slovy: Logika aplikace se přesouvá ze smyčky loop() do jednorázového běhu, který se opakuje při každém probuzení. Jakmile si tenhle způsob uvažování osvojíme, začne deep sleep dávat smysl.
Light sleep (lehký spánek)
V úvodu jsme si vysvětlili, co se u jednotlivých režimů děje, takže teď už nemusíme všechno opakovat. Light sleep si můžeme představit jako řízenou pauzu, ze které se zařízení poměrně rychle probudí a plynule pokračuje dál. To zní jednoduše – a ono to ve skutečnosti jednoduché opravdu je.
Abychom si to ověřili, zkusme si úplně základní příklad. Necháme ESP32 chvíli běžet, pak ho uspíme na přesně daný čas a budeme sledovat, co se po probuzení stane. Nejde o nic složitého, spíš o to vidět celé chování „naživo“ a udělat si jasnou představu, co od light sleep čekat.
Použijeme k tomu následující ukázku:
#include <Arduino.h>
#include "esp_sleep.h"
#define LED_PIN 2
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
Serial.println("Jsem vzhuru, za 5 s usnu...");
delay(5000);
// probuzeni po 10 s
esp_sleep_enable_timer_wakeup(10 * 1000000ULL);
Serial.println("Usinam (light sleep)...");
delay(100);
esp_light_sleep_start();
Serial.println("Kdo me probudil?");
}
void loop() {}
Na začátku děláme přesně to, co bychom čekali:
- nastartujeme sériovou linku
- nastavíme pin pro vestavěnou LED
- LED rozsvítíme
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
Tady se chováme úplně standardně, žádné speciality kolem spánku.
Dále si dáme 5 sekund na to, abychom viděli, že je zařízení aktivní. Prakticky: máme čas otevřít Serial Monitor a ověřit, že program běží tak, jak má.
Serial.println("Jsem vzhuru, za 5 s usnu...");
delay(5000);
Nyní se pustíme do toho „spaní“. Nejdříve si musíme všimnout, že pro používání režimů spánku musíme do kódu přidat knihovnu esp_sleep.h. Následně se můžeme začít zabývat dalšími věcmi okolo režimů spánku, třeba nastavením zdroje probuzení:
esp_sleep_enable_timer_wakeup(10 * 1000000ULL);
Zde nastavujeme probuzení pomocí časovače a zároveň říkáme modulu ESP32: „Probuď se za 10 sekund“ (čas je zadán v mikrosekundách, proto násobíme milionem). Je důležité si uvědomit, že bez nastavení zdroje probuzení bychom zařízení uspali „navždy“ (v řeči světa mikrokontrolérů myšleno do nejbližšího stisknutí resetu)
Samotné nastavení zdroje probuzení nám ještě modul neuspalo, to přichází až teď:
Serial.println("Usinam (light sleep)...");
delay(100);
esp_light_sleep_start();
Příkazem esp_ teprve dojde k samotnému uspání. Dobré je si všimnout krátké pauzy delay(100), po vypsání zprávy na sériový port. Ta je zde proto, aby se stihla odeslat zpráva do sériového portu. Zkuste tuto pauzu vynechat a pak se podívejte, jak vaše zpráva v sériovém monitoru dopadla. 😉
Ve chvíli provedení příkazu esp_ se procesor uspí, běh programu se zastaví a čekáme na probuzení. Jakmile uběhne 10 sekund, časovač „nakopne“ procesor a program dále pokračuje:
Serial.println("Kdo me probudil?");
To je zásadní rozdíl oproti deep sleep – žádný restart, žádné setup() od začátku, zkrátka u light sleep pokračujeme dál.
Je zde ještě jedna věc – dívejte se na vestavěnou LED modulu. Po celou dobu našeho experimentu svítí. Tedy nastavená úroveň HIGH na pinu GPIO 2 je platná i v průběhu lehkého spánku.
Deep sleep (hluboký spánek) – když se ESP32 „opravdu vypne“
Light sleep jsme si vyzkoušeli jako řízenou pauzu, ze které se program plynule vrací zpět do běhu. U deep sleep, jak jsme již naznačili, musíme úplně změnit úhel pohledu – tady už nejde o pauzu, ale o téměř úplné vypnutí zařízení.
Opět si to ukážeme na jednoduchém příkladu. Na první pohled bude kód vypadat skoro stejně:
#include <Arduino.h>
#include "esp_sleep.h"
#define LED_PIN 2
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
Serial.println("Jsem vzhuru, za 5 s usnu...");
delay(5000);
esp_sleep_enable_timer_wakeup(10 * 1000000ULL);
Serial.println("Usinam (deep sleep)...");
delay(100);
esp_deep_sleep_start();
}
void loop() {}
Co je zde jinak?
Na první pohled je rozdíl vlastně jediný:
esp_deep_sleep_start();
místo původního:
esp_light_sleep_start();
(Pravda, druhý rozdíl je chybějící příkaz Serial.println("Kdo me probudil?"); za startem spánku, ale to teď není klíčové.)
Právě tato první drobná změna úplně mění chování celého programu. Ve chvíli, kdy zavoláme esp_ procesor se zastaví, většina periferií se vypne a běh programu končí. Na rozdíl od light sleep se po 10 vteřinách nikam nevracíme. Modul ESP32 se probudí, ale nepokračuje tam, kde skončil – místo toho proběhne reset a spuštění setup() úplně od začátku.
To znamená, že v Serial Monitoru uvidíme opakovaně:
Jsem vzhuru, za 5 s usnu...
A to stále dokola.
Kdyby za uspáním byl zmíněný příkaz Serial.println("Kdo me probudil?"); nikdy by stejně neproběhl.
Je zde ale ještě jeden „viditelný“ rozdíl a to, že během hlubokého spánku vestavěná LED zhasne. Tato LED je nyní vlastně taková naše „diagnostická pomůcka“
LED jsme na začátku programu rozsvítili příkazem:
digitalWrite(LED_PIN, HIGH);
U light sleep LED zůstala svítit i během spánku. U deep sleep LED zhasne ve chvíli, kdy ESP32 usne.
Proč?
Pin GPIO 2 už není aktivně řízen procesorem, jeho stav se neudržuje a hardware se dostane do „klidového“ stavu. Jinými slovy: hodnota HIGH se na tomto pinu ztratí, protože modul ESP32 se během deep sleep prakticky vypne.
Detekce probuzení z deep sleep
Jak poznat, proč se modul ESP32 probudil? U light sleep jsme se tím příliš nezabývali – program se prostě „na chvíli zastaví“ a pak pokračuje dál, takže obvykle víme, co se stalo a proč. U deep sleep je ale situace jiná. Po probuzení se ESP32 restartuje a bez dalšího rozlišení vůbec nepoznáme, jestli jsme zařízení právě zapnuli, nebo jestli se jen probudilo ze spánku.
Ale to by se nám asi hodilo, ne? Určitě chceme, aby se program choval jinak při prvním spuštění (např. inicializace, připojení k Wi-Fi), a jinak při probuzení z deep sleep (např. jen rychlé měření a zase spánek).
ESP32 nám naštěstí poskytuje informaci o tom, co bylo příčinou probuzení. (Takže vlastně to probuzení z deep sleep zase není tak úplně „normální“ reset)
Stačí nám při startu programu načíst hodnotu funkce esp_:
esp_sleep_wakeup_cause_t duvod = esp_sleep_get_wakeup_cause();
Tuto funkci zavoláme typicky hned na začátku setup(). Proměnná duvod nám pak obsahuje jednu z několika hodnot. Pro naše účely jsou nejdůležitější tyto:
ESP_SLEEP_ WAKEUP_ UNDEFINED
běžný start – zapnutí, reset tlačítkem, nahrání programuESP_SLEEP_ WAKEUP_ TIMER
probuzení časovačem (náš typický případ)ESP_/SLEEP_ WAKEUP_ EXT0 EXT1
probuzení externím pinem (viz dále)
Do našeho programu tedy stačí přidat pár řádků:
esp_sleep_wakeup_cause_t duvod = esp_sleep_get_wakeup_cause();
if (duvod == ESP_SLEEP_WAKEUP_TIMER) {
Serial.println("Probuzeni z deep sleep (casovac)");
} else {
Serial.println("Bezny start (reset nebo zapnuti)");
}
Díky tomu můžeme program rozdělit na dvě logické větve:
- První spuštění
- nastavíme vše potřebné
- můžeme provést delší inicializaci
- případně informujeme uživatele
- Probuzení z deep sleep
- víme, že jsme „tu“ už byli
- provedeme jen nutnou práci (např. měření)
- rychle se vrátíme zpět do spánku
Celkový program naší ukázky hlubokého spánku bude tedy vypadat následovně:
#include <Arduino.h>
#include "esp_sleep.h"
#define LED_PIN 2
void setup() {
esp_sleep_wakeup_cause_t duvod = esp_sleep_get_wakeup_cause();
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
if (duvod == ESP_SLEEP_WAKEUP_TIMER) {
Serial.println("Probuzeni z deep sleep (casovac)");
Serial.println("Už se mi spat nechce, tak budu blikat.");
} else {
Serial.println("Bezny start (reset nebo zapnuti)");
Serial.println("Jsem vzhuru, za 5 s usnu...");
delay(5000);
esp_sleep_enable_timer_wakeup(10 * 1000000ULL);
Serial.println("Usinam (deep sleep)...");
delay(100);
esp_deep_sleep_start();
}
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
}
Slibovali možné rozdělení programu podle důvodu spuštění, zde ho vidíme. Logika programu je totiž následující:
- při běžném startu: zavolá se
esp_tím pádem se nikdy nedostaneme dodeep_ sleep_ start() loop()a LED zhasne. - při probuzení z deep sleep: do
setup()sice vstoupíme znovu, ale už znovu neusínáme, takže program doběhne doloop(), kde je „blikací“ kód LED (HIGH → 500 ms → LOW → 500 ms).
Kdybychom chtěli rozlišit všechny možné případy, tj. reset, probuzení časovačem a probuzení piny (EXT0 a EXT1), dotykem či ULP koprocesorem, vypadal by rozhodovací kód kupříkladu následujícím způsobem:
esp_sleep_wakeup_cause_t duvod = esp_sleep_get_wakeup_cause();
switch (duvod) {
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Probuzeni casovacem");
break;
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("Probuzeni EXT0");
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial.println("Probuzeni EXT1");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
Serial.println("Probuzeni dotykem");
break;
case ESP_SLEEP_WAKEUP_ULP:
Serial.println("Probuzeni ULP koprocesorem");
break;
default:
Serial.println("Jine probuzeni - napr. (re)start");
break;
}
Probuzení časovačem
Začneme tím, co už jsme si vlastně nenápadně vyzkoušeli v předchozích ukázkách – probuzení pomocí časovače. Je to vůbec nejjednodušší způsob, jak ESP32 po uspání znovu „probrat“, a v praxi se používá velmi často. Princip je jednoduchý: před uspáním řekneme modulu, za jak dlouho se má probudit, a o zbytek se už postará hardware.
Celé kouzlo je v jediné funkci:
esp_sleep_enable_timer_wakeup(cas_v_us);
Například:
// 60 sekund
esp_sleep_enable_timer_wakeup(60 * 1000000ULL);
- POZNÁMKA: (Proč je tam to
ULL) - Možná nás napadlo, proč používáme:
1000a ne jen000ULL 1000?000 - Důvod je jednoduchý –
ULLznamená unsigned long long, tedy hodnoty jsou pak ukládány jako 64bitové číslo.- Bez toho by při větších číslech mohlo dojít k přetečení a výsledný čas by byl úplně jiný, než čekáme.
Při probouzení modulu ESP32 časovačem je třeba si dát pozor na pořadí příkazů. Musí to být:
- nastavíme zdroj probuzení
- uspíme zařízení
tedy:
esp_sleep_enable_timer_wakeup(...);
esp_deep_sleep_start();
Pokud bychom zapomněli na první krok, ESP32 sice usne… ale už se samo nevzbudí.
Je dobré si uvědomit, že čas neběží v hlavním procesoru – hlídá ho RTC (Real-Time Clock) část čipu, která zůstává aktivní i v deep sleep (na RTC si ještě vzpomeneme!). Díky tomu je probuzení spolehlivé a energeticky velmi úsporné.
Praktická ukázka – DHT11 + deep sleep
Abychom si časovač a deep sleep zasadili do reálného kontextu, podíváme se na jednoduchý, ale velmi typický příklad: pravidelné měření teploty a vlhkosti pomocí čidla DHT11. Použití čidla DHT11 jsme si již ukazovali v článku: ESP32 a čidlo teploty a vlhkosti DHT11/DHT22, takže se zde budeme spíše zaměřovat na režim spánku.
ESP32 se v našem ukázkovém případě vždy probudí, chvíli počká na stabilizaci čidla, provede měření, vypíše hodnoty na sériovou linku a opět se uspí – tentokrát na celých 60 sekund. Celý cyklus se pak neustále opakuje.
Zapojení je přitom velmi jednoduché (viz schéma níže): datový pin čidla připojíme na GPIO 4, napájení na 3,3 V a zem na GND. Pokud používáme samostatné čidlo (ne modul), nezapomeneme ani na pull-up rezistor mezi VCC a datovým pinem.
Na této ukázce je hezky vidět typický „deep sleep scénář“. Zařízení většinu času (v našem případě pouhých 60 s) spí, aby šetřilo energii, a probouzí se jen na krátký okamžik, kdy vykoná svou práci. Právě takhle funguje velká část jednoduchých IoT senzorů napájených z baterie.
Kód programu:
#include <Arduino.h>
#include "esp_sleep.h"
#include "DHT.h"
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("Stabilizace cidla...");
dht.begin();
delay(2000);
float t = dht.readTemperature();
float h = dht.readHumidity();
if (isnan(t) || isnan(h)) {
Serial.println("Chyba cteni");
} else {
Serial.print("Teplota: ");
Serial.println(t);
Serial.print("Vlhkost: ");
Serial.println(h);
}
Serial.println("Usinam na 60 s...");
delay(100);
esp_sleep_enable_timer_wakeup(60 * 1000000ULL);
esp_deep_sleep_start();
}
void loop() {}
Na následujícím obrázku č. 6 výsledný výstup na Serial Monitoru vývojového prostředí Arduino IDE. Vidíme, že došlo k resetu způsobenému probuzením z deep sleep (červeně podtržená zpráva), následně v červeném rámečku vidíme naměřené hodnoty z čidla DHT11.
(vidíme probuzení po hlubokém spánku a načtení hodnot, pak opět spánek).
Externí probuzení (GPIO)
Doposud jsme měli probuzení plně pod kontrolou – nastavili jsme čas a ESP32 se podle něj poslušně probudilo. V praxi ale často potřebujeme něco jiného, a to, aby zařízení reagovalo na událost z okolí. Typicky stisk tlačítka, otevření dveří, signál ze senzoru nebo třeba změnu logické úrovně na pinu.
Právě k tomu slouží externí probuzení pomocí GPIO. ESP32 dokáže i v hlubokém spánku „naslouchat“ vybraným pinům a probudit se ve chvíli, kdy na nich nastane požadovaná změna.
Začneme jednodušší variantou, označovanou jako EXT0.
EXT0: probuzení jedním pinem
EXT0 je nejzákladnější způsob externího probuzení – sledujeme jeden konkrétní GPIO pin a probudíme se při jeho přechodu na definovanou logickou úroveň.
Používá se k tomu funkce:
esp_sleep_enable_ext0_wakeup(GPIO_NUM_X, uroven);
kde:
GPIO_je číslo pinu (např.NUM_X GPIO_)NUM_4 urovenje:0→ probuzení přiLOW1→ probuzení přiHIGH
Na rozdíl od běžného běhu programu je třeba si uvědomit, že nyní je procesor během deep sleep vypnutý a k dispozici zůstává jen malá část čipu (tzv. RTC doména). A právě ta má na starosti:
- sledování vybraného pinu
- vyhodnocení změny
- vyvolání probuzení
Důležitým důsledkem výše uvedeného je, že nelze použít úplně libovolný pin! EXT0 funguje jen na „rodině“ tzv. RTC GPIO pinů (např. GPIO 0, 2, 4, 12–15, 25–27, 32–39).
K EXT0 je dobré ještě poznamenat několik detailů, ve kterých se může skrývat onen pověstný „ďábel“:
EXT0 reaguje na logickou úroveň, ne na hranu. To znamená, že pin musí skutečně přejít do daného stavu a chvíli v něm zůstat. Další věcí je, že pokud pin „visí ve vzduchu“, bude náhodně měnit hodnotu a může (spíše bude) způsobovat falešné probuzení. Řešením je použití pull-up/pull-down rezistoru.
Pozor! Z našeho pokusu s vestavěnou LED by nás mohlo napadnout, že běžné (digitální) interní pull-up/pull-down rezistory se v deep sleep přece vypnou. Je tedy použití interních rezistorů dobrý nápad?
Pokud Vám toto nyní napadlo – skvělé, začínáte myslet v souvislostech!
Je tu však důležitý dodatek!
Je pravda, že když ESP32 přejde do deep sleep, většina digitální části čipu se vypne, a to včetně standardního GPIO driveru – tím pádem: pinMode( nemá během spánku žádný efekt.
ALE! Jak bylo řečeno, EXT0 používá tzv. RTC GPIO a tyto piny jsou napojené na „low-power“ část čipu! Tyto piny mají vlastní konfiguraci, která je naštěstí oddělena od klasického pinMode().
A právě tady platí: Pokud nic neuděláme, pin může skutečně „plavat“ a to vede k náhodnému probouzení nebo naopak nefunkčnímu buzení. Pokud ale chceme mít spolehlivý stav během deep sleep, musíme použít RTC pull-up / pull-down pomocí příkazu:
rtc_gpio_pullup_en(GPIO_NUM_X);
rtc_gpio_pulldown_dis(GPIO_NUM_X);
nebo opačně:
rtc_gpio_pulldown_en(GPIO_NUM_X);
rtc_gpio_pullup_dis(GPIO_NUM_X);
Tím nastavíme pull-rezistory v RTC obvodu, a ty fungují i během deep sleep.
Pojďme předchozí naši úvahu raději shrnout:
Na rozdíl od běžného běhu programu nám u deep sleep nestačí spoléhat na INPUT_. Interní pull-up rezistory, na které jsme zvyklí z Arduino světa, se totiž během hlubokého spánku vypínají. Pokud tedy používáme probuzení přes GPIO (EXT0), je nutné buď použít externí rezistor, nebo sáhnout po RTC pull-up/pull-down, který zůstává aktivní i ve spánku.
Ukázka probuzení jedním pinem
Jako ilustrativní ukázku si ukážeme základní použití EXT0 na jednoduchém příkladu. Myšlenka je přímočará: ESP32 se uspí a čeká, dokud se na zvoleném pinu neobjeví požadovaná logická úroveň – v našem případě LOW. Typicky to znamená třeba stisk vestavěného tlačítka (GPIO 0), které pin „stáhne“ na zem. Jakmile k tomu dojde, modul se probudí a program začne
znovu od začátku.
Scénář následujícího programu je tedy následující: Budicí pin (GPIO 0), na kterém je vestavěné tlačítko vývojového kitu modulu ESP32, bude vyvolávat vzbuzení z hlubokého spánku. Na začátku po krátkém zpoždění (abychom stihli vypsat sériový výstup) ESP32 přejde do deep sleep a čeká na změnu stavu sledovaného pinu.
esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); // LOW
void setup() {
Serial.begin(115200);
pinMode(2, OUTPUT);
Serial.println("Za 10 s spim...");
delay(10000);
esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0);
Serial.println("Usinam...");
delay(100);
esp_deep_sleep_start();
}
- POZNÁMKA ke kódu:
- I když by se mohlo zdát lákavé místo hodnot
0a1ve funkciesp_použít standardní Arduino konstantysleep_ enable_ ext0_ wakeup( GPIO_ NUM_0, 0) LOWaHIGH, není to úplně ideální. Funkceesp_pochází z nižší vrstvy ESP-IDF a pracuje přímo s číselnými hodnotami. Použitísleep_ enable_ ext0_ wakeup() LOWsice funguje (protože má hodnotu 0), ale korektnější je buď použít přímo0/1, nebo si definovat vlastní pojmenované konstanty, např.:#define WAKEUP_LOW 0
#define WAKEUP_HIGH 1 - a pak v kódu použít:
esp_sleep_ enable_ ext0_ wakeup( GPIO_ NUM_0, WAKEUP_ LOW);
Předchozí ukázkový kód ukazuje, jak ESP32 probudit, ale jeho odezva je v tu chvíli (kromě výpisu na sériový port) poměrně nulová. Pozorný čtenář jistě dokáže zkombinovat jeden z předchozích programů, kde jsme již uměli identifikovat důvod probuzení a výše uvedený program pro probuzení jedním pinem (EXT0) a vytvořit následující kód:
#include <Arduino.h>
#include "esp_sleep.h"
#define LED_PIN 2
void setup() {
esp_sleep_wakeup_cause_t duvod = esp_sleep_get_wakeup_cause();
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
if (duvod == ESP_SLEEP_WAKEUP_EXT0) {
Serial.println("Probuzeni z deep sleep (EXT0 - GPIO)");
Serial.println("Tlacitko stisknuto, budu blikat.");
} else {
Serial.println("Bezny start (reset nebo zapnuti)");
Serial.println("Za 10 s spim...");
delay(10000);
// nastaveni EXT0 - probuzeni pri LOW na GPIO 0
esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0);
Serial.println("Usinam...");
delay(100);
esp_deep_sleep_start();
}
}
void loop() {
// bude se vykonavat jen po probuzeni pres EXT0
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
}
Průběh programu bude tedy takový, že modul ESP32 po restartu oznámí, že usne a přejde do hlubokého spánku, ze kterého jej probudí stisk vestavěného tlačítka. Po stisku se probudí (restaruje), vyhodnotí, že byl probuzen a začne blikat.
Oproti situaci předchozího kódu, kde jsme se probouzeli pomocí časovače, jsme zde museli změnit následující:
- místo časovače začít používat EXT0 (pin
GPIO 0, stavLOW) - rozlišit, zda šlo o probuzení přes EXT0 (pozor na jinou konstantu při testování proměnné
duvod)
- INFORMACE:
GPIO 0je u některých desek (např. ESP32 DevKit) zároveň bootovací pin, takže při stisku během resetu může ovlivnit nahrání programu. Proto se v reálných projektech často doporučuje zvolit jiný RTC pin (třebaGPIO 33neboGPIO 32).
EXT1: probuzení více piny
EXT0 jsme si ukázali jako jednoduché řešení pro jeden vstup. Občas ale můžeme narazit na situaci, kdy potřebujeme hlídat více signálů najednou – například více tlačítek, několik kontaktů nebo kombinaci senzorů. A právě k tomu slouží EXT1.
Na rozdíl od EXT0:
- můžeme použít více pinů současně
- můžeme definovat jednoduchou logickou podmínku
- nemůžeme reagovat na konkrétní pin (jen na kombinaci)
Základní volání vypadá takto:
esp_sleep_enable_ext1_wakeup_io(maska_pinu, podminka);
- UPOZORNĚNÍ:
- V některých (starších) tutoriálech můžeme narazit na informaci, že se tato funkce jmenuje
esp_.sleep_ enable_ ext1_ wakeup() - Pokud tuto funkci použijeme, program s ní (zatím) funguje také!
- Tak jak to je?
- Oficiální dokumentace uvádí, že
esp_bude od ESP-IDF v6.0 zastaralá. Současné jádro Arduino ESP32 core v3 zatím využívá ESP-IDF v5.x, ale přesto se už nyní doporučuje přejít na funkcisleep_ enable_ ext1_ wakeup() esp_.sleep_ enable_ ext1_ wakeup_ io() - Není to jen pouhá změna názvu funkce, ale obě funkce se trochu liší.
- Například starší funkce nastavuje celou konfiguraci EXT1 najednou, takže každé její volání přepíše předchozí nastavení. Novější funkce umožňuje postupně přidávat další GPIO piny do již existující konfigurace. Případně je pomocí funkce
esp_odebírat.sleep_ disable_ ext1_ wakeup_ io( maska_ pinu)
Ať již použijeme jakoukoliv funkci, obě mají stejné argumenty a pojďme si je rozebrat podrobněji:
Výběr pinů (bitová maska)
Tady možná přichází první „nezvyklá“ věc. Konkrétně zápis: (1ULL << GPIO_
Znamená „aktivuj sledování GPIO 0 a GPIO 4“
Jak to chápat?
1ULL << GPIO_→ nastav bit proNUM_0 GPIO 01ULL << GPIO_→ nastav bit proNUM_4 GPIO 4|(OR) → spoj obě hodnoty dohromady (tedy jedna nebo druhá ve smyslu sjednocení)
Podmínka probuzení
Druhá část určuje podmínku, kdy se má ESP32 probudit:
ESP_– všechny piny musí býtEXT1_ WAKEUP_ ALL_ LOW LOWESP_– stačí, aby jeden pin bylEXT1_ WAKEUP_ ANY_ HIGH HIGH
- INFORMACE:
- Novější čipy ESP32-S2, ESP32-S3, ESP32-C6 a ESP32-H2 používají odlišnou sadu podmínek.
- A je třeba upozornit, že nejde o nějaké „rozšíření na čtyři režimy“, ale o naprostou změnu logiky těchto dvou podmínek probouzení mezi jednotlivými generacemi čipů.
ESP_– probuzení, když je některý z vybraných GPIO pinů na úrovniEXT1_ WAKEUP_ ANY_ LOW LOWESP_– probuzení, když je některý z vybraných GPIO pinů na úrovniEXT1_ WAKEUP_ ANY_ HIGH HIGH
Zní to složitě?
Ukážeme si to tedy „v praxi“ na běžném modulu ESP32 (např. ESP32 DevKit). Následující program už bude dělat něco, co už tak trochu známe. Po startu modul ESP32 usne a probudí se, při splnění nějaké podmínky. Zde pokud GPIO 0 i GPIO 4 budou LOW současně (obě hodnoty musí být LOW → jinak spíme dál). Po probuzení začne blikat vestavěná LED.
V ukázce budeme pracovat s externím tlačítkem připojeným na GPIO 4 (viz schéma zapojení níže). Tlačítko je zapojené tak, že při stisku stáhne tento pin na úroveň GND (LOW). Tím může splnit podmínku pro probuzení.
Zde je ale potřeba opět zdůraznit jeden důležitý detail – běžný INPUT_ nám v deep sleep nepomůže. Proto je v zapojení použit externí pull-up rezistor (typicky 10 kΩ mezi GPIO 4 a 3,3 V), který drží pin v logické úrovni HIGH. Stiskem tlačítka pak pin spolehlivě přepneme na LOW. Upřímně, to je v praxi nejjednodušší a nejspolehlivější řešení, které funguje nezávisle na režimu spánku.
Alternativní možností je využít interní RTC pull-up rezistor pomocí funkcí rtc_ a rtc_. Tyto rezistory zůstávají aktivní i během deep sleep, takže mohou nahradit externí součástky. V praxi ale platí, že pro spolehlivé zapojení – zejména při delších vodičích nebo v rušeném prostředí – je jistější použít klasický externí pull-up rezistor.
Kód programu:
#include <Arduino.h>
#include "esp_sleep.h"
#define LED_PIN 2
void setup() {
esp_sleep_wakeup_cause_t duvod = esp_sleep_get_wakeup_cause();
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
if (duvod == ESP_SLEEP_WAKEUP_EXT1) {
Serial.println("Probuzeni z deep sleep (EXT1)");
Serial.println("Podminka splnena, budu blikat.");
} else {
Serial.println("Bezny start");
Serial.println("Za 10 s uspim ESP32...");
delay(10000);
// EXT1: GPIO 0 a GPIO 4, obe musi byt LOW
esp_sleep_enable_ext1_wakeup_io(
(1ULL << GPIO_NUM_0) | (1ULL << GPIO_NUM_4),
ESP_EXT1_WAKEUP_ALL_LOW
);
Serial.println("Usinam...");
delay(100);
esp_deep_sleep_start();
}
}
void loop() {
digitalWrite(LED_PIN, HIGH);
delay(500);
digitalWrite(LED_PIN, LOW);
delay(500);
}
Jak zjistit, který pin nás probudil (EXT1)
U EXT1 sice na první pohled nevíme, který pin probuzení způsobil, ale ESP32 nám ve skutečnosti poskytuje všechny potřebné informace.
Pomocí funkce esp_ získáme bitovou masku pinů, které byly v okamžiku probuzení aktivní. Stačí ji rozkódovat a máme jasno. Díky tomu můžeme i při použití více vstupů reagovat přesně na konkrétní událost – což je v praxi docela silný nástroj.
Jak bylo naznačeno, ESP32 po probuzení umí vrátit bitovou masku pinů, které splnily podmínku probuzení:
uint64_t stav = esp_sleep_get_ext1_wakeup_status();
Výsledkem je číslo, ve kterém každý bit odpovídá jednomu GPIO pinu (pokud je bit = 1 → pin se podílel na probuzení).
Například:
uint64_t stav = esp_sleep_get_ext1_wakeup_status();
if (stav & (1ULL << GPIO_NUM_0)) {
Serial.println("Probudil GPIO 0");
}
if (stav & (1ULL << GPIO_NUM_4)) {
Serial.println("Probudil GPIO 4");
}
- POZNÁMKA:
- Tohle řešení funguje skvěle hlavně pro:
ESP_, kdy zjistíme, který pin byl aktivní.EXT1_ WAKEUP_ ANY_ HIGH - V případě
ESP_to moc nedává smysl, protože byly stisknuty přece všechny piny (tak jsme to nastavili).EXT1_ WAKEUP_ ALL_ LOW
Následující obrázek ukazuje identifikaci pinu při nastavení ESP_ – na našem kitu je GPIO 0 držen „nahoře“ externím rezistorem, takže se probuzení vykoná okamžitě. To však není důležité, zde to ukazujeme proto, abychom viděli, jak celý proces určení aktivního pinu probíhá.
ESP_EXT1_WAKEUP_ANY_HIGH .Probuzení dotykem
Vedle klasických digitálních vstupů nabízí ESP32 ještě jednu zajímavou možnost, která se u hobby projektů často přehlíží – kapacitní dotykové senzory. Některé piny totiž dokáží detekovat pouhý dotyk prstu, bez potřeby tlačítka nebo mechanického kontaktu.
To otevírá zajímavé možnosti: místo klasického tlačítka můžeme použít jen kousek vodiče, měděnou plošku na desce nebo třeba kovový předmět. A co je důležité – i tento mechanismus lze využít pro probuzení ze spánku.
Poznámka k Arduino ESP32 Core 3.x
Starší návody pro konfiguraci probuzení pomocí dotyku používají funkci:
touchAttachInterrupt(pin, NULL, threshold);
kde pin určuje dotykový pin, hodnota NULL zakazuje callback funkci a threshold je rozhodovací hodnota sepnutí.
Tato funkce ale byla určena pro starší verze Arduino ESP32 Core (2.x). V současných verzích Arduino ESP32 Core 3.x již není podporovaná a její použití způsobí chybu – nikoliv však při překladu, ale při běhu programu, který pak funguje správně. Pro probuzení z deep sleep proto nově budeme používat novější funkci:
touchSleepWakeUpEnable(pin, threshold);
která má jen dva parametry – pin určující dotykový pin (pozor na konstantu označení dotykových pinů) a rozhodovací hodnotu threshold.
Nastavení probouzení dotykem
V Arduino prostředí nejdříve nastavíme dotykový pin a prahovou hodnotu pomocí funkce:
touchSleepWakeUpEnable(pin, threshold);
pin→ dotykový pin (např.T4, což odpovídáGPIO 13)threshold→ citlivost detekce; při poklesu naměřené hodnoty pod tento práh dojde k probuzení. Hodnoty načtené dotykovým pinem se při dotyku snižují. Prahová hodnotathresholdříká, že se ESP32 probudí, jestliže je hodnota načtená na dotykovém pinu nižší.
Na rozdíl od starší funkce touch zde nepracujeme s callback funkcí ani přerušením. Dotykový senzor je pouze nakonfigurován jako zdroj probuzení z deep sleep.
- INFORMACE:
- Pokud budeme používat model ESP32-S2 nebo ESP32-S3, je třeba změnit prahovou hodnotu, protože dotykový pin u těchto typů funguje trochu jinak. Například často používaná prahová hodnota
40odpovídá u těchto modelů hodnotě asi5000. - Aby program pro dotykové probouzení byl trochu univerzální, používá se níže uvedený trik, který nastavuje rozhodovací úroveň podle použitého čipu:
#if CONFIG_IDF_TARGET_ESP32 // výchozí nastavení pro běžné čipy
#define THRESHOLD 40
#else // nastavení pro ESP32-S2 a ESP32-S3
#define THRESHOLD 5000
#endif
touchSleepWakeUpEnable(T4, THRESHOLD);
Máme-li vyřešené nastavení pinu a jeho citlivosti, povolíme probouzení dotykovým senzorem:
esp_sleep_enable_touchpad_wakeup();
a následně uspíme mikrokontrolér:
esp_deep_sleep_start();
V okamžiku, kdy ESP32 přejde do režimu deep sleep, je hlavní procesor vypnutý. Neběží žádný program, nevykonávají se žádná přerušení ani callback funkce. Nad celým obvodem jen bdí část RTC se svým dotykovým obvodem.
Dotykový obvod pouze:
- sleduje změnu kapacity na vybraném pinu,
- porovnává naměřenou hodnotu s nastaveným prahem,
- při splnění podmínky probudí celý čip.
Jakmile dojde k probuzení, ESP32 se chová stejně jako po resetu a program začíná znovu od funkce setup().
Ukázkový program
#include <Arduino.h>
#include "esp_sleep.h"
void setup() {
Serial.begin(115200);
delay(1000); // aby se stihl otevrit Serial Monitor
esp_sleep_wakeup_cause_t duvod = esp_sleep_get_wakeup_cause();
if (duvod == ESP_SLEEP_WAKEUP_TOUCHPAD) {
Serial.println("Probudil jsem se dotykem!");
} else {
Serial.println("Bezny start (reset nebo zapnuti)");
touchSleepWakeUpEnable(T4, 40); // konst. T4 odpovídá pinu GPIO 13
Serial.println("Dotkni se pinu...");
delay(5000);
esp_sleep_enable_touchpad_wakeup();
Serial.println("Usinam...");
delay(100);
esp_deep_sleep_start();
}
}
void loop() {}
Ve vysvětlení kódu se zase zaměříme pouze na věci týkající se našeho současného tématu, tedy se zaměříme na: Nastavení dotykového pinu a Přechod do spánku.
Nastavení dotykového pinu
touchSleepWakeUpEnable(T4, 40);
- aktivujeme dotykový vstup T4,
- nastavíme prahovou hodnotu 40,
- při poklesu naměřené hodnoty pod tento práh bude možné probudit ESP32.
- Pozn.:
- Jak vidíme, pro nastavení pinů se zde nezadávají čísla pinů, ale čísla dotykových kanálů. Následující tabulka ukazuje, čísla dotykových kanálů, které odpovídají GPIO pinům – platí pro běžné ESP32 (např. ESP32-WROOM, DevKit V1)
| „Touch“ kanál | starší zápis | GPIO pin |
|---|---|---|
| T0 | TOUCH_PAD_NUM0 | GPIO 4 |
| T1 | TOUCH_PAD_NUM1 | GPIO 0 |
| T2 | TOUCH_PAD_NUM2 | GPIO 2 |
| T3 | TOUCH_PAD_NUM3 | GPIO 15 |
| T4 | TOUCH_PAD_NUM4 | GPIO 13 |
| T5 | TOUCH_PAD_NUM5 | GPIO 12 |
| T6 | TOUCH_PAD_NUM6 | GPIO 14 |
| T7 | TOUCH_PAD_NUM7 | GPIO 27 |
| T8 | TOUCH_PAD_NUM8 | GPIO 33 |
| T9 | TOUCH_PAD_NUM9 | GPIO 32 |
Přechod do spánku provedeme následujícím způsobem:
esp_sleep_enable_touchpad_wakeup();
esp_deep_sleep_start();
- povolíme probouzení pomocí dotykového senzoru,
- uspíme ESP32 do režimu deep sleep.
Jakmile se prstem dotkneme pinu GPIO 13, změní se jeho kapacita, dotykový obvod vyhodnotí změnu a ESP32 se probudí. Protože jde o deep sleep, program se po probuzení znovu spustí od funkce setup().
Praktické poznámky
- Citlivost je potřeba doladit. Hodnota
thresholdnení univerzální a závisí na konkrétním zapojení, délce vodiče, vlhkosti prostředí i úrovni rušení. V praxi je vhodné ji odladit experimentálně. - Ne všechny piny podporují dotykové senzory. Dotyková funkce je dostupná pouze na vybraných pinech označených jako
T0ažT9(viz tabulka č. 1 výše). - Doporučuje se nejprve změřit skutečné hodnoty senzoru pomocí funkce
touchRead(). Teprve podle naměřených hodnot je vhodné nastavit odpovídající práh probuzení. - Dotykové senzory jsou citlivější na okolní vlivy než klasická tlačítka. Mohou reagovat nejen na přímý dotyk, ale i na přiblížení ruky nebo změny prostředí. Někdy je to výhoda, jindy naopak zdroj falešných probuzení. 😊
Kdopak to budí…
Ještě by asi bylo fajn si ukázat, jak při použití více dotykových pinů pro probuzení modulu ESP32 identifikovat „pachatele“ tohoto probuzení. K tomu nám poslouží funkce esp_, která vrací číslo dotykového kanálu.
Její použití nám ilustruje následující fragment programu:
touchPin = (touch_pad_t) esp_sleep_get_touchpad_wakeup_status();
switch (touchPin) {
case 0: Serial.println("Zaznamenan dotyk na GPIO 4"); break;
case 1: Serial.println("Zaznamenan dotyk na GPIO 0"); break;
case 2: Serial.println("Zaznamenan dotyk na GPIO 2"); break;
case 3: Serial.println("Zaznamenan dotyk na GPIO 15"); break;
case 4: Serial.println("Zaznamenan dotyk na GPIO 13"); break;
case 5: Serial.println("Zaznamenan dotyk na GPIO 12"); break;
case 6: Serial.println("Zaznamenan dotyk na GPIO 14"); break;
case 7: Serial.println("Zaznamenan dotyk na GPIO 27"); break;
case 8: Serial.println("Zaznamenan dotyk na GPIO 33"); break;
case 9: Serial.println("Zaznamenan dotyk na GPIO 32"); break;
default: Serial.println("Probuzeni nebylo dotykem"); break;
}
RTC paměť (uchování dat mezi probuzeními)
Na úplný závěr se ještě podíváme na jednu velmi praktickou vlastnost ESP32, která krásně doplňuje práci s deep sleepem. Jak už víme, při probuzení se čip chová jako po resetu – všechno se spustí od začátku a běžná proměnná v RAM je pryč.
To ale neznamená, že bychom byli úplně bez možnosti něco si „zapamatovat“.
ESP32 má totiž k dispozici tzv. RTC paměť, která zůstává zachovaná i během deep sleep. A právě tu můžeme využít pro ukládání jednoduchých dat mezi jednotlivými probuzeními.
Jak na to?
Překvapivě jednoduše! Stačí při deklaraci proměnné použít speciální atribut:
RTC_DATA_ATTR int wakeCount = 0;
Tím říkáme, že proměnná nebude uložena v běžné RAM, ale v RTC paměti, která přežije deep sleep.
V následujícím programu necháme modul ESP32 usínat do hlubokého spánku a pravidelně probouzet časovačem. Zároveň budeme počítat a na sériový port vypisovat počet probuzení.
Kód programu:
#include <Arduino.h>
#include "esp_sleep.h"
RTC_DATA_ATTR int wakeCount = 0;
void setup() {
Serial.begin(115200);
wakeCount++;
Serial.print("Pocet probuzeni: ");
Serial.println(wakeCount);
esp_sleep_enable_timer_wakeup(10 * 1000000ULL);
esp_deep_sleep_start();
}
void loop() {}
Při prvním spuštění proměnná wakeCount má hodnotu 0. Následně se zvýší na 1 a ESP32 se uspí. Po probuzení program začne znovu od setup(), ale proměnná wakeCount nezmizela, tak se zvýší na 2. A tak dál… při každém dalším probuzení.
Možná někoho napadlo, že by se měla proměnná wakeCount po probuzení opět vynulovat. Program přece běží od samého začátku!
„Tak přece při každém startu bude mít hodnotu 0, ne?“
Jenže u RTC paměti to (naštěstí!) funguje trochu jinak!
Při skutečném startu (nahrání programu, reset, zapnutí) je paměť inicializována a proměnná se nastaví podle definice. Všechno se chová přesně tak, jak čekáme.
Při probuzení z deep sleep (a tady je ten klíčový rozdíl) RTC paměť se NEreinicializuje, obsah paměti zůstává zachovaný (tedy i proměnná wakeCount)
tak se žádné „nové“ vytvoření proměnné nekoná a neproběhne ani to přiřazení = 0.
Funkce Wake Stub
Pojďme se podívat na drobnou vychytávku týkající se RTC paměti. Stéphane Calderoni ve svém článku Experimenting with ESP32 sleep modes totiž zmiňuje, že modul ESP32 umožňuje vytvořit svou vlastní funkci, která bude uložená v RTC paměti a která se automaticky spustí při probuzení modulu ESP32 z hlubokého spánku.
Dokumentace společnosti Espressif konkrétně říká toto (citujeme):
ESP32 podporuje při probouzení z režimu hlubokého spánku (Deep Sleep) spuštění funkce „Wake Stub“. Tato funkce se spustí okamžitě po probuzení čipu – ještě před provedením běžné inicializace, spuštěním zavaděče (bootloaderu) nebo kódu ESP-IDF. Po dokončení funkce deep sleep wake stub se může SoC (systém na čipu) buď znovu přepnout do režimu spánku, nebo pokračovat v běžném spuštění ESP-IDF. Kód funkce deep sleep wake stub je uložen v paměti „RTC Fast Memory“ a veškerá data, která používá, musí být rovněž uložena v paměti RTC, neboť oblasti paměti RTC si zachovávají svůj obsah i během režimu hlubokého spánku.
Proč vůbec Wake Stub existuje?
Například když chceme:
- velmi rychle zjistit důvod probuzení, rozhodnout, zda se má ESP32 hned zase uspat, nebo pokračovat v probouzení,
- spočítat počet probuzení, (třeba jako nyní potřebujeme my)
- změřit čas, přečíst RTC GPIO… zkrátka ušetřit energii tím, že vůbec nenastartujeme celý systém.
void RTC_IRAM_ATTR wake_stub() {
esp_default_wake_deep_sleep();
if (rtc_gpio_get_level(GPIO_NUM_33)) {
esp_deep_sleep_start(); // okamžitě zpět do spánku
}
}
Tento fragment kódu otestuje po probuzení stav konkrétního RTC GPIO pinu, pokud je HIGH, dojde opět k okamžitému spánku, aniž by musel nabíhat celý čip, spouštět se bootloader a program tento test musel vyhodnotit až někde v sekci setup().
Speciální atribut RTC_ opět překladači jasně říká, jak má s touto funkcí zacházet – tedy uložit do rychlé části RTC RAM.
- POZNÁMKA:
- Upozorňujeme, že implementace funkce
Wake Stubnení pro využití funkce hlubokého spánku povinná. Je nezbytná pouze v případě, pokud chcete zavést určité chování ihned po probuzení SoC.
Co existence Wake Stub pro nás znamená?
Po probuzení z hlubokého spánku můžeme spustit kód, který zůstane zachován v RTC paměti, která nebyla vypnuta. Možnosti tohoto kódu jsou docela omezené, protože zbylé části čipu ESP32 se sotva pobírají, iniciují (nebo dokonce stále ještě dřímou). Ale pro naši inkrementaci proměnné by to mohlo stačit!
Omezení Wake Stub
Uvnitř Wake Stub nemůžeme používat téměř nic z běžného Arduino prostředí. Tak jsme asi na úvod moc slyšet nechtěli.
Serial.println("Ahoj");
digitalWrite(...);
delay(...);
WiFi.begin(...);
apod.
Protože ještě neběží „operační systém“ ani knihovny Arduino, výše uvedené příkazy nemají smysl. Používat můžeme jen funkce dostupné z RTC/ROM nebo uložené v IRAM.
Nastavení Wake Stub funkce:
Pro vytvoření Wake Stub funkce máme dva možné způsoby její registrace.
- Předefinování funkce
esp_wake_ deep_ sleep()
Funkce esp_ je speciální funkce, kterou bootovací kód ESP32 hledá automaticky. Pokud ji ve svém programu vytvoříme, použije ji místo výchozí implementace. Pro zavedení této funkce ji stačí vytvořit a není potřeba volat žádnou registrační funkci.
Jak uvádí dokumentace společnosti Espressif, je možné vytvořit tuto funkci následujícím způsobem:
void RTC_IRAM_ATTR esp_wake_deep_sleep() {
esp_default_wake_deep_sleep();
// Zde přidejte další funkcionalitu
}
Vložení „(před)probouzecího“ kódu je realizováno přímo ve funkci esp_. Tato funkce se provede vždy, když se SoC probudí z hlubokého spánku. Ve funkci (zde na začátku) je potřeba zavolat standardní inicializaci po probuzení esp_.
esp_default_wake_deep_sleep() provádí například:- obnovu některých registrů,
- přípravu CPU,
- nastavení prostředí pro další běh.
Pokud bychom tuto funkci nepoužili, museli bychom všechno potřebné udělat ručně, což je asi to, co nechceme 😉. Ve většině případů se tedy volá jako první. Pak můžeme přidat nějakou naši užitečnou práci.
Nevýhoda tohoto použití je to, že může existovat pouze jedna taková funkce.
Následující program ukazuje, jak by se dalo počítání probuzení řešit pomocí předefinování funkce esp_.
#include <Arduino.h>
#include "esp_sleep.h"
RTC_DATA_ATTR int wakeCount = 0;
void RTC_IRAM_ATTR esp_wake_deep_sleep() {
esp_default_wake_deep_sleep();
wakeCount++;
}
void setup() {
Serial.begin(115200);
Serial.printf("Probuzení číslo %d\n", wakeCount);
esp_sleep_enable_timer_wakeup(5000000);
esp_deep_sleep_start();
}
- Vlastní funkce registrovaná pomocí
esp_set_ deep_ sleep_ wake_ stub()
Druhá možnost jak nadefinovat „(před)probouzecí“ funkci je vytvoření si vlastní funkce:
void RTC_IRAM_ATTR wake_stub() {
esp_default_wake_deep_sleep();
// Zde přidejte další funkcionalitu
}
kterou pak zaregistrujeme jako funkci Wake Stub:
esp_set_deep_sleep_wake_stub(&wake_stub);
Funkci Wake Stub je nutné zaregistrovat voláním esp_, které očekává ukazatel na naši funkci.
- POZNÁMKA:
- V jazyce C/C++ lze ukazatel předat dvěma rovnocennými způsoby:
esp_set_ deep_ sleep_ wake_ stub( wake_stub); - nebo
esp_set_ deep_ sleep_ wake_stub( &wake_stub); - Obě varianty jsou správné, protože při předání funkce jako argumentu kompilátor automaticky převede název funkce na ukazatel na její začátek. Operátor
&tedy není nutný, pouze explicitně vyjadřuje, že se předává adresa funkce. Oba zápisy jsou tedy z hlediska výsledného programu zcela ekvivalentní a kompilátor z nich vytvoří stejný strojový kód.- Proto se můžeme v různých článcích setkat s oběma možnými variantami, které se odvíjejí čistě podle gusta daného autora.
Wake Stub má následující výhody:Wake Stubmůžeme za běhu programu změnit na jinou funkci.
esp_set_deep_sleep_wake_stub(wake_stub1);
// ...
esp_set_deep_sleep_wake_stub(wake_stub2);
// Při dalším probuzení se vykoná už wake_stub2
- Můžeme ji také vrátit na výchozí implementaci.
- Je to obecně flexibilnější řešení.
Kdy se co používá?
esp_– používá se tehdy, když chceme natrvalo nahradit výchozíwake_ deep_ sleep() Wake Stubvlastní implementací. Je to jednodušší a nevyžaduje žádnou registraci.esp_– používá se, když chceme mít možnostset_ deep_ sleep_ wake_stub() Wake Stubvybírat nebo měnit za běhu programu. Je to doporučený způsob, pokud potřebujeme větší flexibilitu.
Pro nové projekty se většinou doporučuje používat esp_, protože nezasahuje do slabě definované systémové funkce esp_ a umožňuje Wake Stub kdykoli změnit. Předefinování esp_ se dnes používá spíše v jednodušších nebo starších příkladech, kde stačí jedna pevně daná implementace Wake Stub.
Následující program ukazuje, jak by se dalo počítání probuzení řešit pomocí vlastní funkce Wake Stub.
#include <Arduino.h>
#include "esp_sleep.h"
RTC_DATA_ATTR int wakeCount = 0;
void RTC_IRAM_ATTR wake_stub() {
esp_default_wake_deep_sleep();
wakeCount++;
}
void setup() {
Serial.begin(115200);
esp_set_deep_sleep_wake_stub(&wake_stub);
Serial.printf("Probuzení číslo %d\n", wakeCount);
esp_sleep_enable_timer_wakeup(5000000);
esp_deep_sleep_start();
}
Poznámka na závěr (k probouzení a light sleep)
Možná už při čtení této závěrečné části vyvstala otázka: Platí tohle všechno i pro light sleep?
U light sleep sice můžeme použít stejné zdroje probuzení (časovač, GPIO, dotyk), ale jejich význam je odlišný. ESP32 se totiž po probuzení nechová jako po resetu, ale jednoduše pokračuje dál v běhu programu.
To znamená, že:
- nemá smysl zjišťovat proč jsme se probudili (rovnou víme, co jsme nastavili),
- neřešíme rozdělení na „první start“ a „probuzení“,
- a už vůbec neřešíme ztrátu stavu – ten zůstává zachovaný.
Naopak u deep sleep je práce s důvodem probuzení klíčová, protože program vždy začíná znovu a bez této informace bychom vůbec nevěděli, co se stalo.
Lze tedy říci, že mechanismy probuzení sice fungují jak pro light sleep, tak pro deep sleep, ale jejich význam je odlišný. U light sleep jde jen o návrat z dočasného uspání, zatímco u deep sleep určují, jaký běh programu se po restartu vlastně spustí.
Závěr
Režimy spánku na ESP32 na první pohled možná působí jako „doplňková funkce“, kterou se vyplatí řešit až ve chvíli, kdy začneme bojovat s výdrží baterie. Ve skutečnosti ale patří k základním stavebním kamenům práce s mikrokontroléry. Jakmile si osvojíme principy uspávání a probouzení, začneme na návrh programu nahlížet trochu jinak – ne jako na nepřetržitě běžící kód, ale jako na sled krátkých, účelově spuštěných akcí.
Právě tenhle způsob uvažování nás posune dál. Najednou dává smysl, proč některá zařízení vydrží fungovat týdny nebo měsíce bez zásahu, zatímco jiná vybijí baterii během pár hodin. Nejde jen o hardware, ale především o to, jak efektivně využijeme čas, kdy zařízení skutečně pracuje – a kdy naopak odpočívá.
ESP32 nám v tomhle ohledu nabízí velmi silný arzenál – od jednoduchého light sleep až po hluboký spánek s možností přesně definovaného probuzení. Jakmile tyto nástroje dostaneme „do ruky“, otevírá se celá řada nových možností – od jednoduchých senzorů až po komplexnější IoT projekty.
A to je možná to nejdůležitější:
Schopnost dobře pracovat se spánkem není jen optimalizace – je to základní dovednost, která odděluje funkční řešení od opravdu dobrého návrhu.
A když nad tím tak přemýšlíme, platí to možná i mimo svět mikrokontrolérů.
Takže dobrou noc…
-->