#include #include #include #include #include // Pins definieren const int GPS_PPS_PIN = 2; // PPS-Signal vom GPS für Sekundentakt const int LICHTSCHRANKE_PIN = 3; // Signal von der Lichtschranke const int SD_CS_PIN = 10; // Chip Select für SD-Kartenleser // Speicher-Intervall für die Lichtschranke (jeder 1800. Impuls) const unsigned long SPEICHER_INTERVALL_IMPULSE = 1800; // Instanzen TinyGPSPlus gps; Adafruit_BME280 bme; // Volatile Variablen für die Interrupts volatile unsigned long ppsMicrosRef = 0; // Systemzeit der letzten GPS-Sekunde volatile unsigned long eventMicros = 0; // Systemzeit des 1800. Impulses volatile unsigned long eventPpsRef = 0; // PPS-Referenzwert beim 1800. Impuls volatile bool eventTriggered = false; // Globale Zähler und Zwischenspeicher unsigned long gesamtImpulsZaehler = 0; unsigned long logImpulsNummer = 0; // Globale GPS-Zeitvariablen (werden im Hintergrund aktualisiert) volatile uint8_t gpsStunde = 0, gpsMinute = 0, gpsSekunde = 0; volatile uint8_t gpsTag = 1, gpsMonat = 1; // Mit funktionierenden Standardwerten initialisiert volatile uint16_t gpsJahr = 2026; // Mit funktionierenden Standardwerten initialisiert // Temporäre Kopien der Zeit für das sichere Loggen im Loop uint8_t logStunde, logMinute, logSekunde, logTag, logMonat; uint16_t logJahr; // Interrupt-Service-Routine für den präzisen GPS-Sekundentakt (Pulse Per Second) void ppsISR() { ppsMicrosRef = micros(); } // Interrupt-Service-Routine für die Lichtschranke void lichtschrankeISR() { gesamtImpulsZaehler++; // Nur beim exakt 1800. Impuls frieren wir die Zeiten ein if (gesamtImpulsZaehler % SPEICHER_INTERVALL_IMPULSE == 0) { eventMicros = micros(); eventPpsRef = ppsMicrosRef; // GPS-Zeitstempel für diesen Moment sichern logStunde = gpsStunde; logMinute = gpsMinute; logSekunde = gpsSekunde; logTag = gpsTag; logMonat = gpsMonat; logJahr = gpsJahr; logImpulsNummer = gesamtImpulsZaehler; eventTriggered = true; } } // Hilfsfunktion zur Formatierung des CSV-Zeitstempels (DD.MM.YYYY;HH:MM:SS) void schreibeZeitstempel(File &file, uint8_t tag, uint8_t monat, uint16_t jahr, uint8_t stunde, uint8_t minute, uint8_t sekunde) { // Verhindert das Schreiben von komplett ungültigen Daten (00.00.0000) uint8_t sichererTag = (tag == 0) ? 1 : tag; uint8_t sichererMonat = (monat == 0) ? 1 : monat; uint16_t sicheresJahr = (jahr == 0) ? 2026 : jahr; if (sichererTag < 10) file.print("0"); file.print(sichererTag); file.print("."); if (sichererMonat < 10) file.print("0"); file.print(sichererMonat); file.print("."); file.print(sicheresJahr); file.print(";"); if (stunde < 10) file.print("0"); file.print(stunde); file.print(":"); if (minute < 10) file.print("0"); file.print(minute); file.print(":"); if (sekunde < 10) file.print("0"); file.print(sekunde); } void setup() { Serial.begin(115200); Serial1.begin(9600); // Echte Hardware-Serielle für GPS auf Uno R4 (Pins 0/1) pinMode(GPS_PPS_PIN, INPUT); pinMode(LICHTSCHRANKE_PIN, INPUT_PULLUP); if (!bme.begin(0x76)) { Serial.println("BME280 nicht gefunden! I2C-Adresse prüfen (0x76 oder 0x77)."); while (1); } if (!SD.begin(SD_CS_PIN)) { Serial.println("SD-Kartenfehler!"); while (1); } // Header für das gemeinsame CSV-Logfile anlegen if (!SD.exists("datenlog.csv")) { File dataFile = SD.open("datenlog.csv", FILE_WRITE); if (dataFile) { dataFile.println("Impuls_Nr;Datum_UTC;Zeit_UTC;Mikrosekunden;Temperatur_C;Luftdruck_hPa;Luftfeuchte_Prozent"); dataFile.close(); } } // Interrupts aktivieren attachInterrupt(digitalPinToInterrupt(GPS_PPS_PIN), ppsISR, RISING); attachInterrupt(digitalPinToInterrupt(LICHTSCHRANKE_PIN), lichtschrankeISR, FALLING); Serial.println("System bereit. Warte auf Lichtschranken-Impulse..."); } void loop() { // 1. GPS-Daten kontinuierlich auslesen while (Serial1.available() > 0) { if (gps.encode(Serial1.read())) { if (gps.time.isUpdated() && gps.date.isValid()) { noInterrupts(); gpsStunde = gps.time.hour(); gpsMinute = gps.time.minute(); gpsSekunde = gps.time.second(); gpsTag = gps.date.day(); gpsMonat = gps.date.month(); gpsJahr = gps.date.year(); interrupts(); } } } // 2. Den 1800. Impuls verarbeiten und zusammen mit dem BME280 speichern if (eventTriggered) { eventTriggered = false; // Sub-Sekunden-Offset in Mikrosekunden berechnen unsigned long logSubSekundeMicros = eventMicros - eventPpsRef; // Umweltdaten vom BME280 genau im Moment des Logs abfragen float temp = bme.readTemperature(); float pres = bme.readPressure() / 100.0F; float hum = bme.readHumidity(); // Daten zeilenweise wegschreiben File dataFile = SD.open("datenlog.csv", FILE_WRITE); if (dataFile) { dataFile.print(logImpulsNummer); dataFile.print(";"); schreibeZeitstempel(dataFile, logTag, logMonat, logJahr, logStunde, logMinute, logSekunde); dataFile.print(";"); dataFile.print(logSubSekundeMicros); dataFile.print(";"); dataFile.print(temp, 2); dataFile.print(";"); dataFile.print(pres, 2); dataFile.print(";"); dataFile.println(hum, 2); // Schließt die Zeile ab dataFile.close(); Serial.print("Ziel erreicht! Impuls "); Serial.print(logImpulsNummer); Serial.println(" erfolgreich geloggt."); } else { Serial.println("Fehler: datenlog.csv konnte nicht geöffnet werden!"); } } }