Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
John Bradnam
Published © GPL3+

Arduino Hourglass Timer

An animated hour glass built on top of a $3 Hourglass Shape Flashing LED DIY Kit.

AdvancedFull instructions provided8 hours1,088
Arduino Hourglass Timer

Things used in this project

Hardware components

Hourglass Shape Flashing LED DIY Kit
Search for "Hourglass Kit" on eBay
×1
ATmega328
Microchip ATmega328
DIL-28 variant programmed with Arduino Bootloader
×1
IC & Component Socket, 28 Contacts
IC & Component Socket, 28 Contacts
×1
IC & Component Socket, 16 Contacts
IC & Component Socket, 16 Contacts
×2
IC plug 16 Contacts
×2
Ribbon Cable, Flat
Ribbon Cable, Flat
50mm 16 Way Cable
×1
Resistor 10k ohm
Resistor 10k ohm
1/8W
×5
Capacitor 100 nF
Capacitor 100 nF
×5
Capacitor 22 pF
Capacitor 22 pF
Disc Ceramic
×2
Capacitor 10 µF
Capacitor 10 µF
16V
×1
8 MHz Crystal
8 MHz Crystal
×1
Rotary Encoder with Push-Button
Rotary Encoder with Push-Button
EC11 (https://www.ebay.com/itm/5-PCS-Rotary-encoder-with-switch-EC11-Audio-digital-potentiometer-20mm-handle-UK/192288155754?epid=21002920482&hash=item2cc544686a:g:LV8AAOSwOsBZn7QX)
×1
Glass 5mm Mercury Switch Angle Tilt Switch
×2
Universal Passive Buzzer AC / 2KHz 3V 5V 12V
×1
M3x5+6mm Male-Female spacers
×2
Rechargeable Battery, 3.7 V
Rechargeable Battery, 3.7 V
400mAh LIPO Battery
×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

Case - Bottom

Knob

Case - Top

Schematics

Schematic

PCB

Hourglass Kit Schematic

Hourglass Kit PCB Layout

Eagle Files

Schematic and PCB in Eagle Format

Code

Boards.txt

Plain text
Board definition for an Arduino UNO with a 8MHz crystal
##############################################################

uno8.name=Arduino Uno (8Mhz)

uno8.vid.0=0x2341
uno8.pid.0=0x0043
uno8.vid.1=0x2341
uno8.pid.1=0x0001
uno8.vid.2=0x2A03
uno8.pid.2=0x0043
uno8.vid.3=0x2341
uno8.pid.3=0x0243

uno8.upload.tool=avrdude
uno8.upload.protocol=arduino
uno8.upload.maximum_size=32256
uno8.upload.maximum_data_size=2048
uno8.upload.speed=57600

uno8.bootloader.tool=avrdude
uno8.bootloader.low_fuses=0xFF
uno8.bootloader.high_fuses=0xDA
uno8.bootloader.extended_fuses=0xFE
uno8.bootloader.unlock_bits=0x3F
uno8.bootloader.lock_bits=0x0F
uno8.bootloader.file=optiboot/optiboot_atmega328.hex

uno8.build.mcu=atmega328p
uno8.build.f_cpu=8000000L
uno8.build.board=AVR_UNO_8
uno8.build.core=arduino
uno8.build.variant=standard

##############################################################

Hour_Glass_V1.ino

C/C++
/*
Enhancing the Hourglass Shape Flashing LED DIY Kit

This timer extends the Hourglass Shape Flashing LED DIY Kit by adding an Arduino, two mercury switches to detect the hourglass orientation, 
a rotatary encoder to set the time from 1 to 99 minutes and a piezo electric speaker. The PCB that came in the kit is still used to hold the 
57 LEDs but the STC15W201S microcontroller is replaced with a 16 pin ribbon cable that connects to my custom Arduino board.

LED Layout
----------
   01, 02, 03, 04, 05, 06, 07
     08, 09, 10, 11, 12, 13
       14, 15, 16, 17, 18
         19, 20, 21, 22
           23, 24, 25
             26, 27
               28
               29
               30
             31, 32
           33, 34, 35
         36, 37, 38, 39
       40, 41, 42, 43, 44
     45, 46, 47, 48, 49, 50
   51, 52, 53, 54, 55, 56, 57
*/

#include <EEPROM.h>
#include "hourglass.h"
#include "notes.h"

#define ENC_A A1
#define ENC_B A2

#define SPEAKER A3

#define SW_UP A4
#define SW_DN A5

#define ENCODER_MIN 1
#define ENCODER_MAX 99
uint8_t encoderLastEncoded = 0;
int encoderLastValue = 1;
int encoderValue = 1;

//define modes to operate in
enum ModeEnum { OFF, SET_TIME, TIMER, TIME_LEFT, COMPLETE };

ModeEnum currentMode;
int minutesTimer;
int currentTimer;
int displayTimer;
long secondsCounter = 0;
#define COUNTS_PER_MINUTE 684000      //11400 * 60

// HOME ON THE RANGE
const uint16_t melody_range[] PROGMEM = {
  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 
};

//Bb, F, G, E
const uint16_t melody_shine[] PROGMEM = {
  DUR_3|NOTE_AS3, DUR_3|NOTE_F4, DUR_3|NOTE_G3, DUR_2|NOTE_E4,
  DUR_4|REST, END_OF_TUNE 
};

//*******************************************************************************************
// A0 .. A5 Pin Change Interrupt
//
ISR (PCINT1_vect)
{
  //Handle any changes to mercury switches
  bool up = (digitalRead(SW_UP) == HIGH);
  bool dn = (digitalRead(SW_DN) == HIGH);
  if (up != dn)
  {
    if (currentMode == SET_TIME && dn != hourGlassGetRotation())
    {
      currentMode = TIMER;
      currentTimer = minutesTimer;
      secondsCounter = 0;
    }
    hourGlassSetRotation(dn);
  }

  //Handle change in rotary encoder
  uint8_t enc = (digitalRead(ENC_B) == HIGH) ? 2 : 0 | (digitalRead(ENC_A) == HIGH) ? 1 : 0;
  uint8_t sum = (encoderLastEncoded << 2) | enc;

  int lastEncoderValue = encoderValue;
  if ((encoderValue < ENCODER_MAX) && (sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011))
  {
    encoderLastValue++;
    encoderValue = encoderLastValue >> 1;
  }
  if ((encoderValue > ENCODER_MIN) && (sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000))
  {
    encoderLastValue--;
    encoderValue = encoderLastValue >> 1;
  }
  encoderLastEncoded = enc;   //store this value for next time  

  if (encoderValue != lastEncoderValue)
  {
    currentMode = SET_TIME;
  }
  
}

//*******************************************************************************************
// Called by refresh timer interrupt when refreshing the display
// 
void timerInterruptHandler()
{
  if (currentMode == TIMER || currentMode == TIME_LEFT)
  {
    if (secondsCounter > 0)
    {
      secondsCounter--;
    }
    else if (currentTimer == 0)
    {
      currentMode = COMPLETE;
      Serial.println("COMPLETE");
    }
    else
    {
      secondsCounter = COUNTS_PER_MINUTE;
      currentTimer--;
      Serial.println(currentTimer);
    }
  }
}

//*******************************************************************************************
// Setup Arduino environment
//
void setup()
{
  Serial.begin(9600);

  pinMode(ENC_A, INPUT);
  pinMode(ENC_B, INPUT);
  pciSetup(ENC_A);
  pciSetup(ENC_B);
  
  pinMode(SW_UP, INPUT);
  pinMode(SW_DN, INPUT);
  pciSetup(SW_UP);
  pciSetup(SW_DN);

  pinMode(SPEAKER, OUTPUT);
    
  hourGlassSetup(timerInterruptHandler);
  hourGlassSetRotation(digitalRead(SW_DN) == HIGH);

  currentMode = SET_TIME;
  
  //Test if last minutesTimer is available otherwise default to 3 minutes
  minutesTimer = readFromEEPROM();
  Serial.println("EEPROM = " + String(minutesTimer));
  if (minutesTimer < 1 || minutesTimer > 99)
  {
    minutesTimer = 3;
    writeToEEPROM(minutesTimer);
  }
  hourGlassDisplayNumber(minutesTimer);
  encoderLastValue = minutesTimer << 1;
  encoderValue = minutesTimer;
  
  delay(200);
}

//*******************************************************************************************
// Install Pin change interrupt for a pin
void pciSetup(byte pin)
{
    *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
    PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
    PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
}

//*******************************************************************************************
// Main loop
//
void loop()
{
  switch (currentMode)
  {
    case SET_TIME:
      if (encoderValue != minutesTimer)
      {
        Serial.println("SET_TIME = " + String(encoderValue));
        minutesTimer = encoderValue;
        writeToEEPROM(minutesTimer);
      }
      hourGlassDisplayNumber(minutesTimer);
      break;

    case TIMER:
      hourGlassPlayAnimation(200);
      displayTimer = 10;
      if (currentMode == TIMER)
      {
        currentMode = TIME_LEFT;
      }
      break;

    case TIME_LEFT:
      if (displayTimer == 0)
      {
        currentMode = TIMER;
      }
      else
      {
        hourGlassDisplayNumber(currentTimer + 1);
        displayTimer--;
      }
      break;

    case COMPLETE:
      hourGlassPlayFrame(TOTAL_FRAMES - 1, 2);    //Display last frame
      delay(500);
      hourGlassClearAllLEDs();
      playSong(SPEAKER, melody_range);
      currentMode = SET_TIME;
      hourGlassDisplayNumber(minutesTimer);
      break;
  }
  delay(100);
}

//*******************************************************************************************
//Play animation
//delta - time in mS between each frame
void hourGlassPlayAnimation(int delta)
{
    for (int i = 0; i < TOTAL_FRAMES; i++)
    {
      for (int j = 0; j < 3; j++)
      {
        hourGlassPlayFrame(i, j);
        if (currentMode != TIMER)
        {
          break;
        }
        hourGlassDelay(delta);
      }
      if (currentMode != TIMER)
      {
        break;
      }
    }
}

//*********************************************************************************************
// EEPROM routines to store last value of timer (0 to 99)
//

void writeToEEPROM(int v)
{
  EEPROM.write(0, (byte)v);
}

int readFromEEPROM()
{
  return (int)EEPROM.read(0);
}

hourglass.h

C/C++
#pragma once

/*
Support functions for Hour Glass Display
Author: jbrad2089@gmail.com

LED Layout
----------

   01, 02, 03, 04, 05, 06, 07
     08, 09, 10, 11, 12, 13
       14, 15, 16, 17, 18
         19, 20, 21, 22
           23, 24, 25
             26, 27
               28
               29
               30
             31, 32
           33, 34, 35
         36, 37, 38, 39
       40, 41, 42, 43, 44
     45, 46, 47, 48, 49, 50
   51, 52, 53, 54, 55, 56, 57
*/

#include "hourglassfont.h"
#include "hourglassanim.h"
#include "notes.h"

#define TOTAL_LEDS 57

//Define direction AK = 0 => P3x = Anode, P1x = Cathode, AK = 1 => P3x = Cathode, P1x = Anode
#define AK 0
#define KA 0x08

//Definitions for connections to D8 - D13 pins
#define P10 (0<<4)
#define P11 (1<<4)
#define P12 (2<<4) 
#define P13 (3<<4)
#define P14 (4<<4) 
#define P15 (5<<4)

//Definitions for connections to D3 - D7 pins
#define P30 3
#define P31 4
#define P33 5
#define P36 6
#define P37 7

//Coordinates for each LED in Hourglass  
//Bit 7, 6, 5, 4 = P10 .. P15 (PB0 .. PB5)
//Bit 3          = AK or KA
//Bit 2, 1, 0    = P30, P31, P33, P36, P37 (PD3 .. PD7)
const uint8_t ledpos[TOTAL_LEDS] PROGMEM = { 
    AK|P10|P30, AK|P10|P31, AK|P10|P33, AK|P10|P36, AK|P10|P37, 
    KA|P10|P30, KA|P10|P31, KA|P10|P33, KA|P10|P36, KA|P10|P37, 
    AK|P11|P30, AK|P11|P31, AK|P11|P33, AK|P11|P36, AK|P11|P37, 
    KA|P11|P30, KA|P11|P31, KA|P11|P33, KA|P11|P36, KA|P11|P37, 
    AK|P12|P30, AK|P12|P31, AK|P12|P33, AK|P12|P36, AK|P12|P37, 
    KA|P12|P30, KA|P12|P31, KA|P12|P33, KA|P12|P36, KA|P12|P37, 
    AK|P13|P30, AK|P13|P31, AK|P13|P33, AK|P13|P36, AK|P13|P37, 
    KA|P13|P30, KA|P13|P31, KA|P13|P33, KA|P13|P36, KA|P13|P37, 
    AK|P14|P30, AK|P14|P31, AK|P14|P33, AK|P14|P36, AK|P14|P37, 
    KA|P14|P30, KA|P14|P31, KA|P14|P33, KA|P14|P36, KA|P14|P37, 
    AK|P15|P30, AK|P15|P31, AK|P15|P33, AK|P15|P36, AK|P15|P37, 
    KA|P15|P30, KA|P15|P31
};

//Row definiton table from top left to bottom right
const uint8_t ledrows[TOTAL_LEDS] PROGMEM = { 
    0,  1,  2,  3,  4,  5,  6 | 0x80,
      7,  8,  9, 10, 11, 12 | 0x80,
       13, 14, 15, 16, 17 | 0x80,
         18, 19, 20, 21 | 0x80,
           22, 23, 24 | 0x80,
             25, 26 | 0x80,
               27 | 0x80,
               28 | 0x80,
               29 | 0x80,
             30, 31 | 0x80,
           32, 33, 34 | 0x80,
         35, 36, 37, 38 | 0x80,
       39, 40, 41, 42, 43 | 0x80,
     44, 45, 46, 47, 48, 49 | 0x80,
   50, 51, 52, 53, 54, 55, 56 | 0x80
};

//Store the current state of the LEDs. Each bit represents each LED on/off state (0 = off, 1 = on)
//=> ledsLow bit 0 is LED 01, bit 1 is LED 02 ... bit 31 is LED 32,
//=> ledsHigh bit 0 is LED 33, bit 1 is LED 34 ... bit 24 is LED 57,
uint32_t ledsLow = 0;
uint32_t ledsHigh = 0;

//Used by ISR handler to determine which LED to turn on. Only one LED can be on at anyone time.
int nextIsrLED = 0;
int lastIsrLED = 0;

//Used to determine which way is up
bool upsideDownIsr = true;

#define COUNTS_PER_SECOND 11400
long hourGlassDelayCounter = 0;

//forward references
bool hourGlassGetLED(int led);
void hourGlassClearAllLEDs();

void (*hourGlassTimerCallback)(void);

//*******************************************************************************************
// Initialse the timer interrupt to handle refreshing of the LEDs
// void (*pTimerFunction)() - callback function to allow hourglass timer to hook into this timer

void hourGlassSetup(void (*pTimerFunction)())
{
  cli();//stop interrupts

  hourGlassTimerCallback = pTimerFunction;

  hourGlassClearAllLEDs();
  
  //set timer2 interrupt at 8kHz
  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0
#if (F_CPU == 8000000)
  // set compare match register for 8Mhz Crystal
  OCR2A = 88;// = (8*10^6) / (11400*8) - 1 (must be <256)
#elif (F_CPU == 16000000)
  // set compare match register for 16Mhz Crystal
  OCR2A = 175;// = (16*10^6) / (11400*8) - 1 (must be <256)
#else
#error "F_CPU not 16000000 or 8000000 or undefined"
#endif  
  // turn on CTC mode
  TCCR2A |= (1 << WGM21);
  // 8 prescaler
  TCCR2B |= (1 << CS21);
  // Set CS21 & CS20 bit for 32 prescaler
  //TCCR2B |= (1 << CS21) | (1 << CS20);   
  // 64 prescaler
  //TCCR2B |= (1 << CS22);
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);

  sei();//allow interrupts

}

//*******************************************************************************************
// Timer ISR to display current state of LEDS
// Each interrupt will only light a single LED when its on. Since there are 57 LEDS, and we
// want to paint all LEDs 200 times per second so the human eye will see it as flicker free, the
// frequency is 57 X 200 = 11400 Hz or an interrupt every 88uS.


ISR(TIMER2_COMPA_vect)
{
  //Don't refresh while note is playing
  if (!playingNote)
  {
    uint8_t oldSREG = SREG;
    cli();
    
    // Alter LED number if display upside down
    int next = (upsideDownIsr) ? (TOTAL_LEDS - 1) - nextIsrLED : nextIsrLED;
    int last = (upsideDownIsr) ? (TOTAL_LEDS - 1) - lastIsrLED : lastIsrLED;
    
    // Physically force last LED off if it was on
    // This stops Ghosting
    if (hourGlassGetLED(last))
    {
      uint8_t _led = pgm_read_byte(&ledpos[lastIsrLED]);
      int _bBit = (_led >> 4) & 0x0F;   //P1x pins Port B
      int _dBit = _led & 0x07;          //P3x pins Port D
  
      // Ensure ports with LED to display are configured as outputs and rest are inputs
      DDRB = (DDRB & B11000000) | (1 << _bBit);        //Assign D8..13 as outputs
      DDRD = (DDRD & B00000111) | (1 << _dBit);        //Assign D3..D7 as outputs
  
      // Clear all output pins except the pins we aren't using
      PORTD = PORTD & 0x07;  //Current state of PD0 to PD2
      PORTB = PORTB & 0xC0;  //Current state of PB6 to PB7
    }
  
    //Now switch on correct LED
    uint8_t led = pgm_read_byte(&ledpos[nextIsrLED]);
    int bBit = (led >> 4) & 0x0F;   //P1x pins Port B
    int dBit = led & 0x07;          //P3x pins Port D
  
    //Ensure ports with LED to display are configured as outputs and rest are inputs
    DDRB = (DDRB & B11000000) | (1 << bBit);        //Assign D8..13 as outputs
    DDRD = (DDRD & B00000111) | (1 << dBit);        //Assign D3..D7 as outputs
  
    // Clear all output pins except the pins we aren't using
    uint8_t dState = PORTD & 0x07;  //Current state of PD0 to PD2
    uint8_t bState = PORTB & 0xC0;  //Current state of PB6 to PB7
  
    //Set or clear pins for the next LED
    if (!hourGlassGetLED(next))
    {
      PORTB = bState;               //d8_d13 LOW
      PORTD = dState;               //d3_d7 LOW
    }
    else if (led & 0x08)
    {
      //P3x = Cathode, P1x = Anode 
      PORTB = bState | (1 << bBit); //d8_d13 HIGH
      PORTD = (dState | B11111000) & ~(1 << dBit);  //d3_d7 LOW
    }
    else 
    {
      //P3x = Anode, P1x = Cathode 
      PORTB = (bState | B00111111) & ~(1 << bBit);  //d8_d13 LOW
      PORTD = dState | (1 << dBit); //d3_d7 HIGH
    }
  
    //Set up for next interrupt
    lastIsrLED = nextIsrLED;
    nextIsrLED++;
    if (nextIsrLED >= TOTAL_LEDS)
    {
      nextIsrLED = 0;
    }
  
    //Handle any delay requests
    if (hourGlassDelayCounter > 0)
    {
      hourGlassDelayCounter--;
    }
      
    if (hourGlassTimerCallback != NULL)
    {
      hourGlassTimerCallback();
    }
  
    // Toggle D2 for scope
    //DDRD = DDRD | 0x00000100; //Configure D2 as an output alter its state so we can measure the frequency 
    //PORTD = PORTD ^ 0x00000100;
    //PIND = 0x00000100;
    
    SREG = oldSREG;
  }
}

//******************************************************************************************
// Replace standard Arduino delay routine with ours since we will be compiling a 8MHz chip
// on a 16Mhz Arduino board
void hourGlassDelay(long mS)
{
  hourGlassDelayCounter = (mS * COUNTS_PER_SECOND) / 1000;
  while (hourGlassDelayCounter > 0)
  {
    sei();
  }
}

//*******************************************************************************************
//Set the rotation of the display
// upsideDown - true if display is upside down
void hourGlassSetRotation(bool upsideDown)
{
  upsideDownIsr = upsideDown;
}

//*******************************************************************************************
//Set the rotation of the display
// return true if display is upside down
bool hourGlassGetRotation()
{
  return upsideDownIsr;
}

//*******************************************************************************************
//Clear all
void hourGlassClearAllLEDs()
{
  ledsLow = 0;
  ledsHigh = 0;
}

//*******************************************************************************************
//Get LED state
//led - Led to switch on or off (0..56)
//on - true to switch on, false to switch off
bool hourGlassGetLED(int led)
{
  if (led < 32) {
    return (ledsLow & ((uint32_t)1 << led)) > 0;
  }
  else {
    return (ledsHigh & ((uint32_t)1 << (led - 32))) > 0;
  }
}

//*******************************************************************************************
//Switch on or off a LED
//led - Led to switch on or off (0..56)
//on - true to switch on, false to switch off
void hourGlassSetLED(int led, bool on)
{
  if (led < 32) {
    if (on) {
      ledsLow |= ((uint32_t)1 << led);
    } else {
      ledsLow &= ~((uint32_t)1 << led);
    }
  }
  else {
    if (on) {
      ledsHigh |= ((uint32_t)1 << (led - 32));
    } else {
      ledsHigh &= ~((uint32_t)1 << (led - 32));
    }
  }
}

//*******************************************************************************************
//Switch on or off a row of LEDS
//row - Led to switch on or off (0..14) (0 = top row, 1 is wnd row from top, etc)
//on - true to switch on, false to switch off
void hourGlassSetRowLED(int row, bool on)
{
    int r = 0;
    for (int i = 0; i < TOTAL_LEDS; i++)
    {
      uint8_t led = pgm_read_byte(&ledrows[i]);
      if (r == row)
      {
        hourGlassSetLED(led & 0x7F, on);
      }
      if (led & 0x80)
      {
        if (r == row)
        {
          break;
        }
        r++;
      }
    }
}

//*******************************************************************************************
//Display upper digit
//digit - 0 .. 9
void hourGlassSetUpperDigit(int digit)
{
  uint32_t bits = pgm_read_dword(&led_upper[digit % 10]);
  for (int i = 0; i < 28; i++)
  {
    hourGlassSetLED(i, (bits & (uint32_t)1 != 0));
    bits = bits >> 1;
  }
}

//*******************************************************************************************
//Display lower digit
//digit - 0 .. 9
void hourGlassSetLowerDigit(int digit)
{
  uint32_t bits = pgm_read_dword(&led_lower[digit % 10]);
  for (int i = 0; i < 28; i++)
  {
    hourGlassSetLED(i + 29, (bits & (uint32_t)1 != 0));
    bits = bits >> 1;
  }
}

//*******************************************************************************************
//Display number
//digit - 0 .. 99
void hourGlassDisplayNumber(int number)
{
  hourGlassClearAllLEDs();
  if (number > 9)
  {
    hourGlassSetUpperDigit(floor(number / 10));
  }
  hourGlassSetLowerDigit(number % 10);
}

//*******************************************************************************************
//Play a frame from the animation
//frame - 0 .. TOTAL_FRAMES - 1 => primary frame
//subframe - 0, 1, 2 =>  central light animation
void hourGlassPlayFrame(int frame, int subframe)
{
  frame = frame % TOTAL_FRAMES;
  uint64 bits;
  uint32_t* p = (uint32_t*)&led_anim[frame];
  bits.h = pgm_read_dword(p++);
  bits.l = pgm_read_dword(p++) & 0xC7FFFFFF;   //Mask LEDs 28 to 30
  //bits.l = pgm_read_dword(p++); // & 0xC7FF;   //Mask LEDs 28 to 30
  //LEDS 28 to 30 make up the sub animation
  bits.l |= (uint32_t)1 << (27 + subframe % 3);
  
  for (int i = 0; i < TOTAL_LEDS; i++)
  {
    if (i < 32)
    {
  		hourGlassSetLED(i, (bits.l & (uint32_t)1 != 0));
  		bits.l = bits.l >> 1;
    }
  	else
  	{
  		hourGlassSetLED(i, (bits.h & (uint32_t)1 != 0));
  		bits.h = bits.h >> 1;
  	}
  }
}

hourglassanim.h

C/C++
#pragma once
/*
Animation sequence for hour glass
Author: jbrad2089@gmail.com

LED Layout
----------

   01, 02, 03, 04, 05, 06, 07
     08, 09, 10, 11, 12, 13
       14, 15, 16, 17, 18
         19, 20, 21, 22
           23, 24, 25
             26, 27
               28
               29
               30
             31, 32
           33, 34, 35
         36, 37, 38, 39
       40, 41, 42, 43, 44
     45, 46, 47, 48, 49, 50
   51, 52, 53, 54, 55, 56, 57
*/

struct uint64
{
  uint32_t h;
  uint32_t l;
}; 

#define TOTAL_FRAMES 18

//Table is 64 bit - Bit 0 = LED 1, Bit 1 = LED 2 ... Bit 56 = LED 57
const uint64 led_anim[TOTAL_FRAMES] PROGMEM = {

    //1   1   1   1   1   1   1
    //  1   1   1   1   1   1
    //    1   1   1   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   0   0
    //      0   0   0   0
    //    0   0   0   0   0
    //  0   0   0   0   0   0
    //0   0   0   0   0   0   0
	//B1111111 111111 11111 1111 111 11 0 0 0 00 000 0000 00000 000000 0000000
	//B1111 1111 1111 1111 1111 1111 1110 0000 _ 0000 0000 0000 0000 0000 0000 0_000 0000
	{ 0x00000000, 0x07FFFFFF },

    //1   1   1   0   1   1   1
    //  1   1   1   1   1   1
    //    1   1   1   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   0   0   0   0
    //  0   0   0   0   0   0
    //0   0   0   0   0   0   0
	//B1110111 111111 11111 1111 111 11 0 0 0 00 010 0000 00000 000000 0000000
  //B1110 1111 1111 1111 1111 1111 1110 0000 - 0100 0000 0000 0000 0000 0000 0_000 0000
	{ 0x00000002, 0x07FFFFF7 },

    //1   1   1   0   1   1   1
    //  1   1   1   1   1   1
    //    1   1   0   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   0   1   0   0
    //  0   0   0   0   0   0
    //0   0   0   0   0   0   0
	//B1110111 111111 11011 1111 111 11 0 0 0 00 010 0000 00100 000000 0000000
  //B1110 1111 1111 1110 1111 1111 1110 0000 _ 0100 0000 0100 0000 0000 0000 0_000 0000
	{ 0x00000202, 0x07FF7FF7 },

    //1   1   0   0   0   1   1
    //  1   1   1   1   1   1
    //    1   1   1   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   0   1   0   0
    //  0   0   0   0   0   0
    //0   0   0   1   0   0   0
	//B1100011 111111 11111 1111 111 11 0 0 0 00 010 0000 00100 000000 0001000
  //B1100 0111 1111 1111 1111 1111 1110 0000 _ 0100 0000 0100 0000 0000 0100 0_000 0000
	{ 0x00200202, 0x07FFFFE3 },

    //1   1   0   0   0   1   1
    //  1   1   1   1   1   1
    //    1   1   0   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   0   1   0   0
    //  0   0   0   0   0   0
    //0   0   1   1   1   0   0
	//B1100011 111111 11011 1111 111 11 0 0 0 00 010 0000 00100 000000 0011100
  //B1100 0111 1111 1110 1111 1111 1110 0000 _ 0100 0000 0100 0000 0000 1110 0_000 0000
	{ 0x00700202, 0x07FF7FE3 },

    //1   1   0   0   0   1   1
    //  1   1   0   0   1   1
    //    1   1   1   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   0   1   0   0
    //  0   0   1   1   0   0
    //0   0   1   1   1   0   0
	//B1100011 110011 11111 1111 111 11 0 0 0 00 010 0000 00100 001100 0011100
  //B1100 0111 1001 1111 1111 1111 1110 0000 _ 0100 0000 0100 0011 0000 1110 0_000 0000
	{ 0x0070C202, 0x07FFF9E3 },

    //1   1   0   0   0   1   1
    //  1   1   0   0   1   1
    //    1   1   0   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   0   1   0   0
    //  0   0   1   1   0   0
    //0   1   1   1   1   1   0
	//B1100011 110011 11011 1111 111 11 0 0 0 00 010 0000 00100 001100 0111110
  //B1100 0111 1001 1110 1111 1111 1110 0000 _ 0100 0000 0100 0011 0001 1111 0_000 0000
	{ 0x00F8C202, 0x07FF79E3 },

    //1   0   0   0   0   0   1
    //  1   1   0   0   1   1
    //    1   1   0   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   0   1   0   0
    //  0   1   1   1   1   0
    //0   1   1   1   1   1   0
	//B1000001 110011 11011 1111 111 11 0 0 0 00 010 0000 00100 011110 0111110
  //B1000 0011 1001 1110 1111 1111 1110 0000 _ 0100 0000 0100 0111 1001 1111 0_000 0000
	{ 0x00F9E202, 0x07FF79C1 },

    //0   0   0   0   0   0   0
    //  1   1   0   0   1   1
    //    1   1   0   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   1   1   1   0
    //  0   1   1   1   1   0
    //0   1   1   1   1   1   0
	//B0000000 110011 11011 1111 111 11 0 0 0 00 010 0000 01110 011110 0111110
  //B0000 0001 1001 1110 1111 1111 1110 0000 _ 0100 0000 1110 0111 1001 1111 0_000 0000
	{ 0x00F9E702, 0x07FF7980 },

    //0   0   0   0   0   0   0
    //  1   0   0   0   0   1
    //    1   1   0   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   0   1   0   0
    //  0   1   1   1   1   0
    //1   1   1   1   1   1   1
	//B0000000 100001 11011 1111 111 11 0 0 0 00 010 0000 00100 011110 1111111
  //B0000 0001 0000 1110 1111 1111 1110 0000   0100 0000 0100 0111 1011 1111 1_000 0000
	{ 0x01FDE202, 0x07FF7080 },

    //0   0   0   0   0   0   0
    //  0   0   0   0   0   0
    //    1   1   0   1   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   1   1   1   0
    //  0   1   1   1   1   0
    //1   1   1   1   1   1   1
	//B0000000 000000 11011 1111 111 11 0 0 0 00 010 0000 01110 011110 1111111
  //B0000 0000 0000 0110 1111 1111 1110 0000 _ 0100 0000 1110 0111 1011 1111 1_000 0000
	{ 0x01FDE702, 0x07FF6000 },

    //0   0   0   0   0   0   0
    //  0   0   0   0   0   0
    //    1   0   0   0   1
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   1   1   0
    //    0   1   1   1   0
    //  0   1   1   1   1   0
    //1   1   1   1   1   1   1
	//B0000000 000000 10001 1111 111 11 0 0 0 00 010 0110 01110 011110 1111111
  //B0000 0000 0000 0100 0111 1111 1110 0000 _ 0100 1100 1110 0111 1011 1111 1_000 0000
	{ 0x01FDE732, 0x07FE2000 },

    //0   0   0   0   0   0   0
    //  0   0   0   0   0   0
    //    0   0   0   0   0
    //      1   1   1   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   1   1   1   0
    //  1   1   1   1   1   1
    //1   1   1   1   1   1   1
	//B0000000 000000 00000 1111 111 11 0 0 0 00 010 0000 01110 111111 1111111
  //B0000 0000 0000 0000 0011 1111 1110 0000 _ 0100 0000 1110 1111 1111 1111 1_000 0000
	{ 0x01FFF702, 0x07FC0000 },

    //0   0   0   0   0   0   0
    //  0   0   0   0   0   0
    //    0   0   0   0   0
    //      1   0   0   1
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   1   1   0
    //    0   1   1   1   0
    //  1   1   1   1   1   1
    //1   1   1   1   1   1   1
	//B0000000 000000 00000 1001 111 11 0 0 0 00 010 0110 01110 111111 1111111
  //B0000 0000 0000 0000 0010 0111 1110 0000 _ 0100 1100 1110 1111 1111 1111 1_000 0000
	{ 0x01FFF732, 0x07E40000 },

    //0   0   0   0   0   0   0
    //  0   0   0   0   0   0
    //    0   0   0   0   0
    //      0   0   0   0
    //        1   1   1
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        0   1   0
    //      0   1   1   0
    //    1   1   1   1   1
    //  1   1   1   1   1   1
    //1   1   1   1   1   1   1
	//B0000000 000000 00000 0000 111 11 0 0 0 00 010 0110 11111 111111 1111111
  //B0000 0000 0000 0000 0000 0011 1110 0000 _ 0100 1101 1111 1111 1111 1111 1_000 0000
	{ 0x01FFFFB2, 0x07C00000 },

    //0   0   0   0   0   0   0
    //  0   0   0   0   0   0
    //    0   0   0   0   0
    //      0   0   0   0
    //        1   0   1
    //          1   1
    //            1
    //            1
    //            1
    //          0   0
    //        0   1   0
    //      1   1   1   1
    //    1   1   1   1   1
    //  1   1   1   1   1   1
    //1   1   1   1   1   1   1
	//B0000000 000000 00000 0000 101 11 1 1 1 00 010 1111 11111 111111 1111111
	//B0000 0000 0000 0000 0000 0010 1111 1100 _ 0101 1111 1111 1111 1111 1111 1_000 0000
	{ 0x01FFFFFA, 0x3F400000 },

    //0   0   0   0   0   0   0
    //  0   0   0   0   0   0
    //    0   0   0   0   0
    //      0   0   0   0
    //        0   0   0
    //          1   1
    //            0
    //            0
    //            0
    //          0   0
    //        1   1   1
    //      1   1   1   1
    //    1   1   1   1   1
    //  1   1   1   1   1   1
    //1   1   1   1   1   1   1
	//B0000000 000000 00000 0000 000 11 0 0 0 00 111 1111 11111 111111 1111111
	//B0000 0000 0000 0000 0000 0000 0110 0000 _ 1111 1111 1111 1111 1111 1111 1_000 0000
	{ 0x01FFFFFF, 0x06000000 },

    //0   0   0   0   0   0   0
    //  0   0   0   0   0   0
    //    0   0   0   0   0
    //      0   0   0   0
    //        0   0   0
    //          0   0
    //            0
    //            0
    //            0
    //          1   1
    //        1   1   1
    //      1   1   1   1
    //    1   1   1   1   1
    //  1   1   1   1   1   1
    //1   1   1   1   1   1   1
	//B0000000 000000 00000 0000 000 00 0 0 0 11 111 1111 11111 111111 1111111
  //B0000 0000 0000 0000 0000 0000 0000 0011 _ 1111 1111 1111 1111 1111 1111 1_000 0000
	{ 0x01FFFFFF, 0xC0000000 }

};

hourglassfont.h

C/C++
#pragma once
/*
Digits 0 to 9 for upper and lower sections
Author: jbrad2089@gmail.com

LED Layout
----------

   01, 02, 03, 04, 05, 06, 07
     08, 09, 10, 11, 12, 13
       14, 15, 16, 17, 18
         19, 20, 21, 22
           23, 24, 25
             26, 27
               28
               29
               30
             31, 32
           33, 34, 35
         36, 37, 38, 39
       40, 41, 42, 43, 44
     45, 46, 47, 48, 49, 50
   51, 52, 53, 54, 55, 56, 57
*/

//Digit definitions for upper triangle of LEDs
//Bit 0 = LED 1, Bit 1  = LED 2 ... Bit 26 = LED 27
const uint32_t led_upper[10] PROGMEM = { 
    //0   0   1   1   1   0   0
    //  0   1   0   0   1   0
    //    1   0   0   0   1
    //      1   0   0   1
    //        1   1   1
    //          0   0
    //            0
    //B0001 1110 0110 0010 1001 0001 1100,
    0x1E6291C,
    
    //0   0   0   1   0   0   0
    //  0   0   0   0   0   0
    //    0   0   1   0   0
    //      0   0   0   0
    //        0   1   0
    //          0   0
    //            0
    //B0000 1000 0000 1000 0000 0000 1000,
    0x0808008,
    
    //0   0   1   1   1   0   0
    //  0   0   0   0   1   0
    //    0   1   1   1   0
    //      1   0   0   0
    //        1   1   1
    //          0   0
    //            0
    //B0001 1100 0101 1100 1000 0001 1100,
    0x1C5C81C,

    //0   0   1   1   1   0   0
    //  0   0   0   0   1   0
    //    0   1   1   1   0
    //      0   0   0   1
    //        1   1   1
    //          0   0
    //            0
    //B0001 1110 0001 1100 1000 0001 1100,
    0x1E1C81C,
    
    //0   0   1   0   1   0   0
    //  0   1   0   0   0   0
    //    0   1   1   1   0
    //      0   0   0   0
    //        0   0   1
    //          0   0
    //            0
    //B0001 0000 0001 1100 0001 0001 0100,
    0x101C114,
    
    //0   0   1   1   1   0   0
    //  0   1   0   0   0   0
    //    0   1   1   1   0
    //      0   0   0   1
    //        1   1   1
    //          0   0
    //            0
    //B0001 1110 0001 1100 0001 0001 1100,
    0x1E1C11C,
    
    //0   0   1   1   1   0   0
    //  0   1   0   0   0   0
    //    0   1   1   1   0
    //      1   0   0   1
    //        1   1   1
    //          0   0
    //            0
    //B0001 1110 0101 1100 0001 0001 1100,
    0x1E5C11C,
    
    //0   0   1   1   1   0   0
    //  0   0   0   0   1   0
    //    0   0   0   1   0
    //      0   0   1   0
    //        0   1   0
    //          0   0
    //            0
    //B0000 1001 0001 0000 1000 0001 1100,
    0x091081C,
    
    //0   0   1   1   1   0   0
    //  0   1   0   0   1   0
    //    0   1   1   1   0
    //      1   0   0   1
    //        1   1   1
    //          0   0
    //            0
    //B0001 1110 0101 1100 1001 0001 1100,
    0x1E5C91C,

    //0   0   1   1   1   0   0
    //  0   1   0   0   1   0
    //    0   1   1   1   0
    //      0   0   0   1
    //        1   1   1
    //          0   0
    //            0
    //B0001 1110 0001 1100 1001 0001 1100
    0x1E1C91C
};

//Digit definitions for lower triangle of LEDs
//Bit 0 = LED 29, Bit 30  = LED 2 ... Bit 26 = LED 56
const uint32_t led_lower[10] PROGMEM = { 
    //            0
    //          0   0
    //        1   1   1
    //      1   0   0   1
    //    1   0   0   0   1
    //  0   1   0   0   1   0
    //0   0   1   1   1   0   0
    //B0011 1000 1001 0100 0110 0111 1000
    0x3894678,
    
    //            0
    //          0   0
    //        0   1   0
    //      0   0   0   0
    //    0   0   1   0   0
    //  0   0   0   0   0   0
    //0   0   0   1   0   0   0
    //B0001 0000 0000 0001 0000 0001 0000
    0x1001010,
    
    //            0
    //          0   0
    //        1   1   1
    //      0   0   0   1
    //    0   1   1   1   0
    //  0   1   0   0   0   0
    //0   0   1   1   1   0   0
    //B0011 1000 0001 0011 1010 0011 1000
    0x3813A38,

    //            0
    //          0   0
    //        1   1   1
    //      0   0   0   1
    //    0   1   1   1   0
    //  0   0   0   0   1   0
    //0   0   1   1   1   0   0
    //B0011 1000 1000 0011 1010 0011 1000,
    0x3883A38,
    
    //            0
    //          0   0
    //        1   0   1
    //      1   0   0   0
    //    0   1   1   1   0
    //  0   0   0   0   0   0
    //0   0   0   0   1   0   0
    //B0010 0000 0000 0011 1000 0110 1000,
    0x2003868,
    
    //            0
    //          0   0
    //        1   1   1
    //      1   0   0   0
    //    0   1   1   1   0
    //  0   0   0   0   1   0
    //0   0   1   1   1   0   0
    //B0011 1000 1000 0011 1000 0111 1000,
    0x3883878,
    
    //            0
    //          0   0
    //        1   1   1
    //      1   0   0   0
    //    0   1   1   1   0
    //  0   1   0   0   1   0
    //0   0   1   1   1   0   0
    //B0011 1000 1001 0011 1000 0111 1000,
    0x3893878,
    
    //            0
    //          0   0
    //        1   1   1
    //      0   0   0   1
    //    0   0   0   1   0
    //  0   0   0   1   0   0
    //0   0   0   1   0   0   0
    //B0001 0000 0100 0010 0010 0011 1000
    0x1042238,
    
    //            0
    //          0   0
    //        1   1   1
    //      1   0   0   1
    //    0   1   1   1   0
    //  0   1   0   0   1   0
    //0   0   1   1   1   0   0
    //B0011 1000 1001 0011 1010 0111 1000,
    0x3893A78,

    //            0
    //          0   0
    //        1   1   1
    //      1   0   0   1
    //    0   1   1   1   0
    //  0   0   0   0   1   0
    //0   0   1   1   1   0   0
    //B0011 1000 1000 0011 1010 0111 1000
    0x3883A78
};

notes.h

C/C++
#pragma once

#include <NewTone.h>

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

bool playingNote = false;

uint16_t playNote(int pin, 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;
  uint8_t duration = (noteRaw & 0xE000) >> 13;
  if (duration == 7)
  {
    duration = 8;
  }
  uint16_t noteDuration = 1800 / duration;

  int led = 0;
  if (frequency != REST)
  {
    NewTone(pin, 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);
  /*
  while (pauseBetweenNotes > 0) 
  {
    _delay_ms(1);
    pauseBetweenNotes--;
  }
  */
  
  if (frequency != REST)
  {
    // stop the tone playing:
    noNewTone(pin);
  }
  return frequency;
}

int playSong(int pin, uint16_t* melody)
{
  //Play each note in the melody until the END_OF_TUNE note is encountered
  playingNote = true;   //Used to stop display refresh while song is playing
  int thisNote = 0;
  uint16_t noteRaw = pgm_read_word(&melody[thisNote++]);
  while (noteRaw != END_OF_TUNE)
  {
    playNote(pin, noteRaw);
    noteRaw = pgm_read_word(&melody[thisNote++]);
  } //while
  playingNote = false;
}

Credits

John Bradnam
151 projects • 194 followers
Contact

Comments

Please log in or sign up to comment.