Vaša IP adresa: 18.118.137.243
Počet návštev: 30441

Lekcia 4: Prerušenia

Externé prerušenia

Externé prerušenia môžu byť spustené prostredníctvom vývodov INT0 a INT1 alebo ktorýmkoľvek z vývodov PCINT23 - PCINT0. Dôležité je pripomenúť, že externé prerušenie sa spustí aj v prípade, že vývody INT0, INT1, PCINT23 - PCINT0 sú nakonfigurované ako výstupy. Externé prerušenia môžeme rozdeliť do dvoch skupín.

Vývody externých prerušení ATmega328P (PDIP). Obr. 1: Vývody externých prerušení ATmega328P (PDIP).

Prvú skupinu predstavujú prerušenia vyvolané vývodmi INT0 a INT1. Prerušenie môže byť spustené nábežnou alebo dobežnou hranou alebo nízkou úrovňou napätia na pinoch INT0 a INT1. Toto nastavenie sa realizuje pomocou registra EICRA. Prerušenie vyvolané nábežnou alebo dobežnou hranou vyžaduje, aby bol aktívny oscilátor MCU. To znamená, že tento typ prerušenia nemôže byť použitý na prebudenie MCU z Power Down módu, kedy je oscilátor vypnutý kvôli redukcii spotreby MCU. Naopak, prerušenie vyvolané nízkou úrovňou na pine INT0 alebo INT1 je detegované asynchrónne (nezávisle od oscilátora MCU). To znamená, že takéto prerušenie môže byť použité na prebudenie MCU z Power Down módu.

Druhú skupinu predstavujú prerušenia, ktoré označujeme ako Pin Change Interrupt (PCI). Tento typ externého prerušenia sa spúšťa pri zmene logickej úrovne na vývodoch označených ako PCINT23 - PCINT0 (pozri Obr. 1). Vývody PCINT23 - PCINT0 sú rozdelené do troch skupín:

Názov skupinyVývody v skupine
PCI2 (Pin Change Interrupt 2)PCINT23 - PCINT16
PCI1 (Pin Change Interrupt 1)PCINT14 - PCINT8
PCI0 (Pin Change Interrupt 0)PCINT7 - PCINT0

Prerušenie od skupiny vývodov PCI2 - PCI0 sa povoľuje v registri PCICR. Následne sa v registroch PCMSK2 - PCMSK0 aktivuje extrerné prerušenie od konkrétneho vývodu v danej skupine. Príklad aktivovania externého prerušenia od pinu PCINT13 (PC5) vyzerá nasledovne:

//Vývod PCINT13 sa nachádza v skupine PCI1
PCICR|=(1<<PCIE1); //Povolím prerušenie od vývodov v skupine PCI1
PCMSK1|=(1<<PCINT13); //Aktivujem prerušnie na vývode PCINT13
sei(); //Globálne povolenie prerušení

Dôležité registre




PCICR - Register slúžiaci na povolenie prerušenia pri zmene logickej úrovne vývodu - Pin Change Interrupt (PCI). Povoľuje skupinu prerušení zahŕňajúci niekoľko vývodov.

  • Bit PCIE2 slúži na povolenie prerušnia od vývodov PCINT23..16. Vývody PCINT23..16 sa aktivujú individuálne v registri PCMSK2.
  • Bit PCIE1 slúži na povolenie prerušnia od vývodov PCINT14..8. Vývody PCINT14..8 sa aktivujú individuálne v registri PCMSK1.
  • Bit PCIE0 slúži na povolenie prerušnia od vývodov PCINT7..0. Vývody PCINT7..0 sa aktivujú individuálne v registri PCMSK0.
  • Poznámka: Prerušenie je aktívne iba v prípade, že sú globálne povolené prerušenia - sei().

PCIFR - Register obsahujúci informácie o uskutočnení externého prerušenia na skupine vývodov pomocou takzvaných príznakov (flag).

  • Logická zmena na niektorom vývode PCINT23..16 vyvolá prerušnie a bit PCIF2 sa nastaví na jednotku. V prípade, že sú globálne povolené prerušenia (sei()) a je povolené prerušenie v registri PCICR (PCIE2=1), tak MCU skočí na zodpovedajúci vektor prerušenia - PCINT2_vect. Bit PCIF2 sa nuluje automaticky vykonaním daného prerušenia alebo zápisom logickej jednotky na tento bit.
  • Logická zmena na niektorom vývode PCINT14..8 vyvolá prerušnie a bit PCIF1 sa nastaví na jednotku. V prípade, že sú globálne povolené prerušenia (sei()) a je povolené prerušenie v registri PCICR (PCIE1=1), tak MCU skočí na zodpovedajúci vektor prerušenia - PCINT1_vect. Bit PCIF1 sa nuluje automaticky vykonaním daného prerušenia alebo zápisom logickej jednotky na tento bit.
  • Logická zmena na niektorom vývode PCINT7..0 vyvolá prerušnie a bit PCIF0 sa nastaví na jednotku. V prípade, že sú globálne povolené prerušenia (sei()) a je povolené prerušenie v registri PCICR (PCIE0=1), tak MCU skočí na zodpovedajúci vektor prerušenia - PCINT0_vect. Bit PCIF0 sa nuluje automaticky vykonaním daného prerušenia alebo zápisom logickej jednotky na tento bit.

PCMSK2 - Register slúžiaci na aktiváciu externého prerušenia PCI na jednotlivých vývodoch MCU v rámci skupiny vývodov PCINT23..16.

  • Bity PCINT23..16 určujú, či je prerušenie (PCI) aktivované na danom vývode MCU.

PCMSK1 - Register slúžiaci na aktiváciu externého prerušenia PCI na jednotlivých vývodoch MCU v rámci skupiny vývodov PCINT14..8.

  • Bity PCINT14..8 určujú, či je prerušenie (PCI) aktivované na danom vývode MCU.

PCMSK0 - Register slúžiaci na aktiváciu externého prerušenia PCI na jednotlivých vývodoch MCU v rámci skupiny vývodov PCINT7..0.

  • Bity PCINT7..0 určujú, či je prerušenie (PCI) aktivované na danom vývode MCU.

Príklad č. 1 (Externé prerušenie)

Príklad demonštruje využitie tlačidla ako zdroja externého prerušenia. Program je zostavený tak, že na začiatku bliká LED0 (pripojená na PORTB0). Stlačenie tlačidla S2 (pripojené na PIND2 resp. INT0) spustí prerušenie a dôjde k zmene blikajúcej LED, t.j. bude blikať LED1. Po opätovnom stlačení tlačidla bude postupne blikať ďalšia a ďalšia LED. Keď bliká posledná LED7 a bude stlačené tlačidlo, tak začne blikať odznova LED0.

#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>//nutne pridat kniznicu preruseni
#include <util/delay.h>

uint8_t LED=0;//poradie LED,napr. LED=3 rozsvieti sa LED3 (PORTB3)

ISR(INT0_vect)//vektor/obsluha externeho prerusenia
{
//prerusenie sa spusti pri zostupnej hrane (prechod z log.1 na log.0) na INT0
cli();//globalne zakazanie preruseni (aby sa nespustilo znova prerusenie)
PORTB=0;//zhasne vsetky LED
LED++;//zmeni LED,ktora bude blikat
if (LED>=8)//LED c.8 nie je,musi byt zmenene na LED0
LED=0;//po LED7 zacne blikat LED0
_delay_ms(200);//zdrzi program 200 ms,preckanie zakmitov tlacidla
//vymaze priznak prerusenia (interrupt flag) zapisom log.1,
//inak by hned voslo do prerusenia kvoli zakmitom tlacidla
EIFR|=(1<<INTF0);
sei();//opatovne povolenie globalnych preruseni
//po dokonceni programu v preruseni,sa program vrati tam kde predtym skoncil
}

int main(void)
{
DDRB=255;//PORTB ako vystupny (pripojene LED)
PORTB=0;//vsetky LED zhasnute

DDRD=0;//PORTD ako vstupny (pripojene tlacidla)
PORTD=255;//zapnutie pull-up rezistorov na PORTD

//nastavenie prerusenia od INT0
EICRA|=(1<<ISC01);//zostupna hrana na pine PIND2 (INT0) spusti prerusenie
EIMSK|=(1<<INT0);//povolenie prerusenia od INT0

uint8_t LED_svieti = 0;
sei();//globalne povolenie preruseni

while (1)
{
if (LED_svieti) //ak LED svieti
{
PORTB&=~(1<<LED);//zhasne LED
LED_svieti=0;
}
else //ak LED nesvieti
{
PORTB|=(1<<LED); //zasvieti LED
LED_svieti=1;
}
_delay_ms(200);//pocka 0.2s
}
}

Príklad č. 2 (Prerušenie od časovača)

Tento príklad je veľmi podobný s príkladom č. 4, ktorý bol uvedený pri časovačoch. Cieľom je nechať blikať LED0 s rovnakou frekvenciou ako v predchádzjúcom príklade. Časť kódu, ktorá realizuje blikanie však nie je umiestnená v nekonečnom cykle while(1) v hlavnom programe main(), ale vykonáva sa v obsluhe prerušenia od časovača. Časovače boli podrobne prísané v Lekcii 2. V tomto príklade sa prerušenie spúšťa každých 200 us, vykoná sa požadovaný počet opakovaní (1000x) pokiaľ sa nedosiahne požadovaný čas (1000 x 200 us = 200 ms) pre zhasnutie/rozsvietenie LED.

#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>//nutne pridat kniznicu preruseni

/* globalne premenne */
uint8_t LED = 0;//poradie LED,napr. LED=3 rozsvieti sa LED3 (PORTB3)
uint8_t LED_svieti = 0;
uint16_t pocitadlo = 0;

ISR(TIMER0_COMPA_vect) //obsluha prerusenia casovaca0
{
//prerusenie sa spusta kazdych 200us
pocitadlo++;
if (pocitadlo == 1000) //ubehlo 200ms = 1000 x 200us
{
pocitadlo=0;
if (LED_svieti) //ak LED svieti
{
PORTB&=~(1<<LED);//zhasne LED
LED_svieti=0;
}
else //ak LED nesvieti
{
PORTB|=(1<<LED); //zasvieti LED
LED_svieti=1;
}
}
}

int main(void)
{
DDRB=255;//PORTB ako vystupny (pripojene LED)
PORTB=0;//vsetky LED zhasnute

/* nastavenie casovaca 0 */
TCCR0B|=(1<<CS01);//zapne casovac, predelicka 8 -> frekvencia casovaca f=1 MHz (T=1 us)
OCR0A=200;//nastavenie registra zhody (200xT=200 us)
TCCR0A|=(1<<WGM01);//automaticke nulovanie casovaca0 (TCNT0) pri dosiahnuti hodnoty v OCR0A
TIMSK0=(1<<OCIE0A); //povolenie prerusenia casovaca0
/* * * * * * * * * * * * */

sei();//globalne povolenie preruseni

while (1)
{
//blikanie LED sa realizuje v preruseni casovaca
}
}

Príklad č. 3 (Prerušenie od časovača + Externé prerušenie)

Tento príklad kombinuje funkcionalitu z Príkladu č. 1 a z Príkladu č. 2. Blikanie LED je realizované v obsluhe prerušenia časovača a navyše je zmena blikajúcej LED realizvaná pomocou tlačidla S2 v obsluhe externého prerušenia.

#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>//nutne pridat kniznicu preruseni
#include <util/delay.h>

/* globalne premenne */
uint8_t LED = 0;//poradie LED,napr. LED=3 rozsvieti sa LED3 (PORTB3)
uint8_t LED_svieti = 0;
uint16_t pocitadlo = 0;

ISR(TIMER0_COMPA_vect) //obsluha prerusenia casovaca0
{
//prerusenie sa spusta kazdych 200us
pocitadlo++;
if (pocitadlo == 1000) //ubehlo 200ms = 1000 x 200us
{
pocitadlo=0;
if (LED_svieti) //ak LED svieti
{
PORTB&=~(1<<LED);//zhasne LED
LED_svieti=0;
}
else //ak LED nesvieti
{
PORTB|=(1<<LED); //zasvieti LED
LED_svieti=1;
}
}
}

ISR(INT0_vect)//vektor/obsluha externeho prerusenia
{
//prerusenie sa spusti pri zostupnej hrane (prechod z log.1 na log.0) na INT0
cli();//globalne zakazanie preruseni (aby sa nespustilo znova prerusenie)
PORTB=0;//zhasne vsetky LED
LED++;//zmeni LED,ktora bude blikat
if (LED>=8)//LED c.8 nie je,musi byt zmenene na LED0
LED=0;//po LED7 zacne blikat LED0
_delay_ms(200);//zdrzi program 200 ms,preckanie zakmitov tlacidla
//vymaze priznak prerusenia (interrupt flag) zapisom log.1,
//inak by hned voslo do prerusenia kvoli zakmitom tlacidla
EIFR|=(1<<INTF0);
sei();//opatovne povolenie globalnych preruseni
//po dokonceni programu v preruseni,sa program vrati tam kde predtym skoncil
}

int main(void)
{
DDRB=255;//PORTB ako vystupny (pripojene LED)
PORTB=0;//vsetky LED zhasnute

/* nastavenie casovaca 0 */
TCCR0B|=(1<<CS01);//zapne casovac, predelicka 8 -> frekvencia casovaca f=1 MHz (T=1 us)
OCR0A=200;//nastavenie registra zhody (200xT=200 us)
TCCR0A|=(1<<WGM01);//automaticke nulovanie casovaca0 (TCNT0) pri dosiahnuti hodnoty v OCR0A
TIMSK0=(1<<OCIE0A); //povolenie prerusenia casovaca0
/* * * * * * * * * * * * */

DDRD=0;//PORTD ako vstupny (pripojene tlacidla)
PORTD=255;//zapnutie pull-up rezistorov na PORTD

//nastavenie prerusenia od INT0
EICRA|=(1<<ISC01);//zostupna hrana na pine PIND2 (INT0) spusti prerusenie
EIMSK|=(1<<INT0);//povolenie prerusenia od INT0

sei();//globalne povolenie preruseni

while (1)
{
//blikanie LED sa realizuje v preruseni casovaca
}
}

Príklad č. 4 (Prerušenie od USART)

V tomto príklade je ukážka ďalšieho typu prerušenia - prerušenie pri prijatí bajtu cez USART. Rozhranie USART bolo podrobne opísané v Lekcii 3. V hlavnom programe main() je v nekonečnom cykle while(1) realizované blikanie LED0. Prijatie bajtu cez rozhranie USART spustí obsluhu prerušenia bez toho, aby došlo k prerušeniu blikania LED0. V obsluhe prerušenia je do PC odoslaný rovnaký bajt ako bol prijatý, takzvané echo. Napríklad, ak z PC pošleme znak A, tak MCU nám podpovie takým istým znakom a teda PC prijme znak A.

#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>//nutne pridat kniznicu preruseni
#include <util/delay.h>

uint8_t LED=0;//poradie LED,napr. LED=3 rozsvieti sa LED3 (PORTB3)

void USART_Init()
{
// Nastavenie USART - 8 datovych bitov,jeden stop bit,ziadna parita
UBRR0=25; //nastavena rychlost 19200 Baud (z datasheetu) pri f_MCU=8MHz
UCSR0B|=(1<<TXEN0)|(1<<RXEN0); //zapnutie vysielaca a prijimaca
}

ISR(USART_RX_vect)//obsluha prerusenie od USART pri prijati bajtu
{
cli();//globalne zakazanie preruseni
while ( !(UCSR0A & (1<<RXC0)) ) ;//caka na dokoncenie prijatia
uint8_t prijatyBajt=UDR0;
//odoslanie toho isteho bajtu nazad
while ( !( UCSR0A & (1<<UDRE0)) ) ; //pocka na vyprazdnenie buffera
UDR0 = prijatyBajt; //odoslanie bajtu cez USART
sei();//opatovne povolenie globalnych preruseni
}

int main(void)
{
DDRB=255;//PORTB ako vystupny (pripojene LED)
PORTB=0;//vsetky LED zhasnute

USART_Init();//inicializacia USART
//povolenie prerusenia od USART pri prijati bajtu (USART Recieve Complete interrupt)
UCSR0B |= (1 << RXCIE0);

uint8_t LED_svieti = 0;
sei();//globalne povolenie preruseni

while (1)
{
if (LED_svieti) //ak LED svieti
{
PORTB&=~(1<<LED);//zhasne LED
LED_svieti=0;
}
else //ak LED nesvieti
{
PORTB|=(1<<LED); //zasvieti LED
LED_svieti=1;
}
_delay_ms(200);//pocka 0.2s
}
}