John Bradnam
Published © GPL3+

World Alarm Clock

A battery powered alarm clock with a analog face, digital face and four city times complete with automatic daylight saving adjustment.

IntermediateFull instructions provided8 hours498
World Alarm Clock

Things used in this project

Hardware components

ATTINY3216
Microchip ATTINY3216
×1
Noika 5110 LCD Screen
×1
Tactile Switch, Top Actuated
Tactile Switch, Top Actuated
17mm shaft with button tops
×3
Buzzer
Buzzer
×1
18650 Battery and Holder
×1
Real Time Clock (RTC)
Real Time Clock (RTC)
DS1302 + 32768KHz watch crystal + CR2032 coin battery and holder
×1
Passive components
2x330R 0805 resistors, 1 x 0.1uF 0805 capacitor, 1 x 10uF 1206 capacitor
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for 3D printing

Schematics

PCB

Eagle Files

Schematic and PCB in Eagle format

Schematic

Code

Analog_World_clock_1.ino

Arduino
/*============================================================================================================
  Analog and Digital Alarm Clock

  CPU:  ATtiny3216
  Display: Nokia 5110
 
  Digital Code: jbrad2089@gmail.com
  Analog Code:  The scientist formerly known as Naegeli ... please subscribe to my YouTube channel
  
  BOARD: ATtiny3226/3216/1616/1626/826/824/824/426/416...
  Chip: ATtiny3216
  Clock Speed: 4MHz Internal
  millis()/micros() Timer: TCD0 (1-series only, default there)
  Programmer: jtag2updi

  ATtiny3216 Pins mapped to Ardunio Pins
                            _____
                    VDD   1|*    |20  GND
   (nSS)  (AIN4) PA4  0~  2|     |19  16~ PA3 (AIN3)(SCK)(EXTCLK)
          (AIN5) PA5  1~  3|     |18  15  PA2 (AIN2)(MISO)
   (DAC)  (AIN6) PA6  2   4|     |17  14  PA1 (AIN1)(MOSI)
          (AIN7) PA7  3   5|     |16  17  PA0 (AIN0/nRESET/UPDI)
          (AIN8) PB5  4   6|     |15  13  PC3
          (AIN9) PB4  5   7|     |14  12  PC2
   (RXD) (TOSC1) PB3  6   8|     |13  11~ PC1 (PWM only on 1-series)
   (TXD) (TOSC2) PB2  7~  9|     |12  10~ PC0 (PWM only on 1-series)
   (SDA) (AIN10) PB1  8~ 10|_____|11   9~ PB0 (AIN11)(SCL)
 

  2022-03-14  - Took Analog_clock_10 as base
              - Modified SELECT button handler to be polled and not interrupt driven. Interrupts
                is only used when the alarm is sounding to break out of sound loop.
              - Added world time screen to display local time and 3 times in other time zones.
              - Added setup mode for world times
              - Added EEPROM support to save alarm and world time zones

==============================================================================================================*/

#include <LCD5110_Graph.h>      //Library for Nokia 5110 display
#include <TimeLib.h>
#include <DS1302RTC.h>

#define SCLK 12   //PC2 D12
#define DIN 13    //PC3 D13
#define DC 14     //PA1 D14
#define CS_LCD 15 //PA2 D15
#define RST 16    //PA3 D16

#define RTC_CE 2  //PA6 D2
#define RTC_IO 1  //PA5 D1
#define RTC_CLK 0 //PA4 D0

#define SPEAKER 9 //PB0 D9
#define SWITCHES 4 //PB5 D4
#define LIGHT 11  //PC1  D11

LCD5110 myGLCD(SCLK,DIN,DC,RST,CS_LCD);
#include "Analog.h"
#include "Digital.h"
#include "Button.h"
#include "Sound.h"
#include "Memory.h"

DS1302RTC rtc(RTC_CE, RTC_IO, RTC_CLK);

#define MY_LCD_CONTRAST 60
#define BACKLIGHT_TIMEOUT 5 //Timeout in seconds
#define BACKLIGHT_BEIGHTNESS 192 //0=full on, 255 = full off
#define BACKLIGHT_OFF 255
uint8_t backlightTimeout = BACKLIGHT_TIMEOUT; //Backlight timeout in seconds
DisplayEnum displayMode = ANALOG;

//Define buttons
enum ButtonEnum { SELECT_BTN, BACK_BTN, NEXT_BTN };
Button selectButton; 
Button backButton; 
Button nextButton; 
//Flag set via interrupt handler when SELECT button pressed
volatile bool selectButtonInterrupt = false;
//Forward references
void selectButtonPressed(void);
void nextButtonPressed(void);
void backButtonPressed(void);

//Secondary menus
enum ClockEnum { CLK, CLK_H, CLK_M, CLK_Y, CLK_O, CLK_D };
ClockEnum clockMode = CLK;
enum WorldEnum { WLD, WLD_0, WLD_1, WLD_2, WLD_3 };
WorldEnum worldMode = WLD;
enum AlarmEnum { ALM, ALM_H, ALM_M, ALM_O };
AlarmEnum alarmMode = ALM;

#define FLASH_TIME 200          //Time in mS to flash digit being set

int lastSeconds = -1;           //Used to detect change in second to update digital clock
int lastMinutes = -1;           //Used to detect change in minute to update analog clock
bool alarmCancelled = false;    //Whether user has cancelled the alarm
bool alarmRinging = false;      //Whether the alarm is currently ringing
uint16_t flash = 0;             //Flash timeout when setting clock or alarm
bool on = false;                //Used to flash display when setting clock or alarm
tmElements_t newTime;           //Used to store new time
tmElements_t newAlarm;          //Used to store new alarm

int8_t dom[] = {31,28,31,30,31,30,31,31,30,31,30,31};

void setup()
{
  pinMode(LIGHT,OUTPUT);
  analogWrite(LIGHT, BACKLIGHT_BEIGHTNESS);

  readEepromData();
  displayMode = (DisplayEnum)EepromData.displayMode;

  initialiseSound(SPEAKER);

  //Initialise Buttons
  selectButton = Button(SELECT_BTN, SWITCHES, 0, 100);
  backButton = Button(BACK_BTN, SWITCHES, 750, 849);
  backButton.Repeat(backButtonPressed);
  nextButton = Button(NEXT_BTN, SWITCHES, 850, 949);
  nextButton.Repeat(nextButtonPressed);
  
  myGLCD.InitLCD();
  myGLCD.setContrast(MY_LCD_CONTRAST);
  
  #ifdef INCLUDE_RTC_DEBUG_MESSAGES
    myGLCD.setFont(SmallFont);
    myGLCD.print("Analog clock", CENTER, 7);
    myGLCD.print("DS1302", CENTER, 16);
    myGLCD.print("Version 1", CENTER, 25);
    myGLCD.update();
  
    delay(2000);
    myGLCD.clrScr();
    if (rtc.haltRTC())
      myGLCD.print("1302 stopped!", CENTER, 7);
    else
      myGLCD.print("1302 working!", CENTER, 7);
    if (rtc.writeEN())
      myGLCD.print("1302 write OK!", CENTER, 16);
    else
      myGLCD.print("1302 write Bad", CENTER, 16);
  #endif    

  setSyncProvider(rtc.get);          // the function to get the time from the RTC
  if(timeStatus() != timeSet)
  {

    #ifdef INCLUDE_RTC_DEBUG_MESSAGES
      myGLCD.print("RTC Sync Bad", CENTER, 25);
    #endif

    newTime.Year = CalendarYrToTm(2020);
    newTime.Month = 4;
    newTime.Day = 30;
    newTime.Hour = 12;
    newTime.Minute = 10;
    newTime.Second = 0;
    time_t t = makeTime(newTime);
    setTime(t);
    //use the time_t value to ensure correct weekday is set
    if (rtc.set(t) != 0) 
    {
      #ifdef INCLUDE_RTC_DEBUG_MESSAGES
        myGLCD.print("Set Time Failed", CENTER, 34);
      #endif
    }
  }
  
  #ifdef INCLUDE_RTC_DEBUG_MESSAGES
    myGLCD.update();
    delay(5000);
  #endif
  
  newTime.Year = CalendarYrToTm(year());
  newTime.Month = month();
  newTime.Day = day();
  newTime.Hour = hour();
  newTime.Minute = minute();
  newTime.Second = second();

  newAlarm.Year = newTime.Year;
  newAlarm.Month = newTime.Month;
  newAlarm.Day = newTime.Day;
  newAlarm.Hour = EepromData.alarmHour;
  newAlarm.Minute = EepromData.alarmMinute;
  newAlarm.Second = 0;
}

//Handle pin change interrupt when SELECT button is pressed
void SwitchInterrupt()
{
  if (musicPlaying)
  {
    musicPlaying = false;
    alarmCancelled = alarmRinging;
  }
}

void loop()
{
  testButtons();
  if (clockMode != CLK)
  {
    updateTime();
  }
  else if (worldMode != WLD)
  {
    updateWorld();
  }
  else if (alarmMode != ALM)
  {
    updateAlarm();
  }
  else if (lastSeconds != second())
  {
    lastSeconds = second();
    if (backlightTimeout > 0)
    {
      backlightTimeout--;
      if (backlightTimeout == 0)
      {
        //Turn off backlight after BACKLIGHT_TIMEOUT seconds
        analogWrite(LIGHT, BACKLIGHT_OFF);
      }
    }
    
    switch (displayMode)
    {
      case ANALOG: 
        if (lastMinutes != minute())
        {
          lastMinutes = minute();
          myGLCD.clrScr();
          drawMinutes(lastMinutes);
          drawHours(hour(),lastMinutes);
          drawDisplay();
          myGLCD.update();      
        }
        break;
        
      case DIGITAL: 
        drawDigitalClock(now(), EepromData.formatDow, EepromData.format24hr, true, true, true, true, true, (lastSeconds & 0x01)); 
        break;
        
      case WORLD: 
        drawWorldClock(now(), true, true, true, true, (lastSeconds & 0x01)); 
        break;
        
      case ALARM: 
        drawAlarmClock(makeTime(newAlarm), EepromData.alarmEnabled, true, true, true, true, true); 
        break;
    }

    if (EepromData.alarmEnabled && newAlarm.Hour == hour() && newAlarm.Minute == minute())
    {
      if (!alarmCancelled)
      {
        attachInterrupt(digitalPinToInterrupt(SWITCHES),SwitchInterrupt,CHANGE);
        alarmRinging = true;
        playSong(melody_range); 
        detachInterrupt(digitalPinToInterrupt(SWITCHES));
      }
    }
    else 
    {
      alarmCancelled = false;
      alarmRinging = false;
    }
    
  }
  delay(50);
}

//Test if any buttons have been pressed
void testButtons()
{

  //Single press buttons
  if (selectButton.Pressed())
  {
    selectButtonPressed();
  }

  //Don't need to check result of pressed since the button handler will invoke its repeat function
  backButton.Pressed();
  nextButton.Pressed();
}

//Handle SELECT button
void selectButtonPressed()
{
  if (clockMode != CLK)
  {
    if (clockMode == CLK_D)
    {
      backlightTimeout = BACKLIGHT_TIMEOUT; //Reset timeout
      clockMode = CLK;
      if (newTime.Year != CalendarYrToTm(year()) || newTime.Month != month() || newTime.Day != day() || newTime.Hour != hour() || newTime.Minute != minute())
      {
        time_t t = makeTime(newTime);
        setTime(t);
        rtc.set(t);
        /*
        if (rtc.set(t) != 0) 
        {
          //myGLCD.print("Set Time Failed", CENTER, 34);
        }
        */
      }
    }
    else 
    {
      clockMode = (ClockEnum)(clockMode + 1);
    }
  }
  else if (worldMode != WLD)
  {
    if (worldMode == WLD_3)
    {
      writeEepromData();
      backlightTimeout = BACKLIGHT_TIMEOUT; //Reset timeout
      worldMode = WLD;
    }
    else 
    {
      worldMode = (WorldEnum)(worldMode + 1);
    }
  }
  else if (alarmMode != ALM)
  {
    //In alarm mode
    if (alarmMode == ALM_O)
    {
      writeEepromData();
      backlightTimeout = BACKLIGHT_TIMEOUT; //Reset timeout
      alarmMode = ALM; 
    }
    else
    {
      alarmMode = (AlarmEnum)(alarmMode + 1);
    }
  }
  else if (backlightTimeout == 0)
  {
    //First press, turn on backlight
    backlightTimeout = BACKLIGHT_TIMEOUT;
    analogWrite(LIGHT, BACKLIGHT_BEIGHTNESS);
  }
  else  
  {
    //if backlight not timed out, switch modes
    backlightTimeout = BACKLIGHT_TIMEOUT; //Reset timeout
    displayMode = (DisplayEnum)(displayMode + 1);
    if (displayMode == END_MODE)
    {
      displayMode = ANALOG;
    }
  }
  //force refresh
  lastSeconds = -1;
  lastMinutes = -1;
}

//Handle NEXT btton
void nextButtonPressed()
{
  if (displayMode == DIGITAL)
  {
    switch(clockMode)
    {
      case CLK:
        //First press of next button   
        flash = millis() + FLASH_TIME;
        clockMode = CLK_H; 
        break; 
        
      case CLK_H: 
        newTime.Hour = (newTime.Hour + 1) % 24;
        break;
        
      case CLK_M: 
        newTime.Minute = (newTime.Minute + 1) % 60;
        break;
        
      case CLK_Y: 
        newTime.Year = ((newTime.Year - 30) + 1) % 100 + 30;
        break;
        
      case CLK_O: 
        newTime.Month = ((newTime.Month - 1) + 1) % 12 + 1;
        break;
        
      case CLK_D:
        uint8_t md = daysInMonth(newTime.Year, newTime.Month);
        newTime.Day = (newTime.Day % md) + 1;
        break;
    }
    updateTime();
  }
  
  else if (displayMode == WORLD)
  {
    switch(worldMode)
    {
      case WLD:   
        //First press of next button   
        flash = millis() + FLASH_TIME;
        worldMode = WLD_0; 
        break;
        
      default:
        int slot = ((int)worldMode) - 1;
        int count = getWorldZonesCount();
        int index = min(getIndexFromCityName(EepromData.selections[slot]), count-1);
        index = (index + 1) % count;
        strcpy(EepromData.selections[slot], getRuleFromIndex(index)->name);
        break;
    }
    updateWorld();
  }
  
  else if (displayMode == ALARM)
  {
    switch(alarmMode)
    {
      case ALM:   
        //First press of next button   
        flash = millis() + FLASH_TIME;
        alarmMode = ALM_H; 
        break;
        
      case ALM_H: 
        newAlarm.Hour = (newAlarm.Hour + 1) % 24;
        break;
        
      case ALM_M: 
        newAlarm.Minute = (newAlarm.Minute + 1) % 60;
        break;
        
      case ALM_O:
        EepromData.alarmEnabled = !EepromData.alarmEnabled;
        break;
    }
    updateAlarm();
  }
}  

//Handle BACK btton
void backButtonPressed()
{
  if (displayMode == DIGITAL)
  {
    switch(clockMode)
    {
      case CLK:
        //First press of back button   
        flash = millis() + FLASH_TIME;
        clockMode = CLK_H; 
        break; 
        
      case CLK_H: 
        newTime.Hour = (newTime.Hour + 24 - 1) % 24;
        break;
        
      case CLK_M: 
        newTime.Minute = (newTime.Minute + 60 - 1) % 60;
        break;
        
      case CLK_Y: 
        newTime.Year = ((newTime.Year - 30 + 100) - 1) % 100 + 30;
        break;
        
      case CLK_O: 
        newTime.Month = ((newTime.Month - 1 + 12) - 1) % 12 + 1;
        break;
        
      case CLK_D: 
        uint8_t md = daysInMonth(newTime.Year, newTime.Month);
        newTime.Day = ((newTime.Day - 1 + md) - 1) % md + 1;
        break;
    }
    updateTime();
  }

  else if (displayMode == WORLD)
  {
    switch(worldMode)
    {
      case WLD:   
        //First press of next button   
        flash = millis() + FLASH_TIME;
        worldMode = WLD_0; 
        break;
        
      default:
        int slot = ((int)worldMode) - 1;
        int count = getWorldZonesCount();
        int index = min(getIndexFromCityName(EepromData.selections[slot]), count-1);
        index = (index + count - 1) % count;
        strcpy(EepromData.selections[slot], getRuleFromIndex(index)->name);
        break;
    }
    updateWorld();
  }
  
  else if (displayMode == ALARM)
  {
    switch(alarmMode)
    {
      case ALM:
        //First press of back button   
        flash = millis() + FLASH_TIME;
        alarmMode = ALM_H; 
        break;
        
      case ALM_H: 
        newAlarm.Hour = (newAlarm.Hour + 24 - 1) % 24;
        break;
        
      case ALM_M: 
        newAlarm.Minute = (newAlarm.Minute + 60 - 1) % 60;
        break;
        
      case ALM_O:
        EepromData.alarmEnabled = !EepromData.alarmEnabled;
        break;
    }
    updateAlarm();
  }
}

//Update time shown when changing the time
void updateTime()
{
  //Make sure days are valid in the current month
  newTime.Day = min(newTime.Day, daysInMonth(CalendarYrToTm(newTime.Year), newTime.Month));
  //Update display
  if (millis() > flash)
  {
    flash = millis() + FLASH_TIME;
    on = !on;
    drawDigitalClock(makeTime(newTime), false, true, (on || clockMode != CLK_H), (on || clockMode != CLK_M),  (on ||  clockMode != CLK_D),  (on || clockMode != CLK_O),  (on || clockMode != CLK_Y), true); 
  }
}    

//Update time shown when changing the time
void updateWorld()
{
  //Update display
  if (millis() > flash)
  {
    flash = millis() + FLASH_TIME;
    on = !on;
    drawWorldClock(now(), (on || worldMode != WLD_0), (on || worldMode != WLD_1),  (on || worldMode != WLD_2),  (on || worldMode != WLD_3), true); 
  }
}    

//Update alarm shown when changing the time
void updateAlarm()
{    
  if (millis() > flash)
  {
    flash = millis() + FLASH_TIME;
    on = !on;
    drawAlarmClock(makeTime(newAlarm), EepromData.alarmEnabled, true, (on || alarmMode != ALM_H), (on || alarmMode != ALM_M),  (on ||  alarmMode != ALM_O), true); 
  }
}

//Return the days in a given year and month
//Feb has 28 unless leap year or the turn of a century
uint8_t daysInMonth(int y, int m)
{
  return dom[m - 1] + ((m == 2 && (y % 4) == 0 && (y % 100) != 0) ? 1 : 0);
}

Analog.h

C/C++
//-------------------------------------------------------
// Code design: 
//	 thescientistformerlyknownasNaegeli 
//   https://www.instructables.com/member/thescientistformerlyknownasNaegeli/

#pragma once

//forward references
void drawMinutes(int m);
void drawHours(int h, int m);
void drawDisplay();

#define CLOCK_CENTRE_X 42
#define CLOCK_CENTRE_Y 24
extern uint8_t SmallFont[];

// Draw an analog clock face on the LCD
//   h - Hours
//   m - Minutes
void drawAnalogClock(time_t t)
{
  myGLCD.clrScr();
  drawMinutes(minute(t));
  drawHours(hour(t),minute(t));
  drawDisplay();
  myGLCD.update();      
}

//Draw the minute hand
void drawMinutes(int m)
{
  float x1, y1, x2, y2, x3, y3, x4, y4, Am, k = 0.0175;
  Am = (m * 6);
  
  x1=25*sin(Am*0.0175);
  y1=25*-cos(Am*0.0175);
  x2=3*sin(Am*0.0175);
  y2=3*-cos(Am*0.0175);
  x3=10*sin((Am+8)*0.0175);
  y3=10*-cos((Am+8)*0.0175);
  x4=10*sin((Am-8)*0.0175);
  y4=10*-cos((Am-8)*0.0175);
  
  myGLCD.drawLine(x1+CLOCK_CENTRE_X, y1+CLOCK_CENTRE_Y, x3+CLOCK_CENTRE_X, y3+CLOCK_CENTRE_Y);
  myGLCD.drawLine(x3+CLOCK_CENTRE_X, y3+CLOCK_CENTRE_Y, x2+CLOCK_CENTRE_X, y2+CLOCK_CENTRE_Y);
  myGLCD.drawLine(x2+CLOCK_CENTRE_X, y2+CLOCK_CENTRE_Y, x4+CLOCK_CENTRE_X, y4+CLOCK_CENTRE_Y);
  myGLCD.drawLine(x4+CLOCK_CENTRE_X, y4+CLOCK_CENTRE_Y, x1+CLOCK_CENTRE_X, y1+CLOCK_CENTRE_Y);
  myGLCD.drawCircle(CLOCK_CENTRE_X, CLOCK_CENTRE_Y,2);
}

//Draw the hour hand
void drawHours(int h, int m)
{
  float x1, y1, x2, y2, x3, y3, x4, y4, Ah;             
  Ah = (h * 30) + (m * 0.5);
  
  x1=20*sin(Ah*0.0175);
  y1=20*-cos(Ah*0.0175);
  x2=3*sin(Ah*0.0175);
  y2=3*-cos(Ah*0.0175);
  x3=8*sin((Ah+14)*0.0175);
  y3=8*-cos((Ah+14)*0.0175);
  x4=8*sin((Ah-14)*0.0175);
  y4=8*-cos((Ah-14)*0.0175);
  
  myGLCD.drawLine(x1+CLOCK_CENTRE_X, y1+CLOCK_CENTRE_Y, x3+CLOCK_CENTRE_X, y3+CLOCK_CENTRE_Y);
  myGLCD.drawLine(x3+CLOCK_CENTRE_X, y3+CLOCK_CENTRE_Y, x2+CLOCK_CENTRE_X, y2+CLOCK_CENTRE_Y);
  myGLCD.drawLine(x2+CLOCK_CENTRE_X, y2+CLOCK_CENTRE_Y, x4+CLOCK_CENTRE_X, y4+CLOCK_CENTRE_Y);
  myGLCD.drawLine(x4+CLOCK_CENTRE_X, y4+CLOCK_CENTRE_Y, x1+CLOCK_CENTRE_X, y1+CLOCK_CENTRE_Y);
}

//Draw the clock front
void drawDisplay()
{  
  for (int i=0; i<12; i++)    // Draw a small mark for every hour
  {
    float x1, y1, x2, y2, Ah;
    Ah = i * 30; 
    x1=30*sin(Ah*0.0175);
    y1=30*-cos(Ah*0.0175);
    x2=26*sin(Ah*0.0175);
    y2=26*-cos(Ah*0.0175);
    myGLCD.drawLine(x1+CLOCK_CENTRE_X, y1+CLOCK_CENTRE_Y, x2+CLOCK_CENTRE_X, y2+CLOCK_CENTRE_Y);
    //myGLCD.setContrast(65);
    myGLCD.setFont(SmallFont);
    myGLCD.print("2",73,3);
    myGLCD.print("3",78,20);
    myGLCD.print("4",73,37);  
    myGLCD.print("8",7,37);
    myGLCD.print("9",0,20);
    myGLCD.print("10",2,3);
  }
}

Digital.h

C/C++
//-------------------------------------------------------
// Code design: 
//	 John Bradnam 
//   jbrad2089@gmail.com

#pragma once

#include "World.h"

//forward references
void drawPrimaryDigits(time_t t, bool h24, bool he, bool me, bool colon);

//Smaller Bug Numbers table
const uint8_t BigNumbers[] PROGMEM =
{
0x0e, 0x18, 0x30, 0x0a,
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0xef, 0xc7, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xc7, 0xef, 0x00, 0x00, 0x7f, 0xbf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf8, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xc7, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x3f, 0x7f, 0x00,   // 1
0x00, 0x00, 0x02, 0x06, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0xe0, 0xd0, 0xb8, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x3b, 0x17, 0x0f, 0x00, 0x00, 0x7f, 0xbf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0x80, 0x00, 0x00,   // 2
0x00, 0x00, 0x02, 0x06, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xbb, 0xd7, 0xef, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 3
0x00, 0xfc, 0xf8, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xf8, 0xfc, 0x00, 0x00, 0x0f, 0x17, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xbb, 0xd7, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x3f, 0x7f, 0x00,   // 4
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x06, 0x02, 0x00, 0x00, 0x00, 0x0f, 0x17, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xb8, 0xd0, 0xe0, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 5
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x06, 0x02, 0x00, 0x00, 0x00, 0xef, 0xd7, 0xbb, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xb8, 0xd0, 0xe0, 0x00, 0x00, 0x7f, 0xbf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 6
0x00, 0x00, 0x02, 0x06, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xc7, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x3f, 0x7f, 0x00,   // 7
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0xef, 0xd7, 0xbb, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xbb, 0xd7, 0xef, 0x00, 0x00, 0x7f, 0xbf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 8
0x00, 0xfc, 0xfa, 0xf6, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0xf6, 0xfa, 0xfc, 0x00, 0x00, 0x0f, 0x17, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0xbb, 0xd7, 0xef, 0x00, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xbf, 0x7f, 0x00,   // 9
};

extern uint8_t SmallFont[];

//Draw a digital clock
// t - time to display
// dow - true to show day of week version of date
// h24 - true for 24hr format
// he - true to display hours
// me - true to display minutes
// de - true to display day
// oe - true to display month
// ye - true to display year
// colon - true to display colon
void drawDigitalClock(time_t t, bool dow, bool h24, bool he, bool me, bool de, bool oe, bool ye, bool colon)
{

  //Draw time in large font
  drawPrimaryDigits(t, h24, he, me, colon);
  
  //Heading
  myGLCD.setFont(SmallFont);
  myGLCD.drawRect(0,3,83,47);
  myGLCD.print(" TIME ", CENTER, 0);
  
  //show date
  String sd = String(day(t));
  String sm = String(monthShortStr(month(t)));
  if (dow)
  {
    myGLCD.print(String(dayShortStr(weekday(t))) + " " + sd + " " + sm, CENTER, 39);
  }
  else
  {
    myGLCD.print((de) ? (String((day(t) < 10) ? " " : "") + sd) : String("  "), 10, 39);
    myGLCD.print((oe) ? sm : String("   "), 26, 39);
    myGLCD.print((ye) ? String(year(t)) : String("    "), 49, 39);
  }
  
  myGLCD.update();
}

//Draw alarm time
// t - time to display
// on - true when alarm is on
// h24 - true for 24hr format
// he - true to display hours
// me - true to display minutes
// ae - true to display alarm state
// colon - true to display colon
void drawAlarmClock(time_t t, bool on, bool h24, bool he, bool me, bool ae, bool colon)
{
  //Draw Alarm time in large digits
  drawPrimaryDigits(t, h24, he, me, colon);
  
  //Heading
  myGLCD.setFont(SmallFont);
  myGLCD.drawRect(0,3,83,47);
  myGLCD.print(" ALARM ", CENTER, 0);
  
  //show alarm state
  myGLCD.print("Alarm: " + String((ae) ? ((on) ? "ON " : "OFF") : "   "), CENTER, 39);

  myGLCD.update();
}

//Draw a world clock
// t - time to display
// w0 - true to display first world time
// w1 - true to display second world time
// w2 - true to display third world time
// w3 - true to display forth world time
// colon - true to display colon
void drawWorldClock(time_t t, bool w0, bool w1, bool w2, bool w3, bool colon)
{
  myGLCD.clrScr();
  
  //Heading
  myGLCD.setFont(SmallFont);
  myGLCD.drawRect(0,3,83,47);
  myGLCD.print(" WORLD ", CENTER, 0);

  myGLCD.print(getWorldTimeString(t,0,w0,colon),2,9);
  myGLCD.print(getWorldTimeString(t,1,w1,true),2,19);
  myGLCD.print(getWorldTimeString(t,2,w2,true),2,29);
  myGLCD.print(getWorldTimeString(t,3,w3,true),2,39);
  myGLCD.update();
}

//Draw time in large font
// t - time to display
// h24 - true for 24hr format
// he - true to display hours
// me - true to display minutes
// colon - true to display colon
void drawPrimaryDigits(time_t t, bool h24, bool he, bool me, bool colon)  
{
  myGLCD.clrScr();
  
  int h = hour(t);
  int m = minute(t);
  myGLCD.setFont(BigNumbers);
  //hour
  if (he)
  {
    if (!h24)
    {
      if (h > 12)
      {
        h = h - 12;
      }
      myGLCD.print(String(h), ((h < 10) ? 24 : 10), 10);
    }
    else
    {
      myGLCD.print(((h < 10) ? "0" : "") + String(h), 10, 10);
    }
  }
  if (me)
  {
    //show minute
    myGLCD.print(((m < 10) ? "0" : "") + String(m), 47, 10);
  }
  if (colon)
  {
    //Show colon
    myGLCD.drawRect(41,14,43,18);
    myGLCD.drawRect(41,26,43,30);
    myGLCD.drawRect(42,14,42,18);
    myGLCD.drawRect(42,26,42,30);
  }
}
  

World.h

C/C++
//-------------------------------------------------------
// Code design: 
//   John Bradnam 
//   jbrad2089@gmail.com

#pragma once

#include <TimeLib.h>
#include <Timezone.h>
#include "Memory.h"

//---------------------  World clock ------------------------------------
//Define your timezone and daylight saving dates here

// Sydney
const TimeChangeRule AEDT PROGMEM = {"AEDT", First, Sun, Oct, 2, 660};    //UTC + 11 hours
const TimeChangeRule AEST PROGMEM = {"AEST", First, Sun, Apr, 3, 600};    //UTC + 10 hours

// New Zealand
const TimeChangeRule NZDT PROGMEM = {"NZDT", Last, Sun, Sep, 2, 780};    // UTC + 13 hours
const TimeChangeRule NZST PROGMEM = {"NZST", First, Sun, Apr, 3, 720};   // UTC + 12 hours

// US Pacific Time Zone (Las Vegas, Los Angeles)
const TimeChangeRule USPDT PROGMEM = {"PDT", Second, Sun, Mar, 2, -420};
const TimeChangeRule USPST PROGMEM = {"PST", First, Sun, Nov, 2, -480};

// US Mountain Time Zone (Denver, Salt Lake City)
const TimeChangeRule USMDT PROGMEM = {"MDT", Second, Sun, Mar, 2, -360};
const TimeChangeRule USMST PROGMEM = {"MST", First, Sun, Nov, 2, -420};

// US Central Time Zone (Chicago, Houston)
const TimeChangeRule USCDT PROGMEM = {"CDT", Second, Sun, Mar, 2, -300};
const TimeChangeRule USCST PROGMEM = {"CST", First, Sun, Nov, 2, -360};

//US Eastern Time Zone (New York, Detroit)
const TimeChangeRule USEDT PROGMEM = {"EDT", Second, Sun, Mar, 2, -240};  //Eastern Daylight Time = UTC - 4 hours
const TimeChangeRule USEST PROGMEM = {"EST", First, Sun, Nov, 2, -300};   //Eastern Standard Time = UTC - 5 hours

// United Kingdom (London, Belfast)
const TimeChangeRule BST PROGMEM = {"BST", Last, Sun, Mar, 1, 60};        // British Summer Time
const TimeChangeRule GMT PROGMEM = {"GMT", Last, Sun, Oct, 2, 0};         // Standard Time

//Central European Time (Frankfurt, Paris)
const TimeChangeRule CEST PROGMEM = {"CEST", Last, Sun, Mar, 2, 120};     // Central European Summer Time
const TimeChangeRule CET PROGMEM = {"CET ", Last, Sun, Oct, 3, 60};       // Central European Standard Time

//Moscow
const TimeChangeRule MSK PROGMEM = {"MSK", Last, Sun, Mar, 1, 180}; 

// UTC
const TimeChangeRule utcRule = {"UTC", Last, Sun, Mar, 1, 0};     // UTC
Timezone zone(utcRule);

struct RULE
{
  char name[4]; // three chars max
  const TimeChangeRule* dl;
  const TimeChangeRule* st;
};

//Define the number of cities or timezones you want to support here
//Leave the NULL entry at the end, it is the delimiter
RULE worldChoices[] = {
  {"UTC", &utcRule, &utcRule},
  {"SYD", &AEDT, &AEST},
  {"AKL", &NZDT, &NZST},
  {"LAX", &USPDT, &USPST},
  {"PHX", &USMST, &USMST},  //Phoenix doesn't have daylight saving or summer time
  {"DEN", &USMDT, &USMST},
  {"IAH", &USCDT, &USCST},
  {"NYC", &USEDT, &USEST},
  {"LON", &BST, &GMT},
  {"PAR", &CEST, &CET},
  {"MOW", &MSK, &MSK},  //Moscow doesn't have daylight saving or summer time
  {"", NULL, NULL}
};

//Return a pointer to the rule for the given city name
int getWorldZonesCount()
{
  int count = 0;
  RULE* p = worldChoices;
  while (p->dl != NULL)
  {
    count++;
    p++;
  }
  return count;
}

//Return a index to the rule for the given city name
int getIndexFromCityName(char* city)
{
  RULE* p = worldChoices;
  int index = 0;
  while (p->dl != NULL  && strcmp(p->name, city) != 0)
  {
    index++;
    p++;
  }
  return index;
}

//Return a pointer to the rule for the given index
RULE* getRuleFromIndex(int index)
{
  RULE* p = worldChoices;
  while (p->dl != NULL  && index > 0)
  {
    p++;
    index--;
  }
  return p;
}

//Return a pointer to the rule for the given city name
RULE* getRuleFromCityName(char* city)
{
  RULE* p = worldChoices;
  while (p->dl != NULL  && strcmp(p->name, city) != 0)
  {
    p++;
  }
  return p;
}

//Return a formatted string for a world time
// t - local time
// i - 0 - local time, 1-3 - world time from selections table
// s - true to show city name 
// colon - true to show colon
// Returns string in the form "nam 00:00 dow"
String getWorldTimeString(time_t t, int i, bool s, bool colon)
{

  //Get local timezone
  TimeChangeRule stdTime;
  TimeChangeRule dayTime;
  char name[4];

  //Get UTC for local time
  time_t utc = t;
  RULE* r = getRuleFromCityName(EepromData.selections[0]);
  if (r->dl != NULL)
  {
    strcpy(name, r->name);    
    memcpy_P(&dayTime, r->dl, sizeof(TimeChangeRule));
    memcpy_P(&stdTime, r->st, sizeof(TimeChangeRule));
    zone.setRules(dayTime, stdTime);
    utc = zone.toUTC(t);
  }

  if (i > 0)
  {
    r = getRuleFromCityName(EepromData.selections[i]);
    if (r->dl != NULL)
    {
      strcpy(name, r->name);    
      memcpy_P(&dayTime, r->dl, sizeof(TimeChangeRule));
      memcpy_P(&stdTime, r->st, sizeof(TimeChangeRule));
      zone.setRules(dayTime, stdTime);
      t = zone.toLocal(utc);
    }
  }

  int h = hour(t);
  int m = minute(t);
  String d = (s) ? String(name) + " " : "    ";
  d += ((h < 10) ? "0" : "") + String(h);
  d += (colon) ? ":" : " ";
  d += ((m < 10) ? "0" : "") + String(m);
  d += " " + String(dayShortStr(weekday(t)));
  return d;
}

Button.h

C Header File
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#pragma once
#include "Arduino.h"

#define DEBOUNCE_DELAY 10

//Repeat speed
#define REPEAT_START_SPEED 500
#define REPEAT_INCREASE_SPEED 50
#define REPEAT_MAX_SPEED 50

class Button
{
	public:
	//Simple constructor
  Button();
	Button(uint8_t name, uint8_t pin);
	Button(uint8_t name, uint8_t pin, uint16_t analogLow, uint16_t analogHigh, bool activeLow = true);

  //Background function called when in a wait or repeat loop
  void Background(void (*pBackgroundFunction)());
	//Repeat function called when button is pressed
  void Repeat(void (*pRepeatFunction)());
	//Test if button is pressed
	bool IsDown(void);
	//Test whether button is pressed and released
	//Will call repeat function if one is provided
	bool Pressed();
	//Return button state (HIGH or LOW) - LOW = Pressed
	int State();
  //Return button name
  uint8_t Name();

	private:
		uint8_t _name;
		uint8_t _pin;
		bool _range;
		uint16_t _low;
		uint16_t _high;
		bool _activeLow;
		void (*_repeatCallback)(void);
		void (*_backgroundCallback)(void);
};

Button.cpp

C/C++
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#include "Button.h"

Button::Button()
{
}

Button::Button(uint8_t name, uint8_t pin)
{
	_name = name;
	_pin = pin;
	_range = false;
	_low = 0;
	_high = 0;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
	pinMode(_pin, INPUT);
}

Button::Button(uint8_t name, uint8_t pin, uint16_t analogLow, uint16_t analogHigh, bool activeLow)
{
	_name = name;
	_pin = pin;
	_range = true;
	_low = analogLow;
	_high = analogHigh;
	_activeLow = activeLow;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
	pinMode(_pin, INPUT);
}

//Set function to invoke in a delay or repeat loop
void Button::Background(void (*pBackgroundFunction)())
{
  _backgroundCallback = pBackgroundFunction;
}

//Set function to invoke if repeat system required
void Button::Repeat(void (*pRepeatFunction)())
{
  _repeatCallback = pRepeatFunction;
}

  bool Button::IsDown()
{
	if (_range)
	{
		uint16_t value = analogRead(_pin);
		return (value >= _low && value < _high);
	}
	else
	{
		return (digitalRead(_pin) == LOW);
	}
}

//Tests if a button is pressed and released
//  returns true if the button was pressed and released
//	if repeat callback supplied, the callback is called while the key is pressed
bool Button::Pressed()
{
  bool pressed = false;
  if (IsDown())
  {
    unsigned long wait = millis() + DEBOUNCE_DELAY;
    while (millis() < wait)
    {
      if (_backgroundCallback != NULL)
      {
        _backgroundCallback();
      }
    }
    if (IsDown())
    {
  	  //Set up for repeat loop
  	  if (_repeatCallback != NULL)
  	  {
  	    _repeatCallback();
  	  }
  	  unsigned long speed = REPEAT_START_SPEED;
  	  unsigned long time = millis() + speed;
      while (IsDown())
      {
        if (_backgroundCallback != NULL)
        {
          _backgroundCallback();
        }
    		if (_repeatCallback != NULL && millis() >= time)
    		{
    		  _repeatCallback();
    		  unsigned long faster = speed - REPEAT_INCREASE_SPEED;
    		  if (faster >= REPEAT_MAX_SPEED)
    		  {
    			  speed = faster;
    		  }
    		  time = millis() + speed;
    		}
      }
      pressed = true;
    }
  }
  return pressed;
}

//Return current button state
int Button::State()
{
	if (_range)
	{
		uint16_t value = analogRead(_pin);
		if (_activeLow)
		{
			return (value >= _low && value < _high) ? LOW : HIGH;
		}
		else 
		{
			return (value >= _low && value < _high) ? HIGH : LOW;
		}
	}
	else
	{
		return digitalRead(_pin);
	}
}

//Return current button name
uint8_t Button::Name()
{
	return _name;
}

Sound.h

C/C++
//-------------------------------------------------------
// Code design: 
//   John Bradnam 
//   jbrad2089@gmail.com

#pragma once

// Constants for notes
#define REST	 0
#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
#define NOTE_D1  37
#define NOTE_DS1 39
#define NOTE_E1  41
#define NOTE_F1  44
#define NOTE_FS1 46
#define NOTE_G1  49
#define NOTE_GS1 52
#define NOTE_A1  55
#define NOTE_AS1 58
#define NOTE_B1  62
#define NOTE_C2  65
#define NOTE_CS2 69
#define NOTE_D2  73
#define NOTE_DS2 78
#define NOTE_E2  82
#define NOTE_F2  87
#define NOTE_FS2 93
#define NOTE_G2  98
#define NOTE_GS2 104
#define NOTE_A2  110
#define NOTE_AS2 117
#define NOTE_B2  123
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951
#define NOTE_C8  4186
#define NOTE_CS8 4435
#define NOTE_D8  4699
#define NOTE_DS8 4978
#define END_OF_TUNE 0xFFFF

#define DUR_8 0xE000
#define DUR_6 0xC000
#define DUR_4 0x8000
#define DUR_3 0x6000
#define DUR_2 0x4000
#define DUR_1 0x2000

// HOME ON THE RANGE
const uint16_t melody_range[] = {
  DUR_8|NOTE_G3, DUR_8|NOTE_G3, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_4|NOTE_E4, DUR_8|NOTE_C4, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_8|NOTE_F4, DUR_8|NOTE_F4, DUR_4|NOTE_F4,
  DUR_8|NOTE_E4, DUR_8|NOTE_F4, DUR_4|NOTE_G4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_B3, DUR_8|NOTE_C4, DUR_1|NOTE_D4,
  DUR_8|NOTE_G3, DUR_8|NOTE_G3, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_4|NOTE_E4, DUR_8|NOTE_C4, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_8|NOTE_F4, DUR_8|NOTE_F4, DUR_4|NOTE_F4,
  DUR_8|NOTE_F4, DUR_8|NOTE_F4, DUR_6|NOTE_E4, DUR_8|NOTE_D4, DUR_8|NOTE_C4, DUR_8|NOTE_B3, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_2|NOTE_C4, DUR_2|REST,
  DUR_2|NOTE_G4, DUR_8|NOTE_F4, DUR_6|NOTE_E4, DUR_8|NOTE_D4, DUR_1|NOTE_E4, 
  DUR_8|NOTE_G3, DUR_8|NOTE_G3, DUR_4|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_B3, DUR_8|NOTE_C4, DUR_2|NOTE_D4,
  DUR_8|NOTE_G3, DUR_8|NOTE_G3, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_4|NOTE_E4, DUR_8|NOTE_C4, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_8|NOTE_F4, DUR_8|NOTE_F4, DUR_4|NOTE_F4,
  DUR_8|NOTE_F4, DUR_8|NOTE_F4, DUR_4|NOTE_E4, DUR_8|NOTE_D4, DUR_8|NOTE_C4, DUR_8|NOTE_B3, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_1|NOTE_C4,
  DUR_4|REST, END_OF_TUNE 
};

//forward references
void playNote(uint16_t noteRaw);

//Pin for speaker
uint8_t speaker = 0;
//Flag to break out of tune when SELECT button is pressed
bool musicPlaying = false;

//Initialise Sound Routines
void initialiseSound(uint8_t pin)
{
  pinMode(pin, OUTPUT);
  speaker = pin;
}

//Play a melody array from SRAM
void playSong(const uint16_t* melody)
{

  //Play each note in the melody until the END_OF_TUNE note is encountered
  musicPlaying = true;
  int thisNote = 0;
  uint16_t noteRaw = melody[thisNote++];
  while (musicPlaying && noteRaw != END_OF_TUNE)
	{
		playNote(noteRaw);
    noteRaw = melody[thisNote++];
	} //while

  musicPlaying = false;
	delay(50);
}

//Play a single note
void playNote(uint16_t noteRaw)
{
	// to calculate the note duration, take one second divided by the note type.
	// e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc.
	uint16_t frequency = noteRaw & 0x1FFF;
	uint16_t duration = (noteRaw & 0xE000) >> 13;
	if (duration == 7)
		duration = 8;
	uint16_t noteDuration = 1800 / duration;

	if (frequency != REST)
	{
		tone(speaker, frequency, noteDuration);
	}
		
	// to distinguish the notes, set a minimum time between them.
	// the note's duration + 30% seems to work well:
	uint16_t pauseBetweenNotes = (noteDuration * 13) / 10;
  delay(pauseBetweenNotes);

	if (frequency != REST)
	{
		// stop the tone playing:
		noTone(speaker);
	}
}

Memory.h

C/C++
//-------------------------------------------------------
// Code design: 
//   John Bradnam 
//   jbrad2089@gmail.com

#pragma once

//Uncomment to reset EEPROM data
//#define RESET_EEPROM

#if defined(__AVR_ATtiny1614__) || defined(__AVR_ATtiny3216__)
#include <avr/eeprom.h>
#else
#include <EEPROM.h>
#endif

enum DisplayEnum { ANALOG, DIGITAL, WORLD, ALARM, END_MODE };

//EEPROM handling
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0DAD0BAD
typedef struct {
  uint32_t magic;
  DisplayEnum displayMode;
  bool format24hr;
  bool formatDow;
  bool alarmEnabled;
  char selections[4][4];
  int alarmHour;
  int alarmMinute;
} EEPROM_DATA; 

EEPROM_DATA EepromData;       //Current EEPROM settings

//-----------------------------------------------------------------------------------
// Forward references

void writeEepromData();
void readEepromData();

//---------------------------------------------------------------
//Write the EepromData structure to EEPROM
void writeEepromData()
{
  //This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.
  #if defined(__AVR_ATtiny1614__) || defined(__AVR_ATtiny3216__)
    eeprom_update_block ((void *) &EepromData , (void *) EEPROM_ADDRESS, sizeof(EepromData));  
  #else
    EEPROM.put(EEPROM_ADDRESS,EepromData);
  #endif
}

//---------------------------------------------------------------
//Read the EepromData structure from EEPROM, initialise if necessary
void readEepromData()
{
  //Eprom
  #if defined(__AVR_ATtiny1614__) || defined(__AVR_ATtiny3216__)
    eeprom_read_block (( void *) &EepromData , ( const void *) EEPROM_ADDRESS, sizeof(EepromData));  
  #else
    EEPROM.get(EEPROM_ADDRESS,EepromData);
  #endif
  #ifndef RESET_EEPROM
  if (EepromData.magic != EEPROM_MAGIC)
  #endif
  {
    EepromData.magic = EEPROM_MAGIC;

    //Initial face that clock starts in
    EepromData.displayMode = ANALOG;

    //Default format
    EepromData.format24hr = false;        //Whether clock shows 12 or 24 hour format
    EepromData.formatDow = true;          //Whether date shows day of week instead of year
    
    //Define the names of the cities/timezones you want to default to
    //The first selection is considered the local timezone.
    strcpy(EepromData.selections[0],"SYD");
    strcpy(EepromData.selections[1],"AKL");
    strcpy(EepromData.selections[2],"NYC");
    strcpy(EepromData.selections[3],"LON");

    //Initial alaram time
    EepromData.alarmEnabled = false;      //Whether alarm is on or off
    EepromData.alarmHour = 6;
    EepromData.alarmMinute = 0;
    writeEepromData();
  }
}

Credits

John Bradnam

John Bradnam

141 projects • 167 followers

Comments