/*
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
}
}