SD-Karte beschreiben

Hallo Allerseits!

Bräuchte wiedermal euren Rat.

folgendes: ich möchte Temperatur aufzeichnen und auf SD-Karte (Datalogging Shield)
Ich habe ein Codeschnipsel für das Aufzeichen von 3 Analogwerten. Dazu wird ein String gemacht, und in jeder Zeile steht eine Messung. Das funktioniert gut.

{
  // make a string for assembling the data to log:
  String dataString = "";

  // 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 += ","; 
    }
  }

  dataFile.print(dataString);

Ich erhalte meine Temeperatur allerdings über einen DS18S20 (I2C). Die Ausgabe über den Serialport funktioniert einwandfrei. Dazu verwende ich folgenden Code:

#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into pin 3 on the Arduino
#define ONE_WIRE_BUS 3

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);
// Address of DS18S20
DeviceAddress Tempsensor = { 0x10, 0x89, 0x14, 0x90, 0x02, 0x08, 0x00, 0x3C };

void setup(void)
{
  // start serial port
  Serial.begin(9600);
  // Start up the library
  sensors.begin();
  // set the resolution to 10 bit
  sensors.setResolution(Tempsensor, 10);
}

void printTemperature(DeviceAddress deviceAddress)
{
  float tempC = sensors.getTempC(deviceAddress);
  if (tempC == -127.00) {
    Serial.print("Error getting temperature");
  } else {
    Serial.print("C: ");
    Serial.print(tempC);
    Serial.print(" F: ");
    Serial.print(DallasTemperature::toFahrenheit(tempC));
  }
}

void loop(void)
{ 
  delay(2000);
  sensors.requestTemperatures();
  Serial.print("Temperatur betraegt: ");
  printTemperature(Tempsensor);
  Serial.print("\n\r\n\r");
}

Wie schaffe ich es nun, den Temperaturwert des DS18S20 anstatt der Analogwerte auf die SD-Card zu schreiben? Hat vlt. jmd. bereits einen fertigen code, den er mir zur Verfügung stellen würde?

In weiterer Folge möchte ich bei jeder Messung die Uhrzeit dazu schreiben und soll dann so aussehen:
z.B.:
20:50:31h, 23,67°C,

Bin für eure Hilfe sehr dankbar, wenn an codes, Bilder oder ähnliches gebraucht wird, einfach schreiben.

Glück auf!
Bernhard

Zunächst die SD-Karte initialisieren, wie im "readWrite" Beispiel der SD beschrieben:

#include <SD.h>

File myFile;

void setup()
{
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }


  Serial.print("Initializing SD card...");
  // On the Ethernet Shield, CS is pin 4. It's set as an output by default.
  // Note that even if it's not used as the CS pin, the hardware SS pin 
  // (10 on most Arduino boards, 53 on the Mega) must be left as an output 
  // or the SD library functions will not work. 
   pinMode(10, OUTPUT);
   
  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
  
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  myFile = SD.open("test.txt", FILE_WRITE);
  
  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
	// close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
  
  // re-open the file for reading:
  myFile = SD.open("test.txt");
  if (myFile) {
    Serial.println("test.txt:");
    
    // read from the file until there's nothing else in it:
    while (myFile.available()) {
    	Serial.write(myFile.read());
    }
    // close the file:
    myFile.close();
  } else {
  	// if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
}

void loop()
{
	// nothing happens after setup
}

Danach kannst Du den Wert aus "tempC" schreiben:

// if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println(tempC);
	// close the file:
    myFile.close();
    Serial.println("done.");

emsig:
dataString += String(sensor);

String-Objekte sind nichts für echte Programmierer.
Und für die Verwendung auf Mikrocontrollern mit stark begrenztem RAM-Speicher schon gar nicht.

Als C-Programmierer verwendet man besser C-Strings/char-Arrays zur Verarbeitung von Zeichenketten.

Das ist effektiver, spart RAM-Speicher und bietet viel mächtigere Verarbeitungsfunktionen als die doch arg beschränkten String-Objekte. Vor allem, da alle Stringfunktionen der AVR libc mit C-/Strings/char-Arrays arbeiten und nicht mit String-Objekten. Inkompatibel zur AVR Standard-Library, ineffektiv, RAM-speicherverschwendend: String-Objekte haben eigentlich nur Nachteile gegenüber einer C-typischen Zeichenkettenverarbeitung.

Darüber, wie man mit einem solchen Objekt-Gewürge eine vernünftige Formatierung hinbekommt, mag ich nicht mal nachdenken.

Die Standard-Formatierungsfunktion für Gleitkommazahlen in der AVR libc heißt " dtostrf()":
http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#ga060c998e77fb5fc0d3168b3ce8771d42

Du kannst aber auch jederzeit "print()" für die Ausgabe von Gleitkommazahlen verwenden.

Danke für die Infos!

Ich hab mittlerweile einen fixfertigen code gefunden, der in etwa meinen Vorstellungen entspricht. Ich möchte es aber (mehr oder weniger) selber schaffen.

Ich hab mir das Beispielsketch SD-write/rewrite zu gemüte geführt und versucht meine Temperatur aufzuzeichen... doch irgendwie ist da noch ein Haken drinnen. Der Code sieht mittlerweile so aus:

/*
  SD card datalogger
 
 This example shows how to log data from three analog sensors 
 to an SD card using the SD library.
 	
 The circuit:
 * SD card attached to SPI bus as follows:
 ** UNO:  MOSI - pin 11, MISO - pin 12, CLK - pin 13, CS - pin 4 (CS pin can be changed)
  and pin #10 (SS) must be an output
 ** Mega:  MOSI - pin 51, MISO - pin 50, CLK - pin 52, CS - pin 4 (CS pin can be changed)
  and pin #52 (SS) must be an output
 ** Leonardo: Connect to hardware SPI via the ICSP header
 		Pin 4 used here for consistency with other Arduino examples
 
 created  24 Nov 2010
 modified 9 Apr 2012 by Tom Igoe
 
 This example code is in the public domain.
 	 
 */

#include <Wire.h>
#include <OneWire.h>
#include <SD.h>
#include "RTClib.h"
#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into pin 3 on the Arduino
#define ONE_WIRE_BUS 7

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);
// On the Ethernet Shield, CS is pin 4. Note that even if it's not
// used as the CS pin, the hardware CS pin (10 on most Arduino boards,
// 53 on the Mega) must be left as an output or the SD library
// functions will not work.
const int chipSelect = 10;

File dataFile;

DeviceAddress insideThermometer = { 0x10, 0x89, 0x14, 0x90, 0x02, 0x08, 0x00, 0x3C };

void setup()
{
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }


  Serial.print("Initializing SD card...");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(SS, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    while (1) ;
  }
  Serial.println("card initialized.");
  
  // Open up the file we're going to log to!
  dataFile = SD.open("datalog.txt", FILE_WRITE);
  if (! dataFile) {
    Serial.println("error opening datalog.txt");
    // Wait forever since we cant write data
    while (1) ;
  }
  sensors.begin();
  // set the resolution to 10 bit (good enough?)
  sensors.setResolution(insideThermometer, 10);
}

void printTemperature(DeviceAddress deviceAddress)
{
  float tempC = sensors.getTempC(deviceAddress);
  if (tempC == -127.00) {
    Serial.print("Error getting temperature");
  } else {
    Serial.print("C: ");
    Serial.print(tempC);
    Serial.print(" F: ");
    Serial.print(DallasTemperature::toFahrenheit(tempC));
  }
}
void loop()
{
  Serial.print("Getting temperatures...\n\r");
  sensors.requestTemperatures();
  
  Serial.print("Inside temperature is: ");
  printTemperature(insideThermometer);
  Serial.print("\n");
  
  dataFile.print("Inside temperature is: ");
  printTemperature(insideThermometer);
  dataFile.print("\n");
 
  
  // The following line will 'save' the file to the SD card after every
  // line of data - this will use more power and slow down how much data
  // you can read but it's safer! 
  // If you want to speed up the system, remove the call to flush() and it
  // will save the file only every 512 bytes - every time a sector on the 
  // SD card is filled with data.
  dataFile.flush();
  
  // Take 1 measurement every 500 milliseconds
  delay(500);
}

Seriel siehts dann so aus:

nur mit dem schreiben auf die SD Karte happerts noch:

kann mir bitte jemand sagen, warum ich den Wert von "insideTemparature" nicht auf die SD Karte bekomme?

Him

soweit ich das erstmal sehe, schreibst Du die Zahlen immer seriell raus, nicht auf die Karte!?
Vielleicht irre ich mich da auch.
Bin nur Anfänger.

Gruß Joe

JoeDorm:
soweit ich das erstmal sehe, schreibst Du die Zahlen immer seriell raus, nicht auf die Karte!?

Du hast recht. Die printTemperature-Funktion schickt die Daten nur über die serielle Schnittstelle raus. Man könnte also entweder zwei Funktionen basteln:

  • printTemperatureSerial
  • printTemperatureSD

oder eine Funktion getTemperature mit Rückgabewert, der dann in deiner loop() jeweils als serielle Ausgabe und auf der SD-Karte landet.

emsig:
Wie schaffe ich es nun, den Temperaturwert des DS18S20 anstatt der Analogwerte auf die SD-Card zu schreiben? Hat vlt. jmd. bereits einen fertigen code, den er mir zur Verfügung stellen würde?

Anbei ein vollständiges Codegerüst für einen Fake-Datenlogger, der sich ausgehend von der Uhrzeit, zu der das Programm kompiliert wurde, die Zeit weiterzählt und alle 10 Sekunden drei Zufallswerte als Temperaturen zuweist und dann in einer Datei loggt.

Die zentralen beiden Funktionen des Loggers sind:

  1. void logTemperatures(float t1, float t2, float t3)
    In dieser Funktion wird in der Variablen "logline" die auszugebende Zeile mit den Logwerten zusammengebastelt und dann die zweite Funktion zum Loggen der fertig zusammengebastelten Zeile aufgerufen.

  2. void writeLogline(char* filename, char* line)
    Diese Funktion bekommt den Dateinamen und die zu loggende Textzeile übergen. Innerhalb der Funktion wird diese Zeile dann sowohl in der Datei gespeichert als auch nochmal auf Serial ausgegeben.

Um daraus einen realen Datenlogger zu machen, brauchst Du jetzt nur noch eine Echtzeituhr aus der Du die echte Zeit statt der Fake-Zeit ausliest, vielleicht sogar mit Datum.

Und statt in der loop Zufallswerte als Temperaturen zuzuweisen müßte dort Dein Code tatsächlich angeschlossene Temperatursensoren auslesen.

Der Code so wie er da steht ist für einen UNO mit Ethernet-Shield und SD-Kartenschacht auf dem Shield.

/*
  SD card datalogger
  rewritten by 'jurs' for German Arduino Forum
  for use without String objects
*/  
#include <SD.h>

// On the Ethernet Shield, CS is pin 4. Note that even if it's not
// used as the CS pin, the hardware CS pin (10 on most Arduino boards,
// 53 on the Mega) must be left as an output or the SD library
// functions will not work.
const int chipSelect = 4;

void setup()
{
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }

  Serial.print("Initializing SD card...");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    while(1);
  }
  Serial.println("card initialized.");
  getFakeTime();
}

char time[]= __TIME__;
int _hour,_min,_sec;

void getFakeTime()
{
  time[2]='\0';
  _hour=atoi(time);
  time[5]='\0';
  _min=atoi(&time[3]);
  _sec=atoi(&time[6]);
}


unsigned long tickTack(){
  static unsigned long prevMillis;
  while( millis() - prevMillis >= 1000)
  {      
    _sec++;
    if (_sec==60) {_min++;_sec=0;}
    if (_min==60) {_hour++;_min=0;}
    if (_hour==24){_hour=0;}
    prevMillis += 1000;	
  }
}



void writeLogline(char* filename, char* line)
{
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  File dataFile = SD.open(filename, FILE_WRITE);
  // if the file is available, write to it:
  if (dataFile) {
    dataFile.println(line); // Logzeile in Datei schreiben
    dataFile.close();
    // print to the serial port too:
    Serial.println(line); // Logzeile auf Serial ausgeben
  }  
  // if the file isn't open, pop up an error:
  else {
    Serial.print("error opening file for writing: ");
    Serial.println(filename);
  } 
}


void logTemperatures(float t1, float t2, float t3)
{
  char t1s[10],t2s[10],t3s[10]; // char-Arrays zur Aufnahme von drei Temperaturen
  char logline[81]; // char-Array zur Aufnahme einer Logzeile
  dtostrf(t1,4,1,t1s); // Umwandeln der float-Temperatur in einen C-String/char-Array
  dtostrf(t2,4,1,t2s); // Umwandeln der float-Temperatur in einen C-String/char-Array
  dtostrf(t3,4,1,t3s); // Umwandeln der float-Temperatur in einen C-String/char-Array
  // Daten Zeit und Temperaturen in eine Zeile packen
  snprintf(logline,sizeof(logline),"%02d:%02d:%02d; T1: %s; T2: %s; T3: %s",_hour,_min,_sec,t1s, t2s, t3s); 
  // Und die Funktion zum Loggen der Zeile aufrufen
  writeLogline("LOGFILEx.TXT", logline);
}


void loop()
{
  tickTack(); // run fake clock
  // make a string for assembling the data to log:
  float faketemp1=random(20,25)+random(0,100)/100.0;
  float faketemp2=random(20,25)+random(0,100)/100.0;
  float faketemp3=random(20,25)+random(0,100)/100.0;
  logTemperatures(faketemp1, faketemp2, faketemp3);
  delay(10000);
}

Hallo zusammen!

Danke für eure tatkräftige Unterstützung!

Der Hinweis mit den zwei Funktionen - 1x für Serial und 1x für SD war sehr hilfreich. Doch irgendwie hab ich so das Gefühl, das geht noch einfacher und vor allem mit weniger benötigten Speicher. Es sind bereits gut 20kb belegt, es sollen aber noch einige Funktionen hinzu kommen.

der Code sieht jetzt so aus:

/*
  SD card datalogger
 
 This example shows how to log data from three analog sensors 
 to an SD card using the SD library.
 	
 The circuit:
 * SD card attached to SPI bus as follows:
 ** UNO:  MOSI - pin 11, MISO - pin 12, CLK - pin 13, CS - pin 4 (CS pin can be changed)
  and pin #10 (SS) must be an output
 ** Mega:  MOSI - pin 51, MISO - pin 50, CLK - pin 52, CS - pin 4 (CS pin can be changed)
  and pin #52 (SS) must be an output
 ** Leonardo: Connect to hardware SPI via the ICSP header
 		Pin 4 used here for consistency with other Arduino examples
 
 created  24 Nov 2010
 modified 9 Apr 2012 by Tom Igoe
 
 This example code is in the public domain.
 	 
 */

#include <Wire.h>
#include <OneWire.h>
#include <SD.h>
#include "RTClib.h"
#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into pin 3 on the Arduino
#define ONE_WIRE_BUS 7

// Setup a oneWire instance to communicate with any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);
// On the Ethernet Shield, CS is pin 4. Note that even if it's not
// used as the CS pin, the hardware CS pin (10 on most Arduino boards,
// 53 on the Mega) must be left as an output or the SD library
// functions will not work.
const int chipSelect = 10;

File dataFile;

DeviceAddress insideThermometer = { 0x10, 0x89, 0x14, 0x90, 0x02, 0x08, 0x00, 0x3C };

void setup()
{
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
   while (!Serial) {
    ; // wait for serial port to connect. Needed for Leonardo only
  }


  Serial.print("Initializing SD card...");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(SS, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("SD-Card fehlt oder defekt...");
    // don't do anything more:
    while (1) ;
  }
  Serial.println("card initialized.");
  
  // Open up the file we're going to log to!
  dataFile = SD.open("datalog.txt", FILE_WRITE);
  if (! dataFile) {
    Serial.println("error opening datalog.txt");
    // Wait forever since we cant write data
    while (1) ;
  }
  sensors.begin();
  // set the resolution to 10 bit (good enough?)
  sensors.setResolution(insideThermometer, 9);
}

void printTemperatureSerial(DeviceAddress deviceAddress)
{
  float tempC = sensors.getTempC(deviceAddress);
  if (tempC == -127.00) {
    Serial.print("Error getting temperature");
  } else {
    Serial.print("C: ");
    Serial.print(tempC);
    Serial.print(" F: ");
    Serial.print(DallasTemperature::toFahrenheit(tempC));
  }
}
void printTemperatureSD(DeviceAddress deviceAddress)
{
  float tempC = sensors.getTempC(deviceAddress);
  if (tempC == -127.00) {
    dataFile.print("Error getting temperature");
  } else {
    dataFile.print("C: ");
    dataFile.print(tempC);
    dataFile.print(" F: ");
    dataFile.print(DallasTemperature::toFahrenheit(tempC));
  }
}
void loop()
{
  //Serial.print("Getting temperatures...\n\r");
  sensors.requestTemperatures();
  
  Serial.print("Temperatur betraegt: ");
  printTemperatureSerial(insideThermometer);
  Serial.print("\n");
  
  dataFile.print("Temperatur betraegt: ");
  printTemperatureSD(insideThermometer);
  dataFile.print("\n");
 
  
  // The following line will 'save' the file to the SD card after every
  // line of data - this will use more power and slow down how much data
  // you can read but it's safer! 
  // If you want to speed up the system, remove the call to flush() and it
  // will save the file only every 512 bytes - every time a sector on the 
  // SD card is filled with data.
  dataFile.flush();
  
  // Take 1 measurement every 100 milliseconds
  delay(100);
}

Wo kann ich ansetzen und vor allem wie?!

Es soll noch hinzu kommen: Uhrzeit der Messung, Taster zum starten und stoppen der Aufzeichnung, eine Zeitmessung zwischen 2 Temparaturen (z.B.: Temp fällt, Stopuhr Start bei 80°C -> Stop bei 50°C).... wird es bald mal einen neuen post dazu geben.

Deine 20 kB sind hauptsächlich die SD Library, die DallasTemperature Library mit float, die RTCLib
und die dadurch erforderlichen anderen Libraries.

Viel kommt also nicht mehr dazu.
Schau erstmal, wann du bei 28kB bist, bevor du dich um kleineren Ersatz für SD und RTCLib kümmerst.

  • Kleinere SD libraries für FAT16 only gibts, SD Cards bis 4GB sollten zum Datenloggen dicke reichen.

Problematischer ist z.Zt. eher, dass du alle 0.1 sec auf die SD schreibst ?
( ohne dataFile.flush(); wird deine Speicherkarte 10 mal länger leben ... )