[code] stellen RTC mit 2 Tasten - mal anders

Aus dem Thread für die Autorennbahn ab https://forum.arduino.cc/index.php?topic=694542.msg4953982#msg4953982 war die Vorgabe eine RTC mit zwei Tasten zu bedienen. Das sowas funktioniert ist nicht das Problem.

Ich will mit meinem Code erreichen:

  • wird nur mit zwei Tasten bedient
  • direkt gestellt ohne Zwischenvariablen.
    – Damit wird auch immer das richtige Datum gesetzt
  • Zusätzlich nicht mehr im SetupCode mal das setzen der RTC einbinden und mal nicht

Verwende die RTCLib von Adafruit. Die übernimmt u.a. die komplette Kommunikation (nicht nur für DS3231)
Herausforderung ist:

  • das Datum soll auch gesetzt werden
  • das Setzen soll beim Überlauf wieder bei 0 (Zeit) oder 1 (Datum) beginnen und die Folgezahl nicht verändert werden
    Beispiel: Beim Überlauf von Stunde 23 auf Stunde 0 würde der Tag, beim Monat 12 auf Monat 1 würde Jahr 1 hochgezählt usw.

Adafruit hat in der Lib eine Funktion TimeSpan, aber die geht auch nur bis einen Tag. Beim Monat sind es aber zwischen 28 bis 31 Tage…

Was der Code machen soll:
Im Setup vergleiche RTC-Zeit mit der Zeit des Kompilat und biete an die Zeit zu setzen.
Nach 10 Sekunden gehe automatisch mit der bisherigen RTC weiter.

Im loop: Wird die Taste zur Einstellung der Uhr gedrückt, verzweige in den Einstellmodus
Im Einstellmodus wird mit einer Taste ausgewählt, welcher Teil der Uhr gestellt wird. Mit der anderen Taste wird der Wert verändert. Nur aufzählend. Kommt es zum Überlauf fange mit dem ersten Wert an und setze den Überlauf zurück.
Wird das Jahr zuweit gestellt, kommt es auf 2021 zurück.
Wenn 30 Sekunden keine Taste gedrückt wird, verlasse den Einstellmodus automatisch.

Gestellt wird nur mit der Größe “Sekunden” (rtc.adjust(Zeit in unixtime))
Ich benötige nur merkende Variablen für den akuellen Zustand vor der Verstellung.
Der Code kommt ohne delay() aus.

Achtung: Ich formatiere anders als viele von Euch. Dadurch sieht das nach sehr vielen Zeilen aus.
Ist aber nicht so schlimm und ich hoffe ich habe es ausreichend kommentiert.

Ist noch nicht fertig, aber vielleicht schaut mal jemand drüber, ob ich mich an irgendeiner Stelle sehr verhauen habe.

Code hier ohne Kommentare wegen der Zeichenbegrenzung. Das Post musste auf 8600 Zeichen an Stelle 9000 gekürzt werden - kommentiert als txt-Anhang

// rtc direkt nur mit zwei Buttons stellen
// Erster Ansatz-2021.04.11
#include <RTClib.h>
RTC_DS3231 rtc;
const uint8_t TL=6;
const uint8_t TR=7;
DateTime now;
void setup() {
  Serial.begin(115200);
  Serial.println(F("Start...."));
  pinMode(TL, INPUT_PULLUP);
  pinMode(TR, INPUT_PULLUP);
  delay(1000);
  rtc.begin();
  now=rtc.now();
  Serial.print(F("RTC-Time: "));
  UhrzeitSeriell();
  uint32_t merkemillis=millis();
  int32_t ut=now.unixtime();
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  Serial.print(F("File-Time: "));
  UhrzeitSeriell();
  now=rtc.now();
  Serial.print(F("Abweichung Uhr / Kompilierzeit (s): "));
  Serial.println(abs(now.unixtime()-ut));
  Serial.println(F("Kompilierzeit übernehmen? Taste links drücken"));
  uint32_t lastmillis=millis();
  while (millis()-lastmillis<10000 && digitalRead(TR)){}
  if (!digitalRead(TR)){
    Serial.println(F("Uhrzeit gestellt"));
  }
  else{
    Serial.println(F("Uhrzeit bleibt"));
    rtc.adjust(ut);
  }
  now=rtc.now();
  rtc.adjust(now.unixtime()+((millis()-merkemillis) / 1000));
  UhrzeitSeriell();
}

void loop(){
  Zeit();
}

void Zeit(){
  static bool zeitStellen=false;
  static unsigned long zeitAusgabe=millis();
  if (!digitalRead(TL) && !zeitStellen){
    Serial.println(F("Löse aus..."));
    zeitStellen=true;
  }
  if (zeitStellen){
    zeitStellen=uhrStellen(zeitStellen);
  }
  if (millis()-zeitAusgabe>10000){
    Serial.print("ZEIT: ");
    UhrzeitSeriell();
    zeitAusgabe=millis();
  }
}
bool uhrStellen(bool uebergabe){
  enum {anfang, se, mi, st, ta, mo, ja, ende};
  static uint8_t auswahl=anfang;
  static bool TLlaststate=LOW;
  static uint32_t lastmillis=0;
  // const uint32_t startzeit=millis();
  uint16_t istJahr=now.year();
  uint8_t istMonat=now.month();
  uint8_t istTag=now.day();
  static uint32_t tik=millis();
  if (millis()-lastmillis>100){
    lastmillis=millis();
    if (!digitalRead(TL) && !TLlaststate){
      TLlaststate=HIGH;
      tik=millis();
    }
    else if (digitalRead(TL) && TLlaststate){
      TLlaststate=LOW;
      auswahl++;
      Serial.print(F("auswahl: "));
      Serial.println(auswahl);
    }
    if (millis()-tik>30000){
      Serial.println("TimeOut Tastendruck ausgelöst");
      auswahl=ende;
    }
    switch (auswahl){
      case anfang:
        if (!digitalRead(TR)){
          auswahl=se;
          tik=millis();
        }
        break;
      case se:
        if (!digitalRead(TR)){
          tik=millis();
          Serial.println(F("sek+"));
          now=rtc.now();
          rtc.adjust(now.unixtime()+1);
          now=rtc.now();
          if (now.second() == 0){
            rtc.adjust(now.unixtime()-60);
          }
          UhrzeitSeriell();
        }
        break;

      case mi:
        if (!digitalRead(TR)){
          tik=millis();
          Serial.println(F("min+"));
          now=rtc.now();
          rtc.adjust(now.unixtime()+60);
          now=rtc.now();
          if (now.minute() == 0){
            rtc.adjust(now.unixtime()-3600);
          }
          UhrzeitSeriell();
        }
        break;

      case st:
        if (!digitalRead(TR)){
          tik=millis();
          Serial.println(F("std+"));
          now=rtc.now();
          rtc.adjust(now.unixtime()+3600);
          now=rtc.now();
          if (now.hour() == 0){
            rtc.adjust(now.unixtime()-86400);
          }
          UhrzeitSeriell();
        }
        break;

      case ta:
        if (!digitalRead(TR)){
          tik=millis();
          Serial.println(F("tag+"));
          now=rtc.now();
          uint8_t lastmonat=now.month();
          rtc.adjust(now.unixtime()+86400);
          now=rtc.now();
          if (now.month() != lastmonat){
            rtc.adjust(now.unixtime()-86400);
            tageKorrektur(1);
          }
          UhrzeitSeriell();
        }

        break;
      case mo:
        if (!digitalRead(TR)){
          tik=millis();
          Serial.println(F("mon+"));
          now=rtc.now();
          istJahr=now.year();
          istMonat=now.month();
          istTag=now.day();
          while (istMonat == now.month()){
            rtc.adjust(now.unixtime()+86400);
            now=rtc.now();
          }
          if (istJahr != now.year()){
            rtc.adjust(now.unixtime()-31536000);
            now=rtc.now();
            while (istJahr != now.year()){
              rtc.adjust(now.unixtime()-86400);
              now=rtc.now();
            }
            monateKorrektur(1);
          }
          tageKorrektur(istTag);
          UhrzeitSeriell();
        }
        break;

      case ja:
        if (!digitalRead(TR)){
          tik=millis();
          Serial.println(F("jahr+"));
          now=rtc.now();
          istJahr=now.year();
          istMonat=now.month();
          istTag=now.day();
          rtc.adjust(now.unixtime()+31536000);
          now=rtc.now();
          while (istJahr == now.year()){
            rtc.adjust(now.unixtime()+86400);
            now=rtc.now();
          }
          if (now.year()>2030){
            rtc.adjust(now.unixtime()-315360000);
            now=rtc.now();
          }
          monateKorrektur(istMonat);
          tageKorrektur(istTag);
          now=rtc.now();
          UhrzeitSeriell();
        }
        break;
      case ende:
        uebergabe=false;
        auswahl=anfang;
        break;
      default:
        Serial.println(F("status nicht gefunden-ENDE"));
        auswahl=ende;
        break;
    }
  }
  return uebergabe;
}
void monateKorrektur(uint8_t sollMonat){
  now=rtc.now();
  while (sollMonat != now.month()){
    if (now.month()<sollMonat){
      rtc.adjust(now.unixtime()+86400);
    }
    else{
      rtc.adjust(now.unixtime()-86400);
    }
    now=rtc.now();
  }
}
void tageKorrektur(uint8_t sollTag){
  now=rtc.now();
  while (sollTag != now.day()){
    if (now.day()<sollTag){
      rtc.adjust(now.unixtime()+86400);
    }
    else{
      rtc.adjust(now.unixtime()-86400);
    }
    now=rtc.now();
  }
}
void UhrzeitSeriell()
{
  now=rtc.now();
  Serial.print(now.day(), DEC);
  Serial.print('.');
  Serial.print(now.month(), DEC);
  Serial.print('.');
  Serial.print(now.year(), DEC);
  Serial.print(" ");
  Serial.print(now.hour(), DEC);
  Serial.print(':');
  Serial.print(now.minute(), DEC);
  Serial.print(':');
  Serial.print(now.second(), DEC);
  Serial.println();
}

RTC_set_202120411.txt (9.61 KB)

Leider durchblicke ich Dein Konzept nicht, weshalb ich nur ein paar mehr oder minder sinnvolle Anmerkungen machen kann:

Du merkst Dir die Zeit der RTC, veränderst sie, um sie dann durch Berechnung zu rekonstruieren. Das verstehe ich nicht unter “wir lassen die Zeit der RTC unverändert”. Im Sekundenbereich dürfte es Abweichungen geben, das gefällt mir nicht so gut, hängt aber auch von der Anwendung ab. Für die Klanguhr dürfte es egal sein.

  uint32_t merkemillis = millis();                // Merker für Korrektur
...
  uint32_t lastmillis = millis();
  while (millis() - lastmillis < 10000 && digitalRead(TR)) // Warte auf Tastendruck oder Zeitablauf
  {}

Das scheint mir doppelt, außerdem könntest Du auch die millis() direkt nutzen, da setup immer nach Reset startet:

  while (millis() < 10000 && digitalRead(TR)) // Warte auf Tastendruck oder Zeitablauf
  {}

Wenn man ODER ins Programm bringen möchte:

  while (millis() > 10000 || !digitalRead(TR)) // Warte auf Zeitablauf oder Tastendruck
  {}
  Serial.println(F("Kompilierzeit übernehmen? Taste links drücken"));
...
  if (!digitalRead(TR))                           // Taste gedrückt?

Hast Du hier links und rechts verwechselt?

      Serial.print(F("auswahl: "));
      Serial.println(auswahl);

Leider wird auf dem Monitor nur eine mir nichts sagende Zahl angezeigt.

Danach habe ich mich dann leider im Programm verlaufen und mein Uhrenmodul zeigte eine unreale Zeit :o

agmue:
Leider durchblicke ich Dein Konzept nicht,

Dann versuch ich es mal ausführlich:
Ich stelle ohne Variablen die Uhrzeit. Dafür nutze ich - so wie Adafruit auch - bis zum ganzen Tag das Maximum der immer sicheren Sekunden.
Sekunde = 1 Sekunde
Minute = 60 Sekunden
Stunde = 3600 Sekunden
Tag = 86400 Sekunden.
Ich habe das aufgeteilt. Einmal Zeit und einmal Datum. Bei Zeit ist das relativ einfach.
Mit jedem rtc.now() wird die Zeit aus der RTC geholt und steht dann als now.unixtime() zur Verfügung.

Mit jedem stellen der Uhr, wird die unixtime mir der Anzahl der Sekunden addiert und in die RTC geschrieben.
Wird Stelle, die verändert wird 0, ist es zu einem Überlauf gekommen. Dann wird nächste Stelle wieder zurück gestellt.
Beispiel:
Gestellt wird die Sekunde. Ist die während des Stellens 0 geworden, ist die Minute aufaddiert.
Es werden 60 Sekunden abgezogen. Damit ist die Sekunde 0 und die Minute hat sich nicht verändert.

          if (now.second() == 0)                // Dann war Überlauf in die Minute
          {
            rtc.adjust(now.unixtime() - 60);    // komme zurück in alte Minute
          }

Das Ganze geht bei Minute und bei Stunde ebenso.
Geht die Stunde auf 0, wird ein Tag aufaddiert. Das wird gemerkt und genau ein Tag (86400 Sekunden) wieder abgezogen.
Das korrigiert dann auch, wenn z.B. am 31.12. die Stunde von 23 auf 0 am 1.1. geht. Gehe ich einen Tag zurück, wird das Jahr und der Monat mit korrigiert und ich muss mich um nichts kümmern.
Bis hierhin nichts, was sich grundlegend von anderen unterscheidet.

Beim Datum ist das aber anders - und hier lag mein Fokus.
Wenn ich mit Variablen einen Tag aufaddiere muss ich selbst dafür sorgen das sowas wie 30.2. oder 31.4. nicht passiert. Das möchte ich der Lib überlassen.
Die Idee dahinter ist, das ich im Tagesrhythmus mit 86400 aufaddiere und wieder auslese.
Am Beispiel Tag:
Ich merke mir den aktuellen Monat in istMonat. Dann addiere ich einen Tag.
Wenn sich der Monat geändert hat, bin ich zuweit. (Alternativ wäre, wenn der Tag == 1)

Es ist unverhältnismäßig zu wollen, einen Monat direkt zu setzen. Dazu brauche ich auch das Jahr und den Tag aus der unixtime und muss dann erstmal berechnen, wohin die Reise geht.
Also gehe wieder in den letzten Monat zurück, indem ein Tag rückwärts gerechnet:

          if (now.month() != istMonat)          // Monat überlaufen? ....
          {
            rtc.adjust(now.unixtime() - 86400);  // komme zurück in aktuellen Monat

Dann bin ich am letzten Tag des Monat - egal wieviele Tage der hat - und lasse dann die unixtime soweit runterrechnen, bis der Tag 1 erreicht ist ( Die Funktion entscheidet selbst, ob hoch oder runtergezählt wird)

            tageKorrektur(1);                    // .... dann stelle auf Monatsanfang

Beim Monat wird das komplizierter.
Ich kann den Monat nur hochstellen, indem ich die Tage hochzähle. Es gibt keine einheitlichen Sekunden für das aufaddieren des Monats.

Also merke ich mir den aktuellen Tag.
Ich merke mir den aktuellen Monat.
Jetzt werden mit jedem gewollten hochzählen des Monats solange Tage addiert, bis der gemerkte Monat (istMonat) != dem aus der unixtime ist.
Durch das hochzählen im Tagesrhythmus wird aber auch der Tag verstellt.
Den lasse ich mit der TageKorrektur(istTag) auf den aktuellen Tag wiederherstellen.

Abgefangen wird auch der Überlauf auf das Jahr.
Es wird sich schon am Anfang das aktuelle Jahr gemerkt. (istJahr) Kommt es zum Überlauf, wenn der Monat von 12 auf 1 gesetzt wird, stelle ich unixtime erst um 365 Tage zurück und dann ggfls jeweils um einen Tag bis das Jahr in der unixtime wieder dem aktuellen Jahr entspricht. Dann setze ich den Monat auf 1 und erst dann den Tag wieder auf den gemerkten.

Beim Jahr ist es ähnlich. Ich addiere auf die unixtime 365 Tage und solange sich das Jahr nicht verändert hat nochmal einen Tag.
Dann setze ich den Monat wieder auf den gemerkten istMonat und dann den Tag.

Solange die Tage/Monate/Jahre verändert werden, werden keine Werte in den Zeiten Stunde/Minute/Sekunde verändert. Addiert wird mmer nur mit ganzen Tagen (86400). Damit können die unverändert bleiben.

weshalb ich nur ein paar mehr oder minder sinnvolle Anmerkungen machen kann:

dafür schonmal Danke!

Du merkst Dir die Zeit der RTC, veränderst sie, um sie dann durch Berechnung zu rekonstruieren. Das verstehe ich nicht unter "wir lassen die Zeit der RTC unverändert". Im Sekundenbereich dürfte es Abweichungen geben, das gefällt mir nicht so gut, hängt aber auch von der Anwendung ab. Für die Klanguhr dürfte es egal sein.

Nicht ganz.
Ich merke mir bei Veränderungen des Datums die Daten für Jahr/Monat/Tag weil es dafür keine festen Werte gibt.
Die unixtime ist meine Berechnungsgrundlage. Darum wird die auch immer erst ausgelesen (now=rtc.now()) und dann mit dem festen Wert addiert (bzw. subtrahiert ) und dann sofort wieder in die RTC geschrieben.

  uint32_t merkemillis = millis();                // Merker für Korrektur


  uint32_t lastmillis = millis();
  while (millis() - lastmillis < 10000 && digitalRead(TR)) // Warte auf Tastendruck oder Zeitablauf
  {}



Das scheint mir doppelt, außerdem könntest Du auch die millis() direkt nutzen, da setup immer nach Reset startet:



while (millis() < 10000 && digitalRead(TR)) // Warte auf Tastendruck oder Zeitablauf
  {}



Wenn man ODER ins Programm bringen möchte:



while (millis() > 10000 || !digitalRead(TR)) // Warte auf Zeitablauf oder Tastendruck
  {}

Der Merker für die Korrektur könnte auch mit 0 angesetzt werden. Ja vielleicht…
Hintergrund ist ja, das ich 10 Sekunden maximale Wartezeit - abhängig ob Taste gedrückt oder nicht - habe, und wenn ich in sekunde 6, 7 oder 9 die Kompilezeit übernehme wenigstens annähernd der realen Zeit entsprechen möchte.

Das mit dem ODER führt zum selben Ergebnis.
Ich prüfe ob die Zeit nicht abgelaufen ist UND ob die Taste NICHT gedrückt.
In beiden Versionen führt das drücken der Taste zum Abbruch und der Übernahme.

  Serial.println(F("Kompilierzeit übernehmen? Taste links drücken"));


  if (!digitalRead(TR))                          // Taste gedrückt?



Hast Du hier links und rechts verwechselt?

Ja. Kommentar wird geändert.

      Serial.print(F("auswahl: "));

Serial.println(auswahl);



Leider wird auf dem Monitor nur eine mir nichts sagende Zahl angezeigt.

Das ist die Position im enum für das switch-Statement. Passe ich noch an.

Danach habe ich mich dann leider im Programm verlaufen und mein Uhrenmodul zeigte eine unreale Zeit :o

Das ist schade…

[Nachsatz] - Das ist der 4te Versuch das Post loszuwerden. Blos gut, das ich das mit STRG-A STRG-C zwischenspeichere…

my_xy_projekt:
Nicht ganz.

Ich würde es vorziehen, wenn sich rtc.adjust() von meiner RTC fernhält, wenn ich nichts verändern möchte, weil das der Normalzustand sein sollte.

my_xy_projekt:
Der Merker für die Korrektur könnte auch mit 0 angesetzt werden. Ja vielleicht....

Den meinte ich nicht, sondern den Zeitablauf nach 10 Sekunden, dann da kommt es nicht auf die Millisekunde an.

my_xy_projekt:
Das mit dem ODER führt zum selben Ergebnis.

Das will ich hoffen, aber Programm (UND) und Kommentar (ODER) passen nicht zusammen. Mit ODER auch im Programm ist es m. E. verständlicher.

my_xy_projekt:
Das ist die Position im enum für das switch-Statement. Passe ich noch an.

Die Bedeutung aus dem enum zu lesen, hat mich wohl überfordert ::slight_smile:

Ich warte auf die Anpassung.

Bis Dienstag oder nimmer mehr ;D