Arduino: Použití přerušení
Dnes se trochu seznámíme s přerušeními, velmi důležitou a základní vlastností Arduina a dalších mikrokontrolérů. I když se zaměříme na Arduino Uno, zde uvedené koncepty jsou stejně platné i pro jiné desky.
Jedním ze způsobů, jak si udržet kontrolu nad externími vstupy nebo událostmi vnitřního časování, je použití přerušení.
Úvod: Co je přerušení?
Přerušení je vlastně přesně to, co sám název napovídá – je to přerušení provádění hlavního programu, aby bylo možné se postarat o něco jiného. Přerušení nejsou v žádném případě něco unikátního pro mikrokontroléry, v počítačích a kontrolérech se používají již desítky let. Například když píšeme na klávesnici, pohybujeme myší nebo přejíždíme po dotykové obrazovce, vytváříme přerušení a tato přerušení uvádějí do činnosti služby, které vytvářejí vhodnou odezvu na naše akce.
Ve své základní podobě funguje přerušení takto:
- Je spuštěn program.
- Dojde k přerušení.
- Program se pozastaví a jeho data se odloží, aby mohl později pokračovat.
- Spustí se kód související s přerušením.
- Když kód přerušení skončí, program pokračuje tam, kde skončil.
Přerušení jsou skvělá pro monitorování událostí, jako je stisknutí spínače nebo spuštění alarmu, ke kterým dochází náhle. Jsou také správnou volbou, když potřebujeme přesně měřit vstupní impulsy.
Existuje mnoho typů přerušení používaných v mikrokontrolérech a mikroprocesorech. Funkce přerušení se liší model od modelu. Všechny je lze obecně rozdělit do dvou kategorií:
- Hardwarová přerušení – Obvykle pocházejí z externích signálů.
- Softwarová přerušení – Jedná se o interní signály, obvykle řízené časovači nebo událostmi souvisejícími se softwarem.
Přerušení na Arduino Uno
Ukážeme si použití tří základních typů přerušení, která modul Arduino Uno podporuje:
- Hardwarové přerušení – signály externího přerušení na konkrétních pinech.
- Přerušení změny pinů (Pin Change Interrupts) – externí přerušení na jakémkoli pinu (seskupené do portů).
- Přerušení časovače – Interní přerušení generovaná časovačem (nastavitelné v programu).
Všechny typy v podstatě fungují stejně. Jakmile dojde k události přerušení, mikrokontrolér spustí nějaký kód, který jsme umístili do „rutiny služby přerušení“ (tzv. ISR). Rutina ISR je v podstatě normální funkce. Na rozdíl od běžných funkcí Arduina do ní nemůžeme napřímo předávat parametry, ani z ní získat návratovou hodnotu. Pokud tak chceme činit, musíme tak činit nepřímo – například prostřednictvím globálních proměnných. Další základní vlastností funkcí ISR (a tedy i dalším určitým omezením) je, že musí být rychlé. Velmi, velmi rychlé! Ve své podstatě to dává smysl, rutinou ISR doslova rušíme mikrokontrolér, který dělá svou práci, a tato práce může zahrnovat věci kolem potřebného načasování. Nemůžeme ho tedy přerušit na dlouho dobu. Budeme si tedy pamatovat, že v rámci ISR nemůžeme dělat určité věci:
- Nemůžeme používat funkce čekání, jako je
delay()
nebodelayMicroseconds()
. - Také nemůžeme použít funkci
millis()
. - Žádný tisk na sériový monitor!
- Pro komunikaci se zbytkem programu používejme pouze globální proměnné (musí být deklarovány jako
volatile
)
Navzdory těmto omezením může ISR provádět velmi užitečnou práci, jako je změna hodnoty jedné nebo více globálních proměnných, které pak lze číst uvnitř hlavní smyčky a tu „zdržovací“ práci provést tam.
Proč používat přerušení?
Abychom ilustrovali přínos použití přerušení, zkusíme si velmi jednoduchý pokus. Použijeme kromě Arduina jen tlačítko. Můžeme použít i LED s ochranným rezistorem, ale pravděpodobně nám bude stačit využití jen té vestavěné na pinu 13.
V našem zapojení chceme použít tlačítko tak, aby fungovalo jako spínač a při každém stisknutí střídavě zapínalo a vypínalo LED. Je to docela běžná aplikace. Je jasné, že jsme ji určitě viděli a pravděpodobně asi již i někdy dříve naprogramovali.
Program pro nás úkol bude vypadat takto:
// pripojeni spinace (pin 2), vestavena LED je na pin 13
const byte ledPin = 13;
const byte buttonPin = 2;
// logicka promenna pro stav tlacitka
bool toggleState = false;
void checkSwitch() { // kontrola stavu spinace
if (digitalRead(buttonPin) == LOW) { // když je stisknut, zmen stav
delay(200); // cekacka proti zakmitani spinace
toggleState = !toggleState; // změna stavu LED
digitalWrite(ledPin, toggleState); // nastaveni pinu s LED
}
}
void setup() {
pinMode(ledPin, OUTPUT); // nastaveni pinu pro LED na vystup
pinMode(buttonPin, INPUT_PULLUP); // nastaveni pinu pro tlacitko na stup s pullup rezistorem
}
void loop() { // hlavni nekonecna smycka
checkSwitch(); // kontrola spinace
}
Popis kódu:
Začínáme tím, že deklarujeme konstanty pro spínač a LED. Také definujeme booleovskou proměnnou togglestate, kterou budeme používat k reprezentaci aktuálního stavu našeho spínače. Na začátku je nastavena na výchozí hodnotu false.
Dále definujeme funkci checkSwitch
. Je to docela jednoduchá funkce, která kontroluje stav tlačítka, pokud je stisknuto, invertuje aktuální přepínací hodnotu a použije ji ke změně stavu LED.
V části setup nastavíme LED pin jako výstup a pin spínače jako vstup s užitím vnitřního pull-up rezistoru.
Vše, co v následné hlavní nekonečné smyčce děláme, je volání funkce checkSwitch
, takže se stále dotazujeme na stav spínače.
Spustíme kód a můžeme se podívat, zda pracuje, jak má.
Úprava kódu
Zdá se, že náš přepínač funguje docela dobře, pokud je to vše, co jsme chtěli udělat – tedy mít pouze přepínač LED – mohli bychom skončit.
Ale co když je přepínač jen jednou součástí většího návrhu? Řekněme, že děláme teploměr, který jej bude používat na přepínání výstupu ze stupňů Celsia na stupně Fahrenheita. Jak snadné by to asi bylo postavit?
Na první pohled to vypadá docela jednoduše, stačí přidat kód pro čidlo DHT11 nebo podobný senzor do předchozího kódu a pomocí přepínací hodnoty určit jednotku naměřené teploty. Ale v praxi to může být trochu jinak!
Pro ilustraci udělejme drobnou úpravu našeho kódu:
// pripojeni spinace (pin 2), vestavena LED je na pin 13
const byte ledPin = 13;
const byte buttonPin = 2;
// logicka promenna pro stav tlacitka
bool toggleState = false;
void checkSwitch() { // kontrola stavu spinace
if (digitalRead(buttonPin) == LOW) { // když je stisknut, zmen stav
delay(200); // cekacka proti zakmitani spinace
toggleState = !toggleState; // změna stavu LED
digitalWrite(ledPin, toggleState); // nastaveni pinu s LED
}
}
void setup() {
pinMode(ledPin, OUTPUT); // nastaveni pinu pro LED na vystup
pinMode(buttonPin, INPUT_PULLUP); // nastaveni pinu pro tlacitko na stup s pullup rezistorem
Serial.begin(9600);
}
void loop() {
checkSwitch(); // kontrola spinace
Serial.println("Zacalo cekani!");
delay(5000); // Pridani 5s cekani (simuluje dlouhou odezvu cidla)
Serial.println("Cekani skoncilo.");
Serial.println("---");
}
V upraveném programu si všimněme dvou rozdílů (zvýrazněno tučně):
- Nastavili jsme sériový monitor a tiskneme na něj v hlavní nekonečné smyčce.
- Do hlavní nekonečné smyčky jsme přidali 5sekundové zpoždění.
Asi se 5sekundové zpoždění zdá jako poměrně hloupá věc, která postrádá jakýkoliv smysl. Ale vložili jsme ji sem schválně, abych simulovali možnou poměrně dlouhou odezvu čidla. Například čtení teplotního čidla DHT11 vyžaduje asi 2sekundové zpoždění, než se senzor stabilizuje. Zkrátka nyní máme v programu nějaký proces, jehož dokončení nějakou dobu trvá.
Po spuštění kódu v modulu Arduino jasně vidíme rozdíl v chování. Možná budeme mít štěstí a skutečně občas LED dokážeme přepnout 😊.
Pointa, kterou se snažíme ilustrovat, je samozřejmě to, že pokud máme ve smyčce cokoli jiného (co může trvat déle než několik milisekund času) pak dotazování na stav tlačítka uvnitř smyčky není nejlepší způsob, jak získat jeho hodnotu.
Lepším řešením je použití hardwarových přerušení.
Hardwarové přerušení
Hardwarové přerušení je externí přerušení a na většině modelů Arduino je omezeno jen na konkrétní piny. Tyto piny jsou nakonfigurovány jako vstupy a mohou spouštět hardwarová přerušení v závislosti na svých logických stavech.
Piny hardwarového přerušení Arduino Uno
Na modulu Arduino Uno jsou pouze dva piny, které jsou schopné hardwarového přerušení:
- Pin 2 – vektor přerušení 0
- Pin 3 – vektor přerušení 1.
Ne všechny desky Arduino jsou omezeny jen na 2 piny hardwarového přerušení, některé desky Arduino (Mega, Leonardo…) mají pinů s přerušením více.
Práce s hardwarovým přerušením
Práce s hardwarovými přerušeními je ve skutečnosti docela jednoduchá, protože potřebujeme udělat pouze dvě věci:
- Napsat funkci, kterou chceme použít jako rutinu služby přerušení.
- Připojit tuto funkci ke konkrétnímu přerušení, které chceme použít a určit, jak jej spustit.
Funkce přerušení (ISR) by se měla řídit pravidly o rychlosti a používání globálních proměnných, ale jinak je to jen funkce jako každá jiná. Může mít jakýkoli platný název, ale nemůže mít vstupní parametry.
Připojení ISR funkce přerušení:
Chceme-li „přilepit“ svou funkci ke konkrétnímu přerušení, použijeme funkci attachInterrupt
. Tato funkce má následující syntaxi a parametry:
attachInterrupt
Interrupt Vector
– přerušení, které chceme použít. Pamatujme si, že toto je interní číslo vektoru přerušení (NE číslo pinu!!!)ISR
– Název funkce ISR, kterou připojíme k přerušení.Mode
– Určuje jak chceme spustit přerušení. Možné jsou čtyři možnosti:RISING
– Spustí se, když vstup přejde z LOW do HIGH.FALLING
– Spustí se, když vstup přejde z HIGH na LOW.LOW
– Spustí se, když je vstup na úrovni LOW.CHANGE
– Spustí se vždy, když vstup změní stav z HIGH na LOW nebo LOW na HIGH.
Funkce attachInterrupt
se zpravidla v programu použije v rámci nastavení v sekci setup.
Funkce digitalPinToInterrupt
Parametr Interrupt Vector
ve funkci attachInterrupt
není stejný jako číslo pinu a může se mezi různými typy desek Arduino lišit. Nejlepší způsob, jak toto číslo získat, je funkce digitalPinToInterrupt
.
Název funkce je zároveň jejím popisem, přijímá číslo pinu a vrací číslo vektoru přerušení.
DigitalPinToInterrupt
můžeme použít přímo v rámci funkce attachInterrupt
.
attachInterrupt(
Toto je obecně preferovaný způsob pro používání hardwarová přerušení, protože umožňuje bezproblémový přenos kódu mezi deskami.
Úprava našeho kódu pro hardwarová přerušení
Nyní, když víme více o používání hardwarového přerušení, upravme program tlačítka a zpoždění tak, aby jej používal.
Zde je upravená verze našeho programu pomocí hardwarového přerušení:
// pripojeni spinace (pin 2), vestavena LED je na pin 13
const byte ledPin = 13;
const byte buttonPin = 2;
// logicka promenna pro stav tlacitka
volatile bool toggleState = false;
void checkSwitch() { // kontrola stavu spinace
if (digitalRead(buttonPin) == LOW) { // když je stisknut, zmen stav
toggleState = !toggleState; // změna stavu LED
digitalWrite(ledPin, toggleState); // nastaveni pinu s LED
}
}
void setup() {
pinMode(ledPin, OUTPUT); // nastaveni pinu pro LED na vystup
pinMode(buttonPin, INPUT_PULLUP); // nastaveni pinu pro tlacitko na stup s pullup rezistorem
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(buttonPin), checkSwitch, FALLING); // pripojeni ISR funkce
}
void loop() {
Serial.println("Zacalo cekani!");
delay(5000); // Pridani 5s cekani (simuluje dlouhou odezvu cidla)
Serial.println("Cekani skoncilo.");
Serial.println("---");
}
Celý program je téměř stejný. Všimněme si, že proměnná toggleState
je nyní deklarována jako volatile
. To je důležité, protože s její hodnotou se manipuluje v rámci rutiny služby přerušení (ISR). Bez příkazu volatile
se může kompilátor Arduino IDE pokusit přeoptimalizovat kód a proměnnou odstranit.
Naše funkce checkSwitch
je téměř identická s tím, co bylo dříve, jediný rozdíl je v tom, že jsme odstranili funkci delay
. Je to proto, že budeme používat checkSwitch
jako rutinu přerušení ISR. Jak jsme si řekli, uvnitř ISR nemůžeme používat čekání.
V sekci setup kromě obvyklých příkazů pinMode
, inicializujeme sériový monitor a poté spustíme attachInterrupt
pro připojení funkce checkSwitch
k hardwarovému přerušení na pinu 2. Používáme režim FALLING
, protože chceme zachytit událost, když stiskneme (a uzemníme) spínač.
Vše, co máme v hlavní nekonečné smyčce, je zpoždění, které nyní poběží nepřetržitě. Jakákoli aktivita přepínače bude nyní řešena přerušením.
Spusťme kód v modulu Arduino a vyzkoušejme jej. Měli bychom vidět, že přepínač funguje, i když v hlavní nekonečné smyčce právě probíhá čekání.
Jak vidíte, hardwarová přerušení jsou mnohem efektivnější metodou zachycení vstupů přepínačů.
Pokud jsme trochu probrali základní použití přerušení na pinu 2, resp. pinu 3, asi nás napadne, zda lze použít podobnou techniku i pro ostatní piny. Přeci jen pouze dvě tlačítka připojená k modulu Arduino Uno je trochu málo!
Bohužel, výše popsaným způsobem lze opravdu na modulu Arduino Uno použít jen piny 2 a 3. Nyní si však ukážeme trochu složitější techniku, pomocí které bychom měli dokázat využít přerušení i na ostatních pinech.
Pin Change Interrupts
Další skupinou přerušení jsou tzv. Pin Change Interrupts (přerušení změnou pinu). Na rozdíl od přerušení, která jsme právě použili, nejsou omezena jen na konkrétní piny. Přerušení Pin Change Interrupts lze použít pro všechny piny. Háček je v tom, že přerušení změnou pinu (Pin Change Interrupts) jsou seskupena do portů a všechny piny na stejném portu vytvářejí stejné přerušení. To nevadí, pokud používáme pouze jeden pin daného portu, jinak ale budeme muset zjistit, který pin vzniklé přerušení způsobil.
Přerušení změny pinu, jak by název mohl naznačovat, je omezeno pouze na monitorování ZMĚNY logického stavu pinu. Takže kupříkladu stisknutí spínače vygeneruje dvě přerušení, jedno při stisknutí spínače a druhé při jeho uvolnění. Pokud potřebujeme vědět, zda bylo přerušení způsobeno úrovní HIGH nebo LOW na vstupu, budeme na to muset přijít sami.
Takže nás čeká trochu více práce, než u předešlého typu přerušení.
Pin Change Ports
Téměř každý pin na čipu ATmega328, na kterém je postaven modul Arduino Uno (a i další desky), může podporovat přerušení na 24 pinech (to zahrnuje i dva piny používané pro krystalový oscilátor 16 MHz). Na Arduino Uno je pak k dispozici 20 pinů pro přerušení změny pinů a jsou rozděleny do tří portů. Například piny 8 až 13 jsou port B, piny A0 až A5 jsou port C a piny 0 až 7 jsou port D (viz následující obrázek a tabulka)
pin | PORT# | PCINT |
---|---|---|
PORT B | ||
8 | PB0 | PCINT0 |
9 | PB1 | PCINT1 |
10 | PB2 | PCINT2 |
11 | PB3 | PCINT3 |
12 | PB4 | PCINT4 |
13 | PB5 | PCINT5 |
PORT C | ||
A0 | PC0 | PCINT8 |
A1 | PC1 | PCINT9 |
A2 | PC2 | PCINT10 |
A3 | PC3 | PCINT11 |
A4 | PC4 | PCINT12 |
A5 | PC5 | PCINT13 |
PORT D | ||
0 | PD0 | PCINT16 |
1 | PD1 | PCINT17 |
2 | PD2 | PCINT18 |
3 | PD3 | PCINT19 |
4 | PD4 | PCINT20 |
5 | PD5 | PCINT21 |
6 | PD6 | PCINT22 |
7 | PD7 | PCINT23 |
Chceme-li používat přerušení změny pinů, musíme provést následující:
- Určíme si pin, který použít. To nám určí, který port budeme používat.
- Povolíme porty, které budeme potřebovat.
- Povolíme pin(y) v portu, který musí být povolen pro přerušení.
- Upravíme příslušné rutiny služby přerušení. Pokud používáme více než jeden pin na stejném portu, pak ISR bude muset být schopen určit, který pin způsobil přerušení.
Podívejme se na tyto kroky trochu podrobněji. Pro náš příklad použijeme zatím pouze jeden pin. Později zkusíme program, který používá dva piny na stejném portu.
Výběr a povolení portu
Prvním krokem je aktivace příslušného portu, který určíme na základě čísla pinu. Chceme-li port aktivovat, použijeme registr řízení přerušení změny pinů (PCICR). PCICR má tři významné bity, bit 0, bit 1 a bit 2. Každý bit je spojen s jedním z portů. Nastavením příslušného bitu na 1 daný port povolíte. Můžeme samozřejmě povolit více než jeden port.
Příklad povolení portu B:
void setup() {
PCICR |= B00000001; // nastaveni bitu 0 pomoci masky OR
}
Povolení/zakázání pinu na portu
Po povolení portu budeme muset ještě povolit pin(y) pro vyvolání přerušení změny pinu. To se udělá úpravou masky změny pinu pro vybraný port. Existují tři masky změny pinu, každá z nich může povolit nebo zakázat 8 pinů. Chceme-li pin aktivovat, nastavíme daní bit v registru na 1. Můžeme povolit tolik pinů, kolik potřebujeme, ale nezapomeňme, že budeme muset najít způsob, jak je pak rozlišit v rutině přerušení.
Příklad povolení pinu D12 na portu B:
void setup() {
PCICR |= B00000001;
PCMSK0 |= B00010000; // nastaveni pinu D12 (bit 4 registru PCMSK0)
}
Rutina přerušení (ISR)
Na rozdíl od hardwarových přerušení zde nevytváříme rutinu přerušení, které dáme libovolný náhodný název. U přerušení Pin Change Interrupts je již ISR definováno, takže budeme muset pro váš port použít tu správnou.
Existují tři porty, tedy tři ISR, které jsou pojmenovány tak, jak je uvedeno zde:
ISR (PCINT0_vect)
ISR pro Port B (piny 8–13)
ISR (PCINT1_vect)
ISR pro Port C (piny A0–A5)
ISR (PCINT2_vect)
ISR pro Port D (piny 0–7)
Stále platí pravidla týkající se rutin přerušení ISR, např. udržujme je krátké a používáme globální (volatile
) proměnné.
Příklad přerušení změny pinu – jednoduché přerušení
V zapojení budeme opět používat pouze jeden spínač a vestavěnou LED. Opriti předchozímu případu přepojíme spínač z pinu 2 na pin 7. Přepínač je připojený na pin 7, což není kolík hardwarového přerušení. Uvidíme, že to teď nevadí, protože vše zaznamenáme pomocí přerušení změny pinu!
Budeme přepínat stav LED pokaždé, když nastane přerušení na pinu 7. Jedna důležitá věc je, že budeme snímat změnu stavu vstupu, takže tlačítko vyvolá dvě přerušení – jedno při stisknutí a druhé při uvolnění.
Díky tomu bude následující kód fungovat trochu, jako by byla LED jen zapojena do série s vypínačem!
// pripojeni spinace (pin 7), vestavena LED je na pin 13
const byte ledPin = 13;
const byte buttonPin = 7;
// logicka promenna pro stav tlacitka
volatile bool togglestate = false;
void setup() {
pinMode(ledPin, OUTPUT); // nastaveni pinu pro LED na vystup
pinMode(buttonPin, INPUT_PULLUP); // nastaveni pinu pro tlacitko na stup s pullup rezistorem
PCICR |= B00000100; // povoleni Portu D (PCIE2, Bit2 = 1)
PCMSK2 |= B10000000; // Vyber pin 7 (PCINT23, Bit7 = 1)
}
void loop() {
// hlavni nekonecna smycka je nyní prazdna
}
ISR (PCINT2_vect) { // Preruseni pro Port D
togglestate = !togglestate; // změna stavu LED
digitalWrite(ledPin, togglestate);
}
Vysvětlení kódu:
Opět začínáme definováním pinů pro spínač a LED. Dále vytvoříme globální (volatile ) proměnnou togglestate
, která bude reprezentovat stav přepínání. V setup použijeme funkci pinMode
k nastavení našich vstupů a výstupů. Pak jsme nastavili naše přerušení změny pinů. Upravíme registr PCICR, aby věděl, že chceme použít port D, a upravíme masku PCMSK2 portu D tak, aby byl pin 7 nastaven jako vstup přerušení.
Ve smyčce nemáme žádný kód, protože vše děláme pomocí rutiny služby přerušení.
Poslední částí programu je rutina služby přerušení, protože používáme port D, používáme ISR(PCINT2_vect)
. V ISR překlápíme stav proměnné toggleState
a hned ho použijeme k nastavení stavu LED.
Přeložíme kód v prostředí Arduino IDE a vyzkoušíme v modulu Arduino Uno. LED by se měla rozsvítit, když spínač podržíme stisknutý, zhasnout, když jej uvolníte. Což je pochopitelně trochu přehnané používání mikrokontroléru, ale nyní to především demonstruje přerušení změny pinu!
Příklad přerušení změny pinu – více přerušení na stejném portu
V předchozím příkladu jsme byli schopni řešit pouze jedno přerušení změny pinu, protože přerušení mělo povolený pouze jeden pin na našem portu. Nebylo tedy pochyb o tom, co způsobilo přerušení.
Ale jak se s tím vypořádat, když povolíme dva nebo více pinů na stejném portu? Budeme muset určit, kdo přerušení způsobil, abychom mohli podle toho jednat.
V tomto případě využijeme dvě tlačítka a dvě LED – viz schéma.
Každé tlačítko bude fungovat jako přepínač pro příslušnou LED. To znamená, že když dojde k přerušení, potřebujeme vědět dvě věci:
- Jaký pin způsobil přerušení?
- Jaký je stav daného pinu, zda LOW nebo HIGH?
// pripojeni spinace (pin 2), vestavena LED je na pin 11
const byte ledPin1 = 11;
const byte buttonPin1 = 2;
// pripojeni spinace (pin 7), vestavena LED je na pin 13
const byte ledPin2 = 13;
const byte buttonPin2 = 7;
// logicke promenne pro stavy tlacitek
volatile bool D2_state = LOW;
volatile bool D7_state = LOW;
void setup() {
pinMode(ledPin1, OUTPUT); // nastaveni pinu pro LED1 na vystup
pinMode(ledPin2, OUTPUT); // nastaveni pinu pro LED2 na vystup
pinMode(buttonPin1, INPUT_PULLUP); // nastaveni pinu pro tlacitko1 na stup s pullup rezistorem
pinMode(buttonPin2, INPUT_PULLUP); // nastaveni pinu pro tlacitko2 na stup s pullup rezistorem
PCICR |= B00000100; // povoleni Portu D (PCIE2, Bit2 = 1)
PCMSK2 |= B10000100; // Vyber pin 7 (PCINT23, Bit7 = 1) a pin 2 (PCINT18, Bit 3 = 1)
}
void loop() {
// hlavni nekonecna smycka je nyní prazdna
}
ISR (PCINT2_vect) { // Preruseni pro Port D
// kontrola spinace na pinu 2
if (digitalRead(buttonPin1) == LOW) { //Pin 2 vyvolal ISR sestupnou hranou
D2_state = !D2_state;
digitalWrite(ledPin1, D2_state); // zmena stavu LED1
}
// kontrola spinace na 7
if (digitalRead(buttonPin2) == LOW) { //Pin 2 vyvolal ISR sestupnou hranou
D7_state = !D7_state;
digitalWrite(ledPin2, D7_state); // změna stavu LED2
}
}
Vysvětlené kódu:
Program začíná, jak očekáváme definicí proměnných pro LED a přepínače. Dvě globální logické proměnné obdobně předchozím programům slouží pro stav a přepínání LED. V Setup stejně tak nastavujeme pomocí pinMode
piny pro spínače a LED (piny pro spínače opět pomocí interních pull-up rezistorů na vstupu).
Poté povolíme port D, jako jsme to udělali i v předchozím programu (zápisem 1 do bitu 2 registru PCICR).
Dále zapíšeme 1 do bit 7 a bitu 2 masky PCMSK2, abychom stanovili, že se s piny 7 a 2 má zacházet jako s piny pro přerušeními změny pinů.
V hlavní nekonečné smyčce opět není žádný kód, vše se provádí v rutině služby přerušení.
ISR rutinou je ISR(PCINT2_vect)
, což je stejná ISR, jako jsme použili v předchozím programu. Pouze tentokrát budeme muset zjistit, zda přerušení vyvolal pin 2 nebo 7.
V tomto programu testujeme podmínku u vstupních pinů stav LOW, protože chceme přepínat LED při stisknutí tlačítka, nikoliv při jeho uvolnění. Proto používáme podmínky, které testují stav každého vstupu. Pokud je jeho stav LOW, přepneme příslušný stav LED.
Spustíme program v modulu Arduino. Tlačítko navázané na pin 2 by mělo ovládat LED na pinu 11 a tlačítko na 7 by mělo fungovat s LED na pinu D13. Měli bychom být schopni přepínat každou z nich nezávisle.
Přerušení změny pinů může být velmi užitečné při skenování skupiny nebo matici přepínačů, nebo když potřebujeme externí přerušení, ale již nemáme volný pin pro klasické hardwarové přerušení.
Přerušení časovače
Časovaná přerušení nepoužívají externí signály. Místo toho jsou tato přerušení generována v programu a jejich časování je založeno na 16 MHz hodinovém oscilátoru modulu Arduino Uno.
Pravděpodobně každý již používal přerušení časovače, aniž by si to uvědomoval, protože jej interně používá několik populárních příkazů nebo knihoven, jako jsou příkazy pro Servo nebo generování tónů Tone. Mějme na paměti, že pokud používáme knihovnu, která využívá časovače, musíme být obezřetní, abychom nepsali konfliktní kód!
Časovače Arduino Uno
Arduino Uno má tři interní časovače, Timer0, Timer1 a Timer2. Tyto časovače nejsou stejné, protože Timer1 je 16bitový časovač, zatímco ostatní dva časovače jsou pouze 8bitové. Počet bitů určuje maximální počet, do kterého může časovač počítat (255 pro 8bitové časovače a 65535 pro 16bitové).
Hodnota v těchto časovačích se zvyšuje buď s taktovací frekvencí, nebo s jejím zlomkem. V programu lze nastavit, při které hodnotě časovače nastane spuštění přerušení, nebo také můžeme spustit přerušení, když časovač přeteče (dosáhne maximální hodnoty). To uvidíme dále.
Dělení taktovací frekvence
Časovače jsou taktovány základním oscilátorem (16 MHz), který je v procesoru ATmega328 zdrojem taktovací frekvence. Standardně je každý „tik“ taktovací frekvence také „tikem“ časovače, takže při 16 MHz by jeden „tik“ časovače představoval základní periodou 62,5 ns. To je poměrně krátká doba a pro mnoho aplikací, kdy potřebujeme měřit čas, by to bylo hodně nepraktické.
Pro zpomalení hodinového signálu má procesor ATmega328 „předděličku“ (v podstatě dělič taktovací frekvence pro hodinovou frekvenci). Předdělička dokáže snížit základní frekvenci na nižší frekvenci, můžeme si vybrat z řady běžných dělení a vytvořit tak pulsy dlouhé až 64 µs.
Taktovací frekvence | Dělicí poměr | Frekvence pro časovač | Doba „tiku“ |
---|---|---|---|
16 MHz | 1 | 16 MHz | 62,5 ns |
8 | 2 MHz | 500 ns | |
64 | 250 kHz | 4 μs | |
256 | 62,5 kHz | 16 μs | |
1024 | 15,625 kHz | 64 μs |
Předděličku natavujeme pomocí registru TCCRxB
(kde x
zastupuje číslo daného časovače). Každý řídicí registr má tři bity výběru režimu hodin. Hodnota těchto bitů určuje nastavení předděličky a také zdroj časování. Hodiny můžeme také úplně zastavit nastavením všech bitů výběru hodin na nulu. Timer0 je 8bitový časovač, používá bity CS01
, CS02
a CS03
.
CS02 | CS01 | CS00 | Popis |
---|---|---|---|
CS12 | CS11 | CS10 | |
0 | 0 | 0 | Časovač vypnut (žádný zdroj časování) |
0 | 0 | 1 | Předdělička vypnuta (časování 16 MHz) |
0 | 1 | 0 | Dělící poměr 8 (časování 2 MHz) |
0 | 1 | 1 | Dělící poměr 64 (časování 250 kHz) |
1 | 0 | 0 | Dělící poměr 256 (časování 62,5 kHz) |
1 | 0 | 1 | Dělící poměr 1024 (časování 15,625 kHz) |
1 | 1 | 0 | Externí hodiny na pinu T0 (sestupná hrana) |
1 | 1 | 1 | Externí hodiny na pinu T0 (vzestupná hrana) |
Timer1 je 16bitový časovač, používá bity CS10
, CS11
a CS12
. Konfigurace předděliček časovačů 0 a 1 jsou obdobné. V případě časovače Timer2 (8bitový časovač, bity CS20
, CS21
a CS22
) je tabulka trochu jiná:
CS22 | CS21 | CS20 | Popis |
---|---|---|---|
0 | 0 | 0 | Časovač vypnut (žádný zdroj časování) |
0 | 0 | 1 | Předdělička vypnuta (časování 16 MHz) |
0 | 1 | 0 | Dělící poměr 8 (časování 2 MHz) |
0 | 1 | 1 | Dělící poměr 32 (časování 500 kHz) |
1 | 0 | 0 | Dělící poměr 64 (časování 250 kHz) |
1 | 0 | 1 | Dělící poměr 128 (časování 125 kHz) |
1 | 1 | 0 | Dělící poměr 256 (časování 62,5 kHz) |
1 | 1 | 1 | Dělící poměr 1024 (časování 15,625 kHz) |
Máme-li nastavenou předděličku, časovač bude zadanou frekvencí postupně čítat od 0 do 255 (resp. 65535). Tím jsme zadali rychlost změny hodnoty časovače. Teď nás čeká nastavení přerušení, zejména stanovení okamžiku, kdy se má přerušení provést.
Použití přerušení časovače
Přerušení časovače lze provozovat v dvou režimech:
- režim shody (Compare Match Mode)
- režim přetečení (Overflow Mode).
Například přetečení časovače je vyvoláno, když časovač dosáhne své maximální hodnoty (255 pro 8bitové a 65535 pro 16bitové časovače). Při přetečení se vyvolá daná rutina přerušení (pokud je přerušení povoleno), čítač se vynuluje a začne znovu počítat od začátku. Přerušení při přetečení zvolíme nastavením bitu 0 (TOIE
) v registru TIMSKx
na hodnotu 1.
Druhým případem je režim shody. Ten pro nás bude zajímavější a budeme se jím tedy dále zabývat. V každém časovači jsou dva výstupní srovnávací registry (OCRA
a OCRB
). Když hodnota čítače dosáhne hodnoty registru OCRA
, vyvolá se přerušení COMPA
(pokud je povoleno). Totéž platí pro COMPB
. Kombinací zadané hodnoty v registru shody a nastavení předděličky můžeme docela dobře dosáhnout „libovolného“ (!) časového období, které potřebujeme (za předpokladu, že je v rozsahu daného časovače).
Pro nastavení přerušení shody musíme nastavit bity 1 (OCIExA
) a 2 (OCIExB
) v registr TIMSKx
.
Nastavením bitů 1 a 2 (Output Compare Match Interrupt Enable) můžeme aktivovat přerušení shody s hodnotu definovanou
v registrech OCRA
a OCRB
. Naopak bit 0, jak bylo zmíněno, nastavuje přerušení přetečení (Timer Overflow Interrupt Enable).
Nastavení přerušení v režim shody může fungovat v několika dalších režimech, které nastavuje registr TCCRxA
. Registr TCCRxA
umožňuje nastavit několik bitů, které ovlivňují chování timeru. Bity WGMx0
a WGMx1
v kombinaci s bitem WGMx2
(v registru TCCRxB
) řídí sekvenci počítání čítače. My se však zaměříme jen na dva základní režimy, a to: Režim normální (čítač) a režim vymazání časovače při porovnání (CTC). Tyto režimu se nastavují při WGMx2
nastaveno na 0 a bity WGMx0
a WGMx1
dle následující tabulky:
WGMx1 | WGMx0 | Režim provozu |
---|---|---|
0 | 0 | normální režim |
0 | 1 | PWM, fázová korekce |
1 | 0 | režim vymazání časovače při porovnání (CTC) |
1 | 1 | Rychlé PWM |
Normalní režim
Podíváme se, co bude znamenat nastavení normálního režimu pro obsluhu přerušení. Představme si, že budeme chtít vyvolat přerušení po 50 „tiknutí“ časovače. Při normálním režimu se bude hodnota časovače normálně inkrementovat, tedy začne na 0, s každým klikem se zvětší o jedičku (1, 2, 3…). Když dosáhne hodnoty shody 50, vyvolá sice přerušení, ale z hlediska čítače se nic nemění a čítač pokračuje (51, 52, 53…). Tak bude pokračovat až do své maximální hodnoty (255 nebo 65535), kdy dojde k přetečení a čítač začíná opět od hodnoty 0. Má-li být druhé přerušení vyvoláno za dalších 50 „tiků“ od prvního, nemůže hodnota shody zůstat stále 50. To by znamenalo, že k druhému přerušení dojde až čítač přeteče a opět dojde k 50. To ale není dalších 50 „tiků“! Musíme tedy při vyvolání přerušení nastavit novou hodnotu příští shody – pro následujících 50 „tiků“, tedy nejdříve na 100, pak 150, 200, 250, 45, 95 atd.
Režim CTC (vymazání časovače při porovnání)
Dříve zmíněný problém normálního režimu řeší režim CTC (Clear Timer on Compare Match), který dělá přestě to, co jeho anglický název naznačuje – při dosažení shody se časovač vynuluje. Čítač tedy bude inkrementovat od 0 do 50, pak vyvolá přerušení (je-li nastaveno), vynuluje se a opět začíná od 0. Vlastně jsme tím zkrátili inkrementační interval časovače a vyvoláváme přerušení při jakémsi jeho „přetečení“ (dosažení hodnoty shody). Pokud tedy na událost shody nastavíme přerušení, bude probíhat vždy při stejné hodnotě shody, nemusíme hodnotu shody měnit.
Dále si ukážeme funkčně stejný program, ale bude řešený oběma režimy. Nikdy nevíme, kdy se nám znalost obojího může hodit.
Nastavení hodnoty shody
Nyní nám už jen zbývá zodpovědět poslední otázku: Jak určíme hodnotu registru OCRA
(resp. OCRB
), která stanoví spuštění přerušení při režimu shody?
Datový list uvádí následující vzorec pro určení generované výstupní frekvence volání přerušení:
kde fint je frekvence vyvolávání přerušení, ftakt základní taktovací frekvence mikroprocesoru, DPP dělící poměr předděličky a HS hodnta shody.
Zpravidla v praxi je ale situace opačná. Známe frekvenci fint, kterou chceme vyvolávat přerušení, hledáme hodnotu shody HS pro dosazení do porovnávacího registru shody. Předchozí vzorec pak můžeme přepsat následovně:
Pomocí tohoto vzorce si jako ukázku můžeme vypočítat, příklad získání hodnoty shody pro spouštění rutiny přerušení s frekvencí fint = 1 Hz. Nastavíme předděličku na dělicí poměr DPP = 1024, základní taktovací frekvence modulu Arduino Uno je ftakt = 16 MHz.
Pro hodnotu shody pak dostaneme hodnotu 15624. Protože tato hodnota přesahuje hodnotu 255, museli bychom za těchto podmínek použít časovač 1 (Timer1), který je 16bitový.
- Tip: 👍
- Pro ty, kteří si v matematice při tomto výpočtu moc nevěří, existuje AVR Timer Interrupts Calculator, který nejen vypočítá hledanou hodnotu shody, ale rovnou vytvoří ukázkový program Blink s užitím přerušení časovače.
ISR rutiny přerušení časovače
Stejně jako u přerušení změny pinů názvy obslužných rutin pro přerušení časovačů již byly určeny. Každý časovač má tři ISR, dvě pro režim porovnání shody a třetí pro režim přetečení.
ISR(TIMER0_COMPA_vect)
přerušení shody časovače 0 s registrem COMPAISR(TIMER0_COMPB_vect)
přerušení shody časovače 0 s registrem COMPBISR(TIMER1_COMPA_vect)
přerušení shody časovače 1 s registrem COMPAISR(TIMER1_COMPB_vect)
přerušení shody časovače 1 s registrem COMPBISR(TIMER2_COMPA_vect)
přerušení shody časovače 2 s registrem COMPAISR(TIMER2_COMPB_vect)
přerušení shody časovače 2 s registrem COMPBISR(TIMER0_OVF_vect)
přerušení přetečení časovače 0ISR(TIMER1_OVF_vect)
přerušení přetečení časovače 1ISR(TIMER2_OVF_vect)
přerušení přetečení časovače 2
Je třeba si uvědomit, že pokud budeme používat režim CTC, můžeme při režimu shody využívat vždy jen jednu rutinu pro shodu. Je to jasné ze samotného principu. Pokud bychom nastavili první hodnotu shody na hodnotu 50 a druhou třeba na 70, je jasné, že při dosažení hodnoty 50 dojde k vynulování čítače a opětovnému čítání od nuly. Kdy by tedy mělo dojít k dosažení hodnoty 70? NIKDY!
Příklad jednoduchého použití časovače
Už toho povídání a hlavně těch registrů bylo moc. Pojďme se podívat na nějaké to praktické použití!
Následující jednoduchý příklad použití časovače nastavuje pro časovač 1 (timer1) volání funkce přerušení s frekvencí 2 Hz. Důsledkem toho bychom chtěli blikat vestavěnou LED s frekvencí 1 Hz (0,5 s nesvítí, 0,5 s svítí). V podstatě vytvoříme základní program Blink! Využijeme Arduino Uno a jeho vestavěnou LED na pinu 13.
Normální režim
#define ledPin 13 // pin vestavene LED
ISR(TIMER1_COMPA_vect) { // funkce rezim porovnani shody
OCR1A += 31249; // zvyseni OCR1A registru na dalsi hodnotu shody
digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}
void setup() {
pinMode(ledPin, OUTPUT); // nastaveni LED jako vystup
noInterrupts(); // zakazat vsechna preruseni
// nastaveni casovace Timer1
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// Nastavení hodnoty shody: 16MHz/[256 preddelicka x 31249 (hodnota shody+1)] = 2Hz
OCR1A = 31249; // hodnota shody
TCCR1B |= (1 << CS12); // nastaveni preddelicky na 256
// nebo zapis TCCR1B |= B00000100;
TIMSK1 |= (1 << OCIE1A); // Povoleni preruseni casovace pro porovnani shody na A
// nebo zapis TIMSK1 |= B00000010;
interrupts(); // povoleni všech preruseni
}
void loop() {
// hlavni nekonecna smycka je nyní prazdna
}
Vysvětlení kódu
V programu používáme normální režim shody, takže poté, co definujeme pin LED, vytvoříme také celé číslo, které bude odpovídat „první“ hodnotě porovnávacího registru OCR1A
.
Dále je zde rutina přerušení ISR, protože na Timer1 používáme režim porovnání shody s registrem OCR1A
, použijeme ISR(TIMER1_COMPA_vect)
.
V ISR měníme nejen stav LED, ale především musíme stanovit hodnotu další shody (používáme normální režim shody). K dosavadní hodnotě registru OCR1A
tedy přičteme hodnotu 31249, což je délka intervalu odpovídající potřebné délce čekání. Pokud tímto přičítáním překročíme maximální hodnotu registru OCR1A
, zapíše se jen hodnota odpovídající jeho rozsahu (0–65535). Tím se vlastně stanoví hodnota shody pro časovač po jeho přetečení.
V setup dočasně deaktivujeme všechna přerušení, abychom zabránili tomu, aby se nějaké neudálo, zatímco stále něco nastavujeme. Pak inicializujeme Timer1 a zapíšeme zmíněnou hodnotu shody. Protože chceme dosáhnout 2 Hz, určili jsme, že 31249, pokud použijeme předděličku 256. Nakonec znovu povolíme všechna přerušení.
CTC režim
#define ledPin 13 // pin vestavene LED
ISR(TIMER1_COMPA_vect) { // funkce rezim porovnani shody
digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}
void setup() {
pinMode(ledPin, OUTPUT); // nastaveni LED jako vystup
noInterrupts(); // zakazat vsechna preruseni
// nastaveni casovace Timer1
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
// Nastavení hodnoty shody: 16MHz/[256 preddelicka x 31249 (hodnota shody+1)] = 2Hz
OCR1A = 31249; // hodnota shody
TCCR1A |= (1 << WGM11); // nastaveni CTC rezimu
TCCR1B |= (1 << CS12); // nastaveni preddelicky na 256
// nebo zapis TCCR1B |= B00000100;
TIMSK1 |= (1 << OCIE1A); // Povoleni preruseni casovace pro porovnani shody na A
// nebo zapis TIMSK1 |= B00000010;
interrupts(); // povoleni všech preruseni
}
void loop() {
// hlavni nekonecna smycka je nyní prazdna
}
Vysvětlení kódu
Program v CTC režimu je téměř stejný. Jsou zde jediné dvě změny:
- z ISR rutiny zmizela změna hodnoty shody, tedy přičítání k registru
OCR1A
- v sekci nastavení parametrů přerušení (setup) přibylo nastavní CTC režimu pomocí bitu 1 (
WGM11
) v registruTCCR1A
Postupně spustíme oba programy v modulu Arduino a sledujme blikající LED. V obou případech bychom neměli zaznamenat rozdíl. Oba programy by měly dělat totéž! Až vzrušení z toho pomine, můžeme v obou programech zkusit experimentovat s různými hodnotami předděličky a hodnotou shody.
A nešlo by to jednodušeji?
Registy, režimy časovače, hodnoty shody… Zatímco programování hardwarového přerušení bylo vcelku pochopitelné, tak přerušení časovače se zatím jeví tak trochu „uživatelsky nepřátelské“! Určitě nejsme první uživatelé, kteří neskrývají své rozčarování nad obsluhou přerušení časovače. A určitě se to již někdo pokusil vyřešil elegantněji.
Odpověď zní: ANO!
Knihovna TimerInterrupt
Výrazné zjednodušení práce s přerušením časovače nám nabízí knihovna TimerInterrupt
vývojáře Khoi Hoang. Dále se podíváme, jak tuto knihovnu využít v našem ukázkovém programu Blink. Pro podrobnější poznání této knihovny a jejího užití doporučujeme navštívit přímo stránku dokumentace knihovny TimerInterrupt
na GitHub.
Knihovnu TimerInterrupt
lze do svého projektu v prostředí Arduino IDE přímo doinstalovat v manažéru knihoven (je třeba vybrat tu od správného vývojáře). Použití knihovny sice práci s přerušením zjednodušuje, ale zároveň nás trochu svazuje pravidly, která stanovil její vývojář. Kupříkladu samotné zvolení časovače se provádí v programu úvodní direktivou #define USE_TIMER_1 true
. Tím se zvolí používání časovače 1, časovač 2 by se naopak zvolil nastavením hodnoty true
pro USE_TIMER_2
. Po nastavení této direktivy lze pak ke kódu připojit knihovnu standardním způsovem #include "TimerInterrupt.h"
, která vytvoří objekt ITimer1
pro práci s přerušením časovače.
Knihovna TimerInterrupt
umožňuje u objektu ITimer1
použít pro přerušení časovače dvě základní metody:
attach
, která nastavuje spouštění rutiny přerušení po pravidelných časových intervalech (zadává se v ms).Interrupt Interval (TIMER_ INTERVAL_ MS, TimerHandler) attach
umožňuje spouštění ISR rutiny se zadanou frekvencí (zadává se v Hz).Interrupt (TIMER_ FREQ_ HZ, TimerHandler)
Za parametr TimerHandler
se u obou funkcí dosadí název funkce, která bude sloužit jako ISR rutina (funkce obsluhy přerušení).
Použití knihovny TimerInterrupt
si ukážeme v ukázkovém programu, který opět bude blikat vestavěnou LED na pinu 13. Aby bylo vidět, že skutečně dochází k užití přerušení, je do hlavní nekonečné smyčky přidán výpis textu na sériový port a pětivteřinové čekání.
#define USE_TIMER_1 true // zvoleni casovace Timer1
#define TIMER_FREQ_HZ 2 // nastaveni frekvence volani preruseni
#define ledPin 13 // pin vestavene LED
#include "TimerInterrupt.h" // knihovna z https://github.com/khoih-prog/TimerInterrupt
void TimerHandler() { // ISR funkce obsluhy preruseni
digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}
void setup() {
pinMode(ledPin, OUTPUT); // nastaveni LED jako vystup
Serial.begin(115200);
while (!Serial);
ITimer1.init(); // Inicializace casovace
if (!ITimer1.attachInterrupt(TIMER_FREQ_HZ, TimerHandler)) // start preruseni a pripojeni ISR
Serial.println("Problem s nastavenim casovace!");
}
void loop() {
Serial.println("Tady si mohu delat cokoliv!");
delay(5000);
}
Vysvětlení kódu
Samotná funkce programu je stále stejná jako u předcházejících variant. Pro nás je klíčové jen použití knihovny TimerInterrupt
(v kódu zvýrazněno tučně). Pro dosažení frekvence přerušení 2 Hz byl použit časovač 1, o kterém víme, že je 16bitový, lze jej tedy použít pro delší časové úseky časování – zde se nám hodí znalosti z předchozí části textu. Časovač 1 je zvolen nastavením hodnoty true
v úvodní direktivě. Následuje nastavení frekvence, kterou bude volána funkce obsluhy přerušení.
Po připojení knihovny TimerInterrupt
je v programu uvedena ISR funkce, zde nazvaná TimerHandler()
, ale název by mohl být libovolný (platný pro funkce).
V sekci setup je nejdříve iniciován objekt ITimer1
, který přísluší časovači 1. Následuje samotné propojení přerušení časovače s ISR funkcí TimerHandler()
pomocí příkazu ITimer1.
. Jelikož může dojít k nějakému problému při nastavení přerušení, kupříkladu k nevhodné kombinaci zvoleného časovače a požadované frekvence, vrací tento příkaz logickou návratovou hodnotu, která určuje, zda nastavení proběhlo úspěšně. Z tohoto důvodu je příkaz vnořen do podmínky, která testuje (ne)úspěšnost nastavení přerušení. A to je vše! S použitím knihovny TimerInterrupt
se obsluha přerušení časovače vrací na rozumnou úroveň používání, třeba jako jsme poznali u přerušení hardwarového na pinech 2 a 3.
Přestože se nám ve světle používání knihovny TimerInterrupt
může zdát, že celé předchozí povídání o přerušení časovače bylo zbytečné, opak je pravdou. Zmíněná knihovna nám standardní použití skutečně ulehčuje, ale pro její používání je třeba znát pravidla a omezení plynucí ze samotného řešení přerušení modulu Arduino Uno. Viděli jsme to například při volbě čítače pro frekvenci 2 Hz. Stejně si musíme uvědomit, že vše to v procesoru modulu Arduino běží na těch čítačích, předděličkách a hodnotách shody. Takže sice můžeme zadávat do funkcí obslužné knihovny co chceme, ale je otázkou, zda požadovaných parametrů ve skutečnosti lze opravdu přesně dosáhnout. Občas není špatné si zkusit, zda potřebná frekvence přerušení je vůbec na úrovni procesoru realizovatelná. A na to potřebujeme znát to předešlé! Ono pro běžné blikání LED je asi jedno, zda je dosažená frekvence 1,0000 Hz, nebo 1,0002 Hz. Ale bude to stačit v jiném projektu, který vyžaduje přesné časování? Zkrátka je občas dobré vědět, co je za knihovnou schováno a na čem stojí její základy. Dobré je to znát také proto, že vývojář Khoi Hoang další vývoj knihovny koncem ledna 2023 ukončil, takže nevíme, zda nám třeba do budoucna ty „hrátky s registry“ ještě nebudou dobré! 😉
Na závěr bychom si však měli připomenout (ještě jednou!) upozornění týkající se časovačů. Jak již bylo naznačeno, jednotlivé časovače se již v modulu Arduino používají pro různé služby. Jakmile do některého časovače zasáhneme, abychom jej nastavili pro naše služby, měli bychom si uvědomit, že tím „poškodíme“ příkazy a služby, které daný časovač již využívají. A to ať k nim přistupujeme napřímo, nebo pomocí nějaké knihovny!
Jednou z takových služeb je kupříkladu generování PWM signálu. Následující tabulka ukazuje, které služby jsou použity na jednotlivých časovačích modulu Arduino Uno:
Timer0 | Timer1 | Timer2 | |
---|---|---|---|
PWM výstup na pinech | 5, 6 | 9, 10 | 3, 11 |
Arduino funkce | delay() millis() micros() |
Servo | Tone() |
Jak vidíme, je tedy vždy dobré si promyslet, které služby ve svém projektu „musíme hodit přes palubu“, pokud budeme chtít využívat přerušení časovače. Také je dobré přitom zohlednit rozlišení jednotlivých časovačů a možnosti nastavení jejich předděličky.
Závěr
Přerušení je skvělý způsob, jak vytvářet spolehlivé projekty, které vyžadují přesné načasování nebo citlivou odezvu uživatelského rozhraní. Zatímco jsme si zde teď docela hezky hráli s přerušeními, mnoho lidí se přerušení stále (zbytečně) vyhýbá. To je jistě škoda, protože právě jsme si ukázali, že je to docela užitečné a jeho používání není ve skutečnosti zas' tak moc obtížné. Možná by se mohlo zprvu zdát obtížnější přerušení časovače, ovšem jen do doby, než si aspoň částečně osvojíme nastavení jeho registrů. Nebo lze vsadit na knihovnu, která nastavení časovačů řeší na úrovní registrů za uživatele.
Přestože využívání přerušení se řadí mezi pokročilejší programátorské techniky, určitě Vám ho doporučujeme začít začleňovat do svých dalších projektů. Pamatujte, že v některých případech není zase tam moc nezdvořilé druhé přerušovat! 😊
Text je překladem a kompilací níže uvedených článků: