The Interrupts
Riešenie pôvodného problému s doručením pizze je však veľmi jednoduché - keď doručovateľ s pizzou dorazí, zazvoní alebo zaklope na dvere zákazníka. Poprípade ho pred príchodom upozorní telefonátom. Ten sa teda nemusí starať o to, či uplynula doba doručenia alebo či náhodou neprišiel skôr. Môže sa venovať svojim dôležitejším povinnostiam a na vznik udalosti doručenia bude automaticky a hlavne včas upozornený.
Tento mechanizmus sa v svete informačných technológií nazýva prerušenie (z angl. interrupt). Koncept prerušení je vo svete mikrokontrolérov veľmi dôležitý.
Prerušenie je mechanizmus, ktorým dá prostredie mikrokontroléru vedieť, že sa práve stalo niečo dôležité. Vo všeobecnosti je možné povedať, že prerušenie je špeciálny signál pre mikrokontrolér, ktorý poslal softvér alebo hardvér a vyžaduje si okamžitú pozornosť.
Výhodou takéhoto prístupu je vo všeobecnosti zníženie zaťaženia mikrokontroléra, ktorý nemusí stále dookola testovať, či k udalosti došlo alebo ešte nie (viď polling). V porovnaní s polling-om má prerušenie lepší reakčný čas, pretože reaguje na udalosť okamžite.
Princíp prerušenia sa využíva aj v architektúre riadenej udalosťami (a zngl. event driven architecture) alebo v programovaní riadenom udalosťami (z angl. event driven programming). V objektovom programovaní je zasa tento prístup opísaný pomocou návrhového vzoru pozorovateľ (z angl. observer).
Podobne, ako keď donášková služba volá priamo zákazníkovi, aby ho informovala o tom, že je donáška pripravená, aj požiadavku o prerušenie dostane od zariadenia priamo mikrokontrolér Tým pádom môže prísť požiadavka o prerušenie kedykoľvek a prerušenie predstavuje asynchrónny spôsob komunikácie.
Hlavný rozdiel medzi polling-om a prerušením je v tom, či sa softvér sám opýta alebo hardvér sám oznámi, či došlo k predmetnej udalosti.
Interrupt Service Routine
Inštrukcie programu, ktorý beží v mikrokontroléri, sa (obyčajne) vykonávajú sekvenčne. To znamená, že po vykonaní jednej inštrukcie sa vykoná nasledujúca inštrukcia v poradí. Akonáhle však mikrokontrolér dostane požiadavku na prerušenie, pozastaví sa vykonávanie inštrukcií programu mikrokontroléra a ten spustí špeciálnu funkciu na spracovanie prerušenia, ktorá sa označuje ISR (z angli. Interrupt Service Routine). Po jej skončení sa obnoví vykonávanie prerušeného programu vykonaním ďalšej inštrukcie v poradí.

ISR funkcie nevracajú žiadnu hodnotu a nemajú žiadny parameter. Ak je teda potrebné vo vnútri ISR funkcie zmeniť stav správania aplikácie, je na to možné použiť globálne premenné. Je to vlastne jediný spôsob, pomocou ktorého je možné prenášať údaje medzi ISR a hlavným programom.
Pri tvorbe ISR je dobré dodržiavať niekoľko odporúčaní:
- ISR majú byť čo najkratšie a čo najrýchlejšie, aby zbytočne nebrzdili hlavný program alebo prípadne ďalšie prerušenia, ktoré môžu nastať.
- Je potrebné sa vo vnútri ISR vyhnúť použitiu funkcie
delay()
! - Nepoužívať sériovú komunikáciu!
- Ak programujete v jazyku C, tak globálne premenné, ktoré používate na zdieľanie údajov medzi ISR a programom, označte pomocou kvalifikátora
volatile
! Tým poviete prekladaču, že táto premenná môže byť použiteľná kdekoľvek v kóde a prekladač jej obsah vždy pri použití znovu načíta a nebude sa spoliehať na jej kópiu v registri. Zabránite tak aj prípadným optimalizáciám prekladača, vďaka ktorým by ju mohol napr. vyhodiť, pretože sa nepoužíva (v hlavnom programe). - Nevypínať ani nezapínať podporu prerušení vo vnútri ISR. V tomto prípade však existujú výnimky, ktoré budú opísané neskôr v kapitole.
Reasons to use Interrupts
Existuje veľa dôvodov, prečo prerušenia používať . Niektoré z nich sú tieto:
- To detect pin changes (eg. rotary encoders, button presses)
- Watchdog timer (eg. if nothing happens after 8 seconds, interrupt me)
- Timer interrupts - used for comparing/overflowing timers
- SPI data transfers
- I2C data transfers
- USART data transfers
- ADC conversions (analog to digital)
- EEPROM ready for use
- Flash memory ready
Types of Interrupts
Pri práci s mikrokontrolérmi je možné prerušenia rozdeliť do dvoch skupín:
- hardvérové prerušenia, známe tiež ako externé prerušenia, alebo tiež pin-change prerušenia, a
- softvérové prerušenia, ktoré sú známe ako interné prerušenia alebo tiež časovače.
Ako už názov napovedá, signál prerušenia prichádza v prípade hardvérových alebo externých prerušení z externého zariadenia. Toto zariadenie je s mikrokontrolérom priamo prepojené. Keďže sú prerušenia asynchrónne, k prerušeniu môže dôjsť kedykoľvek.
Interné prerušenia zasa referujú na čokoľvek vo vnútri mikrokontroléra, čo dokáže vyvolať prerušenie. Príkladom môžu byť napríklad časovače, pomocou ktorých je možné zabezpečiť, aby k vyvolaniu prerušenia dochádzalo pravidelne napr. každú 1 sekundu.
Interrupt Vectors in ATmega328P
V jednej chvíli môžu byť naraz vyvolané viaceré žiadosti o prerušenie a mikrokontrolér sa musí rozhodnúť, ktorá z nich bude ošetrená ako prvá. Je teda potrebné, aby mikrokontrolér vedel povedať, ktoré prerušenia majú prednosť pred inými.
Mikrokontrolér obsahuje tzv. tabuľku vektorov prerušení (viď. tabuľka XXX). Táto tabuľka sa nachádza na začiatku programovej flash pamäti a obsahuje adresy ISR funkcií jednotlivých prerušení. V ich poradí je však aj priorita - čím má prerušenie nižšiu adresu, resp. vektor prerušenia má nižšie číslo, tým má vyššiu prioritu. Z tabuľky je teda možné vidieť, že najvyššiu prioritu má prerušenie od zdroja RESET
a najnižšiu prioritu od zdroja SPM READY
.
Vector No. | Program Address | Source | Interrupt Definition |
---|---|---|---|
1 | 0x0000 |
RESET |
External Pin, Power-on Reset, Brown-out Reset and Watchdog System Reset |
2 | 0x0002 |
INT0 |
External Interrupt Request 0 |
3 | 0x0004 |
INT1 |
External Interrupt Request 1 |
4 | 0x0006 |
PCINT0 |
Pin Change Interrupt Request 0 |
5 | 0x0008 |
PCINT1 |
Pin Change Interrupt Request 1 |
6 | 0x000A |
PCINT2 |
Pin Change Interrupt Request 2 |
7 | 0x000C |
WDT |
Watchdog Time-out Interrupt |
8 | 0x000E |
TIMER2 COMPA |
Timer/Counter2 Compare Match A |
9 | 0x0010 |
TIMER2 COMPB |
Timer/Counter2 Compare Match B |
10 | 0x0012 |
TIMER2 OVF |
Timer/Counter2 Overflow |
11 | 0x0014 |
TIMER1 CAPT |
Timer/Counter1 Capture Event |
12 | 0x0016 |
TIMER1 COMPA |
Timer/Counter1 Compare Match A |
13 | 0x0018 |
TIMER1 COMPB |
Timer/Coutner1 Compare Match B |
14 | 0x001A |
TIMER1 OVF |
Timer/Counter1 Overflow |
15 | 0x001C |
TIMER0 COMPA |
Timer/Counter0 Compare Match A |
16 | 0x001E |
TIMER0 COMPB |
Timer/Counter0 Compare Match B |
17 | 0x0020 |
TIMER0 OVF |
Timer/Counter0 Overflow |
18 | 0x0022 |
SPI , STC |
SPI Serial Transfer Complete |
19 | 0x0024 |
USART , RX |
USART Rx Complete |
20 | 0x0026 |
USART , UDRE |
USART, Data Register Empty |
21 | 0x0028 |
USART , TX |
USART, Tx Complete |
22 | 0x002A |
ADC |
ADC Conversion Complete |
23 | 0x002C |
EE READY |
EEPROM Ready |
24 | 0x002E |
ANALOG COMP |
Analog Comparator |
25 | 0x0030 |
TWI |
2-wire Serial Interface |
26 | 0x0032 |
SPM READY |
Store Program Memory Ready |
: Reset and Interrupt Vectors in ATmega328 and ATmega328P
Handling the External Interrupts with Arduino
Vrátime sa teda k predchádzajúcemu zdrojovému kódu, kedy sme na detekciu pohybu využili metódu polling-u. Tentokrát sa problém pokúsime vyriešiť pomocou prerušenia. Konkrétne sa bude jednať o externé prerušenie, keďže prerušenie vyvolá PIR senzor tým, že pošle signál do mikrokontroléra vtedy, keď bude detekovať pohyb.
Ošetrenie prerušenia sa inicializuje pomocou funkcie attachInterrupt()
. Inicializácia môže prebehnúť kdekoľvek v programe, ale pokiaľ sa ošetrenie prerušenia v programe nemení, je ideálne ho inicializovať vo funkcii setup()
. Táto funkcia má tri parametre, ktoré hovoria o tom, čo všetko je potrebné vedieť pri ošetrovaní prerušenia:
- číslo prerušenia, ktoré je potrebné ošetriť,
- názov (resp. adresu) ISR funkcie, ktorá sa zavolá na jeho ošetrenie, a
- režim prerušenia, ktorý definuje, aké správanie na pin-e vyvolá prerušenie.
[!NOTE]
Miesto čísla prerušenia, ktoré má byť ošetrené, je vhodnejšie použiť číslo pin-u, na ktorom je toto prerušenie sledované. To je možné dosiahnuť pomocou funkcie
digitalPinToInterrupt(pin)
, ktorá automaticky prevedie číslo pin-u na číslo prerušenia. Týmto spôsobom je tiež možné zabezpečiť prenositeľnosť medzi viacerými platformami.
Pozrime sa najprv na to, aké správanie na pin-e vyvolá prerušenie. Prototypovacia doska Arduino UNO pozná tieto štyri režimy prerušenia:
LOW
- Prerušenie je vyvolané vtedy, keď sa na pine nachádza úroveňLOW
. Tento proces sa deje napríklad vtedy, ak je tlačidlo pripojené k digitálnemu pinu v režimeINPUT_PULLUP
a po jeho stlačení sa na pin privedie úroveňLOW
.CHANGE
- Prerušenie je vyvolané vtedy, keď dôjde k zmene úrovne pinu buď zHIGH
naLOW
alebo zLOW
naHIGH
. Tento proces sa deje napríklad pri stláčaní prepínača.RISING
- Prerušenie je vyvolané vtedy, keď dôjde k zmene úrovne zLOW
naHIGH
. Tento proces sa deje napríklad pri stlačení tlačidla.FALLING
- Prerušenie je vyvolané vtedy, keď dôjde k zmene úrovne zHIGH
naLOW
. Tento proces sa deje napríklad pri uvoľnení stlačeného tlačidla.
[!NOTE]
Prototypovacie dosky Arduino Due, Zero a MKR1000 majú navyše režim
HIGH
. K tomuto prerušeniu dôjde vždy vtedy, keď sa na pine bude nachádzať úroveňHIGH
.
Princíp jednotlivých režimov je ilustrovaný na nasledujúcom obrázku:

Na základe uvedených režimov prerušenia mikrokontroléra sa je možné stretnúť ešte s nasledujúcim rozdelením externých prerušení:
Level Interrupts - Prerušenie je vyvolané zakaždým, keď sa na vstupe objaví signál konkrétnej úrovne (
HIGH
aleboLOW
). Pri tomto type prerušenia je dobré dať pozor na to, že pri nezmenenom signále môže k prerušeniu dochádzať opakovane aj počas ošetrovania predchádzajúceho prerušenia.Edge Interrupts - Prerušenie je vyvolané vtedy, keď dôjde k zmene jednej úrovne signálu na druhú (napr. ak dôjde k zmene úrovne z
HIGH
naLOW
alebo zLOW
naHIGH
).
Každý mikrokontrolér je špecifický tým, že na zachytenie externého prerušenia z pripojeného zariadenia nie je možné použiť každý digitálny pin. Vždy je preto potrebné overiť si možnosti mikrokontroléra v jeho dokumentácii. V prípade mikrokontrolérov Arduino sa je možné orientovať pomocou tabuľky XXX.
Board | Digital Pins Usable For Interrupts |
---|---|
Uno, Nano, Mini, other 328-based | /2/, /3/ |
Uno WiFi Rev.2 | all digital pins |
Mega, Mega2560, MegaADK | /2/, /3/, /18/, /19/, /20/, /21/ |
Micro, Leonardo, other 32u4-based | /0/, /1/, /2/, /3/, /7/ |
Zero | all digital pins, except /4/ |
MKR Family boards | /0/, /1/, /4/, /5/, /6/, /7/, /8/, /9/, /A1/, /A2/ |
Due | all digital pins |
101 | all digital pins (Only pins /2/, /5/, /7/, /8/, /10/, /11/, /12/, /13/ work with CHANGE ) |
V porovnaní s mikrokontrolérom ESP8266 je na tom ATmega 328P horšie, pretože je možné použiť piny GPIO0
až GPIO15
. To umožňuje naraz sledovať až 16 rozličných prerušení.
PIR senzor je aktuálne v IoT zariadení na detekciu pohybu pripojený k pin-u č. 2. Ten podľa uvedenej tabuľky je možné použiť na zachytávanie externých prerušení.
Následne je potrebné už len poznať názvy ISR funkcií. V predchádzajúcom prípade sme používali funkcie dve a to alarm()
a idle()
. Funkcia alarm()
bola zavolaná vtedy, keď došlo k zisteniu pohybu (na výstupnom pine PIR senzoru došlo k prechodu z úrovne LOW
do úrovne HIGH
). Funkcia idle()
bola zasa vyvolaná vtedy, keď tento pohyb pominul (na výstupnom pine PIR senzoru došlo k prechodu z úrovne HIGH
na úroveň LOW
).
Vzhľadom na uvedené správanie vieme presne určiť, v akom režime prerušenia má byť zavolaná funkcia alarm()
a v akom funkcia idle()
:
alarm()
sa bude volať v režimeRISING
idle()
sa bude volať v režimeFALLING
V kóde funkcie setup()
to teda bude vyzerať nasledovne:
attachInterrupt (digitalPinToInterrupt(PIN_PIR), alarm, RISING);
attachInterrupt (digitalPinToInterrupt(PIN_PIR), idle, FALLING);
Tu však pozor! Každý mikrokontrolér obsahuje tabuľku vektorov prerušení (viď tabuľka XXX). Každý riadok v tejto tabuľke obsahuje informáciu o tom, ktorá ISR funkcia sa použije na ošetrenie ktorého zdroja prerušenia. To znamená, že na ošetrenie prerušenia na jednom vstupe sa použije len jedna ISR funkcia. V zázname sa nenachádza informácia o tom, v akom režime môže k prerušeniu dôjsť. Ak sme teda v našom prípade na jeden pin namapovali pomocou funkcie attachInterrupt()
dve ISR funkcie, tak sme vlastne prepísali ošetrenie prerušenia prvou funkciou pomocou druhej funkcie. Kód sa úspešne preloží, aj sa spustí, ale k ošetreniu prerušenia dôjde len pomocou ISR funkcie idle()
v režime FALLING
.
Aj napriek tomu je však možné prekonfigurovať ošetrenie prerušenia neskôr v programe. Akonáhle teda k prerušeniu dôjde a vyvolá sa ISR funkcia, jej súčasťou bude volanie funkcie attachInterrupt()
na nastavenie nového ošetrenia prerušenia za iných podmienok. Týmto spôsobom si budú jednotlivé ISR funkcie prehadzovať ošetrenie prerušenia navzájom.
Aby všetko pracovalo správne, je potrebné aplikovať tieto informácie a refaktorovať funkciu setup()
z predchádzajúceho riešenia. Globálnu premennú isMovement
je potrebné označiť kľúčovým slovom volatile
a zadefinovať ošetrenie prerušenia pomocou ISR funkcie alarm()
v režime RISING
(zistenie pohybu). Aktualizovaný kód funkcie setup()
sa nachádza vo výpise XXX.
#include <Arduino.h>
#define PIN_LED 8
#define PIN_PIR 2
volatile bool isMovement;
void idle();
void alarm();
void setup(){
// setup serial
Serial.begin(9600);
while(!Serial);
// set pin modes
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_PIR, INPUT);
// initial state
isMovement = false;
// enter idle state
idle();
}
Následne je potrebné upraviť ISR funkcie alarm()
a idle()
. Okrem pôvodného správania v každej z nich predefinujeme ošetrenie prerušenia:
v prípade ISR funkcie
alarm()
predefinujeme ošetrenie prerušenia pomocou funkcieidle()
v režimeFALLING
(zo stavuALARM
zariadenie prejde do stavuIDLE
, ak na pine dôjde k prechodu z úrovneHIGH
na úroveňLOW
)v prípade ISR funkcie
idle()
predefinujeme ošetrenie prerušenia pomocou funkciealarm()
v režimeRISIGN
(zo stavuIDLE
zariadenie prejde do stavuALARM
, ak na pine dôjde k prechodu z úrovneLOW
na úroveňHIGH
)
Po úprave budú tieto funkcie vyzerať takto:
void idle(){
Serial.println("> Idle State");
// update state
isMovement = false;
digitalWrite(PIN_LED, LOW);
// (re)atach interrupt
attachInterrupt(digitalPinToInterrupt(PIN_PIR), alarm, RISING);
}
void alarm(){
Serial.println("> Alarm State");
// update state
isMovement = true;
digitalWrite(PIN_LED, HIGH);
// (re)atach interrupt
attachInterrupt(digitalPinToInterrupt(PIN_PIR), idle, FALLING);
}
Nakoniec už zostáva len funkcia loop()
, ktorá bude v tomto prípade prázdna. Celý kód, ktorý bol použitý v prípade riešenia pomocou metódy pooling bol totiž presunutý do ISR funkcií. Týmto sme využili všetky výhody, ktoré ponúka mechanizmus prerušení a odstránili sme všetky nedostatky, ktoré ponúka polling. Vo funkcii loop()
teda zostáva priestor na vlastný ḱód, ktorý môže na základe globálnej premennej isMovement
vykonávať ďalšie operácie.
void loop(){
// nothing to do :-/
}
Kompletný výpis kódu sa nachádza vo výpise XXX.
#include <Arduino.h>
#define PIN_LED 8
#define PIN_PIR 2
bool isMovement;
void idle();
void alarm();
void idle(){
Serial.println("> Idle State");
// update state
isMovement = false;
digitalWrite(PIN_LED, LOW);
// (re)atach interrupt
attachInterrupt(digitalPinToInterrupt(PIN_PIR), alarm, RISING);
}
void alarm(){
Serial.println("> Alarm State");
// update state
isMovement = true;
digitalWrite(PIN_LED, HIGH);
// (re)atach interrupt
attachInterrupt(digitalPinToInterrupt(PIN_PIR), idle, FALLING);
}
void setup(){
// setup serial
Serial.begin(9600);
while(!Serial);
// set pin modes
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_PIR, INPUT);
// initial state
isMovement = false;
// enter idle state
idle();
}
void loop(){
// nothing to do :-(
}
[!NOTE]
Sériová komunikácia vo vnútri ISR.
Toto riešenie je možné ešte zjednodušiť a vrátiť sa tak k pôvodnému konceptu, kedy boli prerušenia RISING
a FALLING
zadefinované naraz. Upravíme zapojenie tak, že výstup z PIR senzoru pripojíme naraz ku pinu 2 aj 3. na pine 2 bude mikrokontrolér sledovať prerušenie typu RISING
a na pine 3 prerušenie typu FALLING
.
Upravená schéma zapojenia sa nachádza na obrázku XXX a aktualizovaná podoba kódu riešenia sa nachádza vo výpise XXX.

#include <Arduino.h>
#define PIN_LED 8
#define PIN_IDLE 2
#define PIN_ALARM 3
bool isMovement;
void alarm();
void idle(){
Serial.println("> Idle State");
// update state
isMovement = false;
digitalWrite(PIN_LED, LOW);
}
void alarm(){
Serial.println("> Alarm State");
// update state
isMovement = true;
digitalWrite(PIN_LED, HIGH);
}
void setup(){
// setup serial
Serial.begin(9600);
while(!Serial);
// set pin modes
pinMode(PIN_LED, OUTPUT);
pinMode(PIN_ALARM, INPUT);
pinMode(PIN_IDLE, INPUT);
// initial state
isMovement = false;
// atach interrupt
attachInterrupt(digitalPinToInterrupt(PIN_ALARM), alarm, RISING);
attachInterrupt(digitalPinToInterrupt(PIN_IDLE), idle, FALLING);
}
void loop(){
// nothing to do :-(
}
Týmto prístupom sa síce sprehľadnil a zjednodušil kód, ale stratila sa možnosť využiť druhý pin pre možnosť sledovania prerušení z ďalšieho zariadenia. Preto je vždy dobré takýto prístup zvážiť.
Turning Interrupts On and Off
Ošetrovanie prerušení je po zapnutí mikrokontrolérov ATmega 328P zapnuté. Automaticky sa však vypnú v okamihu, keď dôjde k ošetreniu prerušenia a pomocou ISR funkcie a opät sa automaticky zapnú, keď sa ISR funkcia ukončí.
Existujú však dve makrá, pomocou ktorých je možné ošetrovanie prerušení globálne vypínať a zapínať aj počas vykonávania ISR funkcie. Tieto makrá sú:
interrupts()
- globálne povolí ošetrovanie prerušenínoInterrupts()
- globálne zakáže ošetrovanie prerušení
Nasledujúci fragment kódu ilustruje situáciu, ak by sme chceli opätovne zapnúť prerušenia v ISR funkcii alarm()
:
void alarm(){
interrupts();
isMovement = true;
digitalWrite(PIN_LED, HIGH);
attachInterrupt(digitalPinToInterrupt(PIN_PIR), idle, FALLING);
}
[!WARNING]
Tu je však potrebné si dať veľký pozor, pretože ich nesprávnym použitím môžete dosiahnuť nepredvídateľné správanie. Napríklad povolením prerušení v ISR funkcii môžete dosiahnuť rekurzívne vykonávanie ISR funkcie pri ošetrovaní Level prerušení, ak sa úroveň signálu nezmení.
[!NOTE]
Na globálne vypínanie a zapínanie prerušení je možné použiť priamo aj AVR funkcie
sei()
acli()
. Funkciasei()
globálne prerušenia zapína a funkciacli()
globálne prerušenia vypína. Pri bližšom pohľade do knižniceArduino.h
je možné zistiť, že makráinterrupts()
anoInterrupts()
volajú práve tieto AVR funkcie:
#define interrupts() sei() #define noInterrupts() cli()
V texte však budú použité len makrá
interrupts()
anoInterrupts()
kvôli čítateľnejšiemu kódu.
Handling the Internal Interrupts with Arduino
Do kategórie interných prerušení patria časovače. Pomocou nich je možné napríklad presne načasovať spúšťanie časti programu, čo sa častokrát používa aj na vytvorenie dojmu "paralelizácie" vykonávaných úloh.
Mikrokontrolér ATmega328P obsahuje 3 časovače označené ako TIMER0
, TIMER1
a TIMER2
. TIMER0
a TIMER2
sú 8 bitové, zatiaľ čo TIMER2
je 16 bitový časovač. Ich funkcionalita však závisií od frekvencie mikrokontroléra. V prípade Arduina je použitý externý oscilátor s frekvenciou 16 MHz.
Kažý časovač môže generovať jeden alebo viac prerušení. Tieto sú:
Compare Match Interrupt - Tento typ prerušenia vznikne v situácii, keď je potrebné, aby sa časovač zastavil pri dosiahnutí konkrétnej hodnoty. Jeho aktuálna hodnota sa porovnáva s požadovanou a keď dôjde k zhode, dôjde k vyvolaniu tohto prerušenia.
Overflow Interrupt - Každý časovač používa vnútorné počítadlo, ktoré má svoj rozsah v závislosti od veľkosti použitého registra. Hodnota počítadla sa postupne zvyšuje, a keď je dosiahnutá maximálna hodnota a tá sa znova zvýši o jednotku, dôjde k pretečeniu a vyvolaniu tohto typu prerušenia.
Input Capture Interrupt - Tento typ prerušenia sa používa na zachytenie vzniknutej udalosti na pine. K prerušeniu dôjde vtedy, keď je na pine zaznamenaná konkrétna hrana signálu. Tá môže byť vzostupná, zostupná alebo akákoľvek. Časovač dokáže zaznamenať čas vzniku tejto udalosti.
S použitím týchto časovačov sa je možné stretnúť pri programovaní Arduina bežne. Časovač TIMER0
sa používa pre funkcie pracujúce s časom, ako sú delay()
, millis()
a micros()
. TIMER1
je zasa možné nájsť v knižnici Servo.h
a TIMER2
sa používa pri generovaní zvuku pomocou funkcie tone()
. Funkcia analogWrite()
využíva každý časovač, ale každý pre rozličné piny.
Prehľad vlastností dostupných časovačov na mikrokontroléri ATmega328P sa nachádza v tabuľke XXX.
Timer | Size | Range | Possible Interrupts |
---|---|---|---|
TIMER0 |
8 bits | 0-255 | Compare Match, Overflow |
TIMER1 |
16 bits | 0-65535 | Compare Match, Overflow, Input Capture |
TIMER2 |
8 bits | 0-255 | Compare Match, Overflow |
Osobitným časovačom je Watchdog. Ak sa zariadenie dostane do chybového stavu, je jeho úlohou po uplynutí časového intervalu mikrokontrolér reštartovať. Pre prácu s Watchdog časovačom je k dispozícii knižnica avr/wdt.h
.
TimerOne Library
S časovačmi je možné pracovať dvoma spôsobmi: pomocou štandardných AVR knižníc alebo pomocou knižníc tretích strán. A práve knižnica TimerOne ponúka veľmi jednoduché API, ktoré zakryje nízkoúrovňový prístup ponúkaný knižnicami AVR.
Táto knižnica poskytuje kolekciu funkcií na nastavenie 16 bitového časovača označovaného ako TIMER1
(odtiaľ názov knižnice). Pôvodným zámerom bolo vytvoriť jednoduchý a rýchly spôsob na nastavenie periódy PWM. Nakoniec však bol do knižnice zahrnutý aj prerušenie timer overflow a iné vlastnosti.
Na prácu s knižnicou stačí poznať len tieto funkcie:
Timer1.initialize(microseconds)
- Táto funkcia inicializuje prácu s časovačom a musí byť zavolaná ako prvá. Parametermicroseconds
definuje periódu časovača.Timer1.attachInterrupt(function)
- Vždy po uplynutí periódy sa spustí ISR funkcia, ktorej názvoj je uvedený v parametri.Timer1.detachInterrupt()
- Zakáže prerušenie, takže ISR funkcia sa prestane spúšťať.
Pomocou knižnice je však možné ovládať aj už spustený časovač pomocou týchto funkcií:
Timer1.start()
- Spustí časovač a začne novú periódu.Timer1.stop()
- Zastaví časovač.Timer1.restart()
- Reštartuje časovač od začiatku novej periódy.
Timers in Motion Detection Scenario
Pre ilustráciu použitia časovačov upravíme scenár vytváraného zariadenia na detekciu pohybu. K existujúcim stavom bude pridaný nový stav s názvom WATCHING
. Do neho systém prejde zo stavu IDLE
po zistení pohybu. V stave WATCHING
sa zasvieti LED dióda a spustí sa časovač. Pokiaľ sa v priebehu najbližších 10 sekúnd pohyb neukončí, systém prejde do stavu ALARM
a LED dióda sa rozbliká. Ak sa ale v priebehu týchto 10 sekúnd pohyb stratí, dióda zhasne a systém prejde späť do stavu IDLE
. Zo stavu ALARM
je možné prejsť do stavu IDLE
stlačením tlačidla. Aktualizovaný stavový diagram sa nachádza na obrázku XXX.

Keďže do systému pribudlo tlačidlo, dôjde aj k atualizácii schémy zapojenia. Tá sa nachádza na obrázku XXX.

Zmien sa dočkal aj samotný kód. Vo funkci setup()
prebehne štandardná inicializácia, ale rovnako sa v nej inicializuje časovač a nastaví sa jeho perióda na 1 sekundu. Z funkcie sa systém dostane rovno do stavu IDLE
.
Novou je funkcia watch()
. Tá v premennej countdown
nastaví 10 sekundový odpočet a nastaví ISR funkciu tick()
pre ošetrenie prerušenia časovača. Tá sa bude volať každú sekundu a zakaždým z premennej countdown
odpočíta 1. Pri dosiahnutí hodnoty 0 systém prejde do stavu ALARM
.
Kompletný výpis kódu sa nachádza vo výpise XXX.
#include <Arduino.h>
#include <TimerOne.h>
#define PIN_LED 8
#define PIN_PIR 2
#define PIN_BTN 3
volatile bool isMovement;
volatile byte countdown;
void idle();
void watch();
void alarm();
void idle(){
Serial.println("> Idle State");
isMovement = false;
digitalWrite(PIN_LED, LOW);
// reatach interrupts
detachInterrupt(digitalPinToInterrupt(PIN_BTN));
attachInterrupt(digitalPinToInterrupt(PIN_PIR), watch, RISING);
Timer1.detachInterrupt();
}
void tick(){
countdown--;
if(countdown == 0){
alarm();
}
}
void watch(){
Serial.println("> Watch State");
// update state
digitalWrite(PIN_LED, HIGH);
// reatach interrupts
attachInterrupt(digitalPinToInterrupt(PIN_PIR), idle, FALLING);
countdown = 10;
Timer1.attachInterrupt(tick);
Timer1.restart();
}
void alarm(){
Serial.println("> Alarm State");
// update state
digitalWrite(PIN_LED, HIGH);
isMovement = true;
// reatach interrupts
Timer1.detachInterrupt();
detachInterrupt(digitalPinToInterrupt(PIN_PIR));
attachInterrupt(digitalPinToInterrupt(PIN_BTN), idle, LOW);
}
void setup(){
// set pin modes
pinMode(PIN_LED, OUTPUT);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(PIN_PIR, INPUT);
pinMode(PIN_BTN, INPUT_PULLUP);
Serial.begin(9600);
while(!Serial);
// setup timer
Timer1.initialize(1 * 1000000);
// enter idle state
idle();
}
void loop(){
if (isMovement == true){
digitalWrite(PIN_LED, HIGH);
delay(1000);
digitalWrite(PIN_LED, LOW);
delay(1000);
}
}
Pros and Cons of Interrupts
Výhody:
- lepší reakčný čas v porovnaní s polling-om
- šetrenie zdrojov
Nevýhody:
- Prerušenia sa výrazne horšie ladia, pretože k prerušeniu môže dôjsť aj počas ladenia programu. A vy zrazu neviete, koľko prerušení bolo vykonaných medzi krokovaním aktuálnej časti programu.
Measuring Power when Using Interrupts
- žiadna zmena