John Bradnam
Published © GPL3+

General Purpose Countdown Timer

A general purpose countdown timer. Easily settable for up to one hour. Great for the kitchen. Perfect 5 minute eggs!

IntermediateFull instructions provided12 hours174
General Purpose Countdown Timer

Things used in this project

Hardware components

Microchip Technology ATtiny1614
×1
4-Digit 7-Segment Display
0.36in, Common Cathode, Clock Type
×1
LM1117-5
SOT-223 5V regulator
×1
1N4148 – General Purpose Fast Switching
1N4148 – General Purpose Fast Switching
SOD-323 package
×10
PTS 645 Series Switch
C&K Switches PTS 645 Series Switch
6mm Shaft
×1
USB Mini Socket
SMD variant
×1
DC power jack
2.1mm SMD variant
×1
Buzzer
Buzzer
×1
Passive Components
2 x 39K 0805 package 1 x 33K 0805 package 1 x 22K 0805 package 2 x 0.1uF 0805 package 1 x 10uF ceramic 1206 package 1 x 47uF/10V tantalum 3528 package
×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

STL files for 3D printing

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Code

Countdown_Timer_V1.ino

C/C++
// Countdown Timer V1
// Design and Software: jbrad2089@gmail.com
// Concept based on "Adjustable Countdown Timer" by dmytrosavchuk
//
// 2021-04-22 - V1
//    - Created code base

#ifdef __AVR__
  #include <avr/power.h>
#endif

/*-----------------------------------------------------------------------------
  ATTiny1614 Pins mapped to Ardunio Pins

             +--------+
         VCC + 1   14 + GND
 (SS)  0 PA4 + 2   13 + PA3 10 (SCK)
       1 PA5 + 3   12 + PA2 9  (MISO)
 (DAC) 2 PA6 + 4   11 + PA1 8  (MOSI)
       3 PA7 + 5   10 + PA0 11 (UPDI)
 (RXD) 4 PB3 + 6    9 + PB0 7  (SCL)
 (TXD) 5 PB2 + 7    8 + PB1 6  (SDA)
             +--------+

  PA0 to PA7 can be analog or digital
  PWM on D0, D1, D6, D7, D10

  BOARD: ATtiny1614/1604/814/804/414/404/214/204
  Chip: ATtiny1614
  Clock Speed: 20MHz
  Programmer: jtag2updi (megaTinyCore)
-----------------------------------------------------------------------------*/
#include "Button.h"
#include "Piano.h"

//Pins
#define SPEAKER 0  //(PA4)
#define SWITCHES 1 //(PA5)
#define SEG_C 2    //(PA6)
#define SEG_G_4 3  //(PA7)
#define SEG_E 4    //(PB3)
#define SEG_D 5    //(PB2)
#define SEG_F 6    //(PB1)
#define SEG_A_2 7  //(PB0)
#define SEG_B 8    //(PA1)
#define SEG_G_3 9  //(PA2)
#define SEG_A_1 10 //(PA3)

#define PA0_bm PIN0_bm
#define PA1_bm PIN1_bm
#define PA2_bm PIN2_bm
#define PA3_bm PIN3_bm
#define PA4_bm PIN4_bm
#define PA5_bm PIN5_bm
#define PA6_bm PIN6_bm
#define PA7_bm PIN7_bm
#define PB0_bm PIN0_bm
#define PB1_bm PIN1_bm
#define PB2_bm PIN2_bm
#define PB3_bm PIN3_bm

#define TCB_COMPARE 1000          //Refresh value for 7 segment display
#define BRIGHTNESS 1              //Initial brightness level (0 to 15)
#define DIGITS 4                  //Number of digits in display

uint8_t digits[DIGITS];           //Holds the current digits to display
uint8_t nextDigit = 0;            //Holds next digit to display
uint8_t brightness = BRIGHTNESS;  //Current Brightness level (0 to 15)
uint8_t bamCounter = 0;           //Bit Angle Modulation variable to keep track of things
uint8_t bamBit;                   //Used to store bit to test against brightness value

//Digit array
uint8_t segments[11] = {
//    abcdefg   abcdefg   abcdefg   abcdefg   abcdefg   abcdefg   abcdefg   abcdefg
    B01111110,B00110000,B01101101,B01111001,B00110011,B01011011,B01011111,B01110000,
    B01111111,B01111011,B00000000
};
#define SPACE 10

enum buttonEnum { BTN_NONE, BTN_STOP, BTN_START, BTN_DOWN, BTN_UP };

void stopButtonPressed(void);
void startButtonPressed(void);
void downButtonPressed(void);
void upButtonPressed(void);

Button* stopButton;
Button* startButton;
Button* downButton;
Button* upButton;

volatile uint8_t timerMinutes;
volatile uint8_t timerSeconds;
volatile bool timerRunning = false;
volatile bool timerUpdated = false;
volatile bool alarmOn = false;

int count = 0;

//-------------------------------------------------------------------------
//Initialise Hardware

void setup() 
{
  pinMode(SWITCHES, INPUT);
  pinMode(SPEAKER, OUTPUT);
  digitalWrite(SPEAKER, LOW);
 
  //Initialise digits
  memset(&digits[0],SPACE,4);

  //Set up 7 segment display refresh timer
  TCB1.CCMP = TCB_COMPARE;
  TCB1.INTCTRL = TCB_CAPT_bm;
  TCB1.CTRLA = TCB_ENABLE_bm;

  //Initialise buttons
  stopButton = new Button(BTN_STOP, SWITCHES, 670, 750, false);
  startButton = new Button(BTN_START, SWITCHES, 570, 669, false);
  downButton = new Button(BTN_DOWN, SWITCHES, 0, 100, false);
  downButton->Repeat(downButtonPressed);
  upButton = new Button(BTN_UP, SWITCHES, 460, 569, false);
  upButton->Repeat(upButtonPressed);

  timerRunning = false;
  timerMinutes = 0;
  timerSeconds = 0;

  // Initialize RTC
  while (RTC.STATUS > 0);                           // Wait until registers synchronized
  RTC.PER = 1023;                                   // Set period 1 second
  RTC.CLKSEL = RTC_CLKSEL_INT32K_gc;                // 32.768kHz Internal Oscillator  
  RTC.INTCTRL = RTC_OVF_bm;                         // Enable overflow interrupt
  RTC.CTRLA = RTC_PRESCALER_DIV32_gc | RTC_RTCEN_bm;// Prescaler /32 and enable

  //Enable interrupts
  sei();

  displayMinutes(timerMinutes);
  displaySeconds(timerSeconds);
}

//---------------------------------------------------------------
// RTC Interrupt Handler
ISR(RTC_CNT_vect)
{
  if (timerRunning)
  {
    if (timerSeconds > 0)
    {
      timerSeconds--;
    }
    else if (timerMinutes > 0)
    {
      timerSeconds = 59;
      timerMinutes--;
    }
    timerUpdated = true;
  }
  RTC.INTFLAGS = RTC_OVF_bm;                         // Reset overflow interrupt
}

//-------------------------------------------------------------------------
//Timer B Interrupt handler interrupt each mS - output segments
ISR(TCB1_INT_vect)
{

  //This is 4 bit 'Bit angle Modulation' or BAM, 
  if (bamCounter == (DIGITS * 1) || bamCounter == (DIGITS * 3) || bamCounter == (DIGITS * 7))
  {
    bamBit = bamBit << 1;
  }
  bamCounter++;

  //get the segment bits needed to show the next digit
  uint8_t d = segments[digits[3 - nextDigit]];
  
  //Segment order for d is pabcdefg
  PORTA.DIR = PA6_bm | PA4_bm | PA1_bm;
  PORTB.DIR = PB3_bm | PB2_bm | PB1_bm;

  //Set all digit cathodes HIGH
  uint8_t a = PA7_bm | PA3_bm | PA2_bm;
  uint8_t b = PB0_bm;

  //Only show if bamBit matches brightness level
  if (brightness & bamBit)
  {
    //Set the cathode of the current digit LOW - Rest FLOAT
    switch (nextDigit)
    {
      case 0: a &= ~PA3_bm; PORTA.DIRSET = PA3_bm; break;
      case 1: b &= ~PB0_bm; PORTB.DIRSET = PB0_bm; break;
      case 2: a &= ~PA2_bm; PORTA.DIRSET = PA2_bm; break;
      case 3: a &= ~PA7_bm; PORTA.DIRSET = PA7_bm; break;
    }
  
    //Set the segment anodes HIGH
    //if (d & B10000000) a |= PA4_bm;  //DP
    if (d & B00100000) a |= PA1_bm;  //B
    if (d & B00010000) a |= PA6_bm;  //C
    if (d & B00001000) b |= PB2_bm;  //D
    if (d & B00000100) b |= PB3_bm;  //E
    if (d & B00000010) b |= PB1_bm;  //F
    //Segments A and G are multiplexed with Digits pins - Set Segment HIGH
    if (d & B01000000)  //A
    {
      if (nextDigit == 0) { b |= PB0_bm; PORTB.DIRSET = PB0_bm; } //A2
      else                { a |= PA3_bm; PORTA.DIRSET = PA3_bm; } //A1
    }
    if (d & B00000001)  //G
    {
      if (nextDigit == 2) { a |= PA7_bm; PORTA.DIRSET = PA7_bm; } //G4
      else                { a |= PA2_bm; PORTA.DIRSET = PA2_bm; } //G3
    }
  }
  
  //Set the output pins
  PORTA.OUT = a;  //Set the outputs
  PORTB.OUT = b;

  //Setup for next digit
  nextDigit = (nextDigit + 1) & 0x03;

  //Check if bamCount overflowed, reset if necessary
  if (bamCounter == (DIGITS * 15)) 
  {
    bamCounter = 0;
    bamBit = 0x01;
  }

  //Clear interrupt flag
  TCB1.INTFLAGS |= TCB_CAPT_bm; //clear the interrupt flag(to reset TCB1.CNT)
}

//--------------------------------------------------------------------
//Handle pin change interrupt when button is pressed
void SwitchInterrupt()
{
  alarmOn = false;    //Causes alarm to stop playing
}

//-------------------------------------------------------------------------
// Switch off display and disable interrupt
void displayOff()
{
  //Turn off display timer
  TCB1.CTRLA = TCB1.CTRLA & ~TCB_ENABLE_bm;
  
  //Segment order for d is pabcdefg
  PORTA.DIR = PA6_bm | PA4_bm | PA1_bm;
  PORTB.DIR = PB3_bm | PB2_bm | PB1_bm;

  //Set all digit cathodes HIGH
  PORTA.OUT = PA7_bm | PA3_bm | PA2_bm;
  PORTB.OUT = PB0_bm;
}

//-------------------------------------------------------------------------
// Switch on display and enable interrupt
void displayOn()
{
  //Turn on display timer
  TCB1.CTRLA = TCB1.CTRLA | TCB_ENABLE_bm;
}

//-------------------------------------------------------------------------
// Handle interactions
void loop() 
{
  testButtons();

  if (timerRunning && timerUpdated)
  {
    timerUpdated = false;
    if (timerMinutes == 0 && timerSeconds == 0)
    {
      //Stop timer - Sound alarm
      stopButtonPressed();
      //Turn off display
      displayOff();
      //Attach a pin change interrupt to switches to stop music
      attachInterrupt(SWITCHES,SwitchInterrupt,CHANGE);
      //Play the melody
      alarmOn = true;
      playSong(SPEAKER, melodies[random(NUM_OF_MELODIES)], &alarmOn);
      //Get rid of the pinchange interrupt
      detachInterrupt(SWITCHES);
      //Turn on display
      displayOn();
    }
    else
    {
      //Display updated time
      displayMinutes(timerMinutes);
      displaySeconds(timerSeconds);
    }
  }
  delay(100);
}

//---------------------------------------------------------------
//Test if any buttons have been pressed
void testButtons()
{
  //Single press buttons
  if (timerRunning && stopButton->Pressed())
  {
    stopButtonPressed();
  }
  if (!timerRunning && startButton->Pressed())
  {
    startButtonPressed();
  }
  
  //Don't need to check result of pressed since the button handler will invoke its repeat function
  upButton->Pressed();
  downButton->Pressed();
}

//---------------------------------------------------------------
//Handle Stop btton
void stopButtonPressed()
{
  
  //digitalWrite(LEDS, LOW);
  timerRunning = false;
  displayMinutes(timerMinutes);
  displaySeconds(timerSeconds);
}

//---------------------------------------------------------------
//Handle Menu btton
void startButtonPressed()
{
  //digitalWrite(LEDS, HIGH);
  timerRunning = true;
}

//---------------------------------------------------------------
//Handle DOWN btton
void downButtonPressed()
{
  if (!timerRunning)
  {
    if (timerSeconds > 0)
    {
      timerSeconds--;
    }
    else if (timerMinutes > 0)
    {
      timerMinutes--;
      timerSeconds = 59;
    }
    displayMinutes(timerMinutes);
    displaySeconds(timerSeconds);
  }
}

//---------------------------------------------------------------
//Handle UP btton
void upButtonPressed()
{
  if (!timerRunning)
  {
    if (timerSeconds < 59)
    {
      timerSeconds++;
    }
    else if (timerMinutes < 59)
    {
      timerMinutes++;
      timerSeconds = 0;
    }
    displayMinutes(timerMinutes);
    displaySeconds(timerSeconds);
  }
}

//---------------------------------------------------------------
//Show the minutes value
void displayMinutes(uint8_t num)
{
  for (int i = 0; i < 2; i++)
  {
    digits[i + 2] = num % 10;
    num = num / 10;
  }
}

//---------------------------------------------------------------
//Show the seconds value
void displaySeconds(uint8_t num)
{
  for (int i = 0; i < 2; i++)
  {
    digits[i] = num % 10;
    num = num / 10;
  }
}

//-----------------------------------------------------------------------------------
//Display a number on the 7 segment display
void displayNumber(int number)
{
  for (uint8_t i = 0; i < 4; i++)
  {
    if (number > 0 || i == 0)
    {
      digits[i] = number % 10;
    }
    else
    {
      digits[i] = SPACE;
    }
    number = number / 10;
  }
}

Piano.h

C Header File
#ifndef piano_h
#define piano_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


// SNOOPY'S CHRISTMAS
const uint16_t melody_snoopy[] PROGMEM = {
  DUR_8|NOTE_B3, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_4|NOTE_D4, DUR_8|REST, DUR_8|NOTE_B3, DUR_8|NOTE_B3, DUR_4|NOTE_C4, DUR_8|NOTE_B3, DUR_2|NOTE_A3, DUR_8|NOTE_B3,
  DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_4|NOTE_C4, DUR_8|REST, DUR_8|NOTE_A3, DUR_8|NOTE_B3, DUR_8|NOTE_B3, DUR_8|NOTE_C4, DUR_2|NOTE_D4, DUR_8|NOTE_B3, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_4|NOTE_D4, DUR_8|NOTE_G3, DUR_4|NOTE_G3,
  DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_2|NOTE_E4, DUR_8|NOTE_C4, DUR_4|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_4|REST, DUR_8|NOTE_A3, DUR_8|NOTE_AS3, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_4|NOTE_G3, DUR_8|REST, DUR_8|NOTE_D4, DUR_8|NOTE_D4,
  DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_2|NOTE_D4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_B3, DUR_2|NOTE_A3, DUR_8|NOTE_B3, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_2|NOTE_C4, DUR_8|NOTE_A3, 
  DUR_8|NOTE_B3, DUR_8|NOTE_B3, DUR_8|NOTE_C4, DUR_2|NOTE_D4, DUR_8|NOTE_B3, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_4|NOTE_D4, DUR_4|NOTE_G3, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_2|NOTE_E4, DUR_8|NOTE_A3,
  DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_3|NOTE_D4, DUR_8|NOTE_A3, DUR_8|NOTE_A3, DUR_8|NOTE_AS3, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_1|NOTE_G3, DUR_4|NOTE_G3, DUR_4|NOTE_A3, DUR_4|NOTE_B3,
  DUR_4|NOTE_E4, DUR_4|NOTE_E4, DUR_4|NOTE_D4, DUR_4|NOTE_C4, DUR_4|NOTE_D4, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_2|NOTE_G3, DUR_4|NOTE_E4, DUR_4|NOTE_E4, DUR_4|NOTE_C4, DUR_4|NOTE_C4, 
  DUR_1|NOTE_D4, DUR_4|NOTE_E4, DUR_4|NOTE_E4, DUR_4|NOTE_C4, DUR_4|NOTE_C4, DUR_4|NOTE_D4, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_2|NOTE_G3,
  DUR_4|NOTE_C4, DUR_4|NOTE_B3, DUR_3|NOTE_A3, DUR_8|NOTE_G3, DUR_1|NOTE_G3, DUR_8|REST, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4,
  DUR_2|NOTE_D4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_B3, DUR_2|NOTE_A3, DUR_8|NOTE_B3, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_3|NOTE_C4, DUR_8|NOTE_A3, DUR_8|NOTE_B3, DUR_8|NOTE_B3, DUR_8|NOTE_C4,
  DUR_2|NOTE_D4, DUR_8|NOTE_B3, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_3|NOTE_D4, DUR_4|NOTE_G3, DUR_8|NOTE_C4, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_2|NOTE_E4, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_D4, 
  DUR_3|NOTE_D4, DUR_8|NOTE_A3, DUR_8|NOTE_A3, DUR_8|NOTE_AS3, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_1|NOTE_G3, DUR_2|REST, DUR_4|NOTE_G3, DUR_4|NOTE_A3, DUR_4|NOTE_B3,
  DUR_4|NOTE_E4, DUR_4|NOTE_E4, DUR_4|NOTE_D4, DUR_4|NOTE_C4, DUR_4|NOTE_D4, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_2|NOTE_G3, DUR_4|NOTE_E4, DUR_4|NOTE_E4, DUR_4|NOTE_C4, DUR_4|NOTE_C4, 
  DUR_1|NOTE_D4, DUR_4|NOTE_E4, DUR_4|NOTE_E4, DUR_4|NOTE_C4, DUR_4|NOTE_C4, DUR_4|NOTE_D4, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_2|NOTE_G3, 
  DUR_4|NOTE_C4, DUR_4|NOTE_B3, DUR_3|NOTE_A3, DUR_8|NOTE_G3, DUR_1|NOTE_G3, DUR_4|NOTE_E4, DUR_4|NOTE_E4, 
  DUR_4|NOTE_D4, DUR_4|NOTE_B3, DUR_4|NOTE_D4, DUR_8|NOTE_B3, DUR_8|NOTE_A3, DUR_2|NOTE_G3, DUR_4|NOTE_E4, DUR_4|NOTE_E4, DUR_4|NOTE_D4, DUR_4|NOTE_C4, 
  DUR_1|NOTE_B3, DUR_4|NOTE_E4, DUR_4|NOTE_E4, DUR_4|NOTE_C4, DUR_4|NOTE_C4, DUR_4|NOTE_D4, DUR_8|NOTE_B3, DUR_8|NOTE_A3, 
  DUR_2|NOTE_G3, DUR_4|NOTE_C4, DUR_4|NOTE_B3, DUR_3|NOTE_A3, DUR_8|NOTE_G3, DUR_1|NOTE_G3, 
  DUR_4|REST, END_OF_TUNE 
};

// YOU ARE MY SUNSHINE
const uint16_t melody_sunshine[] PROGMEM = {
  DUR_8|NOTE_G3, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_4|NOTE_E4, DUR_4|NOTE_E4, DUR_8|REST, DUR_8|NOTE_E4, DUR_8|NOTE_DS4, DUR_8|NOTE_E4, DUR_4|NOTE_C4, DUR_4|NOTE_C4,
  DUR_8|REST, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_8|NOTE_E4, DUR_4|NOTE_F4, DUR_4|NOTE_A4, DUR_8|REST, DUR_8|NOTE_A4, DUR_8|NOTE_G4, DUR_8|NOTE_F4, DUR_2|NOTE_E4,
  DUR_8|REST, DUR_8|NOTE_C4, DUR_8|NOTE_D4, DUR_8|NOTE_E4, DUR_4|NOTE_F4, DUR_4|NOTE_A4, DUR_8|REST, DUR_8|NOTE_A4, DUR_8|NOTE_G4, DUR_8|NOTE_F4, DUR_4|NOTE_E4, DUR_4|NOTE_C4,
  DUR_4|REST, DUR_4|NOTE_C4, DUR_8|NOTE_D4, DUR_3|NOTE_E4, DUR_8|NOTE_F4, DUR_4|NOTE_D4, DUR_8|NOTE_D4, DUR_8|NOTE_E4, DUR_2|NOTE_C4, 
  DUR_4|REST, END_OF_TUNE 
};

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

// FR ELISE
const uint16_t melody_elise[] PROGMEM = {
  DUR_4|NOTE_E5, DUR_4|NOTE_DS5, DUR_4|NOTE_E5, DUR_4|NOTE_DS5, DUR_4|NOTE_E5, DUR_4|NOTE_B4, DUR_4|NOTE_D5, DUR_4|NOTE_C5, DUR_2|NOTE_A4, 
  DUR_8|NOTE_C4, DUR_4|NOTE_E3, DUR_4|NOTE_A4, DUR_2|NOTE_B4, DUR_8|NOTE_E3, DUR_4|NOTE_GS3, DUR_4|NOTE_B4, DUR_2|NOTE_C5,
  DUR_8|NOTE_E3,
  DUR_4|NOTE_E5, DUR_4|NOTE_DS5, DUR_4|NOTE_E5, DUR_4|NOTE_DS5, DUR_4|NOTE_E5, DUR_4|NOTE_B4, DUR_4|NOTE_D5, DUR_4|NOTE_C5, DUR_1|NOTE_A4, 
  DUR_4|REST, END_OF_TUNE 
};

// JINGLE BELLS
const uint16_t melody_jingle[] PROGMEM = {
  DUR_4|NOTE_D5, DUR_4|NOTE_B5, DUR_4|NOTE_A5, DUR_4|NOTE_G5, DUR_2|NOTE_D5, DUR_8|NOTE_D5, DUR_8|NOTE_D5,
  DUR_4|NOTE_D5, DUR_4|NOTE_B5, DUR_4|NOTE_A5, DUR_4|NOTE_G5, DUR_1|NOTE_E5,
  DUR_4|NOTE_E5, DUR_4|NOTE_C6, DUR_4|NOTE_B5, DUR_4|NOTE_A5, DUR_1|NOTE_FS5,
  DUR_4|NOTE_D6, DUR_4|NOTE_D6, DUR_4|NOTE_C6, DUR_4|NOTE_A5, DUR_2|NOTE_B5, DUR_4|NOTE_D5,
  DUR_4|NOTE_D5, DUR_4|NOTE_B5, DUR_4|NOTE_A5, DUR_4|NOTE_G5, DUR_2|NOTE_D5, DUR_8|NOTE_D5, DUR_8|NOTE_D5,
  DUR_4|NOTE_D5, DUR_4|NOTE_B5, DUR_4|NOTE_A5, DUR_4|NOTE_G5, DUR_2|NOTE_E5, DUR_4|NOTE_E5,
  DUR_4|NOTE_E5, DUR_4|NOTE_C6, DUR_4|NOTE_B5, DUR_4|NOTE_A5, DUR_4|NOTE_D6, DUR_4|NOTE_D6, DUR_4|NOTE_D6, DUR_4|NOTE_D6,
  DUR_4|NOTE_E6, DUR_4|NOTE_D6, DUR_4|NOTE_C6, DUR_4|NOTE_A5, DUR_1|NOTE_G5,
  DUR_4|NOTE_B5, DUR_4|NOTE_B5, DUR_3|NOTE_B5, DUR_4|NOTE_B5, DUR_4|NOTE_B5, DUR_3|NOTE_B5,
  DUR_4|NOTE_B5, DUR_4|NOTE_D6, DUR_3|NOTE_G5, DUR_8|NOTE_A5, DUR_1|NOTE_B5,
  DUR_4|NOTE_C6, DUR_4|NOTE_C6, DUR_3|NOTE_C6, DUR_8|NOTE_C6, DUR_4|NOTE_C6, DUR_4|NOTE_B5, DUR_4|NOTE_B5, DUR_8|NOTE_B5, DUR_8|NOTE_B5,
  DUR_4|NOTE_B5, DUR_4|NOTE_A5, DUR_4|NOTE_A5, DUR_4|NOTE_B5, DUR_2|NOTE_A5, DUR_2|NOTE_D6,
  DUR_4|NOTE_B5, DUR_4|NOTE_B5, DUR_2|NOTE_B5, DUR_4|NOTE_B5, DUR_4|NOTE_B5, DUR_2|NOTE_B5,
  DUR_4|NOTE_B5, DUR_4|NOTE_D6, DUR_3|NOTE_G5, DUR_8|NOTE_A5, DUR_1|NOTE_B5,
  DUR_4|NOTE_C6, DUR_4|NOTE_C6, DUR_3|NOTE_C6, DUR_8|NOTE_C6, DUR_4|NOTE_C6, DUR_4|NOTE_B5, DUR_4|NOTE_B5, DUR_8|NOTE_B5, DUR_8|NOTE_B5,
  DUR_4|NOTE_D6, DUR_4|NOTE_D6, DUR_4|NOTE_C6, DUR_4|NOTE_A5, DUR_1|NOTE_G5, 
  DUR_1|REST, END_OF_TUNE 
};

// MERRY XMAS
const uint16_t melody_xmas[] PROGMEM = {
  DUR_8|NOTE_A4, DUR_8|NOTE_B4, DUR_4|NOTE_CS5, DUR_8|NOTE_A4, DUR_4|NOTE_E4, DUR_8|REST, 
  DUR_8|NOTE_A4, DUR_8|NOTE_B4, DUR_8|NOTE_CS5, DUR_2|NOTE_B4, DUR_4|REST,
  DUR_8|NOTE_B4, DUR_8|NOTE_CS5, DUR_4|NOTE_D5, DUR_8|NOTE_CS5, DUR_4|NOTE_B4, DUR_8|REST,
  DUR_8|NOTE_E4, DUR_8|NOTE_CS5, DUR_8|NOTE_E5, DUR_8|NOTE_CS5, DUR_8|NOTE_B4, DUR_4|NOTE_A4, DUR_4|REST,
  DUR_8|NOTE_A4, DUR_4|NOTE_FS5, DUR_8|NOTE_FS5, DUR_8|NOTE_FS5,  DUR_8|NOTE_E5, DUR_4|NOTE_D5, DUR_8|REST,
  DUR_8|NOTE_A4, DUR_8|NOTE_D5, DUR_8|NOTE_E5, DUR_8|NOTE_FS5, DUR_4|NOTE_E5, DUR_8|REST,
  DUR_8|NOTE_B4, DUR_8|NOTE_E5, DUR_8|NOTE_FS5, DUR_4|NOTE_G5, DUR_8|NOTE_FS5, DUR_4|NOTE_E5, DUR_8|REST,
  DUR_8|NOTE_A4, DUR_8|NOTE_FS5, DUR_8|NOTE_A5, DUR_8|NOTE_FS5, DUR_8|NOTE_E5, DUR_4|NOTE_D5, DUR_4|REST,
  DUR_8|NOTE_D5, DUR_4|NOTE_B5, DUR_8|NOTE_B5, DUR_8|NOTE_B5, DUR_8|NOTE_A5, DUR_4|NOTE_G5,
  DUR_8|NOTE_G5, DUR_8|NOTE_A5, DUR_4|NOTE_B5, DUR_4|NOTE_A5, DUR_4|REST,
  DUR_8|NOTE_A5, DUR_8|NOTE_A5, DUR_8|NOTE_G5, DUR_8|NOTE_FS5, DUR_8|NOTE_E5, DUR_4|NOTE_E5,
  DUR_8|NOTE_E5, DUR_8|NOTE_E5, DUR_8|NOTE_D5, DUR_8|NOTE_B4, DUR_4|NOTE_D5,
  DUR_4|REST, END_OF_TUNE 
};

#define NUM_OF_MELODIES 6
const uint16_t* melodies[NUM_OF_MELODIES] = {melody_snoopy, melody_elise, melody_range, melody_sunshine, melody_jingle, melody_xmas};

//-----------------------------------------------------------------------------------
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)
  {
    tone(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;
  //uint16_t timeout = (millis() + pauseBetweenNotes;
  //while (millis() < timeout)
  //  ;
  delay(pauseBetweenNotes);

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

//-----------------------------------------------------------------------------------
int playSong(int pin, uint16_t* melody, volatile bool* alarmOn)
{
  //Play each note in the melody until the END_OF_TUNE note is encountered
  int thisNote = 0;
  uint16_t noteRaw = pgm_read_word(&melody[thisNote++]);
  while (*alarmOn && (noteRaw != END_OF_TUNE))
  {
    playNote(pin, noteRaw);
    noteRaw = pgm_read_word(&melody[thisNote++]);
  } //while
}

#endif

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 25
#define REPEAT_MAX_SPEED 5

class Button
{
	public:
	//Simple constructor
	Button(int pin);
	Button(int name, int pin);
	Button(int name, int pin, int analogLow, int 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
  int Name();

	private:
		int _name;
		int _pin;
		bool _range;
		int _low;
		int _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(int pin)
{
  _name = pin;
  _pin = pin;
  _range = false;
  _low = 0;
  _high = 0;
  _backgroundCallback = NULL;
  _repeatCallback = NULL;
  pinMode(_pin, INPUT_PULLUP);
}

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

Button::Button(int name, int pin, int analogLow, int 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)
	{
		int 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();
    		  long faster = speed - REPEAT_INCREASE_SPEED;
  			  speed = (faster >= REPEAT_MAX_SPEED) ? faster : REPEAT_MAX_SPEED;
    		  time = millis() + speed;
    		}
      }
      pressed = true;
    }
  }
  return pressed;
}

//Return current button state
int Button::State()
{
	if (_range)
	{
		int 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
int Button::Name()
{
	return _name;
}

Credits

John Bradnam

John Bradnam

67 projects • 54 followers

Comments