ESP
DIYer
32
LAB

Moderní mikrokontrolér v rukou „bastlíře“

Režimy spánku modulu ESP32 (Arduino IDE)

Aktualizováno: 2. 7. 2026

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.
    • ext0 reaguje na konkrétní pin a úroveň (HIGH/LOW)
    • ext1 umožňuje pracovat s více piny najednou (např. „probuď se, když alespoň jeden z nich změní stav“)
  • 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.

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_deep_sleep_start() 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_light_sleep_start() 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_light_sleep_start() 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.

výstup průběhu programu při light sleep
Obrázek č. 1 – výstup průběhu programu při light sleep.

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.

svíticí vestavěná LED při light sleep
Obrázek č. 2 – svíticí vestavěná LED v průběhu light sleep.

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_deep_sleep_start(); 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“

zhaslá vestavěná LED při deep sleep
Obrázek č. 3 – zhaslá vestavěná LED v průběhu deep sleep.

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_sleep_get_wakeup_cause():

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í programu
  • ESP_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:

  1. První spuštění
    • nastavíme vše potřebné
    • můžeme provést delší inicializaci
    • případně informujeme uživatele
  2. 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_deep_sleep_start() tím pádem se nikdy nedostaneme do 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 do loop(), kde je „blikací“ kód LED (HIGH → 500 ms → LOW → 500 ms).
zjištění důvodu probuzení
Obrázek č. 4 – zjištění důvodu probuzení – výpis v Serial Monitoru.

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: 1000000ULL a ne jen 1000000?
Důvod je jednoduchý – ULL znamená 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:

  1. nastavíme zdroj probuzení
  2. 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.

Připojení čidla DHT11 k ESP32
Obrázek č. 5 – připojení čidla DHT11 ke kitu s modulem ESP32.

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.

Připojené čidlo DHT11 k ESP32
Obrázek č. 6 – připojené čidlo DHT11 ke kitu modulu ESP32.

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.

výstup připojeného čidla DHT11
Obrázek č. 6 – výstup připojeného č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_NUM_X je číslo pinu (např. GPIO_NUM_4)
  • uroven je:
    • 0 → probuzení při LOW
    • 1 → probuzení při HIGH

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(pin, INPUT_PULLUP) 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_PULLUP. 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 0 a 1 ve funkci esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0) použít standardní Arduino konstanty LOW a HIGH, není to úplně ideální. Funkce esp_sleep_enable_ext0_wakeup() pochází z nižší vrstvy ESP-IDF a pracuje přímo s číselnými hodnotami. Použití LOW sice funguje (protože má hodnotu 0), ale korektnější je buď použít přímo 0/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.

výstup připojeného čidla DHT11
Obrázek č. 8 – průběh programu zachycený v Serial Monitoru.

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, stav LOW)
  • rozlišit, zda šlo o probuzení přes EXT0 (pozor na jinou konstantu při testování proměnné duvod)
INFORMACE:
GPIO 0 je 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řeba GPIO 33 nebo GPIO 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_sleep_enable_ext1_wakeup() 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 funkci 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_sleep_disable_ext1_wakeup_io(maska_pinu) odebírat.

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_NUM_0) | (1ULL << GPIO_NUM_4)

Znamená „aktivuj sledování GPIO 0 a GPIO 4

Jak to chápat?

  • 1ULL << GPIO_NUM_0 → nastav bit pro GPIO 0
  • 1ULL << GPIO_NUM_4 → nastav bit pro 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_EXT1_WAKEUP_ALL_LOW – všechny piny musí být LOW
  • ESP_EXT1_WAKEUP_ANY_HIGH – stačí, aby jeden pin byl 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_EXT1_WAKEUP_ANY_LOW – probuzení, když je některý z vybraných GPIO pinů na úrovni LOW
  • ESP_EXT1_WAKEUP_ANY_HIGH – probuzení, když je některý z vybraných GPIO pinů na úrovni 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 0GPIO 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í.

schéma připojení tlačítka č. 2
Obrázek č. 9 – schéma připojení tlačítka č. 2 a externího pull-up rezistoru.

Zde je ale potřeba opět zdůraznit jeden důležitý detail – běžný INPUT_PULLUP 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_gpio_pullup_en() a rtc_gpio_pulldown_dis(). 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_sleep_get_ext1_wakeup_status() 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_EXT1_WAKEUP_ANY_HIGH, kdy zjistíme, který pin byl aktivní.
V případě ESP_EXT1_WAKEUP_ALL_LOW to moc nedává smysl, protože byly stisknuty přece všechny piny (tak jsme to nastavili).

Následující obrázek ukazuje identifikaci pinu při nastavení ESP_EXT1_WAKEUP_ANY_HIGH – 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á.

identifikace aktivního pinu
Obrázek č. 10 – identifikace aktivního pinu při 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á hodnota threshold říká, že se ESP32 probudí, jestliže je hodnota načtená na dotykovém pinu nižší.

Na rozdíl od starší funkce touchAttachInterrupt() 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 40 odpovídá u těchto modelů hodnotě asi 5000.
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álstarší zápisGPIO pin
T0TOUCH_PAD_NUM0GPIO 4
T1TOUCH_PAD_NUM1GPIO 0
T2TOUCH_PAD_NUM2GPIO 2
T3TOUCH_PAD_NUM3GPIO 15
T4TOUCH_PAD_NUM4GPIO 13
T5TOUCH_PAD_NUM5GPIO 12
T6TOUCH_PAD_NUM6GPIO 14
T7TOUCH_PAD_NUM7GPIO 27
T8TOUCH_PAD_NUM8GPIO 33
T9TOUCH_PAD_NUM9GPIO 32
Tabulka č. 1 – přehled dotykových kanálů a GPIO pinů modulu ESP32

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 threshold není 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 T0T9 (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í. 😊
probuzení dotykem na pin
Obrázek č. 11 – probuzení dotykem na pin – odezva v Serial Monitoru.

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_sleep_get_touchpad_wakeup_status(), 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í.

počítání počtu probuzení
Obrázek č. 12 – počítání počtu probuzení proměnnou uloženou v RTC RAM.

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

Doplněno: 2. 7. 2026

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.
Například:
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_IRAM_ATTR 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 Stub není 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.

Například na tohle všechno tedy zapomeňte:
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.

  1. Předefinování funkce esp_wake_deep_sleep()

Funkce esp_wake_deep_sleep() 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_wake_deep_sleep(). 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_default_wake_deep_sleep().

Funkce 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_wake_deep_sleep().

#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();
}
  1. 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_set_deep_sleep_wake_stub(), 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.
Tento způsob zavedení funkce Wake Stub má následující výhody:
  • Wake Stub můžeme za běhu programu změnit na jinou funkci.
Například:
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_wake_deep_sleep() – používá se tehdy, když chceme natrvalo nahradit výchozí Wake Stub vlastní implementací. Je to jednodušší a nevyžaduje žádnou registraci.
  • esp_set_deep_sleep_wake_stub() – používá se, když chceme mít možnost Wake Stub vybí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_set_deep_sleep_wake_stub(), protože nezasahuje do slabě definované systémové funkce esp_wake_deep_sleep() a umožňuje Wake Stub kdykoli změnit. Předefinování esp_wake_deep_sleep() 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?

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…

-->
Pošli signál autorovi: