Forum: Mikrocontroller und Digitale Elektronik Frage zu Arduino-Projekt


von Heinze (Gast)


Angehängte Dateien:

Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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?

von Heinze (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Heinze (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Stefan K. (Gast)


Lesenswert?

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.

von Stefan K. (Gast)


Lesenswert?

@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

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.