Lekce 2 - Přerušení a časovač na modulu ESP-32
V předchozí lekci, Seznámení s ESP-32, jsme se seznámili s čipem ESP-32 a vývojovou deskou ESP-32 DEVKIT DOIT.
V tomto tutoriálu Internetu věcí s ESP32 se zaměříme na možnosti využití přerušení a časování, které nám modul ESP-32 nabízí. Vysvětlíme si princip přerušení a jeho dva typy.
Přerušení
Přerušení jsou klíčovým konceptem v oblasti mikrokontrolerů a
systémů vestavěných zařízení, jako je ESP32. Jako přerušení
označujeme signály generované buď externími nebo interními
událostmi. Tyto signály dočasně přeruší běžící program a
spustí specifickou obslužnou rutinu. Uveďme si příklad s
jednoduchou LED diodou. Řekněme, že máme program, který rozbliká LED.
Mikrokontroler provádí všechny příkazy v programu tak, jak jdou za sebou.
Pokud však chceme monitorovat stav diody v reálném čase, narážíme na
zpoždění, které je dané délkou běžícího programu – dokud neskončí,
nemáme možnost do něj vstoupit. A když program běží ve smyčce
loop()
, jsme od něj odděleni zcela. Zde do hry přichází
přerušení.
Při přerušení mikrokontroler zastaví běh programu a zavolá funkci typu ISR (Interrupt Service Routine). Následně se provedou všechny příkazy v těle funkce a poté se mikrokontroler opět vrátí do hlavního programu. Tento mechanismus umožňuje mikrokontroleru rychle reagovat na důležité události (jako je změna stavu LED diody), aniž by byl nutný průběžný průzkum (polling) stavu diody. To zefektivňuje použití procesorového času a umožňuje mikrokontroleru zvládat více úloh souběžně.
ESP-32 má celkem 32 přerušení na každé jádro a každé z nich má určitou prioritu.
Typy přerušení
Základní rozdělení je odvozeno od zdroje přerušení, dělí se proto na externí/hardwarové a softwarové.
Externí/hardwarové přerušení
Toto přerušení je vyvoláno událostí na externí periferii. Jako příklad si uveďme senzor dotyku. Pokud senzor zaregistruje dotyk, změní se i stav na GPIO a vyvolá se přerušení. Následně se provedou příkazy v těle ISR funkce a poté se řízení vrátí do hlavního programu. Největší výhodou přerušení je, že nemusíme neustále monitorovat stav periferie. Periferie si naopak sama řekne, že u ní nastala změna, pro kterou máme připravenou obslužnou rutinu.
Softwarové přerušení
Tento typ přerušení nastane ve chvíli, kdy program sám explicitně vyvolá přerušení a určí rutinu, která se vykoná. Softwarové přerušení se často používá pro manipulaci s hardwarovými zařízeními, signalizaci chyb, nebo pro realizaci komplexních funkcí v rámci operačního systému. Lze jím také emulovat hardwarové události pro testování nebo ladění kódu, nebo vynutit kontrolu některých funkcí.
Při softwarových přerušení je důležité zajistit správnou synchronizaci a řízení přerušení, aby nedocházelo k nekonzistenci dat nebo jiným problémům.
GPIO přerušení
Výhodou modulu ESP-32 je, že všechny jeho GPIO piny lze nakonfigurovat jako zdroje hardwarového přerušení. Docílíme toho velmi jednoduše, stačí pouze příslušný pin připojit k odpovídající ISR. Toto nám umožní následující makro:
attachInterrupt(GPIOpin, ISR, Event);
Makro obsahuje tři argumenty:
GPIOpin
: číslo pinu, ke kterému má být přiřazeno přerušení,ISR
: název funkce, která má být při přerušení zavolána,Event
: třetí argument určí, při jaké události má být přerušení vyvoláno. Možné hodnoty jsou následovné:LOW
- přerušení se spustí, pokud je na pinu hodnotaLOW (0)
,HIGH
- přerušení se spustí, pokud je na pinu hodnotaHIGH (1)
,CHANGE
- přerušení se spustí vždy, když se na pinu změní hodnota (LOW
naHIGH
neboHIGH
naLOW
),FALLING
- přerušení se spustí, pokud se hodnota na pinu změní zHIGH
naLOW
(doběžná hrana),RISING
- přerušení se spustí, pokud se hodnota na pinu změní zLOW
naHIGH
(náběžná hrana).
Když už nyní víme, co znamená jaký argument, pojďme dosadit konkrétní hodnoty:
attachInterrupt(5, ISR, CHANGE);
Po provedení tohoto příkazu se nám na pátý pin nastaví hardwarové přerušení, které spustíme např. tlačítkem. Pojďme si ještě popsat ISR funkci, která je předávána do makra jako argument:
void IRAM_ATTR ISR(){ // příkaz; // příkaz; }
Tato funkce obsahuje všechny příkazy, které se mají spustit, když je
hlavní program přerušen. Návratový typ funkce IRAM_ATTR
zajistí, že bude funkce uložena do části paměti IRAM a nikoliv do paměti
flash, čímž zajistíme její rychlejší načítání.
Jelikož tato funkce blokuje chod hlavního programu, je vhodné používat ji pro co nejkratší úlohy.
Jak odstranit přerušení?
V některých případech je potřeba odstranit přerušení na dobu
neurčitou. K tomu nám poslouží funkce detachInterrupt()
.
Jelikož jsme si v předchozím kroku nastavili přerušení na pin
5
, pojďme jej nyní odstranit:
detachInterrupt(5);
Po provedení tohoto příkazu se pin přestane chovat jako zdroj
přerušení do doby, kdy bude zavolána funkce attachInterrupt()
nebo bude rebootován systém.
Praktický příklad – zhasnutí a rozsvícení LED diody
Nyní již umíme vše, co potřebujeme k vytvoření jednoduchého programu, který bude díky přerušení zhasínat a rozsvěcovat LED. K tomuto příkladu budeme potřebovat kromě modulu také tlačítko a LED diodu, které společně zapojíme podle následujícího schématu:

Vše máme zapojeno a můžeme se vrhnout na kód. Nejdříve si nastavíme makra pro jednotlivé piny a také definujeme ISR funkci:
#define buttonPin 33 #define LEDPin 32 void IRAM_ATTR menLED(){ digitalWrite(LEDPin, !digitalRead(LEDPin)); }
Při využívání přerušení v programu není potřeba definovat funkci
loop()
, ta tedy zůstane prázdná. Zbývá už jen doplnit tělo
funkce setup()
, a to následovně:
void setup(){
pinMode(LEDPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
attachInterrupt(buttonPin, menLED, CHANGE);
}
Pro úplnost si zrekapitulujme celý program:
#define buttonPin 33 #define LEDPin 32 void IRAM_ATTR menLED(){ digitalWrite(LEDPin, !digitalRead(LEDPin)); } void setup(){ pinMode(LEDPin, OUTPUT); pinMode(buttonPin, INPUT_PULLUP); attachInterrupt(buttonPin, menLED, CHANGE); } void loop(){ }
Nyní nám bude tlačítko fungovat jako přepínač, tzn. že po jeho stisknutí změní hodnotu LED diody na opačnou.
Časování
Když už rozumíme tomu, co je to přerušení a jak funguje, můžeme se pustit do časování. Časování je v podstatě druh přerušení, protože generuje přerušení po určité časové době. Často se využívá třeba jako počítadlo.
Časování na modulu ESP32
Náš mikrokontroler ESP32 obsahuje dvě skupiny časovačů, z nichž každá skupina obsahuje dva hardwarové časovače. Každý časovač má 64 bitů. Ty jsou založeny na 16 bitovém prescaleru (dělička vstupního signálu). Využívání časování je velmi efektivní, protože je při něm jisté, že se načasované události stanou s přesností na milisekundu.
Všechny přerušení vyvolány časovačem se řadí do softwarových přerušení.
Podle frekvence mikrokontroleru se právě díky prescaleru tato frekvence rozdělí na menší celky, tzv. ticky. Nám již známá ISR funkce se tedy vždy spustí po uplynutí určitého počtu ticků.
Pojďme si tedy zrekapitulovat, jak funguje časovač. Časovač používá počítadlo, které se zvýší o jedna vždy, když uplyne určitý časový úsek (tick). Jakmile bude počítadlo na hodnotě, kterou jsme mu v kódu nastavili, časovač spustí přerušení a počítadlo se vynuluje.
Praktický příklad – pravidelné blikání LED diodou
Představte si, že chceme každou sekundu bliknout LED diodou. Nabízí se
použít funkci delay()
, ale to by zabralo veškerý procesorový
čas a nemohli bychom provádět žádné jiné úlohy. Lepší cestou je tedy
použití časovače. Zapojení pro tento příklad ponecháme v podstatě
stejné, jen z něj vymažeme tlačítko. V kódu si nejdříve zase nastavíme
hodnotu makra, vytvoříme proměnnou pro časovač a nadefinujeme si ISR
funkci:
#define LEDPin 32; hw_timer_t *casovac = NULL; void IRAM_ATTR naCas(){ digitalWrite(LEDPin, !digitalRead(LEDPin)); }
Do hodnoty makra je důležité vložit číslo pinu, do kterého jsme připojili LED diodu. V případě, že se čísla neshodují, program nebude fungovat!
Když máme všechno nastaveno, doplníme už jen tělo funkce
setup()
a máme hotovo. Funkce loop()
zůstane opět
prázdná, protože pro ukázku nepotřebujeme provádět jiné operace
(přerušení nastane i tak). Nastavíme pin LED diody na výstup:
pinMode(LEDPin, OUTPUT);
Následně zapneme časovač v našem mikrokontroleru. Prvním parametrem
funkce je číslo časovače, který chceme použít
(hodnoty 0 až 3). Druhým parametrem je hodnota prescaleru, ze
kterého se počítá délka jednoho ticku. My nyní použijeme hodnotu
80
. Tím dosáhneme frekvence 1 MHz, což znamená, že každý
tick trvá jednu mikrosekundu. Poslední parametr určuje, zda má počítadlo
počítat nahoru (true
) nebo dolů (false
):
casovac = timerBegin(0,80,true);
Musíme také určit, která funkce se má provést, když nastane
přerušení. V našem případě využijeme ISR funkci naCas()
,
kterou jsme si vytvořili:
timerAttachInterrupt(casovac, &naCas, true);
Dále nastavíme interval změny hodnoty na LED diodě. K tomu použijeme
funkci timerAlarmWrite()
. První parametr určuje, o který
časovač se jedná, druhý určuje prodlevu v mikrosekundách a třetí
parametr (true
) zajišťuje periodické resetování
časovače:
timerAlarmWrite(casovac, 1000000, true);
A konečně jen povolíme časovač a náš kód je připraven ke spuštění. Teď bude LED dioda blikat vždy po jedné sekundě:
timerAlarmEnable(casovac);
Na závěr si ještě pro jistotu zrekapitulujeme celý program:
#define LEDPin 32 hw_timer_t *casovac = NULL; void IRAM_ATTR naCas(){ digitalWrite(LEDPin, !digitalRead(LEDPin)); } void setup(){ pinMode(LEDPin, OUTPUT); casovac = timerBegin(0,80,true); timerAttachInterrupt(casovac, &naCas, true); timerAlarmWrite(casovac, 1000000, true); timerAlarmEnable(casovac); } void loop(){ }
V příští lekci, Mód hlubokého spánku na modulu ESP-32, si popíšeme mód hlubokého spánku na modulu ESP-32 a předvedeme si jej na praktické ukázce.