DS18B20 Datenlogger auf SD

Hallo!

Ich versuche im Moment einen Datenlogger mit einem Arduino Nano Clone zu bauen.
Er soll die Daten alle 15 Minuten (zum Testen erstmal alle 15 Sek.) vom Sensor auslesen, und zusammen mit einem Zeitstempel vom DS3231 RTC Modul auf eine Micro-SD Karte schreiben.

Die Hardware funktioniert in einzelnen Teilen (Temp. Sensor, RTC, SD Karte beschreiben), der komplette Code von mir aber nicht.

Hier mein bisheriger Code:

/*
 Datalogger mit DS18B20 an D5,
  -Timestamp von DS3231 RTC
  -Speichern auf SD an I2C

 Version vom 18.01.2017 (Funktioniert ne)
 */

#include <Wire.h>
#include <DS3231.h>
#include <SD.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 8

DS3231 clock;
RTCDateTime dt;

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

const int chipSelect = 10;
String w; //minute
int w1;
String dataString = "";
float t;  //für temp.
float z;
float t2;

void setup(){
  pinMode(chipSelect, OUTPUT);
  Serial.begin(9600);
  sensors.begin();
  Serial.print("Initializing SD card... ");
  
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("done");
  
  Serial.print("Initialize DS3231... ");
  clock.begin();
  Serial.println("done");

  // Set sketch compiling time
  // clock.setDateTime(__DATE__, __TIME__);

  // Set from UNIX timestamp
  // clock.setDateTime(1397408400);

  // Manual (YYYY, MM, DD, HH, II, SS
  // clock.setDateTime(2014, 4, 13, 19, 21, 00);
}

void loop(){
  dt = clock.getDateTime();
  w=(clock.dateFormat("s", dt));
  w1=w.toInt();
  
  if (w1 == 0 or w1 == 15 or w1 == 30 or w1 == 45) {
    
    String dataString = "";
    
    //get temp and round:
    sensors.requestTemperatures();
    t = (sensors.getTempCByIndex(0));
    z = round(t*10);
    t2 = z/10;

    //Write to String:
    dataString += String(t2);

    //Write to SD:
    File dataFile = SD.open("datalog.txt", FILE_WRITE);

    // if the file is available, write to it:
    if (dataFile) {
      dataFile.print(dataString);
      dataFile.print(" ");
      dataFile.println(clock.dateFormat("d-m-Y H:i:s", dt));
      dataFile.flush();
      //dataFile.close();
      // print to the serial port too:
      Serial.print(dataString);
      Serial.print(" at: ");
      Serial.println(clock.dateFormat("d-m-Y H:i:s", dt));
    }  
    // if the file isn't open, pop up an error:
    else {
      Serial.println("error opening datalog.txt");
    }
    delay(1000);
  }
}

Die Temperatur wird vom Sensor richtig ausgelesen, Die Uhrzeit vom RTC auch.
Die seriellen Ausgaben vom Programm sind auch so wie ich sie haben will.

Allerdings bekomme ich nach spätestens 8 Ausgaben nur noch die Meldung "error opening datalog.txt".
Wenn ich den Controller resette, passiert nach spätestens 8 Durchläufen wieder exakt das gleiche.

Auf der SD Karte sind dann so 2-3 Ausgaben richtig geschrieben (Temperatur, Uhrzeit, Datum).

Warum stürzt das Programm immer ab? Und warum wird nicht alles auf die SD geschrieben?

Wenn ich den gleichen Code verwende, aber die Zeile

"dataFile.flush();"

durch

"dataFile.close();"

ersetze, Stürzt das Programm nicht ab, allerdings ist die Datalog.txt auf der SD komplett leer.

Ich habe auch schon probiert die abfrage der minute zu entfernen und einfach nen delay gesetzt, dann wurde die SD beschrieben, manchmal fehlte aber einfach der Zeitstempel, oder wurde durch sinnlose Sonderzeichen ersetzt.

Dieser Beispielcode (nur mit Uhr/Zeitstempel hinzugefügt) hier funktioniert übrigens ohne Probleme:

/*
 Abfrage A0-2, serielle Ausgabe, speichern auf SD
 - Timestamp RTC

 Version vom 14.12.16
 */

#include <Wire.h>
#include <DS3231.h>
#include <SD.h>

const int chipSelect = 10;

DS3231 clock;
RTCDateTime dt;

void setup(){
  pinMode (10, OUTPUT);
  Serial.begin(9600);
  Serial.print("Initializing SD card... ");
  
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("done");
  // Initialize DS3231
  Serial.print("Initialize DS3231... ");
  clock.begin();

  // Set sketch compiling time
  // clock.setDateTime(__DATE__, __TIME__);
  Serial.println("done");

  // Set from UNIX timestamp
  // clock.setDateTime(1397408400);

  // Manual (YYYY, MM, DD, HH, II, SS
  // clock.setDateTime(2014, 4, 13, 19, 21, 00);
}

void loop(){
  String dataString = "";
  dt = clock.getDateTime();

  // read three sensors and append to the string:
  for (int analogPin = 0; analogPin < 3; analogPin++) {
    int sensor = analogRead(analogPin);
    dataString += String(sensor);
    if (analogPin < 2) {
      dataString += " "; 
    }
  }
  File dataFile = SD.open("datalog.txt", FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile) {
    dataFile.print(dataString);
    dataFile.print(" ");
    dataFile.println(clock.dateFormat("d-m-Y H:i:s", dt));
    dataFile.close();
    // print to the serial port too:
    Serial.print(dataString);
    Serial.print(" at: ");
    Serial.println(clock.dateFormat("d-m-Y H:i:s", dt));
  }  
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening datalog.txt");
  }
  delay(15000);
}

Das ganze ergibt in meinen Augen einfach keinen Sinn, ich bitte um Hilfe!

Edit:
Das ganze funktioniert mittlerweile, hab den Code unten noch mal geposted!

Ich habe das alles inkl dht22 schon fertig :wink:

Das projekt ist aber nichts für anfänger zum lernen.

Interesse meinen sketch/pinbelegung 1:1 zu übernehmen?

Hallo,

du solltest nur dataFile.close() verwenden.
Nicht dataFile.flush() alleine.
Also entweder dataFile.flush() und dataFile.close() hinterher
oder
nur dataFile.close(), weil flush in close enthalten ist.
Also brauchst du nur dataFile.close(). Unten machst du das - oben nicht.

Du kannst deinen Code gern hier posten mde110, würde sicher helfen!

@Doc_Arduino hab ich sogar probiert beide zu verwenden, das gleiche Ergebnis wie wenn ich nur .close() verwende, die Datalog bleibt leer.

Du arbeitest im Loop mit der Klasse String. Das ist nicht gut für Deinen Arbeitsspeicher und es kann sein, dass dort Dein Problem liegt.

Befasse Dich mal mit Zeichenketten in C, also char-Arrays.

Gruß Tommy

Hallo,

ich sehe noch einen Unterschied zu meinem Datalogger.

ich deklariere und initialisiere vor setup
SdFile root;
File myFile;
char _RtcDateTimeBuf[30];

den String RtcDateTimeBuf setze ich wie folgt zusammen:

// Datum und Zeit für Ausgabe formatieren und in globalen String 'RtcDateTimeBuf' speichern
 snprintf_P(RtcDateTimeBuf,30, PSTR("%02d.%02d.%04d ; %02d:%02d:%02d"),_Tag,_Monat,_Jahr,_Stunde,_Minute,_Sekunde);

die SD Schreibfunktion sieht dann so aus (gekürzt)

boolean WriteDatatoFileSdCard()
{
   boolean error = true;                   // Fehlerstatus "YES" setzen   
      
   if (!SD.begin(_CS_SDcard))
     {
      Serial.println(F("Write SD.begin Fehler!"));
      return error = true;                 // mit Fehlerstatus "true" abbrechen
     }
     
   myFile = SD.open(_SD_Dateiname, FILE_WRITE);
   if (myFile)
     {
      Serial.print(F("writing ... "));  
      myFile.print(_RtcDateTimeBuf);        // RTC Datum und Zeit auf SD-Karte speichern    
      ...
      myFile.close();
      Serial.println(F("writing done"));
      
      return error = false;             // Fehlerstatus setzen, alles fehlerfrei
     } 
      else {
            Serial.println(F("SD error writing file"));  // if the file didn't open, print an error:
            return error = true;                         // Fehlerstatus setzen, es gab ein Problem
           }  
}  // Ende schreiben in Datei auf SD-Karte

Wow, die Lösung war extrem einfach, ziemlich dumm das ich da nicht drauf gekommen bin.
Ich brauche in dem ganzen Programm gar keine String Variable, die ist komplett sinnlos.
Wenn ich die Variable der gerundeten Temperatur einfach auf die SD und in den seriellen Monitor schreibe, funktioniert alles wie gewollt.

Hier der fertige, nun funktionierende Code:

/*
 Datalogger mit DS18B20 an D5,
  -Timestamp von DS3231 RTC
  -Speichern auf SD an I2C

 Version vom 18.01.2017
 */

#include <Wire.h>
#include <DS3231.h>
#include <SD.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 8

DS3231 clock;
RTCDateTime dt;

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

const int chipSelect = 10;
String w; //minute
int w1;
float t;  //für temp.
float z;
float t2;

void setup(){
  pinMode(chipSelect, OUTPUT);
  Serial.begin(9600);
  sensors.begin();
  Serial.print("Initializing SD card... ");
  
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  Serial.println("done");
  
  Serial.print("Initialize DS3231... ");
  clock.begin();
  Serial.println("done");

  // Set sketch compiling time
  // clock.setDateTime(__DATE__, __TIME__);

  // Set from UNIX timestamp
  // clock.setDateTime(1397408400);

  // Manual (YYYY, MM, DD, HH, II, SS
  // clock.setDateTime(2016, 1, 18, 21, 43, 00);
}

void loop(){
  dt = clock.getDateTime();
  w=(clock.dateFormat("s", dt));
  w1=w.toInt();
  
  if (w1 == 0 or w1 == 15 or w1 == 30 or w1 == 45) {
    
    //get temp and round:
    sensors.requestTemperatures();
    t = (sensors.getTempCByIndex(0));
    z = round(t*10);
    t2 = z/10;

    //Write to SD:
    File dataFile = SD.open("datalog.txt", FILE_WRITE);

    // if the file is available, write to it:
    if (dataFile) {
      dataFile.print(t2);
      dataFile.print(" ");
      dataFile.println(clock.dateFormat("d-m-Y H:i:s", dt));
      //dataFile.flush();
      dataFile.close();
      // print to the serial port too:
      Serial.print(t2);
      Serial.print(" at: ");
      Serial.println(clock.dateFormat("d-m-Y H:i:s", dt));
    }  
    // if the file isn't open, pop up an error:
    else {
      Serial.println("error opening datalog.txt");
    }
    delay(1000);
  }
}

Wenn man nun in der Zeile "w=(clock.dateFormat("s", dt));" das s durch ein i ersetzt, frägt er alle 15 min. ab und nicht alle 15 sek. Den delay muss man auf mindestens 60000 verlängern, damit er den Wert auch nur 1x abspeichert wenn die gewünschte Minute erreicht ist.

Prima, dass es funktioniert und danke für die Rückmeldung.

Gruß Tommy

@Flo_BZ:

Ich teste im Moment auch im Zusammenhang mit einem Datalogging Projekt einige libs aus und habe mal testweise deinen Sketch geladen. Momentan teste ich noch mit Uno und Nano, ich fürchte aber, dass ich mit dem Speicherplatz nicht hinkommen werde und auf Mega umsteigen muss, da zur Zeit nur etwa die Hälfte an Sensoren und Aktoren verdrahtet ist aber schon über 2/3 des Platzes belegt ist. Daher suche ich nach den jeweils kleinstmöglichen libraries, um Speicher einzusparen.

Beim Verify mit "deiner" DS3231 kriege ich aber einen Haufen errors, die darauf hindeuten, dass meine RTC library (RinkyDinky_DS3231) eine andere ist, als die, die du benutzt.

Die Fehlermeldungen drehen sich alle um einen zentralen Punkt:

error: no matching function for call to 'DS3231::DS3231()'

Frage also: welche RTC library benutzt du in deinem sketch?

Ich habe mir mal (ich weiß nicht mehr woher) folgende Codeteile für eine DS3231 in einen Sketch kopiert. Da braucht man keine Lib mehr.
Den Wochentag könnte man noch über ein Array (evtl. im PROGMEM) vereinfachen.

#include "Wire.h"
#define DS3231_I2C_ADDRESS 0x68
// Convert normal decimal numbers to binary coded decimal

uint32_t oldMillis;

byte decToBcd(byte val)
{
  return( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return( (val/16*10) + (val%16) );
}
void setup()
{
  Wire.begin();
  Serial.begin(115200);
  // set the initial time here:
  // DS3231 seconds, minutes, hours, day, date, month, year
  //setDS3231time(00,16,14,4,24,8,16);
}
void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte
dayOfMonth, byte month, byte year)
{
  // sets time and date data to DS3231
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set next input to start at the seconds register
  Wire.write(decToBcd(second)); // set seconds
  Wire.write(decToBcd(minute)); // set minutes
  Wire.write(decToBcd(hour)); // set hours
  Wire.write(decToBcd(dayOfWeek)); // set day of week (1=Sunday, 7=Saturday)
  Wire.write(decToBcd(dayOfMonth)); // set date (1 to 31)
  Wire.write(decToBcd(month)); // set month
  Wire.write(decToBcd(year)); // set year (0 to 99)
  Wire.endTransmission();
}
void readDS3231time(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
  Wire.beginTransmission(DS3231_I2C_ADDRESS);
  Wire.write(0); // set DS3231 register pointer to 00h
  Wire.endTransmission();
  Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
  // request seven bytes of data from DS3231 starting from register 00h
  *second = bcdToDec(Wire.read() & 0x7f);
  *minute = bcdToDec(Wire.read());
  *hour = bcdToDec(Wire.read() & 0x3f);
  *dayOfWeek = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month = bcdToDec(Wire.read());
  *year = bcdToDec(Wire.read());
}
void displayTime()
{
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  // retrieve data from DS3231
  readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month,
  &year);
  // send it to the serial monitor
  Serial.print(hour, DEC);
  // convert the byte variable to a decimal number when displayed
  Serial.print(":");
  if (minute<10)
  {
    Serial.print("0");
  }
  Serial.print(minute, DEC);
  Serial.print(":");
  if (second<10)
  {
    Serial.print("0");
  }
  Serial.print(second, DEC);
  Serial.print(" ");
  Serial.print(dayOfMonth, DEC);
  Serial.print("/");
  Serial.print(month, DEC);
  Serial.print("/");
  Serial.print(year, DEC);
  Serial.print(" Day of week: ");
  switch(dayOfWeek){
  case 1:
    Serial.println("Sunday");
    break;
  case 2:
    Serial.println("Monday");
    break;
  case 3:
    Serial.println("Tuesday");
    break;
  case 4:
    Serial.println("Wednesday");
    break;
  case 5:
    Serial.println("Thursday");
    break;
  case 6:
    Serial.println("Friday");
    break;
  case 7:
    Serial.println("Saturday");
    break;
  }
}
void loop() {
  if (millis() - oldMillis >= 1000) {
    oldMillis = millis();  
    displayTime(); // display the real-time clock data on the Serial Monitor,
  }
}

Evtl. kann man da noch RAM sparen.

Gruß Tommy

@rpt007: Ich weiß ehrlich gesagt nicht genau welche DS3231 library ich hier benutze, aber sie müsste bestimmt ziemlich einfach zu finden sein wenn du "DS 3231 library" oder ähnliches googlest

Leider ist das genau das Problem, was im übrigen auch für die Unzahl an gleichbenannten lcd libraries gilt: Ich habe mindestens schon 2 gleichbenannte DS3231 libraries, die aber, wenn man sich die inneren Strukturen und Definitionen anschaut, unterschiedlich aufgebaut sind.

@Tommy56 Ansatz finde ich gut, da lässt sich sicher einiges an RAM einsparen, wenn man nur die Teile direkt als Funktionen aus den libs direkt in den sketch kopiert, die man auch tatsächlich braucht. Mal sehen, ob ich das auch mit den anderen libs machen kann, die ich zur Zeit benutze.

Bin aber kein gelernter Programmierer und muss da vorsichtig sein, was ich weglassen kann und was nicht.

Hallo,

kompiliert der Compiler nicht nur das was er daraus benötigt? Ich denke der ist so schlau.
Kannst ja mal eine Lib mit "Müll" vollstopfen und den RAM Verbrauch vergleichen.

Muss dazu sagen, auch ich nutze nur Funktionen für meine RTC und keine Lib. Da sehe ich was ich mache und wie ich das mache.
Weil zum auslesen braucht man ja nicht viele Zeilen Code.

@Flo_BZ:
Du hast die gleichnamige DS3231 Bibliothek von Korneliusz Jarzebski.

Ich hatte die von Adafruit geladen, daher kamen bei mir die Fehlermeldungen. Mir ging es u.a. darum, die Genauigkeit des eingebauten Temperatursensors des RTC-Moduls im Vergleich zum parallel angeschlossenen BME280 zu testen.

Zu den Bibliotheken habe ich mal eine kleine Übersicht gemacht:

Gleichnamige bzw. fast gleichnamige nachfolgend:

http://www.rinkydinkelectronics.com/library.php?id=73

und alle die, die ich nicht gefunden habe.
Kein Wunder, wenn viele Diskussionen im Forum köstlich aneinander vorbeigehen und es oft daran liegt, dass man gleichnamige, aber nicht gleich funktionierende Bibliotheken geladen hat. Neben der DS3231 sind die vielen lcd Bibs ein weiteres Beispiel.

Gibt es eigentlich keine Ideen, wie man (auf Sicht) diese Konfusion der Namensgleichheit von Bibliotheken auflösen kann?

Gibt es eigentlich keine Ideen, wie man (auf Sicht) diese Konfusion der Namensgleichheit von Bibliotheken auflösen kann?

Du bist ein Witzbold. Das würde nicht zur Arduino-Philosophie passen. Es verbietet nichtmal keiner, die Schnittstellen einer Library von einer Version zu anderen zu ändern. Wenn arduino.cc das selber macht, würde ich ihnen auch nicht zugestehen, das bei anderen zu reglementieren. github kümmert sich da nicht drum, und das ist gut so. Wenn man als Referenz die (eindeutige) URL nähme, wo etwas gefundenes herkommt, wäre man schon ein gutes Stückchen weiter.

Verwende nur Software, die den Stempel "Approved by michael_x" trägt. Da findest du hoffentlich keine einzige, hast also keine Konflikte, und das Problem ist gelöst. :wink: (smiley nur für alle Fälle)

Doc_Arduino:
...nutze nur Funktionen für meine RTC und keine Lib. Da sehe ich was ich mache und wie ich das mache. Weil zum auslesen braucht man ja nicht viele Zeilen Code.

Das sollte übrigens auch mehr beachtet werden! Mehr als Wire.h braucht man eigentlich nicht.

Ich weiß, das war ein wenig provokativ, so etwas zu erwarten :slight_smile:
Vor einem Jahr war ich Einsteiger in die Programmier- und Arduino-Welt; seinerzeit hätte es sehr geholfen, einige Frustrationen und Irrwege zu vermeiden, wenn ich gewusst hätte, dass Bibliotheken beliebig oft unter gleichem Namen existieren (können). Auch die seinerzeit gekauften Bücher und Tutorials sind da teilweise auch nicht eindeutig, aus welcher Quelle die libs stammen.

Wenn man als Referenz die (eindeutige) URL nähme, wo etwas gefundenes herkommt, wäre man schon ein gutes Stückchen weiter.

Genau das mache ich seit einiger Zeit, denn man glaubt ja nicht, was man nach kurzer Zeit selbst alles vergisst, woher man das eine oder andere her hat.

Wie oben gesagt, bin ich kein gelernter Programmierer und tue mich noch etwas schwer mit dem einen oder anderen Verständnis bzgl. C oder C++. Aber den Ansatz, nur einzelne Funktionen aus einer Bibliothek zu nehmen, werde ich (mit aller Vorsicht - blindes copy/paste ist ohne Verständnis des codes keine Option) weiterverfolgen.

rpt007:
Aber den Ansatz, nur einzelne Funktionen aus einer Bibliothek zu nehmen, werde ich (mit aller Vorsicht - blindes copy/paste ist ohne Verständnis des codes keine Option) weiterverfolgen.

Da gebe ich Dir recht. Man sollte grundlegend verstehen, was man tut. (Den Rest kann man durch Tests herausfinden :wink: )

Gerade bei der DS3231 ist die Funktion aber ziemlich einfach zu verstehen. Ich brauche nicht 5 Varianten, wie ich die Uhr stellen kann. Mir reicht die eine, die für mich am einfachsten ist (und sich in diesem Fall sich auch gut zur Synchronisation mit NTP eignet).

Gruß Tommy

und sich in diesem Fall sich auch gut zur Synchronisation mit NTP eignet

Genau das ist super, da ich in einem größeren Projekt voraussichtlich diese Sync einsetzen werde.
Ich taste mich gerade an die für mich noch unberührten Arduino-Gebiete heran.

Mal sehen, was mir da noch alles quer kommt. Die SD-Kartengeschichte habe ich jetzt (fast) im Griff.
Da scheinen die Macher der IDE und included Bibliotheken ab 1.6.9 bis einschl. 1.8.0 einfach Mist gebaut zu haben; ab 1.8.1 und SD 1.11 scheinen die Probleme (bis zur nächste Verschlimmbesserung) weg zu sein.