Watchdog Events im EEPROM mitzählen?

Mein Uhren-/Kalender-Sketch, der recht speicherintensiv ist, hängt sich manchmal auf und der Nano friert ein. Das passiert nur sporadisch, nicht nachvollziehbar und scheinbar zufällig.

Jetzt habe ich den Watchdog eingebaut, möchte aber im EEPROM mitzählen bzw. dort die Anzahl der Resets abspeichern. Nach dem Neustart wird die Resetanzahl auf dem LCD-Display ausgegeben.

Hierfür würde ich in der Watchdog-ISR ein/zwei Byte im EEPROM lesen, um 1 erhöhen und wieder zurückschreiben. Danach kann sich der Nano gerne resetten und neu starten.

Ist das ein sinnvoller Ansatz?

(Ja, ich weiß, ich sollte lieber mal den Sketch fehlerfreier programmieren :mask:)

du könntest auch einfach im setup hochzählen.

Nur was hast davon.
du weist dass er sich neu gestartet hat.
Was ändert das?
Ungefragter Tipp: such den Bug.

Kann man machen, aber ich würde das nicht tun.
Ich würde die Zeit aus der RTC lesen. Und nur dann in den EEprom schreiben, wenn die letzte geschriebene Sekunde nicht der aktuellen entspricht.

Also: Du brauchst ein feld für den Zähler und ein byte für die Sekunde.
Im setup erst die RTC aktivieren, die Sekunde auslesen, die Sekunde aus dem EEprom auslesen und erst wenn die unterschiedlich sind neu schreiben...

Mich interessiert, wie oft der Nano einfriert.
Evtl. könnte ich zusätzlich einen Zeitstempel wegsichern oder Variableninhalte, wenn diese dann überhaupt noch zugänglich sind.

Der Sketch arbeitet viel mit Char-Arrays (ohne String); hier vermute ich einige Unwägbarkeiten bzgl. Stack- und Heap-Nutzung.

Daran glaub ich eher nicht.

Ist das noch immer die Uhr, die schon früher Schwierigkeiten hatte?

Dann baue ein SD-Modul da ran und zeichne in einem Logfile auf, was da passiert, wenn Du das nicht auf der seriellen Schnittstelle ausgeben und in einem Protokollfile aufzeichen kannst.

Mir sieht das eher nach EMV-Problemen aus...

Nein, ist nicht die Word Clock. Die läuft sehr stabil.

Ist ein anderes Projekt mit LCD und Kalendereinträgen, etlichen Feiertagsberechnungen, Sonnenaufgang, Mondphasendarstellung, Luftdruckauswertung (BME280), DCF77 und einer RTC.

Ach - ja, stimmt, das wars :slight_smile:
Möchtest Du den Code vom aktuellen Projekt zur Verfügung stellen?
Vielleicht findet auch ein blindes Huhn ein Korn....

Da müßte ich erst einiges im Sketch anonymisieren, evtl. morgen.
Ich verwende aktuell den Optiboot Bootloader, sonst wirdˋ s knapp mit dem Speicher.

na mach mal.
Tipp: Sowas ganz oben einmal constant festlegen - spart viel Zeit fürs durchschauen. Den Rest erledigt der Compiler.

Nein!
Genau andersrum!

Nach jedem Reset den Counter erhöhen.

#include <EEPROM.h>
#include <Streaming.h> // die Lib findest du selber ;-)
Print &cout = Serial; // cout Emulation für "Arme"


unsigned long resetsImEeprom EEMEM {0};



void setup() 
{
  unsigned long resetsImRam;
  Serial.begin(9600);
  cout << F("Start: ") << F(__FILE__) << endl;
  EEPROM.get(int(&resetsImEeprom),resetsImRam);
  resetsImRam++;
  EEPROM.put(int(&resetsImEeprom),resetsImRam);
 
  
  cout << F("Reset Counter: ") << resetsImRam << endl;

}

void loop() 
{

}
1 Like

Anbei `mal der aktuelle Code. Oben sind mehrere #defines. Für die "lange" Sketch-Version bitte so einstellen:

#define DCF77_Modul       // wenn Zeile auskommentiert, dann kein DCF77 angeschlossen !!
#define BME280
//#define DHT22

Ich hänge auch mal einen Schaltplan an und Fotos der aufgebauten Schaltung.
Von der Uhr gibt es 2 Varianten: eine mit DCF77 und einem BME280 sowie eine abgespeckte Variante ohne DCF77 und mit einem DHT22 ohne Luftdruckmessung.

/*  ----------------------------------------------------------------------------------------------------------------------------------------
      Küchenuhr mit Kalenderfunktion
      Ausgabe von Uhrzeit, Datum, Wochentag, Temperatur, Luftdruck und Luftfeuchtigkeit auf einem LCD-Display
                                              V 1.33 Arduino Forum
                                              domapi Dezember 2021
    ----------------------------------------------------------------------------------------------------------------------------------------
      - je nachdem, welche #defines auskommentiert werden, eignet sich diese Version für:
        - mit/ohne Verwendung von DCF77
        - BME280 oder DHT22-Sensor
      - LCD-Display über I²C
      - RTC3231
      - DCF77 optional
      - BME280 Sensor (DHT22 optional)
      - Zusätzliche Anzeige von Kalendereinträgen (Geburtstage, Mülltermine etc.)
      - Darstellung von Zeit für Sonnenaufgang, -untergang, Mondphase
      - Beleuchtete Fläche des Mondes in % abhängig von der Mondphase
      - Datum des nächsten Vollmondes
      - LEDs von RTC und I2C Adapter ablöten


    --------------------------------------------------------------------------------------------------------------------------------------------------*/

/*
    Arduino Nano:
    //                      +-----+
    //         +------------| USB |------------+
    //         |            +-----+            |
    //         | [ ]D13/SCK        MISO/D12[ ] |    LED DCF77-Empfang (blinkt kurz, bei Empfang einer gültigen Zeit
    // BME280  | [ ]3.3V           MOSI/D11[ ]~|
    //         | [ ]V.ref     ___    SS/D10[ ]~|
    // LDR     | [ ]A0       / N \       D9[ ]~|
    //         | [ ]A1      /  A  \      D8[ ] |
    //         | [ ]A2      \  N  /      D7[ ] |
    //         | [ ]A3       \_0_/       D6[ ]~|
    // I²C SDA | [ ]A4/SDA               D5[ ]~|   Ansteuerung LCD-Display-Beleuchtung mit PWM über Transistor/FET
    // I²C SCL | [ ]A5/SCL               D4[ ] |
    //         | [ ]A6              INT1/D3[ ]~|   n.c. (DHT22 Temperatur und Luftfeuchtigkeitssensor)
    //         | [ ]A7              INT0/D2[ ] |   DCF77-Modul Signal-Ausgang
    //         | [ ]5V                  GND[ ] |
    //         | [ ]RST                 RST[ ] |
    //         | [ ]GND   5V MOSI GND   TX1[ ] |
    //         | [ ]Vin   [ ] [ ] [ ]   RX1[ ] |
    //         |          [ ] [ ] [ ]          |
    //         |          MISO SCK RST         |
    //         | NANO-V3                       |
    //         +-------------------------------+
*/

#define DCF77_Modul       // wenn Zeile auskommentiert, dann kein DCF77 angeschlossen !!
#define BME280
//#define DHT22
#define debug           // wenn auskommentiert, dann keine Textausgabe von Meldungen im seriellen Monitor

#define Anzahl_Termine  8
#define Tage_next       7

#include <avr/wdt.h>        // für Watchdog

// Abfrage und Darstellung von Uhrzeit und Datum auf Basis des Frankfurter Funkuhrsenders und Update der RTC-DS3231

// Date and time functions using a DS3231 RTC connected via I2C and Wire lib
#include <RTClib.h>        // z.B. https://wolles-elektronikkiste.de/ds3231-echtzeituhr
RTC_DS3231 rtc;

#include <TimeLib.h>

// Verlagerung der Wochentagnamen in den Programmspeicher, spart Variablenplatz!
// Verarbeitung ist aber etwas komplizierter!

const char s_0[] PROGMEM = "Sonntag";
const char s_1[] PROGMEM = "Montag";
const char s_2[] PROGMEM = "Dienstag";
const char s_3[] PROGMEM = "Mittwoch";
const char s_4[] PROGMEM = "Donnerstag";
const char s_5[] PROGMEM = "Freitag";
const char s_6[] PROGMEM = "Samstag";


const char *const Wochentag_Name [] PROGMEM = {s_0, s_1, s_2, s_3, s_4, s_5, s_6}; // {"Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"};
char WT_buffer [11];       // für´s Auslesen

/*  Pin     Function                        Nano/Uno
    --------------------------------------------------------------
    GND     Ground pin
    VCC     Power Supply pin
    SCL     Serial Clock pin                A5
    SDA     Serial Data pin                 A4
    SQW     Square-wave Output pin          (It is used to generate square wave or to be used as an interrupt for the alarms)
    32K     32KHz Open-drain Output pin     (This pin is used for a reference clock.)

    !!! Achtung: Diode auf dem DS3231 entfernen, wenn Betrieb mit Batterie anstatt mit Akku !!! sonst würde die Batterie aufgeladen --> kann dann platzen!!!

    I2C Adressen:
    -------------
    - 0x68: DS3231 (= der eigentliche Sensor)
    - 0x57: AT24C32 32Kbit EEPROM (4096 8 bit words of non-volatile storage) --> kann zur Speicherung von Daten verwendet werden!

*/


// --------------------------------------------------------------------------------------------------------------------------------------------------
// I²C-Library

#include <LiquidCrystal_I2C.h>          // LiquidCrystal_I2C Bibliothek einbinden
LiquidCrystal_I2C lcd (0x27, 20, 4);    // Display mit 20 Zeichen/4 Zeilen an der HEX-Adresse 0x27.


/*  LCD wird über I²C-Adaper betrieben, daher nur 5 Anschlüsse notwendig
    - SDA
    - SCL
    - GND
    - +5V
    - Steuerung Hintergrundbeleuchtung via PWM an einem der Jumperbeinchen

    Direkte LCD-Pins
    -----------------
    1   GND
    2   +5V
    3   Displayspannung/Kontrast (4,7k an 0V)
    4   RS
    5   GND (Read/Write)
    6   Enable
    7
    8
    9
    10
    11  D4  Datenleitung
    12  D5
    13  D6
    14  D7
    15  LED + an +5V
    16  LED - an GND
*/

// LCDs können 8 eigen-definierte Zeichen zur gleichen Zeit darstellen, mehr geht leider nicht:
// Character Generator: https://elektro.turanis.de/html/tools/calc_lcd_character.html
// Zeichen ins PROGMEM verlagern: https://github.com/johnrickman/LiquidCrystal_I2C/issues/28

// diese Bilder bleiben immer constant:
const byte    Grad      [8] PROGMEM = {B01100, B10010, B10010, B01100, B00000, B00000, B00000, B00000};     // "°"-Zeichen für Temp.
const byte    Antenne   [8] PROGMEM = {B01010, B00000, B10101, B00100, B10101, B00100, B00100, B00100};     // Antenne für DCF77-Signalempfang
const byte    Kreuz     [8] PROGMEM = {B00100, B00100, B11111, B00100, B00100, B00100, B00100, B00100};     // Kreuz für Todestag
const byte    Aufgang   [8] PROGMEM = {B00100, B01110, B11111, B00100, B00100, B00100, B00100, B00100};     // Pfeil nach oben
const byte    Untergang [8] PROGMEM = {B00100, B00100, B00100, B00100, B00100, B11111, B01110, B00100};     // Pfeil nach unten

// diese 4 Patterns werden dynamisch auf character 5 im LCD umgestellt:

const byte    Mond_2    [8] PROGMEM = {B00000, B00110, B00011, B00011, B00011, B00011, B00110, B00000};     // zunehmende Sichel
const byte    Mond_3    [8] PROGMEM = {B00000, B00110, B00111, B00111, B00111, B00111, B00110, B00000};     // zunehmender Halbmond
const byte    Mond_4    [8] PROGMEM = {B00000, B01110, B00111, B00111, B00111, B00111, B01110, B00000};     // zunehmender Mond
const byte    Mond_6    [8] PROGMEM = {B00000, B01110, B11100, B11100, B11100, B11100, B01110, B00000};     // abnehmender Mond
const byte    Mond_7    [8] PROGMEM = {B00000, B01100, B11100, B11100, B11100, B11100, B01100, B00000};     // abnehmender Halbmond
const byte    Mond_8    [8] PROGMEM = {B00000, B01100, B11000, B11000, B11000, B11000, B01100, B00000};     // abnehmende Sichel

const byte    Mond_5    [8] PROGMEM = {B00000, B01110, B11111, B11111, B11111, B11111, B01110, B00000};     // Vollmond


// --------------------------------------------------------------------------------------------------------------------------------------------------
// BME280-Temperatur-/Luftdruck- und Luftfeuchtigkeitssensor

#ifdef BME280
#include "cactus_io_BME280_I2C.h"            // http://cactus.io/hookups/sensors/barometric/bme280/hookup-arduino-to-bme280-barometric-pressure-sensor
BME280_I2C bme(0x76);                        // I2C using address 0x76

/*
    Da das LCD-Display ebenfalls an der I²C-Schnittstelle hängt, aber 5 V benötigt,
    muss der BME280-Sensor, der nur an 3,3V funktioniert, mit einem Pegel-Wandeler (Level Converter) angeschlossen werden!

    Arduino      Level Converter        BME280-Platine
    ---------------------------------------------------------------------------------------------------------
    5 Volt           HV                 -
    3.3 Volt         LV                 VCC            100nF an GND direkt an Sensorplatine!
    GND              GND                GND
    A4/SDA           HV1                SDA/SDI
    A5/SCL           H22                SCL/SCK
    SDO              -                  GND            Auswahl I2C-Adresse: GND = 0x76, open/3,3V = 0x77


    Anschlüsse stand alone (ohne Level converter):

    Arduino      BME280-Platine
    ------------------------------------------------------------------------
    3.3 Volt         VCC            100nF an GND direkt an Sensorplatine!
    GND              GND
    A4/SDA           SDA/SDI        10k PU an +3,3V !
    A5/SCL           SCL/SCK        10k PU an +3,3V !
    -                SDO            Auswahl I2C-Adresse: GND = 0x76, open/3,3V = 0x77
*/

// Array definieren 6 h x 4 Werte (alle Viertelstunde) = 24 x 2 Bytes, wenn int
// der aktuellste Wert steht im letzten Array-Element <> 0
// Den Luftdruck x 10 in das Array schreiben, d.h. eine Kommastelle ist später auswertbar.
// Damit reicht int-Array, spart Speicher!

int Baro_Trend [24];

// Zähler für Anzahl Luftdruck-Vergangenheitswerte
byte Anzahl_Trendwerte_Baro = 0;

// Höhe über NN am Standort der Messstation
#define Hoehe 345

float Luftdruck        = 0;

#endif

float Luftfeuchtigkeit = 0;
float Temperatur       = 0;


#ifdef DHT22
// --------------------------------------------------------------------------------------------------------------------------------------------------
// DHT22-Temperatur-/Luftfeuchtigkeitssensor

#include <DHT.h>

DHT dht(3, DHT22); //Der Sensor "dht" hängt am Eingang D3

/*
            ---------
            |       |
            |       |
            |       |
            |       |
            |       |
            ---------
             1 2 3 4

             1 = +5V
             2 = Data
             3 = not connected
             4 = GND
*/

//https://www.espruino.com/DHT22
#endif

/*
    --------------------------------------------------------------------------------------------------------------------------------------------------
    DCF77-Empfänger (Amazon)

    (https://www.amazon.de/Unbekannt-Empfangsmodul-Funkzeit-Funkuhr-Frankfurt/dp/B07RRXRY36/ref=asc_df_B07RRXRY36/?tag=googshopde-21&linkCode=df0&hvadid=407101186503&hvpos=&hvnetw=g&hvrand=698829227693340346&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9042621&hvtargid=pla-867197362694&psc=1&th=1&psc=1&tag=&ref=&adgrpid=85271755365&hvpone=&hvptwo=&hvadid=407101186503&hvpos=&hvnetw=g&hvrand=698829227693340346&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9042621&hvtargid=pla-867197362694)

    Hier eine gute Info-Quelle: https://dcf77logs.de/live

    Anschlüsse:
    -----------
    DCF77-Modul        Arduino
    P1                     D?          LOW = Empfänger aktiv, HIGH = inaktiv; dauerhaft auf GND setzen oder über separaten Arduino-Pin steuern, damit kann man das DCf77 auch inaktiv setzen
    T                      D2          Zeitsignal an Interrupt-PIN
    P2                     n.c.
    G                      GND
    V                      3,3V !!!

*/
#ifdef DCF77_Modul
#include "DCF77.h"            // nutzt Interrupt an Pin 2 des Nano !

#define  DCF_PIN 2            // Connection pin to DCF77 device
#define  DCF_INTERRUPT 0      // Interrupt number associated with pin

DCF77 DCF = DCF77(DCF_PIN, DCF_INTERRUPT);
#endif

// --------------------------------------------------------------------------------------------------------------------------------------------------
// LDR für Anpassung LCD-Hintergrundbeleuchtung an Umgebungslicht
// +5V ---PU 1k--A0--LDR----GND

const byte LDR_Pin = A0;                    // Messung der Umgebungsbeleuchtung
const byte LCD_Pin = 5;                     // Ansteuerung der LCD-Hintergrundbeleuchtung via BC557 an Anode der LCD-LEDs = Pin PD5

int        LDR_Helligkeit = 0, LDR_Helligkeit_alt = 0;
const byte Empfindlichkeit    = 10;
const byte Brigthness_min     = 245;       // Minimale Hintegrundbeleuchtung = 245
byte       LCD_Brigthness     = 0;

bool       Flag_brigthness    = false;     // Anpassen Helligkeit starten
int        Anzahl_Stufen      = 0;         // Berechnen über wieviele kleine Anpassungen
int        faktor;

unsigned long millis_erste_Aenderung = 0;
byte Status_hell = 1;


// --------------------------------------------------------------------------------------------------------------------------------------------------

unsigned long  millis_sensor_alt   = 0;            // Aktualisierung der Sensordaten
unsigned long  millis_empfang_alt  = 0;            // Blinken Antennen bei Empfang eines gültigen DCF77-Signals
unsigned long  millis_antenne_alt  = 0;            // Blinken Antennensymbol während der Initialisierung/Aktualisierung
unsigned long  millis_start_update = 0;            // Zeitstempel, wann das Suchen einer neuen DCF77-Zeit gestartet wurde
unsigned long  millis_hell_alt     = 0;

byte           sek_seit_start_akt  = 0;            // Anzahl Sekunden, wann die DCF77-Signalsuche gestartet wurde

byte           previous_Second     = 0;
byte           RTC_Status          = 0;            // State machine für RTC-Abgleich
/*
    0  = Uhr noch nicht mit DCF synchronisiert
    1  = RTC wurde neu gestellt (dann wird die Aktualisierung wieder ausgeschaltet)
*/

#ifdef DCF77_Modul
byte Init_min   = 0;          // Dauer bis Initialisierung der RTC mit DCF77
byte Init_sek   = 0;
byte Update_h   = 0;
byte Update_min = 0;          // Zeitpunkt an dem die letzte Aktualisierung stattfand
#endif

struct event_jahrestag
{
    // Struktur z.B. für Geburtstage und Feiertage
    byte Tag;
    byte Monat;
    int  Jahr;          // 0 = Feiertag
    char Name[21];      // 20 Zeichen + NULL für eine LCD-Displayzeile
    byte Typ;           // 1 = Geburtstag, 2 = Todestag, 3 = Jahrestag
};

struct event_muellabfuhr
{
    // Struktur für Müllabfuhrtermine, hier ohne Text, um Speicherplatz zu sparen
    byte Tag;
    byte Monat;
    byte Jahr;          // hier reicht Byte, später muss halt 2000 dazu addiert werden!
    byte Typ;           // 4 = Restmüll, 5 = Blaue Tonne, 6 = Gelbe Tonne, 7 = Biomüll für "Morgen ..."-Termine jeweils + 10 addieren
};


// Wir brauchen 3 Arrays:
// 1. die 9 Feiertage, die von Ostern abhängen, im SRAM, da diese dynamisch pro Jahr berechnet werden müssen
// 2. die Jahrestage wie Geburtsag etc. im PROGMEM
// 3. Müllabfuhrtermine im PROGMEM ohne Name/Bezeichnung, da der Typ aussagekräftig genug ist (spart Speicher!)
// alle 3 Arrays werden später eingelesen!

// Im Programm dynamisch berechnete zusätzliche Tage wie Beginn/Ende Sommerzeit und 1. - 4. Advent etc.


/*
    Hier die Codes für Sonderzeichen:
    \341  ä
    \342  ß
    \357  ö
    \363  Zeichen unendlich oo
    \365  ü
    \367  pi ( klein )
    \377  alle Leuchtpunkte eingeschaltet
*/

const event_jahrestag Ereignis [] PROGMEM =             // müssen nicht sortiert sein!
{
    // hier die fixen Datümer auflisten
    { 1,  1,    0, "Neujahr",             3},
    { 6,  1,    0, "Heilige Drei K\357nige", 3},
    { 1,  5,    0, "Maifeiertag",         3},
    {15,  8,    0, "Mari\341 Himmelfahrt",  3},
    { 3, 10,    0, "Tag d. dt. Einheit",  3},
    { 1, 11,    0, "Allerheiligen",       3},
    {24, 12,    0, "Heiligabend",         3},
    {25, 12,    0, "1. Weihnachtstag",    3},
    {26, 12,    0, "2. Weihnachtstag",    3},
    {31, 12,    0, "Sylvester",           3},

    // ab hier individuelle Ereignisse eintragen:

    {14,  2, 1985, "Gisela",          1},
    {21,  2,    0, "Valentinstag",    3},
    {20,  3, 1985, "Sabine",          1},
    {12,  4, 1957, "Paula Preller",   1},
    {23,  5, 1963, "Cornelia",        1},
    {27,  5, 1927, "Franz",           1},
    { 1,  6, 1974, "Strunzilein",     1},
    { 8,  6, 1976, "Oskar Wilde",     1},
    { 5,  7, 2020, "Müller Oma",      2},
    {12,  7, 1968, "Michael Huber",   1},
    {14,  7, 2002, "Laurentia",       1},
    {18,  7, 2005, "Mann & Frau",     3},
    { 4,  8, 1992, "Paumillion",      1},
    { 5,  8, 1938, "Karlchen",        1},
    { 2,  9, 1959, "Thomas Müller",   1},
    { 7,  9, 1944, "Hugo E. Balder",  1},
    { 8,  9, 1930, "Doris Bauer",     1},
    {23,  9, 2002, "Franz",           2},
    {25, 10, 1989, "Agnes W\357lfel", 1},
    {11, 11, 2014, "Mayer Opa",       2},
    {11, 11,    0, "Bulzamärdel",     3},
    {12, 11, 1974, "Ursula Albers",   1},
    {19, 11, 1950, "Huber Buam",      1},
    {27, 11, 1947, "Jimmi Hendrix",   1},
    { 5, 12, 1922, "Eckstein Opa",    1},
    { 6, 12,    0, "Nikolaus",        3},
    {15, 12, 2020, "Gundula Gause",   2},
    {26, 12, 1969, "Olga West",       1},
};

const event_muellabfuhr Muellabfuhr [] PROGMEM =
{
    {17,  8, 21, 4},          // Restmüll
    {24,  8, 21, 4},
    {31,  8, 21, 4},
    { 7,  9, 21, 4},
    {14,  9, 21, 4},
    {21,  9, 21, 4},
    {28,  9, 21, 4},
    { 5, 10, 21, 4},
    {12, 10, 21, 4},
    {19, 10, 21, 4},
    {26, 10, 21, 4},
    { 3, 11, 21, 4},
    { 9, 11, 21, 4},
    {16, 11, 21, 4},
    {23, 11, 21, 4},
    {30, 11, 21, 4},
    { 7, 12, 21, 4},
    {14, 12, 21, 4},
    {20, 12, 21, 4},
    {28, 12, 21, 4},

    {24,  8, 21, 5},          // Blaue Tonne
    {21,  9, 21, 5},
    {19, 10, 21, 5},
    {17, 11, 21, 5},
    {15, 12, 21, 5},

    {30,  8, 21, 6},          // Gelbe Tonne
    {13,  9, 21, 6},
    {27,  9, 21, 6},
    {11, 10, 21, 6},
    {15, 12, 21, 6},
    {25, 10, 21, 6},
    { 8, 11, 21, 6},
    {22, 11, 21, 6},
    { 6, 12, 21, 6},
    {20, 12, 21, 6},

    {20,  8, 21, 7},          // Biomüll
    {27,  8, 21, 7},
    { 3,  9, 21, 7},
    {10,  9, 21, 7},
    {17,  9, 21, 7},
    {24,  9, 21, 7},
    { 1, 10, 21, 7},
    { 8, 10, 21, 7},
    {15, 10, 21, 7},
    {22, 10, 21, 7},
    {26, 10, 21, 7},
    {29, 10, 21, 7},
    { 6, 11, 21, 7},
    {12, 11, 21, 7},
    {19, 11, 21, 7},
    {26, 11, 21, 7},
    { 3, 12, 21, 7},
    {10, 12, 21, 7},
    {17, 12, 21, 7},
    {23, 12, 21, 7},
    {31, 12, 21, 7},
};

event_muellabfuhr Muellabfuhr_SRAM;                   // wird für die Kopie in den Variablenspeicher benötigt

event_jahrestag Feiertage_Ostern [9] =
{
    // Die Feiertage, die direkt von Ostern abhängen, müssen berechnet werden, daher werden sie im SRAM abgelegt (im PROGMEM können nur const abgelegt werden!)
    // 9 Platzhalter für die Oster-abhängigen Feiertage

    { 0, 0, 0, "Rosenmontag",         3},
    { 0, 0, 0, "Faschingsdienstag",   3},
    { 0, 0, 0, "Karfreitag",          3},
    { 0, 0, 0, "Ostersonntag",        3},
    { 0, 0, 0, "Ostermontag",         3},
    { 0, 0, 0, "Christi-Himmelfahrt", 3},
    { 0, 0, 0, "Pfingstsonntag",      3},
    { 0, 0, 0, "Pfingstsmontag",      3},
    { 0, 0, 0, "Fronleichnam",        3},
};

event_jahrestag Ereignis_SRAM;                  // wird für die Kopie in den Variablenspeicher benötigt

byte Tag_alt            = 0;                    // altes Jahr, alten Tag merken, um bei Änderungen Aktionen einzuleiten
int  Jahr_alt           = 0;
bool Flag_Tagesereignis = true;


struct event_Termin                             // Struktur zur Speicherung der nächsten Termine
{
    // Struktur Tagesereignisse
    char Name[21];
    byte Typ;           // 1 = Geburtstag, 2 = Todestag, 3 = Jahrestag etc.
    byte Alter;
    byte Tag;
    byte Monat;
    int  Jahr;          // 0 = Feiertag
};

event_Termin Termine_next [Anzahl_Termine];
event_Termin temp_Termine_next;                 // temporär für das Sortieren der Termine nach Datum

byte Index = 0;

byte Dauer_Datum   = 0;
byte Display_Datum = 0;

byte Dauer_Zusatzinfo   = 0;
byte Display_Zusatzinfo = 0;

byte Dauer_Screen   = 0;
byte Display_Screen = 0;

// Variablen für Sonne und Mond ohne Sterne ;-)
byte a_stunde, a_minute, u_stunde, u_minute;
byte delta = 1;
byte Mondphase;

/*  Diese Konstanten sind bereits vordefiniert !!!
    PI       3.1415926535897932384626433832795
    HALF_PI  1.5707963267948966192313216916398
    TWO_PI   6.283185307179586476925286766559
*/

// Ein paar globale Datümer
DateTime Vollmond_ref = DateTime (2021, 7, 24, 4, 37, 11);          // Vollmond als Referenz war am 24.07.2021 04:37:11
DateTime Zeitpunkt_Vollmond_next;
DateTime Datum_aktuell;    // aktuelles Tagesdatum
DateTime Morgen       ;
DateTime Datum_plus_x ;   // aktuelles Tagesdatum + x Tage
DateTime Termin;

// --------------------------------------------------------------------------------------------------------------------------------------------------

void setup()
{

#ifdef debug
    Serial.begin(115200);
#endif

#ifdef DCF77_Modul
    DCF.Start();                  // kann mit DCF.Stop() auch wieder deaktiviert werden
#endif

    DDRB |= (1 << PB4);           //pinMode (12, OUTPUT);  // externe LED angeschlossen, signalisiert DCF77-Empfang
    PORTB &= ~(1 << PORTB4);      //= digitalWrite (12, LOW);

    // I²C-Library
    lcd.init();
    lcd.clear();

    lcd.setCursor(3, 0);
    lcd.print(F("PI-Clock V 1.33"));
    lcd.setCursor(3, 2);
    lcd.print(F("(C)   04.12.21"));

    lcd.setCursor(7, 2);
    lcd.print(char(0xF7));      // PI

    lcd.setCursor(0, 3);

#ifdef DHT22
    lcd.print(F("DHT22 "));
#endif

#ifdef BME280
    lcd.print(F("BME280 "));
#endif

#ifdef DCF77_Modul
    lcd.print(F("DCF77"));
#endif

    lcd.backlight();                  // Hintergrundbeleuchtung einschalten (lcd.noBacklight(); schaltet die Beleuchtung aus).

    DDRD |= (1 << PD5);               // pinMode      (LCD_Pin, OUTPUT);
    PORTD &= ~(1 << PORTD5);          // digitalWrite (LCD_Pin, LOW);

    // an D5 hängt über einen 1,2K Widerstand ein PNP BC557 Tansistor, dessen Kollektor an die Anode der LED zur LCD-Beleuchtung geht (Pin 15 am LCD)
    // der Jumper am I²C-Interface mit dem PCF8574 muss entfernt werden. Der rechte Pin ist die Anode.
    // bei anderen Adapter ggf. ausmessen und anpassen !!!

    delay(3000);

    // Definition der konstanten Bilder

    lcd.createChar_P (0, Grad);         // "°"-Zeichen für LCD-Darstellung
    lcd.createChar_P (1, Antenne);      // Antennen-Zeichen für LCD-Darstellung
    lcd.createChar_P (2, Kreuz);        // Kreuz-Zeichen für LCD-Darstellung
    lcd.createChar_P (3, Aufgang);      // Pfeil nach oben für LCD-Darstellung
    lcd.createChar_P (4, Untergang);    // Pfeil nach unten für LCD-Darstellung
    lcd.createChar_P (6, Mond_5);       // Vollmond

    // RTC-Uhr starten
    rtc.begin();

    if (rtc.lostPower())
    {
        // When time needs to be set on a new device, or after a power loss, the following line sets the RTC to the date & time this sketch was compiled
        rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    }

    // When time needs to be re-set on a previously configured device, the following line sets the RTC to the date & time this sketch was compiled
    // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, format: Jahr, Monat, Tag, Stunde, Minute, Sekunde
    //rtc.adjust(DateTime(2021, 12, 1, 8, 27, 0)); // zum Stellen der Uhr --> 18 s vor ganz hochladen!

    setSyncProvider(syncProvider);                  // Synchronisation der Nano-internen Uhr mit der RTC; erfolgt standardmässig alle 5 min, anpassbar mit setsyncInterval(x sek)
    setSyncInterval(60);

    // SQW-Ausgang am DS3231 auf 1Hz Takt einstellen
    // hier kann eine LED den Sekundentakt anzeigen (SQW ---  LED --- Vorwiderstand --- +5V)
    //rtc.writeSqwPinMode(DS3231_SquareWave1Hz);

#ifdef BME280
    // BME280 Sensor starten
    bme.begin();
    bme.setTempCal(-2);

    // Luftdruck-Array für die Trendermittlung initial auf 0 setzen
    for (byte i = 0; i < 24; i++)
    {
        Baro_Trend [i] = 0;
    }

#endif

#ifdef DHT22
    //DHT22 Sensor starten
    dht.begin();
#endif

#ifdef debug
    Serial.println (F("Start Aktualisierung"));
#endif
    lcd.clear();

    // Falls sich die Uhr aufhängt aus irgendwelchen Gründen, dann sorgt der Watchdog dafür, dass sich die Uhr resettet und wieder von vorne startet

    wdt_enable(WDTO_500MS); // nach 500ms ohne regelmäßigen Reset in der loop(), beißt er zu ;-)

    /*  Einrichtung und Starten des Watchdog (OTPIBOOT Bootloader erforderlich!) läuft nach folgenden Schema ab:

        cli();                              // schaltet alle Interrupts ab. Das ist notwendig, da die Einrichtung des Watchdog Timers gestört werden könnte
        asm("WDR");                         // Assembler Anweisung für den Watchdog Reset
        WDTCSR |= (1<<WDCE) | (1<<WDE);     // leitet die Änderung der Watchdog Parameter ein. Es ist wichtig, dass „|=“ und nicht „=“ verwendet wird.

        Nach dieser Initialisierung muss die eigentliche Änderung innerhalb der nächsten 4 Taktzyklen erfolgen.
        WDTCSR = (1<<WDE) | (1<<WDP3);      // bedeutet: Reset ist aktiviert und der Watchdog Timer ist auf vier Sekunden eingestellt (siehe Einstellungstabelle).
        oder WDTCSR = (1 << WDE) | (1 << WDP0) | (1 << WDP2);         // 500ms / no interrupt, system reset
        sei();                              // lässt Interrupts wieder zu.

        Zeit    Konstante   Prescaler-Bits
                            WDP0    WDP1    WDP2    WDP3
        16 ms   WDTO_15MS   0       0       0       0
        32 ms   WDTO_30MS   1       0       0       0
        64 ms   WDTO_60MS   0       1       0       0
        125 ms  WDTO_120MS  1       1       0       0
        250 ms  WDTO_250MS  0       0       1       0
        500 ms  WDTO_500MS  1       0       1       0
        1 s     WDTO_1S     0       1       1       0
        2 s     WDTO_2S     1       1       1       0
        4 s     WDTO_4S     0       0       0       1
        8 s     WDTO_8S     1       0       0       1
    */
}




// -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
void loop()
{
    //    Serial.println(millis());

#ifdef DCF77_Modul
    time_t DCFtime = DCF.getTime();         /* store the current DCF time in time variable DCFtime, format 32 bit seconds since 1970
                                               time_t speichert einen Zeitstempel, der in ganzen Sekunden beginnend mit dem 1. Januar 1970, 00:00:00 UTC gezählt wird.
                                               time_t entspricht dem Datentyp long, ist also 32 Bit breit und entspricht dem Unix-Timestamp. */

    if (DCFtime != 0)                       // ist eine neue, gültige DCF77 Zeit vorhanden ?
    {
        setTime(DCFtime);                   // setzt die System-Zeit des Arduino auf die DCFtime. Die System-Zeit kann über hour(), minute() etc. abgefragt werden. Wird einfach über millis() weitergezählt, ist also recht ungenau!

#ifdef debug
        Serial.println (F("Gültiges DCF77-Signal gefunden"));
#endif

        if (RTC_Status == 0)                // wenn noch keine Aktualisierung durch DCF77
        {
            // Datum und Uhrzeit der DCF77 in RTC übertragen
            rtc.adjust(DateTime(year(), month(), day(), hour(), minute(), 0));

            // Zeit wurde aktualisiert nach ...
            Init_min = (millis() - millis_start_update ) / 60000;
            Init_sek = ((millis() - millis_start_update ) / 1000) % 60;

            RTC_Status  = 1;
#ifdef debug
            Serial.println (F("1 RTC-Aktualisierung"));
#endif
            Update_h   = hour();
            Update_min = minute();
        }

        millis_empfang_alt = millis();

        // Antennensymbol und LED für 1 Sekunde anzeigen
        PORTB |= 1 << PORTB4;      // = digitalWrite(12, HIGH);

        if (Display_Screen == 0 && Dauer_Screen != 0)           // nur wenn der Überblicksbildschirm schon einmal angezeigt wurde!
        {
            lcd.setCursor(15, 0); lcd.write(byte(1));
        }
    }
#endif

    //---------------------------------------------------------------------------------------------------------------------------
    // Aktionen, die alle Sekunde durchgeführt werden müssen

    if (second() != previous_Second)
    {
        previous_Second = second();

#ifdef DCF77_Modul
        if (RTC_Status == 0)                   // Wenn aktualisiert werden kann
        {
            sek_seit_start_akt++;              // Zeit hochzählen, seit die Aktualisierung gestartet wurde

            if (sek_seit_start_akt > 200)      // nach 200s DCF77-Signalsuche den Aktualisierungversuch erfolglos abbrechen
            {
                RTC_Status = 1;
                PORTB &= ~(1 << PORTB4);      // = digitalWrite(12, LOW);                      // LED ausschalten

                if (Display_Screen == 0 && Dauer_Screen != 0)           // nur wenn der Überblicksbildschirm schon einmal angezeigt wurde!
                {
                    lcd.setCursor(15, 0); lcd.print(' ');   // Antennenzeichen löschen
                }

#ifdef debug
                Serial.println(F("Abbruch Aktualisierung (> 200s)"));
#endif
            }
        }
#endif

#ifdef DCF77_Modul
        //---------------------------------------------------------------------------------------------------------------------------
        // Aktualisierung alle 10 min, immer zu min x9 + 57 sec starten
        if ((minute() % 10 == 9 ) && (second() == 57) && RTC_Status == 1 )  // alle 10 Min versuchen, zu aktualisieren
        {
            RTC_Status = 0;
            millis_start_update = millis();
            sek_seit_start_akt = 0;
#ifdef debug
            Serial.println (F("Start Aktualisierung"));
#endif
        }
#endif


        //-----------------------------------------------------------------------------------------------------------------------------
        if (year() != Jahr_alt)      // wenn neues Jahr, dann Feiertage neu berechnen
        {
            Jahr_alt = year();
            Feiertage_Ostern_abhaengig (Jahr_alt);
        }

        //-----------------------------------------------------------------------------------------------------------------------------
        if (day() != Tag_alt)      // wenn neuer Tag, dann Termine einlesen
        {
            Tag_alt = day();
            Flag_Tagesereignis = false;     // erstmal so tun, als hätten wir noch kein Tagesereignis gefunden
            Index = 0;                      // Index = 0 --> keine Termine zum Anzeigen gefunden

            // Array Termine löschen
            for (byte i = 0; i < Anzahl_Termine; i++)
            {
                Termine_next[i].Typ    = 0;
                strcpy(Termine_next[i].Name, '\0');
                Termine_next[i].Tag    = 0;
                Termine_next[i].Monat  = 0;
                Termine_next[i].Jahr  = 0;
                Termine_next[i].Alter = 0;
            }

            // Ein paar Datümer bestimmen für Vergleichzwecke
            Datum_aktuell = DateTime (year(), month(), day(), 0 , 0 , 0);    // aktuelles Tagesdatum
            Morgen        = Datum_aktuell + TimeSpan(1, 0, 0, 0);
            Datum_plus_x  = Datum_aktuell + TimeSpan(Tage_next, 0, 0, 0);    // aktuelles Tagesdatum + x Tage


            // ----------------------------------------------------------------------------------------------------------------------------------------------------------
            // Prüfen, ob Sommerzeit-Beginn oder Ende sowie ob Adventstermine

            byte SZ_Beginn = 31 - Wochentag (31,  3, year());       // letzten Sonntag im März bestimmen
            Termin = DateTime(year(), 3, SZ_Beginn, 0, 0, 0);       // Sommerzeitbeginn
            if ( Datum_aktuell.unixtime() <= Termin.unixtime() && Termin.unixtime() <= Datum_plus_x.unixtime())
            {
                Termin_eintragen (3, "Beginn Sommerzeit"); // Typ, Text
                Index++;
            }

            byte SZ_Ende   = 31 - Wochentag (31, 10, year());       // letzten Sonntag im Oktober bestimmen
            Termin = DateTime(year(), 10, SZ_Ende, 0, 0, 0);        // Sommerzeitende
            if ( Datum_aktuell.unixtime() <= Termin.unixtime() && Termin.unixtime() <= Datum_plus_x.unixtime())
            {
                Termin_eintragen (3, "Ende Sommerzeit"); // Typ, Text
                Index++;
            }

            // Der 4. Adventssonntag ist stets der Sonntag vor dem 1. Weihnachtsfeiertag, muss also stets in der Periode [18. - 24.12.] liegen:

            byte Advent_4 = 25 - Wochentag (25, 12, year());
            DateTime Vierter_Advent = DateTime(year(), 12, Advent_4, 0, 0, 0);

            for (int i = 3; i >= 0; i--)
            {

                Termin = (Vierter_Advent - TimeSpan(7 * i, 0, 0, 0));

                if ( Datum_aktuell.unixtime() <= Termin.unixtime() && Termin.unixtime() <= Datum_plus_x.unixtime())
                {
                    Termin_eintragen (3, " "); // Typ, Text

                    // Hier dann noch den richtigen Namen ermitteln und eintragen

                    char anr[2];                     // ein kleines Hilfsarray für die Nummer des Adventtages
                    anr[0] = char(4 - i + 48);       // entsprechend befüllen
                    anr[1] = '\0';                   // NULL-Terminator nicht vergessen

                    strcpy(Termine_next[Index].Name, anr);
                    strcat(Termine_next[Index].Name, ". Advent");

                    Index++;
                }
            }

            // Der Buß- und Bettag liegt stets 32 Tage vor dem  4. Advent
            Termin = (Vierter_Advent - TimeSpan(32, 0, 0, 0));
            if ( Datum_aktuell.unixtime() <= Termin.unixtime() && Termin.unixtime() <= Datum_plus_x.unixtime())
            {
                Termin_eintragen (3, "Bu\342- und Bettag"); // Typ, Text
                Index++;
            }

            // Muttertag = 2. Sonntag im Mai
            Termin = DateTime(year(), 5, 15 - Wochentag (1,  5, year()), 0, 0, 0);
            if ( Datum_aktuell.unixtime() <= Termin.unixtime() && Termin.unixtime() <= Datum_plus_x.unixtime())
            {
                Termin_eintragen (3, "Muttertag"); // Typ, Text
                Index++;
            }


            // -----------------------------------------------------------------------------------------------------------------------------------------------------------
            // Feiertage

            for (byte i = 0; i < 9; i++)
            {
                // über die Tabelle mit den Variablen, der durch Ostern festgelegten Feiertagen loopen und Tag/Monat mit aktuellem Datum vergleichen
                // dann den Namen anzeigen etc.

                Termin = DateTime ( year(), Feiertage_Ostern[i].Monat, Feiertage_Ostern[i].Tag, 0, 0, 0 );        // Zeitstempel des Feiertags für den folgenden Datums-Vergleich

                if ( Datum_aktuell.unixtime() <= Termin.unixtime() && Termin.unixtime() <= Datum_plus_x.unixtime())
                {
                    Termin_eintragen (Feiertage_Ostern[i].Typ, Feiertage_Ostern[i].Name); // Typ, Text
                    Index++;                                                              // Index um 1 erhöhen
                }
            }

            // -----------------------------------------------------------------------------------------------------------------------------------------------------------
            for (byte i = 0; i < sizeof(Ereignis) / sizeof(event_jahrestag); i++)
            {
                // Restliche Termine und fixe Feiertage einlesen; über die Tabelle loopen und Tag/Monat mit aktuellem Datum vergleichen
                // dann den Namen anzeigen etc.

                memcpy_P (&Ereignis_SRAM, &Ereignis[i], sizeof(event_jahrestag));                   // zuerst einen Eintrag des PROGMEM-Arrays ins SRAM (Variablenspeicher) kopieren
                Termin = DateTime ( year(), Ereignis_SRAM.Monat, Ereignis_SRAM.Tag, 0, 0, 0 );      // Zeitstempel des Termins für den folgenden Datums-Vergleich

                if ( Datum_aktuell.unixtime() <= Termin.unixtime() && Termin.unixtime() <= Datum_plus_x.unixtime())
                {
                    Termin_eintragen (Ereignis_SRAM.Typ, Ereignis_SRAM.Name); // Typ, Text

                    // dann noch gleich das Alter berechnen
                    if (Ereignis_SRAM.Typ == 1 || Ereignis_SRAM.Typ == 2)    Termine_next[Index].Alter = year() - Ereignis_SRAM.Jahr;

                    if (Index < Anzahl_Termine)
                    {
                        Index++;
                    }
                    else
                    {
                        break;                  // wenn bereits x Ereignisse gefunden, dann Schleife verlassen!
                    }
                }
            }

            // -----------------------------------------------------------------------------------------------------------------------------------------------------------
            // Müllabfuhr

            for (byte i = 0; i < sizeof(Muellabfuhr) / sizeof(event_muellabfuhr); i++)
            {
                // Müllabfuhr-Termine einlesen; über die Tabelle loopen und Tag/Monat mit aktuellem Datum vergleichen

                memcpy_P (&Muellabfuhr_SRAM, &Muellabfuhr[i], sizeof(event_muellabfuhr));                                   // zuerst einen Eintrag des PROGMEM-Arrays ins SRAM (Variablenspeicher) kopieren
                Termin = DateTime (Muellabfuhr_SRAM.Jahr + 2000, Muellabfuhr_SRAM.Monat, Muellabfuhr_SRAM.Tag, 0, 0, 0);    // Zeitstempel des Termins für den folgenden Datums-Vergleich

                if ( Datum_aktuell.unixtime() <= Termin.unixtime() && Termin.unixtime() <= Datum_plus_x.unixtime())
                {
                    switch (Muellabfuhr_SRAM.Typ)
                    {
                        case 4:
                            Termin_eintragen (Muellabfuhr_SRAM.Typ, "Restm\365ll"); // Typ, Text
                            break;
                        case 5:
                            Termin_eintragen (Muellabfuhr_SRAM.Typ, "Blaue Tonne"); // Typ, Text
                            break;
                        case 6:
                            Termin_eintragen (Muellabfuhr_SRAM.Typ, "Gelbe Tonne"); // Typ, Text
                            break;
                        case 7:
                            Termin_eintragen (Muellabfuhr_SRAM.Typ, "Bio-Tonne"); // Typ, Text
                            break;
                    }

                    if (Index < Anzahl_Termine)
                    {
                        Index++;
                    }
                    else
                    {
                        break;                  // wenn bereits x Ereignisse gefunden, dann Schleife verlassen!
                    }
                }

            }
            // Gefundene Ereignisse nach Datum aufsteigend sortieren (primitiver bubble sort; sind ja nur ein paar Einträge)

            for (int k = 1; k <= Index; k++)
            {
                for (int i = 0; i <= Index - 1 - k; i++)
                {
                    // Datum berechnen: 31.10.2021 --> 20211031
                    unsigned long Eintrag_i        = Termine_next[i].Jahr * 10000 + Termine_next[i].Monat * 100 + Termine_next[i].Tag;
                    unsigned long Eintrag_i_plus_1 = Termine_next[i + 1].Jahr * 10000 + Termine_next[i + 1].Monat * 100 + Termine_next[i + 1].Tag;

                    if (Eintrag_i > Eintrag_i_plus_1)
                    {
                        // Zeilen tauschen
                        memcpy (&temp_Termine_next, &Termine_next[i],    sizeof(event_Termin));                  // aktuellen Datensatz komplett ins temp wegsichern
                        memcpy (&Termine_next[i],   &Termine_next[i + 1], sizeof(event_Termin));                 // dann den i+1 ten auf i schrieben
                        memcpy (&Termine_next[i + 1], &temp_Termine_next, sizeof(event_Termin));                 // und den temporär gesicherten nach i+1
                    }
                }
            }

#ifdef debug
            for (byte i = 0; i < Anzahl_Termine; i++)
            {
                Serial.print(Termine_next[i].Typ); Serial.print("  ");
                Serial.print(Termine_next[i].Name); Serial.print("  ");
                Serial.print(Termine_next[i].Tag); Serial.print(".");
                Serial.print(Termine_next[i].Monat); Serial.print(".");
                Serial.print(Termine_next[i].Jahr); Serial.println();
            }
#endif

            // Berechnung von Sonnenauf- und Sonnenuntergang
            delta = 1;

            if (summertime_EU (year(), month(), day(), 3) == true)
            {
                delta = 2;
            }
            sunCalculation( year(), month(), day(), 10.192130, 48.423672, delta, a_stunde, a_minute, u_stunde, u_minute); // Koordinaten aus Google Maps

            Mondphase = GetMoonPhase();
        }

        //-----------------------------------------------------------------------------------------------------------------------------------------------------------------
        // Anzeige der Infos auf dem LCD, ggf. abwechselnd

#ifdef BME280
        Dauer_Screen++;                // Anzeigedauer in Sekunden hochzählen, startet bei 0

        if (Display_Screen == 0)       // Haupt-Screen mit Datum, Terminen, Wetterüberblick
        {
            if (Dauer_Screen == 1)     // beim ersten Aufruf
            {
                lcd.clear();
            }
            Anzeigen_RTC_Zeit();
            Anzeigen_DCF_Update_Sun_Moon();
            Anzeige_Temp_Druck_Feuchte();

            if (Dauer_Screen == 18)     // Anzeigedauer 18 s 1 x Datum plus 5 Termine x 3 s = 6 x 3 = 18 s
            {
                Dauer_Screen = 0;
                Display_Screen++;      // nächste Anzeige
            }
        }

        else if (Display_Screen == 1)    // Darstellung von Kalenderterminen
        {
            // Kalender anzeigen
            Anzeige_Kalendertermine();

            if (Dauer_Screen == 16)      // Anzeigedauer bei 16 wieder zurücksetzen
            {
                Dauer_Screen = 0;
                Display_Screen++;      // nächste Anzeige
            }
        }

        else if (Display_Screen == 2)    // Darstellung von Wetterdetails
        {
            if (Dauer_Screen == 1)       // beim ersten Aufruf
            {
                // Wetterdaten anzeigen
                Anzeige_Wetterdaten();
            }
            if (Dauer_Screen == 6)      // Anzeigedauer immer 6 s
            {
                Dauer_Screen = 0;
                Display_Screen = 0;      // nächste Anzeige

                // Nur die Dauern zurücksetzen; die anzuzeigende Info; hier einfach weiter durch rollieren
                Dauer_Datum = 0;
                Dauer_Zusatzinfo = 0;

                Display_Datum = 0;      // wieder bei der Wochentag/Datumsanzeige starten
            }
        }
#endif

#ifdef DHT22
        Dauer_Screen++;                // Anzeigedauer in Sekunden hochzählen, startet bei 0

        if (Display_Screen == 0)       // Haupt-Screen mit Datum, Terminen, Wetterüberblick
        {
            if (Dauer_Screen == 1)     // beim ersten Aufruf
            {
                lcd.clear();
            }
            Anzeigen_RTC_Zeit();
            Anzeigen_DCF_Update_Sun_Moon();
            Anzeige_Temp_Druck_Feuchte();

            if (Dauer_Screen == 15)     // Anzeigedauer 15 s
            {
                Dauer_Screen = 0;
                Display_Screen = 1;      // nächste Anzeige
            }
        }

        else if (Display_Screen == 1)    // Darstellung von Kalenderterminen
        {
            // Kalender anzeigen
            Anzeige_Kalendertermine();

            if (Dauer_Screen == 16)      // Anzeigedauer bei 16 wieder zurücksetzen
            {
                Dauer_Screen   = 0;
                Display_Screen = 0;      // nächste Anzeige

                // Nur die Dauern zurücksetzen; die anzuzeigende Info; hier einfach weiter durch rollieren
                Dauer_Datum      = 0;
                Display_Datum    = 0;
                Dauer_Zusatzinfo = 0;
            }
        }

#endif
    }

    //---------------------------------------------------------------------------------------------------------------------------------------------------------------------

#ifdef DCF77_Modul
    if ((millis() - millis_antenne_alt > 500))      // alle halbe Sekunde die Antenne ein-/ausschalten (= Zeichen, dass Aktualisierung möglich ist)
    {
        millis_antenne_alt = millis();

        if (RTC_Status == 0)    // wenn initialisiert oder aktualisiert werden darf
        {
            // Antennensymbol blinkt: DCF-Synchro aktiv, LCD nicht gedimmt, warte auf DCF-Signal!

            // Antenne blinken lassen 0,5 Hz
            if (Display_Screen == 0 && Dauer_Screen != 0)           // nur wenn der Überblicksbildschirm schon einmal angezeigt wurde!
            {
                lcd.setCursor(15, 0);
                if ((millis() / 500) % 2 > 0)     lcd.write(byte(1));
                else                              lcd.print(' ');
            }
        }
    }
#endif

    // Helligkeitssteuerung des LCD-Displays

    if ((millis() - millis_hell_alt > 50))      // alle 50 ms die Helligkeit messen und ggf. stufenweise/langsam anpassen, falls sich die Umgebungshelligkeit geändert hat
    {
        millis_hell_alt = millis();

        if ( (RTC_Status == 0) && hour() == 3 )    // wenn initialisiert oder aktualisiert werden darf zwischen 3 und 4 Uhr nachts
        {
            // Das PWM-Signal stört leider manchmal den DCF77-Empfang :-(
            // Daher auf jeden Fall nachts in einem bestimmten Zeitintervall das PWM für die LCD-Beleuchtung abschalten, um die RTC mit dem DCF77-Signal zu synchronisieren
            // Das macht man am besten nachts zwischen 3:00 und 3:59 Uhr. LCD-Display auf volle Helligkeit stellen, kein PWM!
            // nach Empfang eines DCF77-Zeitsignals wird das LCD wieder gedimmt

            PORTD &= ~(1 << PORTD5);      //digitalWrite (LCD_Pin, LOW);  // volle Helligkeit, PWM abschalten
            Flag_brigthness = false;
            LDR_Helligkeit_alt = 0;
        }
        else
        {
            // Ansteuerung der LCD-Beleuchtung über PWM: langsame Anpassung an Umgebungslicht

            if (Flag_brigthness == true)
            {
                Anzahl_Stufen--;
                LCD_Brigthness = min(map(LDR_Helligkeit - (faktor * 10 * Anzahl_Stufen), 20, 1009, 0, 255), Brigthness_min);

                if (Anzahl_Stufen == 0)
                {
                    Flag_brigthness = false;
                    Status_hell = 0;
                }

                // Am Pin hängt ein PNP-Transistor --> 0 entspricht volle Helligkeit, 255 = Display aus
                analogWrite (LCD_Pin, LCD_Brigthness);

                //                lcd.setCursor(17, 0);
                //                if (LCD_Brigthness < 100) lcd.print(' ');
                //                if (LCD_Brigthness < 10)  lcd.print(' ');
                //                lcd.print(LCD_Brigthness);
            }
            else
            {
                LDR_Helligkeit = analogRead(LDR_Pin);

                LDR_Helligkeit = max (LDR_Helligkeit, 20);              // auf minimalen Wert 20 begrenzen
                LDR_Helligkeit = min (LDR_Helligkeit, 1009);            // maximal 1009 verwenden

                // Serial.println(map(LDR_Helligkeit, 0, 1023, 0, 255));


                // Änderungen alle 50 ms messen
                // Zeitpunkt merken, wenn Änderung groß genug war
                // Dann 1,5 s später nochmal messen; wenn immer noch Änderung groß genug, dann anpassen
                // verhindert Reaktion bei kurzzeitigen Veränderungen, z.B. kurzem Schattenwurf, weil jemand vorbeiläuft!


                if (abs(LDR_Helligkeit - LDR_Helligkeit_alt) >= Empfindlichkeit && Status_hell == 0)
                {
                    millis_erste_Aenderung = millis();
                    Status_hell = 1;
                }

                if (abs(LDR_Helligkeit - LDR_Helligkeit_alt) >= Empfindlichkeit && Status_hell == 1 && millis() - millis_erste_Aenderung > 1500)
                {
                    Flag_brigthness    = true;                                                  // Anpassen Helligkeit starten
                    Anzahl_Stufen      = abs(LDR_Helligkeit_alt - LDR_Helligkeit) / 10;         // Berechnen über wieviele kleine Anpassungen

                    if (LDR_Helligkeit_alt < LDR_Helligkeit)                                    // Richtung der Anpassung: -1 für dunkler, +1 für heller
                    {
                        faktor = 1;
                    }
                    else
                    {
                        faktor = -1;
                    }

                    LDR_Helligkeit_alt = LDR_Helligkeit;
                }
            }
        }
    }


#ifdef DCF77_Modul
    //---------------------------------------------------------------------------------------------------------------------------
    // wenn neue Zeit empfangen, LED nach 1000ms ausschalten und Antennenzeichen löschen!

    if ((millis() - millis_empfang_alt > 1000) && RTC_Status > 0 && (PINB & (1 << PB4)) ) //digitalRead(12) == HIGH )
    {
        PORTB &= ~(1 << PORTB4);      //LOW  digitalWrite(12, LOW);

        if (Display_Screen == 0 && Dauer_Screen != 0)           // nur wenn der Überblicksbildschirm schon einmal angezeigt wurde!
        {
            lcd.setCursor(15, 0); lcd.print(' ');
        }
    }
#endif

    wdt_reset(); // Regelmäßiger Reset des Watchdog; entspricht asm("WDR")

}





// =============================================================================================================================

// Funktionen

//---------------------------------------------------------------------------------------------------------------------------
void Anzeige_Temp_Druck_Feuchte()
{
    // Update Temperatur, Luftdruck und Luftfeuchtigkeit

    if (millis() - millis_sensor_alt > 9999 || millis_sensor_alt == 0)    // Sensoren nur alle 10 sec auslesen
    {
        millis_sensor_alt = millis();

#ifdef BME280
        bme.readSensor();

        Luftfeuchtigkeit = bme.getHumidity();

        /*  Das BME280 misst den absoluten Luftdruck auf der aktuellen Höhe über NN.
            Mit zunehmender Höhe nimmt die darüberliegende Luftschicht und damit auch der Luftdruck ab, in Erdnähe etwa 1 hPa je 8 m Höhenunterschied.
            Um miteinander vergleichbare Luftdruckwerte zu erhalten, werden in der Wetterbeobachtung alle Angaben auf Meereshöhe [NormalNull] bezogen.
            Der Einfluß der Ortshöhe auf den Luftdruck muß daher entsprechend korrigiert werden.
        */

        //Berechnung relativer Luftdruck bezogen auf Meereshöhe NN:
        Luftdruck        = (Hoehe / 8) + bme.getPressure_HP() / 100.0;        // vereinfachte Umrechnung auf Luftdruck über Meereshöhe, Korrektur um 1 hPa pro 8 Höhenmeter

        Temperatur       = bme.getTemperature_C();
#endif

#ifdef DHT22
        Luftfeuchtigkeit = dht.readHumidity   ();
        Temperatur       = dht.readTemperature();
#endif
    }

    if (Display_Screen == 0)        // nur anzeigen, wenn 1. Bildschirm dargestellt werden soll
    {
#ifdef BME280
        lcd.setCursor(0, 3);

        lcd.print(byte(Temperatur));
        lcd.print(",");
        lcd.print(int(Temperatur * 10) % 10);
        lcd.write(byte(0));     // eigendefiniertes Gradzeichen        //lcd.print(char(0xDF));  // Standard LCD-Zeichen °
        lcd.print(F("C "));

        lcd.print(Luftdruck, 0);
        lcd.print(F(" hPa "));

        lcd.print(Luftfeuchtigkeit, 0);
        lcd.print(F(" %"));
#endif

#ifdef DHT22
        lcd.setCursor(3, 3);

        lcd.print(byte(Temperatur));
        lcd.print(",");
        lcd.print(int(Temperatur * 10) % 10);
        lcd.write(byte(0));     // eigendefiniertes Gradzeichen        lcd.print(char(0xDF));  // Standard LCD-Zeichen °
        lcd.print(F("C "));

        lcd.setCursor(12, 3);
        lcd.print(byte(Luftfeuchtigkeit));
        lcd.print(",");
        lcd.print(int(Luftfeuchtigkeit * 10) % 10);
        lcd.print(F(" %"));
#endif
    }

#ifdef BME280
    static byte Minute_alt = 0;

    // Luftdruck-Messwert speichern

    if (minute() % 15 == 0 && minute() != Minute_alt)      // alle 15 Minuten einen Wert speichern

        //if (minute() != Minute_alt)      // jede Minute einen Wert speichern zum Testen
    {
        Minute_alt = minute();

        if (Anzahl_Trendwerte_Baro < 24)
        {
            Baro_Trend [Anzahl_Trendwerte_Baro] = Luftdruck * 10;          // aktuellen Luftdruck speichern, mit 10 multiplizieren, dann retten wir eine Nachkommastelle trotz int
            Anzahl_Trendwerte_Baro++;                                     // nächste Speicherstelle im Array
        }
        else
        {
            // Wir haben schon 24 Werte für 6 h abgespeichert
            // nun den ersten (ältesten) Wert löschen, d.h. alle anderen Werte im Array in Richtung [0] verschieben

            for (byte i = 0; i < 23; i++)
            {
                Baro_Trend [i] = Baro_Trend [i + 1];
            }

            // den letzten Wert im RingWT_buffer durch den neuen Messwert ersetzen
            Baro_Trend [23] = Luftdruck * 10;            // aktuellen Luftdruck speichern
        }
    }

#endif
}


//---------------------------------------------------------------------------------------------------------------------------
void Anzeigen_RTC_Zeit ()
{
    // Anzeige von Uhrzeit, Wochentag und Datum auf Basis der Real Time Clock
    // Routine wird 1 x pro Sekunde durchlaufen
    // Die RTC-Zeit wird regelmäßig über eine Routine in die System-Zeit kopiert bzw. mit der DCF77-Zeit synchronisiert
    // Die Ausgabe hier verwendet die Systemzeit! Dann muss nicht lfd. die RTC-Zeit mit I²C gelesen werden!

    lcd.setCursor(6, 0);
    if (hour() < 10) lcd.print(' ');
    lcd.print(hour());
    lcd.print(':');
    printDigits(minute());
    lcd.print(':');
    printDigits(second());

    if (Flag_Tagesereignis == true)
    {
        // Infos abwechselnd darstellen
        // Es werden nacheinander maximal 5 verschiedene Ereignisse pro Tag angezeigt plus dem Wochentag/Datum
        // Die Feiertag-/Jahrestag-Infos stehen im Array Ereignis.Name[]

        Dauer_Datum++;            // Anzeigedauer in Sekunden hochzählen, startet bei 0

        if (Display_Datum == 0)
        {
            if (Dauer_Datum == 1)     // beim ersten Aufruf
            {
                // Wochentag und Datum mittig am LCD ausgeben, fehlende Positionen mit blanks auffüllen
                Ausgabe_Datum();
            }
            if (Dauer_Datum == 3)     // Anzeigedauer immer 3 s
            {
                Dauer_Datum = 0;
                Display_Datum   = 1;      // nächste Anzeige
            }
        }
        else
        {
            // Ereignisse ausgeben, bis zu 5 nacheinander!
            // Müllabfuhrtermine (Typ 4, 5, 6, 7) nur ausgeben, wenn Uhrzeit < 13:00, Erinnerungen für morgen (14, 15, 16, 17) nur ausgeben, wenn Uhrzeit > 16:59
            // Alle andern Termintypen immer (< 4)

            byte offset = 0;
            byte j      = Termine_next[Display_Datum - 1].Typ;


            if (    (4  <= j && j <= 7  && hour() < 13)  ||
                    (14 <= j && j <= 17 && hour() > 16)  ||
                    (j  <  4 ) )
            {

                if (Dauer_Datum == 1)         // nur einmal zu Beginn der Anzeigedauer ausgeben
                {
                    lcd.setCursor(0, 1);
                    lcd.print(F("                    "));

                    if ( 14 <= j && j <= 17 )           // Müllabfuhrerinnerungen für "Morgen"
                    {
                        lcd.setCursor((6 - (strlen(Termine_next[Display_Datum - 1].Name)) / 2), 1);       // mittig ausgeben
                        lcd.print("Morgen ");
                        lcd.print(Termine_next[Display_Datum - 1].Name);
                        lcd.print('!');
                    }
                    else
                    {
                        if (j == 1) offset = 5;     // Geburtstag
                        if (j == 2) offset = 3;     // Todestag

                        lcd.setCursor((10 - (strlen(Termine_next[Display_Datum - 1].Name) + offset) / 2), 1);       // mittig ausgeben
                        lcd.print(Termine_next[Display_Datum - 1].Name);

                        if (j == 1)                // Geburtstag
                        {
                            // Alter ausgeben
                            lcd.print(" (");
                            lcd.print(Termine_next[Display_Datum - 1].Alter);
                            lcd.print(')');
                        }

                        else if (j == 2)                // Todestag
                        {
                            lcd.print(' ');
                            lcd.write(byte(2));
                            lcd.print(' ');
                            lcd.print( year() - Termine_next[Display_Datum - 1].Alter);
                        }
                    }
                }
            }

            else
            {
                // Falls ein Müllabfuhrtermin nicht angezeigt werden soll, da aktuelle Zeit > 13:00 oder bei Erinnerung < 17:00
                Dauer_Datum = 3;
                //Ausgabe_Datum();        // dann einfach nochmal das Datum ausgeben
            }

            if (Dauer_Datum == 3)
            {
                Dauer_Datum = 0;
                Display_Datum++;

                if (Display_Datum > Index || Display_Datum == 6) Display_Datum = 0;
            }
        }
    }

    else
    {
        // Wenn keine Tagesereignisse vorliegen, einfach nur Tag und Datum ausgeben
        Ausgabe_Datum();
    }
}


//---------------------------------------------------------------------------------------------------------------------------
void Ausgabe_Datum()
{
    // Wochentag und Datum mittig am LCD ausgeben, fehlende Positionen mit blanks auffüllen

    strcpy_P(WT_buffer, (char *)pgm_read_word(&(Wochentag_Name[weekday() - 1])));      // Name des Wochentages aus dem PROGMEM lesen
    byte spalte = (10 - (strlen(WT_buffer) + 9) / 2 );

    for (byte i = 0; i < spalte; i++)
    {
        lcd.setCursor(i, 1);
        lcd.print(' ');
    }

    lcd.setCursor(spalte, 1);
    lcd.print(WT_buffer);
    lcd.print(' ');

    printDigits(day());   lcd.print('.');  printDigits(month());    lcd.print('.');  printDigits(year() - 2000);

    spalte = 11 - spalte - strlen(WT_buffer);

    for (byte i = 0; i < spalte; i++)
    {
        lcd.setCursor(19 - i, 1);
        lcd.print(' ');
    }

}



//---------------------------------------------------------------------------------------------------------------------------
void printDigits(int digits)
{
    // Ausgabe

    if (digits < 10) lcd.print('0');
    lcd.print(digits);
}



//---------------------------------------------------------------------------------------------------------------------------
void Anzeigen_DCF_Update_Sun_Moon ()
{
    // Infos zum Sonnenaugang, Mondphase und ggf. DCF-Empfang abwechselnd darstellen

    Dauer_Zusatzinfo++;         // Anzeigedauer in Sekunden hochzählen, startet bei 0

    if (Display_Zusatzinfo == 0)    // Sonnenauf-/untergang
    {
        if (Dauer_Zusatzinfo == 1)
        {
            lcd.setCursor(0, 2); lcd.print("  "); lcd.write(byte(3)); lcd.print(' '); printDigits(a_stunde); lcd.print(':'); printDigits(a_minute);
            lcd.print("  "); lcd.write(byte(4)); lcd.print(' '); printDigits(u_stunde); lcd.print(':'); printDigits(u_minute); lcd.print("  ");
        }

        if (Dauer_Zusatzinfo == 6)
        {
            Dauer_Zusatzinfo = 0;
            Display_Zusatzinfo++;
        }
    }

    else if (Display_Zusatzinfo == 1)    // Mondphase
    {
        if (Dauer_Zusatzinfo == 1)
        {
            // LCD-Ausgabe Mondphase
            lcd.setCursor(0, 2);
            lcd.print("Mond ");

            Mondphase = GetMoonPhase();

            switch (Mondphase)
            {
                // LCD character 5 wird dynamisch genutzt, um verschiedene Bilder darzustellen
                case 1:
                    lcd.print('O');                // Neumond
                    break;
                case 2:
                    lcd.createChar_P (5, Mond_2);  // zunehmende Sichel
                    lcd.setCursor(5, 2);           // ohne das Setzen des Cursors funktioniert es nicht !!!
                    lcd.write(byte(5));
                    break;
                case 3:
                    lcd.createChar_P (5, Mond_3);   // zunehmender Halbmond
                    lcd.setCursor(5, 2);
                    lcd.write(byte(5));
                    break;
                case 4:
                    lcd.createChar_P (5, Mond_4);   // zunehmender Mond
                    lcd.setCursor(5, 2);
                    lcd.write(byte(5));
                    break;
                case 5:
                    lcd.setCursor(5, 2);
                    lcd.write(byte(6));             // Vollmond
                    break;
                case 6:
                    lcd.createChar_P (5, Mond_6);   // abnehmender Mond
                    lcd.setCursor(5, 2);
                    lcd.write(byte(5));
                    break;
                case 7:
                    lcd.createChar_P (5, Mond_7);   // abnehmender Halbmond
                    lcd.setCursor(5, 2);
                    lcd.write(byte(5));
                    break;
                case 8:
                    lcd.createChar_P (5, Mond_8);   // abnehmende Sichel
                    lcd.setCursor(5, 2);
                    lcd.write(byte(5));
                    break;
            }

            lcd.print(' ');

            int Bel_grad = Beleuchtungsgrad(MoonUpdate());
            if (Bel_grad < 10)
            {
                lcd.print(F("0,"));
                lcd.print(Bel_grad);
            }
            else
            {
                lcd.print(Bel_grad / 10);
            }
            lcd.print(F("% "));

            if (month() == Zeitpunkt_Vollmond_next.month() && day() == Zeitpunkt_Vollmond_next.day() )   // Am Tag des Vollmondes nix ausgeben
            {
                lcd.setCursor(11, 2);
                lcd.print(F("        "));
            }
            else
            {
                lcd.setCursor(11, 2);
                lcd.print(' '); lcd.write(byte(6)); lcd.print(' '); printDigits(Zeitpunkt_Vollmond_next.day()); lcd.print('.'); printDigits(Zeitpunkt_Vollmond_next.month()); lcd.print('.');
            }
        }
        if (Dauer_Zusatzinfo == 6)
        {
            Dauer_Zusatzinfo = 0;
            Display_Zusatzinfo++;

#ifndef DCF77_Modul
            if (Display_Zusatzinfo == 2) Display_Zusatzinfo = 0;
#endif
        }
    }

#ifdef DCF77_Modul
    else if (Display_Zusatzinfo == 2)        // DCF-Daten
    {
        if (Dauer_Zusatzinfo == 1)
        {
            // letzte DCF-Aktualisierung
            lcd.setCursor(0, 2); lcd.print("  "); lcd.write(byte(1)); lcd.print(' ');

            if (Init_min == 0 && Init_sek == 0)
            {
                lcd.print(F("--:--  "));
            }
            else
            {
                printDigits(Update_h); lcd.print(':'); printDigits(Update_min); lcd.print("  ");
            }

            // Zeit wurde erstmalig aktualisiert nach ...
            lcd.setCursor(11, 2); lcd.print("I ");

            if (Init_min == 0 && Init_sek == 0)
            {
                lcd.print(F("--:--  "));
            }
            else
            {
                printDigits(Init_min); lcd.print(':'); printDigits(Init_sek); lcd.print("  ");
            }
        }

        if (Dauer_Zusatzinfo == 6)
        {
            Dauer_Zusatzinfo = 0;
            Display_Zusatzinfo++;
            if (Display_Zusatzinfo == 3) Display_Zusatzinfo = 0;
        }
    }
#endif
}


//------------------------------------------------------------------------------------------------------------------------------------------------
int berechne_Ostern (int jahr)
{
    // nach der Gauß-Formel
    // Rückgabewert: Datum von Ostern ab 1. März (max = 56 für 25. April)

    int a, b, c, d, e, f, k, p, q;
    int M, N;

    // Die "magische" Gauss-Formel:

    a = jahr % 19;
    b = jahr % 4;
    c = jahr % 7;
    k = jahr / 100;
    p = (8 * k + 13) / 25;
    q = k / 4;
    M = (15 + k - p - q) % 30;
    N = (4 + k - q) % 7;
    d = (19 * a + M) % 30;
    e = (2 * b + 4 * c + 6 * d + N) % 7;
    f = 22 + d + e;                         // Tag auf März bezogen: 32 bedeutet 1. April usw.


    // Wenn der 26. April ermittelt wird (31 + 26 = 57), dann muss Ostern am 19.4. sein (19 + 31 = 50)

    if (f == 57)
    {
        f = 50;
    }

    // Falls der 25. April ermittelt wird, gilt dies nur wenn d=28 und a>10

    if (f == 56 && d == 28 && a > 10)
    {
        f = 49;
    }

    return f;

}

//----------------------------------------------------------------------------------------------------------------------------------------------
void Feiertage_Ostern_abhaengig (int Jahr)
{
    /*
        Aus dem Osterdatum lassen sich folgende Feiertage berechnen:
        Rosenmontag:        48. Tag vor Ostern
        Fastnachtsdienstag: 47. Tag vor Ostern
        Karfreitag:          2. Tag vor Ostern
        Ostermontag:         1. Tag nach Ostern
        Himmelfahrt:        39. Tag nach Ostern, bzw. 10. Tag vor Pfingsten
        Pfingstsonntag:     49. Tag nach Ostern
        Pfingstmontag:      50. Tag nach Ostern
        Fronleichnam:       60. Tag nach Ostern, bzw. 10. Tag nach Pfingsten
    */


    int ostersonntag;

    DateTime Erster_Maerz = DateTime(Jahr, 3, 1, 0, 0, 0);
    ostersonntag = berechne_Ostern(Jahr);

    DateTime OsterSonntag (Erster_Maerz + TimeSpan(ostersonntag - 1, 0, 0, 0));

    DateTime Rosenmontag        (OsterSonntag - TimeSpan (48, 0, 0, 0));
    DateTime Faschingsdienstag  (OsterSonntag - TimeSpan (47, 0, 0, 0));
    DateTime Karfreitag         (OsterSonntag - TimeSpan ( 2, 0, 0, 0));
    DateTime OsterMontag        (OsterSonntag + TimeSpan ( 1, 0, 0, 0));
    DateTime ChristiHimmelfahrt (OsterSonntag + TimeSpan (39, 0, 0, 0));
    DateTime Pfingstsonntag     (OsterSonntag + TimeSpan (49, 0, 0, 0));
    DateTime Pfingstmontag      (OsterSonntag + TimeSpan (50, 0, 0, 0));
    DateTime Fronleichnam       (OsterSonntag + TimeSpan (60, 0, 0, 0));

    Feiertage_Ostern[0].Tag   = Rosenmontag.day();
    Feiertage_Ostern[0].Monat = Rosenmontag.month();

    Feiertage_Ostern[1].Tag   = Faschingsdienstag.day();
    Feiertage_Ostern[1].Monat = Faschingsdienstag.month();

    Feiertage_Ostern[2].Tag   = Karfreitag.day();
    Feiertage_Ostern[2].Monat = Karfreitag.month();

    Feiertage_Ostern[3].Tag   = OsterSonntag.day();
    Feiertage_Ostern[3].Monat = OsterSonntag.month();

    Feiertage_Ostern[4].Tag   = OsterMontag.day();
    Feiertage_Ostern[4].Monat = OsterMontag.month();

    Feiertage_Ostern[5].Tag   = ChristiHimmelfahrt.day();
    Feiertage_Ostern[5].Monat = ChristiHimmelfahrt.month();

    Feiertage_Ostern[6].Tag   = Pfingstsonntag.day();
    Feiertage_Ostern[6].Monat = Pfingstsonntag.month();

    Feiertage_Ostern[7].Tag   = Pfingstmontag.day();
    Feiertage_Ostern[7].Monat = Pfingstmontag.month();

    Feiertage_Ostern[8].Tag   = Fronleichnam.day();
    Feiertage_Ostern[8].Monat = Fronleichnam.month();
}




/******************************************************************
    /  C-Programm von http://lexikon.astronomie.info/zeitgleichung/neu.html
    /  umgeschrieben auf Arduino by 'jurs' for German Arduino forum
******************************************************************/


//--------------------------------------------------------------------------------------------------------------------------------------------------------------
double JulianischesDatum ( int Jahr, byte Monat, byte Tag, byte Stunde, byte Minuten, double Sekunden )
{
    int   Gregor; // Gregorianischer Kalender
    if (Monat <= 2)
    {
        Monat = Monat + 12;
        Jahr = Jahr - 1;
    }
    Gregor = (Jahr / 400) - (Jahr / 100) + (Jahr / 4); // Gregorianischer Kalender
    return 2400000.5 + 365.0 * Jahr - 679004.0 + Gregor + int(30.6001 * (Monat + 1)) + Tag + Stunde / 24.0 + Minuten / 1440.0 + Sekunden / 86400.0;
}


//--------------------------------------------------------------------------------------------------------------------------------------------------------------
double InPI(double x)
{
    int n = (int)(x / TWO_PI);
    x = x - n * TWO_PI;
    if (x < 0) x += TWO_PI;
    return x;
}


//--------------------------------------------------------------------------------------------------------------------------------------------------------------
double BerechneZeitgleichung(double & DK, double T)
{
    double RA_Mittel = 18.71506921 + 2400.0513369 * T + (2.5862e-5 - 1.72e-9 * T) * T * T;

    double M  = InPI(TWO_PI * (0.993133 + 99.997361 * T));
    double L  = InPI(TWO_PI * (  0.7859453 + M / TWO_PI + (6893.0 * sin(M) + 72.0 * sin(2.0 * M) + 6191.2 * T) / 1296.0e3));

    double e = DEG_TO_RAD * (23.43929111 + (-46.8150 * T - 0.00059 * T * T + 0.001813 * T * T * T) / 3600.0);

    double RA = atan(tan(L) * cos(e));
    if (RA < 0.0) RA += PI;
    if (L > PI) RA += PI;

    RA = 24.0 * RA / TWO_PI;

    DK = asin(sin(e) * sin(L));

    // Damit 0<=RA_Mittel<24
    RA_Mittel = 24.0 * InPI(TWO_PI * RA_Mittel / 24.0) / TWO_PI;

    double dRA = RA_Mittel - RA;
    if (dRA < -12.0) dRA += 24.0;
    if (dRA > 12.0) dRA -= 24.0;

    dRA = dRA * 1.0027379;
    return dRA ;
}



//--------------------------------------------------------------------------------------------------------------------------------------------------------------
/*
    Die aufzurufende Berechnungsfunktion heißt sunCalculation() und erwartet folgende Parameter in dieser Reihenfolge beim Aufruf:

    int year            Jahr für Berechnung
    int month           Monat für Berechnung
    int day             Tag für Berechnung
    float longitude     Längengrad
    float latitude      Breitengrad
    byte UTCoffset      Zeitzone, für Winterzeit 1 und für Sommerzeit 2

    Und in diesen Parametern steht anschließend das Ergebnis:

    byte auf_Stunde, // Ergebnis: Stunde des Sonnenaufgangs
    byte auf_Minute, // Ergebnis: Minute des Sonnenaufgangs
    byte unter_Stunde, // Ergebnis: Stunde des Sonnenuntergangs
    byte unter_Minute  // Ergebnis: Minute des Sonnenuntergangs

    Falls die Geo-Kordinaten in der Form Grad/Bogenminuten/Bogensekunden vorliegt, muss der Wert zunächst in eine Gleitkomma-Dezimalzahl umgerechnet werden, um sie an die Funktion zu übergeben.
*/
// für Verwendung in anderem Programmkontext:
// byte a_stunde, a_minute, u_stunde, u_minute;


void sunCalculation(int year, byte month, byte day, float longitude, float latitude, byte UTCoffset, byte & auf_Stunde, byte & auf_Minute, byte & unter_Stunde, byte & unter_Minute)
{
    double JD2000 = 2451545.0;
    double JD = JulianischesDatum(year, month, day, 12, 0, 0);

    double T = (JD - JD2000) / 36525.0;
    double DK;
    double h = -50.0 / 60.0 * DEG_TO_RAD;
    latitude *= DEG_TO_RAD; // geographische Breite

    double Zeitgleichung = BerechneZeitgleichung(DK, T);
    double Minuten = Zeitgleichung * 60.0;
    double Zeitdifferenz = 12.0 * acos((sin(h) - sin(latitude) * sin(DK)) / (cos(latitude) * cos(DK))) / PI;
    double Aufgang_Ortszeit = 12.0 - Zeitdifferenz - Zeitgleichung;
    double Untergang_Ortszeit = 12.0 + Zeitdifferenz - Zeitgleichung;
    double Aufgang_Weltzeit = Aufgang_Ortszeit - longitude / 15.0;
    double Untergang_Weltzeit = Untergang_Ortszeit - longitude / 15.0;

    double Aufgang = Aufgang_Weltzeit + UTCoffset;         // In Stunden
    if (Aufgang < 0.0) Aufgang += 24.0;
    else if (Aufgang >= 24.0) Aufgang -= 24.0;

    double Untergang = Untergang_Weltzeit + UTCoffset;
    if (Untergang < 0.0) Untergang += 24.0;
    else if (Untergang >= 24.0) Untergang -= 24.0;

    auf_Minute = byte(60.0 * (Aufgang - (byte)Aufgang) + 0.5);
    auf_Stunde = (byte)Aufgang;
    if (auf_Minute >= 60.0)
    {
        auf_Minute -= 60.0;
        auf_Stunde++;
    }
    else if (auf_Minute < 0.0)
    {
        auf_Minute += 60.0; auf_Stunde--;
        if (auf_Stunde < 0.0) auf_Stunde += 24.0;
    }

    unter_Minute = byte(60.0 * (Untergang - (byte)Untergang) + 0.5);
    unter_Stunde = (byte)Untergang;
    if (unter_Minute >= 60.0)
    {
        unter_Minute -= 60.0;
        unter_Stunde++;
    }
    else if (unter_Minute < 0)
    {
        unter_Minute += 60.0; unter_Stunde--;
        if (unter_Stunde < 0.0) unter_Stunde += 24.0;
    }
}

//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
boolean summertime_EU(int year, byte month, byte day, byte hour)
{
    /*
        Sommerzeit
        Beginn: immer letztes Wochenende im März
        Wochentag 31.3. bestimmen (1 = Sonntag - 7 = Samstag): Tag Beginn = 31 - Wochentag + 1
        Ende: letztes Wochenende im Oktober
    */

    if ( month < 3 || month > 10 )   return false;          // -> Winterzeit

    if ( day - Wochentag (day, month, year) >= 25 && (Wochentag (day, month, year) || hour >= 2) )  // after last Sunday 2:00
    {
        if ( month == 10 )   return false;    // October -> Winter
    }
    else                                                                                            // before last Sunday 2:00
    {
        if ( month == 3 )    return false;   // March -> Winter
    }

    return true;        // sonst ist Sommerzeit !
}





//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
byte GetMoonPhase()
{
    byte dMoonphase = MoonUpdate() / 10;        // MoonUpdate liefert int-Wert von 0 - 1000 (hat quasi eine Nachkommastelle, daher / 10

    if (dMoonphase == 0)        dMoonphase = 100;

    if (dMoonphase < 24)
    {
        return 6;                                //"Abnehmender Mond";
    }
    else if (24 <= dMoonphase && dMoonphase <= 26)
    {
        return 7;                                // "Abnehmender Halbmond";
    }
    else if (26 < dMoonphase && dMoonphase < 49)
    {
        return 8;                                //"Abnehmende Sichel";
    }
    else if (49 <= dMoonphase && dMoonphase <= 51)
    {
        return 1;                                 //"Neumond" (Prozentwerte etwas anpassen, damit etwas länger Neumond angezeigt wird);
    }
    else if (51 < dMoonphase && dMoonphase < 74)
    {
        return 2;                                // "Zunehmende Sichel";
    }
    else if (74 <= dMoonphase && dMoonphase <= 76)
    {
        return 3;                                //"Zunehmender Halbmond";
    }
    else if (76 < dMoonphase && dMoonphase < 100)
    {
        return 4;                                // "Zunehmender Mond";
    }
    else //Moonphase == 100
    {
        return 5;                                //"Vollmond";
    }
}


// ---------------------------------------------------------------------------------------------------------------------------------------------------------------
int MoonUpdate()
{
    // Wir rechnen mit int-Wert 0 - 1000 entspricht 0,0 - 100,0 %
    int Mond_Prozent;

    DateTime RTC_Zeit = DateTime(year(), month(), day(), hour(), minute(), second());

    // Nächsten Vollmond berechnen: solange die synodischen Monate auf einen bekannten Vollmondtermin addieren, bis Wert größer als aktuelles Datum ist
    // Die Länge eines synodischen Monats beträgt 29,530575 Tage – das sind 29 Tage, 12 Stunden, 44 Minuten und 2 Sekunden.

    unsigned long Vollmond_next    = Vollmond_ref.unixtime();    // Sekunden seit 1970 des Referenz-Vollmondes
    const unsigned long Mond_sek   = 2551442;                    // Zeit (sek) zwischen 2 Vollmonden: 2551442 = 29 * 86400 + 12 * 3600 + 44 * 60 + 2;

    do
    {
        Vollmond_next = Vollmond_next + Mond_sek;     // immer die Länge des synodischen Mondmonats addieren, bis ein Datum in der Zukunft (> RTC-Zeit) erreicht ist
    }
    while (RTC_Zeit.unixtime() >= Vollmond_next);

    // Nächster Vollmond-Termin
    Zeitpunkt_Vollmond_next = DateTime(Vollmond_next);

    // Termin des letzten Vollmondes
    DateTime Zeitpunkt_Vollmond_last = DateTime(Vollmond_next - Mond_sek);    //showDate(Zeitpunkt_Vollmond_last);

    // Zeit seit dem letzten Vollmond
    TimeSpan Zeit_seit_Vollmond_last = RTC_Zeit - Zeitpunkt_Vollmond_last;    //showTimeSpan(Zeit_seit_Vollmond_last);

    if (    month() == Zeitpunkt_Vollmond_next.month() && day() == Zeitpunkt_Vollmond_next.day() ||
            month() == Zeitpunkt_Vollmond_last.month() && day() == Zeitpunkt_Vollmond_last.day())
    {
        Mond_Prozent = 1000;  // Am Vollmondtag selbst 100% anzeigen --> 1000 = 100,0 %
        //Die 2. if-Bedingung wird benötigt, da ab einer bestimmten Uhrzeit am Vollmondtag bereits der nächste Vollmondtermin oben berechnet wurde
    }
    else
    {
        Mond_Prozent = 1000 * Zeit_seit_Vollmond_last.totalseconds() / Mond_sek;  // sonst anteilig, x 1000 im Nenner berücksichtigt, wg. Prozent-Nachkommastelle
    }

    //Serial.print("%-Mph: "); Serial.println(Mond_Prozent);
    return Mond_Prozent;
}



// ---------------------------------------------------------------------------------------------------------------------------------------------------------------
int Beleuchtungsgrad (int m_phase)        // Prozent der Mondfläche, die beleuchtet / sichtbar ist
{
    // Wir rechnen mit int-Wert 0 - 1000, dann habe wir eine Nachkommastelle, entspricht 0,0 - 100,0 %

    /*
        Da wir nur berechnen wollen, wieviel Prozent der Mondscheibe sichtbar sind, müssen wir nicht wissen, wie groß der Mond an sich ist,
        es reicht die Annahme, dass die Mondscheibe von der Erde aus gesehen in etwa kreisförmig erscheint und dass die Trennlinie zwischen
        Hell und Dunkel (Terminator genannt) in etwa einer halben Ellipse gleicht.
        Für die Berechnung reicht uns daher als “Mondmodell” der sogenannte Einheitskreis mit Radius R = 1 und eine Ellipsenformel.

        Ein Vollmond (entspricht 100% beleuchteter Mondoberfläche) hat in diesem Modell eine Fläche von 1² * π, also 3,1416.
        Ihr erinnert euch sicher noch: Die Fläche eines Kreises ist R²*π.
        Bei Halbmond entprechend π / 2 oder HALF_PI.

        Die Beleuchtung für alle Mondphasen dazwischen (also 0% für Neumond und 50% für Halbmond) berechnen wir,
        indem wir vom Halbmond ausgehen und die der Mondphase entsprechende halbe Ellipsenfläche abziehen oder addieren.

        Die sichtbare Scheibe zwischen Halb und Vollmond errechnet sich aus der Fläche des Halbkreises plus der halben Ellipsenfläche.

        Für eine Scheibe zwischen Neumond und Halbmond errechnen wir die Beleuchtung aus der Fläche des Halbkreises minus der halben Ellipsenfläche.

        phase < 0,5:
            s = cos(phase * TWO_PI);                 // Errechnung kurze Halbachse s der Ellipse
            ellipse = s * 1 * PI;                    // Ellipsenfläche = Produkt der beiden Halbachsen * Pi
            hEllA = ellipse / 2;                     // halbe Ellipsenfläche
            Flaeche_beleuchtet = hmoonA + hEllA;     // Beleuchtete Mondoberfläche = Halbmondfläche + halbe Ellipsenfläche

        phase >= 0,5:
            s = -cos(phase * TWO_PI);                // Errechnung kurze Halbachse s der Ellipse
            ellipse = s * 1 * PI;                    // Ellipsenfläche = Produkt der beiden Halbachsen * Pi
            hEllA = ellipse / 2;                     // halbe Ellipsenfläche
            Flaeche_beleuchtet = hmoonA - hEllA;     // Beleuchtete Mondoberfläche = Halbmondfläche plus halbe Ellipsenfläche

        m_phase muss noch durch 100 geteilt werden, da ein Wert < 1 benötigt wird.

    */

    float Flaeche_beleuchtet = HALF_PI + cos (m_phase / 1000.0 * TWO_PI) * HALF_PI;     // zus.gefasste Formel ! 1000 wg. Nachkommastelle
    int Flaeche_bel_Prozent =  Flaeche_beleuchtet / PI * 1000.0;                        // Beleuchtete Mondoberfläche in % der Vollmondoberfläche (Basis Einheitskreis mit r=1)
    //Serial.print("%-Fläche: "); Serial.println(Flaeche_bel_Prozent);
    return Flaeche_bel_Prozent;
}




//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
uint32_t syncProvider()
{

#ifdef debug
    Serial.println(F("Sync Interne Uhr mit RTC"));
#endif

    DateTime RTC_Zeit = rtc.now();      // die RTC hat immer die normale = Winterzeit !!!

#ifdef DCF77_Modul
    // wenn DCF77_Modul, dann wird lfd. auf Basis des DCF77-Moduls aktualisiert
    // Sommerzeit muss nicht dazugezählt werden !

    return rtc.now().unixtime();   // holt die UNIX-Time aus der RTC-Clock und synchronisiert dann die interne Arduino-Uhr

#endif

#ifndef DCF77_Modul
    // ohne DCF77_Modul muss während der Sommerzeitphase 1 h = 3600 s dazugezählt werden !
    // Die RTC hat weiterhin die normale Zeit = Winterzeit !
    // Nano-intern wird aber mit Sommerzeit gerechnet, diese wird auch auf dem LCD-display angezeigt

    if (summertime_EU(RTC_Zeit.year(), RTC_Zeit.month(), RTC_Zeit.day(), RTC_Zeit.hour()))
    {
        //Serial.println(" --> Sommerzeit");
        return rtc.now().unixtime() + 3600;   // holt die UNIX-Time aus der RTC-Clock und synchronisiert dann die interne Arduino-Uhr
    }
    else
    {
        //Serial.println(" --> Winterzeit");
        return rtc.now().unixtime();   // holt die UNIX-Time aus der RTC-Clock und synchronisiert dann die interne Arduino-Uhr
    }
#endif
}


// -------------------------------------------------------------------------------------------------------------------------
#ifdef BME280
void Anzeige_Wetterdaten()
{
    // Anzeige Luftdruck, Trend etc. auf 2. LCD-Bildschirmseite

    lcd.clear();
    lcd.setCursor(4, 0);
    lcd.print(int(Luftdruck));              // Ausgabe mit Dezimalkomma und einer Nachkommastelle
    lcd.print(',');
    lcd.print(int(Luftdruck * 10) % 10);

    lcd.print(F(" hPa "));

    if (Anzahl_Trendwerte_Baro == 24)        // nur anzeigen, wenn alle Trendwerte erfasst wurden (dauert also 6 h)
    {
        if (abs(Baro_Trend [23] - Baro_Trend [19]) <= 3)        // wenn Delta = +/- 0,3 hPa
        {
            lcd.print(char(0x7e)); // Pfeil nach rechts bei const. Luftdruck seit einer Stunde
        }
        else if (Baro_Trend [23] - Baro_Trend [19] > 3)
        {
            lcd.write(byte(3)); // Pfeil nach oben bei gestiegenem Lufdruck seit einer Stunde
        }
        else if (Baro_Trend [23] - Baro_Trend [19] < -3)
        {
            lcd.write(byte(4)); // Pfeil nach unten bei gesunkenem Lufdruck seit einer Stunde
        }
    }

    // Aktuelles Wetter bestimmen
    lcd.setCursor(0, 1);
    lcd.print(F("Akt. : "));
    if (Luftdruck <= 987)                         lcd.print(F("st\365rmisch"));
    if (Luftdruck >  987 && Luftdruck <= 1001)    lcd.print(F("regnerisch"));
    if (Luftdruck > 1001 && Luftdruck <= 1026)    lcd.print(F("unbest\341ndig"));
    if (Luftdruck > 1026 && Luftdruck <= 1042)    lcd.print(F("Kaiserwetter"));
    if (Luftdruck >  1042 )                       lcd.print(F("trocken"));

    lcd.setCursor(0, 2);
    lcd.print(F("Trend: "));

    // Berechnung Trend
    if (Anzahl_Trendwerte_Baro == 24)            // nur wenn alle 24 Werte erfast wurden, vorher macht die Trendanzeige keinen Sinn
    {
        /*        Tendenz in abhängigkeit der Lufdruckveränderung bstimmen.
                  Sturmwarnung z.B., wenn der Luftdruck innerhalb von 6 Stunden um 5.0 hPa oder mehr fällt

                  Diese simple Art der Tendenzanzeige hat sich für Mitteleuropa bewährt, wo die Schlechtwetter-Tiefdruckgebiete vom Atlantik heranziehen und die Gutwetter-
                  Hochdruckgebiete aus Russland kommen. In anderen Regionen der Welt ist diese Art Wettertendenzanzeige ggf. aber völliger Müll, also nicht außerhalb
                  Mitteleuropas so versuchen, eine Wettertendenz aus dem Luftdruck zu berechnen.
        */

        // Tendenz anzeigen
        int Delta_Druck =  Baro_Trend [23] - Baro_Trend [0];

        if ( Delta_Druck <= -80 )                          lcd.print(F("Sturm/Hagel"));
        if ( -80 < Delta_Druck  &&  Delta_Druck <= -50)    lcd.print(F("Unwetter"));
        if ( -50 < Delta_Druck  &&  Delta_Druck <= -30)    lcd.print(F("scheu\342lich"));
        if ( -30 < Delta_Druck  &&  Delta_Druck <=  -5)    lcd.print(F("Regen"));
        if ( -5  < Delta_Druck  &&  Delta_Druck <=   5)    lcd.print(F("konstant"));
        if ( 5   < Delta_Druck  &&  Delta_Druck <=  30)    lcd.print(F("lange sch\357n"));
        if ( 30  < Delta_Druck  &&  Delta_Druck <=  50)    lcd.print(F("sch\357n/labil"));
        if ( 50 <  Delta_Druck )                           lcd.print(F("Sturmwarnung"));
    }
    else
    {
        lcd.print('-');
    }

    lcd.setCursor(0, 3);
    lcd.print(F("3h: "));
    Ausgabe_Luftdruck(23, 11);      // Delta = aktueller Wert - Wert vor 3 Stunden


    lcd.setCursor(11, 3);
    lcd.print(F("6h: "));
    Ausgabe_Luftdruck(23, 0);      // Delta = aktueller Wert - Wert vor 6 Stunden
}
#endif


//----------------------------------------------------------------------------------------------------------------------------------------------------
#ifdef BME280
void Ausgabe_Luftdruck(byte aktuell, byte letzter)
{
    int Delta_Druck = Baro_Trend [aktuell] - Baro_Trend [letzter] ;

    if (Anzahl_Trendwerte_Baro == 24)           // nur ausgeben, wenn alle 24 Werte gesammelt wurden
    {
        if (Delta_Druck > 0)
        {
            lcd.print('+');
        }
        else if (Delta_Druck < 0)
        {
            lcd.print('-');
        }
        else if (Delta_Druck == 0)
        {
            lcd.print(' ');
        }

        lcd.print(abs(Delta_Druck) / 10);       // Delta-Druck enthält eine Nachkommastelle, daher durch 10 teilen
        lcd.print(',');
        lcd.print(abs(Delta_Druck) % 10);       // mit Modulo erhält man die Nachkommastelle
    }
    else
    {
        lcd.print('-');
    }
}
#endif


// -------------------------------------------------------------------------------------------------------------------------------------------------
// Berechnung des Wochentages aus dem übergebenen Tagesdatum
byte Wochentag (int day, int month, int year)
{
    // 0 = Sunday, 1 = Monday, ... 6 = Saturday
    //        if ((month -= 2) <= 0)
    //        {
    //            month += 12;
    //            year--;
    //        }
    //        return (83 * month / 32 + day + year + year / 4 - year / 100 + year / 400) % 7;

    return ( day += month < 3 ? year-- : year - 2, 23 * month / 9 + day + 4 + year / 4 - year / 100 + year / 400 ) % 7;
}



// -------------------------------------------------------------------------------------------------------------------------
void Anzeige_Kalendertermine()
{
    // Anzeige Kalendertermine in vier 2er Blöcken

    byte sub_screen = (Dauer_Screen - 1) / 4;     // Dauer_Screen - 1 geht von 0 - 15; bei 0 - 3 die ersten beiden Einträge anzeigen, bei 4 - 7 die Einträge 3 und 4 etc.
    // sub_screen läuft von 0 - 3

    if ( (Dauer_Screen - 1) % 4  > 0) return;     // nur beim ersten Mal anzeigen, verhindert LCD-Flimmern

    //Prüfen, ob überhaupt Termine angezeigt werden müssen
    if (Termine_next[sub_screen * 2].Tag == 0)    // sub_screen * 2 hat die Werte 0, 2, 4, 6
    {
        Dauer_Screen = 16;                        // Anzeige der Termine beenden
        return;
    }

    lcd.clear();

    for (byte i = 0; i < 2; i++)
    {
        byte w = i + sub_screen * 2;

        if (Termine_next[w].Tag > 0)                                   // nur ausgeben, wenn Termin vorhanden
        {
            if ( w % 2 == 0 )                                          // bei jeweils 1. Eintrag pro Seite das Datum ausgeben, d.h. w = 0, 2, 4, 6
            {
                Datum_Kalender_ausgeben (i, w);
            }
            else
            {
                if (Termine_next[w].Tag != Termine_next[w - 1].Tag)    // wenn gleicher Tag wie beim letzten Eintrag, dann Datum nicht nochmals ausgeben!
                {
                    Datum_Kalender_ausgeben (i, w);
                }
                else
                {
                    lcd.setCursor(0, i * 2);
                }
            }

            lcd.print(Termine_next[w].Name);

            if (Termine_next[w].Typ % 10 == 1)                  // Geburtstag
            {
                // Alter ausgeben
                lcd.print(" (");
                lcd.print(Termine_next[w].Alter);
                lcd.print(')');
            }
            else if (Termine_next[w].Typ % 10 == 2)                // Todestag
            {
                lcd.print(' ');
                lcd.write(byte(2));
                lcd.print(' ');
                lcd.print(year() - Termine_next[w].Alter);          // wieder auf das Jahr zurückrechnen, ausgehnd vom Alter
            }

        }
    }
}


//---------------------------------------------------------------------------------------------------------------------------------
void Datum_Kalender_ausgeben (byte i, byte w)
// gibt Datum und Wochentagname aus
// - i = Nummer des Termins zur Ermittlung der Zeile auf dem LCD-Display (0 bzw. 1)
// - w = Kalendereintrag/Termin aus dem array (0 - 7)
{
    lcd.setCursor(0, i * 2);
    printDigits (Termine_next[w].Tag)        ; lcd.print('.');
    printDigits (Termine_next[w].Monat)      ; lcd.print('.');
    lcd.print   (Termine_next[w].Jahr - 2000); lcd.print(' ');

    byte WT_Nr = Wochentag ( Termine_next[w].Tag, Termine_next[w].Monat, Termine_next[w].Jahr );
    strcpy_P(WT_buffer, (char *)pgm_read_word(&(Wochentag_Name[WT_Nr])));                           // Name des Wochentages aus dem PROGMEM lesen

    lcd.print  (WT_buffer);
    lcd.setCursor(0, i * 2 + 1);
}


//---------------------------------------------------------------------------------------------------------------------------------
void Termin_eintragen (byte Typ, char* Text)

// Parameter:
// - Typ  = Typ des Termins 1, 2, 3 ...
// - Text = "Name des Termins für LCD"
{
    Flag_Tagesereignis = true;

    Termine_next[Index].Typ = Typ;                         // Vorbelegen mit 3, d.h. Termin ist heute
    if ( Termin.unixtime() == Morgen.unixtime())
    {
        // wenn Termin erst morgen ist, dann + 10
        Termine_next[Index].Typ += 10;
    }
    else if (Termin.unixtime() > Morgen.unixtime())
    {
        // wenn der Termin weiter in der Zukunft liegt, + 20
        Termine_next[Index].Typ += 20;
    }
    strcpy (Termine_next[Index].Name, Text);
    Termine_next[Index].Tag   = Termin.day();
    Termine_next[Index].Monat = Termin.month();
    Termine_next[Index].Jahr  = Termin.year();
}

Hier noch der Schaltplan:

Und so sieht das aufgebaut aus:

Viel Spaß beim Suchen des Fehler!
Ich habe viel kommentiert, nur leider nicht, wo er eingebaut ist.

Bitte v.a. auf die strcpy, strcpy_PPROGMEM, pgm_read_word Befehle etc. schauen.
Vielleicht habe ich irgendwo ein "*" oder "&" vergessen ...

Ach ja: Warum erscheinen meine Code-Zeilen ohne farbliche Darstellung von KEYWORDS?

naja dein kleines Monster wirst selber debuggen müssen.

Tipps:
a) deaktivere alles

//#define DCF77_Modul       // wenn Zeile auskommentiert, dann kein DCF77 angeschlossen !!
//#define BME280
//#define DHT22

Problem behoben? ... dann einzeln wieder reaktiveren

b) deine "Kalender" ...
konditioniere auch deine Tabellen/struct/ ausgaben mit #defines und analysiere, wo der Code bricht.

c) generell:
mach kleinere Einheiten und feststellen zu können wo dein Problem auftritt.

d) statte alle deine Funktionen mit einem "Start", vieleicht sogar mit einem "End" aus und lass einen Logger mitlaufen damit du siehst bei welcher Funktion der Crash auftritt.
es könnte das makro

__func__

verwendet werden, dann brauchst den Funktionsnamen nicht hardcoded hinschreiben.

nur so als Idee:

#define  DEBUG  1     // Turn on debug statements to the serial output

#if DEBUG
#define DEBUG_PRINT(...)       Serial.print(__VA_ARGS__);
#define DEBUG_PRINTLN(...)     Serial.println(__VA_ARGS__);
#define DEBUG_VALUELN(s, x)    { Serial.print(F(s)); Serial.println(x); }
#define DEBUG_STARTFUNC        { Serial.print(F("START ")); Serial.println(__func__);}
#define DEBUG_ENDFUNC          { Serial.print(F("END   ")); Serial.println(__func__);}
#else
#define DEBUG_PRINT(...)
#define DEBUG_PRINTLN(...)
#define DEBUG_VALUELN(s, x)
#define DEBUG_STARTFUNC
#define DEBUG_ENDFUNC
#endif

void setup() {
  Serial.begin(115200);
  DEBUG_STARTFUNC
  Serial.println(F("\nProgram name"));

  Serial.println(F("do something"));
  Serial.println(604799L, BIN);
  DEBUG_ENDFUNC
}

void loop() {
}
1 Like

b) deine "Kalender" ...
konditioniere auch deine Tabellen/struct/ ausgaben mit #defines und analysiere, wo der Code bricht.

Den Punkt verstehe ich nicht ganz. Könntet Du mir bitte ein Beispiel nennen?

Falls sich jemand wundert, warum der Sketch nicht kompiliert:

Kalender_Uhr_Sonne_Mond_V1.33:652:9: error: 'class LiquidCrystal_I2C' has no member named 'createChar_P'; did you mean 'createChar'?

                 lcd.createChar_P (5, Mond_2);  // zunehmende Sichel

                     ^~~~~~~~~~~~

                     createChar

Hierfür muss die LCD library modifiziert werden. Ist hier recht gut beschrieben:

Zeichen ins PROGMEM verlagern:

du hast mehrere Strukturen für deine Kalender-Einträge.

Ich würde da jetzt mal "ohne Kalender" starten und nach und nach einen Kalender nach dem anderen aufnehmen.
Daher jeweils mit #define ein/auschalten - aber auch die die Funktionen die auf diese Strukturen zugreifen.

1 Like

Merci, also klassisches "bedingtes Kompilieren".

1 Like

In welchem Editor?
Hat die Bibliothek das File keywords.txt?

Grüße Uwe

Hier im Forum, in Post #11.
Dort werden kein Farben dargestellt.

In der Arduino IDE am PC natürlich schon.

Ich glaube nicht daß die Forensoftware das Highlightning der Arduino IDE kennt bzw jemals kennen wird.