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:
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:
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:
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:
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:
void setup() {
pinMode(PIN_LEFT_LIGHT_BEAM, INPUT);
pinMode(PIN_RIGHT_LIGHT_BEAM, INPUT);
pinMode(PIN_PUSH_BUTTON, INPUT_PULLUP);
lcd.begin(16, 2);
lcd.clear();
for (byte dir = 0; dir < 2; dir++) {
calculateDisplayValues(dir);
}
displayResults();
displayWaitingStatus();
}
Alles anzeigen
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:
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:
switch (phase) {
case PHASE_READY:
if (leftBeamActive || rightBeamActive) {
millisOnBeginOfPeriod = millis();
phase = PHASE_TIMING_IN_PROGRESS;
currentDir = leftBeamActive ? 0 : 1;
mesauredTime = 0.0;
calculateDisplayValues(currentDir);
displayResults();
displayWaitingStatus();
}
break;
case PHASE_TIMING_IN_PROGRESS:
// Hier kommen beim nächsten Schritt noch Anweisungen rein
break;
case PHASE_WAITING_FOR_TAIL_END:
// Hier kommen beim nächsten Schritt noch Anweisungen rein
break;
}
Alles anzeigen
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:
// 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:
void calculateDisplayValues(byte dir) {
if (mesauredTime > 0.0) {
speedMetersPerSecond[dir] = MILLIMETERS_BETWEEN_LIGHT_BEAMS / 1000.0 / mesauredTime;
speedKilometersPerHour[dir] = speedMetersPerSecond[dir] * 3.6 / RAILWAY_SCALE;
cumulatedSpeeds[dir] += speedKilometersPerHour[dir];
} else {
speedMetersPerSecond[dir] = 0;
speedKilometersPerHour[dir] = 0;
cumulatedSpeeds[dir] += speedKilometersPerHour[dir];
}
if (lapCount[dir] == 0) {
averageSpeed[dir] = 0.0;
} else {
averageSpeed[dir] = cumulatedSpeeds[dir] / lapCount[dir];
}
}
void displayElapsedTime() {
char buffer[6];
float elapsedSeconds = (currentMillis - millisOnBeginOfPeriod) / 1000.0;
lcd.setCursor(0, currentDir);
if (elapsedSeconds > MAX_VALUE_FOR_ELAPSED_TIME) {
strcpy(buffer, "*****");
} else {
dtostrf(elapsedSeconds, 5, 1, buffer);
}
lcd.print(buffer);
}
void displayWaitingStatus() {
lcd.setCursor(15, currentDir);
if (phase == PHASE_READY) {
lcd.print(' ');
} else {
lcd.print('*');
}
}
Alles anzeigen
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:
void resetValues() {
mesauredTime = 0.0;
for (byte dir = 0; dir < 2; dir++) {
speedMetersPerSecond[dir] = 0.0;
speedKilometersPerHour[dir] = 0.0;
averageSpeed[dir] = 0.0;
cumulatedSpeeds[dir] = 0.0;
lapCount[dir] = 0;
calculateDisplayValues(dir);
}
phase = PHASE_READY;
displayResults();
displayWaitingStatus();
}
Alles anzeigen
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.