/*
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
}
Durchlauf Menü
Ein
Aus
Taster
Runter
Hoch
Zurück
Vorwärts