Forum: Compiler & IDEs Arduino: Timer und Delay


von Sören (Gast)


Lesenswert?

Hallo,

mit der Arduino-IDE habe ich ein kleines Programm für einen ATtiny84 
erstellt. Das Programm verwendet die Funktion delay(), die dafür den 
Timer0 des ATtiny84 benutzt.

Außerdem verwendet das Programm den Timer1 im CTC-Mode.

Das läuft auch ganz gut, nur manchmal bleibt das Programm hängen. Wo 
genau das passiert ist nicht klar.

Schließlich habe ich die delay()-Funktion auskommentiert und zum Test 
durch eine einfache Warteschleife ersetzt.
Ergebnis: Das Programm bleibt nicht hängen.

Gegenprobe: Die delay()-Funktion wird benutzt, Timer1 bleibt 
abgeschaltet.
Ergebnis: Das Programm bleibt nicht hängen.

Schlussfolgerung: Timer0 und Timer1 hängen irgendwie zusammen bzw. 
beeinflussen sich.

Natürlich will ich lieber die delay()-Funktion benutzen als die 
Warteschleife. Vor allem aber will ich verstehen, wo sich Timer0 und 
Timer1 in die Quere kommen.

Im Datenblatt
http://www.atmel.com/images/doc8006.pdf
steht auf Seite 115 "Timer0 and Timer1 share the same prescaler module". 
Hängt das damit zusammen?

Es ist auch nicht so wie bei andern ATtiny-Controllern, dass sich 
mehrere Timer die gleichen Control-Register teilen.

Vor allem aber: Wie kann ich also die delay()-Funktion zusammen mit 
Timer1  in friedlicher Koexistenz verwenden?

Danke.
Gruß
Sören

von Oliver S. (oliverso)


Lesenswert?

Am gemeinsamen Prescaler liegt es nicht. Der Fehler dürfte in Zeile 42 
deines Programms zu suchen sein.

Oliver

von Sören (Gast)


Lesenswert?

Jetzt habe ich folgendes gefunden:

Die delay()-Funktion ist hier beschrieben:
https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring.c

und ruft die Funktion micros() auf:
1
void delay(unsigned long ms)
2
{
3
  uint32_t start = micros();
4
5
  while (ms > 0) {
6
    yield();
7
    while ( ms > 0 && (micros() - start) >= 1000) {
8
      ms--;
9
      start += 1000;
10
    }
11
  }
12
}

Die Funktion micros() ist in der gleichen o.g. Datei beschrieben (der 
Übersicht halber wurden die #ifs und #elses etc weggelassen):
1
unsigned long micros() {
2
  unsigned long m;
3
  uint8_t oldSREG = SREG, t;
4
  
5
  cli();
6
  m = timer0_overflow_count;
7
  t = TCNT0;
8
  t = TCNT0L;
9
10
  if ((TIFR0 & _BV(TOV0)) && (t < 255))
11
    m++;
12
13
  SREG = oldSREG;
14
  
15
  return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
16
}

Was zu sehen ist: Die Interrupts werden abgeschaltet:
cli()
Wo aber werden sie wieder eingeschaltet?

Evtl. hängt es ja damit zusammen, dass mein Programm hängen bleibt, dass 
die delay()-Funktion Interrupts abschaltet aber unter gewissen Umständen 
nicht wieder aktiviert.

von Rolf M. (rmagnus)


Lesenswert?

Sören schrieb:
> Wo aber werden sie wieder eingeschaltet?

Hier:

> SREG = oldSREG;

von Sören (Gast)


Lesenswert?

Danke.
Schade.

von Falk B. (falk)


Lesenswert?

@ Sören (Gast)

>Außerdem verwendet das Programm den Timer1 im CTC-Mode.

>Schlussfolgerung: Timer0 und Timer1 hängen irgendwie zusammen bzw.
>beeinflussen sich.

Nö.

>Natürlich will ich lieber die delay()-Funktion benutzen als die
>Warteschleife.

Beides ist nicht sonderlich leistungsfähig. Wenn du schon Timer 1 laufen 
hast, mach es doch gleich richtig. Siehe Multitasking.

von Sören (Gast)


Lesenswert?

Anstatt der vorgegebenen Funktion delay() verwende ich nun meine eigene:
1
void myDelay(unsigned long ms)
2
{
3
    uint32_t start = micros();
4
5
    while (ms > 0) {
6
//        yield();
7
        while ( ms > 0 && (micros() - start) >= 1000) {
8
            ms--;
9
            start += 1000;
10
        }
11
    }
12
}

Der einzige Unterschied ist, dass yield() auskommentiert ist.
Jetzt bleibt nichts mehr hängen.

Auf der Suche nach diesem ominösen yield() und was dort passieren 
könnte, bin ich nicht weiter gekommen. Die Ursache ist also noch immer 
unklar.
Aber so funktioniert das erst mal.

von Karl M. (Gast)


Lesenswert?

Hallo Sören,

void yield(void) wird eine "Warte Funktion" sein, über die man beim 
"Warten auf das Ende der Schleife" wichtige Codeteile ausführen kann.

von Sören (Gast)


Lesenswert?

Ja, das habe ich auch gelesen.
Der Aufruf von yield() erlaubt es, während des Wartens andere Aufgaben 
auszuführen. Offenbar hängt der Header scheduler.h damit zusammen.
Aber weiter habe ich nichts rausgefunden, keine Implementierung 
gefunden.
Dort wäre wahrscheinlich ersichtlich, warum dieser Aufruf zu Problemen 
führt.

von Thomas E. (thomase)


Lesenswert?

Sören schrieb:
> Ja, das habe ich auch gelesen.
> Der Aufruf von yield() erlaubt es, während des Wartens andere Aufgaben
> auszuführen. Offenbar hängt der Header scheduler.h damit zusammen.
> Aber weiter habe ich nichts rausgefunden, keine Implementierung
> gefunden.
> Dort wäre wahrscheinlich ersichtlich, warum dieser Aufruf zu Problemen
> führt.

Die Funktion yield() gibt es erstmal gar nicht.

Die muß vom Anwender erstellt werden und wird dann von dem Arduinozeugs 
in das Arduino-Delay eingebaut, um während des Zeitverdödelns beim Delay 
doch noch etwas sinnvolles zu tun.

von Sören (Gast)


Lesenswert?

Wenn diese Funktion leer ist, ist es umso erstaunlicher, dass mit dieser 
Funktion mein Programm reproduzierbar nach ein paar Sekunden hängen 
bleibt. Kommentiere ich sie aus, läuft das Programm stundenlang (über 
Nacht durchgelaufen, dann wieder von heute Morgen bis jetzt).
Funktion wieder rein kommentieren: Programm bleibt hängen.
Seltsam...

von Thomas E. (thomase)


Lesenswert?

Sören schrieb:
> Kommentiere ich sie aus

Du hast das ganze Prinzip nicht verstanden. Du kommentierst keine 
Funktion aus. Du kommentierst den Aufruf einer Funktion aus. Diese 
Funktion ist nicht dazu da, von dir aufgerufen zu werden. Du musst diese 
Funktion erstellen.
1
void yield(void)
2
{
3
  BerechnePrimzahlen();
4
}

Diese wird dann ins Delay eingebaut, damit die Zeit mit sinnvollen 
Dingen, statt mit Auf-der-Stelle-treten verbracht wird.

Du zäumst das Pferd von hinten auf und wunderst dich, daß der Gaul 
rückwärts läuft und du vom Sattel fällst.

von Sören (Gast)


Lesenswert?

Jetzt verstehe ich tatsächlich, wie diese yield()-Funktion gedacht ist.

Aber:
In der Vergangenheit hatte ich schon häufig die eingebaute 
delay()-Funktion eingesetzt. Diese yield()-Funktion hatte ich nie 
implementiert.

Und jetzt in Zusammenspiel von delay() mit Timer1 bleibt plötzlich das 
Programm hängen. Das ließ sich wie gesagt dadurch beheben, dass yield() 
auskommentiert wurde.

Ja, es ist nur der Aufruf einer Funktion, die nicht implementiert wurde. 
Warum hat das dann solche Auswirkungen?

von Georg M. (g_m)


Lesenswert?

Sören schrieb:
> Aber:
> In der Vergangenheit hatte ich schon häufig die eingebaute
> delay()-Funktion eingesetzt. Diese yield()-Funktion hatte ich nie
> implementiert.

Zitat: "Nichts ist perfekt, und auch ein Compiler ist es nicht."

von Mitlesa (Gast)


Lesenswert?

Sören schrieb:
> Jetzt verstehe ich tatsächlich, wie diese yield()-Funktion gedacht ist.
>
> Aber:

Scheinbar nicht. Thomas hat es dir bereits beschrieben

yield() ist dazu gedacht in einem delay()-Aufruf noch
weiterrechnen zu können damit du nicht auf der Stelle
treten lässt.

Thomas E. schrieb:
> Diese
> Funktion ist nicht dazu da, von dir aufgerufen zu werden. Du musst diese
> Funktion erstellen.

Ein einfaches Beispiel zum Verständnis lässt sich auch
einfach in g* oder in derArduino-Welt finden:
1
unsigned long yieldCount = 0;
2
3
void setup()
4
{
5
  Serial.begin(9600);
6
}
7
8
void yield(void) 
9
{
10
  Serial.println(++yieldCount);
11
}
12
13
void loop() 
14
{
15
  delay(500);
16
}

Du siehst damit dass <yieldCount> verändert wird (obwohl in der
delay() gewartet wird), und das wesentlich häufiger als das
delay es vermuten lässt.

von Sören (Gast)


Lesenswert?

Ich habe inzwischen schon verstanden, wozu dieser Aufruf von yield() in 
der delay()-Funktion enthalten ist: Eben damit nicht nur Zeit vertrödelt 
wird, sondern zwischendurch etwas erledigt werden kann.

Was aber weiter unklar ist, warum ein Aufruf einer nicht implementierten 
Funktion in manchen Fällen das Programm außer Tritt bringt.

von Hugo (Gast)


Lesenswert?

Mitlesa schrieb:
...
>
> Du siehst damit dass <yieldCount> verändert wird (obwohl in der
> delay() gewartet wird), und das wesentlich häufiger als das
> delay es vermuten lässt.

Was bisher noch keiner gesagt hat:
Wodurch wird denn yield() aufgerufen?

Bis jetzt wurde nur gesagt, wozu es benutzt wird. Aber wie ist der 
Aufrufmechanismus? Und wann und wodurch wird es aufgerufen?

von Mitlesa (Gast)


Lesenswert?

Hugo schrieb:
> Aber wie ist der
> Aufrufmechanismus? Und wann und wodurch wird es aufgerufen?

Wenn yield() vom Benutzer eingeführt (implementiert), also in
seinen Sourcen vorhanden ist, wird es implizit vom Arduino
Framework aufgerufen, was aus meinem zitierten Beispiel auch
klar erkennbar ist. Einfach mal lesen und nachdenken ....

von Hugo (Gast)


Lesenswert?

Mitlesa schrieb:
> Hugo schrieb:
>> Aber wie ist der
>> Aufrufmechanismus? Und wann und wodurch wird es aufgerufen?
>
> Wenn yield() vom Benutzer eingeführt (implementiert), also in
> seinen Sourcen vorhanden ist, wird es implizit vom Arduino
> Framework aufgerufen, was aus meinem zitierten Beispiel auch
> klar erkennbar ist. Einfach mal lesen und nachdenken ....

In deinem Beispiel ist zu sehen, dass es aufgerufen wird. Aber nicht 
wodurch. Und wann und wie oft, ist auch nicht zu sehen. Was nützt mir 
eine Funktion, in der ich z.B. eine Variable incrementieren kann, wenn 
ich nicht weiss, in welchem Rhythmus bzw. in welchem Intervall die 
Funktion aufgerufen wird? Verstehst du, auf was ich hinauswill? :-)

von Mitlesa (Gast)


Lesenswert?

Hugo schrieb:
> Aber nicht wodurch.

Wenn du es nicht siehst woher es aufgerufen wird dann wohl von
"irgendwoher" anders. Die Quelle des Aufrufs kann aber nicht
weit sein da das Arduino-System deutlich abgegrenzt ist.

Wenn du es ausprobierst siehst du dass es aufgerufen wird.

Hugo schrieb:
> Was nützt mir
> eine Funktion, in der ich z.B. eine Variable incrementieren kann, wenn
> ich nicht weiss, in welchem Rhythmus bzw. in welchem Intervall die
> Funktion aufgerufen wird?

Ich bin nicht dazu da alle Gedankengänge der Arduino-Schöpfer
zu erklären und zu rechtfertigen. Aber wie wäre es wenn du deine
Nöte bei der Arduino-Gemeinde (sieh dortiges Forum) auslebst?

von Hugo (Gast)


Lesenswert?

Mitlesa schrieb:
> Ich bin nicht dazu da alle Gedankengänge der Arduino-Schöpfer
> zu erklären und zu rechtfertigen.

Sorry, wenn das falsch rüberkam. Ich will nicht, daß du dich für Arduino 
"rechtfertigst".

Ich hatte nur den Eindruck gewonnen, daß du dich auskennst und deshalb 
die Fragen gestellt, die mir unklar waren.

von Rolf M. (rmagnus)


Lesenswert?

Hugo schrieb:
> In deinem Beispiel ist zu sehen, dass es aufgerufen wird. Aber nicht
> wodurch.

Das wurde doch oben gepostet.
Ich hab's hier nochmal markiert:

Sören schrieb:
1
void delay(unsigned long ms)
2
{
3
  uint32_t start = micros();
4
5
  while (ms > 0) {
6
--->    yield();   <----
7
    while ( ms > 0 && (micros() - start) >= 1000) {
8
      ms--;
9
      start += 1000;
10
    }
11
  }
12
}

> Und wann und wie oft, ist auch nicht zu sehen.

So oft, wie's eben geht.

> Was nützt mir eine Funktion, in der ich z.B. eine Variable incrementieren
> kann, wenn ich nicht weiss, in welchem Rhythmus bzw. in welchem Intervall
> die Funktion aufgerufen wird? Verstehst du, auf was ich hinauswill? :-)

Die Funktion ist nur da, damit während des Delays noch was sinnvolles 
gemacht werden kann. Wenn du dafür keinen Anwendungsfall dafür, dann 
lasse sie einfach leer. Ansonsten kannst du da auch im Hintergrund 
Bitcoins minen oder so. Die interessieren sich nicht für irgendwelche 
Intervalle.

von Hugo (Gast)


Lesenswert?

@Rolf: Danke für deine Erläuterungen. Das war jetzt einleuchtend.

von Sören (Gast)


Lesenswert?

Danke auch von mir. Ich glaube, das ist jetzt verstanden.
Die Ursache meines Problems ist mir aber immer noch nicht klar.

Wenn diese Funktion yield() nicht implementiert ist würde ich meinen, 
dass dann auch nichts passiert. Diese Erfahrung hatte ich auch bisher 
gemacht und die delay()-Funktion benutzt, ohne überhaupt von der 
Möglicheit zu wissen, mithilfe von yield() zwischendurch Dinge zu 
erledigen.

Und jetzt plötzlich muss ich diesen Aufruf auskommentieren, damit mein 
Programm läuft. Und nein, nirgends in meinem Code verwende ich eine 
yield()-Funktion.

von Stefan K. (stefan64)


Lesenswert?

Ich nehme mal an, dass die yield() in Arduino als leere weak-Funktion 
implementiert ist. Eine als weak gekennzeichnete Funktion wird nur dann 
benutzt, wenn in Deinem Code nirgends eine Funktion mit gleichem Namen 
definiert ist.

Schreib doch mal in Deine myDelay() eine myYield() statt der yield() und 
definiere eine eigene myYield() in Deinem Code. Innerhalb von myYield() 
solltest Du eine Dummy-Aktion (Hochzählen einer volatile Variablen, Pin 
togglen etc.) einbauen, damit diese nicht wegoptimiert wird.

Falls das Problem damit immer noch auftritt, könnte es sich ev. um ein 
Stackproblem handeln. Immerhin hat Dein Tiny nicht wirklich viel RAM. 
Dafür dürfte auch sprechen, dass der Hänger nur sporadisch auftritt und 
nicht sofort beim ersten Durchlauf.

Das Auskommentieren von yield() sorgt für ein paar Bytes weniger auf dem 
Stack innerhalb von delay(). Das würde allerdings auch bedeuten, dass Du 
mit den Ressourcen maximal am Anschlag bist und mit jeder Erweiterung 
des Programms ähnliche Probleme auftreten können.

Viele Grüße, Stefan

von Nico W. (nico_w)


Lesenswert?

Aktuelle Arduino IDE kompiliert afaik mit LTO. Von daher sollte die 
Funktion normal verschwinden, wenn in der weak Implementierung auch nix 
drinne steht.

von Sören (Gast)


Lesenswert?

Habe das jetzt mal ausprobiert und in delay() eine Funktin myYield() 
aufgerufen, die einen Zähler hochzählt. Das Ergebnis wird in der 
Main-Schleife ausgegeben.

An der Ausgabe ist zu erkennen, dass myYield() tatsächlich aufgerufen 
und der Zähler inkrementiert wird.

Das Programm läuft damit jetzt schon eine ganze Weile ohne hängen zu 
bleiben.

Nach dem Compilieren wird folgendes angezeigt:
Globale Variablen verwenden 121 Bytes (23%) des dynamischen Speichers, 
391 Bytes für lokale Variablen verbleiben.

Das sollte doch reichen. Natürlich weiß ich nicht, was der Arduino für 
seine internen Strukturen benötig. Aber mit fast 400 Bytes ist doch 
einiges zu machen.

Nur zum Spaß habe ich dann nochmal die originale delay()-Funktion 
reinkommentiert. Das Programm bleibt wieder nach ein paar Sekunden 
hängen.

von Oliver S. (oliverso)


Lesenswert?

Dann ist der Fehler in Zeile 42 immer noch drin.

Oliver

von Sören (Gast)


Lesenswert?

Zeile 42 habe ich auch auskommentiert ;-)

von Stefan K. (stefan64)


Lesenswert?

Hast Du eine Library eingebunden, die yield() definiert? Kannst Du in 
Deinem Code yield() finden ?

Gruß, Stefan

von Sören (Gast)


Lesenswert?

Nein, in meinem eigenen Code gibt es kein yield().
Es ist auch keine Library eingebunden.

Leider ist das alles sehr undurchsichtig. Ich werde das aber erst mal 
auf sich beruhen lassen. Ein Fix ist ja gefunden.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

besser wäre du verabschiedest dich gänzlich von delay und baust dir mit 
millis deine Zeitscheiben. Dann blockiert nichts mehr.

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.