#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SD.h>
#include <SPI.h>
// LCD Display Setup
#define LCD_ADDRESS 0x27 // Die I2C Adresse des LCD Displays
#define LCD_COLUMNS 20
#define LCD_ROWS 4
LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_COLUMNS, LCD_ROWS);
// SD Card Setup
const int chipSelect = 10; // Pin für CS (Chip Select) des SD-Kartenmoduls
// Joystick Pins
const int joyXPin = A1;
const int joyYPin = A0;
const int joyButtonPin = 2;
// Taster Pins für Stoppuhr
const int startButtonPin = 3;
const int stopButtonPin = 4;
// Joystick Debounce
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;
// Joystick State
int lastJoyX = 512;
int lastJoyY = 512;
int lastButtonState = HIGH;
// Stoppuhr Variablen
unsigned long startTime = 0;
unsigned long elapsedTime = 0;
bool running = false;
bool timeStopped = false; // Status der gestoppten Zeit
bool stopwatchEnabled = false; // Status der Stoppuhr-Aktivierung durch Joystick-Button
// Menüstruktur
struct Menu {
const char* name;
Menu* parent;
Menu* child;
Menu* next;
Menu* prev;
};
// Menüeinträge definieren
Menu hauptmenu;
Menu zeitmessung;
Menu auswertung;
Menu leonard;
Menu robert;
Menu georg;
Menu superMax;
Menu luca;
Menu gast;
Menu z_leonard;
Menu z_robert;
Menu z_georg;
Menu z_superMax;
Menu z_luca;
Menu z_gast;
Menu leonardNeuerZeit;
Menu robertNeuerZeit;
Menu georgNeuerZeit;
Menu superMaxNeuerZeit;
Menu lucaNeuerZeit;
Menu gastNeuerZeit;
Menu* currentMenu = &hauptmenu;
Menu* selectedMenu = nullptr;
Menu* previousMenu = nullptr; // Variable für das vorherige Menü
// Neue Variable für das Scrollen
int scrollIndex = 0;
// Menüeinträge initialisieren
void setupMenu() {
hauptmenu = {"Hauptmenu", nullptr, &zeitmessung, nullptr, nullptr};
zeitmessung = {"Zeitmessung", &hauptmenu, &leonard, &auswertung, nullptr};
auswertung = {"Auswertung", &hauptmenu, &z_leonard, nullptr, &zeitmessung};
leonard = {"Leonard", &zeitmessung, &leonardNeuerZeit, &robert, nullptr};
robert = {"Robert", &zeitmessung, &robertNeuerZeit, &georg, &leonard};
//georg = {"Georg", &zeitmessung, &georgNeuerZeit, &superMax, &robert};
//superMax = {"SuperMax", &zeitmessung, &superMaxNeuerZeit, &luca, &georg};
//luca = {"Luca", &zeitmessung, &lucaNeuerZeit, &gast, &superMax};
//gast = {"Gast", &zeitmessung, &gastNeuerZeit, nullptr, &luca};
z_leonard = {"Z_Leonard", &auswertung, nullptr, &z_robert, nullptr};
z_robert = {"Z_Robert", &auswertung, nullptr, &z_georg, &z_leonard};
//z_georg = {"Z_Georg", &auswertung, nullptr, &z_superMax, &z_robert};
//z_superMax = {"Z_SuperMax", &auswertung, nullptr, &z_luca, &z_georg};
//z_luca = {"Z_Luca", &auswertung, nullptr, &z_gast, &z_superMax};
//z_gast = {"Z_Gast", &auswertung, nullptr, nullptr, &z_luca};
leonardNeuerZeit = {"neue Zeit stoppen", &leonard, nullptr, nullptr, nullptr};
robertNeuerZeit = {"neue Zeit stoppen", &robert, nullptr, nullptr, nullptr};
//georgNeuerZeit = {"neue Zeit stoppen", &georg, nullptr, nullptr, nullptr};
//superMaxNeuerZeit = {"neue Zeit stoppen", &superMax, nullptr, nullptr, nullptr};
//lucaNeuerZeit = {"neue Zeit stoppen", &luca, nullptr, nullptr, nullptr};
//gastNeuerZeit = {"neue Zeit stoppen", &gast, nullptr, nullptr, nullptr};
}
void setup() {
// LCD initialisieren
lcd.init();
lcd.backlight();
// SD-Karte initialisieren
Serial.begin(9600);
Serial.print("Initializing SD card... ");
if (!SD.begin(chipSelect)) {
Serial.println("Card initialization failed!");
while (true);
}
// Menü initialisieren
setupMenu();
// Joystick und Taster Pins initialisieren
pinMode(joyButtonPin, INPUT_PULLUP);
pinMode(startButtonPin, INPUT_PULLUP);
pinMode(stopButtonPin, INPUT_PULLUP);
// Erstes Menü anzeigen
selectedMenu = currentMenu->child ? currentMenu->child : currentMenu;
displayMenu();
}
void loop() {
int joyX = analogRead(joyXPin);
int joyY = analogRead(joyYPin);
int buttonState = digitalRead(joyButtonPin);
int startButtonState = digitalRead(startButtonPin);
int stopButtonState = digitalRead(stopButtonPin);
// Debouncing
if ((millis() - lastDebounceTime) > debounceDelay) {
// Joystick hoch/runter
if (joyY < 400 && lastJoyY >= 400) {
lcd.clear();
navigateNext();
lastDebounceTime = millis();
}
if (joyY > 600 && lastJoyY <= 600) {
lcd.clear();
navigatePrev();
lastDebounceTime = millis();
}
// Joystick rechts/links (getauscht)
if (joyX < 400 && lastJoyX >= 400) {
lcd.clear();
navigateChild();
lastDebounceTime = millis();
}
if (joyX > 600 && lastJoyX <= 600) {
lcd.clear();
navigateParent();
lastDebounceTime = millis();
}
// Joystick Button Press
if (buttonState == LOW && lastButtonState == HIGH) {
lcd.clear();
selectMenu();
lastDebounceTime = millis();
}
}
// Stoppuhr Logik
if (currentMenu == &leonardNeuerZeit || currentMenu == &robertNeuerZeit /*||
currentMenu == &georgNeuerZeit || currentMenu == &superMaxNeuerZeit ||
currentMenu == &lucaNeuerZeit || currentMenu == &gastNeuerZeit*/) {
if (buttonState == LOW) { // Wenn Joystick-Button gedrückt wird
stopwatchEnabled = true; // Stoppuhr freigeben
}
if (stopwatchEnabled) {
if (startButtonState == LOW && !running && !timeStopped) {
lcd.clear();
startTime = millis();
running = true;
timeStopped = false;
}
if (stopButtonState == LOW && running) {
elapsedTime = millis() - startTime;
running = false;
timeStopped = true; // Zeit gestoppt
Serial.print(previousMenu->name);
Serial.print(" ");
Serial.print(elapsedTime);
displayTime(); // Zeige die gestoppte Zeit an
// Speichere die Zeit auf der SD-Karte
saveTimeToSD(previousMenu->name, elapsedTime);
}
} else {
// Zeige "warten auf Start..." an, wenn die Stoppuhr nicht freigegeben ist
displayWaitingForStart();
timeStopped = false;
}
} else if (timeStopped) {
// Menü für Zeitvalidierung anzeigen
displayTime();
} else if (currentMenu == &z_leonard) {
displayLastTimes("Leonard.txt"); // Letzte Zeiten von Leonard anzeigen
} else if (currentMenu == &z_robert) {
displayLastTimes("Robert.txt"); // Letzte Zeiten von Robert anzeigen
} /*else if (currentMenu == &z_georg) {
displayLastTimes("Georg.txt"); // Letzte Zeiten von Georg anzeigen
} else if (currentMenu == &z_superMax) {
displayLastTimes("SuperMax.txt"); // Letzte Zeiten von SuperMax anzeigen
} else if (currentMenu == &z_luca) {
displayLastTimes("Luca.txt"); // Letzte Zeiten von Luca anzeigen
} else if (currentMenu == &z_gast) {
displayLastTimes("Gast.txt"); // Letzte Zeiten von Gast anzeigen
}*/
// Aktualisiere die Anzeige kontinuierlich
if (running) {
displayStopwatch();
} else if (timeStopped) {
displayTime(); // Zeige die gestoppte Zeit an
} else if (currentMenu == &leonardNeuerZeit || currentMenu == &robertNeuerZeit /* ||
currentMenu == &georgNeuerZeit || currentMenu == &superMaxNeuerZeit
|| currentMenu == &lucaNeuerZeit || currentMenu == &gastNeuerZeit*/) {
displayWaitingForStart(); // Zeige "warten auf Start..." an
} else {
displayMenu(); // Zeige das aktuelle Menü an
}
delay(100); // Update-Intervall für das Display
lastJoyX = joyX;
lastJoyY = joyY;
lastButtonState = buttonState;
}
void displayMenu() {
//lcd.clear();
lcd.setCursor(0, 0);
lcd.print(currentMenu->name); // Name des aktuellen Menüs in der 1. Zeile anzeigen
Menu* menu = currentMenu->child;
// Nach unten scrollen, wenn der Scrollindex gesetzt ist
for (int i = 0; i < scrollIndex && menu != nullptr; i++) {
menu = menu->next;
}
int line = 1; // Startzeile für das Menü
while (menu != nullptr && line < LCD_ROWS) {
lcd.setCursor(0, line);
if (menu == selectedMenu) {
lcd.print("> ");
} else {
lcd.print(" ");
}
lcd.print(menu->name);
menu = menu->next;
line++;
}
}
void displayWaitingForStart() {
lcd.setCursor(0, 0);
lcd.print("warten auf Start...");
}
void displayStopwatch() {
if (running) {
unsigned long currentTime = millis() - startTime;
float seconds = currentTime / 1000.0;
lcd.setCursor(0, 0);
lcd.print("Stoppuhr laeuft:");
lcd.setCursor(0, 1); // Zeilenabstand
lcd.print(seconds, 2); // 2 Dezimalstellen
lcd.print(" s");
}
}
void displayTime() {
lcd.setCursor(0, 0);
lcd.print("Gestoppte Zeit:");
lcd.setCursor(0, 1); // Zeilenabstand
float seconds = elapsedTime / 1000.0;
lcd.print(seconds, 2); // 2 Dezimalstellen
lcd.print(" s");
}
void navigateTo(Menu* menu) {
if (menu) {
previousMenu = currentMenu; // Speichere das aktuelle Menü als vorheriges Menü
currentMenu = menu;
selectedMenu = currentMenu->child ? currentMenu->child : currentMenu;
displayMenu();
}
}
void navigateNext() {
if (selectedMenu && selectedMenu->next) {
selectedMenu = selectedMenu->next;
if (scrollIndex < LCD_ROWS - 1 && scrollIndex < getMenuItemCount(currentMenu) - 1) {
scrollIndex++;
}
displayMenu();
}
}
void navigatePrev() {
if (selectedMenu && selectedMenu->prev) {
selectedMenu = selectedMenu->prev;
if (scrollIndex > 0) {
scrollIndex--;
}
displayMenu();
}
}
void navigateChild() {
if (selectedMenu && selectedMenu->child) {
scrollIndex = 0; // Beim Wechsel ins Kindmenü den Scrollindex zurücksetzen
navigateTo(selectedMenu);
}
}
void navigateParent() {
if (currentMenu->parent) {
navigateTo(currentMenu->parent);
scrollIndex = 0; // Beim Wechsel ins Elternmenü den Scrollindex zurücksetzen
if (currentMenu != &leonardNeuerZeit && currentMenu != &robertNeuerZeit/* &&
currentMenu != &georgNeuerZeit && currentMenu != &superMaxNeuerZeit &&
currentMenu != &lucaNeuerZeit && currentMenu != &gastNeuerZeit*/) {
stopwatchEnabled = false;
timeStopped = false;
}
}
}
void selectMenu() {
if (selectedMenu) {
scrollIndex = 0;
navigateTo(selectedMenu);
}
}
// Hilfsfunktion zur Bestimmung der Anzahl von Menüeinträgen
int getMenuItemCount(Menu* menu) {
int count = 0;
Menu* tempMenu = menu->child;
while (tempMenu != nullptr) {
count++;
tempMenu = tempMenu->next;
}
return count;
}
// Funktion zum Speichern der Zeit auf der SD-Karte
void saveTimeToSD(const char* menuName, unsigned long time) {
char fileName[13];
char truncatedMenuName[9];
strncpy(truncatedMenuName, menuName, sizeof(truncatedMenuName) - 1);
truncatedMenuName[8] = '\0'; // Nullterminierung
snprintf(fileName, sizeof(fileName), "%s.txt", truncatedMenuName);
File file = SD.open(fileName, FILE_WRITE);
if (!file) {
// Datei konnte nicht geöffnet werden
Serial.print("Fehler beim Öffnen: ");
Serial.print(fileName);
return;
}
Serial.print(fileName);
float seconds = time / 1000.0;
file.print(seconds, 2); // 2 Dezimalstellen
file.println(" s");
file.close();
}
void displayLastTimes(const char* fileName) {
Serial.print(fileName);
File file = SD.open(fileName);
if (!file) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Fehler: Datei");
lcd.setCursor(0, 1);
lcd.print("nicht gefunden");
return;
}
const int maxLines = 10; // Maximal 10 letzte Zeiten anzeigen
String lines[maxLines]; // Array zum Speichern der letzten 10 Zeiten
int lineCount = 0;
while (file.available()) {
String line = file.readStringUntil('\n');
if (lineCount < maxLines) {
lines[lineCount] = line;
} else {
for (int i = 1; i < maxLines; i++) {
lines[i - 1] = lines[i];
}
lines[maxLines - 1] = line;
}
lineCount++;
}
file.close();
int firstLineToShow = 0;
int currentLine = 0;
while (true) {
//lcd.clear();
//lcd.setCursor(0, 0);
//lcd.print("Letzte Zeiten:");
for (int i = 0; i < 3; i++) {
if (firstLineToShow + i < lineCount) {
lcd.setCursor(0, i + 1);
lcd.print(lines[firstLineToShow + i]);
}
}
int joyY = analogRead(joyYPin);
int joyX = analogRead(joyXPin);
if (joyY < 400 && lastJoyY >= 400 && firstLineToShow < lineCount - 3) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(currentMenu->name);
firstLineToShow++;
lastDebounceTime = millis();
}
if (joyY > 600 && lastJoyY <= 600 && firstLineToShow > 0) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(currentMenu->name);
firstLineToShow--;
lastDebounceTime = millis();
}
lastJoyY = joyY;
delay(100);
if (joyX > 600 && lastJoyX <= 600) {
lcd.clear();// Exit loop on joystick left movement
navigateParent();
break;
}
lastJoyX = joyX;
}
displayMenu(); // Go back to menu after displaying times
}