Bug DFPlayer mini

Bonjour,

Je travaille sur un projet de jeu musical pour les enfants, le principe est le suivant : une musique est joué au hasard, l'enfant doit reconnaitre l'instrument entendu, et pose la carte correspondante, si c'est gagné, une led verte s'allume et on entend un jingle et une voix qui donne le nom de l'instrument, puis le jeu recommence, si c'est perdu, on entend un autre jingle, la led rouge s'allume, et on entend à nouveau le même morceau.
pour ce projet j'utilise : 1 arduino nano, 1 module rfid rc522, 2 led, 1 interrupteur (pour sélectionner un mode facile ou difficile), et un bouton suivant.

Mon code fonctionne 95% du temps, mais de temps en temps, j'ai un bug.
Soit le morceau suivant s'arrête avant même d'avoir commencé et du coup le programme se bloque en mode écoute, je pense que le problème vient du DFplayer qui envoie 2 fois de suite le message play finished.
J'ai parfois un autre bug, j'ai 2 musiques qui sont jouées en même temps... étrange

Est-ce que mon code est en cause ?

[code]
#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include <SPI.h>
#include <MFRC522.h>
#include <Bounce2.h>
#include <Chrono.h>

#define SS_PIN 10
#define RST_PIN 9

// Jeu de reconnaissance d'instruments avec cartes RFID, 2 modes : 
// mode facile : 1 instrument seul est entendu
// mode difficile : reconnaitre l'instrument dans un duo ou trio.
// 11 instruments à trouver, possibilité d'en rajouter en rajoutant d'autres cartes
// sdcard : chaque dossier correspond à 1 instrument, 
// dans chaque dossier 1er mp3 : instrument solo, 
// les autres fichiers : Duos ou trios contenant 1 seul instrument à trouver

const byte redLedPin = 6;
const byte greenLedPin = 5;
const byte interPin = 4;
const byte buttonPin = 3;
//const byte busyPin = 7;

Chrono timer;
Bounce interMode = Bounce();
Bounce nextButton = Bounce();

SoftwareSerial mySoftwareSerial(2, 8); // RX, TX
DFRobotDFPlayerMini MP3Player;

// sometime ACK messages sent on software serial seems to create bugs when trying to access to number of files in folders... strange but observed.
// if you get "no file found" too often, then try to set the flag to false.
const bool useMP3Ack = true;
// sometime you should try to disable reset flag, if your SD card isn't recognized at boot... strange but observed.
// if the MP3 does not start properly at boot, then try to set the flag to false.
const bool resetMP3OnBoot = true;
// Set volume value (0~30). /!\ 5 is clearly enough if you don't want to damage your ears !
const byte MP3Volume = 20;
// 90 : bonne réponse, 91 : mauvaise réponse
const byte jingle[2] = { 90, 91 };
enum state { initial, lecture, ecoute, attente, rejouer };


state jeuState = initial;
// mode facile = false (ne joue que le 1er morceau de chaque dossier)
// mode difficile = true (joue tous les morceaux sauf le 1er)
bool mode = false;
bool mp3End = false;
int jeuFlag = -1;
int songIndex = -1;
byte songNb = 0;
int folderNb = -1;
byte lastSongNb;
byte lastFolderNb;
const byte nbCard = 11;
byte playlist[nbCard] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
byte playlistSong[20] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
int numCarte = -1;
int reponse;

MFRC522 rfid(SS_PIN, RST_PIN);

// UID des cartes
int cartes[nbCard][4] = {
  { 170, 104, 79, 25 },
  { 58, 13, 146, 25 },
  { 26, 254, 81, 25 },
  { 58, 99, 82, 25 },
  { 10, 120, 151, 25 },
  { 234, 49, 76, 25 },
  { 58, 13, 74, 25 },
  { 154, 177, 135, 25 },
  { 26, 112, 77, 25 },
  { 186, 99, 137, 25 },
  { 212, 141, 149, 42 }
};

int newCard[4] = { 0, 0, 0, 0 };

// mélange aléatoire sans répétition de la liste des dossiers de la sdcard
void shuffle() {
  for (int i = 0; i < nbCard; ++i) {
    int index = random(i, nbCard);
    int temp = playlist[i];
    playlist[i] = playlist[index];
    playlist[index] = temp;
  }

}

// mélange aléatoire sans répétition des mp3 contenus dans 1 dossier, 20 fichiers max par dossier
void shuffleSong() {
  for (int i = 0; i < 20; ++i) {
    int index = random(i, 20);
    int temp = playlistSong[i];
    playlistSong[i] = playlistSong[index];
    playlistSong[index] = temp;
  }

}


// vérifie si l'UID fait partie de la liste des cartes référencées rfid
int verifCarte(int carteTest[]) {
  byte verif = 0;
  for (int n = 0; n < nbCard; n++) {
    for (int c = 0; c < 4; ++c ) {
      if (carteTest[c] == cartes[n][c]) {
        ++verif;
      }
    }
    if (verif == 4) {
      //      Serial.println("Gagné ");
      Serial.print("carte n° ");
      Serial.println(n);
      return n;
    }
    else {
      verif = 0;
    }

  }
  Serial.println("Erreur aucune carte valide");
  return -1;
}

// choix dossier suivant dans la playlist, si mode difficile, choix d'un fichier dans le dossier
// si mode facile, 1er mp3 du dossier choisi.
// mélange aléatoire de la playlist lorsque tous les dossiers ont été parcouru 1 fois.
void getRandomFile() {
  ++folderNb;
  ++songIndex;
  if (folderNb == nbCard) {
    folderNb = 0;
    songIndex = 0;
    shuffle();
    shuffleSong();
  }
  if (mode) {

    int nbFilesInFolder = MP3Player.readFileCountsInFolder(playlist[folderNb]);

    // sometime, the sd card seems to be lazy. Strange. But fixed by a second try.
    if (nbFilesInFolder <= 0)
    {
      Serial.println("Got no file from the folder. Let's try another time, just in case of a lazy sd card");
      delay(100);
      nbFilesInFolder = MP3Player.readFileCountsInFolder(playlist[folderNb]);
    }
    songNb = map(playlistSong[songIndex], 1, 20, 2, nbFilesInFolder);
    //Serial.println(songNb);
  }
  else {
    songNb = 1;
  }
  Serial.print(" dossier n° ");
  Serial.println(playlist[folderNb]);
  Serial.print(" mp3 n° ");
  Serial.println(songNb);
  lastFolderNb = folderNb;
  lastSongNb = songNb;
}

// led verte allumée si reponse juste, led rouge si réponse fausse
void allumeLed(bool rep) {
  if (rep) {
    digitalWrite(greenLedPin, HIGH);
    digitalWrite(redLedPin, LOW);
  }
  else {
    digitalWrite(redLedPin, HIGH);
    digitalWrite(greenLedPin, LOW);
  }
}

// extinction des leds
void stopLed() {
  digitalWrite(greenLedPin, LOW);
  digitalWrite(redLedPin, LOW);
}

// Vérifie si la carte correspond au dossier de la playlist, et donc à l'instrument entendu.
int jeu(int carte) {
  mp3End = false;
  if (carte + 1 == playlist[lastFolderNb]) {
    Serial.println("Bravo, Gagné ");
    allumeLed(1);
    MP3Player.playMp3Folder(jingle[0]);
    delay(2000);
    MP3Player.playMp3Folder(playlist[lastFolderNb]);
    delay(200);
    jeuState = ecoute;
    jeuFlag = 1;
  }
  else {
    mp3End = false;
    Serial.println("perdu");
    allumeLed(0);
    MP3Player.playMp3Folder(jingle[1]);
    delay(2000);
    jeuState = ecoute;
    jeuFlag = 0;
  } 
}

// récupère l'UID de la carte posée, et renvoie le n° de carte correspondant
int readRfid() {
  byte verif = 0;
  if (rfid.PICC_IsNewCardPresent()) { // new tag is available
    if (rfid.PICC_ReadCardSerial()) { // NUID has been readed
      MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak);
      //byte card = rfid.uid.uidByte;
      //Serial.println(card);
      for (int i = 0; i < rfid.uid.size; i++) {
        newCard[i] = rfid.uid.uidByte[i];
        //Serial.print(rfid.uid.uidByte[i] < 0x10 ? " 0" : " ");
        Serial.print("uid carte : ");
        Serial.println(rfid.uid.uidByte[i]);
      }
      for ( int n = 0; n < nbCard; n++) {
        for (int c = 0; c < rfid.uid.size; ++c ) {
          if (newCard[c] == cartes[n][c]) {
            ++verif;
          }
        }
        if (verif == 4) {
          //      Serial.println("Gagné ");
          Serial.print("carte n° ");
          Serial.println(n);
          numCarte = n;
          verif = 0;
          rfid.PICC_HaltA(); // halt PICC
          rfid.PCD_StopCrypto1(); // stop encryption on PCD
          reponse = numCarte;
          return numCarte;
        }
        else {
          //Serial.println(" carte non trouvée ");
          verif = 0;
          //numCarte = -1;
        }
      }
      rfid.PICC_HaltA(); // halt PICC
      rfid.PCD_StopCrypto1(); // stop encryption on PCD
    }
  }
  for (int i = 0; i < 4; ++i) {
    newCard[i] = 0;
  }
  numCarte = -1;
  //  if (numCarte == -1) {
  //    Serial.println("pas de carte posée ou carte invalide");
  //  }
  return numCarte;
}

// gestion de l'interrupteur de mode difficile ou facile
void boutonMode() {
  if (interMode.changed()) {
    int valeurInter = interMode.read();
    if ( valeurInter == LOW) {
      mode = true;
      Serial.println("mode difficile");
    }
    else {
      mode = false;
      Serial.println("mode facile");
    }
  }
}

// bouton suivant attaché à une interruption
void boutonSuivant() {
  MP3Player.pause();
  mp3End = false;
  Serial.println("suivant");
  jeuState = initial;
}

// pour savoir si le mp3 est fini ou non, 
void printDetail(uint8_t type, int value) {
  switch (type) {
    case DFPlayerPlayFinished:
      //      Serial.print(F("Number:"));
      //      Serial.print(value);
      Serial.println(F(" Play Finished!"));
      mp3End = true;
      break;
    default:
      mp3End = false;
      break;
  }
}

// pour savoir si le mp3 est fini ou non.
void mp3State() {
    while (MP3Player.available()) {
      printDetail(MP3Player.readType(), MP3Player.read()); //Print the detail message from DFPlayer to handle different errors and states.
    }
}

void setup() {
  mySoftwareSerial.begin(9600);
  Serial.begin(115200);
  randomSeed(analogRead(A0));
  SPI.begin(); // init SPI bus
  rfid.PCD_Init(); // init MFRC522
  pinMode(redLedPin, OUTPUT);
  pinMode(greenLedPin, OUTPUT);
  //pinMode(busyPin, INPUT);

  // Use softwareSerial to communicate with mp3,
  if ( !MP3Player.begin(mySoftwareSerial, useMP3Ack, resetMP3OnBoot) )
  {
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    Serial.println(F("3.If it still fail : try to change MP3 flags in configuration"));
    while (true) {
      delay(0);
    }
  }
  Serial.println(F("MP3 player ready"));
  MP3Player.setTimeOut(800);
  MP3Player.pause();
  MP3Player.volume(MP3Volume);
  MP3Player.EQ(DFPLAYER_EQ_NORMAL);  // Set EQ
  MP3Player.outputDevice(DFPLAYER_DEVICE_SD);   // Set device we use : SD card
  delay(2000);

  interMode.attach(interPin, INPUT_PULLUP);
  nextButton.attach(buttonPin, INPUT);
  // interruption sur le bouton suivant, pour permettre de passer au mp3 suivant 
  // quelque soit l'état du système
  attachInterrupt(digitalPinToInterrupt(buttonPin), boutonSuivant, RISING);
  interMode.interval(5);
  nextButton.interval(5);
  // vérifie 1 première fois le mode avant de démarrer la loop
  if (interMode.read() == LOW) {
    mode = true;
    Serial.println("mode difficile");
  }
  else {
    mode = false;
    Serial.println("mode facile");
  }
  // mélange 1 fois avant la loop
  shuffle();
}

void loop() {
  interMode.update();
  nextButton.update();
  boutonMode();
  
  int answer = readRfid();

  mp3State();

  switch (jeuState) {
    case initial:
      // initialise les variables
      stopLed();
      jeuFlag = -1;
      mp3End = false;
      reponse = -1;
      MP3Player.pause();
      getRandomFile();
      jeuState = lecture;
      break;
    case lecture:
    // lance la lecture du Mp3 et se met en attente de réponse à la fin du mp3
      //Serial.println("lecture");
        MP3Player.playFolder(playlist[folderNb], songNb);
        delay(200);
      jeuState = ecoute;
      break;
      case ecoute:
      // en cours d'écoute
      //Serial.println("ecoute");
      if (mp3End) {
        Serial.println("mp3 fini");
        //Serial.println(MP3Player.readState());
        if (jeuFlag == 1) {
          jeuState = initial;
        }
        if (jeuFlag == 0) {
          jeuState = rejouer;
        }
        if (jeuFlag == -1) {
        jeuState = attente;
        }
      }
      break;
    case rejouer:
    // si une mauvaise réponse est donnée, on a droit à un autre essai
    // rejoue le dernier mp3 joué
      //Serial.println("rejouer");
      stopLed();
      mp3End = false;
      jeuFlag = -1;
      reponse = -1;
        MP3Player.playFolder(playlist[lastFolderNb], lastSongNb);
        delay(200);
        jeuState = ecoute;
      break;
    case attente:
    // attente de réponse, et vérifie si la réponse est bonne ou pas
      MP3Player.pause();
      //Serial.println(mp3End);
//      if (answer == -1 && reponse == -1) {
//        Serial.println("attente de réponse");
//      }
      if (reponse != -1) {
        jeu(reponse);
      }
      break;
  }

}
[/code]```

Pour illustrer un peu plus le propos, voici ce que me renvoie le moniteur série, lorsque ça bug :

19:33:10.587 -> Bravo, Gagné 
19:33:13.767 ->  Play Finished!
19:33:13.767 ->  Play Finished!
19:33:13.805 -> mp3 fini
19:33:13.978 ->  dossier n° 3
19:33:13.978 ->  mp3 n° 5

et à ce moment là, on n'entend aucun son, heureusement, le programme n'est pas complètement bloqué, si on appuie sur le bouton suivant, on débloque la situation.

Pas facile : le problème peut être logiciel (dépassement de tableau, bug) ou électrique (alimentation, perturbations).

Est-ce que ça arrive de manière aléatoire ? Si oui, ça pourrait venir de tes mélanges (shuffle).
Pour débugguer, il faut faire des Serial.print de tout ce qui peut poser un problème :

  • Affiche les valeurs de tes tableaux après mélange
  • Affiche le nom du fichier de musique à chaque fois que tu en lances un
  • Affiche les ID des cartes lues
  • Affiche... tout ce qui te vient à l'esprit pour vérifier qu'il n'y a pas un truc incongru...

pourquoi une en INPUT et l'autre en PULLUP ?

  interMode.attach(interPin, INPUT_PULLUP);
  nextButton.attach(buttonPin, INPUT);

votre interruption

attachInterrupt(digitalPinToInterrupt(buttonPin), boutonSuivant, RISING);

appelle cette fonction

void boutonSuivant() {
  MP3Player.pause();
  mp3End = false;
  Serial.println("suivant");
  jeuState = initial;
}

➜ c'est délicat de faire appel à Software Serial qui dépend des interruptions au sein d'une interruption. Il se peut que ça ne fonctionne pas du tout.

Vous devriez traiter ce bouton comme interMode, il n'y a pas de raisons pour que la loop soit longue

Input car c’est un bouton module grove, qui a déjà sa resistance intégrée.
Je l’ai attaché à une interruption pour pouvoir passer au morceau suivant, à n’importe quel moment, même si le mp3 joue.
En tout cas le bouton suivant fonctionne bien

J’ai l’impression que ça arrive de manière aléatoire, mais, difficile d’en être sur à 100%

oui mais vous nous avez aussi dit ➜

ça vaudrait le coup d'explorer un peu non ?
Software Serial désactive et réactive les interruptions dans un print qui appelle plein de fois de suite write() (cf cette partie du code) et donc vous pouvez vous prendre une flopée de rebonds du bouton par exemple puisque les interruptions ne sont plus désactivées après le premier octet émis...

bref, ça me parait une mauvaise idée de faire MP3Player.pause() dans une ISR. Votre loop() tourne vite, vous ne faites rien de vraiment bloquant, donc vous ne devriez pas rater un appui utilisateur sur un bouton

J'ai changé le code, et recodé le bouton suivant en enlevant l'interruption, effectivement, ça fonctionne tout aussi bien, mais ça n'a pas corrigé le bug...
En fait j'ai rajouté ça au code :

void printDetail(uint8_t type, int value) {
  switch (type) {
    case TimeOut:
      Serial.println(F("Time Out!"));
      break;
    case WrongStack:
      Serial.println(F("Stack Wrong!"));
      break;
    case DFPlayerCardInserted:
      Serial.println(F("Card Inserted!"));
      break;
    case DFPlayerCardRemoved:
      Serial.println(F("Card Removed!"));
      break;
    case DFPlayerCardOnline:
      Serial.println(F("Card Online!"));
      break;
    case DFPlayerUSBInserted:
      Serial.println("USB Inserted!");
      break;
    case DFPlayerUSBRemoved:
      Serial.println("USB Removed!");
      break;
    case DFPlayerPlayFinished:
      //      Serial.print(F("Number:"));
      //      Serial.print(value);
      Serial.println(F(" Play Finished!"));
      mp3End = true;
      break;
    case DFPlayerError:
      Serial.print(F("DFPlayerError:"));
      switch (value) {
        case Busy:
          Serial.println(F("Card not found"));
          break;
        case Sleeping:
          Serial.println(F("Sleeping"));
          break;
        case SerialWrongStack:
          Serial.println(F("Get Wrong Stack"));
          break;
        case CheckSumNotMatch:
          Serial.println(F("Check Sum Not Match"));
          break;
        case FileIndexOut:
          Serial.println(F("File Index Out of Bound"));
          break;
        case FileMismatch:
          Serial.println(F("Cannot Find File"));
          break;
        case Advertise:
          Serial.println(F("In Advertise"));
          break;
        default:
          break;
      }
    default:
      mp3End = false;
      break;
  }
}

et au moment d'un bug voici le message :

20:06:25.390 ->  dossier n° 7
20:06:25.390 ->  mp3 n° 12
20:06:26.158 -> Time Out!
20:06:26.503 -> DFPlayerError:Cannot Find File

et évidemment il n'y a pas de mp3 n°12 dans le dossier 7, donc ça coince...
c'est donc que cette portion de code qui n'est pas bonne :

 int nbFilesInFolder = MP3Player.readFileCountsInFolder(playlist[folderNb]);

    // sometime, the sd card seems to be lazy. Strange. But fixed by a second try.
    if (nbFilesInFolder <= 0)
    {
      Serial.println("Got no file from the folder. Let's try another time, just in case of a lazy sd card");
      delay(100);
      nbFilesInFolder = MP3Player.readFileCountsInFolder(playlist[folderNb]);
    }
    songNb = map(playlistSong[songIndex], 1, 20, 2, nbFilesInFolder);

Je crois qu'il faut que j'utilise un constrain au lieu du map, non ?
d'après ce que j'ai compris map peut renvoyer une valeur hors de la plage.
Comme dit Boris Vian : "y a quelque chose qui cloche la dedans, j'y retourne immédiatement" :slight_smile:

Le problème peut venir de là en effet. Tu choisis un numéro de chanson de manière aléatoire, mais ce choix suppose qu'il y a 20 chansons dans chaque répertoire. Si ce n'est pas le cas, il faut que tu mémorises dans un tableau le nombre de chanson de chaque répertoire pour vérifier que tu ne dépasses pas ce nombre.
Et traiter si ça dépasse.

sauf que je ne sais pas à l'avance combien il y a de chansons, et je veux garder une possibilité de rajouter (en prévoyant un max de 20 chansons), ou d'enlever des chansons, sans avoir à toucher au code à chaque fois.

D'ailleurs petites questions subsidiaires : Peut-on changer la taille d'un tableau dynamiquement ? est-il possible d'avoir une variable tableau qui évolue en fonction des besoins (qu'on agrandit, qu'on diminue) ?

bon, en fait, il y a des fichiers cachés dans les dossiers, et le DFPlayer les compte, donc je me retrouve avec un nombre de fichiers erronés, je pense que le problème vient de là.

Il faut sans doute nettoyer la carte SD et voir si vous pouvez compter le nombre de fichiers.

Je pense qu'il faut, dans le setup, lire la carte SD : compter le nombre de fichiers (mp3) de chaque répertoire, une fois pour toutes. Pour ça tu peux t'inspirer de l'exemple listfiles :

Comme ça, à chaque exécution, tu auras un contexte propre.

Merci, oui c'est ce que je comptais faire, mais il faut quand même faire attention à bien enlever les fichiers cachés, car sinon ils sont comptés.
la librairie DFPlayerMini inclus déjà une fonction pour compter le nombre de fichiers d'un dossier

bon, ça y est, ça fonctionne, j'ai réussi à corriger le bug, c'était bien une histoire de fichiers cachés que rajoute le mac au moment de copier les mp3 sur la sdcard.
pour résumer, sur les conseils de J-M-L j'ai enlevé l'interruption qui n'était pas nécessaire, et j'ai rajouté ça dans le setup :

[code]
  for (int i = 0; i < nbCard; ++i) {
    mp3Count[i] = MP3Player.readFileCountsInFolder(i + 1);
    Serial.print(" dossier n° : ");
    Serial.println(i + 1);
    Serial.print("nb de fichiers : ");
    Serial.println(mp3Count[i]);
    nbSongTotal += mp3Count[i];
    shuffleSong(i, mp3Count[i]);
  }

[/code]

et j'ai modifié la fonction shuffleSong, maintenant plus de bug.
Merci pour vos conseils

Bravo !

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.