Daten einer Init Datei von SD Karte auslesen

Hallo Forum,

ich habe hier schon viele gute und hilfreiche Informationen gefunden, doch an dieser Stelle habe ich nun wirklich nichts passendes gefunden. Somit wollte ich einen neuen Beitrag eröffnen.

Meine Aufgabe ist einen Datenlogger zu entwickeln an dem unterschiedlich viele Sensoren angeschlossen werden können. Der Datenlogger besteht aus einem Arduino UNO einer SD Karte und einer RTC. Über eine Init Datei auf der SD Karte soll die Konfiguration bestimmt werden.

Die Init Datei könnte so aussehen:

NTC: 5;
MD: 3;
HMD: 2;

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

File myFile;

char Init [100];
int i = 0;

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("Initializing SD card...");

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");

  // 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("INI.txt");
  if (myFile) {
    Serial.println("INI.txt:");
    // read from the file until there's nothing else in it:
    while (myFile.available()) {
      char ltr = myFile.read();
      Init [i] += ltr;
      i++;
      //Serial.write(myFile.read());
    }
    i = 0;
    // close the file:
    myFile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
  Serial.println(Init);
}

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

Ich habe den Code geschrieben und er gibt mir als Ausgabe:

Initializing SD card...initialization done.
INI.txt:
NTC: 5;
MD: 3;
HMD: 2;

Das ist soweit richtig. Jetzt ist aber meine Frage, wie ich die 5, die 3 und die 2 als Integer Werte bekomme, sodass ich sie als Konfigurationsvariablen verwenden kann.
Die Zahlen können auch 2-stellig werden. Kann ich irgendwie nummerische werte in meinem char suchen? Habe da so Funktionen gefunden die aber schlecht für den RAM sind. Hat einer eine Idee für mein Problem.

Wenn Deine Datei Zeilenweise organisiert ist, suchst Du readBytesUntil sowie strtok und atoi (oder sprintf - braucht etwas mehr Speicher).

Die kannst Du dann weglassen.

Gruß Tommy

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

File myFile;

char Init [100];
int i = 0;

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("Initializing SD card...");

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");

  // 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("INI.txt");
  if (myFile) {
    Serial.println("INI.txt:");
    // read from the file until there's nothing else in it:
    while (myFile.available()) {
      char ltr = myFile.read();
      Init [i] += ltr;
      i++;
      //Serial.write(myFile.read());
    }
    i = 0;
    // close the file:
    myFile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
  //Serial.println(Init);
int NTC =   readBytesUntil(";", Init, 10);
  //int NTC = atof(strtok(Init, ";"));
  //int MD = atof(strtok(NULL, ";"));
  Serial.println(NTC);
  //Serial.println(MD);
  //Serial.println(Init);
}

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

Was mach ich mit der readBytesUntil() Funktion falsch?

Schau Dir mal die Verwendung an. File erbt von stream. Du musst es also als

myFile.readBytesUntil('\n',Init,10)

anwenden.

Ich hatte auch geschrieben, das ';' weg lassen und aufs Zeilenende abprüfen.

Gruß Tommy

  int NTC =   myFile.readBytesUntil('\n', Init, 10) ;

Da ist NTC bei mir immer 0.

Weil Du Dich nicht damit beschäftigt hast, was ReadBytesUntil macht, sondern einfach irgendwas hinschreibst.
Es gibt viele Beispiele zu readBytesUntil mit Serial. Das Vorgehen ist das bei File das Gleiche. Es liest Zeichen in den Puffer Init. Beispiele wird Dir Google suchen.

Lass Dir Init per Serial.print anzeigen. Dann zerlege ihn am ':' (strtok) und wandle den 2. Teil in eine Zahl um (atoi). Einige Infos zur Zerlegung gibt es hier.

Gruß Tommy

So ich habe jetzt mal mein Problem in ein eigenes Programm ausgelagert um es zu testen.
Meine Annahme ist, dass die Textdatei in meinem Char drin ist.

Kann ich den Char nach nummerischen Werten durchsuchen?

char eingabe[] = "NTC:  5;MD:   3;HMD: 2;";
char trennzeichen[] = ":;";
char *ptr;
int NTC;

void setup() {
  Serial.begin(9600);
  Serial.println(eingabe);
}

void loop() {
  // Erster Versuch
  ptr = strtok(eingabe, trennzeichen);
  
  // solange was gefunden wurde
  while (ptr != NULL) {
    Serial.println(ptr);  
    ptr = strtok(NULL, trennzeichen);
    NTC=atoi(ptr);
  }
  //NTC = atoi(ptr);
  Serial.print("NTC = ");
  Serial.println(NTC);
  delay(1000);
}

Ausgabe:

NTC: 5;MD: 3;HMD: 2;
NTC
5
MD
3
HMD
2
NTC = 0
NTC
....

Ich kann die Zahlen einzeln schreiben aber wie finde ich raus ob gerade eine Zahl da ist? und wenn ja dann speicher sie mir in die Variablen NTC, MD und HMD.

Gibt es ein
if(ptr == numeric)
{
NTC=atoi(ptr);
}

Du brauchst doch nur mitzählen, beim wievielten Wert Du gerade bist.

Gruß Tommy

Meine Annahme ist, dass die Textdatei in meinem Char drin ist.
Kann ich den Char nach nummerischen Werten durchsuchen?

if (ptr == numeric) geht leider nicht.

Tommys Vorschlag, den Firlefanz "NTC:" usw. zu ignorieren und

  • den 2. Teil per   int ntc =atoi(ptr);
  • den 4. Teil per   int md  =atoi(ptr);
  • den 6. Teil per   int hmd =atoi(ptr);
    auszuwerten, ist am einfachsten.

Alternativ könntest du eingabe erstmal nach ';' aufteilen. Dann jedes Pärchen am ':' trennen, den ersten Teil per strcmp untersuchen und je nach Ergebnis den zweiten Teil der passenden Variablen zuweisen.

Aber da im Problemfall keiner zuschaut oder sinnvoll darauf reagieren könnte, ist einfach auch gut.
Oder steht zu befürchten, dass die Reihenfolge der Werte sich ändert?


Du machst übrigens einen schlechten Eindruck, wenn du von deinem Char schreibst, aber eigentlich ein char array oder ein char* ( in diesem Fall char eingabe[], bzw. char* ptr ) meinst.

Puhhh das war aber was.... Danke für die Tipps habe jetzt was fertig gemacht.

char eingabe[] = "NTC:  5;MD:   3;HMD: 2;";
char trennzeichen[] = ":;";
char *ptr;
int NTC, MD, HMD;
int i = 1;

void setup() {
  Serial.begin(9600);
  Serial.println(eingabe);
}

void loop() {
  // Erster Versuch
  ptr = strtok(eingabe, trennzeichen);

  // solange was gefunden wurde
  while (ptr != NULL) {
    ptr = strtok(NULL, trennzeichen);
    i++;
    if (i  == 2)
      NTC = atoi(ptr);
    if (i ==4)
      MD = atoi(ptr);
    if (i ==6)
      HMD = atoi(ptr);
  }
  i = 0;

  Serial.print("NTC = " );
  Serial.println(NTC);
  Serial.print("MD = " );
  Serial.println(MD);
  Serial.print("HMD = " );
  Serial.println(HMD);
  delay(1000);
}

Ausgabe:
NTC: 5;MD: 3;HMD: 2;
NTC = 2
MD = 3
HMD = 2

Bin für Verbesserungen gerne offen.

michael_x:
Alternativ könntest du eingabe erstmal nach ';' aufteilen. Dann jedes Pärchen am ':' trennen, den ersten Teil per strcmp untersuchen und je nach Ergebnis den zweiten Teil der passenden Variablen zuweisen.

Das geht auch noch einfacher wenn jeder Wert in einer eigenen Zeile steht. strtok() ist dann gar nicht nötig:

char str1[] = "NTC:  5";
char str2[] = "MD: 3";

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

  if (strncmp(str1, "NTC", 3) == 0)
  {
    int val = atoi(str1 + 4);
    Serial.println(val);
  }

  if (strncmp(str2, "MD", 2) == 0)
  {
    int val = atoi(str2 + 3);
    Serial.println(val);
  }
}

void loop()
{
}

Die Länge des Anfangs ist bekannt. Wieso also irgendetwas splitten? Man vergleicht den Anfang in verwendet dann Zeiger-Arithmetik um den zu überspringen

Noch einfach geht es wenn man Kommandos und Identifizierer auf einen einzelnen Buchstaben reduziert. Dann kann man switch/case verwenden

if (i % 2 == 0)

ist true bei 2, 4 und 6.

Daher wird NTC zweimal überschrieben und ist zum Schluss auf dem Wert 2.
Außerdem ist % generell erheblich komplizierter als eine Abfrage wie (i == 2), wenn der Compiler das nicht schlauerweise als gerade/ungerade interpretiert und in Wirklichkeit ein (i & 1) draus macht.

Und du solltest klären, ob du mit i = 1 oder mit i = 0 anfangen willst.

michael_x:

if (i % 2 == 0)

ist true bei 2, 4 und 6.

Jo danke auch gesehen und angepasst in meinem geposteten Quellcode. Hatte erst eine Modulo Idee im Kopf daher der Fehler.

Jetzt habe ich noch ein Problem welches ich mir nicht erklären kann.

Wenn ich das INIT char (char Init [130]) global deklariere läuft das Programm durch, wenn ich es wie im zweiten Code nur in der Funktion deklariere bekomme ich nur Mist als Ausgabe. Ich verwende den char aber nur in dem Unterprogramm.

Wo habe ich nen Denkfehler?

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

File myFile;

int NTC, MD, HMD;
char Init [130];

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("Initializing SD card...");

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");
  fINI();
  int _iaNTC[NTC];
  int _iaMD[MD];
  int _iaHMD[HMD];

  for (int i = 0; i < NTC; i++)
    _iaNTC[i] = analogRead(i);

  for (int i = NTC; i < (NTC + MD); i++)
    _iaMD[i] = analogRead(i);

  for (int i = (NTC + MD); i < (NTC + MD + HMD); i++)
    _iaHMD[i] = analogRead(i);

  Serial.println("NTC");
  for (int i = 0; i < NTC; i++)
    Serial.println(_iaNTC[i]);

  Serial.println("MD");
  for (int i = NTC; i < (NTC + MD); i++)
    Serial.println(_iaMD[i]);

  Serial.println("HMD");
  for (int i = (NTC + MD); i < (NTC + MD + HMD); i++)
    Serial.println(_iaHMD[i]);

  Serial.println("");
  Serial.print("NTC = " );
  Serial.println(NTC);
  Serial.print("MD = " );
  Serial.println(MD);
  Serial.print("HMD = " );
  Serial.println(HMD);

}

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

void fINI()
{
  char trennzeichen[] = "=\n";
  int i = 0;
  int j = 1;

  char *ptr;
  // 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("INI.txt");
  if (myFile) {
    Serial.println("INI.txt:");
    // read from the file until there's nothing else in it:
    while (myFile.available()) {
      char ltr = myFile.read();
      Init [i] += ltr;
      i++;
    }
    i = 0;
    ptr = strtok(Init, trennzeichen);
    // solange was gefunden wurde
    while (ptr != NULL) {
      ptr = strtok(NULL, trennzeichen);
      Serial.println(ptr);
      Serial.print("j=");
      Serial.println(j);
      j++;
      if (j  == 11)
        NTC = atoi(ptr);
      if (j  == 13)
        MD = atoi(ptr);
      if (j  == 15)
        HMD = atoi(ptr);
    }
    Serial.println(" ");
    Serial.println(" ");
    j = 0;

    // close the file:
    myFile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }

  delay(1000);
}
Initializing SD card...initialization done.
INI.txt:
Datum 
j=1
 "03.05.2019 08:14:40"

j=2
Test 
j=3
 "Test2"

j=4
Test1 
j=5
 "Test3"

j=6


j=7
[Sensoren]

j=8
NTC 
j=9
 2    

j=10
MD 
j=11
 1    

j=12
HMD 
j=13
 0  
j=14

j=15
 
 
NTC
661
475
MD
475
HMD

NTC = 2
MD = 1
HMD = 0
#include <SPI.h>
#include <SD.h>

File myFile;

int NTC, MD, HMD;


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("Initializing SD card...");

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");
  fINI();
  int _iaNTC[NTC];
  int _iaMD[MD];
  int _iaHMD[HMD];

  for (int i = 0; i < NTC; i++)
    _iaNTC[i] = analogRead(i);

  for (int i = NTC; i < (NTC + MD); i++)
    _iaMD[i] = analogRead(i);

  for (int i = (NTC + MD); i < (NTC + MD + HMD); i++)
    _iaHMD[i] = analogRead(i);

  Serial.println("NTC");
  for (int i = 0; i < NTC; i++)
    Serial.println(_iaNTC[i]);

  Serial.println("MD");
  for (int i = NTC; i < (NTC + MD); i++)
    Serial.println(_iaMD[i]);

  Serial.println("HMD");
  for (int i = (NTC + MD); i < (NTC + MD + HMD); i++)
    Serial.println(_iaHMD[i]);

  Serial.println("");
  Serial.print("NTC = " );
  Serial.println(NTC);
  Serial.print("MD = " );
  Serial.println(MD);
  Serial.print("HMD = " );
  Serial.println(HMD);

}

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

void fINI()
{
  char trennzeichen[] = "=\n";
  int i = 0;
  int j = 1;
char Init [130];
  char *ptr;
  // 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("INI.txt");
  if (myFile) {
    Serial.println("INI.txt:");
    // read from the file until there's nothing else in it:
    while (myFile.available()) {
      char ltr = myFile.read();
      Init [i] += ltr;
      i++;
    }
    i = 0;
    ptr = strtok(Init, trennzeichen);
    // solange was gefunden wurde
    while (ptr != NULL) {
      ptr = strtok(NULL, trennzeichen);
      Serial.println(ptr);
      Serial.print("j=");
      Serial.println(j);
      j++;
      if (j  == 11)
        NTC = atoi(ptr);
      if (j  == 13)
        MD = atoi(ptr);
      if (j  == 15)
        HMD = atoi(ptr);
    }
    Serial.println(" ");
    Serial.println(" ");
    j = 0;

    // close the file:
    myFile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }

  delay(1000);
}
⸮Initializing SD card...initialization done.
INI.txt:
Datum } "
j=1
⸮.05t501⸮$08D⸮
j=2
68⸮4 Zv⸮dulu
j=3
"+1z⸮dvkt2,7Kunnq⸮⸮J⸮%]",⸮~jh⸮I;
hSfoWow⸮x
⸮R⸮C A⸮6⸮  (⸮⸮D3⸮B2  !!

j=4
HMD 
j=5
 0B!  
j=6

j=7
 
 
NTC
MD
HMD

NTC = 0
MD = 0
HMD = 0

Der erste Code dürfte auch nur zufällig funktionieren, wenn überhaupt.

Init [i] += ltr; 
i++;

ist Unsinn. Überlege mal was += an dieser Stelle macht.

Besser:

Init[i++]=ltr;

Gruß Tommy

Der erste Code funktioniert, weil globale Variable mit 0 initialisiert sind.

Dass Tommys  Init[i++]=ltr;besser ist, stimmt aber.

Bei einem lokalen char Init[130] müsste zum Schluss der Text noch mit
  Init[ i ]=0;
abgeschlossen werden.

Richtig. Global mit 0 initialisiert, lokal zufälliger Inhalt.

Gruß Tommy

Ok Danke Thommy da habe ich noch nicht drüber nachgedacht, aber das macht auch Sinn und funktioniert genau so. UND jetzt kann ich mein Char unten deklarieren in meiner Funktion und es geht auch. Spart satt auch satt Speicher.

Danke :grin:

Spart satt auch satt Speicher.

Das täuscht etwas. Die lokalen 130 byte werden beim Compilieren nicht mitgezählt, werden aber gebraucht, wenn der RAM sowieso am knappsten ist: Während die Datei geöffnet ist.

Wirklich hilft es nur, wenn du außerhalb von fIni() andere Funktionen hast, die ähnlich großen Bedarf an lokalen Variablen haben.

Wenn es geht, ist es gut.

Ansonsten wäre es sparsamer, nicht den ganzen Text der Datei erst zu lesen (kopieren) und dann die 3 int Variablen zu bestimmen. Leider müsste man dazu eine Alternative für strtok() finden/machen oder das Parsen ganz anders realisieren.

Ja das ganze ist ein wenig aufwendig mit dem kompletten einlesen nur um die 3 INI Werte auszulesen. Das ist mir auch schon aufgefallen.

Wie kann ich die Größe meines Array dynamisch machen? bei char Init[] kommt eine Fehlermeldung. Wenn ich den Bereich vergrößere (char Init[160]) bekomme ich in den leeren Speicherstellen immer irgendwelchen mit angezeigt beim Serial.print(Init).