AdaFruit Wave Shield WaveHC dap
Avoir plus de RAM et de Flash!
Note MCHobby: Les plateformes Arduino sont maintenant distribuée avec un ATmega328.
Cette section n'est vraiment pertinente que si vous ne possédez pas d'ATmega328.
Avant d'essayer de jouer de l'audio, vous aurez besoin de faire un peu de place
la mémoire RAM d'Arduino, cela évitera que cela se termine en vilain
dépassement de pile (stack-overflow). Manquer de mémoire RAM est difficile à déboguer et frustrant, d'autant plus si vous disposez d'un ATMega168
Suivez ces instructions (anglais) pour savoir comment avoir plus
de RAM en réduisant la taille de la mémoire tampon de la librairie série (Serial library).
Vous n'aurez pas besoin de faire cela si vous avez un ATmega328.
Notez que la librairie est vraiment grosse (approximativement 10K). Si vous voulez faire beaucoup plus avec Arduino, il est vivement conseillé de faire un Upgrade vers un ATmega328. Ce shield a été développé en prévoyant la disponibilité d'un ATmega328.
Une introduction pour dap_hc.pde
Ceci est un tutoriel de la librairie waveHC basé sur le projet dap_hc.pde
C'est détaillé et un peu intimidant. Mais attardez vous puisque la plupart du code peut être dupliqué d'un sketch à l'autre.
Assurez vosu que vous avez installé la librairie en la téléchargeant depuis le lien ci dessus et placé dans un sous-répertoire WaveHC de votre répertoire "libraries". Le fichier zip contient également le projet dap_hc.pde
Nous avons placé ce sketch en bas de cet article au cas où vous en auriez besoin.
Initialiser la carte
Les tâches complexes nécessaires au rendu musical est assuré par Arduino. Cela nous épargne d'avoir recours à un décodeur MP3ou autre circuit dédicacé. Cela signifie plus de contrôle et de flexibilité mais aussi pas de firmware!
Faisons donc un tour d'horizon du sketch "dapHC.pde". C'est un sketch de type "Digital Audio Player" (dap) utilisant la librairie Wave HC. Nous avions écrit ce projet pour l'utilisé avec notre librairie Adafruit AF_Wave mais Mr. Fat16 à réalisé un fantastique travail en réécrivant notre code en le rendant plus rapide, plus petit et meilleur. Donc nous allons utiliser sa librairie pour ce tutoriel :)
Téléchargez le sketch dapHC.pde et prenez le temps de le lire. La première chose dont nous avons besoin sont quelques objets. La partie la plus difficile est de dialoguer avec la carte. Toutes les cartes sont fabriquée et formattées différemment (juste un peu). Et le formatage repose sur différentes couches - le format de la carte - Le format de la partition et le formatage du système de fichier.
Au début du sketch nous avons l' #include qui incorpore le fichier d'entête de la librairie (fichier header) files ainsi qu'un objet card qui stocke les information concernant la carte, un autre vol relatif à la partition (partition volume) et le système de fichier root.
Nous avons aussi un tampon pour les répertoires, contenant des informations sur tous les répertoires (folders) et un objet stockant des information à propos d'un seul fichier wave.
Ils sont tous obligatoires à moins que vous ne désiriez pas lire la liste des fichiers. Dans ce cas, vous pouvez probablement vous passer de dirBuf
#include "WaveUtil.h" #include "WaveHC.h" SdReader card; // Cet objet maintien des informations sur la carte FatVolume vol; // Maintien des information due la partition de la carte FatReader root; // Maintien des information sur le système de fichier de la carte uint8_t dirLevel; // Niveau d indentation pour fichiers et répertoire (pour un affichage plus sympa) dir_t dirBuf; // tampon pour les lectures des répertoires WaveHC wave; // Le seul objet wave (audio), puisque nous jouons une seul fichier à la fois
La première chose à faire est d'initialiser la carte SD pour faire des lectures. C'est un processus qui se réalise en plusieurs étapes. Dans la fonction setup vous pouvez voir les différentes vérification qui sont faites par le processus d'initialisation.
Voici les étapes:
- Initialisation et affichage sur la connexion série pour indiquer que nous démarrons (OPTIONNEL)
- Vérifier la quantité de mémoire disponible après avoir utilisé la mémoire tampon pour stocker les données audio du Wave, faite en sorte qu'il reste plus de 100 octets (bytes) et gardez un oeil dessus si vous modifiez votre code. Ce test peut être enlevé, il n'est destiné qu'a votre information (OPTIONNEL)
- Initialise les pin modes pour le DAC (lignes de contrôles du DAC). Cela ne devrait pas être modifié à moins que vous ayez aussi modifié la librairie. Il est certainement préférable de les garder tels quels.
- Initialiser la carte SD et vérifier qu'elle réponde. Nous essayons de l'adresser à 8MHz. Si vous disposez d'un Shield Wave 1.0 vous pourriez avoir besoin d'utiliser le mode 4MHz (dé-commentez/re-commentez la ligne appropriée en fonction de la méthode utilisée). Si la carte ne s'initialise pas, alors afficher une erreur et arrêter.
- Autoriser la lecture de bloc partiel. Certaines cartes SD n'apprécient pas vraiment. Commenter en priorité cette ligne si vous avez des problèmes! (OPTIONNEL)
- Essayer de trouver la partition FAT dans les 5 premiers slots. Vous avez bien formatez votre carte en FAT n'est-ce pas? S'il ne peut pas trouver la partition FAT il affichera un message d'erreur. En cas de problème, assurez vous que vous avez bien formaté la carte en FAT (quitte à re-formater la carte).
- Affiche le type de partition FAT qui a été trouvée (OPTIONNEL)
- Essaye d'ouvrir le répertoire racine. Si cela ne fonctionne pas, c'est que quelque-chose s'est mal déroulé au cours du formatage. Essayer de formater la carte une fois de plus!
- Finalement, afficher l'un après l'autre les fichiers trouvés dans les répertoires de la carte. C'est vraiment génial pour déboguer et vous montrera ce qui est disponible sur la carte. Puisque nous n'utilisons pas de nom de fichier long mais le format de base 8.3 pour définir les fichiers, vous aurez besoin de voir comment les fichiers sont nommés sur la partition (et cela peut vraiment aider beaucoup) (OPTIONNEL)
void setup() { Serial.begin(9600); // initialise la libraire serie a 9600 bauds pour deboguer putstring_nl("\nWave test!"); // Indiquer le "demarrage"! putstring("Free RAM: "); // cela peut aider pour deboguer, ne plus avoir assez de RAM est mauvais signe Serial.println(freeRam()); // Initialiser les pins pour le controle du DAC. Ces pins sont définies dans la librairie pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); // if (!card.init(true)) { //spi cadence a 4 MHz si 8MHz ne fonctionne pas if (!card.init()) { //spi cadencé a 8 MHz (par defaut, le plus rapide!) putstring_nl("Init Carte. Echec!"); // Quelque chose ne marche pas, affichons pourquoi sdErrorCheck(); while(1); // Et arrêtons (ne plus rien faire)! } // Activer la lecture optimisee - Certaines cartes pourrait bloquer (timeout). Desactiver en cas de probleme card.partialBlockRead(true); // Recherche de la partition FAT! uint8_t part; for (part = 0; part < 5; part++) { // Vérifier dans les 5 slots disponibles if (vol.init(card, part)) break; // Nous en avons trouvé un, sortons de la boucle } if (part == 5) { // Terminé la boucle sans trouvé de partitions :( putstring_nl("Pas de partition FAT valide!"); sdErrorCheck(); // Qlque chose ne va pas, affichons pourquoi while(1); // et arreter le programme (ne plus rien faire)! } // Indiquons à l'utilisateur ce que nous avons trouvé putstring("Utilise la partition "); Serial.print(part, DEC); putstring(", type est FAT"); Serial.println(vol.fatType(),DEC); // FAT16 ou FAT32? // Essayer d ouvrir le répertoire racine if (!root.openRoot(vol)) { putstring_nl("Echec ouverture répertoire racine!"); // Quelque chose ne va pas, while(1); // et arreter le programme (ne plus rien faire)! } // Whaow! nous avons passé toutes les étapes difficiles. putstring_nl("Fichiers trouvés:"); dirLevel = 0; // Afficher tous les fichiers dans tous les répertoires. lsR(root); }
Chercher les fichiers dans un répertoire
OK maintenant que la carte est initialisée, nous affichons une liste de tous les fichiers que nous trouvons. C'est utile pour deboguer et vous montre aussi comment naviguer dans le système de fichier.
Pour commencer, passer un objet répertoire (directory en anglais) tel que root a la fonction lsR().
lsR effectue les opérations suivantes:
- Lire un fichier du répertoire. Les fichiers sont lus dans l'ordre dans lequel ils sont copiés dans le répertoire, pas par ordre alphabétique!
- Si le répertoire à un nom/liens spécial "." (répertoire courant) or ".." (répertoire parent) ignorez les et répéter encore l'opération 1.
- Il affiche des espaces pour formater correctement les sortie de message. Chaque niveau de répertoire prends deux espaces de plus.
- Affiche le nom du fichier au format 8.3
- S'il s'agit d'un sous répertoire, il crée un nouvel objet et ouvre le sous répertoire. Ensuite, il affiche tous les fichiers de ce nouveau répertoire.
- Il continue à l'étape 1 jusqu'a ce qu'il ne reste plus de fichier à lire
/* * Affichage récursif - Possiblilité de dépassement de pile (stack overflow) * s'il y a trop de répertoire imbriqués */ void lsR(FatReader &d) { int8_t r; // indique le niveau de récursion while ((r = d.readDir(dirBuf)) > 0) { // Lire le prochain fichier dans le répertoire // Ignore les sous répertoires . et .. if (dirBuf.name[0] == '.') continue; for (uint8_t i = 0; i < dirLevel; i++) Serial.print(' '); // Meilleur affichage, place des espaces devant printName(dirBuf); // Afficher le nom de fichier trouvé Serial.println(); // et un retour à la ligne if (DIR_IS_SUBDIR(dirBuf)) { // Nous allons répéter (récursion) pour chaque sous-répertoire FatReader s; // Créer un nouvel objet répertoire pour maintenir les informations dirLevel += 2; // indenter de 2 espaces (pour les affichages futurs) if (s.open(vol, dirBuf)) lsR(s); // Afficher tous les fichiers du répertoire. Maintenant! dirLevel -=2; // retirer l'indentation ajoutée (quand fini de lister le sous-répertoire) } } sdErrorCheck(); // Est-ce que tout est OK? }
Il y a aussi une fonction d'aide nommée printName qui affiche le nom du fichier (dans un format lisible). les fichiers sont nommés au format 8.3, une vieille façon d'adresser les fichier (mais aussi plus simple). C'est un petit peu moins beau qu'afficher les noms au format long, donc regardez quand même comment vos fichiers sont re-nommés en format court. Par exemple, le fichier "Bird song.wav" pourrait être renommé en "BIRDSONG.WAV" ou "BIRDSO~1.WAV" !
/* * Affiche le champs name de dir_t. Format de sortie est 8.3, ressemble donc à SOUND.WAV ou FILENAME.DAT */ void printName(dir_t &dir) { for (uint8_t i = 0; i < 11; i++) { // format 8.3 a 8+3 = 11 lettres if (dir.name[i] == ' ') continue; // N'affiche pas les espaces dans le nom if (i == 8) Serial.print('.'); // placer un point après la 8ieme lettre Serial.print(dir.name[i]); // afficher la lettre à la position n } if (DIR_IS_SUBDIR(dir)) Serial.print('/'); // Afficher un "/" à la fin quand c'est un répertoire }
Une des chose qui apparait dans loop() c'est dir.rewind() (action de remonter) . La raison pour laquelle nous remontons les répertoires c'est que le code de notre Arduino est très simple. Il peut traverser les fichiers d'un répertoire mais seulement 'vers l'avant' (forward), pas vers l'arrière (le format FAT est un peu comme ca).
Donc, si vous avez raté un fichier ou si vous voulez aller en arrière, ou si vous avez traversé le répertoire, vous devrez appeler rewind() pour revenir au début du répertoire!
Jouer tous les fichiers
Le "digital audio player" joue tous les fichier de la carte. Pour ce faire, il cherche les fichiers dans tous les répertoires de façon récursive (comme le fait lsR() ci-avant). Cela produit un code assez similaire eu point précédent.
La grande différence c'est que nous appelons la fonction play() qui recherche les fichiers à jouer!
Le fichier est joué à l'aide de wave.play()
Pour commencer, passez un objet répertoire (tel que root) a play() effectue les étapes suivantes:
- Lit un fichier depuis le répertoire. Les fichier sont lus dans l'ordre dans lequel ils ont étés copiés dans le répertoire, pas l'ordre alphabétique!
- Si le répertoire à un nom/lien spécial comme "." (répertoire courant) or ".." (répertoire parent) sont ignorés et on recommence à l'étape 1.
- Il affiche des espace pour créer un format d'affichage plus sympathique. Chaque niveau de répertoire ajoute deux espaces.
- Il affiche le nom du fichier au format 8.3 .
- Si c'est un sous répertoire, il crée un nouvel objet et ouvre le sous répertoire. Ensuite, il joue tous les fichioers wave qu'il y a dans ce nouveau répertoire.
- Si ce n'est pas un sous répertoire, il va essayer de jouer le fichier en ouvrant celui-ci comme un objet Wave. Cela nécessite d'ouvrir le fiochier et d'essayer de trouver l'entête Wave (Wave header), etc. S'il n'y arrive pas, il affiche un message comme quoi le fichier n'est pas valide puis il passe au fichier suivant.
- Si le fichier wave est valide, il commence à le jouer en appelant la fonction play() de l'objet Wave
- Tant que le fichier wave est jouer, le programme affiche un point toutes les 100 ms.
- Le programme continue à l'étape 1 jusqu'à ce qu'il n'y ait plus de fichier à lire.
/* * Joue récursivement - Possibilité de dépassement de pile (stack overflow) * s'il y a trop de répertoire imbriqués */ void play(FatReader &dir) { FatReader file; while (dir.readDir(dirBuf) > 0) { // lit un à un chaque fichier du répertoire // skip . and .. directories if (dirBuf.name[0] == '.') continue; Serial.println(); // Affiche une nouvelle ligne for (uint8_t i = 0; i < dirLevel; i++) Serial.print(' '); // amélioration de l'affichage, place des espace à l'avant if (!file.open(vol, dirBuf)) { // Ouvre le fichier dans le répertoire Serial.println("file.open failed"); // Qlque chose s'est mal passé :( while(1); // Arrêter } if (file.isDir()) { // Vérifie si c'est un nouveau sous-répertoire putstring("Subdir: "); printName(dirBuf); dirLevel += 2; // Ajouter plus d'espace // play files in subdirectory play(file); // Récursivité! dirLevel -= 2; } else { // HaHa! nous avons trouvé un fichier qui n'est pas un répertoire putstring("Joue... "); printName(dirBuf); // Afficher le nom du fichier if (!wave.create(file)) { // Est-ce un fichier WAV ? putstring(" fichier WAV invalide"); // non, alors on passe au suivant } else { Serial.println(); // Hourra c'est un fichier WAV! wave.play(); // Faisons donc un peu de bruit! while (wave.isplaying) { // La reproduction audio utilise les interruption, putstring("."); // nous affichons donc les points en temps réel delay(100); } sdErrorCheck(); // Tout est OK? // if (wave.errors)Serial.println(wave.errors); // Erreur de decodage wave } } } }
dap_hc.pde
Voici le sketch dans son intégralité.
Note MCHobby: Les commentaires ont étés traduit dans les notes explicatives ci-dessus. Il ne le sont plus dans ce sketch.
#include <FatReader.h> #include <SdReader.h> #include <avr/pgmspace.h> #include "WaveUtil.h" #include "WaveHC.h" SdReader card; // This object holds the information for the card FatVolume vol; // This holds the information for the partition on the card FatReader root; // This holds the information for the filesystem on the card uint8_t dirLevel; // indent level for file/dir names (for prettyprinting) dir_t dirBuf; // buffer for directory reads WaveHC wave; // This is the only wave (audio) object, since we will only play one at a time // Function definitions (we define them here, but the code is below) void lsR(FatReader &d); void play(FatReader &dir); void setup() { Serial.begin(9600); // set up Serial library at 9600 bps for debugging putstring_nl("\nWave test!"); // say we woke up! putstring("Free RAM: "); // This can help with debugging, running out of RAM is bad Serial.println(freeRam()); // Set the output pins for the DAC control. This pins are defined in the library pinMode(2, OUTPUT); pinMode(3, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); // if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you if (!card.init()) { //play with 8 MHz spi (default faster!) putstring_nl("Card init. failed!"); // Something went wrong, lets print out why sdErrorCheck(); while(1); // then 'halt' - do nothing! } // enable optimize read - some cards may timeout. Disable if you're having problems card.partialBlockRead(true); // Now we will look for a FAT partition! uint8_t part; for (part = 0; part < 5; part++) { // we have up to 5 slots to look in if (vol.init(card, part)) break; // we found one, lets bail } if (part == 5) { // if we ended up not finding one :( putstring_nl("No valid FAT partition!"); sdErrorCheck(); // Something went wrong, lets print out why while(1); // then 'halt' - do nothing! } // Lets tell the user about what we found putstring("Using partition "); Serial.print(part, DEC); putstring(", type is FAT"); Serial.println(vol.fatType(),DEC); // FAT16 or FAT32? // Try to open the root directory if (!root.openRoot(vol)) { putstring_nl("Can't open root dir!"); // Something went wrong, while(1); // then 'halt' - do nothing! } // Whew! We got past the tough parts. putstring_nl("Files found:"); dirLevel = 0; // Print out all of the files in all the directories. lsR(root); } //////////////////////////////////// LOOP void loop() { root.rewind(); play(root); } /////////////////////////////////// HELPERS // this handy function will return the number of bytes currently free in RAM, great for debugging! int freeRam(void) { extern int __bss_end; extern int *__brkval; int free_memory; if((int)__brkval == 0) { free_memory = ((int)&free_memory) - ((int)&__bss_end); } else { free_memory = ((int)&free_memory) - ((int)__brkval); } return free_memory; } /* * print error message and halt if SD I/O error, great for debugging! */ void sdErrorCheck(void) { if (!card.errorCode()) return; putstring("\n\rSD I/O error: "); Serial.print(card.errorCode(), HEX); putstring(", "); Serial.println(card.errorData(), HEX); while(1); } /* * print dir_t name field. The output is 8.3 format, so like SOUND.WAV or FILENAME.DAT */ void printName(dir_t &dir) { for (uint8_t i = 0; i < 11; i++) { // 8.3 format has 8+3 = 11 letters in it if (dir.name[i] == ' ') continue; // dont print any spaces in the name if (i == 8) Serial.print('.'); // after the 8th letter, place a dot Serial.print(dir.name[i]); // print the n'th digit } if (DIR_IS_SUBDIR(dir)) Serial.print('/'); // directories get a / at the end } /* * list recursively - possible stack overflow if subdirectories too nested */ void lsR(FatReader &d) { int8_t r; // indicates the level of recursion while ((r = d.readDir(dirBuf)) > 0) { // read the next file in the directory // skip subdirs . and .. if (dirBuf.name[0] == '.') continue; for (uint8_t i = 0; i < dirLevel; i++) Serial.print(' '); // this is for prettyprinting, put spaces in front printName(dirBuf); // print the name of the file we just found Serial.println(); // and a new line if (DIR_IS_SUBDIR(dirBuf)) { // we will recurse on any direcory FatReader s; // make a new directory object to hold information dirLevel += 2; // indent 2 spaces for future prints if (s.open(vol, dirBuf)) lsR(s); // list all the files in this directory now! dirLevel -=2; // remove the extra indentation } } sdErrorCheck(); // are we doign OK? } /* * play recursively - possible stack overflow if subdirectories too nested */ void play(FatReader &dir) { FatReader file; while (dir.readDir(dirBuf) > 0) { // Read every file in the directory one at a time // skip . and .. directories if (dirBuf.name[0] == '.') continue; Serial.println(); // clear out a new line for (uint8_t i = 0; i < dirLevel; i++) Serial.print(' '); // this is for prettyprinting, put spaces in front if (!file.open(vol, dirBuf)) { // open the file in the directory Serial.println("file.open failed"); // something went wrong :( while(1); // halt } if (file.isDir()) { // check if we opened a new directory putstring("Subdir: "); printName(dirBuf); dirLevel += 2; // add more spaces // play files in subdirectory play(file); // recursive! dirLevel -= 2; } else { // Aha! we found a file that isnt a directory putstring("Playing "); printName(dirBuf); // print it out if (!wave.create(file)) { // Figure out, is it a WAV proper? putstring(" Not a valid WAV"); // ok skip it } else { Serial.println(); // Hooray it IS a WAV proper! wave.play(); // make some noise! while (wave.isplaying) { // playing occurs in interrupts, so we print dots in realtime putstring("."); delay(100); } sdErrorCheck(); // everything OK? // if (wave.errors)Serial.println(wave.errors); // wave decoding errors } } } }
Source: Wave Shield créé par LadyAda pour AdaFruit. Crédit: AdaFruit.com.
Traduit par Meurisse D. pour MCHobby.be.
Traduit avec l'autorisation d'AdaFruit Industries - Translated with the permission from Adafruit Industries - www.adafruit.com
Toute référence, mention ou extrait de cette traduction doit être explicitement accompagné du texte suivant : « Traduction par MCHobby (www.MCHobby.be) - Vente de kit et composants » avec un lien vers la source (donc cette page) et ce quelque soit le média utilisé.
L'utilisation commercial de la traduction (texte) et/ou réalisation, même partielle, pourrait être soumis à redevance. Dans tous les cas de figures, vous devez également obtenir l'accord du(des) détenteur initial des droits. Celui de MC Hobby s'arrêtant au travail de traduction proprement dit.