LCD-Display: Menü in einem Menü

Hallo ich habe ein Problem oder komm eher nicht darauf. Ich habe einen Code wo ich auf meinem LCD-Display ein Menü mit dem Namen Autor stehen habe. Ich möchte jetzt gerne in dem Menü Autor wieder ein Menü haben wo ich unterschiedliche Infos wieder auswählen kann. Also grundsätzlich noch zu meinem Code das Menü wird mit dem Joystick gesteuert nach oben und unten ist im Menü durchgehen, Joystick nach rechts ist auswählen und nach links wieder zurück und das möchte ich auch im anderen Menü so beibehalten.

Das wäre der Code für das Menü Autor:
#include <LiquidCrystal.h>

//Definieren der Pins - Joystick
int PinX = A0;
int PinY = A1;
int PinSW = A2;

//Definieren der LCD-Pins - Display
const int rs=12, en=11, d4=5, d5=4, d6=3, d7=6 ;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

//Werte von X, Y und SW - Joystick
int ValueX = 0; //Hoch und Runter
int ValueY = 0; //Links und Rechts
int ValueSW = 0;

//Menüeinträge - Display
const char* menu[] = {"Autor"};
const int menuLength = 1;
int menuIndex = 0;

//Menüzustand - Display
bool inSubMenu = false;

void setup()
{
//LCD initialisieren - Display
lcd.begin(16, 2);
lcd.clear();

Serial.begin(9600);

//pinMode - Joystick
pinMode(PinX,INPUT);
pinMode(PinY,INPUT);
pinMode(PinSW,INPUT);
}

void loop()
{
//Einlesen der Werte von X und Y
ValueX = analogRead(PinX);
ValueY = analogRead(PinY);
ValueSW = analogRead(PinSW);

//Ausgabe auf Serial Monitor
Serial.print("x = ");
Serial.print(ValueX);
Serial.print(", y = ");
Serial.println(ValueY);
delay(200);

if (!inSubMenu)
{
if (ValueX >= 900) //Hoch
{
menuIndex--;
if (menuIndex < 0)
{
menuIndex=menuLength-1;
delay(200);
}
}

if (ValueX <= 100) //Runter
{
menuIndex++;
if(menuIndex >= menuLength)
{
menuIndex = 0;
delay(200);
}
}

lcd.clear();
lcd.setCursor(0,0);
lcd.print(menu[menuIndex]);
lcd.setCursor(0,1);
lcd.print("Rechts Select");

if(ValueY >= 900) //Auswählen durch drücke
{
inSubMenu=true;
delay(200);
}
}
else
{
lcd.clear();
switch(menuIndex)
{
case 0:
lcd.setCursor(0,0);
lcd.print("Stegh");
break;
}

lcd.setCursor(0,1);
lcd.print("Links Zuruck");

if(ValueY<=100)
{
inSubMenu=false;
delay(200);
}
}
}

Und das wäre der Code für das Menü was ich gerne im Menü Autor hätte:
#include <LiquidCrystal.h>

//Definieren der Pins - Joystick
int PinX = A0;
int PinY = A1;
int PinSW = A2;

//Definieren der LCD-Pins - Display
const int rs=12, en=11, d4=5, d5=4, d6=3, d7=6 ;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

//Werte von X, Y und SW - Joystick
int ValueX = 0; //Hoch und Runter
int ValueY = 0; //Links und Rechts
int ValueSW = 0;

//Autor Untermenü - Display
const char* Autormenu[] = {"Name", "Alter", "Klasse", "Hobby"};
const int Autormenulength = 4;
int AutormenuIndex = 0;

//Menüzustand - Display
bool inAutorMenu = false;

void setup()
{
//LCD initialisieren - Display
lcd.begin(16, 2);
lcd.clear();

Serial.begin(9600);

//pinMode - Joystick
pinMode(PinX,INPUT);
pinMode(PinY,INPUT);
pinMode(PinSW,INPUT);
}

void loop()
{
//Einlesen der Werte von X und Y
ValueX = analogRead(PinX);
ValueY = analogRead(PinY);
ValueSW = analogRead(PinSW);

//Ausgabe auf Serial Monitor
Serial.print("x = ");
Serial.print(ValueX);
Serial.print(", y = ");
Serial.println(ValueY);
delay(200);

if(!inAutorMenu)
{
if (ValueX >= 900) //Hoch
{
AutormenuIndex--;
if (AutormenuIndex < 0)
{
AutormenuIndex=Autormenulength-1;
delay(200);
}
}

if (ValueX < 100)
{
AutormenuIndex++;
if (AutormenuIndex >= Autormenulength)
{
AutormenuIndex = 0;
delay(200);
}
}

lcd.clear();
lcd.setCursor(0,0);
lcd.print(Autormenu[AutormenuIndex]);
lcd.setCursor(0,1);
lcd.print("Rechts Select");

if(ValueY >= 900)
{
inAutorMenu = true;
delay(200);
}
}
else
{
lcd.clear();
switch(AutormenuIndex)
{
case 0:
lcd.setCursor(0,0);
lcd.print("Patricia Stegh");
break;

case 1:
lcd.setCursor(0,0);
lcd.print("18");
break;

case 2:
lcd.setCursor(0,0);
lcd.print("5BHME");
break;

case 3:
lcd.setCursor(0,0);
lcd.print("Fussball");
break;

}

lcd.setCursor(0,1);
lcd.print("Links Zuruck");

if (ValueY <= 100)
{
inAutorMenu = false;
delay(200);
}
}
}

Ich danke für die Hilfe und schönen Tag noch!

Im englischen Teil des Forum müssen die Beiträge und Diskussionen in englischer Sprache verfasst werden.
Deswegen wurde diese Diskussion in den deutschen Teil des Forums verschoben.

mfg ein Moderator.

Setze Deinen Sketch bitte in Codetags. Wie das geht, steht hier.
Außerdem formatiere den Code bitte ordentlich. Strg+T in der IDE hilft Dir dabei.
Das kannst Du auch noch nachträglich ändern.

Gruß Tommy

Nimm eine WO_BIN_ICH und eine NEU_ZEICHNEN Variable (global) .

In der Loop überprüfst du die immer. Wenn sie sich ändert, rufst du eine Routine auf, die alles Neu macht.

Wenn z.b. die Pos. "Autor" ausgelöst wird, setzt die Neuzeichen auf TRUE und WO_BIN_ICH auf Autor. Dann fragst du beide ab. In der Abfrage setzt du Neuzeichnen auf FALSE (damit das Display kein Stress hat) , erstellst das Display neu.
Danach kommt eine Abfrage auf Wo_bin_ich auf "Autor" wo du die neuen Positionen abfragst.

Sind einige IF Abfragen mit Variablen. Ist aber eigentlich leicht.

Ich habe das bei meine Virtuellen Plattenspieler mit verschachtelten Menüs so gemacht.

Hier mal ein Ausschnitt des Codes der dir Erklärt was ich meine.

void loop() {
  uint16_t x, y;
  x = 0;
  y = 0;
  uint8_t success;
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID
  uint8_t uidLength;    

  tft.getTouch(&x, &y);
  Serial.printf("x: %i     ", x);
  Serial.printf("y: %i     ", y);
 Serial.println("-----"); 
  // Karte prüfen
  if (wo == "rfid"){
     Serial.println("wo = rfid0"); 
    success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
    Serial.println("wo = rfid1"); 
    if (success == 1 & karte_status == "") { // neue Karte erkannt
    sub_zeichne_platte ("");
    sub_zeichne_box(30,"Album : ","","");
    sub_zeichne_box(100,"Interpret : ","","");
    sub_zeichne_box(170,"Titel : ","","");
      karte_status = "lesen";
      void sub_Karte_lesen();
      karte_status = "gelesen";
    }
    if (success == 0){
      karte_status = "";
      wo = "hauptmenu";
       Serial.println("sussess = 0");
      // Audio stoppen
    }
  }
  // ende Karte prüfen und spielen 

Serial.printf("x1: %i     ", x);
  if (x > 5 && wo == "hauptmenu") {
    sub_rfid_zeichnen();
    wo = "rfid";
    x = 0;
  }  
   if (x > 5 && wo == "rfid") {
    sub_hauptmenu_zeichnen();
    wo = "hauptmenu";
    x = 0;
  }
} 
//---------------- Ende loop
//---------------- Beginn Subs
//----------------

  void sub_hauptmenu_zeichnen() {
    wo == "hauptmenu";
    tft.fillScreen(ILI9341_BLACK);
    tft.setTextSize(1);
    tft.setTextColor(ILI9341_RED);
    tft.fillRect(0, 10, 320, 40, ILI9341_LIGHTGREY); 
    tft.setCursor(40, 30);
    tft.println("RFID - Lesen"); 
    tft.fillRect(0, 60, 320, 40, ILI9341_LIGHTGREY); 
    tft.setCursor(40, 80);
    tft.println("Internet Radio"); 
    tft.fillRect(0, 110, 320,40 , ILI9341_LIGHTGREY); 
    tft.setCursor(40, 130);
    tft.println("System - Einstellungen"); 
    tft.fillRect(0, 160, 320,40 , ILI9341_LIGHTGREY); 
    tft.setCursor(40, 180);
    tft.println("zurück zum Hauptmenü"); 
   }


Gruß

Pucki

Schon wider nicht gelesen?
Es geht um LCD 1602 nicht um TFT, und was Die Adafruit Ili kann ist dir bekannt?

Das ist nichts für dich: Projekt: LCDMenuLib / LCDMenuLib2 ( LCDML ) - Menü mit mehreren Ebenen ?

Die neueste Version sollte sich über den Library Manager finden lassen.

Es ist völlig egal welches Display man benutzt.

Der Code ist zwar ganz anders, aber die Logik die ich erklären wollte nicht.

Gruß

Pucki

Moin @stegh ,

bist Du noch an der Sache dran?

@stegh kannst du einmal die Menüstruktur ausschreiben?

Mit einer höheren Einrückung bitte eine tiefere Menüebene darstellen.

Hallo @stegh ,

ich habe auf Basis des Standes aus Post 1 einmal versucht nachzuvollziehen, was Du vermutlich beabsichtigst ...

Das Ergebnis ist ein Programm, das

  • die Steuerung per Joystick relativ simpel unterstützt
  • die Menü-Texte komplett im PROGMEM speichert
  • ein einfaches System zur Eingabe von Menütexten und ihnen zugeordneten Aktionen (Zürück - Select) bereitstellt.
  • die Daten auf einem 16x2-LCD ausgibt
  • mittels Taste aus einem beliebigen Menü ins Hauptmenü zurück wechselt.

Das Programm und die Daten sind in getrennten Files gehalten; die Handhabung ist im Code beschrieben. Da die Erstellung mit Iterationen inkl. Dokumentation bisher etwa einen Tag verbraucht hat, sind Optimierungen/Korrekturen nicht auszuschließen ...

Kann hier getestet werden: https://wokwi.com/projects/448952210839345153

Anstelle eines Joysticks habe ich bei Wokwi zwei Schiebewiderstände und einen Taster eingesetzt (der "analoge Joystick" im Wokwi -Portfolio unterstützte - zumindest bei meinen Tests - nur die Werte 0 - 512 - 1023).

Hier der Programm-Sketch:

ino-File
/*

  Forum: https://forum.arduino.cc/t/lcd-display-menu-in-einem-menu/1415656
  Wokwi: https://wokwi.com/projects/448952210839345153

  2025/11/29
  ec2021

  Das vorliegende Programm erlaubt das Erstellen von Menütexten und der Handhabung des Menüs mithilfe
  eines Joysticks. Die vertikale angeordnete Y-Achse dient dem Scrollen innerhalb eines Menüs.

  Die horizontale X-Achse bedient die in der zweiten Zeile des Displays angezeigten Aktionen:
  "<Zurück" nach links, "Select>" nach rechts.

  Mit dem simulierten Joystick-Knopf (Taster) kann man aus jedem Menü direkt ins Hauptmenü
  zurückschalten.

  Die Menüs und ihre Zusammenhänge sind in der Datei "LCDMenue.h" definiert und beschrieben.
  Menü-Texte und zugehörige Tabellen werden im PROGMEM abgespeichert und jeweils nur bei Bedarf
  dort ausgelesen, um den RAM-Verbrauch zu reduzieren.

  Das bestehende Konzept ist durch Verwendung der Ziffern ('0' ... '9') auf maximal 10 Menüs beschränkt.
  Bei Bedarf könnte das Programm auch beispielsweise um die Nutzung von Buchstaben erweitert werden.
  Einfach lesbar ließen sich u.a. die Hex-Ziffern "A" .."F" verwenden, um insgesamt 16 Menüs zu erlauben.

  Zur Information:
  Zur Darstellung von Umlauten auf dem LCD-Display sind diese wie folgt zu codieren:
  Für ü \365      Für ä  \341       Für ö \357
  Beispiel für "Zurück": "Zur\365ck"

*/



/*----------------------------------------------------------------------------
     Für Menü-Ergänzungen ist nur die Datei LCDMenue.h anzupassen!
     Der Code holt sich die erforderlichen Daten aus dem PROGMEM
  -----------------------------------------------------------------------------*/

#include <LiquidCrystal.h>
#include "LCDMenue.h"

// Aktionstext für die zweite Displayzeile, hängt davon ab, ob ein Vorgänger- oder Folgemenü
// oder beides vorliegt. Im Fehlerfall erscheint " >< "
const char action[][maxLen] PROGMEM = {" >< ", "<Zur\365ck        ", "         Select>", "<Zur\365ck  Select>"};
const char* const ac[] PROGMEM = {action[0], action[1], action[2], action[3]};
constexpr int anzahlAC = Size(ac);

//Definieren der Pins - Joystick
constexpr uint8_t PinY {A0};
constexpr uint8_t PinX {A1};
constexpr uint8_t PinSW {A2};

//Definieren der LCD-Pins - Display
constexpr uint8_t rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 6 ;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);


//Werte von X, Y und SW - Joystick
constexpr unsigned long updateInterval {300};
constexpr int lowerLimit {100};  // Schaltschwelle für unten bzw. links
constexpr int upperLimit {900};  // Schaltschwelle für oben bzw. rechts
int ValueY = 0; //Hoch und Runter
int ValueX = 0; //Links und Rechts
uint8_t ValueSW = LOW;
uint8_t lastSW = HIGH;

int actMenu = 0;
int actMenueLen = 0;
int menuIndex = 0;
int lastIndex = -1;
char text[maxLen] = "-";
int prevMenu = -1;
int nextMenu  = -1;


void setup()
{
  Serial.begin(115200);
  lcd.begin(16, 2);
  lcd.clear();
  pinMode(PinSW, INPUT_PULLUP);
}


void loop() {
  joystick();
  scrollen();
  bestaetigen();
  anzeigen();
}



void anzeigen() {
  static int lastMenu = -1;
  if (actMenu != lastMenu) {
    lastMenu = actMenu;
    menuIndex = 0;
    lastIndex = -1;
  }
  if (menuIndex != lastIndex ) {
    lastIndex = menuIndex;
    lcd.clear();
    copyMenuFromPROGMEM(actMenu, menuIndex);
    lcd.setCursor(0, 0);
    lcd.print(text);
    lcd.setCursor(0, 1);
    uint8_t mode;
    mode  =  (prevMenu >= 0) ? 1 : 0;
    mode +=  (nextMenu >= 0) ? 2 : 0;
    lcd.print(actionFromPROGMEM(mode));
  }
}

void bestaetigen() {
  static unsigned long lastOk = 0;
  if (millis() - lastOk > updateInterval) {
    lastOk = millis();
    uint8_t mode = 0;
    mode = (ValueX >   lowerLimit) ? 1 : 0;
    mode = (ValueX >=  upperLimit) ? 2 : mode;
    switch (mode) {
      case 0:
        if (prevMenu >= 0) {
          actMenu = prevMenu;
        }
        break;
      case 2:
        if (nextMenu >= 0) {
          actMenu = nextMenu;
        }
        break;
    }
  }
}

void scrollen() {
  static unsigned long lastScroll = 0;
  if (millis() - lastScroll > updateInterval) {
    lastScroll = millis();
    switch (ValueY) {
      case 0 ... lowerLimit:
        menuIndex++;
        menuIndex = menuIndex % actMenueLen;
        break;
      case upperLimit ... 1023:
        menuIndex--;
        menuIndex = (menuIndex < 0) ? actMenueLen - 1 : menuIndex;
        break;
    }
  }
}

char * actionFromPROGMEM(int index) {
  static char buf[maxLen];
  strcpy_P(buf, (char*)pgm_read_word(&(ac[index])));
  return buf;
}


void copyMenuFromPROGMEM(uint8_t menuIndex, int itemIndex) {
  if (menuIndex >= noMenus) return false;
  // Menü-Eintrag (Struct) aus PROGMEM holen
  MenuEntry entry;
  memcpy_P(&entry, &menuTable[menuIndex], sizeof(MenuEntry));
  if (itemIndex >= entry.count) return;
  actMenueLen = entry.count;
  // Textpointer liegt im Struct, Menü ist 2D-Array
  const char (*menu)[maxLen] = entry.ptr;
  // Text aus PROGMEM in den Buffer kopieren
  char buf [maxLen];
  strncpy_P(buf, menu[itemIndex], maxLen - 1);
  buf[maxLen - 1] = '\0';
  prevMenu = value(buf[0]);
  nextMenu = value(buf[1]);
  strncpy(text, &buf[2], maxLen);
}

int value(char c) {
  return (c >= '0' && c <= '9') ? int(c - '0') : -1;  // Umrechnen von Zeichen in Integer-Zahl für die Menü-Nummer
}

void joystick() {
  //Einlesen der Werte von X und Y
  ValueX = analogRead(PinX);
  ValueY = analogRead(PinY);
  ValueSW = digitalRead(PinSW);
  if (ValueSW != lastSW) {
    lastSW = ValueSW;
    if (!ValueSW) {   // Falls die Taste betätigt wird, geht's unmittelbar zurück ins Hauptmenü
      actMenu = 0;
      menuIndex = 0;
    }
    delay(50);  //Simples Entprellen des Tasters
  }
}

Und hier das Include-File:

LCDMenue.h
#pragma once
/*

  Die Menütexte bestehen aus 2 Zeichen für die Nummer des Vorgänger- und des Folgemenüs
  und aus dem anzuzeigenden Text.

  Format "pnTEXT" mit:
     '-' für kein Vorgängermenü (p) bzw, kein Folgemenü (n)
     '0' Vorgänger/Folgemenü hat die Nummer 0
     '1' Vorgänger/Folgemenü hat die Nummer 1
    usw.

  Bitte beachten, dass die beiden Anfangszeichen jedes Eintrags zwingend(!) mindestens mit "--" zu belegen sind,
  da ansonsten die ersten Buchstaben des Textes ausgewertet werden.
  Der folgende TEXT entspricht dem anzuzeigenden Text.

  Beispiele:

                          Vorgängermenü    Folgemenü      Anzeige
  "03Hardrock" bedeutet:     Nr. 0          Nr. 3,      "Hardrock"
  "0-Fussball" bedeutet:     Nr. 0          keines      "Fussball"
  "-2Lied"     bedeutet:     keines         Nr. 2,      "Lied"

  Das Einfügen der Vorgänger/Folgedaten in Anfang des Menütextes hat den Vorteil
  der direkten Lesbarkeit beim Anlegen und erfordert keine weiteren Strukturen im Code.
  Dafür ist das spätere Einfügen und Ändern von Menü-Abläufen aufwendiger und erfordert
  u.U. das Nachpflegen geänderter Menü-Nummern ... Für "Room for Improvement" ist gesorgt ;-)

  Weitere Menüpunkte können entsprechend dem o.a. Konzept in LCDMenue.h eingerichtet werden; es ist
  ein Eintrage für die Basisdaten und ein Eintrag in einer Tabelle erforderlich;
  im Folgenden steht das X für die Menünummer (aktuell 0 ... 9 möglich).

  // Menue X   - Ersetze pn durch die gewünschte Vorgänger/Folgemenü-Nummer
  const char mX[][maxLen] PROGMEM = {"pnTitel","pnTitel", ... };

  Zusätzlich ist eine weitere Zeile am Ende der Tabelle menuTable[] erforderlich:

  const MenuEntry menuTable[] PROGMEM = {
    { m0, Size(m0) },
    { m1, Size(m1) },
    { m2, Size(m2) },
    { m3, Size(m3) },
    { m4, Size(m4) },
  ....
    { mX, Size(mX) }
  };

*/

#define Size(x) sizeof(x)/sizeof(x[0])
constexpr uint8_t maxLen {20};

typedef struct {
  const char (*ptr)[20];  // Pointer auf 2D-Array in PROGMEM
  uint8_t count;          // Anzahl Einträge im Menü
} MenuEntry;

// Ab hier folgen die Demo-Menüeinträge
// Menue 0     - Kein Vorgängermenü, bei "Autor" -> Folgemenü Nr.1 , bei "Lied" -> Folgemenü Nr. 2
const char m0[][maxLen] PROGMEM = {"-1Autor", "-2Lied"};

// Menue 1   - Aus jedem Menüpunkt zurück ins Menü 0, bei  "Fussball" -> Folgemenü Nr. 4  (Sportarten)
const char m1[][maxLen] PROGMEM = {"0-Patricia Stegh", "0-18", "0-5BHME", "04Fussball"};

// Menue 2   - Aus jedem Menüpunkt zurück ins Menü 0, Folgemenü Nr. 3  bei  "Hardrock"
const char m2[][maxLen] PROGMEM = {"0-Deep Purple", "0-Made in Japan", "0-1972", "03Hardrock"};

// Menue 3   - Aus jedem Menüpunkt zurück ins Menü 2, kein Folgemenü
const char m3[][maxLen] PROGMEM = {"2-Pop", "2-Blues", "2-Folksong", "2-Heavy Metal", "2-Grunge"};

// Menue 4   - Aus jedem Menüpunkt zurück ins Menü 1, kein Folgemenü
const char m4[][maxLen] PROGMEM = {"1-Handball", "1-Hockey", "1-Tennis", "1-Polo", "1-Schach"};

const MenuEntry menuTable[] PROGMEM = {
  { m0, Size(m0) },
  { m1, Size(m1) },
  { m2, Size(m2) },
  { m3, Size(m3) },
  { m4, Size(m4) }
};

constexpr uint8_t noMenus = sizeof(menuTable) / sizeof(menuTable[0]);

Die Handhabung mit den beiden Schiebewiderständen ist etwas gewöhnungsbedürftig, vielleicht klappt das ja mit einem echten Joystick besser ... Man könnte zukünftig die Up/Down-Wiederhol-Funktion so anpassen, dass sie erst startet, wenn der Joystick länger im oberen/unteren Bereich gehalten wird ...

Viel Spaß damit ..
ec2021

Und schon ein Update ...

  • Begrifflichkeiten der Durchgängigkeit wegen angepasst
  • Das Handling des Joysticks von den weiteren Funktionen separiert (vereinfacht die Umsetzung auf z.B. einen Drehencoder) und dabei auch die Richtungsauswertung (hoch/runter) korrigiert
  • Im Code die Möglichkeit eingeführt, bei einzelnen Menü-Punkten per Tastendruck eine spezielle Funktion auszulösen. Als Beispiel hierfür zwei Leds und ein passendes Menü hinzugefügt.
  • Weiterhin erlaubt ein Schiebschalter, die automatische Durchlauffunktion des Menüs ein-/auszuschalten. Bei ausgeschaltetem Durchlauf muss man den Joystick (hier den vertikalen Schiebewiderstand für Up/Down) kurzzeitig jeweils einmal aus dem Schaltbereich Richtung Mitte bringen, bevor wieder ein Up oder Down zugelassen wird.

Zum Testen https://wokwi.com/projects/448973877775459329

ino- und h-File
/*

  Forum: https://forum.arduino.cc/t/lcd-display-menu-in-einem-menu/1415656
  Wokwi: https://wokwi.com/projects/448973877775459329

  2025/11/29
  ec2021

  Das vorliegende Programm erlaubt das Erstellen von Menütexten und der Handhabung des Menüs mithilfe
  eines Joysticks. Die vertikale angeordnete Y-Achse dient dem Scrollen innerhalb eines Menüs.

  Die horizontale X-Achse bedient die in der zweiten Zeile des Displays angezeigten Aktionen:
  "<Zurück" nach links, "Select>" nach rechts.

  Mit dem simulierten Joystick-Knopf (Taster) kann man aus jedem Menü direkt ins Hauptmenü
  zurückschalten.

  Die Menüs und ihre Zusammenhänge sind in der Datei "LCDMenue.h" definiert und beschrieben.
  Menü-Texte und zugehörige Tabellen werden im PROGMEM abgespeichert und jeweils nur bei Bedarf
  dort ausgelesen, um den RAM-Verbrauch zu reduzieren.

  Das bestehende Konzept ist durch Verwendung der Ziffern ('0' ... '9') auf maximal 10 Menüs beschränkt.
  Bei Bedarf könnte das Programm auch beispielsweise um die Nutzung von Buchstaben erweitert werden.
  Einfach lesbar ließen sich u.a. die Hex-Ziffern "A" .."F" verwenden, um insgesamt 16 Menüs zu erlauben.

  Zur Information:
  Zur Darstellung von Umlauten auf dem LCD-Display sind diese wie folgt zu codieren:
  Für ü \365      Für ä  \341       Für ö \357
  Beispiel für "Zurück": "Zur\365ck"

*/



/*----------------------------------------------------------------------------
     Für Menü-Ergänzungen ist in den meisten Fällen nur die Datei LCDMenue.h
     anzupassen! Der Code holt sich die erforderlichen Daten aus dem PROGMEM.

     Eine Ausnahme ist die Programmierung der Taste:
     Ein Tastendruck führt - wenn nicht anders programmiert - aus jedem Menü
     zurück in das Hauptmenu. Die Menüpunkte "Taste für Rot" und "Taste für
     Grün" im Menü 5 "Leuchtdioden" sind beispielhaft anders ausgelegt: Siehe
     dazu die Funktion aufTastenDruck(). Dort können für jedes Menü bzw.
     dessen Menü-Items eigene Funktionen geschrieben werden. Der Menüpunkt
     sollte dies in geeigneter Weise für den Anwender anzeigen, z.B. im Text
     oder mit einem Symbol.
  -----------------------------------------------------------------------------*/

#include <LiquidCrystal.h>
#include "LCDMenue.h"

// Aktionstext für die zweite Displayzeile, hängt davon ab, ob ein Vorgänger- oder Folgemenü
// oder beides vorliegt. Im Fehlerfall erscheint " >< "
const char action[][maxLen] PROGMEM = {" >< ", "<Zur\365ck        ", "        Vorw\341rts>", "<Zur\365ck Vorw\341rts>"};
const char* const ac[] PROGMEM = {action[0], action[1], action[2], action[3]};
constexpr int anzahlAC = Size(ac);

//Definieren der Pins - Joystick
constexpr uint8_t PinY {A0};
constexpr uint8_t PinX {A1};
constexpr uint8_t PinSW {A2};

// Leds
constexpr uint8_t ledGruenPin {2};
constexpr uint8_t ledRotPin {7};

constexpr uint8_t switchPin {8};



//Definieren der LCD-Pins - Display
constexpr uint8_t rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 6 ;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);


//Werte von X, Y und SW - Joystick
constexpr unsigned long updateInterval {300};
constexpr int lowerLimit {100};  // Schaltschwelle für unten bzw. links
constexpr int upperLimit {900};  // Schaltschwelle für oben bzw. rechts

int actMenu = 0;
int actMenuLen = 0;
int menuIndex = 0;
int lastIndex = -1;
char text[maxLen] = "-";
int prevMenu = -1;
int nextMenu  = -1;
boolean goPrevious = false;
boolean goNext     = false;
boolean goUp       = false;
boolean goDown     = false;
boolean btPressed  = false;

void setup()
{
  Serial.begin(115200);
  lcd.begin(16, 2);
  lcd.clear();
  pinMode(PinSW, INPUT_PULLUP);
  pinMode(ledGruenPin, OUTPUT);
  pinMode(ledRotPin, OUTPUT);
  pinMode(switchPin, INPUT_PULLUP);
}

void loop() {
  joystick();
  scrollen();
  bestaetigen();
  anzeigen();
  aufTastenDruck();
}

/*---------------------------------------------------------------------------
                 Beginn der "aufTastenDruck()"-Funktion
  ---------------------------------------------------------------------------
  Hier besteht die Möglichkeit, einem Menü oder bestimmten Menü-Punkten
  eigene Funktionen zuzuordnen, die bei Betätigung der Taste ausgeführt
  werden. In dieser Routine muss handled = true; gesetzt werden,
  ansonsten führt der Tastendruck schlußendlich ins Hauptmenü.

*/

void aufTastenDruck() {
  if (btPressed) {
    boolean handled = false;
    switch (actMenu) {
      case 5:
        if (menuIndex == 0) {
          uint8_t state = digitalRead(ledRotPin);
          digitalWrite(ledRotPin, !state);
          handled = true;
        }
        if (menuIndex == 1) {
          uint8_t state = digitalRead(ledGruenPin);
          digitalWrite(ledGruenPin, !state);
          handled = true;
        }
        break;
      default:
        handled = false;
    }
    if (!handled) {
      actMenu = 0;
      menuIndex = 0;
    }
  }
}
/*---------------------------------------------------------------------------
                    Ende der "aufTastenDruck()"-Funktion
  ---------------------------------------------------------------------------*/


void anzeigen() {
  static int lastMenu = -1;
  if (actMenu != lastMenu) {
    lastMenu = actMenu;
    menuIndex = 0;
    lastIndex = -1;
  }
  if (menuIndex != lastIndex ) {
    lastIndex = menuIndex;
    copyMenuFromPROGMEM(actMenu, menuIndex);
    uint8_t mode;
    mode  =  (prevMenu >= 0) ? 1 : 0;
    mode +=  (nextMenu >= 0) ? 2 : 0;
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(text);
    lcd.setCursor(0, 1);
    lcd.print(actionFromPROGMEM(mode));
  }
}

void bestaetigen() {
  static unsigned long lastOk = 0;
  if (millis() - lastOk > updateInterval) {
    lastOk = millis();
    if (goPrevious && prevMenu >= 0) {
      actMenu = prevMenu;
    }
    if (goNext && nextMenu >= 0) {
      actMenu = nextMenu;
    }
  }
}

void scrollen() {
  static boolean lastUp = false;
  static boolean lastDown = false;
  static unsigned long lastScroll = 0;
  if (millis() - lastScroll > updateInterval) {
    lastScroll = millis();
    if (rotierend()) {
      lastUp = false;
      lastDown = false;
    }
    if (goUp && !lastUp) {
      menuIndex++;
      menuIndex = menuIndex % actMenuLen;
    }
    if (goDown && !lastDown) {
      menuIndex--;
      menuIndex = (menuIndex < 0) ? actMenuLen - 1 : menuIndex;
    }
    lastUp = goUp;
    lastDown = goDown;
  }
}

boolean rotierend() {
  return digitalRead(switchPin);
}

char * actionFromPROGMEM(int index) {
  static char buf[maxLen];
  strcpy_P(buf, (char*)pgm_read_word(&(ac[index])));
  return buf;
}

void copyMenuFromPROGMEM(uint8_t menuIndex, int itemIndex) {
  if (menuIndex >= noMenus) return false;
  MenuEntry entry;
  memcpy_P(&entry, &menuTable[menuIndex], sizeof(MenuEntry));
  if (itemIndex >= entry.count) return;
  actMenuLen = entry.count;
  const char (*menu)[maxLen] = entry.ptr;
  char buf [maxLen];
  strncpy_P(buf, menu[itemIndex], maxLen - 1);
  buf[maxLen - 1] = '\0';
  prevMenu = value(buf[0]);
  nextMenu = value(buf[1]);
  strncpy(text, &buf[2], maxLen);
}

int value(char c) {
  return (c >= '0' && c <= '9') ? int(c - '0') : -1;  // Umrechnen von Zeichen in Integer-Zahl für die Menü-Nummer
}

void joystick() {
  static uint8_t lastSW = HIGH;

  int ValueX = analogRead(PinX);
  goPrevious = (ValueX <=  lowerLimit);
  goNext     = (ValueX >=  upperLimit);

  int ValueY = analogRead(PinY);
  goDown   = (ValueY <= lowerLimit);
  goUp     = (ValueY >= upperLimit);

  btPressed = false;
  uint8_t ValueSW = digitalRead(PinSW);
  if (ValueSW != lastSW) {
    lastSW = ValueSW;
    btPressed = !ValueSW;
  }
  delay(50);  //Simples Entprellen des Tasters
}


LCDMenue.h

#pragma once
/*

  Die Menütexte bestehen aus 2 Zeichen für die Nummer des Vorgänger- und des Folgemenüs
  und aus dem anzuzeigenden Text.

  Format "pnTEXT" mit:
     '-' für kein Vorgängermenü (p) bzw, kein Folgemenü (n)
     '0' Vorgänger/Folgemenü hat die Nummer 0
     '1' Vorgänger/Folgemenü hat die Nummer 1
    usw.

  Bitte beachten, dass die beiden Anfangszeichen jedes Eintrags zwingend(!) mindestens mit "--" zu belegen sind,
  da ansonsten die ersten Buchstaben des Textes ausgewertet werden.
  Der folgende TEXT entspricht dem anzuzeigenden Text.

  Beispiele:

                          Vorgängermenü    Folgemenü      Anzeige
  "03Hardrock" bedeutet:     Nr. 0          Nr. 3,      "Hardrock"
  "0-Fussball" bedeutet:     Nr. 0          keines      "Fussball"
  "-2Lied"     bedeutet:     keines         Nr. 2,      "Lied"

  Das Einfügen der Vorgänger/Folgedaten in Anfang des Menütextes hat den Vorteil
  der direkten Lesbarkeit beim Anlegen und erfordert keine weiteren Strukturen im Code.
  Dafür ist das spätere Einfügen und Ändern von Menü-Abläufen aufwendiger und erfordert
  u.U. das Nachpflegen geänderter Menü-Nummern ... Für "Room for Improvement" ist gesorgt ;-)

  Weitere Menüpunkte können entsprechend dem o.a. Konzept in LCDMenue.h eingerichtet werden; es ist
  ein Eintrage für die Basisdaten und ein Eintrag in einer Tabelle erforderlich;
  im Folgenden steht das X für die Menünummer (aktuell 0 ... 9 möglich).

  // Menue X   - Ersetze pn durch die gewünschte Vorgänger/Folgemenü-Nummer
  const char mX[][maxLen] PROGMEM = {"pnTitel","pnTitel", ... };

  Zusätzlich ist eine weitere Zeile am Ende der Tabelle menuTable[] erforderlich:

  const MenuEntry menuTable[] PROGMEM = {
    { m0, Size(m0) },
    { m1, Size(m1) },
    { m2, Size(m2) },
    { m3, Size(m3) },
    { m4, Size(m4) },
  ....
    { mX, Size(mX) }
  };

  LCD: Für ü \365      Für ä  \341       Für ö \357

*/

#define Size(x) sizeof(x)/sizeof(x[0])
constexpr uint8_t maxLen {20};

typedef struct {
  const char (*ptr)[20];  // Pointer auf 2D-Array in PROGMEM
  uint8_t count;          // Anzahl Einträge im Menü
} MenuEntry;

// Ab hier folgen die Demo-Menüeinträge
// Menue 0     - Kein Vorgängermenü, bei "Autor" -> Folgemenü Nr.1 , bei "Lied" -> Folgemenü Nr. 2
const char m0[][maxLen] PROGMEM = {"-1Autor", "-2Lied", "-5Leuchtdioden"};

// Menue 1   - Aus jedem Menüpunkt zurück ins Menü 0, bei  "Fussball" -> Folgemenü Nr. 4  (Sportarten)
const char m1[][maxLen] PROGMEM = {"0-Patricia Stegh", "0-18", "0-5BHME", "04Fussball"};

// Menue 2   - Aus jedem Menüpunkt zurück ins Menü 0, Folgemenü Nr. 3  bei  "Hardrock"
const char m2[][maxLen] PROGMEM = {"0-Deep Purple", "0-Made in Japan", "0-1972", "03Hardrock"};

// Menue 3   - Aus jedem Menüpunkt zurück ins Menü 2, kein Folgemenü
const char m3[][maxLen] PROGMEM = {"2-Pop", "2-Blues", "2-Folksong", "2-Heavy Metal", "2-Grunge"};

// Menue 4   - Aus jedem Menüpunkt zurück ins Menü 1, kein Folgemenü
const char m4[][maxLen] PROGMEM = {"1-Handball", "1-Hockey", "1-Tennis", "1-Polo", "1-Schach"};

// Menue 5   - Aus jedem Menüpunkt zurück ins Menü 0, kein Folgemenü
const char m5[][maxLen] PROGMEM = {"--Taste f\365r Rot", "--Taste f\365r Gr\365n", "--Taste Hauptmen\365", "0-Normal zur\365ck"};

const MenuEntry menuTable[] PROGMEM = {
  { m0, Size(m0) },
  { m1, Size(m1) },
  { m2, Size(m2) },
  { m3, Size(m3) },
  { m4, Size(m4) },
  { m5, Size(m5) }
};

constexpr uint8_t noMenus = sizeof(menuTable) / sizeof(menuTable[0]);


Jetzt reicht's für heute ... ;-))