Integer Werte von SD-Karte lesen

Hallo zusammen!
Ich bin neu hier. Falls ich meine Frage hier in der falschen Kategorie stelle, dann bitte in die richtige verschieben.

Mein Problem:
Ich möchte Messwerte (Windstärke), die in einem integer-Array stehen auf einer SD-Karte zwischenspeichern und am nächsten Tag (nach Stromabschaltung) wieder in das Array einlesen.

Benutzte Hardware:
Arduino Uno ATmega 328 - compatibles Board von AZ-Delivery
ARD SDH 2,8 TD TFT-Display mit SD-Kartensteckplatz von Reichelt

Ich benutze zum rumprobieren folgende Sketche:

Zum Schreiben:`

/*
  TestVersion von mir
  Nur Datei auf Karte Schreiben

Die Daten dafür stammen aus einem integer-Array 
*/

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

const int chipSelect = 10; //Hier muss unbedingt 10 stehen


File myFile;
int Zahl = 20; //Vorgabe für Zähler

int Ary[21];  

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

//Array füllen
for (int count = 0; count <=21; count++)
{
  Ary[count]= count+11;
}

  Serial.print("Initialisierung SD card...");

  if (!SD.begin(chipSelect)) {
    Serial.println("Initialisierung Fehler");
    while (1);
  }
  Serial.println("Initialisierung erfolgreich");

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

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Schreibe in testtest.txt...");
    
   while (Zahl != 0)
   { 
   Serial.println(Ary[Zahl]); 
   myFile.println(Ary[Zahl]);
   Zahl--;
   }
    
    // close the file:
    myFile.close();
    Serial.println("Schreiben fertig");
  } else {
    // if the file didn't open, print an error:
    Serial.println("Fehler beim Schließen");
  }

}

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

und zum Lesen:

/*  TestVersion von mir
  SD card Lesen und in integer Variablen speichern
  dazu dient ein Array

*/

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

const int chipSelect = 10; //Hier muss unbedingt 10 stehen


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 native USB port only
  }


  Serial.print("Initialisierung SD card...");

  if (!SD.begin(chipSelect))
  {
    Serial.println("Initialisierung Fehler");
    while (1);
  }
  Serial.println("Initialisierung erfolgreich");


  // re-open the file for reading:
  myFile = SD.open("testtest.txt");
  if (myFile)
  {
    Serial.println("Inhalt der Datei:");
    Serial.print("Größe der Datei: ");
    Serial.println(myFile.available());  

    // read from the file until there's nothing else in it:
    while (myFile.available())
    {
      Serial.write(myFile.read());  //Erstmal die Werte nur anzeigen
    }
    // close the file:
    myFile.close();
  }
  else
  {
    // if the file didn't open, print an error:
    Serial.println("Fehler beim öffnen der Datei");
  }

  int Vari[21];  //Das Array, das gefüllt werden soll

    // Neuer Durchlauf mit Füllen eines Arrys
  myFile = SD.open("testtest.txt");
  if (myFile)
  {
    Serial.println("Durchlauf Nummer 2");
    Serial.print("Größe der Datei: ");
    Serial.println(myFile.available());

   for(int count=0; count<=21; count++)
    {
      Vari[count]= (myFile.read());  //Das Array wird aus der Datei gefüllt
    }
    // close the file:
    myFile.close();
  }
  else
  {
    // if the file didn't open, print an error:
    Serial.println("Fehler beim öffnen der Datei");
  }
  Serial.write("Nun die Ausgabe:");
  for(int count=0; count<=21; count++)
 {
   Serial.println(Vari[count]);   //Der Inhalt des Arrays wird angezeigt
 }  
}


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

Das sind beides Abwandlungen des "ReadWrite" Beispiels aus der Bibliothek.

Im Array landen die gespeicherten Werte im ASCII-Code mit LF und CR als Trennung. Das nützt mir so natürlich nichts.
Ich hatte überlegt, ob das mit dem Dateityp ".txt" zu tun hat, aber auch mit anderen Dateitypen hat es nicht geklappt. Ich hab im Netz rumgesucht, weil ich bestimmt nicht der Erste bin, der dies Problem hat. So richtig fündig geworden bin ich aber nicht. Mag daran liegen, dass mein Englisch nicht so gut ist.

Ein Beispiel "StringToInt" hab ich gefunden. Leider habe ich es nicht zum laufen bekommen.

Für Tipps wäre ich dankbar.

Gib uns doch mal ein Beispiel, wie Deine Daten aussehen.

Gruß Tommy

Hallo Tommy56,

Die Daten sind ähnlich wie in dem Sketch SD-Schreiben - siehe oben. Nur sind sie nicht so schön "hochgezählt" sondern schwanken.

Es sind ganze Zahlen, in der Regel zwischen 0 und 30, da die Messwerte in m/s anfallen. Nachkommastellen fallen nicht an.

Ich sehe oben aber nichts, wo was geschrieben wird.

Gruß Tommy

Dass die Zahlen als Text auf der SD-Karte stehen, nutzt dir insofern, als das ein universelles Format ist, dass du auch unabhängig vom Arduino lesen kannst.

Hat natürlich den Nachteil, dass beim Lesen mit jedem read nur 1 Zeichen (Ziffer) kommt.
Bei mehreren mehrstelligen Zahlen in einer Zeile braucht man auch noch Trennzeichen dazwischen.
Schau dir erstmal Serial.parseInt an. Am einfachsten so in die Datei schreiben, dass parseInt das gewünschte Ergebnis kriegt.

Hallo Tommy56,

ich hatte oben 2 getrennte Sketche gepostet mit denen ich gerade experimentierte um das Problem mit der Datenspeicherung zu lösen.
Im oberen Sketch werden Testdaten (ganze Zahlen, die vorher in ein Array namens "Ary" gepackt wurden in die Datei "testtest.txt" geschrieben.

Vielleicht musst Du in dem Sketch etwas runterscrollen.

Hallo michael_x,
danke für Deine Antwort.
Ich werde Deinen Vorschlag ausprobieren, verstehe ihn aber nicht so ganz. Da er mit dem Wort "Seriel" beginnt bezieht er sich doch vermutlich auf die Serielle Schnittstelle. Die benutze ich aber im meinen Experimentiersketchen nur dazu, mir die Werte anzeigen zu lassen.
Mein Ziel ist aber, die Werte als 1-2-stellige Integer-Zahlen in das Array zu bekommen.
In einem weiteren Schritt (den ich noch nicht programmiert habe) soll dann damit eine Grafik-Kurve auf dem Display angezeigt werden.
In dem noch zu erstellenden endgültigen Programm wird nichts mehr über die Serielle Schnittstelle ausgegeben.
Die Datenspeicherung auf der SD-Karte soll nur dazu dienen nach Abschaltung des Gerätes die Daten nicht zu verlieren.

Ok, das hatte ich überscrollt.
Also jede Zeile eine Zahl (Byte, also max. 3 Stellen+'\0')
Du musst also erst mal in ein char-Array mit 4 Elementen einlesen, bis Du ein '\n' findest. alles < 0x20 (z.B. das '\a') ignorieren.
Einen Ansatz für Zeilen, nicht direkt für Zahlen, sondern Zeilen, findest Du in meinem Tutorial für Zeichenketten.
Anschließend mit atoi eine Zahl draus machen.

Ich würde keine for-Schleife nehmen, sondern so lange lesen, wie was da ist.

Ein völlig anderer Ansatz wäre das Array binär am Stück zu schreiben/lesen, das kann dann aber wo anders nicht so einfach gelesen werden.

Gruß Tommy

Du findest in meinem Link den Satz:

Serial.parseInt() erbt von der Stream-Dienstklasse.

Die gilt glücklicherweise auch für SD File Objekte, wie du dort sehen kannst. (Ein --vereinfachender-- Unterschied ist, dass das zugrundeliegende read erst -1 liefert, wenn die Datei am Ende ist und man keine langsame serielle Verbindung abwarten muss.)

while (myFile.available())    {
      Serial.println(myFile.parseInt());  //Erstmal die Werte nur anzeigen, jeden in eine Zeile um sie auseinander halten zu können
}

[ Nachtrag: ]
Wenn die Datei menschenfreundlicher sein soll, kannst du natürlich beim Schreiben mehr reinpacken, um sie menschenlesbarer (und editierbarer) zu machen. Dann musst du das natürlich auch entsprechend beim Lesen rausfischen, und parseInt nur dann machen, wenn der Lesezeiger direkt vor der Zahl steht. (evtl. führende Leerzeichen erlaubt)

1 Like

Hallo Michael_x

Vielen Dank für Deine Antwort! Dein Vorschlag funktioniert. Ich habe ihn folgendermaßen eingebaut:

for(int count=0; count<=21; count++)
    {
      //Vari[count]= int((myFile.read()));  //Das Array wird aus der Datei gefüllt
      
      Vari[count]= int(myFile.parseInt());  //Das sorgt dafür, das die Werte als Integer einzeln ausgegeben werden
    }

Hundertprozentig verstanden habe ich es aber noch nicht. Offenbar funktioniert er aber auch mit anderen "Trennzeichen". Ich hatte es mal mit "\t" versucht und das schien auch zu gehen.

Das klingt nach C++. Da kenne ich mich bisher noch nicht aus. Hast Du vielleicht einen Vorschlag, wo man den Umgang damit lernen kann (Buch oder online-Tutorial) und die Sprache einigermaßen verständlich ist?

Danke auch an Tommy!

Ich glaube, Deinen Vorschlag habe ich auch verstanden. Der Vorschlag von Michael scheint mit aber leichter umsetzbar, da er ohne zusätzliche Programmzeilen auskommt.

Das ist allerdings Absicht. Ich möchte die Windwerte über längere Zeit (den ganzen Sommer über) in der Datei sammeln. Aber nur die letzten aktuellen Werte sollen durch den Arduino auf dem Display angezeigt werden.

In diesem Zusammenhang daher noch eine Frage:
Gibt es eine einfache Möchlichkeit beim Lesen der Daten ein "Last in First out" zu realisieren? Zur Anzeige möchte ich ja nur die zuletzt gespeicherten Werte.

Noch 2 weitere einfache Fragen:

  1. ist es so, dass der Dateiname maximal 8 Zeichen lang sein darf (wie früher bei DOS)?
  2. Ändert das Datei-Surfix (z.B. ".txt" irgendetwas an der Art wie die Daten gespeichert werden oder ist das völlig beliebig (ich könnte auch ".and" nehmen)?

Nein und schon überhaupt nicht mit Deiner Art der Speicherung.
Wenn Du Werte überspringen willst, dann müssen alle "Datensätze" (Deine Zahlen) gleich lang sein, damit Du aus der Länge der Datei ausrechnen kannst, wie viele Datensätze drin sind und damit wie viele Bytes Du überspringen musst (mit seek).

Komplexere Variante mit Deiner Speicherart:
Du liest die ersten 21 Zahlen in ein Array ein.
Solange noch was da ist, liest Du das in eine Variable, verschiebst den Arrayinhalt um 1 Wert nach links und schreibst den gerade gelesenen Wert an die letzte Stelle.

  1. ja
  2. nein

Gruß Tommy

Da meine Anwendung nicht zeitkritisch ist, erscheint es mir einfacher zu sein, zwei Dateien zu verwenden. Eine für die Langzeitspeicherung in der immer das nächste aktuelle Wert gespeichert wird und eine, die die letzten aktuellen Werte "puffert".
In der Praxis werden es dann 320 Werte - für jede Pixelspalte einer.

Ich vermute, dass man nicht zwei Dateien gleichzeitig öffnen darf - richtig?
Also erst in eine schreiben, die dann schließen und dann die andere aufmachen und benutzen.

richtig.

Gruß Tommy

Gut beobachtet. :smirk:
"Die Arduino-Sprache" ist C++ und du verwendest sie ohne Hemmungen z.B. bei Serial.println(var); wo Serial ein Objekt der Klasse HardwareSerial ist.
Die allermeisten Libraries stellen ihre Funktionen als Methoden einer Klasse zur Verfügung.
Wie man das am besten lernt ist individuell verschieden, da gab es auch hier schon Diskussionen.
C++ Lehrbücher kennen normalerweise keinen Arduino und ein "Wie programmiere ich einen Arduino" lässt Fragen zu C++ eher wenig beleuchtet.
Augen auf halten, nachsehen und verwenden, was man gesehen hat.

Aber: Schwimmen habe ich mit der Methode
"Wer nicht schwimmen kann, muss tauchen"
gelernt. Irgendwie kriegt man irgendwann raus, wie das mit dem Luftholen geht. Ob ich ein guter Ratgeber bin, kann sich jeder selbst ausdenken.

P.S. Man dürfte zwar zwei Dateien gleichzeitig öffnen, aber dafür hat ein atmega328 nicht genug RAM.
8.3 Dateinamen sind nicht für DOS erfunden worden, dieser Standard wurde damals auch dort verwendet. Notgedrungen müssen SD Libraries heutzutage Speicher > 2GB unterstützen, weil es so kleine leider gar nicht mehr gibt, obwohl so viel Speicher völlig oversized ist.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.