Alarm Clock
This digital alarm clock displays the time on a 4-digit 7-segment display. It has the following features:
- Two buttons for setting the current time (Hour / Minute)
- Programable alarm with a snooze function
- The alarm sound can be easily customized in code, and even play melodies
- The colon symbol blinks to indicate seconds
- Optional RTC to make the clock more accurate
- Power loss recovery: current time and alarm settings are stored in RTC
How to use the clock
To set the time, press the Minute/Hour buttons.
Pressing the Alarm button enables/disables the alarm. The screen will display the alarm state by showing the word "on" or "off".
After enabling the alarm, the current alarm time will be displayed for a few seconds. You can use the Minute/Hour buttons to adjust the alarm time. To finish, press the Alarm button again, or just wait a few seconds.
When the alarm goes off, press the Alarm button briefly to snooze it for 9 minutes. The screen will display four circles to let you know that the alarm was snoozed.
To stop the alarm, hold down the Alarm button for one second or more.
Project Structure
The code is divided into several modules:
- config.h - Configuration options for the clock: whether to use an RTC chip, Snooze time, and more. Using a configuration file a common practice when you have several modules in your program.
- alarm-clock.ino - The main program code. It manages the user interface: the 7-segment display and the buttons.
- Clock - The
Clock
class manages the current time and the alarm state machine. It uses the RTClib library to communicate with the RTC chip and track time. - AlarmTone - The
AlarmTone
class play the alarm sound. You can change the values of theTONES
array,TONE_TIME
andTONE_SPACING
to customize the alarm and play different tones and melodies.
Hardware
Item | Quantity | Notes |
---|---|---|
Arduino Uno R3 | 1 | |
4-Digit 7-Segment | 4 | Common Anode, 14 pins |
220Ω Resistor | 8 | Connect to the 7-segment segment pins |
PNP Transistor | 4 | Optional, recommended |
4.7kΩ Resistor | 4 | If you use the PNP transistors |
12mm Push button | 3 | |
Piezo Buzzer | 1 | Used for the alarm |
DS1307 RTC | 1 | Optional |
- You can also use a Common Cathode 7-segment display, just adjust the
DISPLAY_TYPE
constant in config.h, and switch to NPN transistors.
To keep the clock hardware down to minimum, the Arduino controls the 7-Segment display directly, using the SevSeg library. This approach has a downside, however: it uses 12 GPIO pins!
If you want to save on Arduino pins, you can use a 74HC595 shift register to cut the pin usage down to 6, or even a 7-Segment display with an integrated controller chip, such as TM1637, HT16K33, or MAX7219. In this case, you will need to change the code to use a different display library (SevSeg does not support this case), but this is out of the scope of this project.
Diagram
Pin Connections
Arduino Uno Pin | Device | Device Pin |
---|---|---|
2 | 7-Segment | 14 (Dig 1) |
3 | 7-Segment | 11 (Dig 2) |
4 | 7-Segment | 10 (Dig 3) |
5 | 7-Segment | 6 (Dig 4) |
6 | 7-Segment | 13 (A) |
7 | 7-Segment | 9 (B) |
8 | 7-Segment | 4 (C) |
9 | 7-Segment | 2 (D) |
10 | 7-Segment | 1 (E) |
11 | 7-Segment | 12 (F) |
12 | 7-Segment | 5 (G) |
13 | 7-Segment | 8 (Colon) |
A0 | Hour Button | - |
A1 | Minute Button | - |
A2 | Alarm Button | - |
A3 | Buzzer / Speaker | - |
A4 | DS1307 RTC | SDA |
A5 | DS1307 RTC | SCL |
- The pin numbers for your 7-segment display may differ. Please consult the datasheet relevant to your device to find out the relevant pin numbers.
Source code
alarm-clock.ino
/**
Arduino Digital Alarm Clock
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
*/
#include <SevSeg.h>
#include "Button.h"
#include "config.h"
#include "AlarmTone.h"
#include "Clock.h"
const int COLON_PIN = 13;
const int SPEAKER_PIN = A3;
Button hourButton(A0);
Button minuteButton(A1);
Button alarmButton(A2);
AlarmTone alarmTone;
Clock clock;
SevSeg sevseg;
enum DisplayState {
DisplayClock,
DisplayAlarmStatus,
DisplayAlarmTime,
DisplayAlarmActive,
DisplaySnooze,
};
DisplayState displayState = DisplayClock;
long lastStateChange = 0;
void changeDisplayState(DisplayState newValue) {
displayState = newValue;
lastStateChange = millis();
}
long millisSinceStateChange() {
return millis() - lastStateChange;
}
void setColon(bool on) {
digitalWrite(COLON_PIN, on ? LOW : HIGH);
}
void displayTime() {
DateTime now = clock.now();
sevseg.setNumber(now.hour() * 100 + now.minute());
bool blinkState = now.second() % 2 == 0;
setColon(blinkState);
}
void clockState() {
displayTime();
if (alarmButton.read() == Button::RELEASED && clock.alarmActive()) {
// Read alarmButton has_changed() to clear its state
alarmButton.has_changed();
changeDisplayState(DisplayAlarmActive);
return;
}
if (hourButton.pressed()) {
clock.incrementHour();
}
if (minuteButton.pressed()) {
clock.incrementMinute();
}
if (alarmButton.pressed()) {
clock.toggleAlarm();
changeDisplayState(DisplayAlarmStatus);
}
}
void alarmStatusState() {
setColon(false);
sevseg.setChars(clock.alarmEnabled() ? " on" : " off");
if (millisSinceStateChange() > ALARM_STATUS_DISPLAY_TIME) {
changeDisplayState(clock.alarmEnabled() ? DisplayAlarmTime
: DisplayClock);
return;
}
}
void alarmTimeState() {
DateTime alarm = clock.alarmTime();
sevseg.setNumber(alarm.hour() * 100 + alarm.minute(), -1);
if (millisSinceStateChange() > ALARM_HOUR_DISPLAY_TIME
|| alarmButton.pressed()) {
changeDisplayState(DisplayClock);
return;
}
if (hourButton.pressed()) {
clock.incrementAlarmHour();
lastStateChange = millis();
}
if (minuteButton.pressed()) {
clock.incrementAlarmMinute();
lastStateChange = millis();
}
if (alarmButton.pressed()) {
changeDisplayState(DisplayClock);
}
}
void alarmState() {
displayTime();
if (alarmButton.read() == Button::RELEASED) {
alarmTone.play();
}
if (alarmButton.pressed()) {
alarmTone.stop();
}
if (alarmButton.released()) {
alarmTone.stop();
bool longPress = alarmButton.repeat_count() > 0;
if (longPress) {
clock.stopAlarm();
changeDisplayState(DisplayClock);
} else {
clock.snooze();
changeDisplayState(DisplaySnooze);
}
}
}
void snoozeState() {
sevseg.setChars("****");
if (millisSinceStateChange() > SNOOZE_DISPLAY_TIME) {
changeDisplayState(DisplayClock);
return;
}
}
void setupSevenSegment() {
byte digits = 4;
byte digitPins[] = {2, 3, 4, 5};
byte segmentPins[] = {6, 7, 8, 9, 10, 11, 12};
bool resistorsOnSegments = false;
bool updateWithDelays = false;
bool leadingZeros = true;
bool disableDecPoint = true;
sevseg.begin(DISPLAY_TYPE, digits, digitPins, segmentPins, resistorsOnSegments,
updateWithDelays, leadingZeros, disableDecPoint);
sevseg.setBrightness(90);
pinMode(COLON_PIN, OUTPUT);
}
void setup() {
Serial.begin(115200);
clock.begin();
hourButton.begin();
hourButton.set_repeat(500, 200);
minuteButton.begin();
minuteButton.set_repeat(500, 200);
alarmButton.begin();
alarmButton.set_repeat(1000, -1);
alarmTone.begin(SPEAKER_PIN);
setupSevenSegment();
}
void loop() {
sevseg.refreshDisplay();
switch (displayState) {
case DisplayClock:
clockState();
break;
case DisplayAlarmStatus:
alarmStatusState();
break;
case DisplayAlarmTime:
alarmTimeState();
break;
case DisplayAlarmActive:
alarmState();
break;
case DisplaySnooze:
snoozeState();
break;
}
}
AlarmTone.cpp
/**
Arduino Digital Alarm Clock
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
*/
#include <Arduino.h>
#include "AlarmTone.h"
#define TONE_TIME 500 /* ms */
#define TONE_SPACING 100 /* ms */
static const uint16_t TONES[] = {
500,
800,
};
const uint16_t NUM_TONES = sizeof(TONES) / sizeof(TONES[0]);
AlarmTone::AlarmTone()
: _playing(false)
, _tone_index(0)
, _last_tone_time(0) {
}
void AlarmTone::begin(uint8_t pin) {
_pin = pin;
pinMode(_pin, OUTPUT);
}
void AlarmTone::play() {
if (!_playing || _last_tone_time + TONE_TIME + TONE_SPACING < millis()) {
tone(_pin, TONES[_tone_index], TONE_TIME);
_tone_index = (_tone_index + 1) % NUM_TONES;
_last_tone_time = millis();
}
_playing = true;
}
void AlarmTone::stop() {
noTone(_pin);
_tone_index = 0;
_playing = false;
}
AlarmTone.h
/**
Arduino Digital Alarm Clock
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
*/
#ifndef __ALARM_TONE_H__
#define __ALARM_TONE_H__
class AlarmTone {
public:
AlarmTone();
void begin(uint8_t pin);
void play();
void stop();
private:
uint8_t _pin;
bool _playing;
uint8_t _tone_index;
unsigned long _last_tone_time;
};
#endif /* __ALARM_TONE_H */
Clock.cpp
/**
Arduino Digital Alarm Clock
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
*/
#include <Arduino.h>
#include "Clock.h"
#define MINUTE 60 * 1000L /* ms */
#define TIMESPAN_DAY TimeSpan(1, 0, 0, 0)
#define NVRAM_ADDR_ALARM_ENABLED 0
#define NVRAM_ADDR_ALARM_HOUR 1
#define NVRAM_ADDR_ALARM_MINUTE 2
Clock::Clock()
: _alarm_state(ALARM_OFF)
, _alarm_snooze_time(0)
, _alarm_hour(DEFAULT_ALARM_HOUR)
, _alarm_minute(0) {
}
void Clock::begin() {
# if USE_RTC
if (!_rtc.begin()) {
Serial.println("Couldn't find RTC");
abort();
}
_alarm_state = _rtc.readnvram(NVRAM_ADDR_ALARM_ENABLED) ? ALARM_OFF : ALARM_DISABLED;
_alarm_hour = _rtc.readnvram(NVRAM_ADDR_ALARM_HOUR) % 24;
_alarm_minute = _rtc.readnvram(NVRAM_ADDR_ALARM_MINUTE) % 60;
# else /* USE_RTC */
DateTime zeroTime;
_rtc.begin(zeroTime);
# endif
}
/***** Clock management *****/
DateTime Clock::now() {
return _rtc.now();
}
void Clock::incrementMinute() {
DateTime now = _rtc.now();
DateTime newTime = DateTime(now.year(), now.month(), now.day(), now.hour(),
(now.minute() + 1) % 60);
_rtc.adjust(newTime);
}
void Clock::incrementHour() {
DateTime now = _rtc.now();
DateTime newTime = DateTime(now.year(), now.month(), now.day(),
(now.hour() + 1) % 24, now.minute());
_rtc.adjust(newTime);
}
/***** Alarm management *****/
bool Clock::_isAlarmDueTime() {
auto currentTime = now();
auto alarm = alarmTime();
return ((currentTime.hour() == alarm.hour())
&& (currentTime.minute() == alarm.minute()));
}
bool Clock::alarmEnabled() {
return _alarm_state != ALARM_DISABLED;
}
bool Clock::alarmActive() {
switch (_alarm_state) {
case ALARM_DISABLED:
return false;
case ALARM_OFF:
if (_isAlarmDueTime()) {
_alarm_state = ALARM_ACTIVE;
return true;
}
return false;
case ALARM_ACTIVE:
return true;
case ALARM_SNOOZED:
if (millis() >= _alarm_snooze_time) {
_alarm_state = ALARM_ACTIVE;
return true;
}
return false;
case ALARM_STOPPED:
if (!_isAlarmDueTime()) {
_alarm_state = ALARM_OFF;
}
return false;
default:
return false;
}
}
void Clock::toggleAlarm() {
bool enabled = !alarmEnabled();
_alarm_state = enabled ? ALARM_OFF : ALARM_DISABLED;
_rtc.writenvram(NVRAM_ADDR_ALARM_ENABLED, enabled);
}
DateTime Clock::alarmTime() {
DateTime now = _rtc.now();
DateTime alarm = DateTime(now.year(), now.month(), now.day(), _alarm_hour, _alarm_minute);
return alarm >= now ? alarm : alarm + TIMESPAN_DAY;
}
void Clock::snooze() {
_alarm_state = ALARM_SNOOZED;
_alarm_snooze_time = millis() + SNOOZE_TIME * MINUTE;
}
void Clock::stopAlarm() {
_alarm_state = ALARM_STOPPED;
}
void Clock::incrementAlarmHour() {
_alarm_hour = (_alarm_hour + 1) % 24;
_alarm_state = ALARM_OFF;
_rtc.writenvram(NVRAM_ADDR_ALARM_HOUR, _alarm_hour);
}
void Clock::incrementAlarmMinute() {
_alarm_minute = (_alarm_minute + 1) % 60;
_alarm_state = ALARM_OFF;
_rtc.writenvram(NVRAM_ADDR_ALARM_MINUTE, _alarm_minute);
}
Clock.h
/**
Arduino Digital Alarm Clock
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
The Clock class manages the clock time and the alarm state machine.
*/
#ifndef __CLOCK_H__
#define __CLOCK_H__
#include <RTClib.h>
#include "config.h"
enum AlarmState {
ALARM_DISABLED,
ALARM_OFF,
ALARM_ACTIVE,
ALARM_SNOOZED,
ALARM_STOPPED,
};
class Clock {
public:
Clock();
void begin();
/* Clock management */
DateTime now();
void incrementMinute();
void incrementHour();
/* Alarm Management */
bool alarmEnabled();
DateTime alarmTime();
bool alarmActive();
void toggleAlarm();
void snooze();
void stopAlarm();
void incrementAlarmHour();
void incrementAlarmMinute();
protected:
bool _isAlarmDueTime();
#if USE_RTC
RTC_DS1307 _rtc;
#else /* USE_RTC */
RTC_Millis _rtc;
#endif /* USE_RTC */
byte _alarm_hour;
byte _alarm_minute;
AlarmState _alarm_state;
unsigned long _alarm_snooze_time;
};
#endif /* __CLOCK_H__ */
config.h
/**
Arduino Digital Alarm Clock
Copyright (C) 2020, Uri Shaked.
Released under the MIT License.
Clock Configuration File.
*/
#ifndef __CONFIG_H__
#define __CONFIG_H__
/**
Change to 0 for running without an external RTC (Real time clock)
*/
#define USE_RTC 1
/**
Your 7-Segment display type: COMMON_ANODE or COMMON_CATHODE
*/
#define DISPLAY_TYPE COMMON_ANODE
/**
For how long should we show the alarm status (on/off)?
*/
const int ALARM_STATUS_DISPLAY_TIME = 1000; /* ms */
/**
For how long should we show the alarm hour?
*/
const int ALARM_HOUR_DISPLAY_TIME = 2500; /* ms */
/**
For how long should we display the snooze screen?
*/
const int SNOOZE_DISPLAY_TIME = 500; /* ms */
/**
How many minutes to snooze the alarm for?
*/
const int SNOOZE_TIME = 9; /* minutes */
/**
The default Alarm hour (for non-RTC mode)
*/
const int DEFAULT_ALARM_HOUR = 9;
#endif /* __CONFIG_H__ */