Režim hibernace modulu ESP32
V dnešním článku přímo navážeme na článek Režimy spánku modulu ESP32, ve kterém jsme se ale zabývali jen režimy lehkého a hlubokého spánku. O režimu hibernace jsme se sice v obecném úvodu zmínili, ale téma režimů obou spánků bylo natolik rozsáhlé, že pro režim hibernace už moc prostoru nezbylo.
Přehled spánkových režimů
Než se pustíme do režimu hibernace, ještě jednou si zopakujeme základní charakteristiky režimů spánku modulu ESP32. Jako ilustraci aktivity jednotlivých částí modulu při různých režimech spánku nám více než samotný textový popis mnohem názorněji poslouží animace, kterou jsme sestavili z obrázků uvedených v článku Experimenting with ESP32 sleep modes (autor: Stéphane Calderoni).
- 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é.
Režim hibernace
Jak již víme, jednotlivé režimy spánku se především liší rozsahem vypnutých částí mikroprocesoru. V případě nejhlubšího spánku, tzv. hibernace, se oproti hlubokému spánku navíc deaktivuje interní 8 MHz oscilátor, ULP-koprocesor, ale také paměť pro obnovení RTC. To ještě více snižuje spotřebu energie.
Vypnutí paměti RTC znamená, že během režimu hibernace nemůžeme v modulu uchovat žádná data, tedy alespoň tak, jak jsme zatím byli zvyklí. Pokud budeme potřebovat, aby nějaká data tento stav hluboké neaktivity „přežila“, budeme si muset vystačit s tím, co nám zbylo aktivní nebo najít nějaký jiný fígl.
Co nám vlastně tedy v tom stavu hibernace zbylo? Skoro vše je vypnuté kromě jednoho časovače RTC (na pomalých hodinách) a řadičů některých RTC GPIO pinů pro probuzení. Dobrá zpráva tedy je, že i v režimu hibernace lze čip probudit časovačem RTC nebo RTC GPIO piny. Pro naše následné experimenty budeme využívat výhradně probouzení časovačem, ale jak uvidíme, režim hibernace pro nás bude vlastně jen takový „speciální“ hluboký spánek, takže vše ostatní (například probuzení pomocí RTC GPIO) je s tímto režimem velmi podobné.
Ale dost bylo řečí, pojďme hibernovat!
Nastavení stavu hibernace
Jak již bylo naznačeno, režim hibernace bude vlastně takovým speciálním případem hlubokého spánku. Jediný rozdíl v tomto směru bude, že před spuštěním režimu hlubokého spánku navíc nastavíme oblasti modulu ESP32, které se vypnou. Vývojové prostředí Arduino IDE nemá přímo příkaz pro režim hibernace, takže musíme sestoupit o trochu níž a vše zařídit na úrovni ESP-IDF. Ale, jak na následující ukázce uvidíme, nebude to žádná věda – spíše jen takové formální přidání zvláštního kódu.
Abychom mohli ve svém kódu používat režim hibernace tak, že jej zavoláme určitým příkazem, vytvoříme si následující proceduru, která nám bude stav hibernace vyvolávat:
void hibernate() {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
esp_deep_sleep_start();
}
Na kódu vidíme, že skutečně jde jen o zavolání hlubokého spánku, jen předtím pomocí funkce esp_ povypínáme zmíněné části čipu ESP32. Názvy samotných konstant napovídají, kterých částí se to týká. Jak vidíme, není to nic složitého. Přestože Arduino IDE nemá přímý příkaz pro režim hibernace, nabízí dostatek možností, jak jej efektivně implementovat.
Máme-li připravenou proceduru pro vyvolání hibernace, můžeme to hned vyzkoušet.
Následující ukázka uvede modul ESP32 do stavu hibernace na 10 vteřin. Pak se modul probere. Zatím vlastně nic zajímavého, neboť se to na první pohled vůbec nebude lišit od stavu hlubokého spánku. Dokonce, kdybychom modul ESP32 připojili na klasický ampérmetr, který se nám někde povaluje na stole, asi bychom ani nenaměřili viditelný rozdíl ve spotřebě. Ale nebojte, ukážeme si, že výše uvedený stav je hibernace, jak se sluší a patří.
#include <Arduino.h>
#include "esp_sleep.h"
#define LED_PIN 2
void hibernate() {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
esp_deep_sleep_start();
}
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... (HIBERNACE)");
delay(100);
// esp_deep_sleep_start();
hibernate();
}
void loop() {}
Pokud si trochu vzpomínáme na předešlý článek o režimech spánku, tak asi vidíme, že je to tentýž program, jako jsme již používali. Modul ESP32 se spustí, vypíše hlášku na sériový monitor prostředí Arduino IDE, pak počká pět vteřin a následně usne na 10 vteřin. Stav modulu je demonstrován pomocí svitu vestavěné LED – je-li modul v aktivním stavu (čeká 5 s), LED svítí. Jakmile nastane hibernace, je řadič GPIO vypnut a LED tedy zhasne.
V kódu dokonce vidíme zakomentovaný příkaz spuštění hlubokého spánku, který je nyní nahrazen naší procedurou hibernate(). Pokud opravdu toužíme si trochu „zablbnout“ a porovnat chování (nebo tu proudovou náročnost) během obou těchto režimů, můžeme libovolně zakomentovávat jeden, či druhý příkaz. Tím můžeme vyvolávat hibernaci, nebo hluboký spánek. Zatím ale opravdu moc velký rozdíl nepoznáme.
Výše zmíněný kód zatím jen skutečně berme jen jako ukázku použití stavu hibernace.
Aktivita RTC paměti při stavu hibernace
Zatímco předešlá ukázka moc rozdílu mezi stavem hibernace a hlubokým spánkem neukázala, zkusíme se zaměřit na další aktivitu, která by nás snad již měla přesvědčit.
Opět se odvoláme na předchozí článek o režimech spánku a vzpomeneme si, že během hlubokého spánku (deep sleep) je modul sice vypnutý, ale k dispozici zůstává malá část čipu (tzv. RTC doména). Do této části, respektive do její paměti, jsme dokázali uložit proměnnou, která nám počítala počet probuzení. Zkusíme to nyní také.
Do našeho předchozího programu tedy přidáme proměnnou, která ale bude uložena v RTC paměti (pomocí speciálního atributu RTC_DATA_ATTR při deklaraci). Pomocí této proměnné budeme počítat počet probuzení. Vždy v sekci setup() ji zvětšíme o jednu. Pokud se bude její hodnota během spánku/hibernace
zachovávat, měla by se hodnota této proměnné při každém probuzení zvětšovat.
#include <Arduino.h>
#include "esp_sleep.h"
#define LED_PIN 2
RTC_DATA_ATTR int wakeCount = 0;
void hibernate() {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
esp_deep_sleep_start();
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
wakeCount++;
Serial.print("Pocet probuzeni: ");
Serial.println(wakeCount);
digitalWrite(LED_PIN, HIGH);
Serial.println("Jsem vzhuru, za 5 s usnu...");
delay(5000);
esp_sleep_enable_timer_wakeup(10 * 1000000ULL);
Serial.println("Usinam... (HIBERNACE)");
delay(100);
// esp_deep_sleep_start();
hibernate();
}
void loop() {}
Podíváme-li se na náš kód, opravdu vidíme, že zde přibylo jen pár příkazů týkajících se proměnné wakeCount, která se stará o počítání spuštění kódu – záměrně uvádíme „spuštění“, a nikoliv probuzení, jak uvidíme na obrázku č. 2, který nám ukazuje výstup programu v sériovém monitoru prostředí Arduino IDE.
Opět je dobré zkusit běh programu při hlubokém spánku a při hibernaci. Tady již rozdíl poznáme! Zatímco při hlubokém spánku vše běží, hodnota proměnné wakeCount se při každém spuštění programu zvyšuje, tak při hibernaci se děje „cosi divného“ – viz obrázek č. 2.
Jak vidíme na obrázku č. 2, při spuštění programu se vypíše „Počet probuzení: 1“. Opomeneme-li, že správně by mělo být asi vypsáno číslo 0, protože zatím k žádnému probuzení nedošlo (což teď není podstatné), jde o „rozumnou“ hodnotu. Pak ale dojde k hibernaci. Většina částí modulu ESP32 včetně RTC RAM, kde je uložena naše proměnná, je vypnuta. Tím se informace o této proměnné ztratila. Po probuzení se program opět rozběhne. Při probuzení z deep
sleep (a nyní i hibernace) se RTC paměť nereinicializuje, počítá se s tím, že obsah paměti zůstává při deep sleep zachovaný. Tak se žádné „nové“ vytvoření proměnné nekoná a neproběhne ani to přiřazení = 0. Teď ale při hibernaci byla RTC paměť vypnutá, takže nyní máme v proměnné wakeCount nějaký „maglajs“, který tam vznikl při restartu. Je vůbec otázkou, zda proměnná wakeCount vůbec ještě ukazuje na stejné místo RTC paměti. To se pochopitelně projeví vypsáním naprosto nesmyslné hodnoty. A to také vidíme v okamžicích prvního a druhého probuzení na obrázku č. 2.
Právě ty nesmyslné hodnoty proměnné wakeCount jsou důkazem toho, že byla v průběhu našeho stavu spánku RTC paměť vypnuta – tedy, že se jednalo o stav hibernace a nikoliv o stav hlubokého spánku!
Uchování hodnoty proměnné přes stav hibernace
Už víme, že dokážeme modul ESP32 uvést do stavu opravdu velmi nízké spotřeby (hibernace) a že tento stav „nepřežije“ ani hodnota proměnné uložené v oblasti RTC RAM. To je sice užitečná informace, ale pokud bychom nebyli schopni předávat alespoň některá data mezi následujícími cykly hibernace, značně by nás to při návrhu aplikací omezovalo.
Je pravda, že pro jednoduché aplikace, například různé dataloggery, které se probudí, změří požadovanou hodnotu, odešlou ji například na webové úložiště a poté opět usnou na předem definovanou dobu, nemusí být uchovávání proměnných mezi probuzeními vůbec potřeba.
Přesto se podíváme na některá z možných řešení, jak zachovat obsah proměnné i po přechodu modulu do hibernace.
1. uložení do (emulované) EEPROM
U klasických desek Arduino máme situaci poměrně jednoduchou. Mikrokontroléry řady AVR obsahují přímo na čipu paměť EEPROM, do které lze ukládat data, jež zůstanou zachována i po vypnutí napájení. Tato paměť je určena právě pro ukládání různých nastavení, čítačů nebo jiných hodnot, které potřebujeme uchovat dlouhodobě.
Modul ESP32 sice žádnou samostatnou paměť EEPROM neobsahuje, ale rozhodně to neznamená, že bychom o tuto možnost přišli. Tvůrci vývojového prostředí tento zdánlivý hendikep vyřešili velmi elegantně – vytvořili vrstvu, která chování klasické EEPROM věrně napodobuje a pro ukládání dat využívá interní paměť Flash. Z pohledu programátora je přitom práce téměř stejná, jako bychom používali skutečnou EEPROM.
- POZNÁMKA:
- Samozřejmě bychom mohli potřebná data ukládat i do souborového systému LittleFS, se kterým jsme se seznámili v jiném našem článku. To by však v našem případě představovalo zbytečně složité řešení. Vytvářet soubor, zapisovat do něj jedinou celočíselnou hodnotu a při každém probuzení jej znovu otevírat a číst by bylo poněkud neefektivní. Pro uložení několika bajtů dat je proto použití emulované EEPROM mnohem jednodušší a přirozenější volbou.
Výhody a omezení emulované EEPROM
Výhodou tohoto řešení je, že data uložená do emulované EEPROM zůstávají zachována nejen po probuzení z hibernace, ale dokonce i po úplném odpojení napájení. Pro uložení jediné proměnné typu int, která na ESP32 zabírá pouze 4 bajty, je tedy toto řešení více než dostačující.
Přístup k uloženým datům je navíc díky knihovně EEPROM.h velmi jednoduchý a od programátora nevyžaduje téměř žádnou práci navíc.
Protože je EEPROM na modulu ESP32 pouze emulována pomocí interní paměti Flash, je dobré znát i její omezení. Především je třeba počítat s tím, že zápis do Flash není ani okamžitý, ani neomezený. Zatímco čtení uložených dat je velmi rychlé, zápis trvá obvykle několik milisekund a každá buňka paměti má omezený počet přepisů. U pamětí Flash se běžně udává životnost přibližně 10 000 až 100 000 zápisových cyklů na jednu oblast paměti.
Na první pohled se může zdát, že jde o poměrně malé číslo, ale ve většině běžných aplikací nepředstavuje žádný problém. Pokud například budeme hodnotu čítače ukládat pouze při každém přechodu modulu do hibernace, znamenalo by i deset tisíc zápisů při jednom uspání za minutu životnost téměř jednoho týdne nepřetržitého provozu. V reálných aplikacích se však modul obvykle uspává v mnohem delších intervalech, takže životnost se pohybuje v řádu měsíců až let. Ale ani to není zrovna velká hitparáda!
Seznámení s knihovnou EEPROM.h
Než se pustíme do samotného řešení ukládání počtu probuzení při hibernaci, seznámíme se nejprve se základními principy práce s knihovnou EEPROM.h. Přestože je její použití poměrně jednoduché, je dobré porozumět jednotlivým krokům, které při práci s emulovanou EEPROM provádíme.
Ukážeme si proto postupně všechny důležité operace – od inicializace knihovny, přes zápis dat do paměti až po jejich opětovné načtení. Jednotlivé funkce si představíme na jednoduchém příkladu.
Jakmile budeme tyto operace bezpečně ovládat, využijeme je k vyřešení našeho původního problému – uchování počtu probuzení modulu ESP32 i po přechodu do stavu hibernace.
Připojení knihovny EEPROM.h
Stejně jako u většiny dalších periferií musíme nejprve programu zpřístupnit obslužnou knihovnu. To provedeme známým příkazem:
#include <EEPROM.h>
Tím získáme přístup ke všem funkcím potřebným pro práci s emulovanou EEPROM.
Inicializace paměti
Ještě před prvním použitím je nutné knihovnu inicializovat funkcí EEPROM.begin(). Jejím parametrem je počet bajtů, které chceme pro naši aplikaci vyhradit.
EEPROM.begin(64);
V tomto případě jsme požádali o vytvoření prostoru o velikosti 64 bajtů. Pro naši ukázku by sice stačily pouhé 4 bajty, protože proměnná typu int na ESP32 zabírá právě 4 bajty, je však zvykem ponechat si určitou rezervu pro případné rozšíření programu.
Inicializaci knihovny provádíme pouze jednou, obvykle ve funkci setup().
Uložení hodnoty do EEPROM
Nejjednodušším způsobem zápisu je funkce EEPROM.write(), která zapisuje vždy pouze jeden bajt na zadanou adresu.
EEPROM.write(0, 25);
První parametr určuje adresu v EEPROM, druhý parametr zapisovanou hodnotu. Tato funkce je vhodná zejména pro práci s datovým typem byte.
Budeme-li však chtít ukládat vícebajtové proměnné, například int, float nebo vlastní datové struktury, je výhodnější použít funkci EEPROM.put().
EEPROM.put(0, wakeCount);
Funkce sama zjistí velikost zapisované proměnné a uloží všechny její bajty na zadanou adresu. Programátor tedy nemusí řešit jejich rozdělení ani skládání, což výrazně zjednodušuje práci.
Proč je nutné volat EEPROM.commit()
Možná nás překvapí, že samotným voláním funkcí EEPROM.write() nebo EEPROM.put() ještě nedochází k zápisu do paměti Flash.
Obě funkce nejprve upraví obsah pracovní kopie EEPROM uložené v operační paměti RAM. Teprve příkaz
EEPROM.commit();
způsobí skutečný zápis změněných dat do paměti Flash.
Toto řešení má svou logiku. Program může postupně změnit několik různých hodnot a všechny je následně uložit jediným zápisem. Tím se nejen zrychlí běh programu, ale současně se omezuje počet zápisů do Flash, což přispívá k prodloužení její životnosti.
Zapomeneme-li funkci EEPROM.commit() zavolat, po restartu nebo odpojení napájení zjistíme, že se žádná z provedených změn neuložila.
Načtení uložené hodnoty
Pro čtení dat existují obdobné funkce. Pokud potřebujeme přečíst jediný bajt, použijeme funkci EEPROM.read().
byte hodnota = EEPROM.read(0);
Pro načítání vícebajtových proměnných je opět výhodnější použít funkci EEPROM.get().
EEPROM.get(0, wakeCount);
Stejně jako funkce EEPROM.put() se i EEPROM.get() postará o správný počet bajtů, takže ji lze bez dalších úprav použít pro proměnné typu int, float i pro složitější datové struktury.
Nyní již známe všechny základní funkce potřebné pro práci s emulovanou EEPROM. V následujícím příkladu je spojíme dohromady a využijeme je k uchování počtu probuzení modulu ESP32 i po přechodu do stavu hibernace.
- POZNÁMKA:
- Pokud jste se již s knihovnou
EEPROM.hněkdy setkali, možná vás překvapí, že v našem výkladu budeme přednostně používat funkceEEPROM.put()aEEPROM.get(), zatímco v mnoha (starších) článcích nebo videích narazíte především na dvojiciEEPROM.write()aEEPROM.read(). - Oba způsoby jsou správné a knihovna je podporuje dodnes. Funkce
EEPROM.write()aEEPROM.read(), jak již bylo naznačeno, však pracují vždy pouze s jedním bajtem. Potřebujeme-li uložit vícebajtovou proměnnou, napříkladint, musíme se postarat o zápis i následné načtení všech jejích bajtů. - Naproti tomu funkce
EEPROM.put()aEEPROM.get()dokáží uložit i načíst celou proměnnou libovolného datového typu jediným příkazem. Samy přitom zajistí správný počet bajtů i jejich uložení do paměti. Proto se dnes při výuce i v běžných aplikacích používají častěji. - Také my je tedy v dalším textu budeme upřednostňovat.
Zápis a následné načtení hodnoty z EEPROM
V této ukázce si spojíme oba základní principy práce s emulovanou EEPROM – zápis i čtení uložených dat.
Cílem je ověřit, že data uložená do EEPROM zůstávají zachována nezávisle na aktuálním stavu proměnných v programu. Jinými slovy, že EEPROM uchovává „starou“ hodnotu, i když s ní v kódu mezitím provedeme změnu.
#include <Arduino.h>
#include <EEPROM.h>
int wakeCount = 15;
void setup() {
Serial.begin(115200);
EEPROM.begin(64);
EEPROM.put(0, wakeCount); // Uložení původní hodnoty do EEPROM
EEPROM.commit();
wakeCount = 99; // Změna hodnoty v programu (ale ne v EEPROM)
EEPROM.get(0, wakeCount); // Načtení hodnoty zpět z EEPROM
Serial.print("Hodnota nactena z EEPROM: ");
Serial.println(wakeCount);
}
void loop() { }
Vysvětlení programu
EEPROM.begin( 64); - Inicializuje emulovanou EEPROM a vyhradí 64 bajtů paměti Flash pro ukládání dat.
EEPROM.put( 0, wakeCount); - Uloží aktuální hodnotu proměnné
wakeCount(v tomto případě15) na adresu0. EEPROM.commit(); - Provede fyzický zápis dat do paměti Flash. Bez tohoto kroku by se žádná data po restartu neuložila.
wakeCount = 99;- Změní hodnotu proměnné pouze v paměti RAM. EEPROM tímto krokem ovlivněna není.
EEPROM.get( 0, wakeCount); - Načte původní hodnotu uloženou v EEPROM a přepíše aktuální hodnotu proměnné. Proto se v programu znovu objeví hodnota
15. Serial.println() - Zobrazí hodnotu načtenou z EEPROM, čímž ověříme, že paměť uchovala původní data nezávisle na změnách v programu.
Uložení počtu probuzení při hibernaci ESP32
Nyní spojíme všechny dosud získané poznatky – práci s knihovnou EEPROM.h i přechod do stavu hibernace na modulu ESP32.
Cílem bude jednoduchý čítač, který si bude pamatovat, kolikrát se modul probudil z režimu spánku/hibernace. Hodnota tohoto čítače se bude ukládat do emulované EEPROM, takže zůstane zachována i po přechodu nejen do hlubokého spánku, ale i hibernace. Nebo dokonce po resetu zařízení!
Princip řešení
Po každém probuzení:
- načteme hodnotu čítače z EEPROM
- zvýšíme ji o 1
- zobrazíme ji na sériovém monitoru
- uložíme zpět do EEPROM
- přejdeme do hibernace
Tím vznikne jednoduchý, ale velmi názorný příklad dlouhodobého uchovávání dat.
#include <Arduino.h>
#include <EEPROM.h>
#include "esp_sleep.h"
#define LED_PIN 2
RTC_DATA_ATTR int wakeCount = 0;
void hibernate() {
// vypnutí RTC domén (max úspora)
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
// uložení hodnoty do EEPROM
EEPROM.put(0, wakeCount);
EEPROM.commit();
esp_deep_sleep_start();
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
EEPROM.begin(64);
// kontrola probuzení z deep sleep / hibernace
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) {
// načtení hodnoty z EEPROM
EEPROM.get(0, wakeCount);
wakeCount++;
}
Serial.print("Pocet probuzeni: ");
Serial.println(wakeCount);
digitalWrite(LED_PIN, HIGH);
Serial.println("Jsem vzhuru, za 5 s usnu...");
delay(5000);
esp_sleep_enable_timer_wakeup(10ULL * 1000000ULL);
Serial.println("Usinam...");
delay(100);
hibernate();
}
void loop() {}
Vysvětlení klíčových částí
EEPROM.begin(64);- Inicializuje emulovanou EEPROM a vyhradí 64 bajtů paměti Flash pro ukládání dat.
EEPROM.get(0, wakeCount);- Načte uloženou hodnotu z adresy
0do proměnnéwakeCount. wakeCount++;- Zvýší čítač o jedno probuzení.
EEPROM.put(0, wakeCount);- Připraví novou hodnotu k uložení do EEPROM.
EEPROM.commit();- Provede fyzický zápis dat do Flash paměti. Bez tohoto kroku by se hodnota po restartu neuložila.
Na následujícím obrázku je vidět výstup ze sériového monitoru. Hodnota čítače se při každém probuzení postupně zvyšuje, což potvrzuje, že data uložená v EEPROM zůstávají zachována i po přechodu do hibernace.
Jak vidíme, počet probuzení je nyní správný a při každém dalším se zvětšuje. Tímto jednoduchým způsobem jsme dosáhli toho, že si ESP32 dokáže uchovat stav čítače i v případě, že přechází do hluboké hibernace. Emulovaná EEPROM zde funguje jako malá trvalá paměť, která přežije i úplné vypnutí napájení.
Další možnosti uchování hodnoty při hibernaci
Emulovaná EEPROM není jediným způsobem, jak na ESP32 uchovat hodnotu proměnné mezi jednotlivými cykly hibernace. Existuje několik dalších řešení, která data zachovají i po přechodu do hibernace nebo i po úplném vypnutí napájení.
2. Preferences (NVS úložiště)
Velmi používanou alternativou je knihovna Preferences, která pracuje s interním NVS (Non-Volatile Storage) v paměti Flash.
Oproti emulované EEPROM jde o modernější a stabilnější řešení, které je přímo určené pro ukládání malých dat. Hodnoty se ukládají pod klíčem (například názvem proměnné), což výrazně zlepšuje přehlednost kódu.
Velkou výhodou je, že NVS je navrženo přímo pro časté zápisy a dlouhodobý provoz, takže je pro reálné aplikace často vhodnější než jednoduchá emulace EEPROM.
Uložení a načtení hodnoty pomocí Preferences (NVS)
Pro srovnání si nyní ukážeme stejný princip, jako byl ukázán u EEPROM, ale pomocí modernějšího úložiště Preferences, které využívá interní NVS paměť ESP32.
Na rozdíl od EEPROM zde pracujeme s páry klíč–hodnota, takže není nutné řešit adresy paměti.
#include <Arduino.h>
#include <Preferences.h>
int wakeCount = 15;
Preferences prefs;
void setup() {
Serial.begin(115200);
// otevření prostoru v NVS
prefs.begin("storage", false);
// uložení původní hodnoty
prefs.putInt("wakeCount", wakeCount);
// změna hodnoty v programu (bez vlivu na NVS)
wakeCount = 99;
// načtení hodnoty zpět z NVS
wakeCount = prefs.getInt("wakeCount", 0);
Serial.print("Hodnota nactena z NVS: ");
Serial.println(wakeCount);
prefs.end();
}
void loop() {}
Vysvětlení klíčových částí NVS (Preferences)
V následující části si vysvětlíme pouze ty příkazy, které souvisí s úložištěm NVS (Preferences).
Preferences prefs;- Vytvoříme instanci objektu
Preferences, která nám umožní pracovat s NVS pamětí ESP32. Tento objekt představuje „rozhraní“ k internímu úložišti, ve kterém se data ukládají ve formě klíč–hodnota. prefs.begin("storage", false);- Tento příkaz otevře nebo vytvoří oblast v NVS paměti.
- První parametr
"storage":
Určuje název tzv. namespace (prostor jmen).
NVS umožňuje rozdělit uložená data do více logických oblastí. Název může být libovolný řetězec do přibližně 15 znaků, například"config","data"nebo právě"storage".
V našem případě používáme obecný název"storage", protože ukládáme jednoduchou ukázkovou hodnotu. - Druhý parametr
false:
Určuje režim otevřenífalse→ režim pro čtení i zápis (read/write)true→ pouze pro čtení (read-only)
false, protože potřebujeme do paměti nejen číst, ale také zapisovat hodnotuwakeCount.
prefs.putInt( "wakeCount", wakeCount); - Uloží celočíselnou hodnotu do NVS.
- První parametr
"wakeCount"
Je klíč (název položky), pod kterým je hodnota uložena. Může to být libovolný textový identifikátor. - Druhý parametr
wakeCount
Je samotná ukládaná hodnota.
Výhodou je, že NVS se stará o datový typ automaticky, takže není nutné řešit velikost ani adresy paměti jako u EEPROM.
prefs.getInt( "wakeCount", 0); - Načte hodnotu z NVS.
- První parametr
"wakeCount"
Klíč, podle kterého se hledá uložená hodnota. - Druhý parametr
0
Výchozí hodnota, která se použije v případě, že klíč v paměti ještě neexistuje (například při prvním spuštění programu).
V našem případě tedy pokud ESP32 ještě nikdy neuložilo žádnou hodnotu, vrátí se0.
prefs.end(); - Uzavře přístup k NVS paměti.
Tím se uvolní interní zdroje knihovnyPreferencesa zajistí se korektní ukončení práce s úložištěm. V jednoduchých programech není vždy nutné volatend(), ale je to správný a bezpečný postup, zejména v delších aplikacích.
- Poznámka k procvičení:
- Aby si čtenář opravdu osvojil práci s ukládáním dat, je vhodné si následující úpravu vyzkoušet samostatně:
- Upravte program tak, aby se hodnota
wakeCountautomaticky zvyšovala při každém spuštění a ukládala zpět do NVS, podobně jako v předchozím příkladu s EEPROM. - Tím vznikne stejné řešení jako u hibernace ESP32, ale tentokrát s využitím modernějšího a doporučovaného úložiště
Preferences. - Jak se říká – jen čtení kódu ještě nikoho programovat nenaučilo.
Řešení úkolu:
Dobrá… Ale jen tak zadarmo to nedostanete! 😉
#include <Arduino.h>
#include <Preferences.h>
#include "esp_sleep.h"
#define LED_PIN 2
Preferences prefs;
int wakeCount = 0;
void hibernate() {
// maximální úspora energie
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
// uložení hodnoty do NVS
prefs.putInt("wakeCount", wakeCount);
esp_deep_sleep_start();
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
// otevření NVS prostoru
prefs.begin("storage", false);
// načtení hodnoty z NVS (nebo 0 při prvním spuštění)
wakeCount = prefs.getInt("wakeCount", 0);
// zvýšení čítače probuzení
wakeCount++;
Serial.print("Pocet probuzeni: ");
Serial.println(wakeCount);
digitalWrite(LED_PIN, HIGH);
Serial.println("Jsem vzhuru, za 5 sekund usnu...");
delay(5000);
esp_sleep_enable_timer_wakeup(10ULL * 1000000ULL);
Serial.println("Usinam...");
delay(100);
hibernate();
}
void loop() {}
3. LittleFS a souborový systém
Další možností, jak uložit obsah proměnné přes dobu hibernace, je již zmíněné využití souborového systému LittleFS, který ukládá data do Flash paměti ve formě souborů.
Tento přístup je vhodný zejména tehdy, pokud potřebujeme uchovávat větší množství dat nebo složitější struktury, například konfigurační soubory, logy nebo JSON data.
Pro jednoduché uložení jediné celočíselné hodnoty však představuje zbytečně složité řešení, protože vyžaduje práci se soubory – jejich vytvoření, otevření, zápis i následné čtení.
Proto zde ukázku tohoto řešení nebudeme uvádět – případně odkážeme na dřívější článek: Kam (zase) se soubory? Aneb LittleFS…, který se souborovým systémem LittleFS zabývá podrobně.
Závěr
Režim hibernace na modulu ESP32 představuje nejhlubší formu uspání, při které je spotřeba energie snížena na absolutní minimum. Jak jsme si ukázali, za tuto úsporu ale platíme určitou daň – prakticky veškerá vnitřní paměť včetně RTC RAM je vypnuta, a není tedy možné spoléhat na uchování proměnných tak, jak jsme byli zvyklí u režimu hlubokého spánku. To jsme si ověřili tím, že jsme si ukázali, jak RTC paměť v režimu hibernace skutečně neobstojí. Následně jsme hledali způsob, jak data uchovat i přes úplné uspání čipu. Ukázalo se, že naštěstí ESP32 nám k tomu nabízí několik možností.
Nejjednodušším řešením byla emulovaná EEPROM, která díky využití interní Flash paměti dokáže uchovávat data i po vypnutí napájení. Na tomto principu jsme si postupně vysvětlili základní práci s knihovnou EEPROM.h, od zápisu přes čtení až po nutnost volání EEPROM.commit().
Jako modernější a z praxe často doporučovanou alternativu jsme si následně představili úložiště Preferences (NVS), které pracuje s klíč–hodnota systémem a výrazně zjednodušuje správu uložených dat. Na jednoduchém příkladu jsme si ukázali, že i složitější operace s pamětí lze na ESP32 řešit velmi elegantně a s minimem kódu.
ESP32 nám dává více cest, jak problém řešit, a správná volba vždy závisí na tom, kolik dat skutečně potřebujeme uchovat a jak složitou strukturu aplikace navrhujeme. Pro jednoduché čítače, jako byl náš wakeCount, si bohatě vystačíme s EEPROM nebo Preferences. Pro složitější data už ale možná dává smysl sáhnout po robustnějších nástrojích.