Hallo zusammen,
ich habe mir das Arduino-Projekt "cosmic_raygun" angesehen und versuche
gerade zu verstehen, wie das Programm (siehe Anhang)
funktioniert.
Zu Beginn wird in der "setup"-Routine dafür gesorgt, dass der
Fire/Reload-Button als Eingänge, die LEDs und der Lautsprecher als
Ausgänge konfiguriert werden. Außerdem wird mittels
analogWrite(speakerGndPin,0) kein Ton ausgegeben.
Meine Verständnis-Schwierigkeiten beginnen nun dort, wo das
Hauptprogramm "loop" beginnt. Hier wird Zeit (in Millisekunden)
seit der Konfiguration auf der seriellen Konsole ausgegeben und die LEDs
zum Leuchten gebracht.
Nun wird in einer Zustandsmaschine die Variable "myState" abgefragt, die
initial mit 0 belegt ist. In diesem Zustand wird überprüft, ob "fireBtn"
gedrückt ist. Wenn dem so ist, so wird in "fireTimer" die Zeit (in
Millisekunden) seit der Konfiguration festgehalten und myState=1
eingeleitet.
1. Frage: Was passiert in myState==0, wenn der "fireBtn" nicht gedrückt
ist? Wird dann dieser Zustand solange gehalten, bis dann tatsächlich mal
der "fireBtn" betätgit wurde?
In Zustand myState==1 wird nun folgendes überprüft:
if (fireTimer+fireTime>millis()){
fire();
} else {
myState=2;
}
2. Frage: Der "fireTimer" wurde ja in myState==0 geladen, was genau
passiert nun an dieser Stelle?
Ich habe noch ein paar Fragen, aber vielleicht fangen wir mit den ersten
beiden Fragen an. Vielleicht kann mir jemand ein paar Erklärungen mit
auf den Weg geben, das wäre echt super.
VG, Heinze
Heinze schrieb:> Nun wird in einer Zustandsmaschine die Variable "myState" abgefragt, die> initial mit 0 belegt ist.
Nicht 'initial'.
Immer dann, wenn myState den Wert 0 hat, wird dieser Zweig genommen. Ob
initial oder später spielt keine Rolle. Sobald loop() aufgerufen wird
UND myState auf 0 ist, geht es in diesen Zweig.
> 1. Frage: Was passiert in myState==0, wenn der "fireBtn" nicht gedrückt> ist? Wird dann dieser Zustand solange gehalten, bis dann tatsächlich mal> der "fireBtn" betätgit wurde?
Nachdem es dann keine Zuweisung an myState gibt, behält myState seinen
Wert und ja, beim nächsten Aufruf von loop ist myState dann immer noch 0
und es geht wieder in diesen Zweig hinein, in dem dann eben wieder
überprüft wird, ob der Fire-Button gedrückt ist.
> In Zustand myState==1 wird nun folgendes überprüft:>> if (fireTimer+fireTime>millis()){> fire();> } else {> myState=2;> }>> 2. Frage: Der "fireTimer" wurde ja in myState==0 geladen, was genau> passiert nun an dieser Stelle?
fireTimer + fireTime ergibt logisch gesehen was?
Wenn du das Badewasser aufdrehst und dabei auf ide Uhr schaust und deine
Uhr zeigt 16:00 Uhr, was ergeben dann diese 16:00 PLUS zusätzliche 10
Minuten. Das macht dann 16:10.
Was könnte wohl der Sinn der Sache sein, wenn du dann laufend auf die
Uhr schaust und die tatsächliche Zeit mit diesen 16:10 vergleichst und
bei Überschreiten dieser Zeit etwas tust?
Der Aufruf von millis() führt ja zu einer ständig größer werdenden Zeit
(in ms), während fireTimer+fireTime konstant bleibt. Kann diese
Bedingung denn jemals TRUE werden? Ich glaube hier liegt mein
Verständnisproblem.
VG, Heinze
Heinze schrieb:> Der Aufruf von millis() führt ja zu einer ständig größer werdenden Zeit> (in ms), während fireTimer+fireTime konstant bleibt. Kann diese> Bedingung denn jemals TRUE werden?
Natürlich.
Auf deiner Uhr nimmt ja die Zeit auch laufend zu, wenn du mir mal die
Annahme gestattest, dass du eine Armbanduhr hast, die nicht nach 60
Sekunden die Sekunden wieder zu 0 setzt und dafür die Minuten um 1
hochzählt. Gehen wir einfach mal davon aus, du hättest eine Armbanduhr
die jeden Tag die vergangenen Minuten seit Mitternacht anzeigt. Dann
hast du jeden Tag eine Zahl vor dir, die laufend größer wird.
Jetzt, in diesem Moment liest du ab: 856
Diesen Wert merkst du dir unter dem Namen 'fireTimer'
Jetzt zählst du da noch, sagen wir mal 10 dazu. Die 10 nenne ich jetzt
einfach mal 'fireTime'
wann also ist
fireTimee + fireTime > aktuelle Zeit
mal die Zahlenwerte einsetzen
856 + 10 > 856
ganz offensichtlich der Fall, denn 856 + 10 macht 866
Jetzt warten wir ein wenig. Sagen wir mal 1 Minute
fireTimer + fireTime > aktuelle Zeit
856 + 10 > 857
ganz offensichtlich immer noch der Fall: die errechneten 866 sind immer
noch größer als 857: TRUE
Warten wir noch ein wenig. Seit dem Merken von FireTimer sind bereits 8
Minuten vergangen, deine Armbanduhr zeigt jetzt also schon 864 an
fireTimer + fireTime > aktuelle Zeit
856 + 10 > 864
Immer noch der Fall.
Wir warten weiter. Seit dem Merken des Wertes für fireTimer sind 10
Minuten vergangen (d.h. jetzt muss deine Armbanduhr bereits 866
anzeigen, denn fireTimer wurde ja zum Zeitpunk 856 gemerkt
fireTimer + fireTime < aktuelle Zeit
856 + 10 > 866
Holla. Jetzt stimmt die Abfrage nicht mehr. 866 ist nicht mehr größer
als 866 -> FALSE
(Die Sache ist doch ganz simpel:
Wenn es jetzt 14:25 ist und du jetzt die Herdplatte fürs Gulasch
einschaltest und das Gulasch 26 Minuten kochen muss, wann ist es dann
fertig? Das rechnest du dir aus (14:25 + 00:26) und dann schaust du
dauernd auf die Uhr und siehst nach ob die Zeit schon erreicht ist bzw.
ob es nicht sogar schon ein bischen später ist. Wenn die Uhr dann
irgendwann 14:51 anzeigt, dann ist das Gulasch durch (wobei ein paar
Sekunden drüber nichts ausmachen, denn du wirst es nicht schaffen exakt
um 14:51:00.00000 auf die Uhr zu schauen. Also fragst du nicht ab ob die
Endzeit genau erreicht ist, sondern ob du über die Endzeit drüber bist.
Ob du dann um 14:51:00 oder 14:51:01 oder um 14:51:02 bemerkst, dass die
Zeit um ist, spielt fürs Gulasch keine große Rolle. Daher fragst du das
Ende nicht mit == ab, sondern mit > oder <, je nachdem, wie das dann
genau formuliert ist.
Hallo Karl,
ich mag deine Art und Weise, Dinge zu beschreiben :-)
Jetzt habe ich die Abfrage verstanden.
Werde erst einmal versuchen, die entsprechende Hardware zu bauen und mit
dem Code herumzuspielen.
Weitere Frage folgen bestimmt noch, versprochen!
VG, Heinze
Heinze schrieb:> Hallo Karl,>> ich mag deine Art und Weise, Dinge zu beschreiben :-)
Ach, du bist doch nur scharf auf mein Gulasch! :-)
Im Ernst:
Viele Programmierdinge verwenden wir jeden Tag in unserer Umgebung ohne
uns dessen bewusst zu sein. Es lohnt sich also, sich selbst bzw. andere
bei dem was wir tun, und vor allen Dingen wie sie es tun, zu beobachten.
Man kann dabei viel darüber lernen, welche Strategien sich bewährt haben
(und es daher wert sind, in algorithmischer Form weiterverwendet zu
werden) und welche nicht.
Karl Heinz Buchegger schrieb:> denn du wirst es nicht schaffen exakt> um 14:51:00.00000 auf die Uhr zu schauen. Also fragst du nicht ab ob die> Endzeit genau erreicht ist, sondern ob du über die Endzeit drüber bist.> Ob du dann um 14:51:00 oder 14:51:01 oder um 14:51:02 bemerkst, dass die> Zeit um ist, spielt fürs Gulasch keine große Rolle. Daher fragst du das> Ende nicht mit == ab, sondern mit > oder <, je nachdem, wie das dann> genau formuliert ist.
Verdammt Karl-Heinz... jetzt weiß ich endlich, warum mein Gulasch
ständig anbrennt! ;-)
Im Ernst: Gute Erklärung! Das Beispiel werd' ich mir merken, falls ich
mal wieder ein paar Programmier-Novinzen solche Zeitberechnungen
erklären muss.
@Heinze:
Bei Fließkommazahlen gilt beim Programmieren übrigens eine ähnliche
Vorgehensweise. Wegen Ungenauigkeiten beim Rechnen mit Fließkommazahlen
(insbesondere Rundungsfehlern bzw. Umrechnungsfehlern von dezimale in
binäre Darstellung), sollte man dabei nicht mit "==" vergleichen.
Stattdessen vergleicht man ob die Differenz kleiner ist als ein relativ
kleiner Wert.
Um mal ein Beispiel zu geben:
---- CUT HERE ----
#include <stdio.h>
#include <math.h>
int main() {
float a = 0.1f;
float b = 0.2f;
float c = 0.3f;
if ((c-b) == a) { // das sollte man vermeiden!
printf("Das wird nicht aufgeführt...\n");
}
if (fabs( (c-b) - a ) < 0.001f) { // das ist besser
printf("Das klappt immer.\n");
}
return 0;
}
---- CUT HERE ----
Die Ausgabe des Programms wird sein "Das klappt immer.". Von "Das wird
nicht aufgeführt..." wirst Du wahrscheinlich nichts sehen. Zumindest hat
es sich gerade bei mir beim Test auch so Verhalten (wie das zu erwarten
ist).
Warum ist das so?
Laut Mathematik-Unterreicht sollte 0.3-0.2 = 0.1 sein... also sollte
(c-b) auch 0.1 sein und da a = 0.1 ist, sollte (c-b) == a "true"
ergeben.
Das Problem ergibt sich wegen Umrechnung der Zahlen (0.1f, 0.2f und
0.3f) im Programmcode von dezimal (also so wie wir es in der Mathematik
schreiben) in die rechnerinterne binäre Darstellung. Dabei treten
Ungenauigkeiten auf. Schuld ist hier die Zahlensystem.... Ich lass die
Details mal weg (kannst Du unter http://de.wikipedia.org/wiki/IEEE_754
nachlesen, wie das Funktioniert). Du kannst Dir einfach vorstellen, dass
statt 0.3f in der Variable c sowas wie 0.29999999999987 drin steht. Und
bei den anderen ist das genauso. Das passiert bei fast jeder Umrechnung
von Dezimalschreibweise in die Fließkommadarstellung des Rechners...
Fazit: Du musst bei Fließkommaberechnungen mit (double und float) immer
davon ausgehen, dass nur "ungefähr" die Zahl raus kommt. Die Berechnung
ist natürlich schon sehr genau (der Fehler ist irgendwo ganz weit hinten
nach dem Komma), aber: eben nicht exakt. Und solche Fehler können sich
aufsummieren. Stelle Dir vor Du rechnest: ((c-b)-a) * 10000000. Oder
summierst ganz oft "(c-b)-a" auf.
Für einfache Berechnungen wirst Du das Problem ignorieren können. Wenn
aber mal komisches unerklärliches Verhalten bei Deinem Programm
auftritt, erinner dich daran und prüf nach ob es nicht daran liegt...
Grüße
Stefan