Reklama

Externí přerušení nejen s ATtiny24 – 3. Díl Praktické ukázky se souběhem přerušení INT0 a PCINT

Úvod

Po teoretickém popisu (lze přečíst ZDE), praktických ukázkách s přerušením INT0 (lze přečíst ZDE) je tento mini seriál zakončen článkem o souběhu dvou přerušení najednou a to: INT0 a PCINT.

Popis

Když jsme si vyzkoušeli INT0, měli bychom si vyzkoušet i PCINT a rovnou si u toho ukážeme, co se bude dít, když dojde k vyvolání dvou přerušení “zároveň”. Postup nastavení přerušení jsme probrali v předešlém dílu, takže byste měli rozumět předloženému kódu. V rutinách přerušení jsme si schválně udělali malou smyčku, aby její vykonávání zabralo nějaký čas. Z toho, co za chvíli uvidíte, pochopíte, že byste rutinu měli psát co nejkratší. Čím kratší tím lepší. Neodpustím si dvě malé poznámky. ISR znamená “Interrupt Service Routine”, tedy v češtině přesně to co tu pořád opakujeme – rutina obsloužení přerušení. Slovo “volatile” před deklarací proměnné slouží k tomu, abychom překladači vysvětlili, že proměnnou “i” může měnit nějaká vnější událost. V tomto případě ji sice žádná událost měnit nebude, ale překladač ji pak nebude optimalizovat. On totiž vidí, že celý “for” cyklus i s proměnnou “i” je na nic a jinak by ji vyřadil.

 

001: // přerušení od INT0 a od PCINT7
002: #include <avr/io.h>
003: #include <avr/interrupt.h>
004: #include <avr/sleep.h>
005:
006: // rutina přerušení INT0
007: ISR(EXT_INT0_vect){
008: volatile unsigned int i;// vytvoříme delší pulz na PB0
009: PORTB |= (1<<PORTB0);// nastavujeme na PB0 log. 1
010: for(i=0;i<50;i++){i=i;}// simulujeme práci na nějakém úkolu
011: PORTB &= ~(1<<PORTB0);// PB0 do log. 0
012: }
013:
014: // rutina přerušení PCINT (0..7)
015:ISR(PCINT0_vect){
016: volatile unsigned int i;// vytvoříme delší pulz na PB1
017: PORTB |= (1<<PORTB1);// nastavujeme na PB1 log. 1
018: for(i=0;i<50;i++){i=i;}// simulujeme práci na nějakém úkolu
019: PORTB &= ~(1<<PORTB1);// PB1 do log. 0
020: }
021:
022: int main(void){
023: // nastavujeme PB0 a PB1 jako výstup
024: DDRB |= (1<<DDB0) | (1<<DDB1);
025: DDRB &= ~(1<<DDB2);// nastavujeme PB2 jako vstup (INT0)
026: DDRA &= ~(1<<DDA7);// nastavujeme PA7 jako vstup (PCINT7)
027: // nastavujeme přerušení na Nástupnou hranu
028: MCUCR |= ((1<<ISC01) | (1<<ISC00));
029: // povolujeme přerušení INT0 a PCINT skupiny 0..7
030: GIMSK |= (1<<INT0) | (1<<PCIE0);
031: PCMSK0 |= (1<<PCINT7);// ze skupiny PCINT 0..7 vybíráme pouze PCINT7 (ostatní piny nesledujeme)
032:
033: sei(); // globální povolení přerušení
034: while(1){
035: asm("nop");// nic nedělej – pro snazší ladění
036: }
037: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

 
Obr. 5. - Dvojice přerušení, INT0 a PCINT7

Obr. 1. – Dvojice přerušení, INT0 a PCINT7

 

Tentokrát jsem si k testování přerušení vzal generátor. Ne že by mačkání tlačítek bylo problematické, ale za chvíli si budeme chtít vyzkoušet, co se stane, když přijdou dvě přerušení zároveň a stisknout dvě tlačítka v přesném časovém sledu, jedno o pár mikrosekund dřív jak druhé je dosti problém. Věnujme chvíli průběhům na obr. 1. Na žluté křivce je vstupní signál na INT0 (to je nastaveno na detekci vzestupné hrany). Fialový průběh odpovídá rutině přerušení INT0, ve které tvoříme pulz na pinu PB0 (viz zdrojový kód výše). Světle modrá křivka odpovídá signálu odeslanému na PA7 (PCINT7), tmavě modrá křivka je signál na PB1. S PB1 manipulujeme v rutině přerušení od PCINT. Vzhledem k tomu, že PCINT detekuje jakoukoli změnu na vstupu, vidíme, že rutina přerušení proběhne jak po vzestupné hraně vstupního signálu tak po sestupné. Abyste se toho vyvarovali, tak by pro většinu úkolů stačilo připsat do rutiny podmínku, která zkontroluje stav pinu PA7 a pokud je například nulový, tak nic nevykoná. Tím byste si efektivně připravili detekci vzestupné hrany. Problém byste měli v případě, kdy by vstupní pulz byl natolik krátký, že by stihl změnit stav před tím, než zkontrolujete logickou hodnotu na vstupu. Ale s tím stejně nic neuděláte, takže vás to nemusí trápit. Předpokládám, že do této chvíle bylo celé chování jasné a předvídatelné. A teď se podíváme, co se stane, jestliže přerušení přijde v okamžiku, kdy se provádí rutina jiného přerušení. S výše uvedeným kódem dopadne výsledek jako na obr. 2.

 
Obr. 6. - Nevnořená dvojice přerušení, INT0 a PCINT7

Obr. 2. – Nevnořená dvojice přerušení, INT0 a PCINT7

 

Vidíte, že nejprve přichází signál pro přerušení INT0. Ihned se začíná vykonávat rutina přerušení (fialový signál). Během jeho vykonávání přichází přerušení pro PCINT7 (světle modrý průběh). K jeho okamžitému obsloužení ale nedochází. Čip nejprve dokončí rutinu od INT0. Až po jejím dokončení se začne provádět rutina PCINT (tmavě modrý signál), která už notnou dobu (1 ms) čeká na provedení. To je tím, že překladač na začátku každé rutiny zakáže globálně všechna přerušení. Teprve až po skončení celé rutiny zase přerušení povolí. Dělá to právě proto, aby nebylo možné program během vykonávání rutiny přerušení znovu přerušit. Může se vám stát, že z nějakého důvodu potřebujete mít přerušení vnořená. Tedy chcete jednomu přerušení dát možnost přerušit probíhající rutinu jiného přerušení. Pak musíte buď hned na začátek rutiny vložit globální povolení přerušení, tedy sei(), nebo nějak přesvědčit překladač, aby nevkládal globální zákaz přerušení. Druhý způsob je slušnější. Ve zdrojovém kódu vidíte ukázku jak na to.

 

001: // vnořovaná přerušení od INT0 a od PCINT7
002: #include <avr/io.h>
003: #include <avr/interrupt.h>
004: #include <avr/sleep.h>
005:
006: // rutina přerušení INT0
007: ISR(EXT_INT0_vect, ISR_NOBLOCK){
008: volatile unsigned int i;// vytvoříme delší pulz na PB0
009: PORTB |= (1<<PORTB0);// nastavujeme na PB0 log.1
010: for(i=0;i<50;i++){i=i;}// simulujeme práci na nějakém úkolu
011: PORTB &= ~(1<<PORTB0);// PB0 do log.0
012: }
013:
014: // rutina přerušení PCINT (0..7)
015: ISR(PCINT0_vect, ISR_NOBLOCK){
016: volatile unsigned int i;// vytvoříme delší pulz na PB1
017: PORTB |= (1<<PORTB1);// nastavujeme na PB1 log.1
018: for(i=0;i<50;i++){i=i;}// simulujeme práci na nějakém úkolu
019: PORTB &= ~(1<<PORTB1);// PB1 do log.0
020: }
021:
022: int main(void){
023: // nastavujeme PB0 a PB1 jako výtup
024: DDRB |= (1<<DDB0) | (1<<DDB1);
025: DDRB &= ~(1<<DDB2);// nastavujeme PB2 jako vstup (INT0)
026: DDRA &= ~(1<<DDA7);// nastavujeme PA7 jako vstup (PCINT7)
027: // nastavujeme přerušení na Nástupnou hranu
028: MCUCR |= ((1<<ISC01) | (1<<ISC00));
029: // povolujeme přerušení INT0 a PCINT skupiny 0..7
030: GIMSK |= (1<<INT0) | (1<<PCIE0);
031: PCMSK0 |= (1<<PCINT7);// ze skupiny PCINT 0..7 vybíráme pouze PCINT7 (ostatní piny nesledujeme)
032:
033: sei(); // globální povolení přerušení
034:
035: while(1){
036: asm("nop");// nic nedělej – pro snazší ladění
037: }
038: }
Vzorové zdrojové kódy slouží pouze k jednoduché demonstraci funkce, proto často obsahují globální proměnné, neobsahují většinou ukazatele, dále také neobsahují kontroly proměnných, definování nevyužitých pinů mikrokontroléru a podobné správné programátorské návyky. Proto je nutné tyto kódy brát s patřičnou rezervou.
 

 
Obr. 7. - Vnořená dvojice přerušení, INT0 a PCINT7

Obr. 3. – Vnořená dvojice přerušení, INT0 a PCINT7

 

Na obr. 3. vidíte výsledek, když v rutinách přerušení povolíme další přerušení. Žlutý průběh spustí vykonání rutiny pro INT0 (fialový průběh), než ale stihne svoji práci (projít “for” cyklus) dokončit, přijde signál pro PCINT7 (světle modrý) a program přeruší svoji činnost (tedy vykonávání rutiny pro INT0) a začne vykonávat rutinu přerušení od PCINT7 (tmavě modrý). Po jejím dokončení se vrátí ke své rozdělané práci v rutině INT0. U vnořených přerušení hrozí riziko, že se vnoříte sami do sebe, jinak řečeno, že vás přeruší to samé přerušení, které zrovna vykonáváte. A pokud se to stane několikrát po sobě, tak to nemusí dopadnout dobře. Ale to jsou jen moje domněnky :) První způsob, kdy nové přerušení čeká na dokončení posledního je bezpečný. Na druhou stranu také vás může potkat situace, kde musíte reagovat na několik přerušení, ale na jedno z nich s největší urgencí, pak možnost vnořování oceníte.

Pár poznatků na závěr

Existují ještě různé finty, jak si v malém Atmelu počet externích přerušení navýšit. Třeba modul USI jde použít tak, aby se jako externí přerušení choval. Externím přerušením také můžete spouštět AD převodník. Například v situaci, kdy potřebujete změřit hodnotu napětí v krátkém okamžiku po příchodu vnějšího signálu.

Na čem se testovalo? Jak už jsem napsal v úvodu, pro testy jsem používal čip ATtiny24A. A protože jsem jako student býval tvor spořivý, koupil jsem si atmely raději v SMD provedení, byly totiž levnější. Takže pro tyto pokusy jsem jej musel připájet na malou bastl desku. Tu si koupíte v číně za pár korun nebo si vyrobíte vlastní (doporučuji vyrábět ve větších sadách :D ). K programování můžete používat klasický laciný USBASP (v číně tak od 30 kč). Jiná volba může být i AVR Dragon (dosti drahý cca 1700 kč) nebo ATMEL-ICE (k sehnání tak od 1000 kč). Oba dva mají možnost využívat debugWire rozhraní. To vám umožní ladit program přímo v čipu, včetně všech breakpointů a podobných vychytávek. Kdo neví, ať hledá termíny “in cirtuit debug” nebo “in circuit emulation”. Dragon je ale po elektrické stránce dosti “křehký”. Fóra jsou plná hesel “dead avr dragon”. Možnosti programování by jistě samy vydaly na delší článek, ale já bych ho nemohl sepsat poctivě, neboť mám k dispozici jen tři nástroje a to by bylo jen takové polovičaté. Snad, pokud se mi časem dostane do rukou více nástrojů. Metod je totiž opravdu hodně. Bootloaderem počínaje přes ISP, JTAG, debugWire a paralelním programováním konče. Celá měření probíhala na různých napětích. Ze začátku se pracovalo s 5V napájením, některé testy jsem provedl s pracovním napětím 1.8V (zvlášť abych viděl spotřebu v režimech spánku). Nakonec jsem pracoval s 3.3V kvůli nízkým úrovním signálů z generátoru. Všechna zapojení byla natolik triviální, že jsem se neobtěžoval s kreslením schémat.

Závěr

Doufám, že jste smysl i použití externího přerušení pochopili a že vám tento mini seriál něco dal. Přirozeně bych vám doporučil provést pár vlastních pokusů, abyste získali důvěru ve vlastní kód. Většinu času při programování člověk stejně hledá chyby a je dobré umět rozpoznat, na kterou část programu se můžete spolehnout a na kterou ne. A na to přijdete nejlépe zkouškou. Zdrojový kód takhle na monitoru vypadá úplně jasně, ale když ho píšete sami, vynoří se tolik záludností, že vás to až překvapí :D

 
Obr. 8. - Ukázka zapojení

Obr. 4. – Ukázka zapojení

 
Autor: Michal Dudka
 

Následující a předchozí příspěvek v kategorii:

 
Následující: ATtiny24 analogový komparátor – 1. Díl Teoretický popis
Předchozí: Externí přerušení nejen s Attiny24 – 2. Díl Praktické ukázky s přerušením od INT0

 

[1] ATMEL. 8-bit Microcontroller with 2K/4K/8K Bytes In-System Programmable Flash ATtiny24A ATtiny44A ATtiny84A.[online] citováno 24. července 2017. Dostupné na www: http://www.atmel.com/images/doc8183.pdf
[2] MCU.cz. Přerušení AVR mcu 5.[online] citováno 24. července 2017. Dostupné na www: http://mcu.cz/comment-n1983.html
Následující a předchozí příspěvek v kategorii:

 

 
Tajned facebook
 

Za případné chyby v textu, ve zdrojovém kódě, nebo ve schématickém zapojení se omlouváme.
AUTOŘI NEBEROU ŽÁDNOU ODPOVĚDNOST ZA PŘÍPADNÉ ÚJMY NA ZDRAVÍ ČI MAJETKU.