Hallo zusammen,
ich beschäftige mich gerade mit Code-Optimierung und frage mich, wie man
mehrere Ablauftimer, die zur gleichen Zeit überwacht werden geschickt
integrieren kann. Da muss es doch schlanke Lösungen geben.
Bisher habe ich immer einen Timer laufen lassen, eine Variable erzeugt
und die Zeit, die ich warten möchte, auf den aktuellen counter
aufaddiert (ablauftimer > counter).
Als nächstes - im Programm - benutze ich dann immer eine if-Abfrage
1
if(ablaufzeit<=counter)
2
//dann mach irgendwas
Aber das ist nicht ganz so elegant.
1. Was ist im Falle eines Überlaufs (wie macht ihr es)
2. eine if-abfrage gegenüber einer zahl != 0 kostet zeit und
Prozessortakte
Erste Abhilfe: Ich erzeuge ein Konstrukt, dass gegen 0 abgfragt:
1
if(ablaufzeit==0)...
2
bzw.if(!Ablaufzeit)...
Erweitert: Wenn ich wenige Ablauftimer habe, kann ich das in der ISR
erledigen (CodeBeispiel hier aus dem Forum, Tipp von Peter D.)
Problem: habe ich jetzt viele Timer, die überwacht werden, kann bzw.
möchte ich nicht meine ISR aufblähen und 10 Timer gegen 0 abfragen.
Da muss es doch elegant Lösungen geben. Ich habe schon gesucht, aber
vermutlich nicht die richtigen Suchworte gefunden.
Ich habe etwas interessantes gefunden, was in eine ähnliche Richtung
geht: beliebige Zeitmessung mit Überlauferkennung durch modulo-operation
und Overflowerkennung, wenn der timer einmal komplett durchgelaufen ist.
High-Speed capture mit ATmega Timer: Zeitmessung
Habt ihr einen Tipp für mich? Oder besser noch: einen Link oder
Beispiele?
Was ist jetzt genau dein Problem?
Jeder deiner Timerabfragen kostet in der ISR nur wenige Zyklen. Wenn du
die nicht hast, ist dein Prozessor für die Aufgabe zu langsam. DAs
glaube ich aber nicht. In normalen Anwendungen mach dir da einfach mal
keinen Kopp.
Oliver
In einem meiner Projekte habe ich einen "Endlos"-zähler laufen, ähnlich
dem Unix-timestamp. Das verhindert Probleme durch Überlauf, aber ich muß
natürlich immer auf t > 0 vergleichen.
Da ich nur eine sekundengenaue Granularität brauche, wird der
32bit-Zähler mich und die Hardware lange überleben.
Nur mal so als Einwurf von der Seite und als Beispiel.
M
Mike schrieb:> Als nächstes - im Programm - benutze ich dann immer eine if-Abfrage>
1
>if(ablaufzeit<=counter)
2
>//dann mach irgendwas
3
>
>> Aber das ist nicht ganz so elegant.> 1. Was ist im Falle eines Überlaufs (wie macht ihr es)
Die Variablen grundsätzlich unsigned.
Die Abfrage dann als
1
if((endzeit-startzeit)>wartezeit)
durch die unsigned Rechnung eliminiert sich dann das Problem von
Variablen-Überläufen von selbst. Es kommt immer das richtige Ergebnis
raus, solange die Zeit an sich nicht schon 'übergelaufen' ist.
> 2. eine if-abfrage gegenüber einer zahl != 0 kostet zeit und> Prozessortakte
so schlimm ist das auch wieder nicht.
> Erweitert: Wenn ich wenige Ablauftimer habe, kann ich das in der ISR> erledigen (CodeBeispiel hier aus dem Forum, Tipp von Peter D.)
Natürlich.
> Problem: habe ich jetzt viele Timer, die überwacht werden, kann bzw.> möchte ich nicht meine ISR aufblähen und 10 Timer gegen 0 abfragen.
10 sind doch nicht viel.
generell: Die Aussage "Eine ISR so kurz als möglich" bedeutet nicht,
dass man in einer ISR gar nichts machen darf. Man darf nichts machen,
was lange dauert, aber das schliesst ein paar Variablenmodifikationen
nicht aus. Die paar Takte hast du auf jeden Fall.
Eine ISR schlägt grundsätzlich minimal mit ca 20 bis 25 Takten zu Buche
(durch den Grundaufsatz mit Register sichern etc). Wenn du da noch
weitere 30 Takte verbrutzels, um 10 Variablen zu inkrementieren und auf
einen Wert zu prüfen, dann ist das vertretbar.
Nicht vertretbar wäre es, etwas auf ein LCD auszugeben und dabei bei der
Kommunikation mit dem LCD Mykrosekundenweise warten zu müssen oder
darauf zu warten, dass ein motorgetriebener Schlitten seine Endposition
erreicht.
> Habt ihr einen Tipp für mich?
Optimiere Dinge erst dann, wenn sie zum Problem werden.
Solange du weit weg von einem Laufzeitproblem bist, hat es keinen Sinn
da Takte rauszuschinden. Für Däumchen drehende Prozessoren gibt es kein
Geld zurück.
Vielen Dank Karl Heinz!
Bezüglich der ISR hatte ich schon einige Bauchschmerzen.
- Variable runterzählen
- if-abfrage
- variable manipulieren (erfordert zusätzlich eine globale variable)
und das mit mehreren Timer-variablen.
Aber OK, wenn das in ordnung geht, dann mache ich erst mal so weiter.
Karl Heinz schrieb:> Optimiere Dinge erst dann, wenn sie zum Problem werden.> Solange du weit weg von einem Laufzeitproblem bist, hat es keinen Sinn> da Takte rauszuschinden.
Ich möchte gerne von Anfang an möglichst "sauber" und ordentlich
programmieren und nicht erst damit anfangen, wenn das Kind in den
Brunnen gefallen ist.
Denn zu diesem Zeitpunkt bisher angesammelte Gewohnheiten plötzlich über
Bord zu werfen ist für ein Gewohnheitstier schwierig.
Trotzdem vielen Dank für den optimismus.
Bin trotzdemfür jeden Tipp dankbar. Je früher man sich grundsätzliche
Dinge richtig aneignet, desto weniger Probleme treten später bei
größeren Projekten auf.
Mike schrieb:> Ich möchte gerne von Anfang an möglichst "sauber" und ordentlich> programmieren
sauber und ordentlich programmierst du dann, wenn das Programm leicht zu
verstehen ist und leicht zu verifizieren ist, dass es korrekt ist.
> und nicht erst damit anfangen, wenn das Kind in den> Brunnen gefallen ist.
'Optimieren bei Bedarf' bedeutet ja nicht, dass man dem µC
unsinnigerweise Prügel zwischen die Beine wirft und dann eine
Unschuldsmine aufsetzt.
D.h. man behält natürlich schon eine vernünftige Programmstruktur im
Auge und programmiert nicht unsinnigerweise und absichtlich
anti-optimal.
Aber du brauchst das auch nicht überbewerten. Es gibt nur ganz wenige
Fälle, in denen es tatsächlich um jeden Taktzyklus geht. Zum Beispiel
die Generierung eines Videosignals. Aber ob ein Relais jetzt eine halbe
Millisekunde früher oder später anzieht, ist ziemlich egal. Da ist die
Unsicherheit durch die Mechanik schon größer. Eine halbe Millisekunde
sind aber für einen µC eine kleine Ewigkeit.
Eine Frage die jetzt noch aufgetaucht ist:
BTW ich programmiere gerne auf Atmel-Controllern)
Ich habe bis jetzt jetzt zeitkritische Dinge im ms-Bereich in einer ISR
verarbeitet (Wenn etwas passieren soll, setze ich im Code meine Variable
(condition) auf z.B. 8 (8x10ms = 80ms):
1
IST(Timer)
2
{/* Interrupt alle 10ms */
3
4
if(condition)
5
{
6
condition--;/* 10ms countdown */
7
if(!condition)
8
{/* time expired */
9
PORTB|=0x01;
10
}
11
}
12
}
Auf der anderen Seite habe ich unkritische Dinge direkt in der main.c
verarbeitet:
1
if(Eintrittsbedingung)/* damit nächste if-abfrage "aktiv" ist und nicht zufällig unerwünschte Effekte auslöst */
2
{
3
if((switch_off_time==counter))/* counter increase every 500ms */
4
{/* switch off, if time is elapsed */
5
switch_off=TRUE;/* ready to switch off relays */
6
}
7
}
mit dem Nachteil, das eine zweite if-abfrage hinzukommt, um unerwünschte
effekte zu vermeiden - und damit war bzw. bin ich nicht glücklich!
Idealerweise würde ich dir vorschlagen, du schaust dir einfach hin und
wieder mal den disassembler-code von dem was du da machst an. Und wenn
du dann überlegst, wieviel Prozessorlast das am Ende ausmacht kannst du
dir ausrechnen wo du optimieren willst.
Aber globale Variablen (volatiles) in ner ISR empfehlen sich z.B. in
eine lokale zwischen zuspeichern, wenn du was optimieren willst. Dann
muss der Prozessor nicht jedes mal laden und speichern, sondern kann
auch auch die Arbeit mit der Variable optimieren. Dazu gibts aber viele
Fragen und Antworten, das brauchst du nicht erneut zu fragen, sondern
kannst die Suche betätigen.
Ansonsten, sind eigentlich nur große while-, for- oder sonstige Loops
"böse". (was nicht heißen soll, dass sie immer langsamer sind, bei manch
einem Prozessor, z.B. STM32, kann es schon sein, dass eine Loop
schneller ausgeführt werden kann als Spaghetti-Code. Stichwort: Cache)
Im Großen und Ganzen ist aber "überschaubarer Code", also solcher, bei
dem man noch mit durchschnittlichem Verstand die Prozessorschritte
erahnen kann vollkommen vernachlässigbar. Da dauert dann der Overhead
der ISR (register sichern etc.) schon länger...
Mike schrieb:> mit dem Nachteil, das eine zweite if-abfrage hinzukommt, um unerwünschte> effekte zu vermeiden
was verstehst du unter 'unerwünschte Effekte' und wie aufwändig ist die
Abfrage?
> - und damit war bzw. bin ich nicht glücklich!
wenn ich so meine Programme im Kopf revue passieren lasse, dann würde
ich auch dieses hier in die ISR verschieben, die den Increment von
'counter' macht. Denn zum Inkrementieren muss counter in der ISR sowieso
in ein Register geladen werden. Ob dann noch ein Vergleich mit einer
Abschaltgrenze nachfolgt - das sind nur noch wenige Takte mehr.
Wenn es wirklich viele Timer werden, hilft es, wenn sie sortiert sind.
Dann prüft man immer nur den Timer, der zuerst ablaufen soll. Solange
der nicht abgelaufen ist, braucht man sich die anderen gar nicht
anzusehen.
Der Nachteil dabei ist, daß man beim Starten eines Timers diesen in die
Liste einsortieren muß, d.h. hier hat man jetzt die ganzen Vergleiche
mit den anderen Timern, die man sich im Interrupt eingespart hat.
Außerdem ist das Löschen/Abbrechen eines Timers (falls es nötig ist)
aufwendiger.
Wie man die Liste aufbaut (mit Zeigern oder Indizes, einfach oder
doppelt verkettet, ...) hängt einerseits vom Prozessor ab (auf einem ARM
würde ich Zeiger nehmen, auf 'nem AVR möglichst 8bit-Indizes) und
andererseits von der Anwendung, also welche Vorgänge häufiger vorkommen
(laufen Timer normalerweise ab oder werden sie meist vorzeitig
gelöscht?).
Listenverwaltung mit Beteiligung eines Interrupts ist natürlich eine
haarige Sache, sollte aber meist ohne zu viele volatiles und
Interruptsperren zu schaffen sein.