Probleme beim Öffnen einer Datei auf einem SD-Kartenleser

Hallo zusammen,

ich nutze einen Arduino Micro in Verbindung mit dem “Adafruit Ultimate GPS Breakout v3” und dem “Adafruit Micro-SD Breakout board+”. Beide Komponenten können über die mitgelieferten Libaries problemlos angesprochen werden und erste Dateien mit Loginhalten habe ich bereits gespeichert. Nun scheitere ich aber daran, einen dynamischen Dateinamen an die SD-Klasse zu übergeben, wenn ich SD.open() verwende.

Um im empfohlenen 8.3 Dateinamen zu bleiben, möchte ich gerne folgende Dateien erstellen:

gpslg001.log

Statt 001 wird ein Zähler raufgezählt und soll bei bestimmten Breakpoints 002, 003 etc. werden. Dazu habe ich mir eine kleine Funktion geschrieben “zerofill()”, die den Counter nimmt, dessen Größe prüft und ein entsprechendes Konstrukt zurückliefert.

Nun ist es so, dass beim Aufrufen der Methode SD.open() ein Compilerfehler entsteht, der wie folgt aussieht:

invalid conversion from ‘char’ to ‘const char*’

Und ich habe keine Idee, wie ich das vernünftig lösen könnte, weil mir nicht klar ist, was ‘const char*’ in der Methodendefinition eigentlich ist. Vielleicht kann mir da jemand auf die Sprünge helfen? Ich bin noch so neu im Thema, dass mich die Vielzahl an Möglichkeiten bei der Definition von Variablen aktuell etwas erschlägt…

Danke im Voraus für die Anfänger-Unterstützung :slight_smile:

Hier mal mein kompletter Sketch zum Nachlesen:

#include <SoftwareSerial.h>
#include <Adafruit_GPS.h>


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

Sd2Card card;
SdVolume volume;
SdFile root;

const int chipSelect = 10;
File dataFile;


unsigned int rowcounter = 1;
unsigned int filenum = 1;
char fname;

char zerofill(int i) {
  char output;
  if(i < 10) {
    output = '00';
    output += i;
  } else if(i >= 10 && i < 100) {
    output = '0';
    output += i;
  } else {
    output = i;
  }
  return output;
}


Adafruit_GPS GPS(&Serial1);
HardwareSerial mySerial = Serial1;
#define GPSECHO  false

void setup() {
  Serial.begin(115200);
  while (!Serial); // wait for leo to be ready
  
  GPS.begin(9600);
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);
  GPS.sendCommand(PGCMD_ANTENNA);
  delay(500);
  
  pinMode(SS, OUTPUT);
  
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    while (1) ;
  }
 }


uint32_t timer = millis();
void loop() {

  char c = GPS.read();
  char buffer [20];

  if (GPS.newNMEAreceived()) {
    if (!GPS.parse(GPS.lastNMEA()))
      return;
  }
  if (timer > millis())  timer = millis();
  
  if (millis() - timer > 1000) { 
    timer = millis();
    
    if (GPS.fix) {
      
      if(rowcounter > 3600) {
        rowcounter = 1;
        filenum++;
      }
      
    
      fname = 'gpslg';
      fname += zerofill(filenum);
      fname += '.log';
      Serial.println(fname);
      
      dataFile = SD.open(fname, FILE_WRITE);
      if (dataFile) {
        
        // Starting JSON Object-Root
        dataFile.print("{");
        
        // Starting GPS Object
        dataFile.print("\"gps\":{");
        
        dataFile.print("\"utcD\":\"");
        dataFile.print(2000 + GPS.year, DEC);
        dataFile.print("-");
        dataFile.print(GPS.month, DEC);
        dataFile.print("-");
        dataFile.print(GPS.day, DEC);
        dataFile.print("\",");
        
        dataFile.print("\"utcT\":\"");
        dataFile.print(GPS.hour);
        dataFile.print(":");
        dataFile.print(GPS.minute);
        dataFile.print(":");
        dataFile.print(GPS.seconds);
        dataFile.print(".");
        dataFile.print(GPS.milliseconds);
        dataFile.print(":\",");
        
        dataFile.print("\"la\":\"");
        dataFile.print(GPS.lat);
        dataFile.print("\",");
        
        dataFile.print("\"lat\":\"");
        dataFile.print(dtostrf(GPS.latitude, 6, 4, buffer));
        dataFile.print("\",");
        
        dataFile.print("\"lo\":\"");
        dataFile.print(GPS.lon);
        dataFile.print("\",");
        
        dataFile.print("\"lon\":\"");
        dataFile.print(dtostrf(GPS.longitude, 6, 4, buffer));
        dataFile.print("\",");
        
        dataFile.print("\"alt\":\"");
        dataFile.print(GPS.altitude);
        dataFile.print("\",");
        
        dataFile.print("\"vKno\":\"");
        dataFile.print(GPS.speed);
        dataFile.print("\",");
        
        dataFile.print("\"vKmh\":\"");
        dataFile.print(GPS.speed * 1.852);
        dataFile.print("\",");

        dataFile.print("\"sats\":\"");
        dataFile.print(GPS.satellites);
        dataFile.print("\",");
        
        dataFile.print("\"fix\":\"");
        dataFile.print(GPS.fix);
        dataFile.print("\",");
        
        dataFile.print("\"fixq\":\"");
        dataFile.print(GPS.fixquality);
        dataFile.print("\"");
        
        // Ending GPS Object
        dataFile.print("}");
        
        // Ending JSON Object-Root
        dataFile.println("}");
        
        
        dataFile.close();
        
        rowcounter++;
      }
    }
  }
}

TConnect: Um im empfohlenen 8.3 Dateinamen zu bleiben, möchte ich gerne folgende Dateien erstellen:

gpslg001.log

Statt 001 wird ein Zähler raufgezählt

  char filename[13];
  int number=7;
  snprintf(filename,sizeof(filename),"gpslg%03d.log",number);

"snprintf" ist die "sichere" Variante der Funktion "sprintf" zur Ausgabeformatierung: http://www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html#ga53ff61856759709eeceae10aaa10a0a3

Wie das mit der Formatierung über Formatierungsparameter funktioniert, kannst Du Dir in jedem C-Tutorial ansehen, in dem es anhand der Funktion "printf" (für Bildschirmausgabe eines Computerprogramms, also für Mikrocontroller so nicht direkt verwendbar) erklärt wird. "sprintf" bzw "snprintf" formatieren stattdessen Variablen im RAM, verwenden aber dazu dieselben Formatierungsparameter.

Danke für die schnelle Antwort. Die Kompilierung ist durchgegangen und ich warte gerade gespannt auf den GPS-Fix, um die Funktionalität mal zu sehen...

Allerdings habe ich immer noch nicht verstanden, warum Deine Lösung nun funktioniert, meine aber nicht. Über die Formatierungsparameter bin ich auch schon gestolpert, nur dass ich das bisher immer auf printf() bezogen hatte und eben nicht wusste, dass man dies auch auf Variabeln anwenden kann.

Nur mal der Vollständigkeit halber: Wie hätte ich es denn machen müssen, wenn ich nicht snprintf() nutzen würde? Wie hätte die fname-Variabel definiert sein müssen, damit es mit der SD.open() Methode geklappt hätte? Über lesenswerte Infos im Netz, die diese oder ähnliche Dinge mal ein wenig aufdröseln, wäre ich mehr als glücklich.

LG TConnect

TConnect:
Nur mal der Vollständigkeit halber: Wie hätte ich es denn machen müssen, wenn ich nicht snprintf() nutzen würde?

Viele Möglichkeiten zum Zusammenbasteln des Strings. Zum Beispiel:

  char filename[13]="gpslg";
  int number=7;
  if (number<100) strcat(filename,"0");
  if (number<10) strcat(filename,"0");
  itoa(number,&filename[strlen(filename)],10);
  strcat(filename,".log");
  Serial.println(filename);

Du müßtest Dir auch mal über den Unterschied zwischen “char” und “char array” klarwerden.

Ein einzelnes Zeichen ist ein “char” und wird als Konstante zwischen EINZELNE HOCHKOMMAS gesetzt. Zum Beispiel:

char c='x';

Ein char-Array hat eine bestimmte Größe (Anzahl Zeichen plus eins extra als “Stringende-Zeichen”) und wird als Konstante zwischen DOPPELTE HOCHKOMMAS gesetzt:

 char filename[13]="gpslg";  // String definiert für Maximallänge 12 und abschließendes Nullzeichen

Das abschließende Nullzeichen in Stringkonstanten wird automatisch eingefügt, das braucht nicht geschrieben werden.

Aber ein char-Array (“C-String”) ist eben etwas grundlegend anderes als ein “char”.
Insbesondere kannst Du chars mit dem Pluszeichen addieren:

  char Buchstabe='A'+1;

Das ergibt den Buchstaben ‘B’, weil ‘B’ um genau eine Stelle hinter ‘A’ im ASCII-Alphabet kommt.
Aber ein “char plus irgendwas” ergibt immer nur einen einzelnen char und niemals einen String.

Um char-Arrays (“C-Strings”) miteinander zu verbinden, muss eine Funktion aufgerufen werden: strcat

Lies dir mal ein Tutorial über C Strings durch. z.B.: http://openbook.galileocomputing.de/c_von_a_bis_z/011_c_arrays_011.htm#mja452663858fd23e86c51cfa3b9139f64 Wenn du da oben im Inhaltsverzeichnis schaust werden auch die wichtigsten Funktionen von string.h erklärt.

Generell sei noch gesagt, dass Arrays in C nicht viel mehr als syntaktischer Zucker für Zeiger sind. Array Variablen sind lediglich ein Zeiger auf das erste Element.

Wenn du also das machst:

char filename[13]="gpslg";

Ist filename ein char*. Ein Zeiger auf char. Eine Funktion die einen C String als Parameter hat, hat daher einen char* als Parameter. Das const heißt, dass die Funktion den String nicht verändern kann.

Außerdem solltest du wissen, dass C Strings Null-terminiert sind. Am Ende steht immer ein '\0', damit man weiß wann der String zu Ende ist. Char Arrays müssen daher immer eins größer als der sichtbare String sein!

Ich danke euch für die Hilfe und werde mich in die verlinkten Seiten mal einarbeiten. Ich habe ja schon viel mit z.B. PHP gemacht und achte dort auch immer auf “klare Verhältnisse”. Aber wenn ich mir mal so die ganzen Libaries von z.B. Adafruit anschaue, dann plöppen aktuell mehr Fragezeichen im Dunstkreis meines Kopfes auf als Lösungen. Von daher besteht wohl meine erste Aufgabe (Klappe die zehnte) darin, an dieser Stelle weiter zu lernen :slight_smile:

Also noch mal danke :slight_smile:

Grüße TConnect

//EDIT:
Übrigens empfinde ich es als extrem ernüchternd, wie schnell man an das Ende der Fahnenstange kommt. Ich hatte eigentlich vor, den Logger nicht nur für GPS Daten zu schreiben, sondern gleichzeitig auch noch für Telemetriedaten. Ich habe das 10DOF (ebenfalls von Adafruit) und wollte eigentlich in einer Frequenz von 5Hz oder 10Hz die aktuelle Lage im Raum abbilden. Das Sensorboard bietet da ja alles, was man so braucht. Doch leider kommt man hier beim Einbinden der notwendigen Libaries schneller an die Speichergrenzen als einem lieb ist. Alleine nur die vier includes

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

düsen schon auf 27.992 Bytes von verfügbaren 28.672 Bytes auf dem Arduino Micro… Hm… Bedeutet wohl, dass ich lernen muss, wie das mit der low level Programmierung der Bausteine funktioniert und mir dann einen für exakt diese Zusammenstellung optimierten Sketch zusammenstellen muss… Oder sieht das jemand anders? Gibts was automatisiertes, was z.B. nicht benötigte Methoden und Funktionen aus sämtlichen eingebundenen Ressourcen löscht, bevor kompiliert wird?

Es gibt auch SD Libs die ein paar kB kleiner sind (sdFat oder tinyFat): http://forum.arduino.cc/index.php?topic=230652.0

Auf sehr umfangreiche Funktionen wie sprintf() verzichten kann kurzfristig sinnvoll sein. Die Alternative mit strcat() und itoa() ist ca. 1.3kB kleiner! Wenn da noch viel mehr dazukommt schieben solche Optimierungen das Problem aber nur etwas auf.

Den Bootloader löschen und den Arduino mit einem ISP programmieren spart glaube ich 0,5kB. Aber das bringt nicht wirklich was.

Ansonsten eventuell auf dem Mega umsteigen. Preislich gibt es auch den Atmega1284 als günstige Alternative in der Mitte. Der hat 128k Flash und 16kB RAM (damit sogar mehr RAM als der Arduino Mega!) Aber das ist ein blanker Prozessor, den du auf einem Breadboard oder einer eigenen Platine betreiben musst.

Danke für die Hinweise. Allerdings kommt es bei mir dann doch ein wenig auf den verfügbaren Platz und das Gewicht an, weil das ganze in einem Wetterballon verstaut werden soll. Ich überlege gerade, ob es dann nicht sogar Sinn machen würde, alles auf zwei oder drei Arduino Micro aufzuteilen. Einer verarbeitet die GPS Daten und funkt diese ggf. per ARPS runter (Stichwort Trackuino), der nächste kümmert sich um die Telemetrie und wenn darauf dann kein Platz mehr ist, kümmert sich der dritte um das Abspeichern der Daten auf die SD. Man müsste dann halt schauen, wie die Libaries sinnvoll "verteilt" werden könnten und wie die Kommunikation zwischen den Arduinos dann ablaufen könnte. Ich mag den I2C Bus ja sehr gerne :-)

Aber bevor wir hier dann vollends aus dem Toppic schießen sage ich noch einmal vielen Dank für die ganze Hilfe. Denn mein eigentliches Problem ist ja schon lange gelöst.

LG TConnect