Hallo M1R,
schön, dass dir die Beschreibung gefallen hat.
Ja, das Angebot steht noch, und es kann sich nur noch um Monate handeln, bis ich dann da weiter mache.
Hier muss ich erst mal deine sehr übersichtliche und einheitliche Schreibweise vom Code loben. Ich bin da immer extrem pingelig, aber bei deinem main()-Teil gab es (fast) nichts, was ich während des Lesens umformatiert habe. Echt Klasse.
2 Anmerkungen zu deinem main():
1:
Folgenden Code kannst du noch ändern, um besser zu sehen, dass das Hauptprogramm nur etwas tun muss, wenn der Interrupt etwas ausgelöst hat:
Code:
StartSwitch ();
while (switched == 0) // wenn kein taster gedrückt ist rot leuchten
{
PORTB &= ~(1<<PB0); // grün ausschalten
PORTD |= (1<<PD2); // rot einschalten
}
umbauen zu:
// rot hier nur einmal einschalten
PORTB &= ~(1<<PB0); // grün ausschalten
PORTD |= (1<<PD2); // rot einschalten
StartSwitch ();
while (switched == 0) // wenn kein taster gedrückt ...
{
// ... wird hier normalerweise dein Hauptprogramm ackern und
// heftigste Berechnungen und Motorsteuerungen machen.
// Allerdings darf hier auch nicht zu viel Zeit zur Bearbeitung
// benötigt werden, da diese Code-Konstruktion ja sonst nicht
// schnell genug wieder zum while() kommt um zu reagieren.
}
// ... und jetzt erst muss der Motor gestoppt
// der Rückwärtsgang eingelegt,
// und die getroffene Wand umfahren werden.
Das ist jetzt für deinen Programmablauf zwar nur Haarspalterei, aber genau darum geht es ja bei den Interrupts. Es soll eben nichts 'überflüssiges' während der while()-Arbeitsschleife gemacht werden, da ja der Interrupt bzw. die Information in der Variablen switched nur dann eine Reaktion in deinem Hauptprogramm erzeugen soll, wenn der Interrupt etwas zu sagen hatte. (switched == 1)
2:
Erst einmal liegst du vollkommen richtig, dass es mit der Interruptfunktion "SIGNAL (SIG_OVERFLOW2)" zu tun hat.
Eigentlich ist es nicht unbedingt notwendig dort etwas zu ändern, da 'im Prinzip' schon alles vorhanden ist, aber zum Verständnis mal folgendes:
Annahme von mir: Deine Variable zeit sieht mir danach aus, dass du sie als Sekundenzähler nutzen willst.
==> if (zeit == 1) // und jetzt nach 1 sek ...
Also muss sich der Timer2-Interrupt um diese Variable kümmern. (Du hast sie mit "volatile int zeit;" schon richtig angelegt.)
So ist es bei dir angegeben:
Code:
//----------------------------------------------------------------
//Interruptfunktion timer2 in asuro.c
SIGNAL (SIG_OVERFLOW2)
{
TCNT2 += 0x25;
count36kHz ++;
if (!count36kHz)
timebase ++;
#ifdef RC5_AVAILABLE
if (enableRC5 && !(count36kHz % 8))
IsrRC5(); // wird alle 222.2us aufgerufen
#endif
}
Den Teil zwischen "#ifdef RC5_AVAILABLE" und "#endif" ignorieren wir hier komplett. m.a.r.v.i.n hat hierzu eine tolle Beschreibung in der HTML-Doku zur Lib direkt auf der Startseite unter dem Punkt "Neue Projekte" geschrieben.
Es bleibt übrig:
Code:
//----------------------------------------------------------------
//Interruptfunktion timer2 in asuro.c
SIGNAL (SIG_OVERFLOW2)
{
TCNT2 += 0x25;
count36kHz ++;
if (!count36kHz)
timebase ++;
}
Nun noch mal überlegen. Wann wird diese Funktion überhaupt aufgerufen?
Ja genau. Immer dann wenn der Timer2 eine bestimmte Zeit gearbeitet hat.
Die Vorgabe zur Initialisierung vom Timer2 hast du ja schon gefunden und als eigene Funktion in TimerInit() eingebaut.
Und wann ist "eine bestimmte Zeit" nun genau?
Achtung: Es wird nun kompliziert, aber die Initialisierung ist hier das A und O.
Also mal die Bit genauer betrachten, die in den Registern gesetzt werden:
- Register TCCR2
-- WGM20 und WGM21 setzen den Timer in den "Fast PWM"-Mode
-- COM20 und COM21 setzen im "Fast PWM"-Mode einen 'Output Compare Mode'
-- auf 'Set OC2 on Compare Match, clear OC2 at TOP
-- CS20 setzt einen Takt-Vorteiler auf 1 (8MHz / 1 = 8MHz-Takt)
Für unsere Zeitfunktion ist es nun wichtig zu 'wissen' (Doku zur CPU), dass der 8-Bit-Zähler TCNT2 so initialisiert ist, dass er mit dem Takt von 8MHz immer von 0 bis 255 hochzählt und dann einfach wieder neu bei 0 anfängt.
Außerdem wird noch, jetzt aufpassen, unser gewünschte Interrupt immer 'vorbereitet', wenn der Zähler bei 255 ankommt.
Um diesen 'vorbereiteten' Interrupt auch tatsächlich zu erhalten, muss noch im:
- Register TIMSK
-- TOIE2 gesetzt werden.
Jetzt können wir mal rechnen, wie lang unsere "bestimmte Zeit" nun eigentlich ist. (Teil 1, da noch nicht alles richtig sein wird)
-> 8MHz vom Quarz geteilt durch Takt-Vorteiler 1 geteilt durch 256 (8-Bit-Zähler) sind nun
-> 8000000Hz / 1 / 256 = 31250Hz <== Noch nicht 32kHz.
-> Die "bestimmte Zeit" ist dann 1 / 31250Hz = 0,000032Sekunden
Nach (ungefähr) dieser Zeit bekommen wir somit den ersten Interrupt.
Im Interrupt aber passiert nun als aller erstes folgendes:
-- TCNT2 += 0x25; // 0x25 sind dezimal 37
Hier wird der Zähler TCNT2 'einfach mal zum Spaß' um 37 Werte größer gemacht.
Da der Interrupt ja immer dann kommt, wenn der Zähler TCNT2 aber gerade bei 255 ist, und schon wieder zur 0 umschlagen möchte, heißt dies, dass der Zähler eigentlich nur von 37 bis 255 läuft.
Tut er aber auch nicht, da das "TCNT2 += 0x25;" schon 3 Takte kostet, und somit von 37 – diese 3 Takte bis 255 läuft.
Und warum 3 Takte und warum -?
- Auslesen, addieren und wieder schreiben vom Register TCNT2 kosten jeweils einen Takt.
- In Wirklichkeit läuft die Zeit ja weiter bevor wir mit der Registermanipulation fertig sind.
Jetzt ein neues 2.tes rechnen:
-> 8000000Hz / (256 - (37-3)) = 8000000Hz / 222 = 36036,...Hz <== Da sind unsere guten 36kHz
Uff, jetzt geht es einfach weiter:
36036Hz sind in Sekunden: 1 / 36036Hz = 0,00002775... Sekunden
-- count36kHz ++; zählt also in dieser Zeit um 1 weiter.
Nun müssen wir nur noch deine ursprüngliche Frage beantworten.
Was muss im Interrupt gemacht werden, damit wir dort einen Zähler bekommen, der genau jede Sekunde um 1 weiterzählt?
Wir müssen nur eine bestimmte Anzahl von Interrupts abwarten, bis wir deine Variablen zeit um 1 hochzählen können.
Diese Anzahl ist jetzt leicht auszurechnen:
1 Sekunde (wollen wir haben) durch 0,00002775... Sekunden (Unsere "bestimmte Zeit" vom Interrupt)
-> 1 Sekunde / 0,00002775... Sekunden = 36036
Upps, ist ja die Frequenz von 36kHz. Soll ja auch so sein, da Hz nun mal "Takte pro Sekunde" sind
Also im Interrupt nun:
Code:
//----------------------------------------------------------------
//Interruptfunktion timer2 in asuro.c
SIGNAL (SIG_OVERFLOW2)
{
static int teiler = 0; // static ist dafuer da, dass der Variablen-
// inhalt nicht verloren geht, wenn die
// Funktion beendet wird.
TCNT2 += 0x25;
count36kHz ++;
if (!count36kHz)
timebase ++;
teiler ++;
if (teiler == 36036)
{
teiler = 0;
zeit ++;
}
#ifdef RC5_AVAILABLE
if (enableRC5 && !(count36kHz % 8))
IsrRC5(); // wird alle 222.2us aufgerufen
#endif
}
So, damit glaube ich, dass auch der Timer-Interrupt nicht mehr so im dunkeln liegt.
Auch hier hat es sich ja nun heraus gestellt, dass das wie bei den Tastern erklärte Prinzip vorhanden ist:
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.
A) ist nun die Bearbeitung der Variablen teiler und zeit.
B) war hier natürlich der einfachste Teil mit dem "TIMSK |= (1 << TOIE2);"
Kompliziert war es ja nur an der Stelle zum Initialisieren des Timer 2 und der von waste erfundenen Manipulation am TCNT2-Register im Interrupt selbst .
Ist schon stark, was 4 Zeilen Code alles anrichten können!
C) hast du in deinem main() zu Anfang erledigt.
D) musst du allerdings noch etwas umbauen.
Hinter deinem Aufruf von TimerInit() fragst du ja sofort die Interrupt-Information zeit ab.
Natürlich ist da dann noch keine Sekunde vergangen. Somit wirst du dann die LED nicht auf gelb setzen sondern sofort zum Ende deines Programms kommen.
Hier hilft zum testen auch erst einmal ein schnuckeliges while():
Code:
// und jetzt nach 1sek led gelb mit timer2 interrupt ?????????
//----------------------------------------------------------------
// timer2 Interrupt
zeit = 0;
TimerInit ();
while (zeit != 1) // warten, bis die zeit bei 1 angekommen ist
;
PORTB |= (1<<PB0); // grün einschalten
PORTD |= (1<<PD2); // rot einschalten
//----------------------------------------------------------------
Ich hoffe, du konntest meiner Schwafelei folgen, und das dein 2-Zeiler
Kann es sein, das ich hierfür die "SIGNAL (SIG_OVERFLOW2)" ändern müsste?
Leider ist mir diese Funktion ziemlich unverständlich.
nun nie wieder von dir ausgesprochen werden muss
Klar, wenn noch (oder noch mehr?) Fragen sind, nur zu.
Gruß Sternthaler
Lesezeichen