Externer Interrupt und Atmega8

svenna80

Mitglied
31. Juli 2009
39
0
6
Sprachen
Hallo!
Ich möchte die Interrupts näher kennenlernen. Ich habe einen Atmega8 und programmiere mit C.
Habe eine Programm geschrieben mit dem man eine LED mit nur einen Taster immer wieder ein und aus schalten kann. Leider ist der Atmega dann nur mit dieser einen Aufgabe beschäftigt, weil das Programm ständig den Pin abfragt, an dem der Taster angeschlossen ist. Deswegen möchte ich es jetzt mal mit Interrupts versuchen.
Ich denke mal, dass ich für meine Anwendung einen externen Interrupt brauchte, der anspringt, wenn der Taster gedrückt wird. Aber wie sieht sowas als Programm aus? Ich hab mich schon ein wenig schlau auf mikrocontroller.net gemacht.
Da gibt es z.B. ein Register General Interrupt Mask Register (GIMSK), welches ich wahrscheinlich brauche. Wenn ich in diesem Register das Bit 7 und/oder das Bit6 setze, dann aktiviert das den Pin PD2 (Int0) und den Pin PD3 (Int1) meines Atmegas, an den ich dann einen Taster(der ja den Interrupt auslösen soll) anschließen kann.
Als nächstes gibt´s dann das General Interrupt Flag Register (GIFR). Hier muss ich wohl kein Bit setzen. Das macht der Atmega allein (Bit 7 und/oder das Bit 6), wenn eine Aktion an Int0 oder Int1 stattfindet bzw. nimmt es wieder weg, wenn die Interruptroutine abgelaufen ist. Auf microcontroller.net heißt das irgendwie flag??? Und man muss aufpassen, weil eine 1 bedeutet, dass das Flag gelöscht ist und eine 0 bedeutet, dass das Flag aktiv ist.
Dann gibt´s da noch das MCU Control Register. Für meine Anwendung dürften da primär, das Bit 3/Bit 2 und das Bit 1/Bit 0 (ISC11/ISC10 und ISC01/00) nützlich sein. Sollte ich den Taster an Int1 anschließen, dann müssten ich die Bits3 und 2 betrachten. Bit 3 bekommt eine 1 und Bit 2 bekommt auch eine 1 (für steigende Flanke: Taster wird gedrückt).
Wozu brauche ich die Makros sei() und cli() ?? Sind die wichtig?
Jetzt heißt es da auf mikrocontroller.net: „Zu den aktivierten Interrupts ist eine Funktion zu programmieren, deren Code aufgerufen wird, wenn der betreffende Interrupt auftritt (Interrupt-Handler, Interrupt-Service-Routine). Dazu existiert die Definition (ein Makro) ISR.“
So weit so gut!
Aber wie sage ich dem Atmega jetzt konkret, dass er, wenn der Taster gedrückt worden ist, er meine LED anmachen soll? Plötzlich gibt es Vektoren!?
Wenn ich jetzt so ein externen Interrupt habe, muss ich den dann vorher ganz oben im Programm mit: #define INT0_vect deklarieren? Und dann mit ISR(Vektorname) aufrufen?

Könnte mir wohl jemand ein Programm zeigen, wie das gemacht wird? Hat evtl. jemand von euch ein Link oder weiß, wo ich ein einfaches Beispielprogramm finde?

Dankeschön für die Mühe!

Gruß Sven
 
Hallo,

das alles erklären ist mir heute schon zu spät ... Gääähhhhnnn
Wenn es bis morgen noch keiner gemacht hat 8was ich nicht glaube :D )
schreib ich morgen abend oder so mal was dazu ;)

Gruß
Dino

und gute Nacht ...
 
Super! Da bin ich mal gespannt, wie sich das jetzt verhält.
Gute Nacht! :hello:
 
@Sven: Ich habe keine Ahnung von externen Interrupts, habe mich noch nie damit beschäftigt, deswegen versuche ich es so zu erklären.

ISR ist quasi die Funktion, die aufgerufen wird, wenn der Interrupt zuschlägt. ISR (Interrupt_vector), so wird das Ding definiert. Sprich, zuerst ISR, dass es sich um einen Interrupt handelt und dann in den Klammer auf was das reagieren soll, eben der Interruptvektor. Zum Beispiel, wenn über USART ein Byte empfangen wurde (ISR (USART_RXC_vect) ).

Die Makros sei() aktiviert die Interrupts global und cli() deaktiviert sie.

Grüsse
Heinrich
 
Hallo,
mache es mal kurz:
Code:
#include <avr/io.h>
#include <avr/interrupt.h>



ISR(INT0_vect)							//Interruptvektor
{
	if(PINB1==0)
	{
		PORTB= 0x01;
	}
	else
	{
		PORTB=0x00;
	}
}

int main (void)
{
	DDRB = 0xff; 						//Port B Ausgang
	DDRD = 0x00;  				 		//Port D Eingang
	PORTB = 0x00; 				 		//Port B 00 setzten
	MCUCR |= (1<<ISC01) | (1<<ISC00);           //Steigende Flanke von INT0 als auslöser
	GICR  |= (1<<INT0);					//Global Interrupt Flag für INT0
	sei();								//Interrupt aktivieren

while(1)
{
}



return 0;
}

natürlich alles ohne gewähr ;)
 
Grundsätzliches

Hallo,
grundsätzlich müssen erst einmal die Prioritätenreihenfolge und die Einsprungadressen, die je nach Prozessortype vordefiniert sind, eingehalten werden. Wie diese "Vektoren" bei den einzelnen Controllern aussehen und welche "Trivialnamen" dafür verwendet werden, steht in dem dazugehörigen *.include-File drin.
Beispielsweise Timer Overflow-Einsprungvektor. (TOV1Addr oder so ähnlich.)

Hinter der Angabe des "Vektors" erfolgt der Sprung zur Service-Routine
Das ist in der Regel ein direkter Sprungbefehl rjmp.
Dieser Zeigt auf das Unterprogramm einer InterruptServiceRoutine, die stets mit RETI (nicht mit RET) abgeschlossen werden muß.

Auch , wenn nichts mit der ISR ausgeführt wird, muß dort ein RETI rein.


RETI bedeutet Rückkehr zu dem Punkte, wo das Hauptprogramm unterbrochen wurde bei Aufruf des Interrupts.

Das Hauptprogramm kann nun in einfachsten Falle aus einer Endlosschleife bestehen.


Siehe auch hier mal:
http://www.avr-praxis.de/forum/showthread.php?t=151
Gruß von Oskar01
 
Na denn will ich auch mal was schreiben :D

Also externer Interrupt ...

Die Interrupt-Vektoren :
Das sind Speicherstellen, die angesprungen werden wenn ein Interrupt
ausgelöst wird. Wenn also zB der externe Interrupt 0 ausgelöst wird, dann
lädt der Prozessor die Adresse der Speicherstelle in den Programmzähler,
die den Namen Interrupt-Vektor INT0 hat. Das kannst du mit einem Call
vergleichen, da sich der Prozessor dabei die Adress merkt, wo er die normale
Programmarbeit unterbrochen hat. Beim ATmega 8 ist das der Vektor
2 auf der Adresse 0x001. Diese Adresse und der Vektor sind fest. Das einzige
was man ändern kann ist der Befehl, den man an diese Stelle schreibt.
Normalerweise ist das ein Jump-Befehl (Sprung) zur InterruptServiceRoutine,
die bei diesem Interrupt irgendwas ausführen soll. Am Ende der ISR wird dann
der Befehl RETI ausgeführt. Das ist eigentlich wie ein Rücksprung aus einer
Unterroutine, nur das der Befehl das Interrupt-Flag wieder zurücksetzt.
Ab dem Punkt, wenn der Prozessor den InterruptVektor angesprungen hat,
sind über das InterruptFlag alle weiteren Interrupts gesperrt. Je höher die
Nummer des Interrupt-Vektors ist, desto geringer ist seine Priorität. Das
siehst du alleine schon an der Tatsache das der RESET-Vektor mit Nr0 ganz
am Anfang steht.

Die Register :
Das ist eigentlich eine hierarchische Sache. Du hast Flags die global alle
Interrupts blockieren oder freigeben und du hast Flags die einzelne Zweige
des Interrupt-Baums freigeben oder sperren (zB USART, Timer, extern, ...)
Mit den Befehlen SEI (set) und CLI (clear) setzt oder löschst du die globale
Interrupt-Freigabe. Im Register GICR kannst Du die externen Interrupts 0
oder 1 einzeln freigeben (INT0, INT1) und im Register MCUCR kannst Du
mit den Flags ISC00/01 und ISC10/11 festlegen ob der Prozessor an den
externen Eingängen auf einen Low-Pegel, auf eine fallende oder steigende
Flanke oder einen Pegelwechsel reagieren soll und dann einen Interrupt
auslöst.

Die Anschlüsse :
An den Anschlüssen für INT0 oder INT1 kann man zB einen Taster setzen.
Dann besteht die Möglichkeit, mit einem Tastendruck einen Interrupt
auszulösen. Aber Achtung ! Der Prozessor ist schnell. So ein Taster
prellt noch gerne 2-5ms. Also wird bei einem Tastendruck für jedes prellen
ein Interrupt ausgelöst. Das heißt : Ein Tastendruck sind mehrere Interrupts.
Das belastet den Prozessor teilweise ziemlich. Also nachdenken was man
da an den Anschluß dransetzt.

Soweit sollte es erst mal reichen ... ;)

Lies dir mal die Kapitel "External Interrupts" + "Interrupt Vectors in ATmega8"
im Datenblatt des ATmega8 durch. Das sollte einiges erklären.

Gruß
Dino
 
Wertvolle Beitrage hab ihr geschrieben. Ab Donnerstag hab ich Urlaub und dann probier ich das mal in die Praxis umzusetzen!
Danke! :)
 
Hi Sven,

in der Wikipedia findest Du auch einiges über Interrupts, ihre Funktion, ihren Nutzen usw. Begrifflärungen wie Interrupt, Interrupt-Vektor, Interrupt-Service-Routine und das Thema Priotitäten wird dort beschrieben.

z.B. unter http://de.wikipedia.org/wiki/Interrupt

Wenn Du noch weitere Fragen hast dann frage einfach!

Grüße,
Markus
 
Langsam ernährt sich das Eichhörnchen...

Nabend!

Rückmeldung von mir! :)

Ich hab mir das Datenblatt angeschaut und gesehen, dass die Register teilweise ein wenig anders heißen als ich sie genannt habe!
Na gut, das liegt auch an dem Beispiel von microcontroller.net, das ich als Basis genommen habe. Dort wurde der Externe Interrupt anhand eines anderen Mikrocontroller erläutert.

Es gibt beim Atmega8 also folgende relevante Register:

- MCU Control Register (MCUCR)
Von Interesse sind in dem Register die Bits ISC01/ISC00 und ISC11/ISC10.
Sie bestimmten ob eine steigende oder fallende Flanke an INT0 bzw INT1 den Interrupt auslöst. (Konfiguration im Datenblatt nachschlagen)
C-Code:MCUCR |= (1<<ISC01) | (1<<ISC00); (für steigende Flanke an INT0)

- General Interrupt Control Register (GICR)
Aktiviert den Pin PD2 (INT0) und den Pin PD3 (INT1) des Atmegas
C-Code: GICR |= (1<<INT0) | (1<<INT1);

- General Interrupt Flag Register (GIFR)
Hier muss kein Bit gesetzt werden. Der Atmega (Bit 7 und/oder das Bit 6) setzt das Bit, wenn eine Aktion an INT0 oder INT1 stattfindet bzw. nimmt es wieder weg, wenn die Interruptroutine abgelaufen ist.
 
Hallo Sven,

so wie es aussieht hast Du alles verstanden :D
Und nun ein Interrupt ...

"Hör auf zu basteln und bring endlich den Müll runter danach kannst Du ja weitermachen" :D :D

Das wäre ein "heimischer" Interrupt von höchster Priorität :eek: :rolleyes:

Gruß
Dino
 
*lol* :D Dino,

ja, und außerdem ist der nicht maskierbar. Interrupts dieser Art müssen imm direkt durchschlagen sonst kommt der WatchDog (Frau/Freundin) und schlägt zu. Da nutzt dann die ganze Technik nix

...

Es geht doch allen gleich :p
 
OffTopic

Hi Dino, überlegs Dir gut,

ich glaube wenn ich nochmals die Wahl hätte, ich würde solo bleiben denn wegen einem Liter Milch muss man nicht gleich ne ganze Kuh kaufen :eek:

Oh je, :offtopic: , verweise mich damit zurück in meine Schranken....

Schöne Woche,
Ma
 
Funktionsfähiges Programm

Hier also ein funktionierendes Programm. Frisch aus dem Ofen (gerade erstellt). :)

Code:
//Ein-und Ausschalten einer LED mit Hilfe von zwei Tastern


#include <avr/io.h>
#include <avr/interrupt.h>



ISR(INT0_vect)     //Die Interruptroutine. Kein ";" dahinter sonst Fehlermeldung beim Compilieren! 
                               // INT0_vect ist der Interruptvektor.

{

PORTB |= (1<<PB1);                 //schaltet die LED ein.

}


ISR(INT1_vect)  //Die Interruptroutine. Kein ";" dahinter sonst Fehlermeldung beim Compilieren! 
               // INT1_vect ist der Interruptvektor.

{

PORTB &= ~ (1<<PB1);                 //schaltet die LED aus.

}

int main (void) 

{

DDRD  &= ~(1<<DDD2) | (1<<DDD3);                               //setzt PD2 (INT0) und PD3 (INT1) auf Eingang
                                                               //das sind die beiden Taster auf dem Evolutionsboard 
                                                                //(mit einem RC-Glied gegen Prellen gesichert)

DDRB |= (1<<PB1);                                              //setzt PB1 auf Ausgang. Hier ist eine LED angschlossen.

MCUCR |= (1<<ISC01) | (1<<ISC00) | (1<<ISC11) | (1<<ISC10);   //(Ausloesen des Interrupts bei steigende Flanke an INT0 und INT1)

GICR |= (1<<INT0) | (1<<INT1);                                //Aktiviert den Pin PD2 (INT0) und den Pin PD3 (INT1) des Atmega8 

sei();                                                        //einschalten der Interrupts. Setzt das Global Interrupt Enable Bit im Status Register. 

while(1)
	{

    }

return 0;                 
}
 
Hi
Hier auch mal ein kleiner Beitrag bezüglich Taster und Interrupt...
Es ist ok, wenn du dich mit Interrupt beschäftigst, allerdings hier mal eine kleine Anmerkng:
Wie Zeitkritisch kann ein Tastersignal sein? nSek, mSek ? Interrupt benutzt man, wenn sofort auf etwas reagiert werden muß. In der Regel sind das Signale aus kurzen Impulsen. Wenn da mit Polling rangegangen wird, ist's durchaus möglich, dieses Signal zu verpassen. Taster allerdings wirst du selbst bei umfangreichen Programmen nicht so kurz drücken können, das du im Polling dieses nicht mitbekommst. ( na ja, Zeitverzögerungen und Warteschleifen sowie niedriger Takt ermöglichen da schon dieses "Übersehen".)
Du solltest dein Programm folgendermaßen strukturieren:
Nach Initialisierung im Programm-Loop zuerst eine Abfrage der Eingänge. Ich gehe da so vor, das ich mir ein Byte oder 2 reserviere und da zuerst nix drin stehen habe. In der Leseroutine der Eingänge wird nun geprüft, ist eine Änderung aufgetreten, das Ereignis entsprechend behandelt, d. h. es wird festgestellt, ist die Flanke von 0 nach 1 oder umgekehrt, dieses ebenfalls einem Byte zugeführt und dann der geänderte Wert in das Vergleichsbyte geschoben.
Im Programm kann ich dann die Ereignisse bearbeiten und die Bits zurücksetzen. Bei der Bearbeitung setze ich die Bits, die für die Ausgabe notwendig sind. Am Ende meiner Programm-Loop setze ich die Ausgänge. Getreu nach dem bekannten "EVA" Prinzip:
Eingabe Verarbeitung Ausgabe.
Auch Interrupts passen da hinein, nur das solche Ereignisse nicht gepollt werden, sondern beim Ereignis nur der Wert übernommen und im Programm dann im Polling bearbeitet wird. Stichwort 'Flag'. Damit meine ich nicht das Interruptflag, sondern ein Bit, welches dem Hauptprogramm mitteilt, es war ein Interrupt, schau dir mal die Info an.
Gruß oldmax
 
Gleiche Funktion,aber ohne Interrupts

Hier jetzt ein anderes Programm (ohne Interrupts), aber gleiche Funktion.

Ich hab nicht alles verstanden, wie du das meinst? Vielleicht so:

Code:
#include <avr/io.h>



int main (void) 

{
while(1)
	{

DDRD  &= ~(1<<DDD2) | (1<<DDD3);        //setzt PD2(Taster1) und PD3(Taster2) auf Eingang. 
                                                         //(mit einem RC-Glied gegen Prellen gesichert)


DDRB |= (1<<DDB1);                       //setzt PB1 auf Ausgang. Hier ist LED1 angeschlossen.
DDRD |= (1<<DDD5);                       //setzt PD5 auf Ausgang. Hier ist LED2 angeschlossen.




if ( PIND & (1<<PIND2) )                //Bedingung:wenn Taster1 gedrückt,dann schalte LED1 ein.
                                                 //(wenn Bit an PD2 gesetzt, dann...)

{

PORTB |= (1<<PB1);

}


if ( PIND & (1<<PIND3) )                 //Bedingung:wenn Taster2 gedrückt,dann schalte LED2 aus.
                                                   //(wenn Bit an PD3 gesetzt, dann...)
{

PORTB &= ~(1<<PB1);

}





PORTD |= (1<<PD5);                      //schaltet LED2 ein. Dient nur zur Kontrolle,ob Programm bis zum Ende durchläuft.




    }

return 0;                 
}

Wenn man es so programmiert, und man dort Warteschleifen einbauen muss, dann würde das Programm die Tastereingabe unter Umständen nicht mitbekommen, so wie du auch geschrieben hast.

Gruß Sven
 
Hi
Sehe gerade, das du dieses Thema immer noch nicht zufriedenstellend gelöst hast. Da ich mit C nich viel am Hut habe, erklär ich's noch mal ohne Code, aber ich glaube, du hast es schon erkannt.
Zur Info, Bedenken, das beim Pollen ein Tastendruck übersehen werden könnte, brauchst du nicht zu haben, es sei denn, du bist sehr sehr flink....
Angenommen, du fährst mit "langsamen" 1 MHz und hast 100 Anweisungen und jede benötigt 10 Takte hast du immer noch 1000 Durchläufe pro Sekunde in deinem Programm.
Ich glaube nicht, das es Kontakte gibt, die im ms Bereich schalten, prellen ja. Und das ist bei Interrupt schwierig, wegzublenden. Du willst ja den Impuls mitbekommen, aber kein Prellen... Aber, was ist Prellen und was ist Impuls ?
Daher, Anfang der Programmschleife Eingänge lesen, Bearbeitung durchführen und am Ende die Ausgänge setzen. Ich scheibe mir immer die gelesenen Werte in ein oder zwei Byte, je nachdem was ansteht. Ebenfalls die Ausgänge werden in einem Byte gesammelt und in einer Ausgaberoutine zugeordnet.
Probier einfach mal folgendes aus:
Merkerbyte für Eingänge löschen. Eingänge lesen und Merkerbyte beschreiben. So nun hast du im gesamten Programm die Signallage vorliegen. Irgendwann prellt der Schalter nicht mehr und da du ja auf ein leeres Byte schreibst, ist der Eingang stabil. Also, ist die Reaktionszeit bei 10 oder 20 Programmdurchläufen akzeptabel, nimmst du den ersten Wechsel und wartest 10 oder 20 Durchläufe ab, bevor du das Bit als gültig bezeichnest. Oder du setzt ein Zweites Byte ein und triggerst dort eine Zeit drauf 20-30ms dürften reichen, um die Prellzeit abzuwarten. Da gibt es aber schon fertige Codeschnipsel für.
Gruß oldmax
 

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)