[Résolu] parcours récursif d'une carte SD

Bonjour,

je travaille sur un lecteur mp3 a base d'ESP32 et carte SD.
Acutellement je liste les fichiers a la volé durant la navigation. Quand je sélectionne un dossier, mon programme liste les fichiers et les affichent sur a l'écran. Le hic, c'est que la lib SD est assez lente, et quand je liste avec une lecture en cours, ça freeze la lecture le temps du listing.

Ce que j'ai tenté :

  • passer a lib SDFat, mais il y a incompatibilité avec la lib de l'écran (TFT_eSPI)
  • explorer la piste du multitasking, mais je n'arrive a rien ...

Les 2 options que je vois, c'est : optimiser la vitesse d'excécution du listing, ou procéder différemment en listant tous les fichiers et stocker ça dans un fichier et préférer chercher dans un fichier plutot que de scanner un dossier a chaque fois.
Le truc c'est que je ne sais pas si c'est plus efficace d'aller chercher dans un fichier l'arboressence qui va bien...

Avez vous une idée de comment procéder ?

Merci

Voici ma fonction pour lister les fichiers :

void Listing(String path)
{ 
  Xindex=0;
  tft.fillScreen(TFT_BLACK);
  int i=0;
  CurrDir=path;
  dir= SD.open(path);
  int count=1;
 
  while(true)
  {
    File entry =  dir.openNextFile();
    if(! entry)
    {break;}
  if (entry.isDirectory()) 
    {
      Liste[i]=entry.name();
      tft.setCursor(4,i*15,2);
      tft.println(entry.name());
      i++;
    } else 
    {
      Liste[i]=entry.name();
      tft.setCursor(4,i*15,2);
      tft.println(entry.name());
      i++;
    }
  }
}

si vous n'avez pas trop de fichiers vous pouvez stocker cela en mémoire éventuellement avec une structure arborescente. l'accès sera quasi instantané mais bien sûr ça consomme de la RAM, donc tout dépend du reste du code et de vos besoins

vous pourriez aussi stocker cet index de manière structurée (en gros il faut pouvoir calculer la position sans faire trop de parcours en lecture flash) en SPIFF sur la flash de votre ESP32, ça ne mange pas de RAM et c'est plus lent mais ce sera plus rapide que la carte SD.

Sinon il faut faire comme dans l'ancien temps quand les disques durs étaient lents, vous faites du pre-fetch pour emmener en mémoire en avance de phase le contenu "le plus probable" pour la prochaine action de l'utilisateur... mais c'est quand même de la lecture

Merci pour les pistes.
En fait j'ai du mal a visualiser concrètement comment stocker l'arborescence et surtout comment récupérer l'info quand je suis ici : /artistes/albums/musiques, et que je veux remonter au listing des albums et donc arriver à /artistes/albums.
Est ce que je recrée dans la mémoire flash les dossiers Artistes, dans lequels je recrée les dossiers des albums, dans lesquels je crée des fichiers vides portant le nom des mp3, et je navigue là dedans ?
Vous parlez de stocker l'index de manière structurer, je ne visualise pas la façon de procéder :confused:

Merci

EDIT : En réfléchissant un peu, je pense entrevoir une solution avec un fichier qui index tout
pour une arborescence comme celle ci :
/Artiste A /Album A / Titre A ça donnerait un code 111
/Artiste B/Album A / Titre C => 2103

Et quand je navigue, si je suis sur l'artiste B et que je valide l'album A, ça consisterait a lister les fichiers dont l'index est supérieur ou égale à 2101 et inférieur a 2199 ou qui commence par "21". Ca implique une limitation a 99 fichiers par dossiers... je pense qu'on rencontre rarement ce cas de figure, sauf peut être si on part sur des podcast... a voir.

Je pensais plutôt à des listes chaînées pour refaire le graphe de l’arborescence

enum TypeElement : byte {FICHIER, REPERTOIRE};

struct Element {
   const char * nom;
   TypeElement type;
   Element * parent;
   Element * suivant;
   Element * precedent;
   Element * enfant;
};

Element racine ={"/", REPERTOIRE, nullptr, nullptr, nullptr, nullptr);

Element * elementActif = &racine;

Lorsque vous parcourez l’arborescence sur la carte vous instanciez un Element, allouez de la mémoire pour le nom et définissez son type et mettez à jour ses pointeurs.

L’élément racine est le point de départ
elementActif pourrait être une variable globale qui se souvient de l’élément sélectionné. Les pointeurs de cet élément vous permettent alors de lgraphe

Si au lieu d’un pointeur pour le nom vous mettiez un tableau pré alloué de taille fixe (comme le 8.3 de DOS) alors la structure aurait une taille fixe et serait « flat » et donc au lieu de pointeurs en mémoire vous pourriez juste avoir un numéro séquentiel par rapport au début et mettre tout cela dans un fichier en SPIFF pour ne pas prendre trop de RAM. Pour aller au numéro séquentiel 17 par exemple il suffit de faire un seek de 17*sizeof(Element) et de lire une structure d’un coup.

Le pointeur gauche est optionnel, le fichier du même niveau de répertoire précédent celui en cours peut se retrouver en remontant au parent et en re parcourant la liste jusqu’à ce que le suivant soit l’élément en cours. C’est un peu plus long car il y a un parcours de graphe mais on gagne un pointeur mémoire à chaque Élément

Il y a 1000 façons d’optimiser cette gestion, il suffit de regarder comment les différents file system représentent leur catalogue de fichiers.

On peut aussi utiliser sur ESP les classes container de C++ pour gérer des listes, des arborescences etc ce qui fait que vous n’avez pas à implémenter vous même l’insertion, le parcours ou la recherche mais si vous n’avez jamais fait cela c’est un exercice sympa de le faire vous même

Merci pour cette réponse détaillée.
Je me suis toujours arrangé pour esquiver les pointeurs ^^, mais ça serait l'occasion de m'y mettre.

Juste je ne comprends pas trop comment je vais récupérer la liste des éléments a afficher.

Par exemple pour une arborescence comme celle ci :
/A
/B
/C
/D

Comment serait instancier l'élément racine ?
Parce qu'il y a 4 enfants, et comme je comprends le code la structure ne permet d'avoir qu'un élément enfant.
Comme ça j'aurais tendance a mettre un type array, mais je n'ai aucune idée de la répercussion sur la mémoire.

Merci

il est instance de manière statique. toujours là. c'est le point d'entrée

ensuite les éléments sont crées et raccrochés à l'arbre au fur et à mesure du parcours du système de fichiers

ici on dispose de / à la racine qui a 3 enfants, A B et C qui sont des répertoires
A contient un seul fichier toto
B et C sont vides (ils n'ont pas d'enfant)

EDIT: je viens de voir qu'il manque une flèche de toto vers A comme pointeur parent

ok, je commence a entrevoir le truc, si je comprends bien, quand je parcourt la carte SD, à élément A, je déclare ça comme ça :

Element A ={"A", REPERTOIRE, racine, B, nullptr, toto);

pour le B

Element B ={"B", REPERTOIRE, racine, C, A, nullptr);

et après avoir tout passé, l'élément "racine" aura été mis a jour et aura pour enfants A, B et C ?

Si c'est ça, y a un truc que j'ai encore du mal a saisir ...
Je prend en paralèlle ton tuto sur la mémoire et les pointeur, si je comprends bien dans la structure, "enfant" est de type pointeur sur Element, donc si je veux acceder a ce que contient "enfant de racine" je vais avoir quelque chose comme :
Element test = *racine.enfant

en fait j'ai un gros doute sur le type de la variable test, comme ça, je ne comprends pas comment il va pouvoir me retournerA, B et C

en tout cas merci encore pour tes explications claires (même si pour le moment je nage un peu ^^)

En gros oui mais il faire de linstanciation dynamique (appeler new pour allouer de la mémoire qui ne va pas disparaître à la fin du bloc de code).


Oui mais ce serait dommage de faire une copie :slight_smile:

On fera donc
Element* premierEnfant = racine.enfant;

Et vous ensuite premierEnfant->xxx pour accéder au champ xxx

ok, merci.
Je vais essayer de manipuler tout ça ce week end. Y a de grandes chances que je repasse par là parce que j'ai pas pigé un truc XD

Bon... comme prévu, je galère ^^
J'essaye de délcarer les élements pout recréer l'arborescence de l'image.

J'ai donc fait ça :

Element racine ={"/", REPERTOIRE, nullptr, nullptr, nullptr, nullptr};
Element A = new Element {"A", REPERTOIRE, racine, B, nullptr, toto};

et ça me met ça comme erreur :

Compilation error: 'B' was not declared in this scope

ce qui semble assez logique après coup.
J'ai donc tenté ce code, pour relger basiquement les soucis de déclaration :

Element racine ={"/", REPERTOIRE, nullptr, nullptr, nullptr, nullptr};

Element B;

Element toto;

Element A = new Element {"A", REPERTOIRE, racine, B, nullptr, toto};

et là j'ai ça comme erreur :

Compilation error: could not convert '{"A", REPERTOIRE, racine, B, nullptr, toto}' from '' to 'Element'

Le compilateur préfère quand je mets des * un peu partout :

Element* racine =new Element({"/", REPERTOIRE, nullptr, nullptr, nullptr, nullptr});
Element* B ;
Element* toto;
Element* A = new Element({"A", REPERTOIRE,racine,B,nullptr,toto});

le code Serial.print(A->parent->nom) retourne bien "/" donc la racine est bien mise a jour.

J'ai essayé de faire en sorte d'aller jusqu'a l'enfant de A : toto.

Avec ce code ça fonctionne ;

  Element* A ;
  Element* toto;
Element* racine =new Element({"/", REPERTOIRE, nullptr, nullptr, nullptr, nullptr});
Element* B ;
toto=new Element ({"toto",FICHIER,A,nullptr,nullptr,nullptr});
A=new Element({"A", REPERTOIRE,racine,B,nullptr,toto});

Cependant, je ne dois pas faire les choses comme il faut parce que ça nécessite de connaitre a l'avance toute l'arborescence pour déclarer les variables avant de les utiliser dans le struct.
Qui plus est ça pose un autre souci, dans le cas d'une boucle qui parcourt toute la carte SD, il faudrait générer des noms de variable à la volée.

:face_with_monocle:

Il faut créer les éléments avec des pointeurs nuls au début puis vous établissez les pointeurs ensuite

Vous partez de la racine qui existe de manière statique toujours

Vous avez une variable locale au parcours récursif parentEnCours qui est égale à racine.

Vous lisez la première entrée dans la carte SD. C’est A de type répertoire qui est un fils de parentEnCours.

Vous créez A avec comme parent parentEnCours et de type répertoire. les autres pointeurs sont nullptr et vous mettez à jour enflant de racine pour pointer sur A

Ensuite vous faites un parcours du graphe.

Par exemple en profondeur d’abord.

Comme A est un répertoire vous allez lire son contenu sur la carte SD (récursivelent). A sera le « parent en cours »

Il a un fils toto de type fichier
Vous instanciez donc un nouvel élément de type FICHIER de nom toto dont le parent est parentEnCours et tous les autres pointeurs sont nuls. Comme le pointeur enfant de parentEnCours est nul, Vous modifiez le pointeur enfant de parentEnCours pour pointer sur Toto.

Comme c’est un fichier la recursion en profondeur s’arrête on va voir si parentEnCours en un autre élément enfant sur la carte SD. Il n’y en a pas donc la recursion s’arrête et on remonte d’un niveau

parentEnCours est donc à nouveau racine et on va voir s’il y a un autre enfant. Oui il y’a B de type répertoire que l’on instancie donc avec comme parent parentEnCours (racine) et comme parentEnCours A déjà un enfant on ne modifie pas ce lien. On suit le lien existant pour trouver le premier enfant (A) et on suit le pointeur suivant jusqu’à trouver un élément dont ce pointeur suivant est nul (on est au bout du chaînage). Ici il n’y en a qu’un seul donc on s’arrête à A et on met a jour les pointeurs en disant que B a pour précédent A et que A a pour suivant B.

Ensuite on continue la recursion. B étant de type répertoire il devient parentEnCours et on regarde s’il a des enfants sur la carte SD. Non donc la recursion s’arrête là et on remonte, parentEnCours est à nouveau racine. On demande donc à la racine l’élément suivant après B et on nous donne C. Et ça recommence comme B

Une fois que l’on n’a plus d’enfant inexploré de la racine, le graphe est créé.


Si c’est plus simple avec la bibliothèque SD de faire un parcours en largeur d’abord, ça change un peu l’ordre mais ça reste le même type de récursion.

parentEnCours Est racine et vous avez un autre pointeur enfantPrecedent initialement nul.

On liste le premier élément de parentEnCours c’est A, de type répertoire. on le crée avec comme parent parentEnCours, comme précèdent enfantPrecedent. comme le pointeur enfant de parentEnCours est nul on le fait pointer sur A et comme enfantPrecedent est nul on n’a pas de suivant à mettre à jour. On appelle A maintenant enfantPrecedent.

On demande l’élément suivant de parentEnCours. C’est B de type répertoire. On le crée avec comme parent parentEnCours. Comme parentEnCours à déjà un enfant on ne modifie pas parentEnCours. On définit précédent comme étant enfantPrecedent et comme enfantPrecedent n’est pas nul on met comme suivant l’élément que l’on vient de créer B. B devient maintenant enfantPrecedent.

On demande l’élément suivant de parentEnCours. C’est C de type répertoire. On le crée avec comme parent parentEnCours. Comme parentEnCours à déjà un enfant on ne modifie pas parentEnCours. On définit précédent comme étant (B) et comme enfantPrecedent n’est pas nul on met comme suivant l’élément que l’on vient de créer C. C devient maintenant enfantPrecedent.

On demande l’élément suivant de parentEnCours. Il n’y en a plus.

Donc on va parcourir maintenant tous les enfants de la racine.

On prend A c’est un répertoire donc il devient parentEnCours. enfantPrecedent Est mis à nul.

On parcours à nouveau en largeur tous les enfants de A

On a comme premier élément toto de type fichier. On le crée. On met comme parent parentEnCours et comme précèdent enfantPrecedent. Comme parentEnCours avait son pointeur enfant nul, on le met à jour avec le pointeur sur toto qui devient enfantPrecedent

On demande à parentEnCours (A) son prochain élément, il n’y en a plus. La recursion s’arrête et on remonte d’un cran pour aller demander à B ses enfants. Il n’y en a pas. On passe à C il n’y en a pas. On remonte à la racine et la recursion est terminée.


➜ en gros c’est un parcours récursif soit en profondeur soit le largeur de l’arbre de la carte SD et création de des éléments appropriés. La fonction récursive de parcours gérant l’empilement de variables locales (parentEnCours et enfantPrecedent par exemple).

J’ai vu ce post en anglais qui utilise des containers

Merci @J-M-L pour la réponse très détaillée, je pense entrevoir comment gérer tout ça au niveau des parents/enfants, pour les suivant/précédents c'est moins évident ^^faut que je reprenne tout ça a tete reposée et que je teste pour comprendre.
Je n'ai pas eu le temps de m'y replonger, mais je testerais ça dans les prochains jours.

Merci aussi pour le lien !

désolé mais je bloque au niveau de l'élément B ...

Voici la retranscription en code de ce que j'ai compris des explications :

struct Element {
   const char * nom;
   TypeElement type;
   Element * parent;
   Element * precedent;
   Element * suivant;
   Element * enfant;
};

  Element* A ;
  Element* B ;
  Element* C ;
  Element* toto;
  Element* enfantPrecedent;

Element racine ={"/", REPERTOIRE, nullptr, nullptr, nullptr, nullptr};
Element* parentEnCours=&racine;

A = new Element({"A",REPERTOIRE,parentEnCours,enfantPrecedent,nullptr,nullptr});
parentEnCours->enfant=A;
enfantPrecedent=A;
B = new Element({"B",REPERTOIRE,parentEnCours,enfantPrecedent,nullptr,nullptr});
A->suivant=B;

Ce que je ne comprends pas c'est cette étape :

Comme parentEnCours à déjà un enfant on ne modifie pas parentEnCours.

Comment dit on que B est un enfant de parentEnCours finalement ?
Parce que là quand je fais
`Serial.println(racine.enfant->nom);
ça ne me retourne que A

Et question bonus, s'il y a plusieurs enfants, comment acceder a chacun de ces enfants ?
quelque chose comme ça ?

parentEnCours->enfant+1;

l'adresse mémoire augmente de 11, mais est vide :saluting_face:

si vous partez avec cela

vous avez déjà perdu car vous cablez en dur le nombre d'éléments. dans le vrai code il faudra faire un parcours récursif et instancier les éléments dynamiquement.


mais disons que l'on fasse cela à la main (on déplie la récession) pour avoir une meilleur compréhension

enum TypeElement : byte {FICHIER, REPERTOIRE};


struct Element {
  const char * nom;
  TypeElement type;
  Element * parent;
  Element * precedent;
  Element * suivant;
  Element * enfant;
};

Element racine = {"/", REPERTOIRE, nullptr, nullptr, nullptr, nullptr};


// ---------- ICI LA RECURSION EST SIMULEE MANUELLEMENT --------

  Element* parentEnCours = &racine;
  Element* nouveau = nullptr;
  Element* precedent = nullptr;

  // on a lu "A" de type répertoire. On le crée
  nouveau = new Element({"A", REPERTOIRE, parentEnCours, nullptr, nullptr, nullptr}); // nom, type, parent, precedent, suivant, enfant
  // on le rattache à son parent si c'est le premier enfant du parent
  if (parentEnCours->enfant == nullptr) {
    // c'est le premier enfant donc il n'a pas de précédent
    parentEnCours->enfant = nouveau;
  } else {
    // ce n'est pas le premier enfant, donc on met à jour la double liaison
    nouveau->precedent = precedent;
    precedent->suivant = nouveau;
  }
  // on mémorise
  precedent = nouveau;

  // --------
  // imaginons que la récursion fait une traversée par niveau, ça nous donne l'élément suivant "B"
  // --------

  // ==> c'est exactement le même code qu'au desus
  // on a lu "B" de type répertoire. On le crée
  nouveau = new Element({"B", REPERTOIRE, parentEnCours, nullptr, nullptr, nullptr}); // nom, type, parent, precedent, suivant, enfant
  // on le rattache à son parent si c'est le premier enfant du parent
  // ==>> (ici ce n'est plus le cas, on a déjà A)
  if (parentEnCours->enfant == nullptr) {
    // c'est le premier enfant donc il n'a pas de précédent
    parentEnCours->enfant = nouveau;
  } else {
    // ce n'est pas le premier enfant, donc on met à jour la double liaison
    nouveau->precedent = precedent;
    precedent->suivant = nouveau;
  }
  // on mémorise
  precedent = nouveau;



  // --------
  // imaginons que la récursion fait une traversée par niveau, ça nous donne l'élément suivant "C"
  // --------

  // ==> c'est exactement le même code qu'au desus
  // on a lu "C" de type répertoire. On le crée
  nouveau = new Element({"C", REPERTOIRE, parentEnCours, nullptr, nullptr, nullptr}); // nom, type, parent, precedent, suivant, enfant
  // on le rattache à son parent si c'est le premier enfant du parent
  // ==>> (ici ce n'est plus le cas, on a déjà A)
  if (parentEnCours->enfant == nullptr) {
    // c'est le premier enfant donc il n'a pas de précédent
    parentEnCours->enfant = nouveau;
  } else {
    // ce n'est pas le premier enfant, donc on met à jour la double liaison
    nouveau->precedent = precedent;
    precedent->suivant = nouveau;
  }
  // on mémorise
  precedent = nouveau;


  // --------
  // ici la récursion par niveau, nous dit qu'on a fini, donc on va explorer la liste des enfants de parentEnCours récursivement
  // --------

  // on prend le premier enfant (donc A)
  parentEnCours = parentEnCours->enfant;
  // et on n'a pour le moment pas d'élément précédent à ce niveau
  precedent = nullptr;

  // --------
  // imaginons que la récursion fait une traversée par niveau, ça nous donne l'élément suivant "toto" de type fichier
  // --------

  // ==> c'est exactement le même code qu'au desus (sauf que le type est fichier)
  // on a lu "toto" de type fichier. On le crée
  nouveau = new Element({"toto", FICHIER, parentEnCours, nullptr, nullptr, nullptr}); // nom, type, parent, precedent, suivant, enfant
  // on le rattache à son parent si c'est le premier enfant du parent
  if (parentEnCours->enfant == nullptr) {
    // c'est le premier enfant donc il n'a pas de précédent
    parentEnCours->enfant = nouveau;
  } else {
    // ce n'est pas le premier enfant, donc on met à jour la double liaison
    nouveau->precedent = precedent;
    precedent->suivant = nouveau;
  }
  // on mémorise
  precedent = nouveau;

  // --------
  // ici la récursion par niveau, nous dit qu'on a fini, donc on va explorer le suivant dans la liste des enfants de parentEnCours récursivement
  // --------

➜ vous voyez qu'on a toujours a peu près le même code pour la création d'un élément et son rattachement au parent et précédent


c'est un parcours.

Element* parentEnCours;
for (Element* unEnfant = parentEnCours->enfant; unEnfant != nullptr; unEnfant = unEnfant->suivant) {
  // faire ce que vous voulez avec l'élément pointé par unEnfant
  Serial.print(unEnfant->nom);
  Serial.println(unEnfant->type == FICHIER ? F("\tFICHIER") :  F("\tREPERTOIRE")); 
}

un exemple de parcours récursif (je ne peux pas tester si ça fonctionne) en largeur first

la fonction afficherContenuDossier() liste tous les fichiers du dossier puis ensuite appelle récursivement afficherContenuDossier() pour tous les dossiers

(la variable tabulation peut vous donner une indication de la profondeur si c'est utile)

le premier while serait là où on établit les liens vers le parent et précédent/suivant
le second while c'est là où on descend au niveau ci dessous et il faudrait donc passer le pointeur sur le parent en paramètre à la fonction aussi

#include <SdFat.h>
SdFat sd;

void afficherContenuDossier(SdFile& unDossier, byte tabulation) {
  SdFile fichier;

  if (!unDossier.isDir()) return;
  unDossier.rewind();

  // on parcourt d'abord le répertoire en cours
  while (fichier.openNext(&unDossier, O_RDONLY)) {
    if (! fichier.isHidden()) {
      char nomFichier[20];
      fichier.getName(nomFichier, sizeof nomFichier);
      if (strlen(nomFichier) != 0) {
        for (uint8_t i = 0; i < tabulation; i++) Serial.write('\t');

        if (fichier.isDir()) {
          Serial.write('\'');
          Serial.print(nomFichier);
          Serial.println("'\t(Répertoire)");
        } else {
          Serial.write('\'');
          Serial.print(nomFichier);
          Serial.println("'\t(fichier)");
        }
      }
    }
    fichier.close();
  }

  // maintenant exploration des répertoires de ce niveau
  unDossier.rewind();
  while (fichier.openNext(&unDossier, O_RDONLY)) {
    if (! fichier.isHidden()) {
      char nomFichier[20];
      fichier.getName(nomFichier, sizeof nomFichier);
      if (strlen(nomFichier) != 0) {

        if (fichier.isDir()) {
          afficherContenuDossier(fichier, tabulation + 1);
        }
      }
    }
    fichier.close();
  }

}

void setup() {
  SdFile dossier;

  Serial.begin(115200);
  if (!sd.begin(SdSpiConfig(SS, DEDICATED_SPI))) {
    sd.initErrorHalt(&Serial);
  }

  // Ouvrir le dossier racine
  if (!dossier.open("/")) {
    sd.errorHalt(&Serial, F("Ouverture du dossier impossible"));
  }
  afficherContenuDossier(dossier, 0);
}

void loop() {}

J'essayais de faire un peu les différents points du parcourt récursif à la main pour essayer déja de comprendre comment ça fonctionne. Parce que là je bute sur ces histoires de pointeurs alors que le reste c'est plutot une question de if then else qui me semble a ma portée.

En voyant le code expliqué comme ça, ça semble tellement évident ... et ... beaucoup plus simple a gérer.
Je devrais pouvoir tester ça dans les prochains jours.

Moi qui pensais me débrouiller un peu en C, je me rend compte que je fais plus du bricolage qu'autre chose. :sweat_smile:

En tout cas merci encore pour le temps passer a expliquer et ré-expliquer tout ça !

les pointeurs c'est toujours un peu compliqué au début, c'est pour cela que c'est un bon exercice de faire un code qui construit un arbre.

Bonjour à tous,

Je me permets de m'insérer dans le sujet car je suis en train de faire un projet similaire. Au départ, je ne savais pas trop comment m'y prendre mais la méthode des listes chaînées m'a paru bonne après lecture du sujet.

Pour le moment, j'arrive à afficher la première "couche" de dossier".

void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {

  /* Déclarations et initialisations des variables */
  unsigned char idx_directory;
  unsigned char idx;
  unsigned char pos_end;

  idx_directory = 0;
  idxParcoursDossier = 0;

  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();
  /* On parcourt l'arborescence de la carte SD 
  RACINE
    ↓
  OFFSPRING ->  GOJIRA ->   BLINK 182
    ↓             ↓             ↓
  IGNITION      FMTS        DUDE RANCH
  SMASH         MAGMA       EOTS
  AMERICANA                 TOYPAJ
  */
  while(file) {
    if(file.isDirectory()) {
      /* Nouveau dossier trouvé, on créé un nouvel élément */
      nouveau = new Element({file.name(), REPERTOIRE, nullptr, nullptr, nullptr, nullptr});
      if (elementActif->enfant == nullptr) {
          elementActif->enfant = nouveau;
          Serial.println(nouveau->nom);
      } else {
          nouveau->precedent = precedent;
          precedent->suivant = nouveau;
      }

      precedent = nouveau;
      display_text2(0,LINE_2+(LINE_HEIGHT*idx_directory),nouveau->nom,((idx_selection == idx_directory) ? true : false),SIZE_1);
      idx_directory++;
      Serial.println(nouveau->nom);
      
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
    }
    file = root.openNextFile();
  }
}

Et cela fonctionne bien, que ça soit sur mon écran oled ou sur la console série, j'affiche le nom des dossiers.

Ce que je ne vois pas pour le moment, c'est comment lister des sous-dossier.

Pour moi, je dois le faire ici :

else {
          nouveau->precedent = precedent;
          precedent->suivant = nouveau;
 }

Il faut que je parcours le dossier et que je liste l'enfant (qui correspondrait au premier sous-dossier) puis les "suivants" qui correspondraient aux sous-dossiers suivants.

Mais je ne vois pas comment y accéder, dois-je recréer un nouveau fichier File et faire un parcours similaire au parcours des dossiers ?

Si vous avez des suggestions, merci d'avance,

je ne suis pas sûr de comprendre la question. si vous avez bâti le graphe en mémoire, il suffit de le parcourir

sinon quand vous faites:

est-ce que votre classe Element fait bien la duplication du contenu du nom? Si vous ne conservez que le pointeur, ça ne fonctionnera pas une fois sortie du if