Lire un fichier sur la carte SD; SD, SdFat, SdBaseFile....

Bonjour,

Je m'intéresse à la carte SD et à la lecture d'un fichier. Pour l'instant, je cherche surtout à savoir comment la lire. J'ai utilisé la bibliothèque SD, mais je trouve que cela va vraiment lentement. Voici le programme de test que j'ai utilisé, pour lire sur une carte uno un fichier de 225ko:

// Ce programme teste la vitesse de lecture d'un fichier Test.bin sur la SD
// Pour que le code ne soit pas vide et élliminé, il fait la somme modulo 256
// des octets de ce fichier.

#include <SD.h>

#define BROCHE_CS_DE_SD 10

File fichier;
unsigned long t0; // Départ chronomètre
char c; // Pour que le code soit exécuté

void setup()
{
  Serial.begin(115200);
 
  // Ouverture du fichier sans aucune gestion d'erreur
  SD.begin(BROCHE_CS_DE_SD);
  fichier = SD.open("TEST.BIN"); // fichier de 230.000 octets

  // Départ chronomètre
  t0=millis();
  
  // Lecture
  while (fichier.available()) c+=fichier.read();

  // Affichage du temps
  Serial.print("Durée de la lecture: "); Serial.print(millis()-t0); Serial.println(" millisecondes");
}


void loop()
{
}

C'est juste pour faire des tests, le fichier est là. Dans la vraie vie, j'y ajoute un certain nombre de tests.

Pour ces 225ko, Arduino met 9,3s, mange 10290 octets de programme et demande 909 octets de RAM

J'ai refais le même essai avec SdFat au lieu de SD, et c'est mieux: 6,6s, 8650 octets de programme et 884 octets de RAM.

En passant par SdBaseFile je passe à 6.4s, 7290 octets programme et 868 octets de RAM

J'ai vu que la classe SdFatEX serait plus rapide, mais qu'elle monopolise le bus SPI, ce qui ne me gêne pas à priori car je n'utilise sur ce bus qu'une ou deux cartes SD. Malheureusement je ne trouve nulle part trace de SdFatEX sauf dans la documentation de SdFat-master. D'après cette page elle serait dans SdFat.h/.cpp, mais je n'en vois pas trace, mon compilateur non plus.

Quelqu'un a-t-il essayé SdFatEX?

C'est sûr que la vitesse est une caractéristique important pour moi, mais aussi la taille de la RAM, rien que la bibliothèque utilise plus de 40% des ressources. J'accepterai bien de perdre un peu de vitesse pour gagner de l’espace RAM. J'avais écrit un programme qui au début doit lire une image sur la carte SD, puis par la suite a besoin de la pile. Mais comme la lecture de la carte utilise le buffer en mémoire statique, je n'en ai plus beaucoup par la suite.

Il y a peut être d'autres possibilités d'accès simples sur une SD?

Merci pour votre collaboration.

oui le micro processeur ne pédale pas vite et comme il y a peu de mémoire, on ne peut pas faire de gros cache... donc c'est lent :frowning:

vous pourriez essayer de lire d'un coup bcp plus d'octets ça utilisera un peu mieux le cache mais dans la vraie vie vous avez vraiment besoin de lire les 200k octet après octet pour faire quelque chose alors que votre Arduino a très peu de SRAM ?

Je suis aussi en train d'essayer de récupérer des fichiers depuis une carte SD (sur carte adafruit M0 adalogger). J'utilise SDFat. J'ai monté le débit du port série à 921600 bauds. Au début, je faisais ça caractère par caractère. Bien lent... Maintenant, j'envoie par paquet de 256 octets. 30 s pour 4-5 Mo. Par contre, j'ai des problèmes à la fin. Des fois, il me manque quelques octets à la fin du fichier comme si le buffer interne du port série était bloqué.

MarsaMatruh:
Je suis aussi en train d'essayer de récupérer des fichiers depuis une carte SD (sur carte adafruit M0 adalogger). J'utilise SDFat. J'ai monté le débit du port série à 921600 bauds. Au début, je faisais ça caractère par caractère. Bien lent... Maintenant, j'envoie par paquet de 256 octets. 30 s pour 4-5 Mo. Par contre, j'ai des problèmes à la fin. Des fois, il me manque quelques octets à la fin du fichier comme si le buffer interne du port série était bloqué.

postez votre code... le dernier buffer ne fera pas pile 256 octets

vous pourriez essayer de lire d'un coup bcp plus d'octets ça utilisera un peu mieux le cache mais dans la vraie vie vous avez vraiment besoin de lire les 200k octet après octet pour faire quelque chose alors que votre Arduino a très peu de SRAM ?

J'utilise la lecture pour afficher ses images, à partir d'un bitmap 2 ou 3 octets par couleur que j'envoie sur un écran 2 octets par couleur, ou dans le sens inverse, mais dans les deux cas l'écran est branché sur 8 bits qui ne sont pas sur un seul port, il faut découper les octets. Donc oui, j’ai besoin de le lire un par un. J'utilise aussi la carte SD pour récupérer du texte que j'affiche caractère par caractère, et pareil, il faut que je lise les octets par paquet de 1, 2 ou 3 suivant le caractère. Par exemple pour faire ça:
ImageCiDessus.gif

Les secteurs que l'on lit sur la SD sont de 512 octets. Je ne sais pas trop comment les bibliothèques travaillent. Si j'affiche du texte caractère par caractère que je lis avec un fichier.read(), en ralentissant le programme et en débranchant la SD tout au début, il ne m'affiche n'importe quoi qu'au bout de 512 caractères (à 1 ou 2 près), ce qui correspond à un secteur de la SD et à la taille du buffer de la librairie SD, j'ai été voir, je trouvais que 900 octets de RAM cela faisait beaucoup.
J'en conclus que la lecture de la carte avec la bibliothèque SD se fait bien octet par octet et que le read va lire dans le buffer.

Que je lise octet par octet ou par bloc et que je trie après, cela revient au même.

J'ai monté le débit du port série à 921600 bauds. Au début, je faisais ça caractère par caractère. Bien lent... Maintenant, j'envoie par paquet de 256 octets. 30 s pour 4-5 Mo

Ça, ça m'intéresse, comment fait-on pour changer la vitesse?
Mais je suppose que pour l'écriture il passe par le même buffer, chez moi les temps de lecture et d'écriture sont semblables.

Bien lent c'est quoi? parce que quand je lis ou j'écris octet par octet, en passant avec SdFat, il me faut 6,6s pour 230ko, ce qui fait en gros 30s pour 4Mo. C'est pareil!

ImageCiDessus.gif

vileroi:
Bien lent c'est quoi? parce que quand je lis ou j'écris octet par octet, en passant avec SdFat, il me faut 6,6s pour 230ko, ce qui fait en gros 30s pour 4Mo. C'est pareil!

6,6/0,23 x 4 = 115 s

Je mets mon code:

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

// The red LED flashes during SD card writes
#define RedLED 13 // The red LED on the Adalogger is connected to Digital Pin 13
// The green LED indicates that the GNSS has established a fix 
#define GreenLED 8 // The green LED on the Adalogger is connected to Digital Pin 8

// set up variables using the SD utility library functions:
Sd2Card card;
SdVolume volume;
SdFile root, subdir, finalfile;

// Set the pins used
#define chipSelect 4


String message_recu = "";


void setup() {
  // put your setup code here, to run once:

  // initialize digital pins RedLED and GreenLED as outputs.
  pinMode(RedLED, OUTPUT); // Red LED
  pinMode(GreenLED, OUTPUT); // Green LED
  digitalWrite(RedLED, LOW);
  digitalWrite(GreenLED, HIGH);
  
  
  Serial.begin(921600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  if (!card.init(SPI_HALF_SPEED, chipSelect)) {
    Serial.println("Carte SD non trouvée");
    while (1);
  }

  // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
  if (!volume.init(card)) {
    Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
    while (1);
  }

  root.openRoot(volume);
}

void close() {
  root.close();
  Serial.println("Session closed");
  while (1);
}


void traiter_message()
{
  char subfolder[9];
  char subfile[13];
  int position;
  byte B;
  char hexa[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
  char monbuffer[256];

  digitalWrite(RedLED, HIGH);

  if (message_recu == "ls") {
    // list all files in the card with date and size
    root.ls();
    Serial.println("end");
    message_recu = "";
  }

  if (message_recu == "stop") {
    close();
  }

  if (message_recu.substring(0, 3) == "ls ") {
    message_recu = message_recu.substring(3);
    message_recu.toCharArray(subfolder, message_recu.length()); // we skip the last '/'
    subfolder[message_recu.length() - 1] = '\0';
    subdir.open(root, subfolder, O_RDONLY);
    subdir.ls();
    Serial.println("end");
    message_recu = "";
    subdir.close();
  }

  if (message_recu.substring(0, 4) == "get ") {
    message_recu = message_recu.substring(4);

    position = message_recu.indexOf('/');
    if (position != -1)
    {
      message_recu.toCharArray(subfolder, position + 1);
      subfolder[position + 1] = '\0';
      subdir.open(root, subfolder, O_RDONLY);
      message_recu = message_recu.substring(position + 1);
      message_recu.toCharArray(subfile, message_recu.length() + 1);
      subfile[message_recu.length()] = '\0';
      finalfile.open(subdir, subfile, O_RDONLY);
      Serial.println(finalfile.fileSize());

      position = 0;
      int nbcluster = finalfile.fileSize() / 128;
      for(int compteur = 0 ; compteur < nbcluster ; compteur++)
      {
        for(int l = 0 ; l < 128 ; l++)
        {
          B = finalfile.read();
          monbuffer[2 * l] = hexa[B / 16];
          monbuffer[2 * l + 1 ] = hexa[B % 16];
          position++;
        } 
        Serial.write(monbuffer,256);
      } 
      for(int l = nbcluster * 128 ; l < finalfile.fileSize() ; l++)
      {
        B = finalfile.read();
        Serial.print(hexa[B / 16]);
        Serial.print(hexa[B % 16]);
        position++;
      }
      Serial.println();
      Serial.flush();
      // close the file
      subdir.close();
      finalfile.close();
    }
    else {
      Serial.println("Error in get command");
    }

    message_recu = "";
  }

  if (message_recu != "")  {
    Serial.println("Un message reçu:");
    Serial.println(message_recu);
  }
  digitalWrite(RedLED, LOW);

}

void ecouter()
{
  int c;

  if (Serial.available()) {
    // on a au moins 1 octet en attente
    c = Serial.read(); // on lit la valeur. la fonction read retourne un entier: -1 en cas d'erreur sinon l'octet lu (dans l'octet de poids faible de l'entier)
    if (c != -1) { // s'il n'y a pas eu d'erreur de lecture
      switch (c) {
        case '\n':
          traiter_message();
          message_recu = "";
          break;
        case '\r':
          traiter_message();
          message_recu = "";
          break;
        default:
          message_recu = message_recu + (char)c;
          break;
      }
    }
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  ecouter();
}

Les fichiers sur la carte SD viennent du projet F9P_RAWX_Logger. Ça génère un fichier de log binaire par quart d'heure. Les fichiers sont enregistrés dans un sous-dossier par jour.

En face, côté PC, j'ai fait un programme en Pascal sous Lazarus avec le composant TLazSerial. La cohabitation entre texte et binaire avec TLazSerial n'étant pas très heureuse, j'ai décidé de tout passer en texte en convertissant les octets binaire en hexa.

J'ai surtout des problèmes quand mes fichiers source ont des tailles multiple de 128 octets. C'est pour ça que j'ai rajouté Serial.println(); et Serial.flush(); pour essayer de purger le buffer. À la réception, j'élimine les caractères supplémentaires qui pourraient arriver. Néanmoins, j'ai encore quelques cas pas très reproductibles où ça se bloque avec un fichier arrêté à 99% côté réception.

Les secteurs que l'on lit sur la SD sont de 512 octets. Je ne sais pas trop comment les bibliothèques travaillent. Si j'affiche du texte caractère par caractère que je lis avec un fichier.read(), en ralentissant le programme et en débranchant la SD tout au début, il ne m'affiche n'importe quoi qu'au bout de 512 caractères (à 1 ou 2 près), ce qui correspond à un secteur de la SD et à la taille du buffer de la librairie SD, j'ai été voir, je trouvais que 900 octets de RAM cela faisait beaucoup.

Oui si vous ne fermez pas le fichier entre temps tout un secteur est lu et conservé en cache dans la bibliothèque et les read() vont taper dans le cache

le 512 est câblé en dur un peu partout dans le code d'ailleurs (cf par exemple ici)

6,6/0,23 x 4 = 115 s

String message_recu = "Et une erreur de plus de ma part!";