Forum: Mikrocontroller und Digitale Elektronik Ablauftimer geschickt integrieren - Code-Optimierung


von Mike (Gast)


Lesenswert?

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?

von Oliver S. (oliverso)


Lesenswert?

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

von Micha H. (mlh) Benutzerseite


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Mike (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Mike (Gast)


Lesenswert?

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!

von Luigi (Gast)


Lesenswert?

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...

von Karl H. (kbuchegg)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?


von Ralf G. (ralg)


Lesenswert?

Wenn eine kurze Verzögerung unkritisch ist, und du dir das Denken in 
10er Potenzen abgewöhnst:
Ich mach das immer so:
1
ISR(TIMER2_OVF_vect)
2
{
3
  static uint8_t cnt;
4
  cnt++;
5
  Messages |= MSG_TIMERx1;
6
  if (! ((cnt + 1) & 0x03) )
7
    Messages |= MSG_TIMERx4;    
8
  if (! ((cnt + 3) & 0x07) )
9
    Messages |= MSG_TIMERx8;    
10
  if (! ((cnt + 7) & 0x3F) )
11
    Messages |= MSG_TIMERx64;    
12
}
(Die konstante Addition zu 'cnt' soll bewirken, dass die Nachrichten 
etwas verteilt werden.)

von Nosnibor (Gast)


Lesenswert?

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.

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
Noch kein Account? Hier anmelden.