Ziel ist es eine Stoppuhr zu programmieren. die Funktionsweise ist sehr gut im Beitrag auf der Internetseite von Start Hardware (https://starthardware.org/stoppuhr-mit-arduino-und-segmentanzeige-arduino-tm1637/) beschrieben:
Die Stoppuhr besitzt eine Anzeige und zwei Taster. Zuerst zeigt die Stoppuhr einfach vier Nullen an. Wird nun der Start-Taster betätigt, fängt die Anzeige an, Sekunden hochzuzählen. Drückt man den Zwischenzeit-Taster, stoppt die Zeit auf dem Display. Im Hintergrund wird aber weitergezählt. Ein erneuter Druck auf den Zwischenzeit-Taster zeigt wieder die aktuell laufende Zeit an. Drückt man den Start-Taster, stoppt die Uhr sowohl im Display, als auch im Hintergrund. Nun kann man die Uhr entweder per Druck auf den Zwischenzeit-Taster weiter laufen lassen, oder durch erneuten Druck auf den Start-Taster auf Null zurücksetzen. (© www.starthardware.org)
Um eine derartige Funktionalität zu erzeugen verwendet man vorzugsweise eine State Maschine oder auch Zustandsautomat genannt.
State Maschine (Zustandsautomat)
Ein endlicher Automat (EA, auch Zustandsmaschine, Zustandsautomat; englisch finite state machine, FSM) ist ein Modell eines Verhaltens, bestehend aus Zuständen, Zustandsübergängen und Aktionen. Ein Automat heißt endlich, wenn die Menge der Zustände, die er annehmen kann (später S genannt), endlich ist. Ein endlicher Automat ist ein Spezialfall aus der Menge der Automaten.
Ein Zustandsautomat ist ein mathematisches Modell zur Verhaltensbeschreibung formaler Systeme . Es ist eine sogenannte abstrakte Maschine , die sich zu jeder Zeit in genau einem von endlich vielen Zuständen befinden kann. Der ZA kann als Reaktion auf Eingaben (Events) von einem Zustand in einen anderen wechseln ; der Wechsel von einem Zustand in einen anderen wird als Zustands-Übergang bezeichnet .
Damit lassen sich beliebig komplexe Steuerungsabläufe beschreiben und Relativ einfach in gängigen Programmiersprachen implementieren. Dazu bedient man sich einer sogenannten Zustandstabelle. In dieser ist für jeden Zustand beschrieben bei welchen Ereignissen welcher Folgezustand eingenommen wird.
Ziel ist es dabei immer, die eigentliche Maschine, also das was in der Zustandstabelle ausgedrückt wird, so einfach und überschaubar wie möglich zu präsentieren. Sie implementiert die Logik und definiert was die Maschine (in unserem Fall natürlich das Programm) eigentlich macht und warum sie es macht. Für unsere Stoppuhr ist folgende Zustandsmaschine aus der Spezifikation entstanden:
switch (programState) { case 0: // gestoppt // hier werden einfach vier Nullen auf dem Display angezeigt // wird der Start-Taster gerdückt, springe zu State 1 break; case 1: // gestartet // zeige die vergangene Zeit auf dem Display // Wenn der Zwischenzeit-Taster gedrückt wird, springe zu State 2 // Wenn der Start-Taster gedrückt wird, springe zu State 3 break; case 2: // Zwischenzeit anzeigen // zeige die Zwischenzeit an // Wenn der Zwischenzeit-Taster gedrückt wird, springe zu State 1 break; case 3: // gestoppt // Wenn der Zwischenzeit-Taster gedrückt wird, springe zu State 1 (weiter laufen) // Wenn der Start-Taster gedrückt wird, springe zu State 0 (löschen) break; }
Die Umsetzung erfolgt in meinem Fall nicht mit einer 7-Segmentanzeige sondern mit Hilfe eines 2-zeiligen LCD Displays. Das entsprechende Programm sieht dann so aus:
//===================================== // Stoppuhr // PHOF 2021 // Uno mit LCD inspiriert vom Beitrag // auf www.starthardware.org //===================================== #define numberOfSeconds(_time_) ((_time_ / 1000) % 60) #define numberOfMinutes(_time_) (((_time_ / 1000) / 60) % 60) #include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27,20,2); int startPin = 8; // start, stop, delete int zwischenzeitPin = 9; // pause, continue int stateStart; int stateZwischenzeit; int lastStateStart; int lastStateZwischenzeit; int programState = 0; unsigned long startZeit = 0; long zwischenzeit = 0; long zeitAngehalten = 0; char LCD_Line1[40]; void setup(){ Serial.begin(9600); lcd.init(); lcd.backlight(); lcd.setCursor(0,0); lcd.print("Stoppuhr"); sprintf(LCD_Line1, "%02d:%02d", 0, 0); // Start-Anzeige lcd.setCursor(6,1);lcd.print(LCD_Line1); pinMode(zwischenzeitPin, INPUT); pinMode(startPin, INPUT); }// end loop void showTime(long theTime) { unsigned long myTime = theTime - startZeit; int seconds = numberOfSeconds(myTime); int minutes = numberOfMinutes(myTime); sprintf(LCD_Line1, "%02d:%02d", minutes, seconds); lcd.setCursor(6,1);lcd.print(LCD_Line1); Serial.print(minutes);Serial.print(" min : "); Serial.print(seconds); Serial.println(" sec"); }// end showTime void loop(){ stateStart = digitalRead(startPin); stateZwischenzeit = digitalRead(zwischenzeitPin); switch (programState) { case 0: // gestoppt startZeit = 0; // start if ((stateStart == LOW) && (stateStart != lastStateStart)) { startZeit = millis(); programState = 1; } break; case 1: // gestartet showTime(millis()); // zwischenzeit if ((stateZwischenzeit == LOW) && (stateZwischenzeit != lastStateZwischenzeit)) { zwischenzeit = millis(); programState = 2; } // stop if ((stateStart == LOW) && (stateStart != lastStateStart)) { zeitAngehalten = millis(); programState = 3; } break; case 2: // zwischenzeit showTime(zwischenzeit); // zwischenzeit ausblenden if ((stateZwischenzeit == LOW) && (stateZwischenzeit != lastStateZwischenzeit)) { programState = 1; } break; case 3: // gestoppt // weiter laufen lassen if ((stateZwischenzeit == LOW) && (stateZwischenzeit != lastStateZwischenzeit)) { startZeit = startZeit + (millis() - zeitAngehalten); programState = 1; } // löschen if ((stateStart == LOW) && (stateStart != lastStateStart)) { programState = 0; } break; } // end case lastStateStart = stateStart; lastStateZwischenzeit = stateZwischenzeit; }