Hallo alle zusammen, habe schon einige kleine Sachen mit dem 89C4051 programmiert und auch hinbekommen. Jedoch waren es immer kleine Aufgaben, welche nicht unbedingt Zeit relevant sind / waren. Jetzt programmiere ich erfolgreich mit dem SDCC C-Compiler, bis jetzt kann das Programm nicht viel: Timer0 mit 16-Bit: preload ist so eingestellt, dass ich alle 0.05 s einen Interrupt erhalte, in der Interrupt Routine wird der Timer wieder mit den entsprechenden Werten für 0.05 ms nachgeladen. Jetzt hab ich Probleme damit richtig zurecht zu kommen, die "neue" Zeitbasis richtig einzusetzen; ich habe vor zum Beispiel alle 500 ms einen Port-Pin abzufragen. Ebenfalls soll die Zeitbasis auch für einen Signalgeber benutzt werden, 12 Piepser soll mit 1 oder 2 Hz an und aus gemacht werden für etwa 10 s ... usw. Mir fehlt die entscheidende Idee wie ich dass ganz in den Griff bekomme: Meine Idee war, dass ich in der Timer Interrupt Routine eine Zähl - Variable hochzähle, sagen wir bis 2000 und wenn 2000 erreicht wurde, dann wird sie wieder zurück gesetzt. Desweitern wird in der Routine i mit MODULO 5, 10, 100, usw. überprüft und entsprechende Variablen werden gesetzt. time_5ms. Diese Variablen würde ich dann vor den Aufruf entsprechender Funktionen überprüfen. Zum Beispiel für einen Beep, time500ms und time2s .... Versteht ihr meine Idee? Und geht es eventuell eleganter? Gruß Peter
Peter Max schrieb: > Meine Idee war, dass ich in der Timer Interrupt Routine eine Zähl - > Variable hochzähle, sagen wir bis 2000 und wenn 2000 erreicht wurde, > dann wird sie wieder zurück gesetzt. Die Idee ist schon mal gut. Da du weißt, dass die Routine alle 0.05ms aufgerufen wird, vergehen logischerweise 2000 * 0.05 = 100ms bis diese Variable einmal von 0 bis 2000 zählt. Machst du in dem if, der feststellt ob die 2000 erreicht sind, wieder irgendeine andere Aktion, dann wird diese Aktion folgerichtig alle 100ms ausgeführt. > Desweitern wird in der Routine i mit MODULO 5, 10, 100, usw. überprüft > und entsprechende Variablen werden gesetzt. Kann man machen. Ich kenne jetzt diesen speziellen Prozessor nicht. Allerdings solltest du darauf achten, dass Divisionen (auch Modulo ist im Grunde nichts anderes als eine Division) ohne Hardwareunterstützung zeitaufwändig sind und für den Prozessor eine ganz schöne Belastung darstellen. Aber: Nichts und niemand hindert dich daran, in Analogie zu deiner obigen Idee eine weitere Variable einzuführen, die du ebenfalls bei jedem Aufruf um 1 erhöhst. Nur lässt du sie nicht bis 2000 zählen, sondern bis ... ... mal sehen. Deine Interrupt Routine wird alle 0.05ms aufgerufen. Du möchtest ein Zeitsignal alle 5ms. D.h. wenn diese Variable bis 5/0.05 = 100 gezählt hat, sind genau 5ms vergangen.
1 | timer_5_ms ++; |
2 | if( timer_5_ms == 100 ) { |
3 | timer_5_ms = 0; |
4 | |
5 | // tu was immer es alle 5 ms zu tun gibt
|
6 | }
|
Und genau das gleiche kannst du mit noch weiteren Variablen machen und dir so von den 0.05ms jede beliebige Zeitbasis ableiten, solange sie nur ein Vielfaches von 0.05ms ist. Du kannst diese Zähler auch verschachteln. Deine erste Variable wird alle 100ms auf 0 zurückgesetzt. Machst du in diesem if einen weiteren Zähler, der zb bis 10 zählt, dann wird dieser Zähler alle 10*100ms = 1 Sekunde auf 0 zurückgesetzt. Und an dieses Rücksetz-if kannst du natürlich wieder andere Aktionen kopppeln
1 | timer_005_ms ++; |
2 | if( timer_005_ms == 2000 ) { // das ist alle 100 ms der Fall |
3 | timer_005_ms = 0; |
4 | |
5 | timer_100_ms ++; |
6 | if( timer_100_ms == 10 ) { // das ist daher jede Sekunde der Fall |
7 | timer_100_ms = 0; |
8 | |
9 | // mach die Dinge, die jede Sekunde zu erledigen sind
|
10 | }
|
11 | }
|
Und ich muss dich enttäuschen. So neu ist diese Idee gar nicht :-) Du benutzt sie jeden Tag im täglichen Leben: Deine Uhr erzeugt ein Sekundensignal. Nach 60 Sekunden wird die Sekundenzahl wieder auf 0 gesetzt und dafür 1 Minute gezählt. Nach 60 Minuten geht die Minutenzahl wieder auf 0 und dafür gibt es 1 Stunde mehr. Nach 24 Stunden geht es wieder bei 0 Stunden weiter und dafür wird ein Tag gezählt, etc. etc.
1 | Sekunden++; |
2 | if( Sekunden == 60 ) { |
3 | Sekunden = 0; |
4 | |
5 | Minuten++; |
6 | if( Minuten == 60 ) { |
7 | Minuten = 0; |
8 | |
9 | Stunden++; |
10 | if( Stunden == 24 ) { |
11 | Stunden = 0; |
12 | |
13 | Wochentag++; |
14 | if( Wochentag == 7 ) { |
15 | Wochentag = 0; |
16 | |
17 | }
|
18 | }
|
19 | }
|
20 | }
|
Du kannst dich an jedem if dranhängen und so ganz leicht festlegen, ob Aktionen im Sekundentakt (auch zu bestimmten Sekunden) oder Minuten oder nur jede Stunde etc. passieren. Du kannst auch die Wochentage zu Wochen weiterführen. Oder anstelle von Wochentagen einfach nur Tage zählen. Wenn 30/31/28/29 Tage vergangen sind, wird dann ein Monat weitergezählt. Nach 12 Monaten 1 Jahr, etc. Was immer du willst. Andere Zahlenwerte, aber gleiches Prinzip.
Ja vom Prinzip, war dass auch meine Idee. Das mit dem Modulo bzw. Div sehe ich ein. Ich führe also nicht wie gedacht, mein ganzes Programm in der Interruptroutine aus sondern weiterhin per main. also mein code sieht dann in etwa so aus:
1 | int i = 0, timer_500ms = 0, timer_1s = 0, beep = 0; |
2 | |
3 | void main(void) |
4 | {
|
5 | SetTimer_16bit(); // Timer, Interrupt, etc. |
6 | beep_out(5); |
7 | while (1); |
8 | }
|
9 | |
10 | void beep_out(int dauer) |
11 | {
|
12 | beep = dauer; |
13 | }
|
14 | |
15 | void Timerint(void) __interrupt 1 |
16 | {
|
17 | timer_500_ms ++; |
18 | if( timer_500_ms == 2000xxx ) // das ist alle 500 ms der Fall |
19 | {
|
20 | if beep (!= 0) |
21 | P1_1 = !P1_1; |
22 | else
|
23 | P1_1 = 1; // Definitiv High (aus). |
24 | timer_50_ms = 0; |
25 | }
|
26 | timer_1_s ++; |
27 | if( timer_1_s == 10xxx ) { // das ist daher jede Sekunde der Fall |
28 | for (beep ; beep == 0 ; beep --); |
29 | |
30 | timer_1_s = 0; |
31 | }
|
32 | |
33 | |
34 | }
|
denke ich hab jetzt die entscheidende Idee bekommen, danke für deinen Denkanstoß.
Die Benutzung des Modulus bringt noch weitere Nachteile mit sich! Wenn Du beispielsweise alle 0.05s bis 2000 inkrementierst und dann zuruecksetzt koenntest Du mit MOD 1000 zwar noch relativ einfach einen 50ms-Trigger bauen, aber was machst Du, wenn Du nun einen 60ms-Trigger brauchst? Das Hochzaehlen und Ruecksetzen von weiteren Variablen ist hier definitiv der bessere Weg. Volker
Peter Max schrieb: > void Timerint(void) __interrupt 1 > { > timer_500_ms ++; > if( timer_500_ms == 2000xxx ) // das ist alle 500 ms der Fall > { > if beep (!= 0) > P1_1 = !P1_1; > else > P1_1 = 1; // Definitiv High (aus). > timer_50_ms = 0; > } > timer_1_s ++; > if( timer_1_s == 10xxx ) { // das ist daher jede Sekunde der Fall > for (beep ; beep == 0 ; beep --); Das hier wird dir jeder bessere Compiler rauswerfen. Das ist eine Schleife, die ausser Rechenzeit verbrauchen nichts macht. Wenn die Absicht aber war, dass der Beep genau 'beep' Sekunden dauern soll, dann kannst du das so machen: In der Funktion beep_out schaltest du den Beeper ein und merkst dir die gewünschte Zeitdauer in beep, wie gehabt. Hier an dieser Stelle zählst du beep um 1 runter, denn es ist ja 1 Sekunde vergangen. Ist beep danach 0, dann sind logischerweise seit dem Aufruf von beep_out genau die Anzahl an Sekunden vergangen, die beim Aufruf angegeben wurden. if( beep > 0 ) { beep--; if( beep == 0 ) schalte Beeper aus } > > timer_1_s = 0; > } > > > } > [/c] > > denke ich hab jetzt die entscheidende Idee bekommen, danke für deinen > Denkanstoß. Deine for-Schleife zeigt mir, dass du noch in Abläufen denkst. Du musst deine Denkweise umstellen. Du musst denken: Jetzt ist so und soviel Zeit vergangen, was gibt es jetzt, genau zu diesem Zeitpunkt, zu tun. Das machst du dann und beendest den Interrupt. Insbesondere wartest du keine Zeitdauern. Zeitdauern werden festgelegt, indem ausserhalb des Interrupts eine Funktion eine Zeitdauer (beepe für x Sekunden) in einen Zeitpunkt umwandelt. Im Interrupt wird die aktuelle 'Zeit' hochgezählt (oder wie hier, eine Art 'Eieruhr' heruntergezählt) und wenn der vorher berechnete Zeitpunkt erreicht ist (oder wie hier die Eieruhr auf 0 heruntergezählt wurde), die zugehörige Aktion ausgeführt. Denke also nicht in Einheiten von 'Der Prozessor muss die nächste Zeit dieses und jenes erledigen' sondern: ich lasse eine Aktion etwas starten; zu welchem Zeitpunkt muss mit einer anderen Aktion der Vorgang wieder gestoppt werden. Also nicht sequentiell denken, sondern in Ereignissen.
Ok, besonders meine for-Schleife ist doch eigentlich Käse. Die ist ja im Interrupt und würde nicht so funktionieren wie ich es vor habe. Gut gut ... denke das ganze nochmal durch und dann geht es heute Abend eventuell weiter. Gruß
Hallo alle zusammen, mit einiger Verspätung melde ich mich mit guten aber noch nicht perfekten Neuigkeiten. Im Anhang findet ihr meinen Quellcode meines kleinen Alarmanlagen - Projektes. Es klappt alles super, wenn da nicht doch ein paar Kleinigkeiten wären, die mich noch stören, oder die wie ich finde von mir noch nicht sehr elegant ( so gut es ginge, als Anfänger ) gelöst wurden oder werden können. Damit ihr nicht den gesamten Quellcode studieren müsst hier eine kurze Beschreibung: Der Interrupt zählt die einzelnen Variablen die dann die entsprechenden Funktionen ausführt, z.B. alle500ms ... usw. Da es keine zeitkritische Anwendung ist stört es nicht wenn einige Sachen etwas später passieren. Aber jetzt kommt es, bei der Funktion alle15min ... da passiert es :-) der "Bug" ... der logischer weise keiner ist, weil die Funktion so wie ist völlig richtig arbeitet. alle15min wird bisher nur für die Funktion verwendet, das Licht bei einem Alarm nach 15min wieder auszumachen, nun ... folgendes Szenario, nach dem aktivieren der Anlage, wird der Alarm nach 14 Min ausgelöst ... so und was passiert, das Licht wird bereits nach einer Minute deaktiviert. Natürlich könnte ich die 15min Variable nur im Alarmfall hoch zählen, aber dann wäre der Fehler der unter der Verwendung der alle1min Funktion immer noch minus 59 Sekunden, oder ?? Gibt es hierfür noch eine elegantere Lösung? Gruß Peter Max
Peter Max schrieb: > Der Interrupt zählt die einzelnen Variablen die dann die entsprechenden > Funktionen ausführt, z.B. alle500ms ... usw. > Da es keine zeitkritische Anwendung ist stört es nicht wenn einige > Sachen etwas später passieren. Aber jetzt kommt es, bei der Funktion > alle15min ... da passiert es :-) der "Bug" ... der logischer weise > keiner ist, weil die Funktion so wie ist völlig richtig arbeitet. Ja. Da hab ich dich in etwas hinein-theatert. Du kannst deine Zeitzähler ja zu jedem beliebigen Zeitpunkt wieder auf 0 zurücksetzen. Wenn dein Alarm auslöst, kannst du ja den 15 Minuten Zähler wieder auf 0 setzen. Damit beginnen dann die 15 Minuten erneut genau zu diesem Zeitpunkt zu laufen, wenn ... ja wenn dieser Zähler nicht vom 5 Minuten Zähler abhängen würde. > einem Alarm nach 15min wieder auszumachen, nun ... folgendes Szenario, > nach dem aktivieren der Anlage, wird der Alarm nach 14 Min ausgelöst ... > so und was passiert, das Licht wird bereits nach einer Minute > deaktiviert. > > Natürlich könnte ich die 15min Variable nur im Alarmfall hoch zählen, > aber dann wäre der Fehler der unter der Verwendung der alle1min Funktion > immer noch minus 59 Sekunden, oder ?? > Gibt es hierfür noch eine elegantere Lösung? Was ist das Problem? Du weißt nie, in welchem 'Zwischenstand' sich die ganze Zählerkette befindet. Das ist so, wie wenn du eine Uhr benutzt und sagst, dass 1 Minute vergangen ist, wenn sich die Minutenanzeige verändert. Ist dein erster Zeitpunkt genau bei 01 Sekunden, dann hast du fast keinen Fehler. Ist dein erster Zeitpunkt aber bei 59 Sekunden, dann hast du einen riesengroßen Fehler. Die Minute (nach der Def. dass eine Minute vergangen ist, wenn sich die Anzeige ändert) ist dann völlig falsch. Aber du musst ja nicht die 15 Minuten abzählen, indem du abwartest bis 3 mal 5 Minuten vergangen sind. Du weißt ja das 15 Minuten gleich 15*60 = 900 Sekunden sind. Wenn du deinen 15 Minuten-Zähler also nach jeder Sekunde um 1 erhöhst und stattdessen bis 900 laufen lässt, dann ist der 'Überlauf' auf 900 auch bei 15 Minuten, aber der Fehler ist dann maximal 1 Sekunde. Und das sollte reichen. Zu diesem Zwecke würde ich die Variable auch nicht timer_15_min nennen (auch wenn ich diesen Begriff so eingeführt habe) sondern den licht_timer, der alle 1 Sekunde um 1 erhöht wird und bis zu einer Obergrenze zählt und dann das Licht ausmacht. Willst du haben, dass diese Zeitdauer wieder von vorne anfängt, dann setzt du den licht_timer einfach wieder auf 0. Die Funktion dieses Timers verändert sich dann weg von 'alle 15 Minuten ab einschalten der Anlage' hin zu '15 Minuten nachdem der Timer zuletzt auf 0 gesetzt wurde'. Und das ist ja letztenedes das Ziel. Egal aus welchem Grund das Licht eingeschaltet wurde (wer diesen Timer auf 0 gesetzt hat), nach genau 15 Minuten (+- 1 Sekunde) geht es wieder aus.
1 | #define LIGHT_DURATION 900 // 900 Sekunden = 15 Minuten
|
2 | |
3 | unsigned int light_time; |
4 | |
5 | ...
|
6 | |
7 | void Timer0_int(void) __interrupt 1 { // Interrupt Service Routine von Timer0 |
8 | TL0 = 0x78; // Reset Timer Preload |
9 | TH0 = 0xEC; |
10 | |
11 | time50_ms ++; |
12 | |
13 | if (time50_ms == 10){ |
14 | time50_ms = 0; |
15 | time250_ms ++; |
16 | alle50ms(); |
17 | };
|
18 | |
19 | if (time250_ms == 5){ |
20 | time250_ms = 0; |
21 | time500_ms ++; |
22 | alle250ms(); |
23 | };
|
24 | |
25 | if (time500_ms == 2){ |
26 | time500_ms = 0; |
27 | time1_s ++; |
28 | alle500ms(); |
29 | |
30 | if( light_time < LIGHT_DURATION ) { |
31 | light_time++; |
32 | if( light_time == LIGHT_DURATION ) |
33 | // Licht ausschalten
|
34 | }
|
35 | }
|
36 | |
37 | ...
|
Du solltest dir auch überlegen, dasselbe Schema auch auf andere Dinge anzuwenden. So gesehen hast du 2 verschiedene 'Arten' von Timern. Die einen ticken ständig und regelmässig vor sich hin, sobald die Anlage unter Spannung steht. Die anderen fangen mit ihrer Zählerei immer dann wieder von vorne an, wenn ihr zugehöriger Zähler auf 0 gesetzt wird. Bei manchen Dingen (wie zb dem light_timer) ist es auch oft einfacher, die Zeit nicht hoch, sondern herunter zu zählen, wie eine Eieruhr. Der Wert in der Variablen sagt dir dann wieviel Zeit noch vergehen muss, bis das Ereignis eintreten wird. Das kann zb dann sinnvoll sein, wenn dein Licht im Normalfall nach 15 Minuten ausgehen soll, im Alarmfall aber 30 Minuten brennen soll. Du musst dann einfach nur die gewünschte Zeitdauer (in Sekunden) dem light_timer zuweisen und brauchst dich in der ISR nicht darum kümmern, welche Sonderfälle gelten. Ist der timer nicht 0, so wird er um 1 heruntergezählt (das soll verhindern, dass der timer ins 'negative' zählt) und wenn der timer auf 0 runterkommt, dann wird ausgeschaltet. In der ISR ist es dabei völlig egal, mit welchem Wert der timer anfängt bzw. auf welchen Wert er zwischendurch (auch wenn er schon runtergezählt hat) gesetzt wird.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.