In SD Datei sofort zur letzten Zeile springen, wie ?

In einer Datei auf der SD-Karte wird für jede neue Zeile die ganze Information an Konfigurationen gespeichert.
Jedesmal wenn eine neue Konfiguration gespeichert wird, fügt Arduino eine neue Zeile hinzu.

Wie kann ich jetzt, wenn ich die Datei auf die SD Karte aufmache, sofort zur letzten Zeile springen und nur dort einlesen ?

Mein Code sieht erst mal so aus:

configFile = SD.open("conffile.txt");
if (configFile) {
Serial.println("conffile.txt:");

// read from the file until there's nothing else in it:
while (configFile.available()) {
int i = configFile.read();

Serial.write(i);
if( i == '1' )
Serial.write("!");
}
// close the file:
configFile.close();
} else {
// if the file didn't open, print an error:
Serial.println("error opening conffile.txt");
}

Liefert configFile.available() "false" zurück, so kann man zwar herausfinden dass er am Ende gekommen ist, aber dann bin ich aus der while-Funktion schon raus. Und wenn ich jedesmal eine Zeile vorher in eine Variable gespeichert habe, damit ich DIE noch ausserhalb der Schleife habe, dann wäre das performance-Verlust.

Später möchte ich mit scanf auslesen

Da die "Zeilen" unterschiedlich lang sein werden, gibt es keine Möglichkeit an den Anfang der letzten Zeile zu springen. Es sei denn, du hättest dir die Position irgendwo gespeichert.

Generell kann man die aktuelle Position mit position() abfragen und abspeichern. Und mit seek() den Dateizeiger auf eine Position setzen. Dann kann man am Anfang der Zeile die Position speichern und solange Zeichen einlesen bis ein Linefeed kommt. Dann ist man am Ende der Zeile. Du weißt auch dass du am Ende der Datei bist wenn read() -1 liefert.
Du kannst also jeweils den Anfang jeder Zeile abspeichern. Dann solange Zeichen einlesen bis zu am Ende bist. Dann mit seek() auf den letzten Zeilenanfang springen. Ich verwende das um den Zeiger auf bestimmte Zeilennummern zu setzen.

Eine Alternative wäre wenn du es schaffst jede Zeile genau gleich lang zu machen (z.B. indem du Zahlen auf eine Konstante Breite formatierst). Dann weißt du genau um wie viele Zeichen du zurück musst wenn der Dateizeiger am Ende steht (was man schon beim Öffnen erreichen kann).

Zahlen die mit einem Delimiter getrennt sind kann man übrigens schön mit strtok() und atoi() Parsen. Das ist wesentlich speicher-schonender als scanf()

Ok danke für die wichtigen Funktionen seek und position.

Verstehe ich das so richtig:

Die Position vom Anfang der letzen Zeile in eine Variable abspeichern. Mit seek auf 0 gehen, und dort die Position aus der Variable schreiben.

Beim einlesen wieder, liest er gleich am Anfang die Position ein und springt sofort zur Anfang der letzten Zeile ?

Macht irgendwie Sinn.

Wenn es Codebeispiele gibt, würde ich mir das gerne anschauen, ob die vielleicht noch eleganter geschrieben sind.

Nicht ganz. Gib mal etwas Zeit dann sehe ich mal ob ich was fabrizieren kann. Code wie man eine bestimmte Zeile aus einer Datei ausliest habe ich schon. Das sollte sich anpassen lassen.

Ich habe viel zu weit ausgeholt. :slight_smile:
Die Position braucht zum Lesen man gar nicht. Man muss nur solange zeilenweise auslesen bis die Datei zu Ende ist. Die letzte Zeile hat man damit automatisch. Eigentlich offensichtlich.

Die Position braucht man wenn man eine Zeile überschreiben will. Dann muss man zurückspringen.

Lesen geht ganz einfach so:

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

const unsigned int STRING_BUFFER_SIZE = 31;
char string_buffer[STRING_BUFFER_SIZE];

const char filename[] = "test.txt";

void setup()
{
  Serial.begin(9600);
  SD.begin(SS);
  delay(1000);


  File file = SD.open(filename, FILE_READ);
  get_last_line(file);

  Serial.print(F("last line: "));
  Serial.println(string_buffer);

  parse();

  if (file)
    file.close();
}

void loop()
{
}

bool read_line(File& file)
{
  static unsigned int index;

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

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

  return false;
}

bool get_last_line(File& file)
{
  if (file) //wenn Datei geöffnet
  {
    while (read_line(file)) //Zeile auslesen. Gibt false zurück wenn am Ende der Datei
      ;

    return true;
  }
  return false;
}

void parse()
{
  int value1 = atoi(strtok(string_buffer, ","));
  int value2 = atoi(strtok(NULL, ","));
  int value3 = atoi(strtok(NULL, ","));
  int value4 = atoi(strtok(NULL, ","));

  Serial.println(F("\nvalues:"));
  Serial.println(value1);
  Serial.println(value2);
  Serial.println(value3);
  Serial.println(value4);
}

Die Test-Datei sind Zeilen mit jeweils 4 Integern. z.B. so:

10,0,41,17
20,-3,40,17
30,0,50,20
40,10,50,4
50,-10,100,400

Achtung: damit das sicher funktioniert, sollte die Datei am Ende der letzten Zeile nochmal einen Zeilenumbruch haben!! Sonst wird zwar der String eingelesen aber nicht terminiert und der Index wird nicht zurückgesetzt.

Diese Fehlerquelle könnte man wahrscheinlich hiermit ausschließen:

else if (c == -1 || c == '\n')

Bei vielen Zeilen kann man auch die Datei von hinten lesen, bis man ein Zeilenende findet - danach beginnt dann die letzte Zeile.

Oder wenn eine maximale Zeilenlänge bekannt ist, so viel vor EOF mit dem Lesen anfangen.

Serenifly:
Ich habe viel zu weit ausgeholt. :slight_smile:

Whow echt vielen Dank! Hätte nicht gedacht dass jemand den Code hier doch zeigt. Danke für die Mühe. Ich werde auch nochmal ein bißchen abändern aber im Großen und Ganzen kann ich das schon mal übernehmen.

So stell ich mir Zusammenhalt vor. Klar kann ich auch andere Methoden versuchen.