Arduino: Timer und Delay

Gast #5246718
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
Gast #5246757
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.
#5246865
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.
Gast #5247288
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.
Gast #5247526
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.
#5247694
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.
Gast #5247760
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...
#5248560
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.
Gast #5249546
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?
Gast #5249593
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.
Gast #5249600
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.
Gast #5249604
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?
Gast #5249607
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 ....
Gast #5249626
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? :-)
Gast #5249649
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?
Gast #5249668
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.
#5249717
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.
Gast #5249731
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.
#5249759
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
Gast #5249994
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.
Gast #5251707
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren