Hallo M1.R,
oh je mi ne.
Ich bin mir relativ sicher, dass du schon alle möglichen Doku's gelesen hast. Ich grüble deshalb schon lange darüber nach, was ich dir da noch wie schreiben kann. Na gut, wollen wir das mal angehen.
Vorweg:
Es gibt unterschiedliche Arten von Interrupts.
1) Durch externe Dinge ausgelöst: Ein Pin am Prozessor ändert die Spannung.
2) Durch interne Dinge ausgelöst: Z.B. ein Timer läuft ab, oder der ADC ist fertig.
3) Durch dein Programm ausgelöst: Nennt sich Softwareinterrupt und lassen wir hier weg.
Alle Interrupts haben folgendes gemeinsam:
A) Ein Programmcode in der Interruptfunktion muss von dir geschrieben werden.
B) Der entsprechende Interrupt muss über das dazugehörende Bit im entsprechenden Steuerbyte zugelassen werden.
C) Der CPU muss generell erlaubt werden Interrupts auszuführen.
D) Die Verarbeitung im Hauptprogramm muss sich auf Interrupts einstellen.
Warum aber so ein Aufwand?
Dein Hauptprogramm im Asuro hat ja folgenden Aufbau:
Code:
int main (void)
{
Init ();
while (1)
{
tu_was ();
}
return 0;
}
An der Stelle tu_was(); arbeitet dein Programm deine Hauptaufgabe ab und der Asuro ist die ganze Zeit damit beschäftigt endlos etwas zu tun.
Wenn die Aufgabe nun darin besteht, dass der Asuro einfach geradeaus fahren soll, bis ein Taster gedrückt wird um zu wenden, kannst du in der While()-Schleife ja "immer die Tasten abfragen" und dann entsprechend reagieren.
Dieses "immer die Tasten abfragen", nennt man 'pollenden' Betrieb, da du dich in der Schleife selbst drum kümmerst, auf die Tasten zu achten.
Somit sieht dein Programm nun ungefähr so aus:
Code:
int main (void)
{
Init ();
while (1)
{
if (PollSwitch () > 0)
fahre_ein_stueck_zurueck_und_drehe ();
else
tu_was ();
}
return 0;
}
Nun kommt der Interrupt ins Spiel,
damit du dich eben nicht um solche Dinge kümmern musst.
Hier bietet es sich natürlich an, das man einen externen Interrupt nutzt, um den durch den Taster verursachten Spannungswechsel zu registrieren.
In der Lib gibt es dazu folgende Funktionen:
A) Die Interruptfunktion:
Code:
SIGNAL (SIG_INTERRUPT1)
{
switched = 1;
GICR &= ~(1 << INT1); // Externen Interrupt 1 sperren
}
B) Die Vorbereitung, das der Interrupt am Pin INT1 zugelassen wird:
Code:
void StartSwitch (void)
{
SWITCH_OFF; // Port-Bit auf LOW
DDRD &= ~SWITCHES; // Port-Bit SWITCHES als INPUT
MCUCR &= ~((1 << ISC11) | (1 << ISC10)); // Low level erzeugt Interrupt
GICR |= (1 << INT1); // Externen Interrupt 1 zulassen
}
C) Die allgemeine Erlaubnis, dass Interrupts ausgeführt werden dürfen:
Code:
void Init (void)
{
...
...
sei ();
}
Bis dahin haben wir nur Arbeit gehabt und noch keinen Nutzen.
Jetzt aber wird es Spannend.
Dein Hauptprogramm ändern wir nun ein kleines bisschen ab:
Code:
int main (void)
{
Init ();
switched = 0;
StartSwitch ();
while (1)
{
if (switched == 1)
{
fahre_ein_stueck_zurueck_und_drehe ();
switched = 0;
StartSwitch ();
}
else
tu_was ();
}
return 0;
}
Bäh. da kommt ja immer mehr Programmcode zusammen.
Das ist zwar nicht schön, aber es bringt einen entscheidenden Vorteil. Du nutzt jetzt eine Interruptfunktion, die darauf aufpasst, ob ein Taster gedrückt wurde.
Und was ist daran so toll?
Du sparst enorm viel Rechenzeit in deiner while()-Schleife, da du dich dort nicht mehr darum kümmern musst die Information, ob eine Taste gedrückt wurde, zu ermitteln. Du kannst dich in deinem Programm auf die Auswertung dieser Information konzentrieren und hast dann dafür mehr Rechenzeit für die tu_was()-Funktion zur Verfügung.
Was aber, wenn man genau wissen will welche Taste gedrückt wurde?
Hier reicht es nun, dass im Hauptprogramm nur genau einmal die PollSwitch()-Funktion aufgerufen wird. Und zwar dann natürlich, wenn feststeht, dass eine Taste gedrückt wurde.
Also ändern wir wieder ein bisschen:
Code:
int main (void)
{
unsigned char taste;
Init ();
switched = 0;
StartSwitch ();
while (1)
{
if (switched == 1)
{
taste = PollSwitch ();
fahre_ein_stueck_zurueck_und_drehe (taste);
switched = 0;
StartSwitch ();
}
else
tu_was ();
}
return 0;
}
Hier haben wir nun die Interruptfunktion dazu benutzt, dass uns angezeigt wird, dass irgendeine Taste gedrückt wurde. Nun holen wir wie gewohnt mit der PollSwitch()-Funktion die Info, welche Taste(n) das waren und geben diese Info einfach an unsere fahre_ein_stueck_zurueck_und_drehe()-Funktion weiter. Die kann dann entscheiden, ob nach rechts oder links gedreht wird.
Aber entscheidend ist hier, dass die PollSwitch()-Funktion nicht permanent aufgerufen werden muss.
Das heißt nun einfach 'Interrupt'-Betrieb. (im Gegensatz zum 'pollenden' Betrieb)
Was uns bis jetzt noch fehlt, ist eine Möglichkeit, dass wir in unserem Hauptprogramm die Interruptfunktionalität abschalten können. Denn ist sie einmal aktiv, dann wird man sie so schnell auch nicht wieder los.
Dafür gibt es in der Lib die folgende Funktion:
Code:
void StopSwitch (void)
{
GICR &= ~(1 << INT1); // Externen Interrupt 1 sperren
}
Und wo bitte wurde die Variable switched definiert?
- In der Lib gibt es die Datei globals.c
-- Darin wird mit:
volatile int switched;
diese Variable definiert. Hier ist es ganz wichtig, dass dieses 'volatile' angegeben ist. Damit verbieten wir dem Compiler, dass er diese Variable in einem CPU-Register speichert. Sie muss vom Compiler im RAM gespeichert werden.
Das ist immer dann notwendig, wenn der Inhalt sowohl in der Interruptfunktion als auch außerhalb davon benutzt/geändert werden soll. Und hier wollen wir ja den Inhalt dazu benutzten, dass dein Hauptprogramm mitgeteilt bekommt das etwas passiert ist.
Und was passiert nun eigentlich?
- Dein Hauptprogramm macht alle technische Vorbereitungen:
-- sei(); <== Interrupts grundsätzlich erlauben
-- GICR |= (1 << INT1); <== Steuerbit INT1 im Steuerregister GICR setzen
- Dein Hauptprogramm macht logische Vorbereitungen:
-- switched = 0; <== Information initialisieren
- Dein Hauptprogramm dreht sich im Kreis und arbeitet:
-- tu_was();
- Nun kommt die Wand und drückt unerwartet eine Taste
-- INTERRUPT-FUNKTION setzt nur switched = 1;
- Dein Hauptprogramm berücksichtigt diese Information und wertet sie aus:
-- if (switched == 1)
- Und zu guter letzt, initialisiert dein Hauptprogramm eine weitere Interrupt-Behandlung:
-- if (switched == 1)
-- {
---- switched = 0;
---- StartSwitch ();
Dies ist hier notwendig, da die Interruptfunktion nicht nur die Variable switched auf 1 setzt, sondern hier auch noch den Punkt B) rückgängig macht und somit einen weiteren Tasteninterrupt nicht mehr zulässt.
Dies ist hier sinnvoll, da der Interrupt sonst so lange permanent aufgerufen wird, bis alle Tasten losgelassen wurden. Das aber wiederum dauert aus CPU-Sicht sehr, sehr lange bis die Motoren den Asuro endlich von der Wand wegbewegt haben. Und das wiederum würde uns nur kostbare Rechenzeit stehlen.
So, da ich nicht weiß, ob dir diese Beschreibung überhaupt weitergeholfen hat, höre ich hier erst einmal auf. (Ist doch etwas aufwändig)
Wenn dir das aber helfen konnte, dann melde dich mal, und ich werde auch noch zu internen Interrupts (ADC, Timer) etwas schreiben.
Gruß, und natürlich ein 'Schönes neues Jahr' wünscht dir
Sternthaler
Lesezeichen