Forum: Mikrocontroller und Digitale Elektronik Funktion zu zeitaufwändig?


von Hans B. (hansb)


Lesenswert?

Hallo zusammen,

ich habe eine Funktion (myZeit()) geschrieben, die in der Timer ISR jede 
Millisekunde aufgerufen wird.
Die Funktion soll mir Rückschlüsse über die Laufzeit des uCs geben, also 
wie lang ist er schon eingeschaltet.

Die ISR an sich wird alle 500 us aufgerufen.

varZeit ist ein struct:
1
typedef struct
2
{
3
 int16_t Millisekunde;
4
 byte Sekunde;
5
 byte Minute;
6
 byte Stunde;
7
 INT16_t Tag;
8
 byte Jahr;
9
}strZeit;
10
11
12
void myZeit()
13
{
14
  if(++varZeit.Millisekunde == 1000)
15
  {
16
    varZeit.Millisekunde= 0;
17
    if(++varZeit.Sekunde == 60)
18
    {
19
      varZeit.Sekunde= 0;
20
      if(++varZeit.Minute == 60)
21
      {
22
        varZeit.Minute= 0;
23
        if(++varZeit.Stunde == 24)
24
        {
25
          varZeit.Stunde= 0;
26
          if(++varZeit.Tag == 365)
27
          {
28
            varZeit.Tag= 0;
29
            varZeit.Jahr++;
30
          }
31
        }
32
      }
33
    }
34
  }
35
}

Nun meine Frage(n):
1. Ist diese Funktion so umsetzbar oder habe ich einen Gedankenfehler?
2.Wären diese Abfragen zuviel für die ISR bzw. kann es zu Problemen 
führen, wenn weitere ISRs laufen?
3. Gibt es eine elegantere Möglichkeit oder habt ihr eine Idee, wie man 
es eleganter lösen könnte?

Ich hoffe, ich habe nichts vergessen und die Fragen sind verständlich.
Vielen Dank für eure Hilfe.

Viele Grüße
HB.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Hans Bode schrieb:
> int16_t
Hast/brauchst du auch negative Tage?

> byte Jahr;
Und nur 255 Jahre?

> 1. Ist diese Funktion so umsetzbar
Ja.

> oder habe ich einen Gedankenfehler?
Ja, du vergisst die Schaltjahre...

BTW: Monate und Jahre müssen nicht unbedingt im Interrupt erledigt 
werden. Da macht es nichts aus, wenn die mal 10ms später aktualisiert 
werden...

> 3. Gibt es eine elegantere Möglichkeit oder habt ihr eine Idee, wie man
> es eleganter lösen könnte?
Ich würde das ausserhalb der ISR in der Hauptschleife machen:
In der ISR wird nur bis 1000 gezählt, und dann ein Sekundenflag gesetzt. 
Das wird dann in der Hauptschleife entsprechend deiner Vorlage 
ausgewertet.

von Purzel H. (hacky)


Lesenswert?

Ja. Ja. So etwa. Nicht alle jahre haben 365 Tage. Ich wuerd das Jahr 
weglassen, und mit Longint Tag arbeiten. Das Teil wird in einer Pyramide 
eingemauert ?
Und die muss alle 500us aufgeufen werden weil die Information so 
wahnsinig wichtig ist ? Einmal die Sekunde waere besser, oder das 
maximale timer interval.

von Karl H. (kbuchegg)


Lesenswert?

Hans Bode schrieb:

> 2.Wären diese Abfragen zuviel für die ISR

Die paar Abfragen und Erhöhungen wirst du nicht wirklich merken.
Das sieht nur im C Code nach viel aus. Wenn man sich aber den Assembler 
Code dazu ansieht merkt man ... das sind eine Handvoll Befehle .... wenn 
überhaupt.


Das einzige:
Zieh den Code selber in die ISR rein.
Funktionsaufrufe aus einer ISR heraus sind generell teuer.

von Hans B. (hansb)


Lesenswert?

Lothar Miller schrieb:
> Ich würde das ausserhalb der ISR in der Hauptschleife machen:
> In der ISR wird nur bis 1000 gezählt, und dann ein Sekundenflag gesetzt.
> Das wird dann in der Hauptschleife entsprechend deiner Vorlage
> ausgewertet.

Vielen Dank für die Antwort.

Schaltjahre sind jetzt erstmal nicht so wichtig ;)

Aber ich werde die Funktion umschreiben, so dass nur noch ein 
Sekundenflag gesetzt wird, daran habe ich gar nicht gedacht und schon 
zig-Mal in anderen Versionen gemacht. Danke :)

A...aha Soooo. schrieb:
> Und die muss alle 500us aufgeufen werden weil die Information so
> wahnsinig wichtig ist ? Einmal die Sekunde waere besser, oder das
> maximale timer interval.

Ich brauch die 500 us Takt, da ich noch Messungen vornehmen möchte und 
dazu setze ich alle 500 us ein Flag mit dem ich dann weiterarbeiten kann 
:)

Lothar Miller schrieb:
>> int16_t
> Hast/brauchst du auch negative Tage?

Ich glaube, ich stelle die Datentypen auf unsigned um :) Danke für den 
Hinweis.


Jetzt noch eine Frage:
Ich habe das struct als volatile definiert und habe gelesen, dass man 
die volatile Variable zuvor in eine Temp-Variable (nicht-volatile) 
sichern sollte und am Ende wieder zurückschreiben - ist das hier 
notwendig/sinnvoll?

Danke für die schnellen Antworten.

von Hans B. (hansb)


Lesenswert?

Karl heinz Buchegger schrieb:
> Das einzige:
> Zieh den Code selber in die ISR rein.
> Funktionsaufrufe aus einer ISR heraus sind generell teuer.

Wäre eine Lösung, die Funktion als "inline" zu definieren?

von Karl H. (kbuchegg)


Lesenswert?

Hans Bode schrieb:

> Ich habe das struct als volatile definiert und habe gelesen, dass man
> die volatile Variable zuvor in eine Temp-Variable (nicht-volatile)
> sichern sollte und am Ende wieder zurückschreiben - ist das hier
> notwendig/sinnvoll?

Es wird nichts oder nicht viel bringen.
Auch das Umkopieren kostet Taktzyklen und du greifst auf jede Variable 
mehr oder weniger maximal 3 mal zu. Das wird nicht wirklich lohnen. Da 
kostet dir das Hin und Her Kopieren mehr Takte als du einsparen kannst.

Zieh lieber den Funktionsinhalt in die ISR hinein. Das bringt dir viel 
mehr.

(Ich würde hier ausnahmsweise auch kein Flag für die Hauptschleife 
machen. Auf die Art kann die Hauptschleife machen was sie will und wenn 
sie 10 Sekunden wartet, ist das auch kein Problem - die Uhr läuft 
deswegen unbeirrt weiter solange nur die Interrupts aktiviert sind)

von Karl H. (kbuchegg)


Lesenswert?

Hans Bode schrieb:
> Karl heinz Buchegger schrieb:
>> Das einzige:
>> Zieh den Code selber in die ISR rein.
>> Funktionsaufrufe aus einer ISR heraus sind generell teuer.
>
> Wäre eine Lösung, die Funktion als "inline" zu definieren?

Kann man so nicht sagen.
inline ist ein Hinweis für den Compiler. Er kann aber er muss nicht 
inlinen.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Ja das kann helfen. Dann weiss der Compiler eher was in der Funktion ab 
geht und kann sich das Retten/Restaurieren einiger Register sparen.

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Generell würde ich eh "nur" die Sekunden oder sogar Minuten in einem 
passend großem int (oder mehreren ablegen) und wenn man dan wirklich an 
details interessiert ist dann erst die Stunden..Tage...Wochen...Jahre... 
daraus berechnen.

von Helmut L. (helmi1)


Lesenswert?

Ich wuerde nur die Sekunden in einer ulong hochzaehlen.

Bei der Abfrage kann man diese Sekunden dann in YYYY.MM.DD HH.MM.SS 
umrechnen. So wie es auch im PC gemacht wird. Da werden auch nur die 
Sekunden vom 1.1.1980 oder 1.1.1970 gezaehlt und bei der Abfrage 
umgerechnet.

von Hans B. (hansb)


Lesenswert?

Karl heinz Buchegger schrieb:
> (Ich würde hier ausnahmsweise auch kein Flag für die Hauptschleife
> machen. Auf die Art kann die Hauptschleife machen was sie will und wenn
> sie 10 Sekunden wartet, ist das auch kein Problem - die Uhr läuft
> deswegen unbeirrt weiter solange nur die Interrupts aktiviert sind)

Ich könnte ja nur die Stunden, Tage, Jahre (und evtl. Minuten) in die 
Hauptschleife packen, da es da nicht genau auf die Sekunde ankommt (bei 
Minuten bin ich mir nicht so sicher).

Und den Rest der Funktion kopiere ich in die ISR.

Läubi .. schrieb:
> Generell würde ich eh "nur" die Sekunden oder sogar Minuten in einem
> passend großem int (oder mehreren ablegen)

Hat das denn Vorteile oder einfach nur um, ich sage mal, Variablen zu 
"sparen"?

Vielen Dank für eure Antworten. Das hilft mir sehr.

von Helmut L. (helmi1)


Lesenswert?

Hans Bode schrieb:
> Hat das denn Vorteile oder einfach nur um, ich sage mal, Variablen zu
> "sparen"?

Deine ganze Routine dazu besteht nur aus ein

Seconds++;


Und das wars.

von Hans B. (hansb)


Lesenswert?

Helmut Lenzen schrieb:
> Ich wuerde nur die Sekunden in einer ulong hochzaehlen.

Wäre das denn schneller? Ich meine long ist ja einiges größer als byte?
Ich vermute mal, dass es schneller ist, weil man nur noch einen Befehl 
benötigt, der die Variable (und nicht 8 Variablen) inkrementiert?

von Karl H. (kbuchegg)


Lesenswert?

Hans Bode schrieb:
> Helmut Lenzen schrieb:
>> Ich wuerde nur die Sekunden in einer ulong hochzaehlen.
>
> Wäre das denn schneller? Ich meine long ist ja einiges größer als byte?
> Ich vermute mal, dass es schneller ist, weil man nur noch einen Befehl
> benötigt, der die Variable (und nicht 8 Variablen) inkrementiert?

In so einem Fall gibt es nur eines.
Schalt das Listfile ein und sieh im Assembler Code nach.
Alles andere ist stochern im Nebel.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Hans Bode schrieb:
> Ich könnte ja nur die Stunden, Tage, Jahre (und evtl. Minuten) in die
> Hauptschleife packen, da es da nicht genau auf die Sekunde ankommt (bei
> Minuten bin ich mir nicht so sicher).
>
> Und den Rest der Funktion kopiere ich in die ISR.
Aber aufpassen: du wirst bei so einer Aufteilung dann das Problem 
bekommen, dass die Sekunden (und evtl. Minuten) sofort hochgezählt 
werden, die restliche Zeit aber später...

Das kann dann z.B. bei einer Wecker-Funktion (Zeitvergleich) zu 
seltsamen Effekten führen, weil die Sekunden im Interrupt schon von 59 
auf 0 übergelaufen sind, aber die Minuten noch nicht aktualisisert 
wurden.

An so einem Fehler kannst du dich dämlich suchen...  ;-)

von Helmut L. (helmi1)


Lesenswert?

Auch fuer sowas hat die einfache nur Sekundenzaehlerei einen Vorteil. 
Zum Vergleich bei Weckzeiten ist nur ein Vergleich noetig.

von Karl H. (kbuchegg)


Lesenswert?

Lothar Miller schrieb:

> An so einem Fehler kannst du dich dämlich suchen...  ;-)

Genau.
Bei solchen Sachen sollte man 'die Zeit', egal wie sie dargestellt wird, 
als integrale Einheit betrachten und immer alles durchziehen.

Wir reden doch hier nicht von einem zeitaufwändigem Datenbank update.
Wenn du Sekunden, Minuten, Tage etc getrennt haben willst, dann sind das 
6 Bytes die im Fall des Falles erhöht werden. Das sind Peanuts im 
Vergleich zum Rest. Nichts worüber es sich lohnt grossartig 
philosophisch nachzudenken.
Die paar Takte die dafür draufgehen lohnen das Risiko nicht, dass man 
sich da Race Conditions einhandelt.

von Hans B. (hansb)


Lesenswert?

Also, wenn ich die Zeit direkt aufteilen möchte, packe ich die ganzen 
Abfragen von der Funktion einfach in die ISR. Die paar Bytes machen 
keinen Unterschied. Ok :)

Die Version mit den Seconds:

Ich habe mal im Listfile nachgeschaut (ist das erste Mal, dass ich mir 
das anschaue - ich hoffe, ich interpretiere es richtig):

Pro Abfrage sind es in etwa 13 Assembler-Befehle. Damit würde die 
Version, die ich geschrieben habe einiges mehr an Befehlen abarbeiten 
müssen und somit länger dauern.

Hier mal ein Ausschnitt für die Version mit Seconds(hier: varZeitInt32) 
als einzige Variable (und Milliseconds (varMS))
1
 113             if(++varMS == 1000)
2
   \                     ??TIMER_A0_ISR_1:
3
   \   000000D0   ....               LDI     R30, LOW(varMS)
4
   \   000000D2   ....               LDI     R31, (varMS) >> 8
5
   \   000000D4   8180               LD      R24, Z
6
   \   000000D6   8191               LDD     R25, Z+1
7
   \   000000D8   9601               ADIW    R25:R24, 1
8
   \   000000DA   ....               LDI     R30, LOW(varMS)
9
   \   000000DC   ....               LDI     R31, (varMS) >> 8
10
   \   000000DE   8380               ST      Z, R24
11
   \   000000E0   8391               STD     Z+1, R25
12
   \   000000E2   3E88               CPI     R24, 232
13
   \   000000E4   E003               LDI     R16, 3
14
   \   000000E6   0790               CPC     R25, R16
15
   \   000000E8   F471               BRNE    ??TIMER_A0_ISR_2
16
    114               varZeitInt32++;
17
   \   000000EA   ....               LDI     R30, LOW(varZeitInt32)
18
   \   000000EC   ....               LDI     R31, (varZeitInt32) >> 8
19
   \   000000EE   8100               LD      R16, Z
20
   \   000000F0   8111               LDD     R17, Z+1
21
   \   000000F2   8122               LDD     R18, Z+2
22
   \   000000F4   8133               LDD     R19, Z+3
23
   \   000000F6   5F0F               SUBI    R16, 255
24
   \   000000F8   4F1F               SBCI    R17, 255
25
   \   000000FA   4F2F               SBCI    R18, 255
26
   \   000000FC   4F3F               SBCI    R19, 255
27
   \   000000FE   8300               ST      Z, R16
28
   \   00000100   8311               STD     Z+1, R17
29
   \   00000102   8322               STD     Z+2, R18
30
   \   00000104   8333               STD     Z+3, R19

Edit:

Ich muss mich korrigieren:
Die Version mit einer Variablen entsprechen 33 Befehlen.
Meine alte Version: Im Minimum: 24 Befehle - Maximum: 64 Befehle(wenn 
alle If-Anweisungen durchlaufen werden)

von Udo S. (urschmitt)


Lesenswert?

Genau das was Läubi vor mir gesagt hat ist mir sofort durch den Kopf 
gegangen. Warum berechnest Du 2000 mal pro Sekunde Sekunden Minuten 
Stunden Tage Monate und Jahre. Wann und wieoft brauchst Du diese Zeit?
Incrementiere mit einem long oder 64-Bit long Wert und mach die 
Zeiterfassung/Auswertung so wie es C, Java und andere Sprachen machen. 
Die teuere Auswertung in das Kalenderformat erst dann wenn man es 
braucht.
Das ist dann zwar programmtechnisch aufwendiger aber du kannst alle 
Spezialitäten (Monatslänge, Schaltjahre) problemlos erschlagen und hast 
einen einfachen kleinen und wenig Speicher benötigenden Zeitwert, den 
man auch problemlos irgendwo speichern oder übertragen kann.

von Hans B. (hansb)


Lesenswert?

Nachtrag:

Ich stelle mal eine kleine Rechnung an:

Angenommen, der uC läuft jeden 1000. Tick alle Befehle der If-Struktur 
durch, dann braucht der bei jedem 1000. Tick: 64 CPU-Zyklen, um die 
Struktur abzuarbeiten.
Die 999 zuvor hat er mit je 24 CPU-Zyklen abgearbeitet.
In Summe: 24040 CPU-Zyklen für 1000 Aufrufe.

Bei der Methode mit einer Variablen wäre das dann theoretisch:
33*1000 = 33000 CPU-Zyklen.

Wäre die Rechnung so korrekt?
Ich habe jetzt angenommen, dass für jeden Assembler-Befehl genau ein 
CPU-Takt benötigt wird.

Vielen Dank für eure Hilfe.
(Das ist jetzt nur rein interessehalber)

von Hans B. (hansb)


Lesenswert?

Udo Schmitt schrieb:
> Das ist dann zwar programmtechnisch aufwendiger aber du kannst alle
> Spezialitäten (Monatslänge, Schaltjahre) problemlos erschlagen und hast
> einen einfachen kleinen und wenig Speicher benötigenden Zeitwert, den
> man auch problemlos irgendwo speichern oder übertragen kann.

Ich werde es vermutlich umschreiben, auf die genannte Methode. Scheint 
ja einige Vorteile mit sich zu bringen.

Danke  euch allen.

von Udo S. (urschmitt)


Lesenswert?

Noch was, wenn Du zeiten nur in einem long oder 64bit Long speicherst, 
kannst Du so ein Datum/Zeitwert auch einfach vergleichen. Denk darüber 
nach wie Du 2 Werte speichern müsstest (structs) und wie aufwendig für 
dein Format ein Vergleich auf größer kleiner gleich ist!

von Helmut L. (helmi1)


Lesenswert?

Hans Bode schrieb:
> Bei der Methode mit einer Variablen wäre das dann theoretisch:
> 33*1000 = 33000 CPU-Zyklen.

Warum? Du sollst diese Variable jede Sekunde hochzaehlen nicht im ms 
Schritten.

von Hans B. (hansb)


Lesenswert?

Helmut Lenzen schrieb:
> Warum? Du sollst diese Variable jede Sekunde hochzaehlen nicht im ms
> Schritten.

Ich wollte aber gern die Millisekunden für andere Zwecke mitloggen, da 
kann ich die doch einfach mit verwenden ;)

Udo Schmitt schrieb:
> Noch was, wenn Du zeiten nur in einem long oder 64bit Long speicherst,
> kannst Du so ein Datum/Zeitwert auch einfach vergleichen. Denk darüber
> nach wie Du 2 Werte speichern müsstest (structs) und wie aufwendig für
> dein Format ein Vergleich auf größer kleiner gleich ist!

Da ist was dran. Wurde ja auch schon erwähnt.
Ich habe es jetzt umgeschrieben und verwende nur noch Millisekunden und 
Sekunden; den Rest berechne ich dann in der main durch entsprechende 
Funktionen.

Vielen Dank für eure große Hilfe.

von Karl H. (kbuchegg)


Lesenswert?

Hans Bode schrieb:

> Die 999 zuvor hat er mit je 24 CPU-Zyklen abgearbeitet.
> In Summe: 24040 CPU-Zyklen für 1000 Aufrufe.

Das ist aber eine Milchmädchenrechnung, die so nicht gilt.

Natürlich werden in den allermeisten Fällen nur ganz wenige Takte 
anfallen (BTW: Deine Zahlen kommen mir hoch vor, hast du die Optimierung 
aktiviert?), entscheidend ist aber nicht der Durchschnitt sondern der 
schlimmste Fall, das Maximum.

Wenn du Badewart bist und alle Minute zur Ablenkung immer nur 3 Sekunden 
nicht aufs Becken siehst, du aber einmal am Tag 30 Minuten aufs Klo 
gehst und in diesen 30 Minuten ertrinkt ein Kind, kannst du dich auch 
nicht darauf berufen, dass du im Durchschnitt das Becken nur 3.1 
Sekunden pro Minute aus den Augen gelassen hast.

von Hans B. (hansb)


Lesenswert?

Karl heinz Buchegger schrieb:
> Wenn du Badewart bist und alle Minute zur Ablenkung immer nur 3 Sekunden
> nicht aufs Becken siehst, du aber einmal am Tag 30 Minuten aufs Klo
> gehst und in diesen 30 Minuten ertrinkt ein Kind, kannst du dich auch
> nicht darauf berufen, dass du im Durchschnitt das Becken nur 3.1
> Sekunden aus den Augen gelassen hast.

Ja, du hast recht. Meine Rechnung ist eine Milchmädchenrechnung - sollte 
auch nur zum groben Vergleich (gaanz grob) dienen.

Karl heinz Buchegger schrieb:
> (BTW: Deine Zahlen kommen mir hoch vor, hast du die Optimierung
> aktiviert?

Ich habe einfach nur die Zeilen zusammengezählt aus dem lst-file. Und da 
kam ich auf 24 bzw. 33.

Ich habe den Compiler einfach kompilieren lassen. Ich habe keine 
Optimierung eingestellt, aber an den Einstellungen habe ich sowieso 
nichts vorgenommen - habe den Compiler voreingestellt bekommen.

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.