Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
John Bradnam
Published © GPL3+

Nokia Analog Digital Alarm Clock

A battery powered alarm clock using the Nokia 5110 LCD display controlled by a ATtiny1614 processor and DS1302 RTC.

IntermediateFull instructions provided16 hours1,384
Nokia Analog Digital Alarm Clock

Things used in this project

Hardware components

Microchip ATtiny1614
×1
Nokia 5110 LCD Display
×1
DS1302 RTC board
Get one from eBay (around $1) and remove DS1302, battery holder and crystal from board.
×1
Resistor 330 ohm
Resistor 330 ohm
1/8 watt 1%
×2
Resistor 100 ohm
Resistor 100 ohm
1/8 watt 1%
×1
Resistor 220 ohm
Resistor 220 ohm
1/8 watt
×1
Capacitor 100 nF
Capacitor 100 nF
×1
Capacitor 10 µF
Capacitor 10 µF
1206 SMD ceramic
×1
Buzzer
Buzzer
×1
PTS 645 Series Switch
C&K Switches PTS 645 Series Switch
17mm shaft with button tops
×3
18650 battery holder + battery
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Case - Front

Case - Back

Schematics

Adapter PCB

Eagle Files

Clock Schematic

Clock PCB

Adapter Schematic

Code

Nokia_Analog_Digital_Alarm_Clock_V1.ino

C/C++
/*============================================================================================================
  Analog and Digital Alarm Clock

  CPU:  ATtiny1614
  Display: Nokia 5110
 
  Digital Code: jbrad2089@gmail.com
  Analog Code:  The scientist formerly known as Naegeli ... please subscribe to my YouTube channel
  
  BOARD: ATtiny1614/1604/814/804/414/404/214/204
  Chip: ATtiny1614
  Clock Speed: 8MHz
  Programmer: jtag2updi (megaTinyCore)

  Note: Originally designed for ATtiny84 but LCD5110 graph library requires a minimum of 576 bytes to buffer 
  display. Processor was replaced with a ATtiny1614. Currently the Analog Clock and Alarm Clock
  components will exceed the 16K Flash limit if they are both included.

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

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

//Base memory 12080 bytes - All is 17713 bytes
#define INCLUDE_ANALOG  //15937 - 12080 = 3857 bytes
//#define INCLUDE_ALARM   //13766 - 12080 = 1686 bytes
#define INCLUDE_SOUND

#define SCLK 10   //PA3 (pin 13) D10
#define DIN 8     //PA1 (pin 11) D8
#define DC 5      //PB2 (pin 7)  D5
#define CS_LCD 3  //PA7 (pin 5)  D3
#define RST 1     //PA5 (pin 2)  D1

#define RTC_CE 9  //PA2 (pin 12) D9
#define RTC_IO 6  //PB1 (pin 8)  D6
#define RTC_CLK 4 //PB3 (pin 6)  D4

#define SPEAKER 2 //PA6 (pin 4)  D2
#define SWITCHES 7 //PB0 (pin 7) D7 
#define LIGHT 0   //PA4 (pin 2)  D0

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

DS1302RTC rtc(RTC_CE, RTC_IO, RTC_CLK);

#define 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
enum DisplayEnum { ANALOG, DIGITAL, ALARM, END_MODE };
int 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
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 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
uint16_t debounceTimeout = 0;   //Used to debounce SELECT switch
bool format24hr = false;        //Whether clock shows 12 or 24 hour format
bool formatDow = true;          //Whether date shows day of week instead of year
bool alarmEnabled = false;      //Whether alarm is on or off
bool alarmCancelled = false;    //Whether user has cancelled the alarm
bool alarmRinging = false;      //Whether the alarm is currently ringing
long 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);

  #ifdef INCLUDE_ALARM
    #ifdef INCLUDE_SOUND
      initialiseSound(SPEAKER);
    #endif
  #endif

  //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(LCD_CONTRAST);
  
  setSyncProvider(rtc.get);          // the function to get the time from the RTC
  if(timeStatus() != timeSet)
  {
    //myGLCD.print("RTC Sync Bad", CENTER, 25);
    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
    rtc.set(t);
    /*
    if (rtc.set(t) != 0) 
    {
      //myGLCD.print("Set Time Failed", CENTER, 34);
      //myGLCD.update();
      //delay(5000);
      //myGLCD.clrScr();
    }
    */
  }
  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 = 6;
  newAlarm.Minute = 0;
  newAlarm.Second = 0;

  attachInterrupt(digitalPinToInterrupt(SWITCHES),SwitchInterrupt,CHANGE);
}

//Handle pin change interrupt when SELECT button is pressed
void SwitchInterrupt()
{
  if (digitalRead(SWITCHES) == LOW)
  {
    debounceTimeout = millis() + 10;
  }
  else if (debounceTimeout > 0 && millis() > debounceTimeout)
  {
    #ifdef INCLUDE_ALARM
    if (musicPlaying)
    {
      musicPlaying = false;
      alarmCancelled = alarmRinging;
    }
    else
    {
      selectButtonInterrupt = true;
    }
    #else
      selectButtonInterrupt = true;
    #endif
  
    debounceTimeout = 0;
  }
}

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

    #ifdef INCLUDE_ALARM
      if (alarmEnabled && newAlarm.Hour == hour() && newAlarm.Minute == minute())
      {
        if (!alarmCancelled)
        {
          alarmRinging = true;
          #ifdef INCLUDE_SOUND
            playSong(melody_range); 
          #endif
        }
      }
      else 
      {
        alarmCancelled = false;
        alarmRinging = false;
      }
    #endif
    
  }
  delay(50);
}

//Test if any buttons have been pressed
void testButtons()
{
  //Single press buttons
  if (selectButtonInterrupt)
  {
    selectButtonInterrupt = false;
    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 (alarmMode != ALM)
  {
    //In alarm mode
    if (alarmMode == ALM_O)
    {
      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)
    {
      #ifdef ANALOG
        displayMode = ANALOG;
      #else
        displayMode = DIGITAL;
      #endif
    }
  }
  //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();
  }
  #ifdef INCLUDE_ALARM
  
  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:
        alarmEnabled = !alarmEnabled;
        break;
    }
    updateAlarm();
  }
  #endif
}  

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

  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:
        alarmEnabled = !alarmEnabled;
        break;
    }
    updateAlarm();
  }
  
#endif  
}

//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 alarm shown when changing the time
void updateAlarm()
{    
  if (millis() > flash)
  {
    flash = millis() + FLASH_TIME;
    on = !on;
    drawAlarmClock(makeTime(newAlarm), 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

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

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++
/*
  ARDUINO PLAYER PIANO - 12 keys + preset songs
 */
#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(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);
	}
}

Credits

John Bradnam
147 projects • 181 followers

Comments