Eine Menüsteuerung mit zwei Tastern und drei LEDs
Mit dem folgenden Programm kann eine einfache Menüsteuerung mit zwei Tastern (Menüpunkt ändern, Menüpunkt aktivieren) und drei LEDs realisiert werden.
Menüanzeige
Als Grundlage für die Menüsteuerung dient ein endlicher Automat zur Auswahl verschiedener Menüeinträge. Für jeden Menüeintrag wird eine unterschiedliche Kombination leuchtender LEDs dargestellt. Hierfür wird die Funktion
setLeds(bool pLed1, bool pLed2)
genutzt. Neben den LEDs wird auch der serielle Monitor zur Darstellung der Menüsteuerung genutzt.
// Aufzählungsdatentyp für die Menüeinträge
enum MenuStates { STATE0, STATE1, STATE2, STATE3 };
MenuStates menuState = STATE0;
// Bei jedem Aufruf ändert sich der gewählte Menüeintrag. Es findet also ein
// Übergang (Änderung) des `menuState` statt: (old state -> new state)
void doMenu() {
switch (menuState) {
case STATE0: // old menu state
menuState = STATE1; // new menu state
setLeds(0, 1); // aktuellen Menüeintrag anzeigen
Serial.print("\n [Menue: STATE1] ");
break;
case STATE1:
menuState = STATE2; // new menu state
setLeds(1, 0); // aktuellen Menüeintrag anzeigen
Serial.print("\n [Menue: STATE2] ");
break;
case STATE2:
menuState = STATE0; // next menu state
setLeds(0, 0); // aktuellen Menüeintrag anzeigen
Serial.print("\n [Menue: STATE0] ");
break;
}
}
Aufrufen der Menü-Funktionen
Zusätzlich zum endlichen Automaten der Menüsteuerung gibt es die Funktion doTasks()
, welche - wenn der Menüeintrag aktiviert wurde - die zugehörigen Menü-Funktionen doEvent1()
, doEvent2()
, etc. aufruft.
Diese Event-Funktionen enthalten den für den entsprechenden Menüeintrag auszuführenden Programmcode. Der Rückgabewert der Event-Funktionen gibt an, ob das Event bereits beendet ist, oder ein weiteres Mal aufgereufen werden soll.
// Gibt an, ob ein Menüeintrag aktiviert ist.
bool taskIsRunning = false;
// Die Funktion ruft mit Hilfe der globalen Variable `menuState` die `doEvent()`
// Funktionen für den aktivierten Menüeintrag auf. Sie sollte regelmäßig in
// loop() aufgerufen werden.
void doTasks() {
switch (menuState) {
case STATE0:
taskIsRunning = !doEvent0(); // doEvent0() returns true if finished.
break;
case STATE1:
taskIsRunning = !doEvent1();
break;
case STATE2:
taskIsRunning = !doEvent2();
break;
}
}
Beispiele für Event-Funktionen
Die Beispiele für die Event-Funktionen schalten eine zuerst LED an und nach einer Sekunde (doEvent0()
) oder fünf (doEvent1()
) Sekunden wieder aus. Diese Event-Funktionen sind blockierend, unterbrechen also das Programm, bis sie beendet wurden.
// Jede `doEvent` Funktion muss `true` zurückgeben, wenn sie beendet ist.
// Für jeden Menüeintrag sollte es eine eigene `doEvent` Funktion geben.
// Blockierendes Event
bool doEvent0() {
digitalWrite(pinTaskRunning, HIGH);
Serial.print("Event 0 is blocking for 1 sec . ");
delay(1000);
Serial.print("finished");
digitalWrite(pinTaskRunning, LOW);
return true; // gibt `true` zurück, wenn die Funktion beendet ist
}
// Blockierendes Event
bool doEvent1() {
digitalWrite(pinTaskRunning, HIGH);
Serial.print("Event 1 is blocking for 5 sec ..... ");
delay(5000);
Serial.print("finished");
digitalWrite(pinTaskRunning, LOW);
return true;
}
Das Funktion doEvent2()
schaltet eine LED an und wartet dann auf eine Tastereingabe, um die LED wieder auszuschalten und das Event zu beenden. Dieses Event ist nicht blockierend.
// Nichtblockierendes Event
bool doEvent2() {
digitalWrite(pinTaskRunning, HIGH);
Serial.print("\n\t\tEvent 2 is running (Press Button) ");
if (debouncerSelect.fell()) {
Serial.print(" ... finished");
digitalWrite(pinTaskRunning, LOW);
return true; // Task wurde durch den Taster beendet.
}
else
return false; // Task ist immer noch aktiv und wird beim nächsten
// loop() Durchlauf erneut aufgerufen.
}
Alles zusammen
In der loop()
-Funktion werde zu guter Letzt alle Teile für die Menüsteuerung zusammengefügt. Nachdem die Zustände der, mit der Bounce2 Bibliothek entprellten, Taster aktualisierte worden sind, beginnt die eigentliche Menüsteuerung:
- Wenn ein Task (
doEvent()
Funktionen) noch aktiv istif (taskIsRunning)
, soll dieser erneut aufgerufen werden. - Ist kein Task aktiv, werden die Taster zur Menüsteuerung eingelesen:
- Wurde der UP Taster gedrückt, wird mit
doMenu()
das Menü aktualisiert, also ein neuer Menüeintrag angezeigt. - Wurde der SELECT Taster gedrückt, wird der aktuelle Menüeintrag mit
taskIsRunning = true
aktiviert. Beim nächstenloop()
Durchlauf wird dann mitdoTasks()
die zugehörige Event-Funktion aufgerufen.
- Wurde der UP Taster gedrückt, wird mit
void loop() {
// aktualisieren der Tasterzustände
debouncerSelect.update();
debouncerUp.update();
if (taskIsRunning) {
doTasks();
}
else {
if (debouncerUp.fell()){ // if button was pressed
doMenu();
}
if (debouncerSelect.fell()){ // if button was pressed
taskIsRunning = true;
}
}
}
Im Flussdiagramm ist der gesamte Ablauf noch einmal zusammengefasst.
Aufgaben
Analysieren Sie das Programmgerüst für ein einfaches Menü. Gehen Sie dabei auf folgende Fragen ein:
Um welchen Datentyp handelt es sich bei der Variablen
menuState
und wofür wird die Variable verwendet?MenuStates menuState = STATE0;
Was macht die Funktion
setLeds(0, 1)
?Warum handelt es sich bei den Funktionen
doEvent0
unddoEvent1
um blockierende Funktionen und beidoEvent2
um eine nichtblockierende Funktion?Welche Bedeutung hat der Rückgabewert der Event-Funktionen und wofür wird er benötigt?
Was macht die Funktion
doTasks()
und warum wird sie nur in derif
-Verzweigung aufgerufen?if (taskIsRunning) {
doTasks();
}
Implementieren Sie die beschriebene Menüssteuerung.
Erweitern Sie die Menüsteuerung um einen weiteren Taster. Die Menüeinträge sollen mit den zwei Tastern in verschiedene Richtungen (auf/ab) ausgewählt werden können.