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
inti=0,timer_500ms=0,timer_1s=0,beep=0;
2
3
voidmain(void)
4
{
5
SetTimer_16bit();// Timer, Interrupt, etc.
6
beep_out(5);
7
while(1);
8
}
9
10
voidbeep_out(intdauer)
11
{
12
beep=dauer;
13
}
14
15
voidTimerint(void)__interrupt1
16
{
17
timer_500_ms++;
18
if(timer_500_ms==2000xxx)// das ist alle 500 ms der Fall
19
{
20
ifbeep(!=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.
voidTimer0_int(void)__interrupt1{// 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.