Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 3 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
|
This small battery powered clock can either be configured as a switchable analog + digital clock or as a digital alarm clock. Unfortunately there is not enough space in the ATtiny1614 processor to incorporate all modes.
Using the STL files provided, print the front and the back. I used a 0.2mm layer height and no supports. There are two mounts in the front to support the printed circuit board. Drill these out with a 2.5mm drill and create a thread with a 3mm tap.
Building the electronicsMy original plan with this build was to use a ATtiny84 processor (8K Flash, 512bytes RAM). However when I went to program the clock, I realised that the graphics library requires enough RAM to buffer the display. It required (84 x 48 / 8) = 504 bytes. To remedy this problem, I created a small adapter to hold a ATtiny1614 processor (16K Flash, 2K RAM).
One of the other issues I ran into was the different variants of Nokia 5110 displays.
The PCB is designed for the left variant. The headers on the middle variant are wider and the right hand variant only has a single header row.
I used a machined plugs and sockets to mount the Nokia 5110 LCD display. Use a 14 pin IC socket for the microprocessor so that the adapter board can plug in.
The ATtiny1614 processor is a new breed of microprocessors from Microchip. Please read my tutorial on how to program these processors.
The "Nokia_Analog_Digital_Alarm_Clock_V1.ino" file contains the following definitions:
#define INCLUDE_ANALOG
#define INCLUDE_ALARM
The program is too large to fit in the flash memory when both the ANALOG code and ALARM code are included. You need to comment out the #define statement on the component you wish to leave out.
You will need to download and install the LCD5110_Graph library into the Arduino IDE. Also you will need the DS1302RTC library as well.
Using the clockThe clock has three buttons. The left most is SELECT, the middle button is BACK and the right most button is NEXT.
When the SELECT button is pressed, the back-light will light up for 5 seconds allowing you to read the display if it is dark.
Pressing SELECT again will circle between the Analog and Digital clock faces or the Digital and Alarm clock faces depending on what configuration has been programmed.
To set the time and date:
While on the TIME screen, press either the BACK or NEXT buttons. The Hours will start to flash. Use the BACK and NEXT buttons to set the hour value in 24 hour format. Press SELECT again and the Minutes will start to flash. Again use the BACK and NEXT buttons to set the Minutes value. Pressing the SELECT button again will allow you to set the Year, Month and finally the Day. After which the SELECT button will store the new time.
To set the alarm:
While on the ALARM screen, press either the BACK or NEXT buttons. The Hours will start to flash. Use the BACK and NEXT buttons to set the hour value in 24 hour format. Press SELECT again and the Minutes will start to flash. Again use the BACK and NEXT buttons to set the Minutes value. Pressing the SELECT button again will allow you turn on or off the alarm. After which the SELECT button will store the new alarm parameters. (Note: the alarm time is always shown in 24 hour format).
Demo/*============================================================================================================
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);
}
//-------------------------------------------------------
// 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);
}
}
//-------------------------------------------------------
// 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);
}
}
/*
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);
};
/*
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;
}
/*
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);
}
}
Comments