The Play6_HC Example

Get more RAM & Flash!

Before you try to play audio, you'll want to free up some Arduino RAM, so that you don't end up with a nasty stack-overflow. Running out of RAM is hard to debug and frustrating, and likely if you're using a '168.

Follow these instructions on how to get more RAM by reducing the input Serial library buffer. You don't need to do this if you're using an ATmega328.

Note that the library is pretty big (about 10K) so if you want to do a lot more, I suggest upgrading to an ATmega328. The shield was designed with the expectation that this upgrade would be available.

A tour of play6_hc.pde

This is a tutorial of the waveHC library.

It's detailed and a little daunting. But stick with it since much of the code is going to be duplicated from this sketch to others!

Make sure you install the library by downloading it from the link above and sticking WaveHC folder in the libraries folder.

In case you need the sketch we're referring to here, it's at the bottom of the page.

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 an object for holding the current file information f and an object for storing information about a single wave file wave.

#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
FatReader f;      // This holds the information for the file we're playing


WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time

#define DEBOUNCE 100  // button debouncer
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 it's more than 100 bytes and keep an eye on it as you modify your code. This test can be removed, it's 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. It's 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 can't find a FAT partition it will print out that it failed, so make sure you format it again if it's giving you trouble.
  7. Print out what kind of FAT partition was found. (OPTIONAL)
  8. Try to open up the root directory. If this doesn't work, something is messed up with the formatting. Try to format it again!
void setup() {
  // set up serial port
  Serial.begin(9600);
  putstring_nl("WaveHC with 6 buttons");
  
   putstring("Free RAM: ");       // This can help with debugging, running out of RAM is bad
  Serial.println(freeRam());      // if this is under 150 bytes it may spell trouble!
  
  // 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);
 
  // pin13 LED
  pinMode(13, OUTPUT);
 
  // enable pull-up resistors on switch pins (analog inputs)
  digitalWrite(14, HIGH);
  digitalWrite(15, HIGH);
  digitalWrite(16, HIGH);
  digitalWrite(17, HIGH);
  digitalWrite(18, HIGH);
  digitalWrite(19, HIGH);
 
  //  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("Ready!");
  dirLevel = 0;
}

Button interfacing

We want to play a sound each time a button is pressed. We will use a function called check_switches() that goes through the 6 buttons (digital 14 through 20) to see if they have been pressed. If so, we play SOUND1.WAV (for example) completely through. The function that we call here that does the playing is called playcomplete() and we pass the name of the Wave file in quotes just like you see here.
void loop() {
  //putstring(".");            // uncomment this to see if the loop isnt running
  switch (check_switches()) {
    case 1:
      playcomplete("SOUND1.WAV");
      break;
    case 2:
      playcomplete("SOUND2.WAV");
      break;
    case 3:
      playcomplete("SOUND3.WAV");
      break;
    case 4:
      playcomplete("SOUND4.WAV");
      break;
    case 5:
      playcomplete("SOUND5.WAV");
      break;
    case 6:
      playcomplete("SOUND6.WAV");
  }
}

byte check_switches()
{
  static byte previous[6];
  static long time[6];
  byte reading;
  byte pressed;
  byte index;
  pressed = 0;

  for (byte index = 0; index < 6; ++index) {
    reading = digitalRead(14 + index);
    if (reading == LOW && previous[index] == HIGH && millis() - time[index] > DEBOUNCE)
    {
      // switch pressed
      time[index] = millis();
      pressed = index + 1;
      break;
    }
    previous[index] = reading;
  }
  // return switch number (1 - 6)
  return (pressed);
}

Playcomplete & Playfile

Here is where we open the file and play it.

Playcomplete is very simple, it just calls a function that starts the audio playback and then sits in a loop doing nothing.

Playfile is the important function. It finds and opens the file and plays it.

  1. It first sees if we're already playing any audio. If so, it stops it.
  2. Now it opens the root directory and looks for the file by the name we requested. If it can't find it, the function returns.
  3. If it finds it, it tries to turn it into a Wave file object, looking for the right header in the file. If not it also returns.
  4. If it succeeds, it begins to play.
// Plays a full file from beginning to end with no pause.
void playcomplete(char *name) {
  // call our helper to find and play this name
  playfile(name);
  while (wave.isplaying) {
  // do nothing while its playing
  }
  // now its done playing
}

void playfile(char *name) {
  // see if the wave object is currently doing something
  if (wave.isplaying) {// already playing something, so stop it!
    wave.stop(); // stop it
  }
  // look in the root directory and open the file
  if (!f.open(root, name)) {
    putstring("Couldn't open file "); Serial.print(name); return;
  }
  // OK read the file and turn it into a wave object
  if (!wave.create(f)) {
    putstring_nl("Not a valid WAV"); return;
  }
  
  // ok time to play! start playback
  wave.play();
}

play6_hc.pde

#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
FatReader f;      // This holds the information for the file we're play

WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time

#define DEBOUNCE 100  // button debouncer

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

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);
}

void setup() {
  // set up serial port
  Serial.begin(9600);
  putstring_nl("WaveHC with 6 buttons");
  
   putstring("Free RAM: ");       // This can help with debugging, running out of RAM is bad
  Serial.println(freeRam());      // if this is under 150 bytes it may spell trouble!
  
  // 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);
 
  // pin13 LED
  pinMode(13, OUTPUT);
 
  // enable pull-up resistors on switch pins (analog inputs)
  digitalWrite(14, HIGH);
  digitalWrite(15, HIGH);
  digitalWrite(16, HIGH);
  digitalWrite(17, HIGH);
  digitalWrite(18, HIGH);
  digitalWrite(19, HIGH);
 
  //  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("Ready!");
}

void loop() {
  //putstring(".");            // uncomment this to see if the loop isnt running
  switch (check_switches()) {
    case 1:
      playcomplete("SOUND1.WAV");
      break;
    case 2:
      playcomplete("SOUND2.WAV");
      break;
    case 3:
      playcomplete("SOUND3.WAV");
      break;
    case 4:
      playcomplete("SOUND4.WAV");
      break;
    case 5:
      playcomplete("SOUND5.WAV");
      break;
    case 6:
      playcomplete("SOUND6.WAV");
  }
}

byte check_switches()
{
  static byte previous[6];
  static long time[6];
  byte reading;
  byte pressed;
  byte index;
  pressed = 0;

  for (byte index = 0; index < 6; ++index) {
    reading = digitalRead(14 + index);
    if (reading == LOW && previous[index] == HIGH && millis() - time[index] > DEBOUNCE)
    {
      // switch pressed
      time[index] = millis();
      pressed = index + 1;
      break;
    }
    previous[index] = reading;
  }
  // return switch number (1 - 6)
  return (pressed);
}


// Plays a full file from beginning to end with no pause.
void playcomplete(char *name) {
  // call our helper to find and play this name
  playfile(name);
  while (wave.isplaying) {
  // do nothing while its playing
  }
  // now its done playing
}

void playfile(char *name) {
  // see if the wave object is currently doing something
  if (wave.isplaying) {// already playing something, so stop it!
    wave.stop(); // stop it
  }
  // look in the root directory and open the file
  if (!f.open(root, name)) {
    putstring("Couldn't open file "); Serial.print(name); return;
  }
  // OK read the file and turn it into a wave object
  if (!wave.create(f)) {
    putstring_nl("Not a valid WAV"); return;
  }
  
  // ok time to play! start playback
  wave.play();
}
Last updated on 2015-12-26 at 01.06.17 PM Published on 2013-07-17 at 12.39.05 PM