Datenlogger - String Buffer Grösse - Daten auf SD Karte speichern

Hallo,

ich habe ein kleines Problem. Ich möchte gerne mehrere Daten von meinem Datenlogger auf der Speicherkarte ablegen.

Die Ausgabe über den seriellen Monitor klappt. Aber die Daten auf der Speicherkarten werden nur zur Hälfte einer Zeile übertragen und danach nur noch Müll. So verhält sich das bei jeder Übertragung.

Wenn ich die Übertragung auf die Hälfte der Datenlänge beschränke (11 Datensätze) dann habe ich keine Fehler.

Hat das was mit der begrenzter Buffer Größe zu tun?

Hier ein Ausschnitt von meinem Quellcode:

[sup]
      String toWrite1 = "";      
      toWrite1 = String(ID) + " ";      
      toWrite1 += getStamp() + ";";     
      toWrite1 += dToStr(getTemp(0) + CALBO, 4, 1) + ";";      
      toWrite1 += dToStr(getTemp(1) + CALRA, 4, 1) + ";";     
      toWrite1 += dToStr(max1, 4, 1) + ";";     
      toWrite1 += dToStr(max2, 4, 1) + ";";   
      toWrite1 += dToStr(voltDC, 4, 1) + ";";
      toWrite1 += dToStr(currDC, 4, 1) + ";";
      toWrite1 += dToStr(instantPower, 5, 1) + ";";      
      toWrite1 += dToStr(energyDC, 6, 1) + ";";
      toWrite1 += dToStr(waterKwh, 6, 1) + ";";
      toWrite1 += dToStr(water / 1000, 7, 3) + ";";
      toWrite1 += dToStr(ac / 1000, 6, 1) + ";;";


//Serielle Ausgabe
    if (timeElapsedOutput > intervalOutput) {
      timeElapsedOutput = 0;  // Zeitschleife zurücksetzen
      Serial.println(toWrite1);
    }
    
//Speichern auf SD Karte 
    if (timeElapsedSD > intervalSD) {
      timeElapsedSD = 0;  // Zeitschleife zurücksetzen
      writeSD(toWrite1);
    }
    
      Serial.flush();
[/sup]

Bist Du sicher daß es kein RAM Problem ist, weil Du strings verwendest?
Grüße Uwe

Was kann man dagegen tun?

Auf die dämliche, unsinnige, ineffiziente, RAM-fressende String Klasse komplett verzichten. Wenn es denn unbedingt sein muss, verstehe wenigstens was du da eigentlich machst und wieso es die reserve() Funktion gibt:

Die String Klasse ist schlecht programmiert und hat kein "allocate ahead" wie bessere Implementierungen. Das heißt für jedes "+" (und wenn es nur ein char ist) wird realloc() aufgerufen und der Puffer vergrößert. Auf die Klasse sollte man einfach verzichten. Es gibt eigentlich kaum einen Grund dafür. Du meinst vielleicht, dass du etwas gewinnst wenn du den String für Serial und SD wiederverwendest, aber abgesehen von weniger Codezeilen ist das nicht der Fall.

Der SD Karte ist es vollkommen egal ob das alles in einem String steht, oder ob du oft hintereinander print() aufrufst. Der Code wird dadurch unschöner je nachdem wie man es programmiert, aber es ist wesentlich schneller und Speicher-schonender.

Um das etwas schöner und übersichtlicher zu machen gibt es die Streaming Lib,
http://arduiniana.org/libraries/streaming/
Damit hat man hat man C++ ähnliche Output Streams. Dabei wird lediglich der << Operator mit inline-Funktionen überladen, aber der Code der compiliert ist genau der gleiche. Wesentlich besser als diese unsäglichen print() Reihen die man sonst hat. Das sollte mit allem funktionieren, das von der Print Klasse ableitet ist. Also auch der SD Lib. Wirklich ausprobiert habe ich es damit nicht, aber es kompiliert.

Die Float Formatierung geht damit genauso wenn man _FLOAT davor schreibt:

float f = 1.233659;
Serial << _FLOAT(f, 4) << ";" << _FLOAT(f, 2) << endl;

Wobei das fast so aussiehst als ob du in dToStr() dtostrf() machst (wegen den zwei Paramtern). Da ist die String Klasse genauso überflüssig. Du kannst da einfach statt einem String einen char* auf den Puffer zurückgeben solange dieser global oder lokal static deklariert ist (aber nicht wenn er nur lokal nicht-static ist!).

Alternativ geht auch die SdFat Lib. Die hat noch umfangreichere Stream Funktionen. Ist aber nicht unbedingt nötig wenn es mit obiger Lib geht.

Hallo,

Du kennst ja bei Strings jedes Bit einzeln, wie man das so mitbekommt. :wink:

Macht es bei mir Sinn meine String Variablen in das Streaming Format abzuändern?

Mein RAM Verbrauch liegt noch knapp unter 2kB. Der Sketch ist allerdings schon 32kB groß. Geplant war das es am Ende auf einem Uno läuft. Vom RAM vielleicht noch machbar. Die Sketchgröße wäre aber schon zu goß. Hilft die Streaming-Methode RAM und Flash einzusparen? Sonst muß ich notgedrungen beim Mega2560 bleiben.

Wenn ich Dich richtig verstehe, macht es Sinn in Zukunft immer gleich das Streaming zuverwenden?

Edit:
Worauf ich hinaus ist. Kann man einfach zum Bsp.
Serial.println(F("Initialization done."));
durch
Serial << "Initialization done." << endl;
ersetzen und spart dadurch RAM und Rechenzeit?

Das gleiche mit meinem zusammengesetzten Stringbuffern bzw. char Arrays oder wie man das genau nennt?
z.Bsp.: snprintf(SD_Dateiname,sizeof(linebuf),"Y_%02d%02d%02d.txt", Jahre, Monat, Tag);

SDcard_Write_Taster_LcdSPI_DS1820_026.ino (29.6 KB)

Die Streaming Lib ändert nichts am Code gegenüber char Arrays! Die überlad wie gesagt nur den << Operator:

template<class T> 
inline Print &operator <<(Print &stream, T arg) 
{ 
  stream.print(arg); 
  return stream; 
}

Der Assembler Code sollte dabei identisch sein. Sie macht nur Ausgaben übersichtlicher und kompakter, da man nicht ständig Serial.print() schreiben muss und mehrere Ausgaben schön in einer Zeile schreiben kann.

z.B. das:

Serial << "Temp: " << temp << " Grad" << endl;

statt:

Serial.print("Temp: ");
Serial.print(temp);
Serial.println(" Grad");

Und wenn du das F() weglässt, landet der String wieder im RAM!

sprintf() hat den Vorteil, dass du Zahlen formatieren kannst. "%02d" sorgt dafür dass alle Ziffern zwei Zeichen breit sind. Das ist etwas du mit einer einfachen print() Anweisung nicht schaffst.

Was du da noch optimieren kannst, ist die _P Version und PSTR() zu verwenden:

snprintf_P(SD_Dateiname,sizeof(linebuf), PSTR("Y_%02d%02d%02d.txt"), Jahre, Monat, Tag);

Damit belegt der Format-String kein RAM mehr. Ähnlich wie das F() Makro für print()

Das bringt aber auch nicht viel wenn du generell schon an der Grenze bist.

Hier ist noch ein print() ohne F():

Serial.println("DS3231 I2C Busfehler");

Hallo,

Danke für die Erklärung.

Alles was ich im RAM einspare landet im Flash. Wenn ich also versuchen würde RAM einzusparen, wächst mein Flashverbrauch noch weiter über 32kB hinaus? Würde also bei mir als Bsp. keinen Sinn machen um noch alles auf einem Uno unterzubringen? Hab ich mal Pech gehabt. :slight_smile:

Die Strings landen im Flash und RAM wenn man keine Dinge wie F() Makro und PROGMEM verwendet. Wenn ja, belegen sie nur Flash.

Der AVR hat eine modifizierte Harvard Architektur. Das heißt Flash und RAM haben getrennte Adress-Räume. Und der Befehlsatz arbeitet generell mit Adressen im RAM und Register-Bereich. Um auf Flash zuzugreifen gibt es auf Assembler-Ebene nur ein paar begrenzte Befehle die Daten aus dem Flash ins RAM kopieren. Der AVR gcc Compiler umgeht dieses Problem indem die Strings beim Programmstart aus dem Flash ins RAM kopiert werden.

Bei PROGMEM Verwendung wird wird insofern etwas mehr Flash belegt als dass man immer noch die Daten bei Bedarf ins RAM kopieren muss. Dadurch entsteht etwas mehr Code. Aber die Strings selbst belegen genauso Flash wie vorher auch.

Sieht so aus also ob die die Grenzen des UNOs erreicht hast. Bei beiden Speicher Arten. Der RAM Verbrauch liegt auch an der SD Lib. Die hat glaube ich einen 512kB Puffer für die Sektoren der Karte.
Beim RAM kann man nicht mehr viel machen, und wenn du 32k Flash erreicht hast ist es mit dem Optimieren sowieso größtenteils vorbei.

Hallo,

vielen herzlichen Dank. Dann muß ich mir bei diesem Projekt um RAM Optimierung keine Gedanken mehr machen. Eine Sorge weniger. :wink: