AdSense

Sonntag, 8. September 2013

SD-Karten mit dem Arduino beschreiben und auslesen

Wer seinen Arduino als Datenlogger nutzt, um z.B. einen Temperaturverlauf auf zu zeichnen, steht schnell vor dem Problem, wie die erfassten Daten gespeichert werden sollen. Natürlich könnte man alle Daten irgendwie in einem Array speichern und dann per serielle Schnittstelle an den PC übertragen. Doch hier ist der Speicher begrenzt und fällt einmal der Strom aus, sind die Daten futsch. Außerdem benötigt man zusätzliche Software, um die Daten von der seriellen Schnittstelle in Empfang zu nehmen und aus zu werten. Das Problem mit dem flüchtigen Speicher könnte man umgehen, wenn man die Daten im EEPROM des Arduinos speichert - doch auch hier ist der Speicherplatz und vorallem die Zahl der Schreibzyklen begrenzt. Auch das Problem mit der Übertragung bleibt bestehen.

Am Besten speichert man seine Daten daher auf einer Speicherkarte. Hier sind die Daten auch bei Stromausfall sicher gespeichert, außerdem lässt sich die Speicherkarte direkt mit dem PC verbinden und auslesen. Wenn man die Daten im richtigen Format speichert, kann man sie auch gleich mit Excel, Matlab oder ähnlichen Programmen auswerten.

Hardware

Bei Ebay gibt es SD-Karten-Module bereits für einen Euro. Ich habe mich für das Modul von LC Technology entschieden:
Die Pinbelegung ist klar ersichtlich. Neben Pins für die Spannungsversorgung (+5V, +3,3V und GND) gibt es Pins für den SPI-Bus, mit dem auf die Speicherkarte zugegriffen werden kann. Die Pins sind zwar zweireihig angeordnet, die obere und die untere Reihe sind aber jeweils intern verbunden. SD-Karten arbeiten mit einem Spannungspegel von 3,3V. Das Modul kann wahlweise mit 3,3V oder mit 5V versorgt werden. Im letzteren Fall bringt ein eingebauter Spannungswandler die Spannung automatisch auf 3,3V. Die SPI-Pins werden allerdings NICHT gewandelt, sie dürfen also auch NICHT direkt mit dem Arduino verbunden werden - sonst wird die SD-Karte gegrillt. Hier muss also ein Pegelwandler zwischen geschaltet werden.

Der fertige Aufbau sieht so aus:



Das Modul wird über den 5V-Ausgang vom Arduino versorgt. Die SPI-Pins werden über den Pegelwandler wie folgt mit dem Arduino verbunden:
MOSI an Pin 11
MISO an Pin 12
SCK an Pin 13
CS an Pin 4

Ich habe einen bi-direktionalen Wandler genommen und lasse alle Pins über den Wandler laufen. Beides ist streng genommen nicht nötig. Theoretisch reicht auch ein Pegelwandler, der nur in eine Richtung von 5V auf 3,3V wandelt (z.B. den 74HC4050). Über diesen Wandler laufen dann nur die Pins MOSI, SCK und CS. Der MISO-Pin kann direkt mit dem Arduino verbunden werden.

Ist alles richtig verkabelt, kann die SD-Karte in das Modul gesteckt werden. Der Arduino kann mit SD-Karten im Format FAT16 und FAT32 umgehen, laut Arduino-Referenz wird FAT16 allerdings bevorzugt.

Software

Die SD-Library für Arduino liefert einige Beispiele, mit der die Funktion des Aufbaus gleich getestet werden kann. Ich habe mich für das Beispiel "Datalogger" entschieden. Im Beispiel werden die Werte der drei analogen Eingänge in einer txt-Datei auf der Karte gespeichert.

#include <SD.h>

const int chipSelect = 4;

void setup()
{
 // Open serial communications and wait for port to open:
  Serial.begin(9600);
  
  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:
    return;
  }
  Serial.println("card initialized.");
}

void loop()
{
  // 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 += ","; 
    }
  }

  // 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("datalog.txt", FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile) {
    dataFile.println(dataString);
    dataFile.close();
    // print to the serial port too:
    Serial.println(dataString);
  }  
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening datalog.txt");
  } 
}

In der setup()-Funktion wird zuerst eine serielle Verbindung aufgebaut, mit dem Befehl SD.begin(chipSelect) wird dann die SD-Karte initialisiert. Als Parameter wird hier der Pin übergeben, an dem CS angeschlossen ist. War die Initialisierung erfolgreich, geht es in den loop()-Funktion weiter. Hier werden die Werte der analogen Eingänge ausgelesen und in einer String-Variable gespeichert . Mit dem Befehl SD.open() wird dann die Datei "datalog.txt" im Schreib-Modus geöffnet.  Ist das Öffnen erfolgreich, wird der Wert der String-Variable in die Datei geschrieben. Dann wird die Datei wieder geschlossen.

Ähnlich einfach ist das Auslesen von Dateien.

  dataFile = SD.open("test.txt");
  if (dataFile) {
    Serial.println("test.txt:");
    
    // read from the file until there's nothing else in it:
    while (dataFile.available()) {
     Serial.write(dataFile.read());
    }
    // close the file:
    data.close();
  } else {
   // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }

 Auch hier muss zuerst wieder die Datei mit dem open()-Befehl geöffnet werden. Wurde die Datei erfolgreich geöffnet, kann sie mit dem Befehl read() Zeile für Zeile ausgelesen werden, bis das Ende der Datei erreich wurde. Dies wird mit dem Befehl available() überprüft. Abschließend wird die Datei mit dem close()-Befehl wieder geschlossen.

14 Kommentare:

  1. Hallo Udo!

    Das klingt alles interessant.
    Ich versuche mich derzeit an einem Arduino Mega 2560. Ich habe ein Micro SD Card(TF) Adapter, der direkt an den Pins 50-53 hängt und GND und 5V+. Zusätzlich habe ich einen GPS Sensor, der vorher an den Pins 52,53 war, den musste ich aber umplanen, weil die durch den TF Adapter belegt sind. Habe den GPS Sensor derzeit an 18,19 zu hängen, aber GPS zeigt mir keine vollständigen Daten, sondern nur Teile von Ihnen. Was kann ich tun bzw. wokönnte ich GPS noch anschließen und wie würde mein Sketch dazu aussehen? Danke

    AntwortenLöschen
    Antworten
    1. Ein paar Infos wären hilfreich. Um was für einen GPS-Sensor handelt es sich genau? Wie wird er angesprochen? Auch über SPI?

      Löschen
  2. Hallo Udo,

    in Deinem Text schreibst Du, dass Du CS der SD-Card mit Pin 4 des Arduino UNO verbindest.
    In dem Programm definierst Du Pin 10 des Arduino Uno als OUTPUT für Chip select.
    Auf Deinem Foto sieht es aber aus, als ob Pin 10 nicht genutzt wird.
    Habe ich etwas falsch verstanden?

    AntwortenLöschen
    Antworten
    1. Das stimmt schon alles so. Als CS-Pin wird Pin 4 definiert (ganz am Anfang mit const int chipSelect = 4;). Pin 10 ist der Standard-Pin, der "normalerweise" als CS-Pin vorgesehen ist. Um die korrekte Funktion der Lib zu gewährleisten, muss dieser Pin als Output definiert - auch wenn er dann gar nicht benutzt wird (siehe auch im Kommentar zu der Code-Zeile).

      Löschen
    2. Danke für die schnelle Antwort!

      Löschen
  3. Hallo,
    ich hoffe, dass noch jemand diesen Blog liest und mir vielleicht bei meinem Problem helfen kann.
    Ich habe Deinen sketch-Teil als Basis genommen, um von der SD eines ethernet shields (als webclient) ein JPG-File zu lesen und das Bild IMAGE.JPG auf dem Browser auszugeben.

    Hier mein sketch-teil:
    webFile = SD.open("IMAGE.JPG");

    Serial.print(F("filename: ")); Serial.println(filename);

    if (webFile) {
    while(webFile.available()) {
    client.write(webFile.read()); // send Picture to client
    }
    webFile.close();
    } else {
    // if the file didn't open, print an error:
    Serial.println("error opening IMAGE.JPG");
    }


    Leider erhalte ich auf dem Browser nur kryptischen Kram:

    ÿØÿþ$W ð@ 2 Q Q ÿÛ„ !*# ' $1%'+,/// #373-6*./- - --------------------------------------------------ÿþ ÿÀ ð @ ! ÿÄ ¢ } !1A Qa "q 2‘¡ #B±Á RÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖ×ØÙÚáâãäåæçèéêñòóôõö÷øùú w !1 AQ aq "2 B‘¡±Á #3Rð brÑ $4á%ñ &'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖ×ØÙÚâãäåæçèéêòóôõö÷øùúÿÝ ÿÚ ?ùèfR §,9«G@ж¨d¹E µ×ØD[VUí ¹?g0E{hŒºÅŠsóܯþ…]O‹_> ó÷õ'?†Ö¨ê‡sšÑíÍÍÒF§2·*¿N¤û í #µ€"’#AË ¬}M)²ÑËêÒ @¿@HgÐWKáä+¤Êþ·L „·ô¡ì&?ÄàC¨E’Y¢³… ûíÏõ¬0J 2RKŸº¾”£±HayZ@ /1 *ƒÂÖN· ^¬r>ç1# îsÅZÜ™ ܱøŠ@H+


    Was mache ich falsch? Hat jemand vielleicht Erfahrung mit diesem Thema und kann mir Tipps geben, wie ich meine IMAGE.JPG auf den Browser bekomme.

    Guß Ulli

    AntwortenLöschen
    Antworten
    1. Ich kenne mich mit dem webFile-Zeug leider nicht so gut aus. Mein Ansatz wäre aber: Wenn man mit dem Browser auf etwas zugreift erwartet dieser ja eine HTML-Datei (normalerweise). Das was du gepostet hast sieht aus, wie wenn man eine Bild-Datei mit dem Editor öffnet. Probier doch mal genau das, rechtsklick auf IMAGE.JPG und dann öffnen mit Texteditor - da müsste theoretisch die selbe Zeichenfolge drin stehen. Wie man das Problem jetzt aber dann löst weiß ich nicht, da müsstest du mal ein bisschen nach dem WebClient suchen, vielleicht gibt es da schon fertige Codes die schon Bilder ausgeben. Dieser Link könnte hilfreich sein: http://startingelectronics.org/tutorials/arduino/ethernet-shield-web-server-tutorial/SD-card-web-server-image/

      Löschen
    2. Es ist genau so, wie Udo es geschrieben hat. Du musst dem Browser zuerst eine HTML-Seite schicken, in der das Bild über < i m g src="IMAGE.jpg" /> (ohne Leerzeichen natürlich) eingebunden ist. Daraufhin wird der Browser das Bild von deinem Arduino "anfordern" und erst dann kannst du es ihm schicken. Wie genau das geht ist im genannten Tutorial gut erklärt. Wenn es nicht klappt, kannst du gerne hier nachfragen. Wir helfen gern weiter.

      Löschen
  4. Hi Leute, kann es sein, dass man in einem Sketch nicht gleichzeitig beschreiben und auslesen kann? Bei mir scheint es jedenfalls nicht zu gehen. So wie es aussieht, ist bei einem von beiden Aktionen dataFile falsch. Was kann man machen?

    AntwortenLöschen
    Antworten
    1. Hast du dataFile nach dem lesen bzw schreiben wieder mit close() geschlossen?
      Also du musst öffnen - schreiben schließen - öffnen - lesen - schließen.

      Löschen
  5. Wie sieht es bei dieser Platine aus:
    http://amzn.to/1TGZaFU

    Meiner Meinung nach brauche ich hier einen Pegelwandler für die SPI-Pins (Bin mir aber nicht sicher deshalb frage ich)

    AntwortenLöschen
  6. Hallo alle miteinander,
    Ich möchte mit meinem Arduino DUE Daten eines analogen Beschleunigungssenors, dem ADXL 326, auf eine SD Karte schreiben um diese dann später vom PC aus auswerten zu können. In dem folgenden Code erhalte ich trotz einiger Zeit des Googelns und der Rumprobiererei immer wieder den selben Fehler:

    Arduino: 1.6.0 (Windows 7), Platine: "Arduino Due (Programming Port)"

    Kalibration_DUE.ino: In function 'void loop()':





    D:\Users\User_1\Desktop\Arduino_IDE_for_RePhone-master\hardware\arduino\sam\cores\arduino/Print.h:78:12: note: no known conversion for argument 1 from 'int [6]' to 'long unsigned int'

    Fehler beim Kompilieren


    [code]
    //Einbinden der SD Karten und SPI Bibliothek 
    #include 
    #include 

    const int chipSelect = 4;

    //Definieren der Anschluss-Achsen
    const float x_axis = A0;
    const float y_axis = A1;
    const float z_axis = A2;

    //Einlesen der Raw Werte
    const float xRawMin = 1976;
    const float xRawMax = 2130;
    const float yRawMin = 1970;
    const float yRawMax = 2123;
    const float zRawMin = 1971;
    const float zRawMax = 2130;

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

    analogReadResolution(12); //AD Wandler auf 12 bit setzen

    Serial.print("Initialisieren der SD Karte...");
    pinMode(10, OUTPUT);

    if (!SD.begin(chipSelect))
    {
      Serial.println("FEHLER: SD Karte nicht gefunden");
      return;
    }  
    else {
      Serial.println("SD Karte initialisiert");
    }

    }

    void loop()
    {

    //Werte auslesen
    float x_value_0 = analogRead(x_axis);
    float y_value_0 = analogRead(y_axis);
    float z_value_0 = analogRead(z_axis);

    //Werte in g umrechnen
    long x_value_1 = map(x_value_0, xRawMin, xRawMax, -1000, 1000);
    long y_value_1 = map(y_value_0, yRawMin, yRawMax, -1000, 1000);
    long z_value_1 = map(z_value_0, zRawMin, zRawMax, -1000, 1000);

    float x_value = x_value_1 / 1000.0;
    float y_value = y_value_1 / 1000.0;
    float z_value = z_value_1 / 1000.0;

    //gemessene Werte ausgeben
    Serial.println("x-Achse");
    Serial.print("xRawMin - xRawMax : ");
    Serial.print(xRawMin);
    Serial.print(" - ");
    Serial.println(xRawMax);
    Serial.print("x_value_0 = ");
    Serial.println(x_value_0);
    Serial.print("x = ");
    Serial.print(x_value, 4);
    Serial.println("g");
    Serial.println();

    Serial.println("y-Achse");
    Serial.print("yRawMin - yRawMax : ");
    Serial.print(yRawMin);
    Serial.print(" - ");
    Serial.println(yRawMax);
    Serial.print("y_value_0 = ");
    Serial.println(y_value_0);
    Serial.print("y = ");
    Serial.print(y_value, 4);
    Serial.println("g");
    Serial.println();

    Serial.println("z-Achse");
    Serial.print("zRawMin - zRawMax : ");
    Serial.print(zRawMin);
    Serial.print(" - ");
    Serial.println(zRawMax);
    Serial.print("z_value_0 = ");
    Serial.println(z_value_0);
    Serial.print("z = ");
    Serial.print(z_value, 4);
    Serial.println("g");

    Serial.println("----------------------------");

    delay(2000);

    }


    [/code]


    Weiß von euch vielleicht jemand wie man dieses Problem lösen kann?

    MfG

    Mr. Sensor

    AntwortenLöschen
    Antworten
    1. Da hab ich wohl den falschen Code kopiert aber die richtige Fehlermeldung ;)
      richtiger Code kommt jetzt hier:

      #include
      #include

      const int chipSelect = 4;

      const int xInput = A0;
      const int yInput = A1;
      const int zInput = A2;
      const int buttonPin = 2;

      // Raw Ranges:
      // initialize to mid-range and allow calibration to
      // find the minimum and maximum for each axis
      int xRawMin = 2048;
      int xRawMax = 2048;
      int yRawMin = 2048;
      int yRawMax = 2048;
      int zRawMin = 2048;
      int zRawMax = 2048;


      // Take multiple samples to reduce noise
      const int sampleSize = 10;

      void setup()
      {

      //analogReference(EXTERNAL);
      //wurde über Anpassung der Hardware gelöst
      //(Umlöten des Jumpers)

      Serial.begin(9600);

      analogReadResolution(12);
      //Auflösung des AD Wandlers auf 12 bit setzen

      /*
      Serial.print("Initialisieren der SD Karte...");
      pinMode(10, OUTPUT);

      if (!SD.begin(chipSelect))
      {
      Serial.println("FEHLER: SD Karte nicht gefunden");
      return;
      }
      else {
      Serial.println("SD Karte initialisiert");
      }
      */

      pinMode(2, INPUT);

      }

      void loop()
      {
      int xRaw = ReadAxis(xInput);
      int yRaw = ReadAxis(yInput);
      int zRaw = ReadAxis(zInput);

      //RawRanges in Array zum Speichern ablegen
      int RawRanges[] = {xRawMin, xRawMax, yRawMin, yRawMax, zRawMin, zRawMax};

      Serial.println(RawRanges);

      //neues File auf SD Karte erstellen
      File RawRangesFile = SD.open("RawRanges.txt", FILE_WRITE);

      if (RawRangesFile)
      {
      RawRangesFile.println(RawRanges);

      RawRangesFile.close();
      }

      else
      {
      Serial.println("Error: RawRanges.txt konnte nicht geoeffnet werden");
      }

      if (digitalRead(buttonPin) == LOW)
      {
      AutoCalibrate(xRaw, yRaw, zRaw);
      }

      else
      {
      Serial.print("Raw Ranges: X: ");
      Serial.print(xRawMin);
      Serial.print("-");
      Serial.print(xRawMax);
      Serial.print(", Y: ");
      Serial.print(yRawMin);
      Serial.print("-");
      Serial.print(yRawMax);
      Serial.print(", Z: ");
      Serial.print(zRawMin);
      Serial.print("-");
      Serial.print(zRawMax);
      Serial.println();
      Serial.print(xRaw);
      Serial.print(", ");
      Serial.print(yRaw);
      Serial.print(", ");
      Serial.print(zRaw);

      // Convert raw values to 'milli-Gs"
      long xScaled = map(xRaw, xRawMin, xRawMax, -1000, 1000);
      long yScaled = map(yRaw, yRawMin, yRawMax, -1000, 1000);
      long zScaled = map(zRaw, zRawMin, zRawMax, -1000, 1000);

      // re-scale to fractional Gs
      float xAccel = xScaled / 1000.0;
      float yAccel = yScaled / 1000.0;
      float zAccel = zScaled / 1000.0;

      Serial.print(" :: ");
      Serial.print(xAccel, 5);
      Serial.print("G, ");
      Serial.print(yAccel, 5);
      Serial.print("G, ");
      Serial.print(zAccel, 5);
      Serial.println("G");


      delay(500);
      }
      }

      // Read "sampleSize" samples and report the average

      int ReadAxis(int axisPin)
      {
      long reading = 0;
      analogRead(axisPin);
      delay(1);
      for (int i = 0; i < sampleSize; i++)
      {
      reading += analogRead(axisPin);
      }
      return reading/sampleSize;
      }
      //
      // Find the extreme raw readings from each axis
      //
      void AutoCalibrate(int xRaw, int yRaw, int zRaw)
      {
      Serial.println("Calibrate");
      if (xRaw < xRawMin)
      {
      xRawMin = xRaw;
      }
      if (xRaw > xRawMax)
      {
      xRawMax = xRaw;
      }
      if (yRaw < yRawMin)
      {
      yRawMin = yRaw;
      }
      if (yRaw > yRawMax)

      if (yRaw > yRawMax)
      {
      yRawMax = yRaw;
      }
      if (zRaw < zRawMin)
      {
      zRawMin = zRaw;
      }
      if (zRaw > zRawMax)
      {
      zRawMax = zRaw;
      }
      }

      Und zwar will der Compiler hier meinen RawRanges Array nicht fressen...
      Bin langsam ziemlich am verzweifeln

      MfG

      Mr. Sensor

      Löschen
  7. Danke! Endlich mal jemand, der einen Pegelwandler beschreibt anstatt halbgaren Pfusch zusammen zu stöpseln.

    AntwortenLöschen