#include #include #include #include #include #include // Pin-Konfiguration const int LIGHT_BARRIER_PIN = 2; const int RTC_SQW_PIN = 3; const int SD_CS_PIN = 10; // Sensor-, Uhr- und GPS-Objekte RTC_DS3231 rtc; Adafruit_BME280 bme; TinyGPSPlus gps; // Volatile Variablen für die Interrupts volatile unsigned long totalPulses = 0; volatile unsigned long ppsMicros = 0; volatile unsigned long currentRtcEpoch = 0; volatile bool ppsFlag = false; // Hilfsvariablen für die Datenübergabe aus dem Interrupt volatile unsigned long targetPulseNumber = 0; volatile unsigned long targetMicros = 0; volatile unsigned long targetEpoch = 0; volatile unsigned long targetPpsMicros = 0; volatile bool logFlag = false; // Status-Variablen für Langzeitstabilität bool sdCardReady = false; unsigned long lastGpsSyncDay = 0; void pulseInterrupt() { totalPulses++; // Alle 600 Impulse Daten-Schnappschuss einfrieren if (totalPulses % 300 == 0) { targetPulseNumber = totalPulses; targetMicros = micros(); targetEpoch = currentRtcEpoch; targetPpsMicros = ppsMicros; logFlag = true; } } void ppsInterrupt() { ppsMicros = micros(); currentRtcEpoch++; ppsFlag = true; } void setup() { Serial.begin(115200); Serial1.begin(9600); // GPS an Hardware-Serial (Pin 0/1) Wire.begin(); pinMode(LIGHT_BARRIER_PIN, INPUT_PULLUP); pinMode(RTC_SQW_PIN, INPUT_PULLUP); // 1. RTC initialisieren if (rtc.begin()) { rtc.writeSqwPinMode(DS3231_SquareWave1Hz); currentRtcEpoch = rtc.now().unixtime(); } else { Serial.println("Kritischer Fehler: RTC nicht gefunden!"); } // 2. BME280 initialisieren if (!bme.begin(0x76)) { Serial.println("Warnung: BME280 nicht gefunden!"); } // 3. Erster SD-Karten-Testversuch checkSDCard(); // Interrupts aktivieren attachInterrupt(digitalPinToInterrupt(RTC_SQW_PIN), ppsInterrupt, RISING); attachInterrupt(digitalPinToInterrupt(LIGHT_BARRIER_PIN), pulseInterrupt, FALLING); Serial.println("Dauerbetrieb gestartet. Warte auf Impulse..."); } void loop() { // Hintergund-Task 1: GPS-Daten für tägliche RTC-Korrektur parsen while (Serial1.available() > 0) { gps.encode(Serial1.read()); } // Hintergrund-Task 2: Tägliche RTC-Korrektur atomar regeln if (ppsFlag) { ppsFlag = false; // Einmal am Tag RTC mit GPS-Zeit abgleichen if (gps.time.isValid() && gps.date.isValid() && gps.date.day() != lastGpsSyncDay) { if (gps.time.age() < 500) { DateTime gpsTime(gps.date.year(), gps.date.month(), gps.date.day(), gps.time.hour(), gps.time.minute(), gps.time.second()); noInterrupts(); rtc.adjust(gpsTime); currentRtcEpoch = gpsTime.unixtime(); lastGpsSyncDay = gps.date.day(); interrupts(); Serial.println("RTC erfolgreich mit GPS synchronisiert."); } } } // Log-Task: Wenn 600 Impulse erreicht wurden if (logFlag) { logFlag = false; // Lokale Kopien sichern (atomar geschützt vor PPS-Interrupts) noInterrupts(); unsigned long pulseNum = targetPulseNumber; unsigned long rtcSec = targetEpoch; unsigned long mMicros = targetMicros; unsigned long pMicros = targetPpsMicros; interrupts(); // KORREKTUR: Ausreichend großer Textpuffer für das Datum (25 Bytes statt 1 Zeichen) char dateTimeBuffer[25]; DateTime logTime(rtcSec); snprintf(dateTimeBuffer, sizeof(dateTimeBuffer), "%02d.%02d.%04d %02d:%02d:%02d", logTime.day(), logTime.month(), logTime.year(), logTime.hour(), logTime.minute(), logTime.second()); // Sensorwerte auslesen float temp = bme.readTemperature(); float press = bme.readPressure() / 100.0F; float hum = bme.readHumidity(); // Hochgenaue Sub-Sekunden-Berechnung mit Überlaufschutz unsigned long offsetMicros = 0; if (mMicros >= pMicros) { offsetMicros = mMicros - pMicros; } else { offsetMicros = (0xFFFFFFFF - pMicros) + mMicros + 1; } if (offsetMicros > 1000000UL) { offsetMicros = 999999UL; } unsigned long exactEpochMs = (rtcSec * 1000) + (offsetMicros / 1000); unsigned int remainingMicros = offsetMicros % 1000; // SD-Karten-Verfügbarkeit prüfen if (!sdCardReady) { checkSDCard(); } if (sdCardReady) { // Sicherheits-Check: Lässt sich die Datei überhaupt öffnen? File dataFile = SD.open("datalog.csv", FILE_WRITE); if (dataFile) { // Zustand prüfen während geschrieben wird. Schlägt fehl, wenn Karte entnommen wurde. size_t bytesWritten = 0; bytesWritten += dataFile.print(pulseNum); bytesWritten += dataFile.print(";"); bytesWritten += dataFile.print(dateTimeBuffer); bytesWritten += dataFile.print(";"); bytesWritten += dataFile.print(exactEpochMs); bytesWritten += dataFile.print(";"); bytesWritten += dataFile.print(remainingMicros); bytesWritten += dataFile.print(";"); bytesWritten += dataFile.print(temp, 2); bytesWritten += dataFile.print(";"); bytesWritten += dataFile.print(press, 2); bytesWritten += dataFile.print(";"); bytesWritten += dataFile.println(hum, 2); dataFile.close(); // Wenn trotz open() nichts geschrieben werden konnte, ist die Karte weg if (bytesWritten == 0) { Serial.println("Schreibfehler: Karte im Schreibvorgang entfernt!"); sdCardReady = false; } else { Serial.print("Daten auf SD geschrieben ["); Serial.print(dateTimeBuffer); Serial.print("] bei Impuls: "); Serial.println(pulseNum); } } else { Serial.println("Schreibfehler: Karte physisch entfernt?"); sdCardReady = false; } } // WICHTIG: Kein 'else' Block mehr, der die Ausführung blockiert. // Wenn die SD-Karte fehlt, läuft das Programm ohne Verzögerung weiter. if (!sdCardReady) { Serial.print("SD fehlt! Daten im RAM gepuffert -> Impuls: "); Serial.print(pulseNum); Serial.print(" | Zeit: "); Serial.print(dateTimeBuffer); Serial.print(" | MS: "); Serial.println(exactEpochMs); } } } void checkSDCard() { // SD.end() schließt alte, hängengebliebene SPI-Sitzungen der entnommenen Karte SD.end(); if (SD.begin(SD_CS_PIN)) { sdCardReady = true; File dataFile = SD.open("datalog.csv", FILE_WRITE); if (dataFile) { if (dataFile.size() == 0) { dataFile.println("Impuls_Nummer;Datum_Uhrzeit;Zeitstempel_Epoch_ms;Sub_Mikrosekunden;Temperatur_C;Luftdruck_hPa;Feuchtigkeit_Prozent"); } dataFile.close(); } Serial.println("SD-Karte bereit und aktiv."); } else { sdCardReady = false; } }