Hallo zusammen,
ich habe ein "Detail"-Verständnisproblem im Umgang mit Timern.
Die Funktion, die ich programiert habe, läuft nicht so, wie ich es gerne
hätte.
Ich schreibe mal was ich vorhabe, daher zeige ich keinen Code, es geht
hier um das Verständnis.
Ich möchte zwei Ausgänge zeitabhängig schalten. Bisher habe ich immer
delays benutzt, möchte aber zukünftig mit Timerinterups arbeiten.
Ablauf: Ausgang 1 50ms einschalten, 150ms warten, dann Ausgang 2 80ms
Einschalten, wieder 150ms warten und dann evtl wiederholen.
Mein Gedankengang war: Timer arbeitet im CTC-Mode.
Die Abfolge Ausgänge schalten erledige ich mit der switch Anweisung,
schalte dort den Timer ein und schreibe die Zeiteinheit in das OCRA
Register. Jetzt warte ich in der Interruptroutine auf den Overflow,
schalte dort den Timer aus und zähle die VAriable für die
switch-Anweisung eins höher.
Ist dieser Ablauf richtig, oder wie löst man solche Probleme? Hier noch
mal kurz zusammengefasst:
1
switch (variable)
2
case 1:
3
Ausgang 1 setzen
4
OCRA "laden" mit 50ms
5
Timer einschalten (CS-Register setzen)
6
switch-variable um eins erhöhen
7
break;
8
9
case2:
10
hier passiert nichts
11
ich warte lediglich auf den Interrupt, der die switchVariable um eins erhöht und den Timer stoppt.
12
daher steht hier lediglich "break"
13
14
case3:
15
Ausgang 1 rücksetzen
16
OCRA "laden" mit 150ms (Wartezeit)
17
Timer einschalten (CS-Register setzen)
18
switch-variable um eins erhöhen
19
break;
20
21
case4:
22
hier passiert nichts
23
ich warte lediglich auf den Interrupt, der die switchVariable um eins erhöht und den Timer stoppt.
24
daher steht hier lediglich "break"
25
26
usw...
27
//ende switch
28
29
In der Interruptroutine steht
30
ISR(TIMER1_COMPA_vect) //CTC Mode, Vergleich mit OCRA
31
{
32
switchVariable um eins erhöhen (wurde im Code mit volatile definiert)
Wieso lässt du den Timer nicht mit 1ms laufen und verwendest Zähler?
Also TimerInterupt alle 1ms in dem ein Zähler hochgezählt wird..
Zähler == 0 --> Ausgang1 = HIGH
Zähler == 50 --> Ausgang1 = LOW
Zähler == 200 --> Ausgang2 = HIGH
Zähler == 280 --> Ausgang2 = LOW
Zähler == 330 --> Zähler = 0
Mike schrieb:> Die Abfolge Ausgänge schalten erledige ich mit der switch Anweisung,> schalte dort den Timer ein
Diese Denkweise ist meistens schlecht.
Es gibt zwar Fälle in denen man Timer ein/aus schaltet, aber einem
Anfänger würde ich davon abraten.
Der Timer wird am Programmanfang gestartet und läuft dann durch!
Genauso wie deine Armbanduhr auch die ganze Zeit tickt. Wenn du damit
was stoppen willst, dann stellst du die ja auch nicht auf 0 und
'startest sie', sondern du lässt sie die ganze Zeit weiterlaufen. Willst
du 10 Sekunden stoppen, dann machst du das (in diesem Fall) anders. Du
schaust auf die Uhr, siehst den Sekundenzeiger bei 28, rechnest im Kopf
ganz schnell 28+10 macht 38 und dann schaust die immer wieder auf den
Sekundenzeiger, ob der bei 38 ist.
Oder du schaust nicht auf den Sekundenzeiger, hörst aber die Uhr
ticken,. Und dann zählst du mit jedem Tick den du hörst: 10, 9, 8, 7, 6,
... 2, 1, 0.
Aber die Uhr an sich ... die läuft die ganze Zeit weiter.
Und genau das gleiche machst du auch mit dem Timer im µC.
vergiss dieses 'ich starte den Timer, ich stoppe den Timer' etc. Der
Timer läuft durch! Die ganze Zeit!
Das was bei deiner Armbanduhr das 'Ticken' des Uhrwerks ist, dass ist
bei deinem Timer der Aufruf einer ISR. Das ist dein Ticken. Richtest du
den Timer so ein, dass eine ISR (hausnummer) alle 1 ms aufgerufen wird,
dann ist deine Aufgabenstellung lediglich das Runterzählen von ein paar
Zählvariablen bis auf 0 innerhalb dieser ISR und zugehöriges Schalten
von Ausgängen.
(Ist eine Möglichkeit. Es gibt auch andere, wie du ja auch deine
Armbanduhr auf verschiedene Art und Weise zum Stoppen von Zeiträumen
benutzen kannst)
Edit:
Ich möchte noch hinzufügen, dass man deine konkrete Problemstellung
natürlich auch so lösen kann, wie du das vorschlägst. Kann man. Aber die
hat den Nachteil, dass du damit den Timer spezifisch auf nur diese eine
Aufgabe festnagelst. Die andere Version ist die wichtigere. Denn mit der
kannst du auch 100 Ausgänge nach irgendwelchen zeitlichen Mustern
schalten lassen. Diese Technik verallgemeinert daher viel besser, wenn
deine Aufgabenstellung mal anders aussieht.
Vielen Dank schon mal für die Antworten.
Aber wie stelle ich das konkret an?
Bei dem Schalten von Ausgängen warte ich auf ein Zeichen von "aussen":
Ich frage mit meinem Programm einen Taster ab.
- dann wird der Ausgang geschaltet (ich merke mir die zeit)
- programm läuft weiter
->1 komme ich an dem punkt, an dem im programm der zähler abfragt wird,
bin ich erst bei 49ms oder aber ich habe unter umständen schon 78ms
erreicht
->2 verlagere ich die "intelligenz" in die ISR:
50ms sind um, dann tue das, abhängig vom vorherigen zustand und
welcher Ausgang kommt als nächstes?...
Folge: ich blähe die ISR auf, was ja nicht gewünscht ist
ich kriege momentan die kurve noch nicht, die ISR klein zu halten, die
intelligenz im Programm zu verarbeiten aber die Vorgänge zeitgenau zu
steuern
Mein Programm ist etwas größer und braucht für einen durchlauf ein paar
ms
Mike schrieb:> Vielen Dank schon mal für die Antworten.>> Aber wie stelle ich das konkret an?
Indem du dir zb für jeden 'Zeitraum' eine Variable einrichtest.
Die könnte zb die Wertbedeutungen haben
1
-1 mach nichts, Zeit läuft nicht
2
0 die Zeit ist abgelaufen
3
>0 die noch verbleibende Restzeit
In der ISR machst du dann
1
volatileint8_tZeit1;
2
3
ISR(...)// die richtest du zb für 1ms ein
4
{
5
if(Zeit1!=-1)// läuft die Zeit überhaupt?
6
{
7
Zeit1--;
8
if(Zeit1==0)// Zeit abgelaufen
9
{
10
...machewasimmereszumachengibt
11
...zbPinsabschalten,zbandereZeitenstarten
12
}
13
}
14
}
gestartet wird das ganze dann ganz einfach dadurch, dass du der
Variablen Zeit1 einen Wert zuweist, nämlich die Anzahl der MIllisekunden
nach der irgendwas wieder abgeschaltet werden soll.
Das einschalten machst du in der Hauptschleife, du setzt Zeit1 auf die
gewünschte Zeit und innerhalb der ISR, wenn die Zeit abgelaufen ist,
schaltest du den Ausgang wieder ab.
Ganz einfach.
Mike schrieb:> Vielen Dank schon mal für die Antworten.>> Aber wie stelle ich das konkret an?> Mein Programm ist etwas größer und braucht für einen durchlauf ein paar> ms
Dann würde ich das Schalten der Ausgänge in die ISR verlegen.
> die ISR klein zu halten
Man muss nicht auf Biegen und Brechen ALLES aus einer ISR raushalten. In
einer ISR darf ruhig auch ein bischen was gemacht werden. Nicht ekzessiv
aber doch. Eine Variable abfragen, bei Bedarf dekrementieren und einen
Ausgang schalten ist noch lange nicht exzessiv. Eine Ausgabe auf LCD
oder UART wäre exzessiv. Aber den Fall hast du ja nicht.
Man hat einen Zaehler(Variable), die wird im Interrupt, sofern ungleich
null decrementiert. Im interrupt setzt man ein Flag(Boolean) wenn der
Interrupt kam. Also muss man im main nur irgendwo :
if (flag) {
if (zehler=0) {...}
flag=0
}
Karl Heinz Buchegger schrieb:> gestartet wird das ganze dann ganz einfach dadurch, dass du der> Variablen Zeit1 einen Wert zuweist, nämlich die Anzahl der MIllisekunden> nach der irgendwas wieder abgeschaltet werden soll.> Das einschalten machst du in der Hauptschleife, du setzt Zeit1 auf die> gewünschte Zeit und innerhalb der ISR, wenn die Zeit abgelaufen ist,> schaltest du den Ausgang wieder ab.> Ganz einfach.
Er möchte aber eine definierte Sequenz fahren...
Daniel V. schrieb:> Karl Heinz Buchegger schrieb:>> gestartet wird das ganze dann ganz einfach dadurch, dass du der>> Variablen Zeit1 einen Wert zuweist, nämlich die Anzahl der MIllisekunden>> nach der irgendwas wieder abgeschaltet werden soll.>> Das einschalten machst du in der Hauptschleife, du setzt Zeit1 auf die>> gewünschte Zeit und innerhalb der ISR, wenn die Zeit abgelaufen ist,>> schaltest du den Ausgang wieder ab.>> Ganz einfach.>> Er möchte aber eine definierte Sequenz fahren...
Niemand hindert ihn daran, innerhalb der ISR durch Zuweisung an eine
2.te Zähle-Time-variable diesen 2.ten Zeitraum zu starten. Genau
deswegen hab ich auch postuliert, dass ein Zählwert von -1 in der
Time-variable die Bedeutung von (Zeit läuft momentan nicht) hat.
Das ist eine Möglichkeit.
Eine andere wäre es, alle Zeiten der Squenz zu addieren, einen
entsprechenden Zähler dafür aufzusetzen und dann in der ISR anhand des
aktuellen Zählwertes die entsprechenden Ausgänge zu schalten.
Viele Wege führen nach Rom.
> Mein Programm ist etwas größer und braucht für einen durchlauf ein paar ms
Dann sieh zu, dass du als erstes mal alle _deley_ms los wirst. Jetzt
weißt du auch warum delays (ausser in ganz kurzen Fällen, wie sie zur
Protokollerzeugung machmal gebraucht werden), kein taugliches Mittel für
echte Programme sind.
_delay_ms ist gut, weil Anfänger auch mal irgendwas brauchen um ihre
Programm ins Laufen zu kriegen. Aber irgendwann ist der Punkt erreicht,
an dem man einsehen muss, dass _delay_ms eine Sackgasse ist. Für die
ersten Programme ok, aber irgendwann müssen die Stützräder am Fahrrad
weg und man muss richtig Rad fahren.
Karl Heinz Buchegger schrieb:> -1 mach nichts, Zeit läuft nicht> 0 die Zeit ist abgelaufen> >0 die noch verbleibende Restzeit> In der ISR machst du dann
1
volatileint8_tZeit1;
2
3
ISR(...)// die richtest du zb für 1ms ein
4
{
5
if(Zeit1!=-1)// läuft die Zeit überhaupt?
6
{
7
Zeit1--;
8
if(Zeit1==0)// Zeit abgelaufen
9
{
10
...machewasimmereszumachengibt
11
...zbPinsabschalten,zbandereZeitenstarten
12
}
13
}
14
}
Warum baust du da noch -1 ein?
1
if(Zeit1)
2
{
3
Zeit1--;
4
if(!Zeit1)
5
{
6
//mach was
7
}
8
}
Das tut es doch genauso. Mit dem Unterschied, daß im nächsten Interrupt
die Variable nicht auf -1 gesetzt wird, sondern auf 0 stehen bleibt.
Rein akademisch betrachtet, lässt sich Zeit dann sogar auf 255 setzen.
mfg.
Daniel V. schrieb:> Thomas Eckmann schrieb:>> Rein akademisch betrachtet, lässt sich Zeit dann sogar auf 255 setzen.>>>> mfg.>> 255 ist doch -1
Ach ja? Du hast überhaupt nichts verstanden.
mfg.
Thomas Eckmann schrieb:> Warum baust du da noch -1 ein?
gute Frage.
Frage ich mich im Moment auch.
Irgendwie hatte ich das Gefühl, dass eine Unterschiedung zwischen
* die Zeit läuft grundsätzlich nicht, und
* die Zeit ist abgelaufen
sinnvoll wäre.
Wenn die Auswertung innerhalb der Hauptschleife passiert, ist das
manchmal sinnvoll. Aber hier sehe ich momentan auch nicht, was das
bringen sollte.
Oh ha, vielen Dank!
Sehr viele neue Denkansätze.
Das hilft mir sehr, ich danke euch.
Jetzt muss ich nur noch mein Programm dahingehen umbauen.
Falls Probleme auftauchen melde ich mich noch einmal.
Die Delays habe ich mittlerweile alle raus.
Aber ich habe sehr viele ADC-Messungen mit Mittelwertbildung im
Programm, die etwas Zeit benötigen.
Ich schaue mal, wie ich zurechtkomme.
Daniel V. schrieb:> Thomas Eckmann schrieb:>> Rein akademisch betrachtet, lässt sich Zeit dann sogar auf 255 setzen.>>>> mfg.>> 255 ist doch -1
darum gehts nicht.
Es geht darum, dass ich einen speziellen Wert postuliert habe, für ....
eigentlich nichts.
Es geht auch darum, dass ich dafür akzeptiert habe, den möglichen
Wertebereich für die Zeiten von 255ms auf maximal 127ms einzuschränken.
Speziell letzteres ist oft eine schwerwiegende Limitierung.
Thomas Eckmann schrieb:> Daniel V. schrieb:>> Thomas Eckmann schrieb:>>> Rein akademisch betrachtet, lässt sich Zeit dann sogar auf 255 setzen.>>>>>> mfg.>>>> 255 ist doch -1
rein akademisch betrachtet.
>> Ach ja? Du hast überhaupt nichts verstanden.>> mfg.
Doch, doch. Du hast 255 zur Verfügung, KH nur die Hälfte. Wobei man dann
auch ein uint8_t Zeit1 verwenden könnte.
Mike schrieb:> Die Delays habe ich mittlerweile alle raus.> Aber ich habe sehr viele ADC-Messungen mit Mittelwertbildung im> Programm, die etwas Zeit benötigen.
OK.
Allerdings muessen die ja meistens nicht 'in einem Rutsch' gemacht
werden.
Die Messungen kann man oft ja auch auf mehrere Durchgänge der
Hauptschleife aufteilen.
Im Prinzip ist das eigentlich sowas wie ein allgemeines Prinzip in der
µC-Programmierung. Mach in einem Durchgang durch die Hauptschleife eine
Sache. Mache diese Sache gut und sieh zu, dass sie nicht zu lange
dauert. Wenn du dazu einen Zähler mit dazunimmst, dann kannst du pro
Durchgang 1 Sache machen, entsprechend mitzählen und nach n derartigen
'Sachen' wertest du die gesammelten Daten aus.
Das ist so einer der Schlüssel zu Programmen, die scheinbar mehrere
Dinge gleichzeitig machen können: keine exzessiv lang andauernden
Teilprozesse. Oder salopp ausgedrückt: gewartet wird auf nichts und
niemanden.
>Aber ich habe sehr viele ADC-Messungen mit Mittelwertbildung im
Programm, die etwas Zeit benötigen.
Ein Mittelwert ist eine eher einfache Sache : Siehe auch
http://www.ibrtses.com/embedded/exponential.html
Ein paar additionen und shifts pro messwert.
Mike schrieb:> Die Delays habe ich mittlerweile alle raus.> Aber ich habe sehr viele ADC-Messungen mit Mittelwertbildung im> Programm, die etwas Zeit benötigen.
Müssen sie nicht.
Man muß nicht n Werte einlesen, aufaddieren, dividieren. Dann die
nächsten n Werte und so weiter und so fort...
Sondern man liest einen Wert ein, addiert auf und schreibt den
eingelesenen Wert in ein Array der Grösse n. Aus dem Array holt man sich
den ältesten Wert und subtrahiert ihn vom noch nicht geteilten
Durchschnittswert.
Dann besteht Mittelwertbidung aus einmal Addieren, einmal Subtrahieren
und einmal Dividieren und nicht n Mal Addieren und einmal Dividieren.
Dividieren sollte man auch nicht durch 10 oder irgendeinen anderen
krummen Wert. Sondern immer durch Zweierpotenzen 8, 16, 32... Damit wird
nur geschoben statt dividiert.
Auf den ADC muß man auch nicht ständig warten, sondern erledigt das in
der ADC-ISR. Kleinigkeiten kann man nämlich sofort erledigen.
Kostet natürlich ein bisschen RAM. Für einen Attiny13 ist das nichts.
Aber beim Atmega gibt es für nicht verwendeten RAM kein Geld zurück. Und
wenn man nicht zum Atmega8 gezwungen wird, kann man auch einen 328 mit
doppelt soviel RAM verwenden. Falls es doch knapp wird.
Man kann sich das Array aber auch sparen, indem man, an Stelle des
ältesten Wertes, vom Durchschnittswert Durchschnittswert/n abzieht. Dann
wird es allerdings ein bisschen ungenauer und kostet durch die weitere
Division(Shift) etwas mehr Rechenzeit.
mfg.
Thomas Eckmann schrieb:> Auf den ADC muß man auch nicht ständig warten, sondern erledigt das in> der ADC-ISR.
Ich mach das gerne auch schon mal so, dass ich die Reihenfolge
vertausche
1
...
2
3
Wert holen
4
ADC für den nächsten Messwert starten
5
Wert verarbeiten
6
....
d.h. der ADC arbeitet für sich eigenständig dahin, während das Programm
was anderes macht. Kommt das Pgm beim nächsten Durchlauf durch die
Hauptschleife wieder zur Stelle der ADC Abfrage, dann ist der ADC längst
wieder fertig, ich hol mir den Wert und starte sofort die nächste
Messung. Die läuft dann, während der zuletzt geholte Wert bearbeitet
wird und das Pgm wieder was anderes macht.
Im Grunde besteht der 'Trick' an dieser Stelle einfach nur in der
Erkenntnis, dass der ADC ja auch ganz alleine arbeiten kann und dabei
das Programm nicht benötigt.
Karl Heinz Buchegger schrieb:> Im Grunde besteht der 'Trick' an dieser Stelle einfach nur in der> Erkenntnis, dass der ADC ja auch ganz alleine arbeiten kann und dabei> das Programm nicht benötigt.
Bei ADC-Kleinigkeiten, wie Potis einlesen oder Batteriespanung messen,
benutze ich den ADC-Interrupt meist gar nicht, sondern mache das in der
Timer0-ISR, indem ich den ADC mit Timer0 triggere. Bei einem üblichen
1-ms-Systick ist der ADC beim Eintritt in die Timer-ISR (schon
lange)fertig und der ADC-Wert kann dort verarbeitet oder zumindest zur
weiteren Verwendung eingelesen werden. Spart eine ISR und den damit
verbundenen üblichen Overhead.
Zum Videos digitalisieren ist das natürlich zu langsam. Aber meistens
reicht das. Handbediente Potis muss man nicht 100000 Mal in der Sekunde
einlesen.
mfg.
Dieser Thread entpuppt sich als wahrer Wissenspool,bzw. Wissensquelle
für effiziente Programmierung!
Ich bin begeistert und lese gerne mit - jeden Beitrag. Interessant ist
auch die unterschiedliche Sichtweise.
Ich habe am WE mein Programm umgebaut. Ich hatte eigentlich noch fragen,
aber durch weitere diskussionen und Anregeungen hier im Thread, konnte
ich mir selbst helfen.
Danke und Gruß
Mike