Sturzi's Arduino-Bastelprojekt #2 - Speed-o-Meter und Rundenzähler mit Infrarot-Lichtschranken

  • Spurt anziehen


    Wenn wir das Ziel, vor Weihnachten fertig zu sein, erreichen wollen, müssen wir jetzt zum Endspurt ansetzen.


    Die heutige Version wird im Stande sein, eine Geschwindigkeitsmessung zu starten. Wenn wir aus dem Grundzustand (zurückgesetzte Werte in der Anzeige) eine der beiden Lichtschranken ansprechen lassen, wird die Geschwindigkeits-Messung aktiviert. Man wird dies an der Anzeige der verstrichenen Zeit im Display sehen. Für die Beendigung der Messung (wenn die zweite Lichtschranke anspricht), müsst ihr nochmals ein paar Tage Geduld haben.


    Heute gibt es ziemlich viele neue Anweisungen. Ich werde zuerst alle neuen Code-Blöcke mit den nötigen Erklärungen vorstellen und am Schluss werde ich das ganze Programm nochmals posten.


    Folgende beiden Konstanten sind oben einzufügen:

    Code
    const float RAILWAY_SCALE = 1.0 / 87.0;                 // Masstab der Modellbahn
    const float MILLIMETERS_BETWEEN_LIGHT_BEAMS = 2000.0;   // Distanz in mm zwischen den beiden Lichtschranken

    Diese beiden Konstanten müsst ihr an eure Gegebenheiten anpassen. Beide werden für die Berechnung der Geschwindigkeit gebraucht. Die Messung ermittelt die Zeit für die Fahrt zwischen den beiden Lichtschranken. Mit der Zeit und der Distanz MILLIMETERS_BETWEEN_LIGHT_BEAMS kann die Geschwindigkeit des Modells in Meter pro Sekunde berechnet werden. Um die Geschwindigkeit auf's Vorbild umrechnen zu können, brauchen wir den Massstab RAILWAY_SCALE.



    Auch die folgenden drei Konstanten sind zu übernehmen und an eure Gegebenheiten anzupassen:

    Code
    const byte PIN_LEFT_LIGHT_BEAM = 12;                    // Pin Nummer für linke Lichtschranke
    const byte PIN_RIGHT_LIGHT_BEAM = 11;                   // Pin Nummer für rechte Lichtschranke
    const byte PIN_PUSH_BUTTON = 3;                         // Pin Nummer für Reset Taster

    Diejenigen, die zum Zurücksetzen der Anzeige-Werte die Select-Taste auf dem Keypad-Shield brauchen, benötigen die Konstante PIN_PUSH_BUTTON nicht.



    Dieser Code-Block muss von Allen übernommen werden:

    Code
    byte phase = 0;                             // Enthält die aktuelle Phase der Messung
    const byte PHASE_READY = 0;                 // Speed-o-Meter ist bereit, aber Zeitmessung hat noch nicht gestartet
    const byte PHASE_TIMING_IN_PROGRESS = 1;    // Zeitmessung ist aktiv (Zeit läuft)
    const byte PHASE_WAITING_FOR_TAIL_END = 2;  // Zeitmessung ist vorbei, Schutzzeit abwarten
    
    const unsigned long MILLIS_TO_WAIT_TO_GET_READY_AGAIN = 3000;   // Schutzzeit in Millisekunden

    Die Variable phase werden wir benötigen, um festzuhalten in welcher Phase die Messung gerade ist. Sie kann einen der darunter stehenden drei konstanten Werte annehmen. Die Kommentare müssten eigentlich reichen, um die drei vorkommenden Phasen zu verstehen.


    Hier nochmals mit einfachen Worten:

    Im Grundzustand ist die Phase auf 0. Wenn nun eine der beiden Lichtschranken anspricht, wird die Messung gestartet und die Phase geht auf 1. Wenn die andere Lichtschranke anspricht, ist die Messung vorbei und das Ergebnis kann berechnet und angezeigt werden. Dann geht die Phase auf 2.

    Warum geht die Phase nicht gleich wieder zurück auf 0? Weil, obwohl die eigentliche Messung vorbei ist, der Rest der Lok oder des Zuges noch immer an der zweiten Lichtschranke vorbei fährt. Das Lok- oder Zug-Ende müssen wir zuerst abwarten, aber um das einigermassen zuverlässig erkennen zu können, braucht es eine Schutz-Zeit (MILLIS_TO_WAIT_TO_GET_READY_AGAIN), die wir nach dem vermeintlichen Ende auch noch abwarten müssen. Erst dann geht die Phase wieder auf 0 und wir sind bereit für die nächste Messung. Die Schutz-Zeit könnt ihr nach euren Bedürfnissen einstellen. Wenn ihr nur einzelne Loks messt, reichen auch 1000 ms. Wenn ihr ganze, langsam fahrende Güterzüge, mit viel Gelegenheit, zwischen den Wagen durchzublicken, messt, muss die Zeit vielleicht auf 4000 oder sogar 5000 ms gesetzt werden. Hier eine höhere Zeit zu wählen, hat als einzigen Nachteil, dass es länger geht, bis das Speed-o-Meter jeweils wieder für die nächste Messung bereit ist.



    Der folgende Code-Block muss auch noch vor der setup() Funktion eingefügt werden:

    Code
    unsigned long millisOnBeginOfPeriod;            // Millisekunden zum Beginn einer Zeitnahme
    float mesauredTime = 0.0;                       // Gemessene Zeit für die eben beendete Vorbeifahrt
    float speedMetersPerSecond[2] = {0.0, 0.0};
    float speedKilometersPerHour[2] = {0.0, 0.0};
    float averageSpeed[2] = {0.0, 0.0};             // Durchschnittsgeschwindigkeiten
    float cumulatedSpeeds[2] = {0.0, 0.0};          // aufkumulierte Geschwindigkeiten
    int lapCount[2] = {0, 0};                       // Rundenzähler
    byte currentDir = 0;                            // aktuelle Messrichtung
    unsigned long currentMillis;                    // Um die aktuelle Messdauer anzuzeigen

    Achtung: Drei dieser Variablen haben wir schon bisher im Programm drin gehabt (mit Phantasie-Werten innerhalb der geschweiften Klammern). Passt auf, dass diese Variablen jetzt nicht doppelt im Programm vorhanden sind. Die Phantasie-Werte sollten jetzt auf 0.0, bzw. 0 sein.


    Auch hier müssten die Namen der Variablen und die Kommentare vermuten lassen, um was es geht.



    Die bisherige setup() Funktion wird ersetzt durch diese:

    In den Zeilen 2 bis 4 werden die Pins für die verwendeten Eingänge zugeordnet. Die Zeile mit dem PIN_PUSH_BUTTON ist nur nötig für diejenigen mit einem gewöhnlichen Taster. Das mit dem INPUT_PULLUP ist im Beitrag 74 beschrieben.


    Zeilen 6 und 7: Nichts Neues, das hatten wir schon.


    Zeilen 9 bis 13: Hier wird alles mit den Grundwerten (0) berechnet und angezeigt. Ohne das wäre die Anzeige leer.



    Zuoberst in der loop() Funktion benötigen wir diese beiden Anweisungen:

    Code
      bool leftBeamActive = ! digitalRead(PIN_LEFT_LIGHT_BEAM);
      bool rightBeamActive = ! digitalRead(PIN_RIGHT_LIGHT_BEAM);

    Hier werden die beiden Lichtschranken eingelesen und deren Zustände werden den entsprechenden boolschen Variablen zugeordnet. Beachtet, dass die Lichtschranken normalerweise HIGH sind (also 5V anliegen). Wenn man sie zum Ansprechen bringt, geht die Spannung auf 0V. Deshalb der not-Operator (das Ausrufezeichen). Gelesen werden die Zeilen so: Wenn der Pin nicht HIGH ist, setzen wir die Variable auf true, sonst auf false.


    Nach dem bereits vorhandenen Code in der loop() Funktion if (clearButtonPressed ....) kommt folgendes dazu:

    Was hier steht, bedarf einer ausgiebigen Erklärung. Das wird mir für heute zu viel (euch wahrscheinlich auch). Deshalb erkläre ich das im nächsten Schritt (morgen oder übermorgen). Für heute nehmt das einfach mal zur Kenntnis.



    Auch im loop(), unterhalb kommt noch Folgendes dazu:

    Code
      // Diese Section dient der laufenden Zeitanzeige während die Zeitmessung im Gang ist
      if (phase == PHASE_TIMING_IN_PROGRESS) {
        currentMillis = millis();
        displayElapsedTime();
      }

    Hier sagt der Kommentar im Programm, wofür das ist.



    Unterhalb der loop() Funktion kommen diese drei neuen Funktion dazu:

    Auch das möchte ich im nächsten Schritt erklären.



    Und zuletzt muss die bestehende Funktion resetValues() noch etwas ausgebaut werden. Am besten ersetzt ihr den bisherigen Inhalt von resetValues() durch diesen:


    Ich wollte hier noch die ganzen Programme anhängen, aber die Foren-Software sagt, der Beitrag sei zu lang. Deshalb mache ich das in einem (oder mehreren) Folge-Beiträgen.

  • Dieser Beitrag gehört zum Vorhergehenden:


    Hier das Programm für Typ A:

  • Dieser Beitrag gehört zu den Vorhergehenden.


    Hier das Programm für Typen C und D:


    Wenn ihr jetzt das Programm ausführt, müsstet ihr durch das Aktivieren einer Lichtschranke die Messung starten können. Während der Messung sollte die Zeit links im Display hochzählen (obere Zeile bei linker Lichtschranke, untere Zeile bei rechter Lichtschranke). Weil das Programm ja noch nicht ganz fertig ist, könnt ihr die Messung nur durch den Taster beenden und gleichzeitig die Werte zurücksetzen.


    Wir sind jetzt nicht mehr weit vom fertigen Programm entfernt (nur noch ein paar Anweisungen). Ich schulde euch aber noch ziemlich viele Erklärungen zu den einzelnen Funktionen und Programmschritten.

  • Erklärung der switch-Anweisung in der loop() Funktion


    Ich wiederhole hier nochmals den neuen Code in der loop() Funktion:


    Zeile 1: Hier beginnt der switch Befehl. Der switch Body reicht bis zur zugehörigen schliessenden Klammer auf Zeile 23. Der Wert in der Klammer, also hier phase, ist ausschlaggebend, welcher case ausgeführt wird. Wir haben hier drei cases, nämlich 0, 1 und 2, hier ausgedrückt durch die sprechenden Konstanten PHASE_... .


    Für den Fall, dass phase 0, bzw. PHASE_READY ist, geht die Programmausführung auf Zeile 4 weiter.

    Für den Fall, dass phase 1, bzw. PHASE_TIMING_IN_PROGRESS ist, geht die Programmausführung auf Zeile 16 weiter.

    Für den Fall, dass phase 2, bzw. PHASE_WAITING_FOR_TAIL_END ist, geht die Programmausführung auf Zeile 20 weiter.

    In jedem anderen Fall geht die Programmausführung nach der schliessenden geschweiften Klammer auf Zeile 23 weiter.


    Die break Anweisungen sind dazu da, dass die Programmausführung am Ende der Verarbeitung eines cases nicht in den nächsten case durchfällt, sondern beendet wird.


    Erklärung des ersten cases: Wir erreichen diesen dann und nur dann, wenn phase den Zustand PHASE_READY hat. Im Ausgangszustand, wenn die Anzeige zurückgesetzt wurde, ist das der Fall. Dann wird also das if auf Zeile 4 ausgeführt. Solange keine der beiden Lichtschranken angesprochen hat, wir der Teil im if Body nicht ausgeführt. Bedenkt, dass wir uns hier im loop() befinden, wo die Befehle rasend schnell (mehrere tausend Mal pro Sekunde) durchlaufen werden. Erst wenn eine der beiden Lichtschranken anspricht (die beiden senkrechten Striche || sind der or-Operator und bedeuten oder), also wenn entweder die linke Lichtschranke oder die rechte Lichtschranke angesprochen hat, ist die if Bedingung erfüllt und die Anweisungen im if Body werden ausgeführt. Das ist der Moment, wo die Zeitmessung beginnt. Ihr könnt die Zeitmessung mit jeder der beiden Lichtschranken starten, weil hier ein oder programmiert ist.


    Auf der Zeile 5 merken wir uns die aktuelle Zeit.


    Auf Zeile 6 setzen wir phase auf den nächsten Wert, nämlich auf 1, bzw. PHASE_TIMING_IN_PROGRESS. Damit sorgen wir dafür, dass das Starten der Messung nur einmal durchgeführt wird, denn schon im nächsten Durchlauf von loop() geht es dann in den nächsten case.


    Auf Zeile 7 (currentDir = leftBeamActive ? 0 : 1) setzen wir die Fahr-Richtung auf 0 wenn die linke Lichtschranke angesprochen hat und sonst auf 1. Es ist wichtig, dass wir die aktuelle Fahrrichtung kennen, weil die Messung nur durch die der Start-Lichtschranke entgegengesetzte beendet werden kann. Wir können für das Beenden der Messung nicht eine beliebige Lichtschranke nehmen, wie beim Starten.


    Auf den Zeilen 8 bis 11 sorgen wir dafür, dass unsere Werte schön auf den Initialzustand gesetzt und entsprechend angezeigt werden, weil jetzt der Moment da ist, wo wir eine neue Messung starten.


    Das Fehlen von sinnvollen Anweisungen in den cases PHASE_TIMING_IN_PROGRESS und PHASE_WAITING_FOR_TAIL_END ist der Grund dafür, dass wir im Moment eine gestartete Messung nicht, bzw. nur durch Zurücksetzen mit dem Taster beenden können.

  • Bei mir scheint es zu funktionieren. Der Reset Schalter auf dem Breadboard ist entfernt, beide Lichtschranken reagieren und ganz rechts im Display erscheint nach dem Start ein Sternchen.

    Gruss Erwin



    Wer rast, der verpasst das Leben.


    Kein Platz für weitere Sammelstücke ist nur eine faule Ausrede. ;) Es gibt für alles eine Lösung.

  • Bei mir scheint es zu funktionieren. Der Reset Schalter auf dem Breadboard ist entfernt, beide Lichtschranken reagieren und ganz rechts im Display erscheint nach dem Start ein Sternchen.

    Erfreulich, gratuliere! Hast du mit Messungen von beiden Richtungen probiert? Erscheint die laufende Zeitanzeige auf der der Richtung entsprechenden Zeile (links nach rechts: obere Zeile, rechts nach links: untere Zeile)? Das Zurücksetzten mit der Select-Taste funktioniert?

  • Hast du mit Messungen von beiden Richtungen probiert?

    Röbi, du kennst mich doch, ich probiere alles. ;) Funktioniert genauso wie du es beschreibst. Allerdings musste ich zuerst den Schalter auf dem Breadboard entfernen, mit diesem ergab es seltsame Erscheinungen, bzw. das Programm funktionierte mal so mal so.

    Gruss Erwin



    Wer rast, der verpasst das Leben.


    Kein Platz für weitere Sammelstücke ist nur eine faule Ausrede. ;) Es gibt für alles eine Lösung.

  • Allerdings musste ich zuerst den Schalter auf dem Breadboard entfernen, mit diesem ergab es seltsame Erscheinungen, bzw. das Programm funktionierte mal so mal so.

    Das hört sich für mich so an, als hätte beim pinMode(PIN_PUSH_BUTTON, INPUT_PULLUP) für den Schalter nur INPUT statt INPUT_PULLUP drin gestanden.

  • Nachdem der Compiler wieder einmal rot sah, habe ich gar nicht erst nach dem Fehler in meinem Programm gesucht. Da ich nicht verstehe was ich mache, für Anfänger ist das Programm viel zu komplex, habe ich einfach dein Programm aus Beitrag #123 eingesetzt. Dort gibt es den von dir erwähnten pinMode gar nicht mehr. Zuerst startete der Zähler ohne mein zutun, konnte mit dem Schalter auf dem Anzeigemodul zurückgesetzt werden, startete aber wieder sofort von alleine. Manchmal blieb er nach dem Reset doch stehen. Zwischendurch zählte er auf der unteren, mal auf der oberen Zeile.

    Gruss Erwin



    Wer rast, der verpasst das Leben.


    Kein Platz für weitere Sammelstücke ist nur eine faule Ausrede. ;) Es gibt für alles eine Lösung.

  • Da ich nicht verstehe was ich mache, für Anfänger ist das Programm viel zu komplex

    Viel einfacher kann man aber ein Speed-o-Meter gemäss unserer Spezifikation (Beitrag 64) nicht realisieren. :S



    Zuerst startete der Zähler ohne mein zutun, konnte mit dem Schalter auf dem Anzeigemodul zurückgesetzt werden, startete aber wieder sofort von alleine. Manchmal blieb er nach dem Reset doch stehen. Zwischendurch zählte er auf der unteren, mal auf der oberen Zeile.

    Mit dieser Info ist meine Prognose folgende (bin mir dabei sogar sehr sicher): Bei dir sind die Lichtschranken zu empfindlich eingestellt. Sie sprechen schon an, wenn du mit der Hand nur annähernd in die Nähe des Breadboard kommst. Stell' sie mit dem blauen Poti so ein, dass sie erst ansprechen, wenn du mit dem Finger etwa zwei cm davor bist. Das Ansprechen erkennt man am Leuchten der grünen LED.

  • Viel einfacher kann man aber ein Speed-o-Meter gemäss unserer Spezifikation (Beitrag 64) nicht realisieren.

    Röbi, das glaube ich dir gerne. Zwischen Weihnachten und Neujahr werde ich im Arduino Projektbuch weiterfahren. Wenn ich dort alles begriffen habe und ich selber gestellte Aufgaben lösen kann, dann weiss ich, ich habe es verstanden.


    Die Empfindlichkeit habe ich jetzt eingestellt.

    Gruss Erwin



    Wer rast, der verpasst das Leben.


    Kein Platz für weitere Sammelstücke ist nur eine faule Ausrede. ;) Es gibt für alles eine Lösung.

  • Gratuliere Röbi, Dein Programm funktioniert wie beschrieben.


    Linke Lichtschranke startet die obere Zeitmessung, die restlichen Anzeigen bleiben auf 0. Ganz rechts erscheint ein *.


    Dasselbe auch mit der rechten Lichtschranke und der unteren Anzeigezeile.


    Super, danke.

    Gruss
    Kurt

  • Jawohl - auch bei mir noch Erfolgsmeldung - danke auch für den Tipp mit der Empfindlichkeit des IR Sensors.


    Habe bemerkt, dass die etwas ungleich eingestellt waren. Mit etwas ausprobieren hab ich sie nun so eingestellt, dass sie das Vorbeifahren eines Fahrzeuges jeder Farbe (farbe und/oder Material spielt offenbar eine Rolle) sauber auslöst.


    Offenbar gibt es sogar Loks mit „Stealth“ Technologie. Meine Märklin 3791 BR03 wird am Kessel nicht erkannt - erst das Führerhaus löst die Schranke aus. Irgendwie lustig.

    Bischi :hi:

  • Offenbar gibt es sogar Loks mit „Stealth“ Technologie. Meine Märklin 3791 BR03 wird am Kessel nicht erkannt - erst das Führerhaus löst die Schranke aus. Irgendwie lustig.

    Ich kenne mich mit Loks nicht so gut aus, aber wenn sie einen Kessel und ein Führerhaus hat, ist es vermutlich eine Dampflok und wenn es eine Dampflok ist, ist sie vermutlich schwarz. Schwarz ist heikel für Lichtschranken. Wie das sichtbare Licht reflektiert schwarz auch das IR-Licht schlecht. Ich würde schwarze Loks mit einem geschobenen Wagen (der nicht schwarz ist) messen. Wenn du beide Fahrrichtungen messen willst: Vorne und hinten ein Wagen.

  • Hoi Röbi und Mitschüler

    Auch ich kann nach einstellen der Empfindlichkeit der IR Sensoren eine funktionierende Rückmeldung geben.

    Ich freue mich sehr.

    Weiterhin gespannt wie es weitergeht.

    Adventliche Grüsse Andy

  • Hallo Röbi, hallo Mitstreiter

    Es funktioniert fast wie beschrieben. Trotz INPUT_PULLUP scheint es mir beim Zurückstellen einen Kurzschluss zu verursachen. Mit einem zusätzlichen Widerstand ist das Problem behoben.


    Gruss

    Peter

  • Trotz INPUT_PULLUP scheint es mir beim Zurückstellen einen Kurzschluss zu verursachen. Mit einem zusätzlichen Widerstand ist das Problem behoben.

    Peter, eines diesbezüglichen Fehlers bin ich mir nicht bewusst. Das musst du mir genauer erklären. Wie sieht deine Schaltung genau aus? Wo behebt ein zusätzlicher Widerstand das vermeintliche Problem?

  • Hallo Röbi


    Ist dies so übersichtlich? (Ein leistungsfähiges Zeichenprogramm ist leider nicht mehr in meinem Besitz.)


    Pluspol-----Widerstand 51Ohm--*--Taster-------Minuspol. Steuerleitung zwischen dem Widerstand und dem Schalter(ab *) auf Pin 3.




    Gruss


    Peter