ESP32 und SD Card

Hallo,

ich möchte mit einem ESP32 Daten auf einer SD Karte speichern.
Der Aufbau scheint richtig zu sein, denn das enthaltene Beispiel (SD_Test) funktioniert.

Allerdings muss ich gestehen, dass ich den Code nicht vollständig verstehe.

/*
 * Connect the SD card to the following pins:
 *
 * SD Card | ESP32
 *    D2       -
 *    D3       SS
 *    CMD      MOSI
 *    VSS      GND
 *    VDD      3.3V
 *    CLK      SCK
 *    VSS      GND
 *    D0       MISO
 *    D1       -
 */
#include "FS.h"
#include "SD.h"
#include "SPI.h"

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println("Not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.name(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}

void createDir(fs::FS &fs, const char * path){
    Serial.printf("Creating Dir: %s\n", path);
    if(fs.mkdir(path)){
        Serial.println("Dir created");
    } else {
        Serial.println("mkdir failed");
    }
}

void removeDir(fs::FS &fs, const char * path){
    Serial.printf("Removing Dir: %s\n", path);
    if(fs.rmdir(path)){
        Serial.println("Dir removed");
    } else {
        Serial.println("rmdir failed");
    }
}

void readFile(fs::FS &fs, const char * path){
    Serial.printf("Reading file: %s\n", path);

    File file = fs.open(path);
    if(!file){
        Serial.println("Failed to open file for reading");
        return;
    }

    Serial.print("Read from file: ");
    while(file.available()){
        Serial.write(file.read());
    }
    file.close();
}

void writeFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Writing file: %s\n", path);

    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("File written");
    } else {
        Serial.println("Write failed");
    }
    file.close();
}

void appendFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}

void renameFile(fs::FS &fs, const char * path1, const char * path2){
    Serial.printf("Renaming file %s to %s\n", path1, path2);
    if (fs.rename(path1, path2)) {
        Serial.println("File renamed");
    } else {
        Serial.println("Rename failed");
    }
}

void deleteFile(fs::FS &fs, const char * path){
    Serial.printf("Deleting file: %s\n", path);
    if(fs.remove(path)){
        Serial.println("File deleted");
    } else {
        Serial.println("Delete failed");
    }
}

void testFileIO(fs::FS &fs, const char * path){
    File file = fs.open(path);
    static uint8_t buf[512];
    size_t len = 0;
    uint32_t start = millis();
    uint32_t end = start;
    if(file){
        len = file.size();
        size_t flen = len;
        start = millis();
        while(len){
            size_t toRead = len;
            if(toRead > 512){
                toRead = 512;
            }
            file.read(buf, toRead);
            len -= toRead;
        }
        end = millis() - start;
        Serial.printf("%u bytes read for %u ms\n", flen, end);
        file.close();
    } else {
        Serial.println("Failed to open file for reading");
    }


    file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }

    size_t i;
    start = millis();
    for(i=0; i<2048; i++){
        file.write(buf, 512);
    }
    end = millis() - start;
    Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
    file.close();
}

void setup(){
    Serial.begin(115200);
    if(!SD.begin()){
        Serial.println("Card Mount Failed");
        return;
    }
    uint8_t cardType = SD.cardType();

    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        return;
    }

    Serial.print("SD Card Type: ");
    if(cardType == CARD_MMC){
        Serial.println("MMC");
    } else if(cardType == CARD_SD){
        Serial.println("SDSC");
    } else if(cardType == CARD_SDHC){
        Serial.println("SDHC");
    } else {
        Serial.println("UNKNOWN");
    }

    uint64_t cardSize = SD.cardSize() / (1024 * 1024);
    Serial.printf("SD Card Size: %lluMB\n", cardSize);

    listDir(SD, "/", 0);
    createDir(SD, "/mydir");
    listDir(SD, "/", 0);
    removeDir(SD, "/mydir");
    listDir(SD, "/", 2);
    writeFile(SD, "/hello.txt", "Hello ");
    appendFile(SD, "/hello.txt", "World!\n");
    readFile(SD, "/hello.txt");
    deleteFile(SD, "/foo.txt");
    renameFile(SD, "/hello.txt", "/foo.txt");
    readFile(SD, "/foo.txt");
    testFileIO(SD, "/test.txt");
    Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
    Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));



}

void loop(){

}

Ich nehme mit 10 hz Sensordaten (Integer) auf und möchte diese auf die SD Karte Schreiben.
Wie gehe ich dafür korrekt vor.

Benutze ich die im Beispiel vorhandenen Funktionen. Wie z.B: writeFile(SD, “/hello.txt”, "Hello "); ?
Diese brauchen allerdings const char als Typen. Wie konvertiere ich meine Werte korrekt?

Ich hab zwar Grundlegende Programmierkenntnisse, aber meine c Kenntnisse sind leider beschränkt. Daher verstehe ich auch nicht im Datail was hier passiert.
Was sind z.B. fs::FS &fs?

void writeFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Writing file: %s\n", path);

    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("File written");
    } else {
        Serial.println("Write failed");
    }
    file.close();
}

Ich habe auch versucht mit folgendem Code zu arbeiten. Aber die Datei lässt sich so leider nicht öffnen.

String dataString="Das ist ein Test";
File dataFile = SD.open("datalog.txt", FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile) {
    dataFile.println(dataString);
    dataFile.close();
    // print to the serial port too:
    Serial.println(dataString);
  }
  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening datalog.txt");
  }

Vielen Dank für Lesen.

Ich nehme mit 10 hz Sensordaten (Integer) auf und möchte diese auf die SD Karte Schreiben.

Ich würde Dir empfehlen, die Daten nicht sofort zu schreiben, sondern zuerst im Speicher zu agregieren. Flash-Speicher können meist ca. 10'000 mal geschrieben werden, danach ist die Zelle futsch. Durch geschicktes Management des Treibers der SD-Karten können etwas mehr Transaktionen verarbeitet werden, indem die geschriebenen Daten über die ganze Karte möglichst gut verteilt werden.
Trotzdem, wenn Du 10 Mal pro Sekunden einen neuen Wert schreibst, wird die SD-Karten kaum einen Tag überstehen. Es macht also Sinn, die Daten über eine Minute (oder so) zu agregieren und dann in einem Rutsch zu schreiben, der ESP-32 hat zum Gück ja genügen Speicher (RAM) dafür.

Du solltest Dich mal etwas mit Zeichenketten in C beschäftigen (das dort gesagte gilt auch für C++).

sprintf/snprintf wären auch gute Anhaltspunkte.

Wieviele Werte willst Du denn für eine Messreihe speichern und wie lange soll das Ganze laufen?
Evtl. wäre ein FRAM eine Lösung.

Gruß Tommy

Ich würde Dir empfehlen, die Daten nicht sofort zu schreiben

Ich würde da etwas widersprechen, und vorschlagen, dir darüber vorerst nicht allzuviele Gedanken zu machen.

Erstens wird physikalisch sowieso nicht sofort geschrieben, sondern erst, wenn ein Block ("sektor" = 512 Byte) voll ist. Zweitens machen SD Karten selbst schon ein sognanntes wear levelling

Wenn du allerdings wirklich mit 10 Hz = alle 100 ms Daten schreiben willst, solltest du, schon aus aus Performance-Gründen, nicht jedesmal { open, write, close } machen. Dann wird jede "Zelle" deiner SD Card ca. 1 mal im Leben geschrieben, oder meinetwegen ein Dutzend mal, wenn du deinen Datenmüll tatsächlich mal löschst.
Selbst wenn du jedesmal { open, write25byte, close } machst, wird der gleiche Sektor nur ca. 20 Mal geschrieben, bis er voll ist. Jedenfalls weit jenseits der 10.000 Mal.
Die größte Belastung der SD Karte ist mechanisch, häufiges Rein/Raus in Arduino und PC. ( Oder wofür brauchst du die SD Karte ?)

Diese brauchen allerdings const char als Typen. Wie konvertiere ich meine Werte korrekt?

  1. [color=blue]const char* [/color] ist etwas anderes als [color=blue] const char [/color].
  2. Wenn ein Funktionsparameter löblicherweise als const char* deklariert ist, heisst das, die Funktion wird den Text nicht verändern. Du kannst ihr problemlos einen Text übergeben, den du gerade eben selber erst zusammengebastelt hast.
  3. Tommy hat natürlich recht: char* ist gewöhnungsbedürftig für Leute, die sonst ohne groß nachzudenken mit Strings hantieren. Sollte man sich aber reinknien.

Was sind z.B. fs::FS &fs?

Das kommt von dir, du hast #include "FS.h" in deinen Sketch geschrieben.
( Soll vermutlich das FileSystem auf der SD Karte sein. )

Hängt wohl auch mit SD.h zusammen.

  SD.begin() in setup()
Das ominöse SD ist so ein FS - Objekt ( bzw. das einzige mögliche ) , das du unbewußt irgendwo definiert hast und nun jeder Funktion mitgibst.

Ich habe auch versucht mit folgendem Code zu arbeiten. Aber die Datei lässt sich so leider nicht öffnen.

Hmm. Der größte Unterschied ist String statt char*, aber das passiert erst nach dem open. Und eigentlich sollte File auch String-Objekte per print schreiben können.

Du bekommst die Fehlermeldung "error opening datalog.txt" ?
Egal ob die Datei schon vorher existiert oder nicht ?

Und wenn du stattdessen

 File dataFile = SD.open("/datalog.txt", FILE_WRITE);

schreibst ?