heim - Internet-Setup
Unterbrechungen in atmega8. Trainingskurs

Einer der Vorteile des ATmega8-Mikrocontrollers ist seine große Auswahl an verschiedenen Interrupts.

Unterbrechen ist ein Ereignis, bei dessen Eintreten die Ausführung des Hauptprogramms unterbrochen und eine Funktion aufgerufen wird, die einen Interrupt eines bestimmten Typs behandelt.

Interrupts werden in interne und externe unterteilt. Zu den Quellen interner Interrupts gehören integrierte Mikrocontrollermodule (Timer, USART-Transceiver usw.). Externe Interrupts treten auf, wenn externe Signale an den Mikrocontroller-Pins ankommen (z. B. Signale an den RESET- und INT-Pins). Die Art der Signale, die zum Auftreten eines Interrupts führen, wird im Steuerregister eingestellt MCUCR, insbesondere in den Bits - ISC00 (Bit 0) und ISC01 (Bit 1) für den Eingang INT 0; ISC10 (Bit2) und ISC11 (Bit3) für INT1-Eingang.

Im ATmega8-Mikrocontroller hat jeder Interrupt seinen eigenen Interrupt-Vektor(Adresse am Anfang des Programmspeicherbereichs, in dem der Befehl zum Sprung zur angegebenen Interrupt-Routine gespeichert ist). In Mega8 haben alle Interrupts die gleiche Priorität. Treten mehrere Interrupts gleichzeitig auf, wird der Interrupt mit der niedrigeren Vektornummer zuerst bearbeitet.

Interrupt-Vektoren in Atmega8

Adresse Quelle unterbrechen Beschreibung
0x0000 ZURÜCKSETZEN Signal zurücksetzen
0x0001 INT0 Externe Interrupt-Anforderung am INT0-Eingang
0x0002 INT1 Externe Interrupt-Anforderung am INT1-Eingang
0x0003 T/C1 Timer-Erfassung T/C1
0x0004 T/C1 T/C1-Timer abgleichen, Register A vergleichen
0x0005 T/C1 Vergleichen Sie es mit dem Vergleichsregister B des Timers T/C1
0x0006 T/C1 T/C1-Zählerüberlauf
0x0007 T/C0 Überlauf des T/C0-Zählers
0x0008 SPI SPI-Datenübertragung abgeschlossen
0x0009 UART Der UART-Transceiver hat den Datenempfang abgeschlossen.
0x000A UART Das UART-Datenregister ist leer
0x000B UART Die Datenübertragung durch den UART-Transceiver ist abgeschlossen
0x000C ANA_COMP Unterbrechung durch Analogkomparator

Unterbrechungsmanagement

4 Register sind für die Verwaltung von Interrupts in ATmega8 verantwortlich:

GIMSK(auch bekannt als GICR) – Interrupts basierend auf Signalen an den Eingängen INT0, INT1 verbieten/aktivieren

GIFR- Verwaltung aller externen Interrupts

TIMSK, TIFR- Verwaltung von Unterbrechungen durch Timer/Zähler

Registrieren GIMSK(GICR)

INTFx=1: Am INTx-Eingang ist ein Interrupt aufgetreten. Beim Eintritt in die Interrupt-Handling-Routine wird INTFx automatisch auf den Protokollstatus zurückgesetzt. 0

Registrieren TIMSK

7 6 5 4 3 2 1 0
TOIE1
OCIE1A
OCIE1B
-
TICIE
-
TOIE0
-

TOIE1=1: T/C1-Überlauf-Interrupt aktiviert

OCIE1A=1: Interrupt, wenn das Vergleichsregister A mit dem Inhalt des aktivierten Zählers T/C1 übereinstimmt

OCIE1B=1: Interrupt, wenn das Vergleichsregister B mit dem Inhalt des aktivierten Zählers T/C1 übereinstimmt

TICIE=1: Interrupt aktiviert, wenn die Erfassungsbedingung erfüllt ist

TOIE0=1: T/C0-Überlauf-Interrupt aktiviert

Registrieren TIFR

7 6 5 4 3 2 1 0
TOV1
OCF1A
OCF1B
-
ICF1
-
TOV0
-

TOV1=1: T/C1-Überlauf aufgetreten

OCF1A=1: Vergleichsregister A stimmt mit dem zulässigen Inhalt des Zählers T/C1 überein

OCF1B=1: Vergleichsregister B stimmt mit dem zulässigen Inhalt des Zählers T/C1 überein

ICF=1: Erfassungsbedingungen erfüllt

TOV0=1: T/C0-Überlauf aufgetreten

Beim Aufrufen der Interrupt-Handhabungsunterroutine wird das dem Interrupt entsprechende TIFR-Registerflag automatisch auf den Protokollstatus zurückgesetzt. 0

Interrupts funktionieren nur, wenn allgemeine Interrupts im SREG-Statusregister aktiviert sind (Bit 7 = 1). Wenn ein Interrupt auftritt, wird dieses Bit automatisch auf 0 zurückgesetzt, wodurch nachfolgende Interrupts deaktiviert werden.

In diesem Beispiel ist der INT0-Pin im Pull-Up-Eingangsmodus aktiviert. Wenn der Pin mithilfe einer Taste mit Masse kurzgeschlossen wird, wird an ihm eine logische 0 gesetzt (die Flanke des Signals fällt von der Versorgungsspannung auf 0) und der Interrupt-Handler wird ausgelöst, wodurch die mit dem Null-Pin des Ports verbundene Glühbirne eingeschaltet wird B

void lampON()
{
PORTB.0=1;
DDRB.0=1;
}

unterbrechen void ext_int0_isr(void)
{
lampON();
}

DDRD.2=0;
PORTD.2=1;

SREG|= (1 while(1) (

Das obige Beispiel zeigt auch, wie Interrupt-Vektoren in Code Vision AVR festgelegt werden (Interrupt void ext_int0_isr(void)). Für andere Fälle werden Interrupt-Vektoren ähnlich festgelegt:

EXT_INT0 2
EXT_INT1 3
TIM2_COMP 4
TIM2_OVF 5
TIM1_CAPT 6
TIM1_COMPA 7
TIM1_COMPB 8
TIM1_OVF 9
TIM0_OVF 10
SPI_STC 11
USART_RXC 12
USART_DRE 13
USART_TXC 14
ADC_INT 15
EE_RDY 16
ANA_COMP 17
TWI 18
SPM_READY 19

Was ist eine Unterbrechung?
Interrupt ist eine Art Funktion, die ausgeführt wird, wenn ein Signal an einem Controller-Eingang ankommt.
Beim Arbeiten in AVR Studio werden Interrupts mithilfe von Makros erstellt ISR() , SIGNAL() Und UNTERBRECHEN(). Sie markieren eine Funktion als Interrupt-Handler. Ihr Unterschied besteht darin UNTERBRECHEN() Und ISR() Definieren Sie eine Handler-Funktion für den Fall, dass die allgemeine Unterbrechung aktiviert ist (der Handler kann unterbrochen werden), und SIGNAL() für den Fall, dass die allgemeine Unterbrechung deaktiviert ist.

Lassen Sie uns damit mit der Theorie abschließen und mit der Praxis fortfahren (obwohl es weiter unten noch mehr Theorie geben wird).
Lassen Sie uns das folgende Diagramm in ISIS zusammenstellen:

Wie Sie wahrscheinlich schon vermutet haben, schreiben wir einen Interrupt (der durch eine Taste erzeugt wird), der aufleuchtet und die Diode ausschaltet.
Öffnen Sie also das Studio und erstellen Sie ein Standardprojekt.
Um Interrupts zu verwenden, schließen Sie die Header-Datei ein:

#enthalten

Lassen Sie uns zustimmen, dass der Interrupt (physisch) den Strom am Controller-Zweig nicht ein- oder ausschaltet (da ich bereits darüber nachgedacht habe, dass dies erledigt ist), sondern nur das Flag ändert. Bei bestimmten Werten schaltet sich die Diode ein und aus.
Lassen Sie uns dieses Flag global setzen:

Int num = 1;

Lassen Sie uns nun einen Interrupt deklarieren:

ISR(SIG_INTERRUPT1)( if (num == 1) num = 0; else num = 1; )

Wie Sie sehen, wird in den Makroklammern der sogenannte Interrupt-Vektor angegeben. Dieser Vektor teilt dem Compiler mit, für welchen Eingang der Interrupt generiert wird. Für INT1 ist es SIG_INTERRUPT1. Bei einem ADC ist dies beispielsweise SIG_ADC. (Die gesamte Liste ist im Buch „Shpak Yu.A. Programming in C for AVR and PIC microcontrollers“ perfekt beschrieben.)
Kommen wir nun zur Hauptfunktion unseres „Programms“.
Wir müssen Interrupts im Allgemeinen und für INT1 im Besonderen aktivieren:

Sei(); // im Allgemeinen GIMSK |= (1<

Sobald dies erledigt ist, müssen Sie das Interrupt-Verhalten konfigurieren. Es kann auf unterschiedliche Weise generiert werden.
Laden Sie das Datenblatt herunter (es gibt einen Link beim Erstellen des Projekts) und suchen Sie im Interrupt-Bereich nach der folgenden Tabelle:

Ich denke, Sie werden verstehen, wie man das übersetzt.
Lassen Sie uns den Interrupt-Generierungsstatus für jede „logische Änderung an INT1“ festlegen.

MCUCR = (0<

Nun legen wir den gesamten Port C als Ausgang fest:

DDRC = 0xff; // Port C - Ausgabe

Nun, das sollte schon klar sein:

While (1)( if (num == 1) PORTC |= 1; // ersten Ausgang C einschalten else PORTC &= ~1; // ersten Ausgang C ausschalten _delay_ms(100); // 100 ms warten)

Es besteht kein Grund zu warten. Darüber hinaus verringert sich dadurch die Leistung. Aber ich will es so.
Gesamtes Programm:

#define F_CPU 8000000UL // 8MHz #include #enthalten #enthalten int num = 1; ISR(SIG_INTERRUPT1)( if (num == 1) num = 0; else num = 1; ) int main (void)( sei(); GIMSK |= (1<

Wir kompilieren Hex und bauen die Schaltung in Proteus zusammen. Wir freuen uns über die Unterbrechungsfunktion beim Ändern der Tastenposition.

Es kommt oft vor, dass eine Mikroschaltung leise arbeiten und arbeiten muss, aber aus irgendeinem Grund alles fallen lässt und etwas anderes tut. Und dann - kehren Sie wieder zur ursprünglichen Aufgabe zurück ... Es ist zum Beispiel wie eine Uhr - sie zeigt die Zeit an, bis die Weckerzeit kommt. Und es scheint, als gäbe es keine äußeren Einflüsse – Knopfdruck, Reset – und die Mikroschaltung schaltet sich selbst.

Dies kann mithilfe von Interrupts implementiert werden – Signalen, die den Prozessor über das Eintreten eines Ereignisses informieren.

Hier ein Beispiel aus dem Alltag: Sie sitzen in der Küche, trinken Tee mit Himbeermarmelade und leckeren Leckereien und warten auf Gäste. Woran erkennt man, dass jemand angekommen ist? Es gibt zwei Möglichkeiten: Entweder machen wir alle fünf Minuten eine Pause von der Marmelade, ich meine den Tee, und rennen, um zu sehen, ob jemand an der Tür steht, oder wir kaufen eine Türklingel und warten in aller Ruhe an einem beheizten Ort, bis jemand klingelt.

Wenn also ein Gast anruft, ist das ein Ereignis. Dementsprechend brechen wir ab und eilen zur Tür.

Die Mikroschaltung hat also Unterbrechungen. Und nicht nur einer. Interrupts werden in externe unterteilt – diese werden bei einer bestimmten Spannung an einigen Pins der Mikroschaltung (INT0, INT1 und manchmal auch am gesamten PCINT-Port) ausgelöst – und intern – wenn der Zähler überläuft, wird der Watchdog-Timer ausgelöst, bei Verwendung von USART, wenn ein analoger Komparator, ADC und andere Peripheriegeräte unterbrochen werden.

Dementsprechend entsteht ein Prioritätsproblem. Es ist, als würden wir immer noch da sitzen und Tee trinken, aber sie klingeln nicht nur an unserer Tür, sondern auch am Telefon... Und man lässt sich nicht auseinanderreißen, es muss zuerst etwas getan werden. Daher enthält das Datenblatt eine Tabelle mit Interrupt-Vektoren. Je niedriger die Interrupt-Nummer, desto höher die Priorität.

Hier gibt es mehrere Feinheiten...

Ein Ereignis ist eingetreten – eine Interrupt-Anfrage wurde gesendet, d. h. das sogenannte „Interrupt-Request-Flag“ ist gesetzt. Wenn alles in Ordnung ist, die Unterbrechung behoben ist, dann ist das Leben wunderbar und es wird verarbeitet.

Wenn jedoch ein Interrupt deaktiviert ist (z. B. ein Interrupt mit höherer Priorität wird bereits verarbeitet), bleibt dieses Anforderungsflag hängen, bis Interrupts aktiviert werden. Danach überprüft der Chip das Anforderungsregister in der Reihenfolge seiner Priorität und verarbeitet es, falls ein Flag vorhanden ist.

ABER! Es stellt sich heraus, dass selbst wenn die Unterbrechung verarbeitet wird, es keine Tatsache ist, dass das Ereignis, das sie verursacht hat, noch am Leben ist ... Es ist, als ob die Türklingel und das Telefon gleichzeitig klingelten, Sie gingen ans Telefon und die Gäste bereits entschied, dass niemand zu Hause war und ging. Und es schien, als gäbe es ein Ereignis – es klingelte an der Tür, aber es war niemand hinter der Tür.

Ein weiteres Problem besteht darin, dass das Ereignis noch mehrmals auftreten kann, während ein weiterer Interrupt verarbeitet wird und das Anforderungsflag bereits gesetzt ist. Wir nahmen den Anruf entgegen, öffneten die Tür – und schon waren eine ganze Schar Gäste da! Beängstigend? Beängstigend...

Ein weiteres Merkmal der Verwendung von Interrupts – und nicht nur von Interrupts: Wiedereintritt (oder Wiedereintritt).

Ein wiedereintrittsfähiges Programm ist ein Programm, das von mehreren Benutzern (oder Prozessen) aufgerufen werden kann, ohne zumindest einen Fehler zu verursachen und höchstens ohne Rechenaufwand zu verursachen – zum Beispiel, wenn ein anderer Benutzer den Code nicht erneut ausführen muss.

Mit anderen Worten: Wenn in der Küche, während Sie Gäste begrüßen, niemand ein paar Leckereien stiehlt, dann ist alles wiederkehrend.)

Im Allgemeinen ist es eine ernste Sache – wenn man es nicht berücksichtigt, kann man lange Zeit mit der Frage „Warum funktioniert es nicht?!“ leiden. Dies muss beispielsweise berücksichtigt werden, wenn mehrere Interrupts verarbeitet werden und jeder einzelne eine globale Variable ändert ...

Interrupts sind normalerweise NICHT wiedereintretend. Das heißt, während der Interrupt ausgeführt wird, können Sie denselben Interrupt nicht erneut aufrufen. Zum Schutz vor wiederholten Eingaben in den Handler werden Interrupts zum Zeitpunkt seiner Verarbeitung automatisch verboten (wenn Sie Interrupts im Interrupt aktivieren möchten). Man muss zehn-, zwanzigmal darüber nachdenken, bevor man so einen überstürzten Schritt wagt).

Betrachten wir die Arbeit mit externen Interrupts: Wir müssen erstens konfigurieren, welches Ereignis die Unterbrechung auslöst, und zweitens, damit der Chip genau diese Unterbrechung überhaupt verarbeiten kann.

Das MCUCR-Register ist für das erste im ATmega8-Chip verantwortlich – die Bits ISC11–ISC10, verantwortlich für INT1, und ISC01–ISC00, verantwortlich für INT0.

Tabelle 1. Definition von Ereignissen zur Generierung eines Interrupts über INT1

Entsprechend gilt das Gleiche auch für INT0.

Jetzt müssen wir nur noch die Interrupts an dem von uns benötigten Pin aktivieren – das GIGR-Register hat die Bits INT0 und INT1; auf die gewünschte „1“ gesetzt - und die externe Unterbrechung ist aktiviert! Aber es ist noch zu früh, um sich zu freuen – zusätzlich zu externen Interrupts müssen Interrupts im Allgemeinen aktiviert werden – setzen Sie das ganz linke Bit I des SREG-Registers auf „1“. Dasselbe kann mit dem Assembler-Befehl erfolgen: asm sei;

Schauen wir uns ein einfaches Beispiel an: Ein Knopf wird an den INT0 (D.2)-Pin des ATmega8-Chips angeschlossen (an den Pin und an Null); drücken – es kommt zu einem Interrupt und die LED an Pin B.0 leuchtet auf. Die LED ist dementsprechend mit dem Bein und dem Gerät verbunden:

//Programm für ATmega8, wenn Sie eine Taste an Pin INT0 (D.2) drücken – verbunden mit 0 – //schaltet die LED an Pin B.0 durch externen Interrupt ein – verbunden mit 1 //Typen neu definieren typedef unsigned char byte; sbit ddrButton bei ddD2_bit; //Generierungsschaltfläche sbit pinButton at pinD2_bit; sbit portButton bei portD2_bit; sbit ddrLight bei ddB0_bit; //Ausgang für die LED, zum Pull-up-Ausgang, 0 auf der Sbit-Taste schalten portLight bei portB0_bit; Byte flagButton = 0; //Button-Click-Flag; gedrückt - 1 void INT0_interrupt() org IVT_ADDR_INT0 //Sie müssen mindestens eine leere Funktion schreiben - //da der Compiler sie nicht selbst erstellt. Sonst funktioniert es nicht ( flagButton = 1; ) //Verarbeitung des Tastendrucks - unter Berücksichtigung von Bounce void buttonLight() ( if(flagButton) //wenn der Knopf gedrückt wird ( portLight = 0; //die LED einschalten delay_ms(500); portLight = 1; //die LED ausschalten flagButton = 0; ) ) void main() ( //Alle verwendeten Ports initialisieren ddrB = 0; portB = 0; ddrD = 0; portD = 0; // Initialisierung des Buttons – zum Eingang mit Pull-up portButton = 1; ddrButton = 0; //Initialisierung der LED, die bei 0 aufleuchtet und Drücken des Buttons – zum Ausgang und zu 1 portLight = 1; ddrLight = 1; / /Konfigurieren externer Interrupts MCUCR.ISC00 = 0; //Interrupt wird durch logische 0 auf INT0 generiert MCUCR.ISC01 = 0; GICR.INT0 = 1; //externen Interrupt aktivieren INT0 asm sei;//SREG.B7 = 1; / /enable unterbricht grundsätzlich Interrupts (Bit I); Befehle ähneln while(1) ( buttonLight() ; ) )

Ein wenig über die Syntax. Die Interrupt-Funktion wird wie folgt geschrieben: void function_name() org IVT_ADDR_INT0.

Das Schlüsselwort org gibt an, dass als nächstes die Interrupt-Adresse aus dem Datenblatt kommt. Den Namen des Interrupts haben wir aus der Bibliothek: Wir geben IVT ein und drücken dann Strg + Leertaste (ich liebe solche Dinge ˆˆ). Sie können der Compiler-Hilfe zufolge auch iv anstelle des Wortes org verwenden.

Noch eine kleine Anmerkung: Erstens sollte die Unterbrechung niemals umständlich sein – ein paar Zeilen und das war’s. Dies liegt daran, dass die Mikroschaltung bei der Verarbeitung eines Interrupts durch nichts anderes abgelenkt werden kann, was bedeutet, dass wir bei mehreren Interrupts das Eintreten eines Ereignisses verpassen können.

Es kann sich auch herausstellen, dass wir überhaupt keine Interrupt-Verarbeitung benötigen – es reicht beispielsweise aus, dass die Schaltung aus dem Schlafmodus aufgewacht ist. Aber in diesem Fall müssen Sie immer noch eine Interrupt-Funktion schreiben, auch eine leere – den sogenannten „Stub“. Im Prinzip schreiben manche Compiler automatisch leere Funktionen für jeden Interrupt, aber das ist bei uns nicht der Fall – wir müssen es manuell machen.

Heute werden wir uns mit dem Konzept der Unterbrechung und seiner Verwendung befassen. Natürlich verzichten wir nicht auf ein Trainingsprogramm, aber dieses Mal verzichten wir auf das Blinken der LEDs. Schon gut. Lasst uns so etwas wie eine Türklingel bauen.

Aufgabe: Lassen Sie den Mikrocontroller beim Drücken einer Taste einen Piepton abgeben.
Diagramm für unser Beispiel. Projektdateien.

Wir erstellen ein Ringprojekt im alten Arbeitsbereich.
Legen Sie die Projekteinstellungen für die Release-Konfiguration fest:

Wählen Sie den Typ des Mikrocontrollers aus.
Allgemeine Optionen > Ziel > Prozessorkonfiguration
Ich habe diesen ATmega8535.

Erlauben Sie die Verwendung von Bitnamen, die in der Header-Datei definiert sind
Aktivieren Sie unter Allgemeine Optionen > System das Kontrollkästchen Bitdefinitionen in I/O-Include-Dateien aktivieren
Bisher haben wir noch keine Bitnamen verwendet, aber heute werden wir sie brauchen.

Ändern Sie den Ausgabedateityp.
Linker > Ausgabe.
Aktivieren Sie im Feld Ausgabedatei das Kontrollkästchen Standard überschreiben und ersetzen Sie die Erweiterung d90 durch hexadezimal
Wählen Sie im Feld „Format“ die Option „Andere“ und im Dropdown-Menü „Ausgabeformat“ den Dateityp „Intel-Standard“.

Speichern Sie das Projekt und den Arbeitsbereich.

______________________________ Unterbrechen ___________________________

Stellen Sie sich die Situation vor. Sie sitzen bei der Arbeit und grübeln über einem weiteren Mikrocontroller-Programm. Der Chef kommt auf Sie zu und sagt: „Hören Sie, Pash, wir haben Oszilloskope für unsere Abteilung gekauft – Tektronix, Vierkanal.“ Hilf Vasya, sie zu ziehen.“ Du denkst: „Na ja, mein Gott, nur dieser Gedanke war im Weg … und auf dich.“ Und der Chef sieht dich so an und seine Augen sind so freundlich, so freundlich. Wie kannst du ihn ablehnen? Nun, Sie lassen alles stehen und liegen und gehen mit einem Freund Oszilloskope kaufen. Sie haben es hereingebracht. Wir haben berichtet. Und sie setzten sich wieder zu ihrem Programm. Ungefähr so ​​sieht der Interrupt-Mechanismus aus.

Ganz einfach, aber es gibt eine Reihe grundlegender Punkte.
Erstens:
- Du hast deinen Job gemacht
- Zur gleichen Zeit kaufte jemand Oszilloskope
- Bei Eintritt des Ereignisses „Oszilloskope gekauft“ unterbrechen Sie Ihre Arbeit
- Seit einiger Zeit gehen Sie einer anderen Arbeit nach - dem Tragen von Oszilloskopen
- Anschließend kehren Sie an Ihren Arbeitsplatz zurück und erledigen Ihre Arbeit dort, wo Sie aufgehört haben

Zweitens:
- Sie könnten Ihren Chef einfach schicken und nirgendwo hingehen
- Nachdem Sie zu den Oszilloskopen aufgebrochen sind, können Sie dort längere Zeit bleiben oder gar nicht mehr zurückkehren
- Wenn Sie an Ihren Arbeitsplatz zurückkehren, haben Sie Ihre brillanten Ideen möglicherweise bereits vergessen

Das ist alles sehr ähnlich zu dem, was in einem Mikrocontroller passiert. AVR-Mikrocontroller umfassen eine ganze Reihe von Peripheriegeräten (Timer/Zähler, Analog-Digital-Wandler, Analogkomparator, asynchroner Transceiver usw.). Die Stärke des Mikrocontrollers besteht darin, dass alle diese Geräte parallel und unabhängig voneinander sowie parallel zum ausgeführten Programm arbeiten können. Jedes Peripheriegerät kann beim Eintreten eines bestimmten Ereignisses einen Interrupt auslösen. Der Interrupt tritt nur auf, wenn er aktiviert ist. Die Interrupt-Freigabe wird für jedes Gerät separat eingestellt. Darüber hinaus gibt es ein globales Enable/Disable-Flag für alle Interrupts – das ist das I-Flag im SREG-Register. Wenn ein Interrupt auftritt, speichert der Mikrocontroller den Inhalt des PC-Programmzählers auf dem Stapel, d. h. er merkt sich die Stelle, an der er unterbrochen wurde. Lädt die Adresse des entsprechenden Interrupt-Vektors in den Programmzähler und springt zu dieser Adresse. Es wird ein bedingungsloser Sprungbefehl ausgeführt, der zur Interrupt-Verarbeitungsunterroutine führt. Deaktiviert Interrupts durch Zurücksetzen des I-Flags und führt die Unterroutine aus. Nach der Ausführung der Interrupt-Handling-Routine aktiviert der Mikrocontroller Interrupts durch Setzen des I-Flags und stellt den Inhalt des Programmzählers wieder her, d. h. er kehrt an die gleiche Stelle im Programm zurück, an der er unterbrochen wurde.

Theoretisch sollte der Interrupt-Handler den Inhalt der Mikrocontroller-Register nicht beschädigen, da diese möglicherweise Daten aus dem gerade ausgeführten Programm enthalten. Dazu werden zu Beginn des Interrupt-Handling-Unterprogramms die Inhalte der Mikrocontroller-Register auf dem Stack gespeichert und am Ende des Unterprogramms wiederhergestellt. Somit kann der Mikrocontroller nach dem Verlassen des Interrupts das Programm weiter ausführen, als ob nichts passiert wäre. Bei der Programmierung im Assembler schreibt der Programmierer selbst die Registerspeicherung vor, in C übernimmt dies der Compiler.

_______________________________________________________________

Lassen Sie uns nun über den Timer sprechen. Der ATmega8535 verfügt über drei integrierte Timer/Zähler – zwei Acht-Bit-Timer (T0, T2) und einen Sechzehn-Bit-Zähler (T1). Wir werden einen 8-Bit-Timer/Zähler T0 verwenden. Dieser Timer besteht aus drei Registern – dem Steuerregister TCCR0, dem Zählregister TCNT0 und dem Vergleichsregister OCR0. Beim Starten des Timers erhöht das Zählerregister TCNT0 seinen Wert bei jeder Taktflanke um eins. Die Taktfrequenz wird aus mehreren möglichen Werten im Steuerregister TCCR0 ausgewählt. Außerdem wird über dieses Register die Betriebsart des Timers eingestellt. Der Timer T0 kann einen Interrupt auslösen, wenn ein „Überlauf“-Ereignis auftritt – das ist, wenn das Zählregister TCNT0 überläuft, und beim Auftreten eines „Koinzidenz“-Ereignisses – das ist, wenn der Wert des Zählregisters TCNT0 gleich wird Wert des Vergleichsregisters OCR0. Die Flags, die diese Interrupts aktivieren, befinden sich im TIMSK-Register.
Wir werden den T0-Timer/Zähler so konfigurieren, dass er einen „Match“-Ereignis-Interrupt bei 5 kHz auslöst. In der Handler-Funktion invertieren wir den Zustand des Mikrocontroller-Ausgangs, an den der Piezo-Lautsprecher angeschlossen ist. Somit beträgt die Piezo-Schallfrequenz 2,5 kHz. (Es ist der Piezolautsprecher, der angeschlossen ist! Nicht verwechseln. Der Widerstand eines Piezolautsprechers hängt von der Frequenz ab und beträgt bei 2,5 kHz normalerweise die Einheit Com, sodass er ohne Begrenzungswiderstand direkt an den Ausgang des Mikrocontrollers angeschlossen werden kann.) .

Nun zum Programm. Es ist nicht mehr möglich, das Programm Zeile für Zeile zu schreiben, deshalb gebe ich gleich den Text an. Im Folgenden werden wir alle Zeilen einzeln analysieren und alles wird klar. Auf Makros habe ich bewusst verzichtet, das Programm ist klein und ich möchte es nicht überladen.

int hauptsächlich( Leere )
{
//I/O-Ports einrichten
DDRD = (0<PORTD = (1<

//Timer T0 einrichten
TCCR0 = (1<TCNT0 = 0;
OCR0 = 0xc8;

//Interrupts aktivieren
__enable_interrupt();

//Hauptprogrammschleife – Abfrage der Schaltfläche
während(1){
Wenn((PIND & (1<TIMSK = (1<anders
TIMSK = 0;
}
zurückkehren 0;
}

//Interrupt-Handler für Timer T0

__unterbrechen Leere Timer0CompVect( Leere)
{
PORTD ^= (1<}

Porteinstellungen

In unserer Schaltung sind ein Taster und ein Piezo-Lautsprecher an Port D angeschlossen. Der Pin, an dem der Taster angeschlossen ist, muss als Eingang konfiguriert und der Pull-up-Widerstand eingeschaltet sein. Der Pin, an dem der Piezo-Lautsprecher angeschlossen ist, muss auf Ausgang eingestellt sein.

DDRD = (0<PORTD = (1<

Einstellen des Timers

Der Betriebsmodus des T0-Timers ist CTC (Zurücksetzen bei Koinzidenz), das Taktsignal ist clk/8. Wir spiegeln dies im TCCR0-Register wider

TCCR0 = (1<

Für alle Fälle setzen wir das Zählregister TCNT0 zurück

Schreiben Sie 0xc8 in das Vergleichsregister OCR0. Warum? Weil ich damit gerechnet habe Taschenrechner. Nun, auf dem Papier sieht diese Berechnung so aus.
Taktfrequenz des Mikrocontrollers 8 MHz
Das Timer-Taktsignal beträgt 8000000 Hz/8 = 1000000 Hz.
Zeit einer Zeituhr 1/1000000 = 1 µs
Die Zeit eines Zyklus der von uns benötigten Frequenz beträgt 1/5000 Hz = 200 μs
Wie viele Timer-Ticks passen in 200 µs? 200/1 = 200 Ticks
200 im Hexadezimalformat = 0xс8

Eine detaillierte Beschreibung des T0-Timers finden Sie in der Dokumentation zum ATMega8535.

Wir haben den Timer konfiguriert und ermöglichen mit der eingebauten Funktion einen allgemeinen Interrupt.

__enable_interrupt();

Schaltfläche „Umfrage“.

Wenn die Taste nicht gedrückt wird, wird der Ausgang des Mikrocontrollers über einen internen Pull-up-Widerstand mit Strom verbunden, d ist eine Null am Ausgang. Um festzustellen, ob eine Taste gedrückt wurde, müssen Sie den Inhalt des PIND-Registers lesen und den Wert des Nullbits überprüfen (eine Taste ist mit PD0 verbunden). Wir werden den Button in einer Endlosschleife abfragen.

während (1)
{
Wenn((PIND & (1<//Wenn die Taste gedrückt wird, sollte der Mikrocontroller quietschen
}
anders {
//Wenn nicht, sei still wie ein Fisch
}
}

Vergessen Sie nicht == ist kein Zuweisungsoperator =.

Handhabung des Drückens/Loslassens der Taste

Durch Drücken der Taste aktivieren wir die Unterbrechung des Timers T0 und durch Loslassen deaktivieren wir ihn. Dazu manipulieren wir das OCIE0-Bit des TIMSK-Registers

TIMSK = (1<// Unterbrechung des Timers T0 aufgrund des Koinzidenzereignisses zulassen

TIMSK = 0; //Unterbrechung deaktivieren

Da wir nur einen Timer verwenden, ist es nicht erforderlich, einzelne Bits zu setzen oder zurückzusetzen.

Interrupt-Funktion

_____________________ Syntax der Interrupt-Funktion _____________________

Die Interrupt-Funktion wird mit der Direktive #pragma vector= und einem Funktionswort angegeben __unterbrechen. Die Funktion muss vom Typ void sein und darf keine Parameter annehmen.

#pragma-Vektor = Adresse
__unterbrechen Leere Name( Leere)
{
//unser Code befindet sich hier
}

Name– Funktionsname, wählen Sie nach unserem Ermessen
Adresse– Interrupt-Vektor-Adresse, kann durch Nummer oder durch in der Mikrocontroller-Header-Datei definierte Namen angegeben werden (iom8535.h – Abschnitt „Interrupt-Vektor-Definitionen“)

______________________________________________________________

Für unsere Aufgabe sieht die Interrupt-Handler-Funktion so aus

#pragma vector = TIMER0_COMP_vect
__unterbrechen Leere Timer0CompVect( Leere)
{
PORTD ^= (1<//invertiere das Signal an Pin PD1
}

Nun, das ist alles. Ich hoffe, dass alles klar ist.
Im nächsten Artikel werden wir den Mikrocontroller dazu bringen, eine Melodie abzuspielen.

Interrupt – ein Ereignis, das eine sofortige Reaktion des Prozessors erfordert. Die Reaktion besteht darin, dass der Prozessor die Verarbeitung des aktuellen Programms unterbricht ( unterbrochenes Programm) und fährt mit der Ausführung eines anderen Programms fort ( Programm unterbrechen), speziell für diese Veranstaltung konzipiert. Nach Abschluss dieses Programms kehrt der Prozessor zur Ausführung des unterbrochenen Programms zurück.

Jedes Ereignis, das eine Unterbrechung erfordert, wird begleitet von Unterbrechungssignal, benachrichtigte den Computer darüber und rief an Unterbrechungsanforderung.

Programmstatus stellt eine Menge von Zuständen aller Speicherelemente zum entsprechenden Zeitpunkt dar (z. B. nachdem der letzte Befehl ausgeführt wurde). Wenn ein Interrupt auftritt, speichert der Mikrocontroller den Inhalt des Programmzählers auf dem Stapel und lädt die Adresse des entsprechenden Interrupt-Vektors hinein. Der letzte Befehl der Interrupt-Serviceroutine muss ein Befehl sein, der zum Hauptprogramm zurückkehrt und den zuvor gespeicherten Programmzähler wiederherstellt. Während der Interrupt-Handler ausgeführt wird, können sich einige Informationen ändern. Daher ist es beim Wechsel zum Interrupt-Handler erforderlich, die geänderten Elemente zu speichern. Die Menge solcher Elemente ist Programmzustandsvektor. In diesem Fall sind andere Informationen über den Zustand der Speicherzellen nicht von Bedeutung oder können programmgesteuert wiederhergestellt werden.

Anfangszustandsvektor enthält alle notwendigen Informationen für den ersten Start des Programms. In vielen Fällen enthält der Anfangszustandsvektor nur ein Element – ​​die Startadresse des zu startenden Programms.

Unterbrechungsvektor ist der Vektor des Anfangszustands des unterbrechenden Programms (Handler) und enthält alle notwendigen Informationen zum Wechseln zum Handler, einschließlich seiner Startadresse. Jeder Interrupt-Typ verfügt über einen eigenen Interrupt-Vektor, der die Ausführung des entsprechenden Handlers initiiert. Typischerweise werden Interrupt-Vektoren an speziell zugewiesenen festen Speicherplätzen mit kurzen Adressen gespeichert, die darstellen Interrupt-Vektortabelle. Um zum entsprechenden Interrupt-Programm zu springen, muss der Prozessor über einen Interrupt-Vektor und die Adresse dieses Vektors verfügen. An dieser Adresse steht in der Regel ein unbedingter Sprungbefehl zum Interrupt-Handling-Unterprogramm.

Die Steuerung des Speicherns und Zurückgebens wird in der Regel dem Interrupt-Handler übertragen. In diesem Fall besteht der Handler aus drei Teilen – dem vorbereitenden (Prolog) und dem abschließenden (Epilog), die den Programmwechsel sicherstellen, und dem eigentlichen Unterbrechungsprogramm, das die von der Anfrage angeforderten Operationen ausführt. Die Reaktionszeit ist definiert als das Zeitintervall vom Eingang einer Interrupt-Anforderung bis zum Beginn der Ausführung des unterbrechenden Programms.


t p– Reaktionszeit des Systems auf Unterbrechung;
t z– Zeit zum Speichern des Status des unterbrochenen Programms;
t ppr– Zeitpunkt des eigentlichen Unterbrechungsprogramms;
Zinn– Zeit, den Zustand des unterbrochenen Programms wiederherzustellen

Bei mehreren Anfragequellen muss eine bestimmte Reihenfolge zur Bearbeitung eingehender Anfragen festgelegt, genannt, werden vorrangige Beziehungen oder Servicedisziplin. Die Menge aller möglichen Prozessor-Interrupt-Typen ist Interrupt-System Mikrocontroller. Die Servicedisziplin legt fest, welche von mehreren gleichzeitig eingehenden Anfragen zuerst bearbeitet wird und ob dieser oder jener Interrupt-Handler das Recht hat, diese Anfrage zu unterbrechen.
Wenn während der Verarbeitung eines Interrupts eine Interrupt-Anforderung mit höherer Priorität empfangen wird, wird die Steuerung an den Interrupt-Handler mit höherer Priorität übertragen und der Interrupt-Handler mit niedrigerer Priorität wird ausgesetzt. Entsteht Verschachtelung unterbrechen. Die maximale Anzahl von Programmen, die sich gegenseitig anhalten können, wird aufgerufen Tiefe der Unterbrechungen.

Wenn die Unterbrechungsanforderung nicht bearbeitet wird, wenn eine neue Anforderung von derselben Quelle (gleicher Priorität) eintrifft, dann Unterbrechen Sie die Systemsättigung. In diesem Fall gehen einige Interrupt-Anfragen verloren, was für den normalen Betrieb des Mikrocontrollers nicht akzeptabel ist.

Eigenschaften des Interrupt-Systems Sind:

  • Gesamtzahl der Interrupt-Anfragen Anzahl der Quellen von Interrupt-Anfragen;
  • Interrupt-Darstellungstyp – in der Regel wird eine Interrupt-Anfrage durch einen logischen Signalpegel dargestellt;
  • Interrupt-Priorität – bestimmt die Reihenfolge, in der jede Interrupt-Anfrage verarbeitet wird; je höher die Priorität, desto kürzer ist die Verzögerung bei der Ausführung des Interrupt-Programms dafür;
  • Reaktionszeit – das Zeitintervall zwischen dem Erscheinen der Unterbrechungsanforderung und dem Beginn der Ausführung des unterbrechenden Programms;
  • Unterbrechungsverzögerung – bestimmt durch die Gesamtzeit zum Speichern und Wiederherstellen des Programms;
  • Tiefe stimmt normalerweise mit der Anzahl der Prioritätsstufen im Interrupt-System überein;
  • Systemsättigung unterbrechen;
  • zulässige Momente der Programmunterbrechung (normalerweise das Ende der Ausführung des nächsten Befehls).

Maskierung unterbrechen Wird verwendet, um den Mikrocontroller anzuweisen, auf jede Art von Interrupt zu reagieren oder ihn zu ignorieren. Die Interrupt-Maske stellt einen Binärcode dar, dessen Bits den Quellen der Interrupt-Anfrage zugeordnet sind. Das eine Bit im Binärcode weist den Mikrocontroller an, diese Art von Interrupt zu verarbeiten. Ein Nullbit hingegen erlaubt dem Mikrocontroller nicht, mit der Verarbeitung von Interrupts des angegebenen Typs fortzufahren.
In der Regel gibt es neben der Maskierung von Interrupts auch ein globales Interrupt-Enable-Bit, dessen Nullwert alle Interrupt-Handler deaktiviert (außer Hardware-Reset und Sprung zum Anfang des ausführenden Programms).
Zusätzlich zum Interrupt-Masken-Binärcode gibt es auch einen Binärcode Interrupt-Flags, wodurch der Interrupt-Handler die Quelle des Interrupts festlegen kann, wenn im Mikrocontroller mehrere Quellen mit der angegebenen Anforderung vorhanden sind.



 


Lesen:



Klassen und Namespaces, die Namespaces verwenden und deklarieren

Klassen und Namespaces, die Namespaces verwenden und deklarieren

Klassen und Namespaces .NET Framework-Klassen Der vielleicht größte Vorteil beim Schreiben von verwaltetem Code – zumindest im Hinblick auf ...

Broschüre zum Thema „Computer und Kinder“ Richtige Handhaltung

Broschüre zum Thema

Machen Sie spezielle Übungen für Ihre Augen! 1. Intensives Zusammendrücken und Öffnen der Augen in schnellem Tempo und häufiges Blinzeln der Augen. 2. Augenbewegung...

Snowboarden: Wie hat alles angefangen?

Snowboarden: Wie hat alles angefangen?

Snowboarden ist eine olympische Sportart, bei der man mit einer speziellen Ausrüstung – einem Snowboard – von schneebedeckten Hängen und Bergen hinunterfährt. Ursprünglich Winter...

Foto der Lage auf der Weltkarte, Beschreibung

Foto der Lage auf der Weltkarte, Beschreibung

Von der Antike bis heute wurden auf der Welt viele Wasserstraßen – künstliche Kanäle – angelegt. Die Hauptaufgabe solcher künstlichen Systeme besteht darin,...

Feed-Bild RSS