SD Karte lesen und schreiben

Hallo
Ich habe ein SD kartenmodul das ich auslesen und beschreiben will.
Das Modul ist da und alles wird richtig erkannt, das läuft also.
Nur mit dem lesen und beschreiben habe ich kein glück.
Ich will in die textdatei nur eine zahl schreiben und später wieder auslesen.

Kurzfassung:
Ich habe einen zähler (int drinkzaehler;) den ich nach einem durchlauf um einen erhöhe.

Meine idee:
Erste zeile der textdatei auslesen und an drinkzaehler übergeben.
Den drinkzaehler wert um einen erhöhen.
Den alten wert in der datei löschen.
Den neuen wert wieder zurück schreiben.

Ich kriege es nicht hin !
Das ist jetzt der sketch womit ich versuche das zu bewerkstelligen.

#include <SD.h>
#include <SPI.h>
#define SDdaten_Lenght 5                        // Länge zeichenfolge von sd lesen
File dataFile;
char SDdaten[SDdaten_Lenght];                // SD Daten 5 zeichen lang
int drinkZaehler = 0;                                // Zähler für fertige drinks auf 0 setzen
byte SDdata_count = 0;                           // SD array zähler
const int chipSelect_Pin = 53;                   // Port an dem CS des kartenlesers angeschlossen ist

void setup() {
  SD.begin(53);
  pinMode(chipSelect_Pin, OUTPUT);           // CS Port SD Kartenleser als ausgang festlegen
}

void loop() {
  dataFile = SD.open("dzahl.txt", FILE_READ);    // Datei öffnen auf SD Karte

  if (dataFile)
  {
    while (dataFile.available())
    {
      Serial.write(dataFile.read());
      SDdaten[SDdata_count] = dataFile.read();       // schreibt in das SDdaten array
      SDdata_count++;                                        // verschiebt SDdaten array um 1
    }
    dataFile.close();                                            // Datei schliessen
    drinkZaehler = SDdaten;                                 // SDdaten an drinkZaehler übergeben
    drinkZaehler++;                                            // drinkZaehler für die drinks einen hochzählen
    Serial.println(drinkZaehler);
  }
  else
  {
    Serial.println("Fehler beim Lesen dzahl.txt");
  }
  //----------------------------------------------   // Schreiben auf SD Karte

    dataFile = SD.open("dzahl.txt", FILE_WRITE);

  if (dataFile)                                                      // ist die datei verfügbar dann...
  {
    dataFile.println(drinkZaehler);                            // schreib die daten in den file
    dataFile.close();                                             // Datei wieder schliessen
  }
  else
  {
    Serial.println("Fehler beim schreiben in dzahl.txt");
  }
}

Das wandeln von char , byte und int macht mir glaube ich die grössten probleme.
Und das löschen des wertes in der ersten zeile der datei.
Ich kann damit in die datei schreiben nur immer untereinander !
Und er schreibt immer das falsche was wohl am konvertieren liegt :slight_smile:
Auch die begrenzung auf 5 zeichen wie ich sie oben angegeben habe muss nicht sein.
Er kann ruhig den inhalt der ersten zeile komplett einlesen solang wie sie ebend ist.
Wird eh nicht übermässig viel werden.

Wäre jemand bereit mir bei dem Problem zu helfen ?

Bei dir fehlen jegliche Kenntnisse zur Behandlung von Strings in C.

Achtung:
Diese Version geht davon aus dass die Zahl in der Text-Datei mit einem LF abgeschlossen ist!! Also wenn du die Datei per Hand anlegst unbedingt danach einen Zeilenumbruch machen! Man kann das auch so machen, dass das Ende der Datei abgefangen ist, aber da war ich zu faul dazu.
Und die Zahl muss in der ersten Zeile stehen.

In setup() ist kurzes Code Stück dass eine Datei korrekt erstellt. Wenn die einmal existiert darf man das natürlich nicht mehr machen. Sonst wird die alte überschrieben.

Um die Zahl dann richtig zu überschreiben ist es am einfachsten man formatiert sie auf eine konstante Breite und füllt vorne mit Nullen auf. Das wird in printNumber() gemacht. Wenn man wirklich immer nur inkrementiert bräuchte man es nicht unbedingt, aber sehr wohl wenn man eine Zahl auch mit kleineren Zahlen überschreiben kann (z.B. "10" mit "9"). Das ist aber nicht viel Code. Also kann man es auch gleich so machen.

readStream() liest einen String ein bis zu einem LF. Danach steht der String in stringBuffer.

Der Test-Sketch inkrementiert mit jedem eingelesenen Zeichen von Serial den Wert in der Datei. Was man eingibt ist egal. Du kannst auch mehrere Zeichen auf einmal eingeben. Bei "aaa" wird dann dreimal inkrementiert.

#include <SD.h>
#include <SPI.h>

const int NUMBER_WIDTH = 5;
const int STRING_BUFFER_SIZE = 10;
char stringBuffer[STRING_BUFFER_SIZE];

File dataFile;

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

  //Datei neu erstellen. Mit 0 anfangen
  if (SD.exists("test.txt"))
    SD.remove("test.txt");

  dataFile = SD.open("test.txt", FILE_WRITE);
  if (dataFile)
  {
    printNumber(dataFile, 0);

    //Datei auslesen
    dataFile.seek(0);  //Dateizeiger auf Anfang setzen
    readStream(dataFile);
    Serial.print("read: "); Serial.println(stringBuffer);
    Serial.println();

    dataFile.close();
  }

}

void loop()
{
  if (Serial.available())
  {
    Serial.read();

    dataFile = SD.open("test.txt", FILE_WRITE);
    if (dataFile)
    {
      dataFile.seek(0);

      //Datei lesen
      readStream(dataFile);
      Serial.print("read: "); Serial.println(stringBuffer);

      //String konvertieren und inkrementieren
      unsigned int value = atol(stringBuffer);
      value++;

      //Zahl zurückschreiben
      dataFile.seek(0);
      printNumber(dataFile, value);

      //wieder auslesen
      dataFile.seek(0);
      readStream(dataFile);
      Serial.print("new value: "); Serial.println(stringBuffer);
      Serial.print("as integer: "); Serial.println(atol(stringBuffer));
      Serial.println();

      dataFile.close();
    }
  }
}

void printNumber(Stream& stream, unsigned int value)
{
  unsigned long num = 10;
  for (int i = 0; i < NUMBER_WIDTH - 1; i++)
  {
    if (value < num) stream.print('0');
    num = num * 10;
  }

  stream.println(value);
}


bool readStream(Stream& stream)
{
  static byte index;

  while (stream.available())
  {
    char c = stream.read();

    if (c >= 32 && index < STRING_BUFFER_SIZE - 1)
    {
      stringBuffer[index++] = c;
    }
    else if (c == '\n' && index > 0)
    {
      stringBuffer[index] = '\0';
      index = 0;
      return true;
    }
  }
  return false;
}

WOW !!! :o
Da magst du recht haben das ich damit probleme habe.
Aber es ist auch das erste mal das ich was mit einem Arduino mache oder C.

Ich werde mal versuchen den code in meinen Sketch zu übernehmen.
Ich dachte wie du an meinem beispiel gesehen hast das es mit viel weniger code geht.
Das was du gepostet hast hätte ich mit sicherheit nicht hin bekommen.
Ich muss das erstmal Testen.
Bis dahin Danke ich dir für deine Hilfe.
Ich werde berichten !
bye

Hallo,

Serenifly ist unser String Guru, das macht der mit links und 40 Fieber. :slight_smile:
Mußt nur genau lesen was er schreibt und testen und ausprobieren. Ggf. nachfragen. Du packst das.

Topper_Harley:
Ich dachte wie du an meinem beispiel gesehen hast das es mit viel weniger code geht.

Es geht auch mit weniger, aber der Code ist dann schlechter und weniger allgemein.

Wie gesagt bräuchte man in deinem Fall die Zahl nicht unbedingt auf eine konstante Breite zu formatieren. Aber irgendwann willst du dann vielleicht doch mal was anderes machen als nur zu Inkrementieren. Dann würdest du da schnell Probleme bekommen.

Man kann auch das Einlesen anders machen. Aber so ist es vollkommen allgemein. Mit readStream() kann man allen möglichen Text einlesen (nicht nur von SD, sondern auch von Serial oder Ethernet!). Du könntest damit auch mehr als eine Zahl einlesen. Ich verwende das gleiche Prinzip für Config Dateien mit Strings wie "100,200,300". Da liest man dann die entsprechende Zeile aus teilt den String in Teil-Strings auf und konvertiert diese. Beim Zurückschreiben kann man der Einfachheit halber alle Werte neu schreiben, auch wenn sich nur einer geändert hat.

Es ist auch kein Problem darüber eine weitere Funktion zu legen die eine bestimmte Zeile aus einer Datei ausliest. Dazu muss man nur mehrmals readStream() (ein besserer Name wäre wohl readLine() ) aufrufen und die Anzahl der Aufrufe zählen. Dann kann man Dateien mit mehreren Zeilen und mehreren Werten pro Zeile haben.
Das schöne ist dann, dass der Code um eine Zeile einzulesen gleich bleibt. Man muss also nicht alles neu schreiben nur weil man das Format der Datei geändert hat.

Ein paar deiner grundlegenden Fehler waren folgende:
1.) Dein eingelesener String ist nicht unbedingt NULL-terminiert. Sondern nur wenn du weniger als 5 Zeichen einliest (da das Array am Anfang alles auf NULL steht. Und das auch nur weil es global ist).
2.) Du weißt dann einfach den String an eine Integer Variable zu. Um Strings in Zahlen zu konvertieren braucht man Funktionen wie atoi(), atol() oder atof(). Das steht für "ascii to int/long/float"
3.) Wenn du eine Datei zum Schreiben öffnest steht der Dateizeiger am Ende der Datei. Deshalb steht in meinem Code immer wieder file.seek(0) um auf den Anfang zu gehen

Ansonsten war es nicht völlig falsch und ging schon stark in die richtige Richtung :slight_smile:

Und wie gesagt ist das nur Test-Code! Der Teil in setup() ist nur um erst mal die Datei zu erstellen. Das kann man auch per Hand machen. In fertigem Code willst du da vielleicht nicht jedesmal die Datei löschen und bei Null anfangen, sondern eine bestehende Datei beibehalten. Kommt auf die Anwendung an.

Genauso lese ich nach dem Schreiben die Zeile wieder zur Kontrolle aus. Das dient nur dazu um es zu Veranschaulichen und dir zu zeigen dass es funktioniert. In deinem eigentlichen Code brauchst du das nicht.

Hi
Saubere Arbeit funktioniert prima.
mit einer kleinen einschränkung die von meinem aufbau kommen kann.
Ich habe ja 2 Mega2560 per sda/scl verbunden.
Schreiben soll der slave auf die SD.
Er macht alles sobald ich das hier auskommentiere:

void loop()

// if (Serial.available())
// {
// Serial.read();

Und natürlich auch die schliessende klammer weiter unten.
Wenn ich das weg lasse macht er alles wie gewollt.
Er bekommt aus irgendeinem grund wohl eine meldung das Serial nicht verfügbar ist und dann macht er nix.
Ist aber für mich jetzt nicht weiter schlimm weil ich oben weiter ja schon die sd abfrage und dort kriege ich immer ein OK das sd da ist und auch volumen und,und ,und.
Also ist das wohl eher ein schönheitsfehler der mit anderen umständen zusammen hängen wird.
Ich bin auf jeden fall Glücklich das dieses problem auch gelöst ist.
Noch ein paar kleine hürden und dann können die Fahrprogramme für die Motoren und Servo, geschrieben werden :-))

Danke,Danke,Danke :-))) :grin:

Das ist wie gesagt nur ein Test-Sketch. Du willst das Schreiben ja irgendwie anders auslösen. Ist mache das in Test-Sketches nur immer gerne mit Serial weil das sehr einfach geht und man keine Taster anschließen muss.

Ja aber als beispiel ist der sketch schon recht gut :slight_smile:
Funktioniert wie gesagt ohne serial Top !

eine meldung das Serial nicht verfügbar ist

Ist wohl ein Missverständnis:
Serial.available() liefert die Anzahl vorhandener, aber noch nicht gelesener Zeichen im Eingabepuffer.

Der Quick and Dirty Weg um eine Zahl auszulesen ist übrigens parseInt()

Das hätte hier gereicht, aber ich habe mal bewusst etwas weiter ausgeholt. Ein Nachteil ist dass es blockierend ist, was bei Serial große Probleme bereitet. Bei SD wäre das kein Problem gewesen (da man nicht auf eintreffende Daten warten muss), aber wenn die Datei komplexer wird (mehrere Werte und vor allem mehrere Zeilen) sind andere Optionen besser.

Wenn der String nicht riesig lange ist (was bei Ethernet Anwendung manchmal der Fall ist), ist es in den meisten Fällen am besten man liest eine Zeile bis zum Ende ein und bearbeitet dann diese Zeile. Egal was die Datenquelle ist. Das ist nicht der kürzeste Code, aber man ist am flexibelsten. Der größte Vorteil dabei ist dass Einlesen und Parsen/Bearbeiten getrennt sind. Wenn sich das Format einer Zeile ändert, muss man nur die Auswertung der Zeile anders machen.

Hallo Leute und vor allem Hallo Serenifly,

ich habe Deinen Code auch mal ausprobiert, nur leider ersetzt er nicht die erste Zeile, sondern holt sich den Wert aus der ersten Zeile, inkrementiert ihn und schreibt ihn in die zweite Zeile. Führe ich das Ganze nochmal aus, dann holt er sich wieder den Wert aus der ersten Zeile, inkrementiert und fügt es in die dritte Zeile ein.... usw.

Habe versucht ein wenig dran rum zu basteln, aber es ändert sich nichts.

Jetzt habe ich ??? Vor der Stirn stehen.

SirTiGer

seek(0) setzt den Datei-Zeiger auf den Anfang zurück. Da ist nichts mit mehreren Zeilen

Tja, das habe ich mir auch gedacht. Wahrscheinlich sehe ich den Wald vor lauter Bäumen wieder nicht.
Ich schicke gegen Abend mal den Code, die Ausgabe des Seriellen Monitors und den Inhalt der Textdatei.

Bis dann.
Würde mich freuen, wenn Du nachher noch mal vorbei schaust.

SirTiGer

Wahrscheinlich sehe ich den Wald vor lauter Bäumen wieder nicht

Immer besser, solche Probleme erstmal mit "einzelnen Bäumen", also einem minimalen Testprogramm, anzusehen. Wenn dann der Fehler weg/anders ist, war in der Regel eine der anderen, aus Versehen ungeprüften, Annahmen falsch.

So, wie versprochen.
Hier der Code und das Ergebnis.

// Codeschnipsel-Beispiel von Serenifly
// http://forum.arduino.cc/index.php?topic=371048.0
#include <SD.h>
#include <SPI.h>
const int NUMBER_WIDTH = 5;
const int STRING_BUFFER_SIZE = 10;
char stringBuffer[STRING_BUFFER_SIZE];
File dataFile;
void setup() {
  Serial.begin(9600);
  SD.begin(4);  
  //Datei neu erstellen. Mit 0 anfangen
  if (SD.exists("test.txt")) {
    SD.remove("test.txt");
  }
  dataFile = SD.open("test.txt", FILE_WRITE);
  if (dataFile) {
    dataFile.seek(0);
    printNumber(dataFile, 0);
    //Datei auslesen
    dataFile.seek(0); //Dateizeiger auf Anfang setzen
    readStream(dataFile);
    Serial.print("read: "); Serial.println(stringBuffer);
    Serial.println();
    dataFile.close();
  }
}
void loop() {
  if (Serial.available()) {
    Serial.read();
    dataFile = SD.open("test.txt", FILE_WRITE);
    if (dataFile) {
      dataFile.seek(0);
      //Datei lesen
      readStream(dataFile);
      Serial.print("read: "); Serial.println(stringBuffer);
      //String konvertieren und inkrementieren
      unsigned int value = atol(stringBuffer);
      value++;   
      //Zahl überschreiben
      dataFile.seek(0);
      printNumber(dataFile, value);
      //wieder auslesen
      dataFile.seek(0);
      readStream(dataFile);
      Serial.print("new value: "); Serial.println(stringBuffer);
      Serial.print("as integer: "); Serial.println(atol(stringBuffer));
      dataFile.close();
    }
  }
}
void printNumber(Stream& stream, unsigned int value) {
  unsigned long num = 10;
  for (int i = 0; i < NUMBER_WIDTH - 1; i++) {
    if (value < num) stream.print('0');
    num = num * 10;
  }
  stream.println(value);
}
bool readStream(Stream& stream) {
  static byte index;
  while (stream.available()) {
    char c = stream.read();
    if (c >= 32 && index < STRING_BUFFER_SIZE - 1) {
      stringBuffer[index++] = c;
    } else if (c == '\n' && index > 0 || c == '\r' && index > 0) {
      stringBuffer[index] = '\0';
      index = 0;
      return true;
    }
  }
  return false;
}

Hier die Ausgabe des Seriellen Monitors:

read: 00000

read: 00000
new value: 00000
as integer: 0
read: 00000
new value: 00000
as integer: 0
read: 00000
new value: 00000
as integer: 0
read: 00000
new value: 00000
as integer: 0
read: 00000
new value: 00000
as integer: 0

Hier der Inhalt der Textdatei:

00000
00001
00001
00001
00001
00001

Bei mir macht das was es soll:

read: 00000
new value: 00001
as integer: 1

read: 00001
new value: 00002
as integer: 2

read: 00002
new value: 00003
as integer: 3

read: 00003
new value: 00004
as integer: 4

read: 00004
new value: 00005
as integer: 5

read: 00005
new value: 00006
as integer: 6

read: 00006
new value: 00007
as integer: 7

read: 00007
new value: 00008
as integer: 8

read: 00008
new value: 00009
as integer: 9

read: 00009
new value: 00010
as integer: 10

Und es steht dann auch entsprechend nur das in der Datei:

00010

Wie gesagt es ist wichtig dass auch diese einzelne Zeile mit einem Zeilenumbruch abgeschlossen ist. Extra abzufragen ob die Datei zu Ende ist spare ich mir

Das ist doch merkwürdig oder?
Habe nie dran gezweifelt, daß Dein Code funktioniert. Nur Leider nicht bei mir!

Ätzend. Ich habe mir gestern einen Wolf gesucht...
Könnte es noch ein Einstellungsproblem sein?

SirTiGer

So....
Das Problem ist gelöst. Alles neue muß nicht gut sein.

Das Problem war das Update der SD Library...
Ich hatte die 1.22 drauf. Mit der 1.20 funktioniert es.

SirTiGer