UTC Zeitstempel also Sekunden seit 1970 in FRAM schreiben

Ich hänge immer noch in meinem Programm, bei der Ablage als 4 Byte Zahl dieser UTC Zeit (Sekunden seit 1970) im FRAM. Kann mir jemand erst mal nur zeigen, was ich da bei FRAM Schreiben → Zeit und Datum schreiben machen muss, dass ich an dieser Stelle diesen Zeitstempel ins FRAM Schreibe ??

Ich schreiben den Messwert in die ersten 4 Byte, das funktioniert und möchte den Zeitstempel in den zweiten 4 Byte ablegen. Was ich immer noch nicht schaffe.

FRAM Auslesen ist dann die nächste Baustelle, da wird bis jetzt auch nur der Messwert ausgelesen, was auch funktioniert. Aber das ist jetzt erst mal uninteressant. Mir würde der Schritt reichen, dass der UTC Zeitstempel im FRAM steht. Ich tappe da rum wie ein Blinder, weil ich keine Ahnung habe, wie ich den Wert aufbereiten muss, dass ich in in den 4 Byte ablegen kann.

Hier das derzeitige Programm.


```cpp
/* 
    Abfrage von analogen Werten mit dem ESP32
    am ADS1115 mit Weitergabe per I2C an den I2C - FRAM, 
    wenn die Spannung deutlich abweicht vom letzten Messwert. 
    Programm im Moment vom 18.01.2026
*/
#include <WiFi.h>
//#define NTP_SERVER "de.pool.ntp.org"
#define NTP_SERVER "Fritz.Box"
#define TZ_INFO "WEST-1DWEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00"  // Western European Time
//--------------------------------------------------------------------------------------------
#include <Adafruit_ADS1X15.h>  // bindet Wire.h für I2C mit ein
Adafruit_ADS1115 ads;
#define ADS_I2C_ADDR 0x48
//-----------------------------FRAM-----------------------------------------------------------
#include "Adafruit_EEPROM_I2C.h"
#include "Adafruit_FRAM_I2C.h"
Adafruit_FRAM_I2C i2ceeprom;
#define EEPROM_ADDR 0x50  // the default address!
// -------------------OLED Display einrichten-------------------------------------------------
#include <U8g2lib.h>  // als U8g2 im Bibliotheksverwalter zu finden
U8G2_SH1106_128X64_NONAME_F_HW_I2C oled(U8G2_R0);
//------------------------ADS1115 16Bit Analog-Digital Sensor---------------------------------
const float multiplier = 0.125F;  // ADS1115-Multiplikator bei einf. Verstärkung
int A_Value;                      // Messwert per GPIO
int adc0;                         // Messwert an Kanal 0 des ADS1115
int adc1;                         // Messwert an Kanal 1 des ADS1115
int adc2;                         // Messwert an Kanal 2 des ADS1115
int adc3;                         // Messwert an Kanal 3 des ADS1115
float A_mv, ads_mv0, ads_mv1;     // Messwert in Millivolt umgerechnet
//--------------------------------------------------------------------------------------------
//----------------------- Spannungsmessungen Speichern ---------------------------------------
int Spannung2neu = 0;
int Spannung2alt = 0;
float f = 0;
uint8_t buffer[4];  // floats are 4 bytes!
int Speicher = 4;
int Speicher2 = 0;
int Zaehler = 0;
int Zaehlerkontrolle = 0;
byte LSpin = 27;
byte Messtiefe = 10;
byte Reagieren = 0;
//------------------------- Minuten Tackt zur Ausgabe ----------------------------------------
unsigned long Sekundenablauf01 = 0;  // Zeit für die Ausgabe Aufs Display & Serial
const unsigned long Pausezeit01 = 60000;
unsigned long Zeitablauf = 0;
//------------------------ PLatzverbrauch am FRAM pro Std. -----------------------------------
unsigned long Sekundenablauf02 = 0;  // Zeit für die Ausgabe Aufs Display & Serial
const unsigned long Pausezeit02 = 60000 * 60;
//-------------------------------- Zeit Messungen --------------------------------------------
unsigned long Messungstart = 0;
unsigned long Messungende = 0;
unsigned long Laufzeit = 0;

//############################################################################################
// ----------------------------------------------------SETUP----------------------------------
//############################################################################################
void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("Analog-Test ESP32");
  //-----------------------------------------------------------
  Serial.println(F("NTP Server Abfrage"));
  NTP_Zeit();

  // ------------------EPROM Ereichbar ??--------------------
  if (i2ceeprom.begin(0x50)) {  // Sie können die neue I2C-Adresse hier einfügen, z. B. begin(0x51);
    Serial.println(F("I2C FRAM gefunden"));
  } else {
    Serial.println(F("I2C-FRAM nicht identifiziert ... überprüfen Sie Ihre Verbindungen.?\r\n"));
    while (1) delay(10);
  }
  //-----------------------ADS 1115---------------------------
  ads.begin(ADS_I2C_ADDR, &Wire);
  // Werte 1-fach verstärken (ESP32 liefert  max. 3,3V)
  ads.setGain(GAIN_ONE);
  //----------------------- Oled Display ---------------------
  oled.begin();
  oled.clearBuffer();  // Textspeicher löschen
  //------- Erste Displayausgabe gleich nach dem Start -------
  Sekundenablauf01 = 60000;
}
//############################################################################################
// ----------------------------------------------------LOOP-----------------------------------
//############################################################################################
void loop() {
  Messungstart = millis();
  Zeitablauf = millis();

  if (Zeitablauf - Sekundenablauf01 >= Pausezeit01) {  // Eine Minute abgelaufen?
    Displayausgabe();
    Sekundenablauf01 = millis();
  }
  Kontrollen();
  Messen();
  if (Reagieren == 1) {
    SpeicherdatenSchreiben();
    Spannung2alt = Spannung2neu;
    SpeicherdatenLesen();
    Reagieren = 0;
  }
  /*
  Messungende = millis();
  Laufzeit = Messungende - Messungstart;
  if (Laufzeit >= 20){
  Serial.println(Laufzeit);
  }
  */
}  //---------------------------------- LOOP ENDE ---------------------------------------------

//############################################################################################
//-------------------------- WIFI Verbindung und Zeit holen ----------------------------------
//############################################################################################
void NTP_Zeit() {
  WiFi.mode(WIFI_STA);
  WiFi.begin("FRITZ!Box Gastzugang", "GastvonFranzKoehler");
  while (WiFi.status() != WL_CONNECTED)  // Ist WLAN Connect?
  {
    delay(500);
  }
  struct tm local;
  configTzTime(TZ_INFO, NTP_SERVER);  // ESP32 Systemzeit mit NTP Synchronisieren
  getLocalTime(&local, 10000);        // Versuche 10 s zu Synchronisieren
  WiFi.mode(WIFI_OFF);
}
//############################################################################################
//---------------------------------- Displayausgabe ------------------------------------------
//############################################################################################
void Displayausgabe() {
  tm local;
  getLocalTime(&local);
  oled.clearBuffer();               // Textspeicher löschen
  oled.setFont(u8g2_font_6x12_tr);  // Kleine Schrift 6x12
  oled.setCursor(10, (1 * 10));
  oled.print(&local, " Datum: %d.%m.%y");
  // Hier das Problem. Die Zeitausgabe
  oled.setCursor(10, (2 * 10));
  oled.print(&local, " Zeit : %H:%M");  // 16 Zeichen neue Zeit schreiben
  oled.sendBuffer();

  // Messung auf das Display ausgeben
  oled.setFont(u8g2_font_10x20_mf);
  //ADS Kanal 0 anzeigen
  oled.setCursor(20, (2 * 20));
  oled.println(F("ADR "));
  oled.print(Speicher);
  //ADS Kanal 1 anzeigen
  oled.setCursor(20, (3 * 20));
  // if (ads_mv1 < 0) { ads_mv1 == 0; }
  oled.print(ads_mv0);
  oled.print(F(" Volt "));
  oled.sendBuffer();

  /*
    struct tm enthält diese Atribute:
    ===============================================
    Member   Type  Meaning                   Range
    tm_sec   int   seconds after the minute  0-60*
    tm_min   int   minutes after the hour    0-59
    tm_hour  int   hours since midnight      0-23
    tm_mday  int   day of the month          1-31
    tm_mon   int   months since January      0-11
    tm_year  int   years since 1900
    tm_wday  int   days since Sunday         0-6
    tm_yday  int   days since January 1      0-365
    tm_isdst int   Daylight Saving Time flag
    ==============================================
    */
}
//------------------------------------------------------------------------------------------
//############################################################################################
// ------------------------------------ Messung per ADS1115 ----------------------------------
//############################################################################################
void Messen() {
  // - Kanal 0 messen (single-ended)
  adc0 = ads.readADC_SingleEnded(0);
  ads_mv0 = ads.computeVolts(adc0);
  // - Kanal 1 messen (single-ended)
  adc1 = ads.readADC_SingleEnded(1);
  ads_mv1 = ads.computeVolts(adc1);
  // oder
  // - differenzielle Messung an Kanal 0/1
  //adc0 = ads.readADC_Differential_0_1();
  //ads_mv = (adc0 * multiplier);
  //Serial.printf("; I2C: %4.2f mV\n", ads_mv);
  //===============================================
  //------------------------Ist die Spannung verändert ? Ja, dann speichern !-----------------
  // Hier wird die Messung "float" durch das *100, um zwei Kommas nach hinten geschoben
  // und in einen "int" übergeben. Dadurch sind die Werte hinter dem Komma abgeschnitten.
  // Das heißt, aus den gemessenen z.B. "3,1255768" Volt werden 312,55768
  // und mit int sind die stellen hinterm Komma weg.
  // Für den Vergleich als <-> neu bleibt also nur noch der Wert "312"
  // Die dritte stelle hinterm Komma ändert sich schon rel. oft.
  // -----------------------------------------------------------------------------------------
  Spannung2neu = ads_mv0 * Messtiefe;
  if (Spannung2alt == Spannung2neu) {
  } else {
    // Messung auf das Display ausgeben
    oled.setFont(u8g2_font_10x20_mf);
    //ADS Kanal 0 anzeigen
    oled.setCursor(20, (2 * 20));
    oled.println(F("ADR "));
    oled.print(Speicher + 4);
    //ADS Kanal 1 anzeigen
    oled.setCursor(20, (3 * 20));
    oled.print(ads_mv0);
    oled.print(F(" Volt "));
    oled.sendBuffer();
    Reagieren = 1;
    tone(LSpin, 1000, 100);  // Piep wenn sich die Spannung ändert.
  }
}
// ------------------------------- Messen Ende ---------------------------------------------

//##########################################################################################
//------------------------------ FRAM Schreiben --------------------------------------------
//##########################################################################################
void SpeicherdatenSchreiben() {
  Speicher = Speicher + 4;
  Zaehler = Zaehler + 1;
  Zaehlerkontrolle = Zaehlerkontrolle + 1;
  // Messwert Schreiben--------------------------------------------------------------------
  f = ads_mv0;
  //memcpy(buffer, (void *)&f, 4);
  memcpy(buffer, (void *)&f, 4);
  i2ceeprom.write(Speicher, buffer, 4);
  // Zeit & Datum Schreiben----------------------------------------------------------------
  tm local;
  getLocalTime(&local);
  //Serial.print(&local);
  Speicher = Speicher + 4;
  memcpy(buffer, (void *)&local, 4);
  i2ceeprom.write(Speicher, buffer, 4);
}
//------------------------------ Schreiben Ende ---------------------------------------------
//
//###########################################################################################
//------------------------------ FRAM Auslesen ----------------------------------------------
//###########################################################################################
void SpeicherdatenLesen() {
  Speicher2 = Speicher - 4;
  //Speicher2 = Speicher;
  i2ceeprom.read(Speicher2, buffer, 4);
  memcpy((void *)&f, buffer, 4);
  Serial.print(F("Rücklese Wert: "));
  Serial.print(f, 8);
  Serial.print(F(" ->  Speicherstelle = "));
  Serial.print(Speicher);
  Serial.print(F(" ->  Wie viele Werte = "));
  Serial.println(Zaehler);
}
//------------------------------ Auslesen Ende ---------------------------------------------
//
//##########################################################################################
//-------------------------- Wichtige Kontrollen -------------------------------------------
//##########################################################################################
void Kontrollen() {
  if (Zeitablauf - Sekundenablauf02 >= Pausezeit02) {  // FRAM Aufzeichungen pro Stunde messen
    if (Zaehlerkontrolle >= 50) {
      Messtiefe = Messtiefe / 10;
      Zaehlerkontrolle = 0;
    }
    Sekundenablauf02 = millis();
  }
  tm local;
  //00:00:XX = 3600(=1 Std) * std = 0 + min = 0 * 60 = 0 + sec = maximal 59 sec
  uint32_t nbSek = 3600ul * local.tm_hour + local.tm_min * 60ul + local.tm_sec;
  if (nbSek <= 2)  // zwischen 00:00:00 Uhr und 00:00:59 Uhr Zeit vom NTP holen
  {
    NTP_Zeit();
  }
}
//------------------------------- Kontrollen Ende -------------------------------------------
```

Die Struktur tm enthält keinen UTC-Zeitstempel und ist nicht 4 Byte groß. Sie kopieren willkürlich die ersten 4 Byte einer deutlich größeren Struktur, was zu einem inkonsistenten und nicht portablen Wert führt.

void SpeicherdatenSchreiben() {
  Speicher = Speicher + sizeof f;
  Zaehler = Zaehler + 1;
  Zaehlerkontrolle = Zaehlerkontrolle + 1;

  f = ads_mv0;
  memcpy(buffer, &f, sizeof f);
  i2ceeprom.write(Speicher, buffer, sizeof f);

  tm local;
  getLocalTime(&local);
  time_t utc = mktime(&local);

  Speicher = Speicher + sizeof utc;
  memcpy(buffer, &utc, sizeof utc);
  i2ceeprom.write(Speicher, buffer, sizeof utc);
}

(Der Operator sizeof liefert die Anzahl der Bytes, die für die Darstellung einer Variablen oder eines Typs belegt werden.)

1 Like

tm local ist die Instanz der tm Struktur.
Du sollst nicht die ganze Struktur abspeichern, sondern nur den Zeitstempel (4 byte)

schau dir noch mal den MVP an den ich dir gemacht habe:

ESP32 NTP Example.ino - Wokwi ESP32, STM32, Arduino Simulator

da wird nicht tm local ("tm tm" bei mir) gespeichert sondern time_t now (der 4bytige Wert!).

Warum?

Wenn Du nicht diese Krücke nimmst, sondern das, was Dir bereits vorliegt, dann bekommst Du nach dem Schreibvorgang als Rückgabewert die nächste Adresse an der Du schreiben kannst.

Ja, das ist es ja, ws ich meine. Die Sekunden ab 1970 sind z.B. derzeit etwa “1.769.353.173”. Das hat locker Platz in 4 Byte. Da gehen “4.294.967.295” rein.

Ja, ich würde gerne fürs erste was nehmen, was ich im Moment auch verstehe. Es bringt nichts, wenn ich keinerlei Ahnung habe was da abgearbeitet wird. Und das Programm, das ich hier habe, ist leichte Kost. Dann dazu lernen ist ein weitere Schritt. aber es mach erst mal Sinn auf das aufzubauen was man kapiert.

Da habe ich ja schon öfter versucht an Adafruit_FRAM_I2C.h anzupassen. Aber da kommen immer Comilierungs Fehler, die ich nicht kapiere. Die Fehlerbeschreibung habe ich ja mal im anderen Thema reingehängt. Aber da hat keiner was dazu gewusst / gesagt / sagen wollen. :woozy_face: Somit war ich damit auch einmal mehr in einer Sackgasse.

Gibt es denn niemanden, der mir einfach mal sagen könnte, was ich in dem Teil hier:

 // Zeit & Datum Schreiben----------------------------------------------------------------
  tm local;
  getLocalTime(&local);
  //Serial.print(&local);
  Speicher = Speicher + 4;
  memcpy(buffer, (void *)&local, 4);
  i2ceeprom.write(Speicher, buffer, 4);
}
//------------------------------ Schreiben Ende ---------------------------------------------

ändern muss, dass ich diese Wert, Sekunden seit 1970 im Buffer habe ?

Ich kann mir keinen Konfortablen Lenker für Rennrad bauen, solange das Ding unten noch keine Räder hat. Ich muss klein anfangen.

  time_t now;
  time(&now);                // da steht nun die UTC drinnen - in einem 4 bytigen Wert!!!

time_t statt tm!

1 Like

➜ post #2...

1 Like

Und es macht keine Sinn, einfach nur diesen Wert Sekunden seit 1970 zu speichern und dann beim Auslesen wieder zu Datum und Uhrzeit zu verwandeln ?

Der Code, den Sie haben,

tm local;
getLocalTime(&local);
...
memcpy(buffer, (void *)&local, 4);

ergibt keinen Sinn. Die 4 im memcpy() gibt die Anzahl der Bytes an, die Sie in den Puffer kopieren möchten, aber die Variable local ist eine Struktur, die deutlich mehr als 4 Bytes umfasst.

Eine struct tm sieht typischerweise so aus.

struct tm {
  int tm_sec;
  int tm_min;
  int tm_hour;
  int tm_mday;
  int tm_mon;
  int tm_year;
  int tm_wday;
  int tm_yday;
  int tm_isdst;
  long int __tm_gmtoff;
  const char *__tm_zone;
};

Wenn Sie also die gesamte Struktur speichern möchten, benötigen Sie deutlich mehr als 4 Bytes.

➜ Der Operator sizeof sagt Ihnen, wie viele Bytes erforderlich sind.

Mach das doch!
Das hier:

Ist 14 Tage alt!

Da hast Du Deinen 4 Byte-Wert.

Brauchtest Du nur 1:1 übernehmen:

void SpeicherdatenSchreiben() {
  Speicher = Speicher + 4;
  Zaehler = Zaehler + 1;
  Zaehlerkontrolle = Zaehlerkontrolle + 1;
  // Messwert Schreiben--------------------------------------------------------------------
  f = ads_mv0;
  //memcpy(buffer, (void *)&f, 4);
  memcpy(buffer, (void *)&f, 4);
  i2ceeprom.write(Speicher, buffer, 4);
  // Zeit & Datum Schreiben----------------------------------------------------------------
  time_t local;
  time(&local);
  //Serial.print(&local);
  Speicher = Speicher + 4;
  
  memcpy(buffer, (void *)&local, 4);
  i2ceeprom.write(Speicher, buffer, 4);
}

Ja danke. habe ich gerade geschrieben und dann den Speicher ausgelesen und da stand dann auch die Zeit drin.

// Zeit & Datum Schreiben----------------------------------------------------------------
  time_t now;
  time(&now);
  Speicher = Speicher + 4;
  memcpy(buffer, (void *)&now, 4);
  i2ceeprom.write(Speicher, buffer, 4);

OK, jetzt bekomme ich sie wieder ausgelesen in einer unsignet long Zahl “Zeitbuffer”.

Rücklese Wert: 2.69625020 Zeitbuffer = 1770034058 ->  Speicherstelle = 68 ->  Wie viele Werte = 8
Rücklese Wert: 2.59425020 Zeitbuffer = 1770034059 ->  Speicherstelle = 76 ->  Wie viele Werte = 9
Rücklese Wert: 2.49950004 Zeitbuffer = 1770034059 ->  Speicherstelle = 84 ->  Wie viele Werte = 10
Rücklese Wert: 2.38425016 Zeitbuffer = 1770034059 ->  Speicherstelle = 92 ->  Wie viele Werte = 11
Rücklese Wert: 2.29775000 Zeitbuffer = 1770034059 ->  Speicherstelle = 100 ->  Wie viele Werte = 12
Rücklese Wert: 2.18924999 Zeitbuffer = 1770034059 ->  Speicherstelle = 108 ->  Wie viele Werte = 13
Rücklese Wert: 2.09250021 Zeitbuffer = 1770034059 ->  Speicherstelle = 116 ->  Wie viele Werte = 14
Rücklese Wert: 1.98937511 Zeitbuffer = 1770034060 ->  Speicherstelle = 124 ->  Wie viele Werte = 15
Rücklese Wert: 1.88587511 Zeitbuffer = 1770034060 ->  Speicherstelle = 132 ->  Wie viele Werte = 16
Rücklese Wert: 1.90225005 Zeitbuffer = 1770034063 ->  Speicherstelle = 140 ->  Wie viele Werte = 17
Rücklese Wert: 2.00150013 Zeitbuffer = 1770034065 ->  Speicherstelle = 148 ->  Wie viele Werte = 18
Rücklese Wert: 2.10212517 Zeitbuffer = 1770034068 ->  Speicherstelle = 156 ->  Wie viele Werte = 19
Rücklese Wert: 2.20775008 Zeitbuffer = 1770034070 ->  Speicherstelle = 164 ->  Wie viele Werte = 20
Rücklese Wert: 2.30087519 Zeitbuffer = 1770034071 ->  Speicherstelle = 172 ->  Wie viele Werte = 21
Rücklese Wert: 2.40937519 Zeitbuffer = 1770034071 ->  Speicherstelle = 180 ->  Wie viele Werte = 22
Rücklese Wert: 2.50100017 Zeitbuffer = 1770034071 ->  Speicherstelle = 188 ->  Wie viele Werte = 23
Rücklese Wert: 2.60475016 Zeitbuffer = 1770034072 ->  Speicherstelle = 196 ->  Wie viele Werte = 24
Rücklese Wert: 2.70250010 Zeitbuffer = 1770034072 ->  Speicherstelle = 204 ->  Wie viele Werte = 25
Rücklese Wert: 2.80312514 Zeitbuffer = 1770034072 ->  Speicherstelle = 212 ->  Wie viele Werte = 26
Rücklese Wert: 2.90412521 Zeitbuffer = 1770034073 ->  Speicherstelle = 220 ->  Wie viele Werte = 27
Rücklese Wert: 3.00025010 Zeitbuffer = 1770034073 ->  Speicherstelle = 228 ->  Wie viele Werte = 28
Rücklese Wert: 3.10587525 Zeitbuffer = 1770034073 ->  Speicherstelle = 236 ->  Wie viele Werte = 29
Rücklese Wert: 3.21762514 Zeitbuffer = 1770034073 ->  Speicherstelle = 244 ->  Wie viele Werte = 30
Rücklese Wert: 3.30237508 Zeitbuffer = 1770034074 ->  Speicherstelle = 252 ->  Wie viele Werte = 31

Jetzt muss ich es wieder in Zeitformat bringen, dann habe ich fürs Erste gewonnen.

 // Zeit / Datum auslesen-------------------------------------------------------------------
  Speicher2 = Speicher;
  i2ceeprom.read(Speicher2, buffer, 4);
  memcpy((void *)&Zeitbuffer, buffer, 4);
  Serial.print(F(" Zeitbuffer = "));
  Serial.print(Zeitbuffer);

Das Lesen in einen Zwischenpuffer ist hier wahrscheinlich unnötig.

Sie können direkt in Zeitbuffer lesen, indem Sie dessen Adresse und Größe übergeben.

i2ceeprom.read(Speicher2, reinterpret_cast<uint8_t *>(&Zeitbuffer), sizeof Zeitbuffer);

Selbe Stelle!

void testprint(const uint32_t unixzeit)
{
  time_t t = unixzeit;
  tm tm;
  localtime_r(&t, &tm);
  tm.tm_year += 1900;
  tm.tm_mon += 1;
  // zaehler - Channel - Zeit in UXT - Zeit lesbar - Value in Digit - Value in V - nächste Adresse
  Serial << "  UXT: " << _WIDTH(uinxzeit, 8) << " # " \
         << _WIDTHZ(tm.tm_mday, 2) << '.' << _WIDTHZ((tm.tm_mon), 2) << '.' << (tm.tm_year) << " # " \
         << _WIDTHZ(tm.tm_hour, 2) << ':' << _WIDTHZ(tm.tm_min, 2) << ':' << _WIDTHZ(tm.tm_sec, 2) << " * " <<endl;
}

Und wo nehme ich die unixzeit her ? :roll_eyes:

Das sind bei Dir die vier Byte, die Du gerade eben aus dem FRAM wieder zurückgeholt hast, in #17 wird es als Parameter in die Funktion gegeben.
Bei Dir:

time_t now; // dasselbe wie Zeitbuffer

Bei @my_xy_projekt :

void testprint(const uint32_t unixzeit) 
...
  time_t t = unixzeit;
1 Like

testprint(Zeitpuffer);

1 Like