Speicherfresser Problem mit CHAR Funktion

Hallo zusammen,

ich habe ein großes Problem mit dem Speicher.
Ich lasse mir auf einem LCD Display Datum und Uhrzeit anzeigen. (uRTCLib)
Hierzu habe ich mir zwei char Funktionen geschrieben, die mir die aktuellen Werte liefern.

Das läuft jede Sekunde und pro Durchlauf werden 24 Byte Speicher verwendet.
Nach fünf Minuten ist der Speicher voll und mein Mega 2560 hängt sich auf.

Wie bekomme ich es hin, dass der Speicher nicht jedes Mal neu verwendet wird.

Innerhalb von loop():

    if (printRTCEveryPeriod(1, DATE_FORMAT_EUROPEAN)) {
        printRTC(DATE_FORMAT_EUROPEAN);
        printRTCTemperature();
        Serial.println();
        lcd.setCursor(0, 2);
        lcd.print("                    ");
        lcd.setCursor(0, 2);
        lcd.print(date2lcd());
        lcd.setCursor(12, 2);
        lcd.print(time2lcd());

    }

Die beiden Funktionen für Datum und Uhrzeit lauten:

char* date2lcd() {
	char tDateString[10];
	sprintf_P(tDateString, PSTR("%02u.%02u.20%2u"), rtc.day(), rtc.month(), rtc.year());
	char* ret = (char*)malloc(10);
	return strcpy(ret, tDateString);
}

char* time2lcd() {
	char tTimeString[9];
	sprintf_P(tTimeString, PSTR("%02hhu:%02hhu:%02hhu"), rtc.hour(), rtc.minute(), rtc.second());
	char* ret = (char*)malloc(10);
	return strcpy(ret, tTimeString);
}

Gruß

Wo sind die free()?

Überlebenstipp: RAII

Oder nach Möglichkeit ganz auf dynamische Allokierungen verzichten

mach bitte mal einen vollständigen kompilierbaren Sketch mit Links auf alle verwendeten Libraries.

Da gibts sicher eine bessere Variante als du es bisher umgesetzt hast.

PS: Spricht irgendwas für die Verwendung von sprintf_P ? Hast das oft im Sketch? Weil nur wegen DD.MM.YYYY und HH:MM:SS würde ich das nicht auf einem Mega verwenden.

Das "nach Möglichkeit" kannst du zur Vereinfachung einfach streichen.
Mach tTimeString und tDateString static und liefere sie zurück.

Oder liefere einen char buf[11] als Parameter mit, dann brauchst du sogar nur insgesamt 11 byte, die abwechselnd mit Datum und Uhrzeit gefüllt werden, wenn du es mit dem RAM sparen übertreiben willst.
P.S.
Für "12.12.2001" reichen übrigens 10 byte nicht.

Mal ein Auszug aus meinem Tutorial, wie man das ohne sprintf & Co. machen kann:

int stunde = 3, minute = 29, sekunde = 7, tag = 7, monat = 8, jahr = 2016;

char puffer[20];

// Hängt die formatierte Uhrzeit an eine Zeichenkette an
void addZeit(char * ziel, int st, int mi, int se) {
char buf[9]; // 00:00:00
  buf[0] = (st / 10) + '0'; // Zehner
  buf[1] = (st % 10) + '0'; // Einer
  buf[2] = ':';
  buf[3] = (mi / 10) + '0';
  buf[4] = (mi % 10) + '0';
  buf[5] = ':';
  buf[6] = (se / 10) + '0';
  buf[7] = (se % 10) + '0';
  buf[8] = 0; // die abschließende 0
  strcat(ziel,buf);
} 
// Hängt das formatierte Datum an eine Zeichenkette an
void addDatum(char * ziel, int ta, int mo, int ja) {
char buf[11]; // 00.00.0000
  buf[0] = (ta / 10) + '0'; // Zehner
  buf[1] = (ta % 10) + '0'; // Einer
  buf[2] = '.';
  buf[3] = (mo / 10) + '0';
  buf[4] = (mo % 10) + '0';
  buf[5] = '.';
  buf[6] = (ja / 1000) + '0'; // Tausender
  ja = ja % 1000;             // Rest 3-stellig
  buf[7] = (ja / 100) + '0';  // Hunderter
  ja = ja % 100;              // Rest 2-stellig
  buf[8] = (ja / 10) + '0';   // Zehner
  buf[9] = (ja % 10) + '0';   // Einer
  buf[10] = 0; // die abschließende 0
  strcat(ziel,buf);
}

void setup() {
  Serial.begin(115200);
  Serial.println("start");
  strcpy(puffer,"Zeit: ");
  addZeit(puffer,stunde,minute,sekunde);
  Serial.println(puffer);
  strcpy(puffer,"Datum: ");
  addDatum(puffer, tag,monat,jahr);
  Serial.println(puffer);
}
void loop() {
}

Gruß Tommy

2 Likes

Nööö

Hallo,

vielen Dank für Eure Hinweise.
Ich habe die Funktionen nun wie folgt geändert und habe sogar noch ein paar wenige Bytes gespart.

char* date2lcd() {
	static char tDateString[10];
	sprintf_P(tDateString, PSTR("%02u.%02u.20%2u"), rtc.day(), rtc.month(), rtc.year());
	return tDateString;
}

char* time2lcd() {
	static char tTimeString[10];
	sprintf_P(tTimeString, PSTR("%02hhu:%02hhu:%02hhu"), rtc.hour(), rtc.minute(), rtc.second());
	return tTimeString;
}

Gruß

Ist immer noch zu kurz, das wurde Dir aber schon gesagt.

Gruß Tommy

Und dir damit gleich die nächsten Frikadellen ans Knie genagelt.
Unsinnig Speicher verplempert....
static, ohne Not.

Mag funktionieren, aber schön ist datt nich.

Na dann viel Spaß bei den Folgefehlern.
Du solltest Dir mal das von mir verlinkte Tutorial zu Gemüte führen, um die Grundlagen zu erlernen.

Gruß Tommy

Hi,

das stimmt, dass es bereits erwähnt wurde.

Ich überlege nur, warum das so sein soll.
Das Datum hat das Format 00.00.0000. Das sind 10 Zeichen.
Die müssten doch in ein 10 Zeichen char array passen. Oder was übersehe ich?

Zumindest funktioniert es und das Datum wird angezeigt.

Gruß

Ja.
Was wird hier erklärt.

Gruß Tommy

Es gibt sicher Kriterien, nach denen das nicht schön ist.

Ich finde es schön, dass sowas in der Arduino-Programmierumgebung mit ihren Einschränkungen (z.B. nur single thread) überhaupt geht :slight_smile:

liefere einen char buf[11] als Parameter mit

ist übrigens nicht übertrieben sparsam, wie ich vorhin schrieb, sondern (in unser beider Augen, richtig?) schöner.

Was ich definitiv seit Jahren lerne ist, dass Schön immer im Auge des Betrachters liegt und Code den der eine scheiße findet für einen anderen perfekt ist.
Wie auch in vielen anderen Dingen, gibt es wie immer viele Wege nach Rom.
Es wird auch weiterhin die Leute geben, die ihren Weg für richtig halten.
Da gibt es die volle Akzeptanz bei mir.

Die Lösung, die ich jetzt gewählt habe ist für mich der richtige Weg.
Ein Dreizeiler, der macht was er soll und auch noch ein paar Bytes weniger braucht, als der 11 Zeiler von Tommy56.

btw: Habe das Array auf 11 geändert. Keine Ahnung, warum mir das mit dem letzten Zeichen nicht wieder eingefallen ist.

Euch vielen Dank für die Denkanstöße.

struct RTC
{
  int   day()    {return 1;}
  int   month()  {return 2;}
  int   year()   {return 3;}
  int   hour()   {return 4;}
  int   minute() {return 5;}
  int   second() {return 6;}
};
RTC rtc;

template<size_t N> char *date2str(char (&buffer)[N], RTC &rtc)
{
  static_assert(N>10,"Buffer zu klein");
  snprintf_P(buffer, N, PSTR("%02u.%02u.20%02u"), rtc.day(), rtc.month(), rtc.year());
  return buffer;
}

template<size_t N> char *time2str(char (&buffer)[N], RTC &rtc)
{
  static_assert(N>8,"Buffer zu klein");
  snprintf_P(buffer, N, PSTR("%02hhu:%02hhu:%02hhu"), rtc.hour(), rtc.minute(), rtc.second());
  return buffer;
}


void setup()
{
  Serial.begin(9600);
}

void loop()
{
  char buf[20];
  Serial.println(date2str(buf, rtc));
  Serial.println(time2str(buf, rtc));
  delay(5000);
}

nur wenn du snprintf sonst auch noch in Verwendung hast. Ansonsten ist die Version von @Tommy56 sicher sparsamer.

Ich würde ja Tommy und Combie kombinieren:

/*
  Speicherfresser Problem

   https://forum.arduino.cc/t/speicherfresser-problem-mit-char-funktion/1146860/16
   2023-07-11 by noiasca
*/

struct RTC {
  int   day()    {return 1;}
  int   month()  {return 2;}
  int   year()   {return 23;}  // was gibt dein RTC wirklich retour?
  int   hour()   {return 4;}
  int   minute() {return 5;}
  int   second() {return 6;}
};
RTC rtc;

template<size_t N> char *date2str(char (&buffer)[N], RTC &rtc) {
  static_assert(N>10,"Buffer zu klein");
  buffer[0] = (rtc.day() / 10) + '0'; // Zehner
  buffer[1] = (rtc.day() % 10) + '0'; // Einer
  buffer[2] = '.';
  buffer[3] = (rtc.month() / 10) + '0';
  buffer[4] = (rtc.month() % 10) + '0';
  buffer[5] = '.';
  buffer[6] = '2'; // Tausender
  buffer[7] = '0';  // Hunderter
  buffer[8] = (rtc.year() / 10) + '0';   // Zehner
  buffer[9] = (rtc.year() % 10) + '0';   // Einer
  buffer[10] = 0; // die abschließende 0
  return buffer;
}

template<size_t N> char *time2str(char (&buffer)[N], RTC &rtc) {
  static_assert(N>8,"Buffer zu klein");
  buffer[0] = (rtc.hour() / 10) + '0'; // Zehner
  buffer[1] = (rtc.hour() % 10) + '0'; // Einer
  buffer[2] = ':';
  buffer[3] = (rtc.minute() / 10) + '0';
  buffer[4] = (rtc.minute() % 10) + '0';
  buffer[5] = ':';
  buffer[6] = (rtc.second() / 10) + '0';
  buffer[7] = (rtc.second() % 10) + '0';
  buffer[8] = 0; // die abschließende 0
  return buffer;
}

void setup() {
  Serial.begin(15200);
}

void loop() {
  char buf[11];
  Serial.println(date2str(buf, rtc));
  Serial.println(time2str(buf, rtc));
  delay(5000);
}

Solange ich nicht in Flashmangel stolpere würde ich den Fokus auf Wartbarkeit legen.
Zudem bin faul....

Am Rande:
Für ungenutzten Speicher gibts kein Geld zurück.

Immerhin scheint dir ja die Absicherung gegen unwillkommene Abschmierer gefallen zu haben.
Wenn auch der zweite Schutz, gegen Fehler/Irrtümer beim zusammenstoppeln, dabei unter den Tisch gefallen ist.

Nicht wirklich.
Ich habe mir die letzten Tage einfach mal Gedanken gemacht, wie ich es mit den ganzen Anregungen hier am Besten hinbekommen kann.

Ich bin jetzt auf die RTClib gewechselt und löse es folgend:

char germanDate[11];
formatGermanDate(rtc.now(), germanDate);

void formatGermanDate(DateTime dateTime, char* formattedDate) {
	char d[3]; char m[3]; char y[5];
	sprintf(d, "%02d", dateTime.day());
	sprintf(m, "%02d", dateTime.month());
	sprintf(y, "%04d", dateTime.year());
	snprintf(formattedDate, 11, "%s.%s.%s", d, m, y);
}

Von hinten durch die Brust ins Auge!

Aber wenn es funktioniert......
(kann es ja nicht böse sein)