spudnut1
Published

Westminster Chiming Clock with Weather and Date

Chiming clock using RTC that has switchable chimes, cuckoo sounds, etc. and can also highlight special events.

IntermediateShowcase (no instructions)5,041
Westminster Chiming Clock with Weather and Date

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×2
DHT11 Temperature & Humidity Sensor (4 pins)
DHT11 Temperature & Humidity Sensor (4 pins)
×1
Adafruit DS3231 RTC
×1
Adafruit Seven Segment 1.5" LED Red
×1
Adafruit LCD 20x4 Backlit with Backpack
×1
Adafruit Wave Shield
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
×1
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
×3

Story

Read more

Custom parts and enclosures

Acrylic Box

I ordered this as an enclosure after mounting things using L brackets and a base.

Schematics

Component and Arduino Pin Connections

All the connections (except for details on building the Wave Shield)

Code

Clock Code

Arduino
This is the logic for the clock Arduino for time and weather and to control the chiming.
/*
 * Clock project using LED, LCD and Wave Shield
 * Provides date, time, temp, humidity and limited number of event notifications
 * Also can chime in multiple ways, including Westminster chimes and cuckoo clock
 * Chimes are provided by Adafruit Wave Shield on serial bus
 * A second Arduino to run the Wave Shield is used due to memory limitations
 * and all the global variables and libraries involved in sound, LED, LCD, etc.
 */

/*   Files on SD Card must match this sketch:
 *    in format SOUNDnn.WAV where nn is two digit sound number
 *    and one file called "STARTUP.WAV"  that is played once on SETUP
1 Short 1 (no Westminster)
2 Short 2
3 Short 3
4 Short 4
5 Short 5
6 Short 6
7 Short 7
8 Short 8
9 Short 9
10  Short 10
11  Short 11
12  Short 12
13  Q1 (Quarter Hour Chimes)
14  Q2
15  Q3
16  Long 1 (Westminster Hours)
17  Long 2 (with built in Q4)
18  Long 3
19  Long 4
20  Long 5
21  Long 6
22  Long 7
23  Long 8
24  Long 9
25  Long 10
26  Long 11
27  Long 12
28  Cuckoo Clock 1
29  Cuckoo Clock 2
30  Cuckoo Clock 3
31  Cuckoo Clock 4
32  Cuckoo Clock 5
33  Cuckoo Clock 6
34  Cuckoo Clock 7
35  Cuckoo Clock 8
36  Cuckoo Clock 9
37  Cuckoo Clock 10
38  Cuckoo Clock 11
39  Cuckoo Clock 12


 */

// Code to set up the clock
// Pushbutton 1 is the toggler , cycle between vhime, year, month, day, hours, minutes, seconds 
// Pushbutton 2 is the increment up one
// Pushbutton 3 is the decrement down one

// Chime setup
//  0 = No chimes
//  1 = Chime for hour and 1/2 hour only
//  2 = Westminster hours + quarterly
//  3 = Cuckoo clock (hours and 1/2 hour only like above but different chime
//  4 = Westminster silenced after 11pm and before 7am


// include the libraries for LCD Display, DHT-11 Temperature/humidity sensor and DS3231 RTC and
// include libraries for bigger seven segment display with backpack
#include "DHT.h"
#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal_I2C.h>
#include "Adafruit_LEDBackpack.h"


// Set up output functions

Adafruit_7segment matrix = Adafruit_7segment();

LiquidCrystal_I2C lcd(0x27,20,4);     // initialize LCD

#define DHTPIN 8     //Temp Humidity sensor on pin 8
#define DHTTYPE DHT11   // DHT 11 because the sensor is that type

// Initialize DHT sensor for normal 16mhz Arduino
DHT dht(DHTPIN, DHTTYPE);

// Initialize for Adafruit DS3231 RTC real time clock
RTC_DS3231 rtc;


char daysOfTheWeek[7][12] = {"Sunday   ", "Monday   ", "Tuesday  ", "Wednesday", "Thursday ", "Friday   ", "Saturday "};

// Event arrays
// Load with month of 'event', day of event and description to be displayed

const int eventnumber = 19;         // number of events and remember: No strings longer than 20 characters!
int eventmonth [eventnumber] = {6,4,11,2,7,12,11,6,12,6,3,12,1,9,10,3,2,2,7};
int eventday [eventnumber] = {11,1, 23, 12,4,3,12,29,25,18,17,31,1,3,31,17,14,2,14};
char eventdescription [eventnumber] [21] = 
      {"Dana's Anniversary","April Fool's Day", "Joan's Birthday", "Sydney's Birthday", "Happy July 4th!", "Dana's Birthday",
   "David's Birthday", "Our Anniversary", "Merry Christmas", "Leah's Birthday", "St Patrick's Day", "New Year's Eve", "New Year's Day",
   "Forrest's Birthday", "Happy Halloween", "Saint Patrick's Day", "Valentine's Day!", "Groundhog Day", "Bastille Day"};


 
const int pb1pin = 10;              //  Pin assignments for reset, increment and decrement
const int pb2pin = 11;
const int pb3pin = 12;

const int potpin = A0;               // Pin assignment for analog reading potentiometer for LED brightness
int potvalue = 15;                  // Brightness for LED (default to full 0-15)

int setupindex = 0;                 // first thing to set if required
bool insetupmode = false;           // and assume RTC is set, OK, etc., and no 'set' required

// date time array for setting, reading, displaying

const int setsize = 7;            // size of the setting array
const int setchime = 0;           // set chime choice first to help seconds synching
const int setyear = 1;            // index name for each element
const int setmonth = 2;
const int setday = 3;
const int sethour = 4;
const int setminute = 5;
const int setsecond = 6;       

int setarray [setsize] = {0,2019, 1, 1, 1, 1,0};   // set year, month, day,  hour, minutes, seconds and chimes 
int lowlimit [setsize] = {0,2019, 1, 1, 0, 1,0};  // lower limit for each
int highlimit [setsize] = {4,2050, 12, 31, 23, 59, 59};  //high limit for each
char setdesc [setsize] [8] = {"Chimes","Year", "Month", "Day", "Hour", "Minutes", "Seconds"};
char  chimedesc [5] [12] = {"Silent     ", "Hours Only ", "Westminster", "Cuckoo     ", "Night Quiet"};
char  shortchimedesc [5] [5] = {"SLNT","HOUR","WSTM", "CUCK", "ANSO"};   // ANSO stands for Automatic Night Silent Option (11:00 to 6:59am silenced)
const int silent = 0;                                 // no chiming at all
const int hoursonly = 1;                              // no prelude, just chime number of hours and half hour single chime
const int westminster =2;                             // hours + Westminster prelude per quarter
const int cuckoo = 3;                                 // Same as hoursonly, but cuckoo clock sounds
const int ANSO = 4;                                   // Same as westminster, but skip hour from 11:01pm-6:59am (silent then)
const int cqtr0 = 1;       //  Chime the hour
const int cqtr1 = 2;       //  Chime the quarter hour
const int cqtr2 = 3;       //  Chime the half hour
const int cqtr3 = 4;        // Chime the 3/4 hour


//        The following four 'starts' need to be synchronized with the SD Card files
//        They correspond to first file of each chime type (e.g. cuckoo single is SOUND27.WAV)

const int firstshort = 1;       //file position and name of short, single chime
const int firstquarter = 13;    //same for Westminster quarters (1,2,3)
const int firstwestminster = 16; //same for Westminster 1-12 (including their 4th quarter introductions
const int firstcuckoo = 28;      // finally, where the cuckoo clock sounds start


int  setstrike = -1;            // Chime/strike flag
byte  alreadychimed = false ;   // Used to keep from chiming multiple times during the "hot" second
int displaychiming = 0;         // display'chiming' when a signal sent to Wave Shield
const int BounceDelay = 250;    // Not really 'bounce', its a change of state detection
const int wavechannel = 8;      // I2C channel for communicating to Wave Shield

int i;                          // generic index variable
bool event;                     // logic flag "on" = event found
int eventindex;                 // and the event we found
int dayoftheweek;               // stored day of the week (0-6, 0 = Sunday)

int phours;                     // for print conversion of military time
int oldseconds = -1;            // only update display if seconds change (current seconds not equal last time)

float temperaturef;             // farenheit temperature back
float temperaturec;             // centrigade temperature back (not used)

float humidity;                 // and humidity

int LEDTime;              // used for converting single time to 4 digit
float tempadjust = -3.9;  // temperature adjustment for sensor (I found it didn't read right against 'comps')
byte degreesymbol = 223;  // LCD output of 'degree' symbol
int THupdate = -1;        // sensor changes too frequently, only update every 15 seconds



void setup() 
  // put your setup code here, to run once:

{
  Wire.begin();                     // initialize I2C interface
  lcd.init();                       // initialize LCD
  lcd.backlight();
  dht.begin();                      // initialize the temp/humidity sensor
  matrix.begin(0x70);              // initialize LED
  Serial.begin (9600);            //uncomment if needed to debug
 

 pinMode(pb1pin, INPUT_PULLUP);  // The three pushbuttons - reset
 pinMode(pb2pin, INPUT_PULLUP);   // increment
 pinMode(pb3pin, INPUT_PULLUP);   // decrement
 
if (! rtc.begin()) {                        // check that clock is there
    lcd.print("Couldn't find RTC");       // clock missing is a fatal error
    while (1);
  }

if (rtc.lostPower()) {           // if power lost force a setup, else load 'current' values and 
  lcd.print("RTC lost power!");  // only change time on a PB 1 push if its known already
  insetupmode = true;
  for (i=0; i<setsize;i++) {setarray[i] = lowlimit[i];}
  delay(3000);} else
    {DateTime (setarray[setyear],setarray[setmonth],setarray[setday],setarray[sethour],setarray[setminute],setarray[setsecond])  = rtc.now();
    insetupmode = false;
    }

    // Two alternative modes of setting date and time (for debugging):
    // This line sets the RTC to the date & time this sketch was compiled
 //    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
 //   rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));

        setarray[setchime] = westminster;                         // default chime is Westminster after reboot
}      // end of setup

void loop() {
  // Main code divided into setup mode and non-setup mode
 
   
    while (insetupmode){               // Main code for time, date set


        LEDBrightness();                                   // Make sure LED brightness hasn't changed
 
        // Read the increment button

        if (digitalRead(pb2pin) == LOW) {setarray[setupindex]++;
        delay(BounceDelay);
        if (setarray[setupindex] > highlimit[setupindex]) {setarray[setupindex] = lowlimit[setupindex];}
  } 
 

        // Read the decrement Button
   
    if (digitalRead(pb3pin) == LOW) {setarray[setupindex]--;
      delay(BounceDelay);
      if (setarray[setupindex] < lowlimit[setupindex]) {setarray[setupindex] = highlimit[setupindex];}
   }
  
    // Display what we are setting up and the value 
      lcd.setCursor(0,0);
      lcd.print("In Setup Mode:      ");
      lcd.setCursor(0,2);
      lcd.print(setdesc[setupindex]);   // what we are setting up
      lcd.print("  ");                   // and current value
      if (setupindex != setchime) {lcd.print(setarray[setupindex]);
      lcd.print(" ");}
       if (setupindex == setchime) {lcd.print(" "); // Treat Chimes diffently
           i = setarray[setupindex];             // because its not a number, it is
           lcd.print(chimedesc[i]);}         // it is none, hours, all westminster,  night silent or cuckoo!
            
 
     // Read for another increment of index 
     
  if (digitalRead(pb1pin) == LOW){              // Rolling through Chime, Year, Month, Day, Hour, Minutes, Seconds 
      setupindex++ ;
      clearline(2);
      delay(BounceDelay);
    if (setupindex >= setsize) {insetupmode = false;       // and finally exiting setup mode after setting chime, date and time
      rtc.adjust(DateTime(setarray[setyear],setarray[setmonth],setarray[setday],setarray[sethour],setarray[setminute],setarray[setsecond]));
       lcd.clear();                     // clear display from setup stuff
       THupdate = -1;                   // and force immediate update of temperature and humidity
       
   }  //exit setup mode when done
  } 

   }  // End of "While" for setup

   // Begin regular loop for date, time, temp humidity and event  display

   while (!insetupmode){
   
 
      // and onto date, time, etc.
  
    DateTime now = rtc.now();
    setarray[setyear] = now.year();
    setarray[setmonth] = now.month();
    setarray[setday] = now.day();
    setarray[sethour] = now.hour();
    setarray[setminute] = now.minute();
    oldseconds = setarray[setsecond];           // store old seconds
    setarray[setsecond] = now.second();
    dayoftheweek = now.dayOfTheWeek();
    
//  Update display once a second, but not more frequently

    if (oldseconds != setarray[setsecond]) 

    {                                         // start display code
     
// At start of new day, clear the event & date lines because may have become shorter
    if (setarray[sethour] == 0 && setarray[setminute] == 0 && setarray[setsecond] == 0){
        clearline(3);             // clear the event line
        clearline(1);}            // clear the date line    

// Temperature and humidity update

  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
      humidity = dht.readHumidity();
  
  // Read temperature as Fahrenheit
      temperaturef = (dht.readTemperature(true))+ tempadjust;   // read and correct for inaccuracy in sensor
     
  // Check if any reads failed and exit early (to try again).
  if (isnan(humidity) || isnan(temperaturec) || isnan(temperaturef)) {
     temperaturef = 0;              // use 0 as a no-read
     humidity = 0;                  // error indication
  } 
      
    // (note: line 1 is the second row, since counting begins with 0)
  
    // only update temp and humidity display every 15 seconds to eliminate flicker
    
    if (THupdate <= 0) 
    {
    lcd.setCursor(0, 2);
    
    // Print Temperature Farenheit
     lcd.print(temperaturef,1);
    // lcd.setCursor(4,2);
      lcd.print((char)degreesymbol);             // print degree symbol
      lcd.print("F  ");                         // farenheit

   // Print Humidity
     lcd.print(humidity,1);
     lcd.print("%H");
     THupdate = 15;                             // after print, reset the counter
    } else --THupdate;                          // if no print of temp and hum, decrement

      // Print chime condition

      lcd.setCursor(16,2);
      i = setarray[setchime];
      lcd.print(shortchimedesc[i]);

    
    lcd.setCursor(4, 0);          // position for date, time, etc.
  
   if (setarray[sethour] <=12)     // convert military time to am pm
   {phours = setarray[sethour];
   }
   else
   {phours = setarray[sethour]-12;}

 if (phours <= 0){phours = 12;} // don't print 0 for midnite, print "12"
   
          printtwo(phours," ");
          lcd.print(":");
          printtwo(setarray[setminute],"0");
          lcd.print(":");
          printtwo(setarray[setsecond],"0");
          if (setarray[sethour] < 12)
            {lcd.print(" AM   ");}
            else
          {lcd.print(" PM   ");}

 // Now update the LED Time

   LEDBrightness();                                   // Make sure brightness hasn't changed
   LEDTime = (phours*100) + setarray[setminute] ;   // hours and minutes (shifting hours 2 segments left)
   matrix.print(LEDTime, DEC);                                                    // print to LED
  // matrix.drawColon((setarray[setsecond] ==((setarray[setsecond]/2)*2)));         // blink only on even seconds
     matrix.drawColon(setarray[setsecond]%2);                                     // blink only on even seconds
   matrix.writeDisplay();                                                        // and push out the LED print
 
    
  // Now update the LCD date and event lines too
   lcd.setCursor(0, 1);
  // lcd.print(monthsOfTheYear[now.month()-1]);     // took up too much space on the LCD
   lcd.print(setarray[setmonth]);                        // so went with mm/dd/yyyy
   lcd.print("/");
   lcd.print(setarray[setday], DEC);
   lcd.print("/");
   lcd.print(setarray[setyear], DEC); 
   lcd.print(" ");
   lcd.print(daysOfTheWeek[dayoftheweek]);


    lcd.setCursor(0,3);                   // Last line of display is Event or greeting`
  
    event = floatingevent(setarray[setmonth], setarray[setday], dayoftheweek);             // return true if already have an event, false if not
 
    if (event == false) {                                                                  // if no special (non-static) event
    for (int i = 0; i < eventnumber; i++){                                                // then check the statics
 
    if ((setarray[setmonth] == eventmonth[i]) && (setarray[setday] == eventday[i])) {
       event = true;                                                // set if match on month and day
       eventindex = i;
     }}
  
    if (event == true) {                                            // if so, use it, else generic msg
    lcd.print( eventdescription[eventindex]);}
    else 
       if (setarray[sethour] >= 6 && setarray[sethour] <= 11){lcd.print("Good Morning!       ");} else
         if (setarray[sethour] >= 12 && setarray[sethour] <= 16){lcd.print("Good Afternoon!     ");} else 
          if (setarray[sethour] >= 17 && setarray[sethour] <= 21){lcd.print("Good Evening!       ");} else
             {lcd.print("Good Night!         ");} 
          
  
    }
    }        // end of display update logic

  // Logic for chiming
    setstrike = -1;                            // initialize 'strike' flag to "no strike"
   
    if ((setarray[setchime] != silent) && !((setarray[setchime] == ANSO) && ((setarray[sethour] == 23 && setarray[setminute] > 0) || (setarray[sethour] >= 0 && setarray[sethour] < 7))))   // if silenced due to setting silent
    {                                                                                                                                                           //   or ANSO and 11pm-6:59am then skip this                                                                                                  
       if (setarray[setminute] == 0 && setarray[setsecond] == 0){         // Look for 'on the hour'
          setstrike = cqtr0;
        } else
          if (setarray[setminute] == 15 && setarray[setsecond] == 0) {    // Look for on the quarter hour
            setstrike = cqtr1;
          } else 
            if (setarray[setminute] == 30 && setarray[setsecond] == 0){   // Look for on the 1/2 hour
              setstrike = cqtr2;}
              else 
              if (setarray[setminute] == 45 && setarray[setsecond] == 0){   // Look for on the three-quarter hour
              setstrike = cqtr3;}
              else {alreadychimed = false;}                                 // none of the above, reset ability to chime
         
          if (setstrike >0 && !alreadychimed ) {
              chime(setarray[setchime], setstrike, setarray[sethour]);  // call chiming with 'type of chime'; 0,15,30,45 ; and # hours
              alreadychimed = true;                                     // we will be here multiple times within a second
               
              }
    
     }             // end of logic for chiming
     
       
       // Read a potential request for an entry into setup from PB 1
      if (digitalRead(pb1pin) == LOW){                // to see if we go back to setup mode
        insetupmode = true;
        setupindex = 0;
        lcd.clear();
        delay(BounceDelay);
  } 
        
        
   } // end of not in setup while
 }  // end of sketch
 
 void clearline(int line){                      // Simply clears the line its called with and
   lcd.setCursor(0,line);
   lcd.print("                    ");           // and then
   lcd.setCursor(0,line); }                     // Repositions cursor to start of line

 void printtwo(int value, String  fillchar){      // print two always and use fill to make it so
        if (value <10)  {lcd.print(fillchar);}    // blank space for hours, 0 for minutes, seconds
        lcd.print(value);
 }

 // Routine to deal with combinations of chime type, time of day and the current hour
 // called parameters are chime type (e.g., Westminster), Strike type (hour, qtr, half, 3/4) and the hour in military time
 
 void chime (int chimetype, int strikeflag, int chour){        
 
    int chour1;                                                                     // am pm variable
   int callbyte =0;                                                                 // used to talk to Wave Shield
  
   if (chour <=12){chour1 = chour;}  else {chour1 = chour-12;}                      // convert military time to am pm
   
  
   if (chour1 <= 0){chour1 = 12;}                                                   // don't chime 0 for midnite, chime 12
   if (chimetype == hoursonly && strikeflag == cqtr2){callbyte = firstshort;}       //  1/2 hour only, do single chime (same as 1pm, short chime)
        else if ( chimetype == hoursonly && strikeflag == cqtr0) {
            callbyte = chour1;}                                               // hours only and strike hours  
          else   if (((chimetype == westminster) ||(chimetype == ANSO))  && strikeflag == cqtr1) {callbyte = firstquarter;}     // First Quarter
            else   if (((chimetype == westminster) ||(chimetype == ANSO)) && strikeflag == cqtr2) {callbyte = firstquarter+1;}     // Second Quarter
              else   if (((chimetype == westminster) ||(chimetype == ANSO)) && strikeflag == cqtr3) {callbyte = firstquarter+2;}     // Third Quarter
                else   if (((chimetype == westminster) ||(chimetype == ANSO)) && strikeflag == cqtr0) {callbyte = (chour1+firstwestminster-1);}   // Strike Westminster hours 
                  else   if (chimetype == cuckoo && strikeflag == cqtr2){callbyte = firstcuckoo;}       //  1/2 hour only, do single cuckoo (same as 1pm, single cuckoo)
                    else if ( chimetype == cuckoo && strikeflag == cqtr0) { callbyte = (chour1+firstcuckoo-1);}  // hours only and strike cuckoo hours
                                                                     
       
        Wire.beginTransmission(wavechannel);                              // finally transmit single byte result to Wave Shield
        Wire.write(callbyte);                                             // sends one byte
        Wire.endTransmission();                                           // stop transmitting
                                                            
       
        }     // end of Chime
           
 // Routine to look up floating events (Memorial Day, Thanksgiving, Mother's Day and Fathers Day)
 /* Memorial Day is Last Monday in May                                                 
  *  Thanksgiving is 4th Thursday in November
  *  Mother's Day is 2nd Sunday in May
  *  Father's Day is 3rd Sunday in June
  *  MLK Day is 3rd Monday in February
  *  Memorial Day is last Monday in May
  *  Labor Day is first Monday in September 
  *  Daylight Savings Times starts 2nd Sunday in March
  *  Daylight Savings Times ends first Sunday in November
  *  
  */

 bool floatingevent(int m, int d, int dow){                           // called with Month, Day within month and day of the week (0 = Sunday)
   const int sunday = 0;
   const int monday = 1;
   const int thursday = 4;
   String floatstring = "                    ";
   
   if ((dow == thursday) && (m==11) && (d >= 22) && (d <= 28)){                            // Thanksgiving
      floatstring = "Thanksgiving Day!";}
      else if ((dow == sunday) && (m==5) && (d >= 8) && (d <= 14)){                       // Mother's Day
        floatstring = "Mother's Day!";}
        else if ((dow == sunday) && (m==6) && (d >= 15) && (d <= 21)){                    //Father's Day
          floatstring = "Father's Day!";}
          else if ((dow == monday) && (m==1) && (d >= 15) && (d <= 21)){                  //MLK Day
            floatstring = "MLK Day!";}
            else if ((dow == monday) && (m==2) && (d >= 15) && (d <= 21)){                //President's Day
              floatstring = "President's Day!";}
              else if ((dow == monday) && (m==9) && (d <= 7) ){                           //Labor Day
                floatstring = "Labor Day!";}
               else if ((dow == monday) && (m==5) && (d+7 > 31) ){                        //Memorial Day
                floatstring = "Memorial Day!";}
                  else if ((dow == sunday) && (m==3) && (d >= 8) && (d <= 14)){           // DST begins
                    floatstring = "DST Begins";}
                    else if ((dow == sunday) && (m==11) && (d <= 7) ){                     //DST ends
                      floatstring = "DST Ends";}
                  
     if (floatstring == "                    " ){ return false;} else {
                 lcd.print(floatstring);
                 return true;}
    
 }  // end of floatingevent
 void LEDBrightness(){
  potvalue  = analogRead(potpin);                                       // Read potentiometer value
  potvalue = map(potvalue, 0, 1023, 0, 15);                             //map to valid value for brightness
  if (potvalue == 14) {potvalue = potvalue+1;}                            // round up if not full brightness
  matrix.setBrightness(potvalue);
  return;
 }  //end of LEDbrightness
  
    
  

  

Sound Code

Arduino
This program runs on the 2nd Arduino with the Wave Shield. Its pretty simple -- just responds to the I2C 'call' and plays the requested sound file on the SD Card
// Companion to clock module
// Plays the appropriate Sound file when called with a single byte
// on Serial Channel
// Much of the code here comes from PLAY6 example sketch




// #include <FatReader.h>
//#include <SdReader.h>
#include "WaveUtil.h"
#include "WaveHC.h"
#include <Wire.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

int soundchannel = 8;   //I2C channel to listen to [MUST match with clock sketch]
char  filename[15] = {"SOUND00.WAV"};                 // foundational file name SOUNDNN.WAV
int dectoascii = 48;                                  // convert integer to ascii
byte z = 0;

/* Byte should be 0-39 for files
 * Also can chime in multiple ways, including Westminster chimes and cuckoo clock
 * Chimes are provided by Adafruit Wave Shield on serial bus
 * Separate Arduino and Shield used due to memory limitations
 */

/*   Files on SD Card must match this sketch:
 *    in format SOUNDnn.WAV where nn is two digit sound number
 *    and one file called "STARTUP.WAV" that is played once during setup
1 Short 1 (no Westminster)
2 Short 2
3 Short 3
4 Short 4
5 Short 5
6 Short 6
7 Short 7
8 Short 8
9 Short 9
10  Short 10
11  Short 11
12  Short 12
13  Q1 (Quarter Hour Chimes)
14  Q2
15  Q3
16  Long 1 (Westminster Hours)
17  Long 2 (with built in Q4)
18  Long 3
19  Long 4
20  Long 5
21  Long 6
22  Long 7
23  Long 8
24  Long 9
25  Long 10
26  Long 11
27  Long 12
28  Cuckoo Clock 1
29  Cuckoo Clock 2
30  Cuckoo Clock 3
31  Cuckoo Clock 4
32  Cuckoo Clock 5
33  Cuckoo Clock 6
34  Cuckoo Clock 7
35  Cuckoo Clock 8
36  Cuckoo Clock 9
37  Cuckoo Clock 10
38  Cuckoo Clock 11
39  Cuckoo Clock 12

   */
   
   


/* 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 if needed for debugging
     Serial.begin(9600);                    
 
  Wire.begin(soundchannel);                // join i2c bus with address defined
  Wire.onReceive(receiveEvent); // register event

//  putstring_nl("Sound Receiver"); 
//  delay (1000);
//   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. These pins are defined in the library
  pinMode(2, OUTPUT);                       // and actually soldered between Arduino
  pinMode(3, OUTPUT);                       // and the WAVE shield
  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("Ready!");
  playcomplete("STARTUP.WAV");              // play once to show its working
  
 
}

void loop() {
  
   while (z > 0){                   // wait for the interrupt
   
    filename[5] = (z/10) + dectoascii;            // create filename from byte sent
    filename[6] = (z - ((z/10)*10))+ dectoascii;
//    Serial.print(z);
//    Serial.print("  ");
//    Serial.println(filename);
      playcomplete(filename);                     // will play to completion
      z= 0;                                       // and reset flag
        }
    }
   




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


// function that executes whenever data is received from master
// this function is registered as an event, see setup()

void receiveEvent() {
  int x = Wire.read();    // receive byte as an integer
//  Serial.println(x);         // print the integer
  z = x;                  //make the byte available
  return ;
}

Credits

spudnut1

spudnut1

10 projects • 15 followers

Comments