Arduino Interrupt
Exempel som använder interrupt
Allmänt om interrupt
Interrupt är engelska och betyder avbrott. Processorn gör ett avbrott från sin normala programkörning för att köra en speciell programkod. När denna kod är avslutad återgår processorn till att göra vad den gjorde innan.
Det här är alltså en
asynkron grej. Dvs, processorn kör koden du skrivit som vanligt ("synkron verksamhet" i sammanhanget) och när ett interrupt inträffar - antingen pga en timer-händelse ("timerinterrupt") eller elektrisk händelse utanför processorn ("signalinterrupt") - då
avbryter processorn
tillfälligt den vanliga kodkörningen för att istället köra koden som hör till interruptet. När denna kod körts färdigt, då
återvänder processorn till den vanliga körningen koden.
2 olika typer av interrupt
Det finns 2 olika kategorier av interrupt
Timer -interrupt
Vi sätter en timer och när den räknat ner - eller upp - till ett visst värde, så genereras ett interrupt. Samtidigt som interruptet genereras nollställs räknaren och börjar på nytt räkna ner - eller upp. På detta sätt kan vi skapa ett interrupt t.ex. 1 gång per sekund eller 100 gånger per sekund. Användbart i t.ex. en
klocka eller där någonting skall uppdateras eller läsas av med en viss frekvens.
I botten ligger processorns klockfrekvens, som är väldigt hög. Så vi
delar ner denna klockfrekvens flera gånger för att få en hanterbar hastighet på räknare i vår timer. Att få fram ett lämpligt värde här, dvs räkna ut inställningarna för interruptet, det handlar alltså både om att
dela ner klockfrekvensen till lämplig hastighet och att räkna ut hur stort värde vi skall räkna till eller räkna ner från. Mer detaljer längre ner.
Signal -interrupt
Dessa interrupt triggas när en signal på en ledning ändras. Så vi kan t.ex. koppla en knapp till en ingång, som anropar ett interrupt varje gång knappen trycks på. Eller t.ex. en givare eller en sensor på en ingång, som triggar ett interrupt. Denna metod kan användas för att t.ex.
läsa av i vilken position en BLDC -motor befinner sig, så att vi vet vilka elektromagneter i motorn som skall magnetiseras.
Signal -interrupt varianter: Pin change vs External interrupt
Bland signal-interrupten finns i sin tur 2 olika typer, det ena är
"pin-change-interrupt" och det andra är
"external interrupt". Den sublima skillnaden är att
pin-change-interrupt'en delar interruptvektor (interruptvektor = interruptfunktion som anropas vid interruptet) medans
external interrupt -pinnarna har egna interrupt-vektorer. Nästan vilken pinne som helst kan konfigureras som pin-change-interrupt men bara 2 stycken kan fungera som external interrupt.
Nedan ser du vilka 2 pinnar som kan användas som external interrupt (egen interruptvektor), det är INT1 och INT0, dvs de blå pinnarna 2 och 3.

Dessa kan användas som t.ex. som i exemplet där jag räknar antalet
kontaktstuds. Det väsentliga här är att interruptpinnen triggar funktionen knapptryck() nedan och inget annat. Dvs en sådan här pinne "external interrupt", den triggar sin egen funktion. Det finns 2 stycken dito att tillgå, så du kan skapa 2 stycken sådana här funktioner.
volatile int antal_knapptryck = 0;
void setup()
{
Serial.begin(9600);
pinMode(2, INPUT);
digitalWrite(2, HIGH);
attachInterrupt(0, knapptryck, FALLING);
}
void loop()
{
Serial.println(antal_knapptryck);
delay(1000);
}
void knapptryck()
{
antal_knapptryck++;
}
Pin-change interrupt
Pin-change delar interruptvektor. Det betyder att vi kan konfigurera en eller flera pinnar att generera ett interrupt och när signalen förändras (låg blir hög eller hög blir låg) då anropas sedan en gemensam funktion för dessa pinnar. Denna funktion/interruptvektor får sedan klura ut själv exakt vilket pinne det var som orsakade interruptet, ifall det behövs.
Vi kan ta exemplet där vi låter en
BLDC -motors hallsensorer trigga interrupt.
Vi sätter upp så att pinne 8,9,10 genererar ett interrupt vid förändring. Oavsett vilken av dessa pinnar 8,9 eller 10 som genererar interruptet så anropas samma funktion här. Denna funktion får sedan klura ut vad som egentligen hände här. I detta fall så tar interruptfunktionen samtliga 3 signaler 8,9,10 och använder dem tillsammans som en pekare i en tabell - eftersom det i just detta fall avgör vilken lindning i BLDC -motorn som skall elektrifieras. Men man kan tänka sig att 8,9,10 också var 3 stycken knappar eller vad som helst. Då hade interruptfunktionen fått klura ut själv exakt vilken pinne som genererade interruptet. Detta kan göras t.ex. genom att lagra tidigare tillstånd och jämföra vid varje interrupt.
// testprogram för hallgivare och styrsignaler
byte clockwise[8]=
{
B000000,
B000110,
B011000,
B010010,
B100001,
B100100,
B001001,
B000000
};
byte counterClockwise[8]=
{
B000000,
B001001,
B100100,
B100001,
B010010,
B011000,
B000110,
B000000
};
byte phase = 0;
byte lstPhase =0;
void setup()
{
pinMode( 8, INPUT_PULLUP); // HC Grön kabel
pinMode( 9, INPUT_PULLUP); // HB Blå kabel
pinMode(10, INPUT_PULLUP); // HA Gul kabel
// pin change på pinne 8,9,10
PCICR = 1;
PCMSK0 = 7;
Serial.begin(9600);
}
// pin change på 8,9,10 anropas nedan ISR
ISR (PCINT0_vect)
{
phase = PINB & 7;
}
String toBinary(int n, int size)
{
String r;
while(n!=0) {r=(n%2==0 ?"0":"1")+r; n/=2;}
while(r.length()<size)
r="0"+r;
return r;
}
void loop()
{
if(phase!=lstPhase)
{
Serial.print("Hall=(");
Serial.print(phase);
Serial.print(")=");
Serial.print(toBinary(phase,3));
Serial.print(" Medurs=");
Serial.print(toBinary(clockwise[phase],6));
Serial.print(" Moturs=");
Serial.print(toBinary(counterClockwise[phase],6));
Serial.println();
lstPhase = phase;
}
}
Okej, då dyker vi lite djupare. Nedan handlar huvudsakligen om Atmega 328.
Databladet för Atmega 328 finns här.
Timer -interrupt
Denna typ av interrupt konfigurerar du så att de sker med ett visst tidsintervall. Varje gång avbrottet görs anropas en speciell funktion som du skrivit, när funktionen är färdig så återgår processorn till vad den gjorde tidigare.
Timer -interrupt är användbart t.ex. för att läsa en signal med ett visst tidsintervall eller uppdatera en skärm med en viss frekvens. Eller för att hålla eller mäta tiden i någon applikation.
Atmegan har 3 stycken timer -räknare, timer0 (8 bitar), timer1 (16 bitar) och timer2 (8 bitar). Dessa räknare tickar upp varje gång timer -klockan ger signal så ska ske. Dessa räknare fungerar nästan på samma sätt. Timer1 är lite mer komplex men jag går inte in på det nu.
CTC Mode, Clear Timer on Compare Match
Ett CTC timer interrupt sker när en räknare har nått ett på förhand inställt värde i
compare match registren OCR0A, OCR1A eller OCR2A. När detta värde nåtts nollas även räknaren och processen börjar om där den räknar upp till det förvalda värdet varpå ett nytt interrupt äger rum osv.
Genom att räkna ut ett lämpligt
compare match value samt ställa in hastigheten på timer-klockan som räknar upp dessa 3 timers (vilket görs med TCCR0B, TCCR1B eller TCCR2B), så kan vi skapa ett system med timer-interrupt med de exakta tids-intervall vi önskar.
Prescaler
Atmegan kör på en klockfrekvens som är 16 Mhz. Det är en fasligt hög hastighet. Vi vill justera timerklockan (som räknar upp timer0, timer1, timer2) till ett lägre värde. Detta gör vi genom att sätta en prescaler. Vi delar alltså ner klockfrekvensen med en prescaler (pre=först, scaler=skala). Vi sätter prescalern i Timer/Counter Control Registrets 3 första bitar; TCCR0B, TCCR1B eller TCCR2B beroende på timer.
Här nedan ser du att vi kan sätta de 3 första bitarna,
Clock Select Bits.

Följande alternativ har vi. Dvs vi kan i praktiken välja mellan att dividera klockfrekvensen med 1, 8, 64, 256 eller 1024. Vi kan också räkna upp timern med en extern klockkälla. Det är inte aktuellt för oss just nu.

Timerhastigheten i Hz = Klockfrekvens (som är 16MHz) / prescaler
Vår timer räknare räknas alltså upp med Timerhastigheten här ovan. Om vi väljer en prescaler på 1024 så får vi en Timerhastighet enligt.
Timerhastigheten i Hz = Klockfrekvens (som är 16MHz) / 1024 = 16 KHz
Detta betyder vidare att om vi väljer ett "compare match value" på 255 så kommer vi får ett interrupt 16.000 / 255 = cirka 60 gånger per sekund. Vi kan skriva en ekvation för hela uttrycket.
Interrupfrekvens (Hz) = 16 MHz / (prescaler * (compare match register + 1))
Man kan såklart möblera om denna ekvation och får då
compare match register = ( 16MHz/ (prescaler * önskad interruptfrekvens)) - 1
Säg att vi vill ha ett timerinterrupt 100 gånger per sekund för att läsa av en signal. Vi väljer t.ex. en prescaler till 1024 och kan då räkna ut vårt compare match register -värde enligt.
compare match register = ( 16MHz/ (1024 * 100)) - 1 = 155
Detta värde får plats i alla timer's så du kan välja timer0, timer1 eller timer2 för detta. Men säg att du önskar ett interrupt per sekund för att läsa av en avståndsmätare eller något annat.
compare match register = ( 16MHz/ (1024 * 1)) - 1 = 15624
Detta värde får inte plats i timer0 (8 biter dvs 0-255) eller timer2 (8 biter dvs 0-255) men däremot i timer 1 (16 bitar dvs 0-65025).
Sammanfattning
För att sätta upp ett timer -interrupt behöver vi välja en lämplig prescaler (1, 8, 64, 256 eller 1024) och räkna ut ett lämpligt värde till vårt compare match register. Mer konkret gör vi såhär:
Vi börjar med att nolla registren vi arbetar med så att vi vet vilka flaggor vi sätter och inte.
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
Jag stoppar in vårt uträknade compare match -värde i OCR1A. Jag vill ha 1 interrupt per sekund.
OCR1A = 15624;
Sedan sätter vi prescaler, i detta fall 1024 för 1 interrupt per sekund. Om du tittar i tabellen ovan ser du att prescaler för 1024 innebär att vi ska sätta de 3 minst signifikata bitarna i TCCR1B = 101. Decimala siffran för 101 är 5.
Det finns olika sätt att få dit dessa bitar. Vi kan skriva så här:
TCCR1B |= 5;
Vi kan skriva såhär också, vissa tycker det är tydligare:
TCCR1B |= (1 << 2) | (1 << 0);
Eller så kan vi utnyttja de makron som är definierade för de olika bitpositionerna. AVR har kodat en väldig massa makron i sina include -filer, t.ex. kan man i avr\include\avr i filen för atmega328 (iom328p.h, sök på datorn så hittar du den) hitta följande makron
#define CS10 0
#define CS11 1
#define CS12 2
#define WGM12 3
#define WGM13 4
#define ICES1 6
#define ICNC1 7
Vi kan alltså sätta bit 0 och 2 i TCCR1B genom att skriva som nedan.
TCCR1B |= (1 << CS12) | (1 << CS10);
När detta är gjort slår vi också på enable timer compare interrupt genom att sätta bit 1 i TIMSK1 -registret. Vi kan som ovan beskrivet göra detta på flera sätt t.ex. använda makrot i filen iom328p.h
TIMSK1 |= (1 << OCIE1A);
Aktiverar CTC mode genom att sätta bit 1 i TCCR1A
TCCR1A |= (1 << WGM11);
När ovan är gjort kan vi förvänta oss att en ISR anropas, nämligen nedan. Så allt som återstår är lite kod i denna rutin
ISR(TIMER1_COMPA_vect)
{
}
Exempel. Ett interrupt med 1 Hz, dvs 1 gång per sekund.
void setup()
{
cli(); // innan vi ändrar något stäng interrupt
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS10 and CS12 bits for 1024 prescaler
TCCR1B |= (1 << CS12) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
sei(); // när vi är färdiga dra igång interrupt
}
ISR(TIMER1_COMPA_vect)
{
// anropas en gång per sekund
}
Det som händer här ovan är alltså att TCNT1 räknas upp och när värdet når OCR1A, dvs 15624, vilket sker efter exakt en sekund, så triggas ett interrupt samtidigt som TCNT1 nollas och uppräkningen börjar om.
Exakt i vilket tempo TCNT1 räknas upp bestäms alltså av prescalern. Om prescaler är 1024, så motsvaras 15624 av 1 sekund.
Är det oklart hur prescaler -bitarna sätts, titta nedan (från
ATMEGA328 datablad).
Overflow Interrupt
Istället för att TCNT1 räknar upp till OCR1A med ett visst tempo (som avgörs av prescaler), så finns en mode där man istället sätter TCNT1 till ett värde som sedan istället
räknas ner och när räknaren når noll så triggas ett interrupt. Dvs, helt omvänt.

I min
stegmotorstyrning med h-brygga har jag använt detta för att enkelt kunna kontrollera tiden mellan interrupt via en potentiometer. Se nedan kod.
class ElstStepper
{
public:
ElstStepper(int p1, int p2, int p3, int p4)
{
pinMode(pin1 = p1, OUTPUT);
pinMode(pin2 = p2, OUTPUT);
pinMode(pin3 = p3, OUTPUT);
pinMode(pin4 = p4, OUTPUT);
lastStep = 0;
}
void StepMotor(int dir)
{
digitalWrite(pin1,pin1_phase[lastStep]);
digitalWrite(pin2,pin2_phase[lastStep]);
digitalWrite(pin3,pin3_phase[lastStep]);
digitalWrite(pin4,pin4_phase[lastStep]);
lastStep+=dir;
if(lastStep<0)
lastStep =3;
lastStep%=4;
}
private:
int lastStep;
int pin1;
int pin2;
int pin3;
int pin4;
int pin1_phase[4]={HIGH,LOW,LOW,HIGH};
int pin2_phase[4]={LOW,HIGH,HIGH,LOW};
int pin3_phase[4]={HIGH,HIGH,LOW,LOW};
int pin4_phase[4]={LOW,LOW,HIGH,HIGH};
};
long val;
int stepSpeed = 1;
int stepDir = 1;
ElstStepper stepmotor( 8,9,10,11);
void setup()
{
//Serial.begin(9600);
// initialize timer1
noInterrupts();
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = stepSpeed;
TCCR1B |= (1 << CS11); // prescaler = 8
TIMSK1 |= (1 << TOIE1); // overflow interrupt enable
interrupts();
}
ISR(TIMER1_OVF_vect)
{
TCNT1 = stepSpeed;
stepmotor.StepMotor(stepDir);
}
void loop()
{
val=analogRead(0) -520;
if(val>510)
val = 510;
if(val<-510)
val=-510;
stepSpeed = abs(val) * 75;
if(val>0)
stepDir=1;
else
stepDir=-1;
//delay(100);
//Serial.println(val);
}
Det som händer här ovan är alltså att jag läser värdet från en potentiometer, kopplad som en spänningsdelare så att jag får ett värde 0-1023 på en analog ingång. När potentiometern står i mitten så står stegmotorn stilla, är tanken. När jag vrider åt ena eller andra hållet så skall stegmotorn snurra åt detta hål med stigande hastighet ju mer jag vrider. Jag sätter alltså
stepSpeed till ett värde mellan 0 och max cirka 38000 (dvs 510 x 75 = 38250). Detta blir räknaren i interrupt-rutinen som räknas ner med en hastighet som är ungefär klockfrekvensen dividerat med prescalern.
Jag använder en prescaler på 8 och får då cirka 50 anrop per sekund som lägst.
Lite repetition
Så med en prescaler på 256 så får vi alltså 62500 timer-ticks per sekund.
16000000/256 = 62500
Detta betyder alltså att med prescaler på 256 så räknas räknaren upp till 62500 på en sekund eller 31000 på en halv sekund eller till 6250 på en tiondels sekund.
Med en prescaler på 8 får vi 2 miljoner timer-tics per sekund.
16000000/8 = 62500 = 2000000
Detta betyder alltså att med prescaler på 8 så räknas räknaren upp till 2000000 på en sekund (går inte räkna till 2 miljoner) eller 200000 på en tiondels sekund (går inte heller) eller till 20000 på en hundradels sekund (går utmärkt med timer1), dvs 100 anrop per sekund.
Så med en prescaler på 8 och du räknar upp till ca 40000 så anropas alltså interrupt-funktionen 50 gånger per sekund.
Pin-Change -interrupt
Kapitel 17 (EXINT - External Interrupts) i manualen (se ovan länk) handlar om det vi vill göra.
Vi behöver göra 3 saker här. Det första är att aktivera pin change interrupt (PCICR). Det andra är att välja vilka pinnar detta skall angå (PCMSK0 för port B, PCMSK1 för port C, PCMSK2 för port D). Sist men inte minst behövs förstås en interruptfunktion.
1. Aktivera Pin Change Interrupt, PCICR

Observera att PCINT[...] avser pinnarna på själva kretsen. Du får alltså snegla på den här bilden...

...och sedan översätta till denna (eller omvänt)...

Dvs:
PCICR |= 0b00000001; // Aktivera PB0-PB5
PCICR |= 0b00000010; // Aktivera PC0-PC6
PCICR |= 0b00000100; // Aktivera PD0-PD7
PCICR |= 0b00000111; // Aktivera alla ovan
2. Sätt Pin Change Mask för pinnarna ifråga
I manualen hittar vi i bakvänd ordning följande register i vilket vi skall sätta flaggor för de pinnar vi vill ha våra pin change -interrupt på.

Dvs, ovan kokar ner till följande exempel.
PCMSK0 |= 0b00000010; // aktivera pinne PB1
PCMSK1 |= 0b00010000; // aktivera pinne PC4
PCMSK2 |= 0b01000010; //aktivera PD1 & PD6
Skriv en interruptfunktion
Beroende på vilka pinnar vi valt att använda från de 3 portarna så behöver vi skriva kod i en eller flera av nedanstående ISR -funktioner. Observera att vi i dessa interrupt -funktioner måste testa vilken av pinnarna som genererade interruptet. Det är som tidigare nämt detta som är skillnaden mellan "pin-change -interrupt" och "external interrupt". External interrupt har enga interruptfunktioner (vektorer, avbrottsvektorer) och då vet vi alltid vad som hände när en sådan funktion anropas. Med pin-change måste vi ta reda på vilken pinne som orsakade interruptet själv. Men säg att man behöver 3 interrupt, då kan man iofs lägga dessa på var sin port (dvs en på Port B, en på Port C och en på Port D) och isåfall vet vi ju nedan vem som orsakade interruptet.
ISR(PCINT0_vect) // Port B
{
// kod som hanterar pin change interrupt
// på port B
}
ISR(PCINT1_vect) // Port C
{
// kod som hanterar pin change interrupt
// på port C
}
ISR(PCINT2_vect) // Port D
{
// kod som hanterar pin change interrupt
// på port C
}
BLDC -motorstyrning
Ett bra exempel, där pinchange -interrupt gör jobbet, är exemplet om vi vill kommutera en borstlös motor med hallsensorer. Vi vill då läsa av hallsensorerna när värdet från dessa ändras och beroende på hallsensorernas status så vill vi elektrifiera vissa kopparlindningar i motorn (EC Motor = Elektroniskt kommuterad motor). Det är detta som är den elektroniska kommutering som får en borstlös motor att snurra. Titta på nedan kod, som står beskrivet utförligt under hur man skapar elektronik för en BLDC -motor.
// driva borstlös motor med hallgivare
byte clockwise[8]=
{
B000000,
B000110,
B011000,
B010010,
B100001,
B100100,
B001001,
B000000
};
byte counterClockwise[8]=
{
B000000,
B001001,
B100100,
B100001,
B010010,
B011000,
B000110,
B000000
};
byte phase = 0;
byte lstPhase =0;
void setup()
{
pinMode( 0, OUTPUT);
pinMode( 1, OUTPUT);
pinMode( 2, OUTPUT); // LIC Grå = PD2
pinMode( 3, OUTPUT); // HIC Grön = PD3
pinMode( 4, OUTPUT); // LIB Lila = PD4
pinMode( 5, OUTPUT); // HIB Blå = PD5
pinMode( 6, OUTPUT); // LIA Orange = PD6
pinMode( 7, OUTPUT); // HIA Gul = PD7
pinMode( 8, INPUT_PULLUP); // HC Grön kabel
pinMode( 9, INPUT_PULLUP); // HB Blå kabel
pinMode(10, INPUT_PULLUP); // HA Gul kabel
// pin change på pinne 8,9,10
PCICR = 1;
PCMSK0 = 7;
PORTD = clockwise[PINB & 7]<<2; // startläge
Serial.begin(9600);
}
// pin change på 8,9,10 anropas nedan ISR
ISR (PCINT0_vect)
{
phase = PINB & 7;
PORTD = clockwise[phase]<<2;
}
String toBinary(int n, int size)
{
String r;
while(n!=0) {r=(n%2==0 ?"0":"1")+r; n/=2;}
while(r.length()<size)
r="0"+r;
return r;
}
void loop()
{
if(phase!=lstPhase)
{
Serial.print("Hall=(");
Serial.print(phase);
Serial.print(")=");
Serial.print(toBinary(phase,3));
Serial.print(" Medurs=");
Serial.print(toBinary(clockwise[phase],6));
Serial.print(" Moturs=");
Serial.print(toBinary(counterClockwise[phase],6));
Serial.println();
lstPhase = phase;
}
}
Det som gör jobbet här ovan är alltså att vi konfigurerar pinne 8,9 och 10 att generera ett interrupt när någon förändring sker på dessa pinnar. När en förändring skett så läser vi av signalerna från 8,9 och 10 och beroende på vad dessa signaler är så elektrifierar vi de lindningar i motorn vi önskar. Detta är pinchange -interrupt at work i ett mycket bra exempel.
External interrupts
För External Interrupts kan man använda följande funktion vilket gör allt enklare.
attachInterrupt(pin, ISR, mode)
Pinne är den pinne som det är önskvärt bevaka. ISR är den funktion som skall anropas och mode är vad som skall trigga interruptet signalmässigt.
LOW triggar ISR interruptet när pinnen är låg
CHANGE triggar ISR när pinnens värde skiftar.
RISING triggar ISR skiftar från LOW till HIGH.
FALLING triggar ISR skiftar från HIGH till LOW.
Exempel. När jag räknar kontaktstuds så skriver jag koden såhär.
volatile int antal_knapptryck = 0;
void setup()
{
Serial.begin(9600);
pinMode(2, INPUT);
digitalWrite(2, HIGH);
attachInterrupt(0, knapptryck, FALLING);
}
void loop()
{
Serial.println(antal_knapptryck);
delay(1000);
}
void knapptryck()
{
antal_knapptryck++;
}
ATmega328P vektorer
| Interrupt | Namn |
2 | External Interrupt Request 0 | INT0_vect |
3 | External Interrupt Request 1 | INT1_vect |
4 | Pin Change Interrupt Request 0 | PCINT0_vect |
5 | Pin Change Interrupt Request 1 | PCINT1_vect |
6 | Pin Change Interrupt Request 2 | PCINT2_vect |
7 | Watchdog Time-out Interrupt | WDT_vect |
8 | Timer/Counter2 Compare Match A | TIMER2_COMPA_vect |
9 | Timer/Counter2 Compare Match B | TIMER2_COMPB_vect |
10 | Timer/Counter2 Overflow | TIMER2_OVF_vect |
11 | Timer/Counter1 Capture Event | TIMER1_CAPT_vect |
12 | Timer/Counter1 Compare Match A | TIMER1_COMPA_vect |
13 | Timer/Counter1 Compare Match B | TIMER1_COMPB_vect |
14 | Timer/Counter1 Overflow | TIMER1_OVF_vect |
15 | Timer/Counter0 Compare Match A | TIMER0_COMPA_vect |
16 | Timer/Counter0 Compare Match B | TIMER0_COMPB_vect |
17 | Timer/Counter0 Overflow | TIMER0_OVF_vect |
18 | SPI Serial Transfer Complete | SPI_STC_vect |
19 | USART Rx Complete | USART_RX_vect |
20 | USART Data Register Empty | USART_UDRE_vect |
21 | USART Tx Complete | USART_TX_vect |
22 | ADC Conversion Complete | ADC_vect |
23 | EEPROM Ready | EE_READY_vect |
24 | Analog Comparator | ANALOG_COMP_vect |
25 | Two-wire Serial Interface | TWI_vect |
26 | Store Program Memory Read | SPM_READY_vect |
Lycka till!
(sidan kommer antaligen byggas på om jag kommer på mer som kanske är användbart :))