ESP8266 NTP DS3231 Zeit setzen

Hallo zusammen,

ich habe mal wieder eine Denkblockade und komme nicht zum gewünschten Ergebnis. Ziel ist es die Uhrzeit per WLAN zu holen und diese dann in den DS3231 zu speichern unter Berücksichtigung der Sommer / Winterzeit. Da der DS3231 relativ genau ist, möchte ich die Uhrzeit nur einmal jede Stunde evtl. auch nur einmal pro Tag holen. Zu meinem Problem:

  1. Wie setze ich den Code von jurs richtig ein, damit die Uhrzeit später stimmt? Meine Befürchtung ist, das ich die Sommerzeit berechne (+1Std) und dann wird die "Sommerzeit vom NTP Server übertragen" und ich rechne dann quasi 1 Stunde mehr drauf. Hoffe Ihr versteht was ich meine....

  2. Ich verwende die ESP Library von hier. Auf dem ESP ist eine NTP Firmware von Tomer drauf. Ich habe noch ein Problem mit dem ESP8266, wenn er die Zeit holt, steht der µC ca.für 4 Sec bzw. ich habe ein Delta von 4s bei meiner Zeit. Wie kann ich diesen BUG beseitigen? Ebenso habe ich noch eine Frage: Wie kann ich vorher Prüfen ob die Internet Verbindung besteht? Schalte Nachts mein WLAN ab und dann kann er sich ja gar keine Zeit mehr hole wenn ich den Intervall auf jede Stunde setze.

/*
Includes
*/
#include 
#include 
#include 
#include 

SoftwareSerial debugSerial(10,11);

/*
Variables
*/
unsigned long CurrentMillisUpdateTime;
unsigned long PrevMillisUpdateTime = 0;

//################################  Setup #######################################//

//################################    WLAN - Setup   #######################################//
#define WIFI_SSID "GEHEIM" // Enter your WLAN SSID
#define WIFI_PASS "GEHEIM" // Enter your WLAN Pass

//################################ Time Sync Update #######################################//
const long UpdateTime = 60000; // Time in ms (60sec = 60000ms / 60min = 360000)


//#########################################################################################//

//#########################################################################################//
ESP8266_NTP esp8266_ntp;

#define RTC_ADDRESS 0x68       // I2C Address of RTC 0x68


byte RTC_Minute,RTC_Hour,ESP_Sec,ESP_Minute,ESP_Hour=0;                


void setup() {
  
  debugSerial.begin(38400);
  if(esp8266_ntp.initialize(WIFI_SSID, WIFI_PASS) == 0) {
      esp8266_ntp.setGMTOffset(2);
  }
  Wire.begin();                                  
  Wire.beginTransmission(RTC_ADDRESS);           
  Wire.write(0x0E);                               
  Wire.write(0x00);                               
  Wire.endTransmission();                        
  RTC_Start();
  SyncTime();
  Read_RTC_Time();
  debugSerial.print(RTC_Hour);
  debugSerial.print(RTC_Minute);
  debugSerial.print(RTC_Sec);
}



void loop() {
  SyncTime ();
  Read_RTC_Time();
  debugSerial.print(RTC_Hour);
  debugSerial.print(":");
  debugSerial.print(RTC_Minute);
  debugSerial.print(":");
  debugSerial.println(RTC_Sec);
}



void SyncTime(){
  CurrentMillisUpdateTime = millis();
  if(CurrentMillisUpdateTime - PrevMillisUpdateTime >= UpdateTime){
    if(esp8266_ntp.initialize(WIFI_SSID, WIFI_PASS) == 0) {
      esp8266_ntp.setGMTOffset(2);
      PrevMillisUpdateTime = CurrentMillisUpdateTime;
      ESP_Hour=hour(getNtpTime());
      ESP_Minute=minute(getNtpTime());
      ESP_Sec=second(getNtpTime());
      RTC_Set();
    }   
  }
}

time_t getNtpTime(){
  esp8266_ntp.setGMTOffset(2);
  return esp8266_ntp.getTime();
}

boolean SummerTime(){
  DateTime now = RTC.now();
  if (now.month()<3 || now.month()>10) return false; 
  if (now.month()>3 && now.month()<10) return true; 
  if (now.month()==3 && now.day()<25) return false; 
  if (now.month()==10 && now.day()<25) return true; 
  if (now.month()==3 && (now.hour() + 24 * now.day())>=(1 + 24*(31 - (5 * now.year() /4 + 4) % 7)) 
     || now.month()==10 && (now.hour() + 24 * now.day())<(1 + 24*(31 - (5 * now.year() /4 + 1) % 7)))
    return true;
  else
    return false;
}

void Read_RTC_Time(){
  Wire.beginTransmission(RTC_ADDRESS);
  Wire.write(0x00);               
  Wire.endTransmission();
  Wire.requestFrom(RTC_ADDRESS, 3);
  RTC_Sec= bcdToDec(Wire.read());
  RTC_Minute= bcdToDec(Wire.read());
  RTC_Hour= bcdToDec(Wire.read());
}

void RTC_Set(){
  Wire.beginTransmission(RTC_ADDRESS);
  Wire.write(0x00);
  Wire.write(decToBcd(ESP_Sec));    
  Wire.write(decToBcd(ESP_Minute));
  Wire.write(decToBcd(ESP_Hour));      
  Wire.endTransmission();
}

void RTC_Start(){
  Wire.beginTransmission(RTC_ADDRESS);
  Wire.write(0x00);
  Wire.write(decToBcd(RTC_Sec));    
  Wire.endTransmission();
}

byte decToBcd(byte val){
  return ((val/10)<<4)+(val%10);
}

byte bcdToDec(byte val){
  return ((val>>4)*10)+val%16;
}

Wenn du jede Stunde einen Abgleich machst, brauchst du keine DS3231. Da reicht auch der ungenaue DS1307 (der ist über Tage hinweg genau genug, wenn der NTP mal nicht zur Verfügung steht).

ht81: 2. Ich verwende die ESP Library von hier. Auf dem ESP ist eine NTP Firmware von Tomer drauf. Ich habe noch ein Problem mit dem ESP8266, wenn er die Zeit holt, steht der µC ca.für 4 Sec bzw. ich habe ein Delta von 4s bei meiner Zeit. Wie kann ich diesen BUG beseitigen?

Von welchem NTP-Server rufst Du denn die Zeit ab? Das kann ich in Deinem Code nicht erkennen. Viele Zeitserver im Internet wie pool.ntp.org sind auch stets chronisch überlastet. Die reagieren dann auf Anfragen entweder sehr lahm, oder überhaupt nicht und liefern eine Timeout.

Was macht eine "NTP Firmware von ..." und was macht diese Library namens "esp8266_ntp.h"?

Üblicherweise: Wenn Du möchtest, dass etwas vernünftig funktioniert, dann mach es selbst!

Und wenn Du Dinge schon von fremdem Code erledigen läßt, der in einer Firmware eingebaut ist oder in einer Drittanbieter-Library steckt, und Du trotzdem keine BUGS leiden kannst, dann vermeidest Du es am besten, BUGS ohne Not selbst zu programmieren, so wie zum Beispiel sowas:

      ESP_Hour=hour(getNtpTime());
      ESP_Minute=minute(getNtpTime());
      ESP_Sec=second(getNtpTime());

Du machst also dreimal eine Zeitabfrage bei einem NTP-Server, getrennt für Stunden, Minuten und Sekunden?

Damit riskierst Du, dass bei der zweiten oder dritten Zeitabfrage die Sekunde überhaupt nicht mehr zur selben Minute oder Stunde gehört wie bei der ersten Abfrage.

Beispiel: Du bekommst bei der ersten NTP-Abfrage die Zeit 12:59:59 und nimmst davon die Stunde Du bekommst bei der zweiten NTP-Zeitabfrage die Zeit 13:00:00 und nimmst davon die Minute Du bekommst bei der dritten NTP-Zeitabfrage die Zeit 13:00:01 und nimmst davon die Sekunde

Und dann setzt Du die Zeit auf 12:00:01, wenn Du mit drei Anfragen drei verschiedene Zeiten abgefragt hast?

Und wunderst Dich, dass das auch alles so lange dauert?

Falls Du es nicht wußtest: Dreimal eine Zeitanfrage bei einem NTP-Server zu machen dauert auch dreimal so lange wie eine einzige Zeitabfrage beim NTP-Server zu machen.

So, und jetzt kommst Du und darfst dreimal überlegen: "Wie kann ich diesen BUG beseitigen?" und dabei die benötigte Zeit für eine NTP-Zeitsynchronisation im Sketch glatt dritteln und dabei gleichzeitig noch weniger fehlerträchtig machen?

Wo ist die Dokumentation zu Deiner Library? Welchen Wert liefert die Library zum Beispiel im Fehlerfall zurück? Bei Deiner NTP-Zeitsynchronisation fehlt auch die komplette Fehlerbehandlung! Mal angenommen, die Funktion getNtpTime() liefert im Fehlerfall "0" als Wert zurück, dann synchronisierst Du Deine RTC danach gnadenlos auf 0 Uhr 0 Minuten und 0 Sekunden, wenn ein Fehler bei der NTP-Zeitabfrage auftritt. Absicht? Oder BUG?

In Sachen Häufigkeit der Zeitsynchronisation stimme ich sschultewolter zu. Eine DS3231 geht in 52 Wochen um maximal 64 Sekunden falsch. Es drüfte daher voll ausreichend sein, einmal am Tag eine Zeitsynchronisation durchzuführen. Vorzugsweise am Tag, wenn Dein WLAN nachts ausgeschaltet ist.

Und wenn Du eine stets sekundengenaue "lokale Zeit" unter Berücksichtigung von Sommerzeit benötigst, dann läßt Du eine RTC-Echtzeituhr natürlich am besten NICHT auf Sommerzeit laufen. Sondern die RTC läuft dann immer durchgehend in Zonenzeit (Berlin UTC+1). Und ob Sommerzeit herrscht und eine Stunde auf die Zeit draufaddiert werden muss, stellst Du dann jedesmal nach dem Auslesen der Uhrzeit aus der RTC fest: D.h. die Ausleselogik aus der RTC wäre: - lies Datum und Zeit aus der RTC - stelle fest, ob dies in die Sommerzeit fällt - wenn ja, addiere eine Stunde drauf (und passe ggf. das Datum an) Dann bekommst Du beim Auslesen stets die korrekte lokale Zeit, ohne bei einer Zeitumstellung auf die nächste Zeitsynchronisation und Umstellung in der RTC warten zu müssen.

Hallo Ihr beide,

muss zugeben das ich wohl doch etwas mit der Aufgabe überfordert bin und ich es noch nicht verstanden habe.

Ich hätte jetzt folgendes gemacht

setSyncProvider(getNtpTime()); 
ESP_Hour=hour();
ESP_Minute=minute();
ESP_Sec=second();

Bin mir aber gar nicht sicher. Ich hole mir die Zeit mit setSnycProvider und setze die Interne. Dann rufe ich direkt die Stunden, Minuten und Sekunden und speichere diese in meine Variablen.

ht81: muss zugeben das ich wohl doch etwas mit der Aufgabe überfordert bin und ich es noch nicht verstanden habe.

Dein Problem scheint vor allem zu sein, dass Du denkst, Du arbeitest mit zwei verschiedenen Zeiten, wobei die eine Zeit aus der RTC kommt und die zweite Zeit per NTP aus dem Internet. Das ist aber falsch.

Bei Deiner Kombination aus Libraries und Funktionen arbeitest Du aber tatsächlich mit DREI verschiedenen Zeiten, die Du miteinander zu synchronisieren versuchst. Die drei Zeiten sind:

  1. "Systemzeit", bereitgestellt von Deiner library Diese "Systemzeit" wird aus dem 16 MHz Systemtakt des Arduino gebildet und weitergezählt. Auf modernen Arduino-Boards ist diese Zeit nicht besonders genau, sie läuft pro Tag etliche Minuten falsch, wenn Du sie nur einmalig beim Programmstart aus einer RTC ausliest und dann einfach immer nur weiterlaufen läßt, ohne regelmäßige Synchronisation.

  2. "RTC-Zeit", bereitgestellt von Deiner Read_RTC_Time() Funktion Die RTC-Zeit wird durch die DS3231 RTC-Uhr gebildet und weitergezählt Diese Zeit ist relativ genau, bis auf ca. 60 Sekunden pro Jahr Aber diese Zeit kommt nicht von alleine auf Dein Arduino-Board, Du mußt sie aus der RTC auslesen.

  3. "NTP-Zeit", bereitgestellt aus Deiner getNtpTime() Funktion Die NTP-Zeit wird auf einem externen Zeitserver im Internet gebildet und dort weitergezählt Diese Zeit ebenfalls sehr genau, aber die Abfrage kann fehlschlagen, wenn der Server überlastet ist, wenn Dein Internetprovider Probleme hat, oder wenn Du im Heimnetzwerk den Router oder das WLAN abgeschaltet hast.

Wenn Du nun drei verschiedene Zeiten hast und Dein Sketch irgendwas mit den Zeiten anfangen soll, mußt Du einen Plan haben, welche Zeit Dein Programm verwenden soll und welche Zeit Du mit welcher anderen Zeit synchronisieren möchtest.

Bei dieser Kombination würde sich anbieten: - als laufende Zeit die "Systemzeit" zu verwenden - die "Systemzeit" einmal pro Minute mit der "RTC-Zeit" zu synchronisieren - die "RTC-Zeit" einmal pro Tag mit der "NTP-Zeit" zu synchronisieren. Aber das natürlich nur dann, wenn eine NTP-Zeit auch erfolgreich vom Zeitserver gelesen werden konnte.

Wobei Du, falls die Zeiten für Schaltvorgänge genutzt werden sollen, die Schaltlogik darauf auslegen müßtest, dass es in dem Fall einige Sekunden der "Systemzeit" mehrmals vorkommen können, wenn eine Synchronisation "Zeit zurückstellen" erfolgt, oder auch, dass einige Sekunden "Systemzeit" komplett ausfallen können, wenn eine Synchronisation "Zeit vorstellen" erfolgt.

:confused:

Jetzt ist der Ofen aus. Ich habe mittlerweile echt keinen Pan mehr wie ich das ganze angehen soll. Du schreibst das die Uhrzeit immer in der Zonenzeit Berlin UTC+1 laufen soll. Ok, aber welche Zeit bekomme ich den bei dem NTP Server? Die ist doch immer aktuell?!

Ziel ist es die Wordclock von VolvoDani um den ESP zu erweitern, bzw. ein Stellen den Uhr mittels WLAN zu ermöglichen.

Die Systemzeit würde ich mittels

setSyncProvider(rtc.now());

Und das per Funktion mit millis() alle 60s. Aber das könnte ich doch mit

setSyncInterval(60000);

; auch, oder?

Oder die andre Überlegung geht in:

time_t t = rtc.now();

das wieder rum mit millis alle 60s und arbeiten tut ich dann mit

hour(t);
minute(t);
second(t);

Das müsste ja dann die interne Zeit sein, die immer weiterläuft

setSyncProvider(rtc.now) setzt nur die Funktion von der die genaue Zeit kommt. Das macht man einmal. Beachte auch den Aufruf. Keine Funktions-Klammern. Da wird ein Funktions-Zeiger übergeben.

setSyncInterval() ist was anderes. Die Time Library funktioniert so dass sie die Zeit erst mal lokal zählt (d.h sie läuft mit dem Systemtakt der nicht besonders genau ist). Und wenn du die Zeit ausliest wird die lokale Zeit zurückgegeben. mit syncInterval stellst du ein wie oft die lokale Zeit mit der Zeit-Quelle abgeglichen wird.

Hi,

ok. Das bedeutet das wenn ich setSyncProvider(rtc.now) nehme und ich z.B. den setSyncInterval(10) setze, wird die (lokale) Zeit nahezu fast identisch der des RTC sein, der Arduino wird ja quasi alle 10ms abgeglichen. Ich brauche also diese Parameter nur einmalig im Setup aufrufen und das war´s.

Die Zeit kann ich mir dann mittels:

Serial.print(hour());
Serial.print(":");
Serial.print(minute());
Serial.print(":");
Serial.print(second());

anzeigen lassen. Habe ich das soweit richtig verstanden?

der Arduino wird ja quasi alle 10ms abgeglichen

Der Abgleich findet glaube ich beim Auslesen der Zeit statt. Also wenn du ein Zeit-Intervall von 10ms hast und du alle 1 Sekunde die Zeit ausliest, wird auch nur alle 1 Sekunde die RTC ausgelesen.

Ansonsten ja.

:confused: Ok, danke für die Antwort, wenn auch ich nicht verstehe warum man dann diese Funktion nutzen soll. Es wäre dann egal welche Zeit dort steht, da e ja eh nur beim Abgleich gesync wird. Oder ich verstehe es immer noch nicht....

Ich versuche mal heute Abend etwas damit zu spielen.

Kommt auf die Zeitquelle an. Bei einer RTC wäre es egal. Aber du willst dich nicht jede Sekunde mit einem NTP Server synchronisieren.

Also,

ich habe meinen Code nun etwas umgebaut und lese jetzt auch nur einmal die Zeit aus und nicht 3mal. Trotzdem hatte ich ein Delta zwischen 2 - 3 Sekunden. Ich habe nun mein Problem gefunden. Der Fehler liegt an der Windows Uhr vom Laptop. Habe diese zwar vor dem Testen immer gesynct, jedoch läuft de nicht richtig. Wenn ich die Uhrzeit von http://www.atomzeit.eu/ mit der des Laptops vergleiche habe ich das besagte Delta. Wenn ich meinen RTC via NTP abgleiche stimmt die Zeit exakt, wie von mir gewünscht.

Ich hätte nicht erwartet das die Uhr falsch geht. Ist es auch noch nie aufgefallen?

Habe jetzt soweit alle Punkte von jurs umsetzen können, wenn auch nicht im perfektem Code, jedoch funktional. Setzte mich grad etwas mit der Fehlerbehandlung auseinander.

Hättet ihr mir noch ein Vorschlag wie ich

- die "RTC-Zeit" einmal pro Tag mit der "NTP-Zeit" zu synchronisieren. Aber das natürlich nur dann, wenn eine NTP-Zeit auch erfolgreich vom Zeitserver gelesen werden konnte.

dieser Anforderung gerecht werden kann?

Meine momentane Lösung ist diese hier:

boolean CheckWIFIStatus(){
Serial.println("AT+CWJAP?");
  if(!Serial.find(WIFI_SSID)) {
    return false;
  }
  return true;
}

Ich prüfe ob ich mit dem Netzwerk verbunden bin. Das funktioniert soweit auch. Aber wenn ich z.B. zwar mit der FritzBox verbunden bin und diese Verbindung besteht ist es immer "wahr", selbst wenn die FritzBox das DSL Signal verlieren würde oder wenn der NTP Server nicht erreichbar wäre. Wie könnte eine erweiterte Lösung aussehen?

Der Test, ob die FritzBox "da" ist, ist ja schon was. Geht der negativ aus, brauchst du keinen NTP zu fragen.

Fragst du einen NTP nach der Zeit, dann gibts im Prinzip nur 2 Ergebnisse: 1. Du bekommst eine Zeit 2. Die Verbindung wird nicht aufgebaut, bricht ab, oder Timeout.

Ok,

genau. Das könnte ich so testen.

boolean CheckWIFIStatus(){
  Serial.println("AT+CWJAP?");
  if(!Serial.find(WIFI_SSID)) {
    return false;
  }
  Serial.println("AT+CIPNTP?"); 
  if(!Serial.findUntil("Time: ", " ")) { 
  return false;
  }
return true;
}

Wäre es so ok von der Logik?

Das kann ich dir leider nicht sagen, da ich den ESP8266 nicht gut genug kenne.

Und wie ist das hier ausgegangen?