Sélection fichier sur carte SD

Bonjour,
Je vous expose mon problème en détails :

Fonction pour la sélection d’un fichier sur carte SD

Contexte matériel :
Carte UNO R3
Afficheur LCD 2 x 40 caractères + 5 boutons via I²C (shield Adafruit)
Shield carte SD + RTC Adafruit

But de la fonction affiFichier(num) :
Afficher successivement sur un afficheur LCD les fichiers présents sur la carte SD (sous la racine) afin d’en sélectionner un pour utilisation en lecture ou pour l’effacer. Le premier affiché doit être le plus récent.
2 boutons (+) et (-) incrémentent ou décrémentent un compteur dont la valeur est transmise à la fonction via la variable locale num.
Si num = 0 (valeur initiale), la fonction renvoie le nom du dernier fichier ; c’est le cas seulement au 1er lancement de la fonction.
Si num > 0, la fonction renvoie le nom du fichier correspondant à son rang.
La variable globale nbFichiers enregistre le nombre de fichiers présents sur la carte.
La variable globale noFichier indique le n° du fichier courant (celui qui s’affiche) ; c’est cette variable qui est sollicitée par les touches (+) et(-). Elle est « bridée » entre 2 et nbFichiers.

Fonctionnement :
Au départ, tout fonctionne correctement. Au 1er lancement, le nom du dernier fichier s’affiche et la variable nbFichiers est initialisée avec le nombre de fichiers présents. Les touches + et – font bien leur travail et le compteur num suit bien la manœuvre entre ses 2 limites. Le programme effectue bien la bouche while en fonction du compteur.
Et tout à coup, la bouche while ne s’exécute plus, à cause de la variable fichier (= dossier.OpenFile()) qui reste à 0 (ou false). Pourquoi ? Ce n’est pas toujours le même fichier qui bloque.
Par contre, fait étrange, ça bloque toujours au bout de 11 appels de la fonction !!! Toujours 11 quel que soit le nombre de fichiers présents ! Quelle que soit l’action (aléatoire) sur les boutons + et -. Que se passe-t-il donc à ce moment là ? :o
La variable nbFichiers est bien constante, n et num évoluent bien (j’ai placé des Serial.print pour visualiser les variables). Mais ce n’est pas bloquant pour les reste du système qui fonctionne toujours.
Je ne comprends pas ce qui coince ! Avez-vous une idée ?
Merci d’avance !

Voici le code incriminé :

String affiFichier(uint8_t num) { // affiche un fichier de la carte SD
  File dossier = SD.open("/");
  File fichier = dossier.openNextFile();
  String fic;
  uint8_t n = 0;
  bool x = false;
  if (num == 0) {
    x = true;
  }

  while (fichier & ((n < num) | x)) { // x à 1 masque la condition n < num si num = 0
    n++;
    fic = fichier.name();
    fichier.close();
    fichier = dossier.openNextFile();
  }
  fichier.close();
  if (num == 0) {   // au 1er lancement mémorise le nombre de fichiers
    nbFichiers = n;
    noFichier = n;  // commence par le dernier fichier
  }
  lcd.setCursor(31, 1);
  lcd.print(fic.substring(0, 8));
  return fic;
}

La partie concernée du programme appelant (menu déroulant géré par des tables en EEPROM) :

Switch (prog) {
	......
          case 18:  //  fichier suivant (bouton +)
            noFichier++;
            if (noFichier > nbFichiers) {
              noFichier--;
            }
            else {
              nomFichier = affiFichier(noFichier);
            }
            break;

          case 19:  //  fichier précédent (bouton -)
            noFichier--;
            if (noFichier < 2) {
              noFichier = 2;
            }
            else {
              nomFichier = affiFichier(noFichier);
            }
            break;
	......

J en ai pas lu en détail mais Comment savez vous que c’est la partie incriminée ou concernée ? Un débordement de variable n’importe où ailleurs dans le code pourrait expliquer votre soucis par exemple

Faites un mini programme qui ne contient que cette partie et regardez si vous avez le problème ou pas

Bonsoir, et merci pour l’idée !

J’ai donc écrit un petit programme réduit à son strict minimum :
Test de 2 boutons + et - câblés pour la circonstance, lancement de la fonction incriminée à l’initialisation puis à chaque appui sur les touches. Le module LCD a été retiré.

Le problème est identique, sauf que maintenant le dysfonctionnement survient au 20ème appel de la fonction (au lieu de 11 en fonctionnement normal). J’appuie de manière aléatoire sur les 2 boutons et à chaque essai c’est toujours au bout de 20 appels que la variable fichier se bloque sur 0 (donc boucle while ignorée). J’ai essayé d’ajouter des fichiers, idem, toujours blocage à 20 !
Je n’arrive pas à trouver ce qui cloche… La variable openNextFile() est-elle bien utilisée ?

Voici mon sketch d’essai :

// Programme de test
// Défilement des fichiers de la carte SD (en partant du dernier) avec 2 boutons + et -

#include <SPI.h>                        // bibliothèque bus SPI
#include <SD.h>                         // bibliothèque carte SD

const int chipSelect = 10;  // D10 = signal CS (chip select) de la carte SD en SPI
const int pinPlus = 8; // fil bleu
const int pinMoins = 9; // fil jaune

String nomFichier = "";         // nom du fichier courant
uint8_t nbFichiers = 0;         // nombre de fichiers sur la carte SD
uint8_t noFichier = 0;          // n° de fichier dans la liste

uint8_t etatAvantP = 0;          // état du bouton + : état antérieur
uint8_t etatActuelP = 0;         //                    état courant
uint8_t etatAvantM = 0;          // état du bouton - : état antérieur
uint8_t etatActuelM = 0;         //                    état courant

void setup() {

  pinMode(pinPlus, INPUT);
  pinMode(pinMoins, INPUT);

  // Debugging output
  Serial.begin(9600);

  // test carte SD
  if (!SD.begin(chipSelect)) {
    Serial.println("SD absente ou HS");
    return;
  }
  Serial.println("Choisir un fichier");
  nomFichier = affiFichier(0);  // affichage nom dernier fichier 
                                // et initialisation du nombre de fichiers dans nbFichiers
}

void loop() {

  // TEST DES BOUTONS (boutons + et - à 1 au repos, à 0 en appuyant)

  etatActuelP = digitalRead(pinPlus);
  if (etatActuelP != etatAvantP) {
    if (!etatActuelP) { // bouton + appuyé
      noFichier++;
      if (noFichier > nbFichiers) {
        noFichier--;
      }
      Serial.println(noFichier);
      nomFichier = affiFichier(noFichier);  // affichage nom fichier indexé par noFichier > 0
      Serial.print("+ ");
      Serial.println(nomFichier);
      Serial.println("Retour boucle d'attente");
    }
    etatAvantP = etatActuelP;
    delay(10);
  }

  etatActuelM = digitalRead(pinMoins);
  if (etatActuelM != etatAvantM) {
    if (!etatActuelM) { // bouton - appuyé
      noFichier--;
      if (noFichier < 1) { 
        noFichier = 1;
      }
      Serial.println(noFichier);
      nomFichier = affiFichier(noFichier);  // affichage nom fichier indexé par noFichier > 0
      Serial.print("- ");
      Serial.println(nomFichier);
      Serial.println("Retour boucle d'attente");
    }
    etatAvantM = etatActuelM;
    delay(10);
  }
}

// **********************************************************************


String affiFichier(uint8_t num) { // affiche un fichier de la carte SD
  Serial.print("Fonction avec valeur ");
  Serial.println(num);
  File dossier = SD.open("/");
  File fichier = dossier.openNextFile();
  String fic;
  uint8_t n = 0;
  bool x = false;
  if (num == 0) {  // (voir ci-dessous)
    x = true;
  }
  Serial.println(nbFichiers);
  Serial.println(fichier);
  while (fichier & ((n < num) | x)) { // x à 1 masque la condition (n < num) si num = 0
    n++;
    fic = fichier.name();
    fichier.close();
    fichier = dossier.openNextFile();
    Serial.print(n);
    Serial.println(fic);
  }
  fichier.close();
  if (num == 0) {   // au 1er lancement mémorise le nombre de fichiers
    nbFichiers = n;
    noFichier = n;  // commence par le dernier fichier
  }

  Serial.println("Resultat :");
  Serial.print(num);
  Serial.println(fic);
  Serial.println("Fin fonction");
  return fic;
}

// **********************************************************************

Bonjour Botanicus,

Une idée comme cela : tu utilise & et |, es-tu sur ?

Normalement c'est && et || pour les test et & et | pour les opérateurs logiques !!!

C'est peut être ton problème, à vérifier :slight_smile:

Bon test,
Jean-Nono

c’est louche effectivement les & et |

j’ai pris votre code et je l’ai modifié un peu en m’assurant de bien me remettre au début du répertoire à chaque parcours et de bien fermer tout ce que j’ouvre (chaque open() doit avoir son close() )

j’avais la flemme de brancher des boutons alors j’ai juste utilisé l’entrée série, un + passe au fichier d’après et un - au fichier d’avant (si possible)

j’en ai profité pour virer la classe String que vous utilisez, c’est une mauvaise idée de morceler votre mémoire…

J’ai conservé votre approche avec la librairie SD mais ma recommendation cependant serait de laisser tomber la librairie SD qui est en standard, elle n’est pas la plus à jour et de passer à la librairie SDFat qui est bcp plus robuste.

Je commence par le dernier fichier car il semble c’est ce que vous voulez, donc tapez - pour revenir en arrière

ça donne cela - aucun soucis de stabilité - même après de nombreuses demande, en tapant des trucs comme “-----------+++++±----++++++±-----++±+±±±±±±±±----++++±+++++++±------------+±±+±±-------------+++±±±+++++++++” dans la console série (réglée à 115200 bauds)

jetez un oeil

// Programme de test
// Défilement des fichiers de la carte SD
// en partant du dernier

// on se déplace avec l'entrée série en tapant + ou -

#include <SPI.h>                        // bibliothèque bus SPI
#include <SD.h>                         // bibliothèque carte SD

const int chipSelect = SS;  // SS est défini pour vous, D10 sur UNO, D53 sur MEGA (Slave select)
uint16_t nbFichiers = 0;    // nombre de fichiers sur la carte SD
uint16_t noFichier; // le fichier en cours

//-------------------------------------------------
// calcule le nombre de fichiers à la racine
//-------------------------------------------------
uint16_t compteNbFichiers()
{
  uint16_t n = 0;
  File dossier = SD.open("/");
  File fichier;

  if (dossier) {
    dossier.rewindDirectory(); // on se met au début

    while (fichier = dossier.openNextFile()) {
      n++; // un de plus
      fichier.close(); // on ferme celui là
    }
    dossier.close();
  } else Serial.println(F("Erreur lecture racine"));

  return n;
}

//-------------------------------------------------
// affiche un fichier à la racine de la carte SD.
// le nombre total de fichiers doit être correct
//-------------------------------------------------
const char * affiFichier(uint16_t num) {
  uint16_t n = 0;
  const byte maxFileLength = 20;
  static char nomDuFichier[maxFileLength + 1]; // +1 pour le '\0' à la fin, static pour que la mémoire reste allouée car on exporte ce pointeur vers  le reste du programme

  nomDuFichier[0] = '\0'; // initialisé à vide

  File dossier = SD.open("/");
  dossier.rewindDirectory(); // on se met au début
  File fichier;

  if (num <= nbFichiers) {
    while (n <= num) {
      fichier = dossier.openNextFile(); // on passe au suivant
      if (fichier) {
        n++; // on se souvient où on est
        if (n == num) { // si on a trouvé le bon, on copie son nom
          strncpy(nomDuFichier, fichier.name(), maxFileLength);
          nomDuFichier[maxFileLength] = '\0'; // par précaution si le nom était trop long
        }
        fichier.close();
      } else break; // erreur de lecture
    }
   // si en sortie du while on n'a pas rempli le nom de fichier, c'est qu'il y a eu un soucis
    if (nomDuFichier[0] == '\0') Serial.println(F("erreur de lecture"));
  } else {
    Serial.println(F("index incorrect"));
  }
  dossier.close();
  return nomDuFichier;
}

// **********************************************************************

void setup() {

  // Debugging output
  Serial.begin(115200);

  // test carte SD
  if (!SD.begin(chipSelect)) {
    Serial.println(F("SD absente ou HS. stop."));
    while (true); // on meurt ici, pas la peine d'aller plus loin
  }

  nbFichiers = compteNbFichiers();
  Serial.print(nbFichiers);  Serial.println(F(" fichiers à la racine"));
  Serial.print(nbFichiers); Serial.print(F("\t"));
  Serial.println(affiFichier(nbFichiers));
  Serial.println(F("\n+ = fichier suivant, - = fichier précédent"));
  noFichier = nbFichiers; // n° de fichier dans la liste, on part du dernier
}

void loop() {
  const char * nomFichier = NULL;

  if (Serial.available()) {
    char c = Serial.read();
    switch (c) {
      case '+':
        if (++noFichier > nbFichiers) {
          noFichier--;
        }
        nomFichier = affiFichier(noFichier);  // affichage nom fichier indexé par noFichier > 0
        Serial.print(noFichier); Serial.print(F("\t"));
        Serial.println(nomFichier);
        break;

      case '-':
        if (--noFichier < 1) {
          noFichier = 1;
        }
        Serial.print(noFichier); Serial.print(F("\t"));
        nomFichier = affiFichier(noFichier);  // affichage nom fichier indexé par noFichier > 0
        Serial.println(nomFichier);
        break;
    }
  }
}

Merci JeanNono !

Effectivement, il fallait écrire && et || pour des comparaisons ; erreur de débutant impardonnable ! Mais après correction, même problème. Donc chercher ailleurs....

Merci J-M-L !

On voit la patte du spécialiste aguerri ! Dans votre code je découvre de nombreuses notions nouvelles pour moi, ainsi que certaines syntaxes et astuces intéressantes. Je vais imprimer et étudier ce code à tête reposée en essayant de tout comprendre. C'est en forgeant....

Peut-être à bientôt pour des explications !
@+
Pierre

Bonsoir J-M-L (et les autres...) !

Résultat des courses :
Après adaptation du sketch à mon contexte, il fonctionne très bien.
Je n'ai plus qu'à l'intégrer dans mon projet. Encore merci !

Mais j'avoue avoir des (grosses) lacunes en programmation Arduino (ou c) étant plus habitué, dans le passé, à programmer en assembleur ou en Visual Basic....

Dans la fonction affiFichier() je ne comprends pas tout :
D'abord la valeur retournée de type char * : je pense qu'il s'agit d'un pointeur vers une adresse constante qui contiendra le nom du fichier renvoyé. Il faut que j'approfondisse mes connaissances dans le domaine des pointeurs.
Puis la constante maxFileLength = 20 et le tableau nomDuFichier : s'il s'agit de la taille maxi du nom de fichier, pourquoi 20 ? Je croyais qu'on ne pouvait pas dépasser 8 caractères (+ 3 pour l'extension).
Et enfin la fonction strncpy() que je ne connais pas ; que renvoi-t-elle exactement ?
A ce propos, où peut-on trouver toutes ces fonctions spéciales qui ne sont pas mentionnées même dans la référence étendue Arduino ?

D'autres questions diverses me viennent à l'esprit, mais chaque chose en son temps !

@+
Pierre

botanicus:
Bonsoir J-M-L (et les autres...) !

Bonsoir!

botanicus:
Résultat des courses :
Après adaptation du sketch à mon contexte, il fonctionne très bien.
Je n'ai plus qu'à l'intégrer dans mon projet. Encore merci !

Bonne nouvelle :slight_smile:

botanicus:
Dans la fonction affiFichier() je ne comprends pas tout :
D'abord la valeur retournée de type char * : je pense qu'il s'agit d'un pointeur vers une adresse constante qui contiendra le nom du fichier renvoyé. Il faut que j'approfondisse mes connaissances dans le domaine des pointeurs.

exactement. je déclare un tableau de caractère, que je ne rends pas global (on pourrait mais je préfère qu'il reste caché dans la fonction :)) et la fonction retourne un pointeur sur cette zone (l'adresse de la case mémoire du début du tableau). Une chaîne de caractère en C ou C++ si on n'utilise pas les Strings c'est simplement un tableau de caractères terminé par un caractère num noté '\0' (c'est à dire on met 0 dans la case mémoire). On appelle cela un c-string

botanicus:
Puis la constante maxFileLength = 20 et le tableau nomDuFichier : s'il s'agit de la taille maxi du nom de fichier, pourquoi 20 ? Je croyais qu'on ne pouvait pas dépasser 8 caractères (+ 3 pour l'extension).

si vous utilisez la librairie SDFat, elle supporte les noms longs - j'ai mis 20 comme ça un peu arbitrairement - mais oui dans votre cas on pourrait se contenter de à 8+3+1 (pour le point) et + 1 pour le '\0' qui marque la fin

botanicus:
Et enfin la fonction strncpy() que je ne connais pas ; que renvoi-t-elle exactement ?

c'est une fonction qui copie au maximum n caractères d'une c-string dans une autre c-string. vous lui donnez donc en paramètre les 2 c-strings et le nombre max de caractères à copier (cf la doc). il y a un petit cas particulier si la chaine source est trop longue alors la fonction n'écrit pas le '\0', c'est pour cela que je le rajoute à la main dans la destination.

botanicus:
A ce propos, où peut-on trouver toutes ces fonctions spéciales qui ne sont pas mentionnées même dans la référence étendue Arduino ?

oui parce que ça n'a rien à voir particulièrement avec le monde Arduino. ce sont des fonctions standards du monde du C ou C++. Vous pouvez aller voir par exemple ces listes de fonctions fréquentes : stdlib.h et string.h qui travaillent sur des c-strings

bon courage !

Merci pour toutes ces explications instructives !

J’ai de quoi m’amuser… :slight_smile:

Juste une dernière question : toutes les fonctions c++ peuvent-elles être utilisées sous l’IDE Arduino ou y a-t-il des restrictions ?

@+
Pierre

La plus grande majorité est dispo

OK, merci.

Encore un blocage :
Pour afficher mes noms de fichiers sur un afficheur, je les limite volontairement à 8 caractères (+ extension).
Je ne trouve pas de fonction cstring pour n'afficher que les 8 1ers caractères du nom (sans l'extension) [soit l'équivalent de "chaine.substring(0, 8)" ou du Basic "left(chaine,8)"]. J'ai bien trouvé strncat("",nomDuFichier,8) mais les chaînes se multiplient à chaque passage !... Pas compris le fonctionnement.

Sinon, à part ce détail, j'ai pu intégrer les fonctions en question dans mon projet.

Le plus simple: Mettez un ‘\0’ en position 8 dans le buffer - ça marque la fin de la chaîne et les caractères des 0 à 7 seulement seront affichés (ou en 8+1+3=12 pour avoir la totale). si le nom était plus court alors vous avez mis un zéro dans le buffer après la fin de la chaîne et à l'affichage ça ne change rien, s'il était plus long vous avez alors tronqué la chaîne à cet endroit en insérant le caractère de fin de chaîne.

si vous ne souhaitez pas afficher l'extension du nom de fichier (par exemple parce que toutes vos extensions (genre xxx.txt ou yyyy.log) sont les mêmes) mais que vous ne savez pas quelle est la longueur du nom avant l'extension, ce que vous voulez faire c'est remplacer le '.' par le caractère de fin de chaîne. pour cela il y a la fonction [url=http://www.cplusplus.com/reference/cstring/strchr/]strchr()[/url] qui retourne un pointeur sur la première occurence d'un caractère donné dans la chaîne passée en paramètre.

voici un exemple:

char message1[] = "20171016.log";
char message2[] = "16oct.log";

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

  Serial.print("message 1 Avant: "); Serial.println(message1);
  Serial.print("message 2 Avant: "); Serial.println(message2);

  char * ptr1 = strchr (message1, '.' ); // on cherche un '.' dans la c-string message1
  char * ptr2 = strchr (message2, '.' ); // on cherche un '.' dans la c-string message2

  if (ptr1) {
    // si le pointeur est non nul c'est qu'on a trouvé le '.'
    *ptr1 = '\0'; // on remplace le point par le marqueur de fin de chaîne
  }

  if (ptr2) {
    // si le pointeur est non nul c'est qu'on a trouvé le '.'
    *ptr2 = '\0'; // on remplace le point par le marqueur de fin de chaîne
  }

  Serial.print("message 1 Apres: "); Serial.println(message1); // message1 a été tronqué pour l'affichage
  Serial.print("message 2 Apres: "); Serial.println(message2); // message2 a été tronqué pour l'affichage
}

void loop() {}

Merci !

Finalement c'est très simple, mais il fallait y penser....
J'ai opté pour "nomDuFichier[8]='\0';" qui me convient pour mon appli.

Mais je garde de côté la 2e solution qui pourra m'être utile par la suite.

@+
Pierre