Zum Hauptinhalt springen

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:

  1. Wenn ein Task (doEvent() Funktionen) noch aktiv ist if (taskIsRunning), soll dieser erneut aufgerufen werden.
  2. Ist kein Task aktiv, werden die Taster zur Menüsteuerung eingelesen:
    1. Wurde der UP Taster gedrückt, wird mit doMenu() das Menü aktualisiert, also ein neuer Menüeintrag angezeigt.
    2. Wurde der SELECT Taster gedrückt, wird der aktuelle Menüeintrag mit taskIsRunning = true aktiviert. Beim nächsten loop() Durchlauf wird dann mit doTasks() die zugehörige Event-Funktion aufgerufen.
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.

drawing
Aufgaben
  1. Analysieren Sie das Programmgerüst für ein einfaches Menü. Gehen Sie dabei auf folgende Fragen ein:

    1. Um welchen Datentyp handelt es sich bei der Variablen menuState und wofür wird die Variable verwendet?

      MenuStates menuState = STATE0;
    2. Was macht die Funktion setLeds(0, 1)?

    3. Warum handelt es sich bei den Funktionen doEvent0 und doEvent1 um blockierende Funktionen und bei doEvent2 um eine nichtblockierende Funktion?

    4. Welche Bedeutung hat der Rückgabewert der Event-Funktionen und wofür wird er benötigt?

    5. Was macht die Funktion doTasks() und warum wird sie nur in der if-Verzweigung aufgerufen?

      if (taskIsRunning) {
      doTasks();
      }
  2. Implementieren Sie die beschriebene Menüssteuerung.

  3. 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.

  1. A very simple Arduino task manager
  2. Using an OLED Display with Arduino