AdaFruit Wave Shield WaveHC dap

De MCHobby - Wiki
Sauter à la navigation Sauter à la recherche

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.

Initialize the card

The hard work of playing music is all done right on the Arduino. This lets us skip having an MP3 decoder or other dedicated chip. That means more control and flexibility, but more firmware! Lets take a tour through the canonical sketch "dapHC.pde" this is a Digital Audio Player (dap) sketch using the Wave HC librarry. We used to use the Adafruit AF_Wave library but Mr. Fat16 did a fantastic job rewriting our code and making it faster, smaller and better. So we're going to use his library in this tutorial :)

Download the dapHC.pde sketch and read along! The first thing we need are some objects. The tough part is talking to the card. All cards are manufactured and formatted a little different. And the formatting has many layers - the card format - the partition format and the filesystem formatting. At the beginning of the sketch we have the #include to get the library header files in and an object for storing information about the card card, partition volume vol and filesystem root. We also have a directory buffer, for information on any folder/directories and an object for storing information about a single wave file wave

These are all pretty much manditory unless perhaps you dont want directory traversal in which case you can probably skip dirBuf

#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

First thing that must be done is initializing the SD card for reading. This is a multistep process. In the setup loop you can see the multiple checks we do as we proceed through the initialization process

Here are the steps:

  1. Wake up and print out the to Serial Monitor that we're running (OPTIONAL)
  2. Check how much free RAM we have after we have used the buffer for storing Wave audio data, make sure its more than 100 bytes and keep an eye on it as you modify your code. This test can be removed, its for your use (OPTIONAL)
  3. Set the pin modes for the DAC control lines. These should not be changed unless you've modified them in the library as well. Its probably best to keep them as-is
  4. Initialize the SD card and see if it responds. We try to talk to it at 8MHz. If you have a waveshield 1.0 you may need to use 4MHz mode so comment out one line and uncommment the other to swap which method is used. If the card fails to initialize, print out an error and halt.
  5. Allow partial block reads. Some SD cards don't like this so if you're having problems, comment this out first! (OPTIONAL)
  6. Try to find a FAT partition in the first 5 slots. You did format the card to FAT format, right? If it cant find a FAT partition it will print out that it failed, so make sure you format it again if its giving you trouble
  7. Print out what kind of FAT partition was found (OPTIONAL)
  8. Try to open up the root directory. If this doesnt work, something is messed up with the formatting. Try to format it again!
  9. Finally, print out the files found, one after the other in the directories on the card. This is great for debugging and will show you what you've got on there. Since we dont have long filename access and use the 'base' 8.3 format to define files, you'll need to see what the files are named on the partition and this helps a lot (OPTIONAL)
 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);
}

Looking for files in a directory

OK now that you've initialized the card, we perform a recursive list of all files found. This is useful for debugging and ALSO shows how you can navigate the file system

To start, pass a directory object (like root) to lsR() which will do the following:

  1. Read a file from the directory. The files are read in the order they are copied into the directory, not alphabetical order!
  2. If the directories are the special links "." (current directory) or ".." (upper directory) it ignores them and goes to step 1 again.
  3. It prints out spaces to create a nicely formatted output. Each level of directory gets 2 spaces
  4. It prints out the name of the file in 8.3 format
  5. If it is a subdirectory, it makes a new object and opens up the subdirectory. Then it prints out all of the files in that new directory
  6. It continues to step 1 until there are no more files to be read
/*
 * 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?
}

There is also a helper called printName which prints out the file in a nice format. Files are named in 8.3 format, an older and simpler way of addressing files. Its a little less pretty than "Long Name Format" so watch out to see what your files are renamed as. For example "Bird song.wav" may be renamed to "BIRDSONG.WAV" or "BIRDSO~1.WAV" !

/*
 * 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
}

One thing that appears in loop() is dir.rewind(). The reason we rewind a directory is that our Arduino code is very simple. It can go through the files in a directory but only 'forward', not backward (FAT format is kinda like that). So if you skipped a file and want to go back, or you've gone through the directory, you will need to call rewind() to set it back to the beginning!

Playing all the files

The digital audio player plays all files in the card. To do that it recursively looks in every directory, just like lsR() above so the code looks somewhat similar. The big difference is we call the play() routine to play a file!

To start, pass a directory object (like root) to lsR() which will do the following:

  1. Read a file from the directory. The files are read in the order they are copied into the directory, not alphabetical order!
  2. If the directories are the special links "." (current directory) or ".." (upper directory) it ignores them and goes to step 1 again.
  3. It prints out spaces to create a nicely formatted output. Each level of directory gets 2 spaces
  4. It prints out the name of the file in 8.3 format
  5. If it is a subdirectory, it makes a new object and opens up the subdirectory. Then it plays all of the wave files in that new directory
  6. If it isn't a subdirectory, it will try to play the file by opening it as a Wave object. That requires looking through the file and trying to find a Wave header, etc. If it doesnt succeed it will print out that its not valid and skip to the next file
  7. If the wave file is valid, it will finally start the file by calling play() on the Wave object
  8. While the wave sound file is playing, it prints out a dot every 100 ms or so.
  9. It continues to step 1 until there are no more files to be read
/*
 * 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
      }
    }
  }
}

dap_hc.pde

Here is the full 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
      }
    }
  }
}

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.