Problem beim Auslesen von einzelner Zeile (SD Karte)

Hallo,

ich habe ein Problem und hoffe das ihr mir weiterhelfen könnt :slight_smile:

Ich möchte Analoge Messwerte mit aktueller Zeit mehrmals die Sekunde auf einer SD Karte speichern und bei Bedarf die Werte zu einer bestimmten Zeit wieder auslesen.

Mittlerweile bin ich soweit das die Daten auf der SD Karte in einer .txt als String gespeichert werden. Das sieht dann ungefähr so aus:

Jahr/Monat/Tag/Stunde/Minute/Sekunde/Analog0/Analog1/Analog2

16;10;20;15;29;47;172;598;509
16;10;20;15;29;47;211;703;426
16;10;20;15;29;48;633;350;188
16;10;20;15;29;48;678;354;396

Das Speichern funktioniert problemlos, allerdings weiß ich nicht wie ich zum Beispiel unter 5000 einzelnen Messwerten, eine Zeile auslese, bzw. wenn ich die Messwerte von 16;10;20;14;03;00 auslesen möchte und diese in einer Variable speichern möchte.
Ich denke ein Problem sind die vielen Daten, wenn man zum Beispiel jede Zeile ausliest und in einem String speichert und dann überprüft ob die gewünschten Messwerte dabei sind.

Hat jemand eine Idee/Tipps?
Kann man die Messwerte vielleicht anders abspeichern um sie nachher besser auszulesen?
Ich bin für jede Antwort dankbar! :slight_smile:

MfG
Johnsen

Nimm sdfat als Library

Da findest du in den Beispielen das Gesuchte

Eine Zeile in einen C String einlesen und dann mit strtok() splitten

Feste Datensatzgrößen verwenden.
Damit bekommst du einen Index welchen du als Seek Wert verwenden kannst. Damit kannst du das Suchen beschleunigen. Also auch mal ein paar Tausend Datensätze überspringen.

Also auch mal ein paar Tausend Datensätze überspringen

Das würde ich nicht überbewerten :wink:
Solange zeilenweise lesen, bis der Zeitstempel passt, geht recht flott. Zumal im Hintergrund sowieso ganze Sektoren (512 byte) von der SD gelesen und gepuffert werden.

Generelle Grundregel zum Optimieren:
Optimieren sollte man erst, wenn es funktioniert und erwiesenermaßen zu langsam ist. (Also jetzt nicht)

Vielen Dank für die Hilfe :slight_smile:
Ich werde es so ausprobieren und mich gegebenenfalls noch einmal melden.

Es ist für die anderen Teilnehmer immer sehr brauchbar, wenn am Ende ein Rückmeldung kommt, ob es funktioniert und wie.

Gruß Tommy

Ich habe die sdfat Library genommen und es funktioniert soweit. Allerdings dauert es ungefähr 4 Minuten bis ich einen Wert der an 100000 Stelle ist, gefunden habe.

Gibt es noch eine Möglichkeit das Ganze zu beschleunigen oder ist da die Grenze erreicht?

Hier der aktuelle Code:

const int chipSelect = 4;

#include <SPI.h>
#include "SdFat.h"
SdFat sd;
SdFile myFile;



int Anzahl = 0; 
char Zeile[70];
char Zeichen;

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    SysCall::yield();
  }
  Serial.println("Type any character to start");
  while (!Serial.available()) {
    SysCall::yield();
  }

  
  if (!sd.begin(chipSelect, SPI_HALF_SPEED)) {
    sd.initErrorHalt();
  }

if (!myFile.open("messdata.txt", O_READ)) {
    sd.errorHalt("messdata.txt kann nicht gelesen werden");
  }


while (myFile.available())
{
    
    Zeichen = myFile.read();
    

    if ((Zeichen=='\n') || (Zeichen=='\r'))
    {
      Anzahl = 0;
      
      String out = Zeile;
     if ((out.substring(0,2) == "16") && (out.substring(3,5) == "11"))
     {
      Serial.println(out);
     }
           
      
      
      Zeile[0] = '\0';
      
    }
    else
    {
     Zeile[Anzahl] = Zeichen;
     Anzahl++;                     
    }
 
}

}

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

Es gibt die Methode fgets() um eine Zeile in einen C String einzulesen. Und dann arbeite direkt mit diesem weiter und spare dir die String Klasse. z.B. etwas Zeiger-Arithmetik und strncmp(). Oder strtok() und strncmp()

Was nicht gut ist das:

if ((Zeichen=='\n') || (Zeichen=='\r'))

Zeilen werden mit CR + LF abgeschlossen. Das ist also zweimal wahr. Und danach machst du zweimal String.substring() was sehr langsam ist.
Aber wie gesagt am besten direkt fgets() nehmen und die Standard C String Funktionen. Mit der String Klasse tust du dir keinen Gefallen wenn das so oft gemacht wird.

Wenn du die gewünschte Zeilennummer kennst, mache was combie vorgeschlagen hat. Alle Datensätze auf eine konstante Breite formatieren (z.B. drei Ziffern mit führenden Nullen) und dann einfach den Dateizeiger per seekSet() auf die gewünschte Zeile setzen. Schneller geht es nicht.
Wenn du aber nicht weißt wo der Wert steht bringt es nichts.

Hier mal als Beispiel wie simpel der Vergleich mit C Strings ist:

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

  char str[] = "16;10;20;15;29;47;172;598;509";

  checkString(str, "16");
  checkString(str + 3, "10");

  checkString(str, "14");
  checkString(str + 3, "11");

}

void loop() 
{
}

void checkString(const char* str, const char* compare)
{
  if (strncmp(str, compare, 2) == 0)
    Serial.println(F("gleich"));
  else
    Serial.println(F("nicht gleich"));
}

Da wird nirgends zusätzlicher Speicher angelegt

Eventuell muss man aber die Daten von der SD Karte erst mal mit atoi() in Integer wandeln und Integer vergleichen. Kommt darauf an wo die Vergleichswerte genau herkommen. Geht aber genauso mit Zeigerarithmetik: atoi(str) und atoi(str + 3)

Allerdings dauert es ungefähr 4 Minuten bis ich einen Wert der an 100000 Stelle ist, gefunden habe.

Der hunderttausendste Buchstabe oder die hunderttausendste Zeile?
Na egal, ich wette, ohne String und substring() mit Serenifly's Vorschlag ist "die SD Karte" schon deutlich schneller :wink:
Wenn die Zeitstempel-Werte "16;10;20;15;29;47;" immer gleich breit sind (also mit führenden Nullen), kannst du auch sehr leicht spezielle Wandlungs- und/oder Vergleichsfunktionen bauen, die sogar schneller als strncmp oder atoi wären.

michael_x:
Wenn die Zeitstempel-Werte "16;10;20;15;29;47;" immer gleich breit sind (also mit führenden Nullen), kannst du auch sehr leicht spezielle Wandlungs- und/oder Vergleichsfunktionen bauen, die sogar schneller als strncmp oder atoi wären.

Am Anfang soll wohl das Datum verglichen werden. Das wären immer zwei Ziffern wenn man führende Nullen verwenden (ohne das geht auch subString() nicht). Da könnte man in einer inline Funktion (damit man sich den Funktionsaufruf spart) einfach jeweils die zwei Zeichen direkt vergleichen.

ohne das geht auch subString() nicht

Auch wieder wahr. :wink:

Hier also ein Beispiel, speziell für unseren Fall.
Sollte schneller laufen als strcmp oder atoi.
Bleibt auszuprobieren, ob der Unterschied merkbar ist, wenn sehr viele Zeitwerte abgesucht werden müssen.

  char zeit[] = "16;10;31;22;00;00";
  byte cmp[] = {16, 11, 1, 0, 0, 0} ; // Vergleichswert: 2016-Nov-01

  if (check(zeit, cmp) ) tuwas(); // zeit >= Vergleichswert
...
bool check( char* text, byte cmp[]) {
// liefert true, wenn text im Format "jj;MM;dd;hh;mm;ss" 
// einen Zeitstempel >= cmp darstellt
   
   for (byte i = 0; i < 6; i++) {
     if (((*text-'0')*10 + *(text+1)-'0') < cmp[i]) return false;
     text+=3;
   }
   return true;
}

nur compiliert, ungetestet

michael_x:
Das würde ich nicht überbewerten :wink:
Solange zeilenweise lesen, bis der Zeitstempel passt, geht recht flott.

Na na...
Wie du siehst, eben nicht.

Frage: (rhetorisch)
Warum arbeiten Datenbanken bevorzugt mit fester Datensatzgröße?
Warum legen Datenbanken einen Index an?

Richtig!
Um schnell in großen Datenmengen rein greifen zu können.

Vorschlag: (eine von vielen Möglichkeiten)
Verwende die DBase Struktur/Verfahren.
Das bekommt man auf einem AVR noch recht gut hin.
Und kann dann auch noch vom PC gelesen werden.

Was wirklich Zeit dauert, ist das Kopieren eines Sektors von der SD in den RAM. Keine Ahnung wie du es schaffen willst, da wesentliche Mengen zu überspringen: Binär suchen statt sequentiell lesen?

Wann willst du den Index erzeugen, der dir zu jedem Zeitstempel die absolute Position (oder auch die Satznummer bei festen Satzlängen) liefert? Und wo willst du die abspeichern?

Vorschlag: (eine von vielen Möglichkeiten)
Verwende die DBase Struktur/Verfahren.
Das bekommt man auf einem AVR noch recht gut hin.

Ein Uno mit 2kB RAM kann nur 1 Sektor offen haben (max. einen zweiten für die Dateiverwaltung )
“dBase for Uno” wäre eine echte Herausforderung. Kriegst auch ein Karma von mir, wenn du es vorführst. :wink:

Juut....

DBase habe ich gewählt, weil:

  1. es ist eine professionelle, weit verbreitete Datenstruktur/System. Die Daten sind portabel.
  2. es ist das primitivste auf dem Gebiet (am ehesten noch auf einem AVR machbar)

Außerdem bleibts ja dem TE überlassen, wie er es denn tut.

Der Index ist optional, kann nach belieben gelöscht und wieder konstruiert werden.
Möglichkeiten als Vorschlag:

  1. Indexeintrag beim Tageswechsel
  2. Indexeintrag beim Stundenwechsel
    (das kann man auch mit CSV Dateien veranstalten)

Das reduziert die Suchoperation auf ein Bruchteil dessen, was Suchen in einer Textdatei mit variabler Stringlänge verursacht.

Die DBase File Header kann man vorher zusammenklöppeln, und entweder im Flash ablegen, oder schon vom PC/Excel schreiben lassen.
Achtung: "Number of bytes in the record" muss stetig aktualisiert werden.
Alternativ 1: Am Ende einer Sitzung.
Alternativ 2: Mit einem Hilfsprogramm bevor man das einer anderen DB oder Excel vorwirft.

"dBase for Uno" wäre eine echte Herausforderung.

Wenn der UNO auch die Strukturierung verwalten soll, dann ja. (ist hier nicht nötig)

Soll UNO aber nur mit einer festen Struktur arbeiten, also der Programmierer übernimmt die DBMS Aufgaben, sehe ich da gar kein Problem.

Schreiben verzögert sich um die Index Erstellung.
Lesen und Suchen geht im Gegensatz zu Textdateien, rasend schnell.

Hilfreich ist sicherlich ein geordneter "Shutdown", bevor man den Stecker zieht.
Aber das ist sowieso so, sobald mit Schreibpuffern gearbeitet wird.