Skip to content

Instantly share code, notes, and snippets.

@raveren
Last active May 12, 2022 17:45
Show Gist options
  • Save raveren/4729733eb70b6218ad0912ebb4968c4b to your computer and use it in GitHub Desktop.
Save raveren/4729733eb70b6218ad0912ebb4968c4b to your computer and use it in GitHub Desktop.
arduino alarm clock consisting of RTC_DS3231 clock module, button and old school phone dial. https://photos.app.goo.gl/sZ6CsrMxHYXJodcP6
/*
rodo laika
pradejus rinkti - nustatomas zadintuvas
paspaudus sverti - rodo zadintuvo laika (arba 8888 jei dar neivestas)
palaikius sverti 5 sek - nustatomas laikas
skamba zadintuvas
skamba minute - tada snuzina 5 min
paspaudus sverti - uzsnuzina 5 min
palaikius sverti 3 sek - isjungia zadintuva
maksimaliai snuzina 1 val, tada issijungia
*/
// install RTClib, TM1637 and ezButton from arduino library manager for these to work:
#include "RTClib.h"
#include <TM1637Display.h>
#include <ezButton.h>
/***
* _____ _____ _ _ _____
* | __ \|_ _|| \ | | / ____|
* | |__) | | | | \| || (___
* | ___/ | | | . ` | \___ \
* | | _| |_ | |\ | ____) |
* |_| |_____||_| \_||_____/
*
*
*/
// pin 6, geltonas laidas. mygtukas tel diske sukliksi tiek kartų pagal tai koks skaičius
// renkamas nuo 1 iki 10;
ezButton btn_phone_number(6);
// pin 7, žalias laidas. mygtukas tel diske įsijungia tada, kai diskas pradedamas sukti,
// išjungimas baigus sukti diską. btn_phone_dial_is_spinning.isPressed() returns true while
// the dial is spinning
ezButton btn_phone_dial_is_spinning(7);
// pin 2, žadintuvo snooze mygtukas
ezButton btn_snooze(2);
#define skambucio_rite_a 10 // skambučio ritės 1 pin
#define skambucio_rite_b 11 // skambučio ritės 2 pin
// Define the connections pins for TM1637 4 digit 7 segment display
TM1637Display display = TM1637Display(8, 9);
/***
* _ _ _
* /\ (_) | | | |
* / \ _ __ _ __ __ __ __ _ _ __ _ __ _ | |__ | | ___ ___
* / /\ \ | '_ \ | '_ \ \ \ / // _` || '__|| | / _` || '_ \ | | / _ \/ __|
* / ____ \ | |_) || |_) | \ V /| (_| || | | || (_| || |_) || || __/\__ \
* /_/ \_\| .__/ | .__/ \_/ \__,_||_| |_| \__,_||_.__/ |_| \___||___/
* | | | |
* |_| |_|
*/
// Create rtc and display object
RTC_DS3231 real_time_clock;
const int DISPLAY_DOUBLE_COLON = 0b01000000;
enum global_states {
SHOWING_TIME,
SETTING_ALARM,
SHOWING_ALARM_TIME,
BLARING_ALARM,
SETTING_TIME,
SNOOZING
};
enum global_states global_state = SHOWING_TIME;
/**
* SETTINGS
*/
const int LED_BLINK_TIME_MS = 200;
const int ALARM_SNOOZE_TIME_MINS = 5;
const int MAX_ALARM_SNOOZE_TIME = 100; // hours * 100 + minutes
// time for alarm - stored as 100 x hrs + mins, i.e. 9:48 is 948 and 15:36 is 1536
int alarm_time = -1;
int snoozed_alarm_time = -1;
bool is_alarm_snoozed = false;
unsigned int currently_setting_time = 0;
unsigned int which_digit_is_being_set = 0;
// for toggling (blinking) led
bool last_led_state_on = true;
// store milliseconds since led last toggled
unsigned long led_blink_timer = 0;
unsigned long ringing_timer = 0;
// this stores when the snooze button was PRESSED DOWN ()
unsigned long snooze_button_pressed = 0;
unsigned long alarm_time_displayed_since = 0;
/***
* _ ____
* | | / /\ \
* ___ ___ | |_ _ _ _ __ | | | |
* / __| / _ \| __|| | | || '_ \ | | | |
* \__ \| __/| |_ | |_| || |_) || | | |
* |___/ \___| \__| \__,_|| .__/ | | | |
* | | \_\/_/
* |_|
*/
void setup() {
Serial.begin(9600);
// Check if RTC is connected correctly
if (!real_time_clock.begin()) {
Serial.println("Couldn't find real time clock module");
while (1); // halt program
}
// Check if the RTC lost power (battery removed) and if so, set the time to whenever this was
// compiled & uploaded...
if (real_time_clock.lostPower()) {
real_time_clock.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
display.setBrightness(5);
display.clear();
btn_phone_number.setDebounceTime(5);
btn_phone_number.setCountMode(COUNT_FALLING);
btn_phone_dial_is_spinning.setDebounceTime(10);
btn_phone_dial_is_spinning.setCountMode(COUNT_FALLING);
btn_snooze.setDebounceTime(10);
btn_snooze.setCountMode(COUNT_FALLING);
pinMode(skambucio_rite_a, OUTPUT); // signalas į skambučio ritę
pinMode(skambucio_rite_b, OUTPUT); // signalas į skambučio ritę
}
/***
_
| |
| | ___ ___ _ __
| | / _ \ / _ \ | '_ \
| || (_) || (_) || |_) |
|_| \___/ \___/ | .__/
| |
|_|
*/
void loop() {
/***
INITIALIZE
*/
btn_phone_dial_is_spinning.loop(); // MUST call the loop() function first
btn_phone_number.loop(); // MUST call the loop() function first
btn_snooze.loop(); // MUST call the loop() function first
// we use the main button for multiple functionalities - always be registering when it's pressed
if (btn_snooze.isPressed()) {
snooze_button_pressed = millis(); // save when the button was pressed
}
if (btn_snooze.isReleased()) {
snooze_button_pressed = 0;
}
switch (global_state) {
/***
_ __ _ _ _ _
| | / _| | || | | | | |
__| | ___ | |_ __ _ _ _ | || |_ ___ | |_ __ _ | |_ ___
/ _` | / _ \| _|/ _` || | | || || __| / __|| __|/ _` || __|/ _ \
| (_| || __/| | | (_| || |_| || || |_ \__ \| |_| (_| || |_| __/
\__,_| \___||_| \__,_| \__,_||_| \__| |___/ \__|\__,_| \__|\___|
*/
case SHOWING_TIME:
led_blink_timer = 0; // we are not blinking the display anymore
ringing_timer = 0; // alarm is not ringing
if (btn_phone_dial_is_spinning.isPressed()) { // means we started dialing
global_state = SETTING_ALARM;
alarm_time = 0; // reset alarm time
Serial.println("SETTING_ALARM");
return;
}
// isPressed() is a one-time event, it's reported once and then becomes off
// regardless of button state - pressed or not
if (btn_snooze.isPressed()) {
switch_to_showing_alarm_time();
Serial.println("SHOWING_ALARM_TIME");
return;
}
if (is_longpress(5000)) {
global_state = SETTING_TIME;
which_digit_is_being_set = 0;
Serial.println("SETTING_TIME");
return;
}
if (is_alarm_time_now()) {
if (!is_alarm_snoozed) {
global_state = BLARING_ALARM;
ringing_timer = millis();
Serial.println("BLARING_ALARM");
return;
}
} else {
// we just need this variable set for the one minute when alarm is on
is_alarm_snoozed = false;
}
show_current_time();
return;
break;
/***
_ _ _ _
| | | | (_) | |
___ ___ | |_ | |_ _ _ __ __ _ __ _ | | __ _ _ __ _ __ ___
/ __| / _ \| __|| __|| || '_ \ / _` | / _` || | / _` || '__|| '_ ` _ \
\__ \| __/| |_ | |_ | || | | || (_| | | (_| || || (_| || | | | | | | |
|___/ \___| \__| \__||_||_| |_| \__, | \__,_||_| \__,_||_| |_| |_| |_|
__/ |
|___/
*/
case SETTING_ALARM:
if (btn_phone_dial_is_spinning.isReleased()) {
// todo almost identical to setting time, refactor before changing more
int reported_count = btn_phone_number.getCount();
btn_phone_number.resetCount();
if (which_digit_is_being_set == 0) {
reported_count++; // somewhy the first number you dial in always gets registered as less by one
}
if (reported_count >= 10) {
reported_count = 0;
}
if (which_digit_is_being_set == 0) {
if (reported_count > 2) {
reported_count = 2;
}
reported_count = reported_count * 1000;
}
if (which_digit_is_being_set == 1) {
if (alarm_time > 1000 && reported_count > 3) {
reported_count = 3;
}
reported_count = reported_count * 100;
}
if (which_digit_is_being_set == 2) {
if (reported_count > 5) {
reported_count = 5;
}
reported_count = reported_count * 10;
}
if (which_digit_is_being_set == 3) {
}
alarm_time += reported_count;
display.showNumberDecEx(alarm_time, DISPLAY_DOUBLE_COLON, true);
// Serial.print(alarm_time);
// Serial.print(" - alarmo reiksme; pridetas skaitmuo: ");
// Serial.print(reported_count);
// Serial.print(" i pozicija # ");
// Serial.println(which_digit_is_being_set);
// Serial.println("isReleased.........................................................................");
which_digit_is_being_set++;
if (which_digit_is_being_set >= 4) {
which_digit_is_being_set = 0;
snoozed_alarm_time = -1;
is_alarm_snoozed = false;
switch_to_showing_alarm_time();
}
}
break;
/***
_ _ _
| | (_) | |
___ | |__ ___ __ __ _ _ __ __ _ __ _ | | __ _ _ __ _ __ ___
/ __|| '_ \ / _ \\ \ /\ / /| || '_ \ / _` | / _` || | / _` || '__|| '_ ` _ \
\__ \| | | || (_) |\ V V / | || | | || (_| | | (_| || || (_| || | | | | | | |
|___/|_| |_| \___/ \_/\_/ |_||_| |_| \__, | \__,_||_| \__,_||_| |_| |_| |_|
__/ |
|___/
*/
case SHOWING_ALARM_TIME:
if (alarm_time == -1) {
blink_number(8888);
} else {
blink_number(alarm_time);
}
// if we start dialing right now - ignore it completely
btn_phone_number.resetCount();
// show the alarm on screen for 1.5s
if (millis() - alarm_time_displayed_since >= 1500) {
global_state = SHOWING_TIME;
Serial.println("SHOWING_TIME");
return;
}
break;
/***
_ _ _
| | | | (_)
| |__ | | __ _ _ __ _ _ __ __ _
| '_ \ | | / _` || '__|| || '_ \ / _` |
| |_) || || (_| || | | || | | || (_| |
|_.__/ |_| \__,_||_| |_||_| |_| \__, |
__/ |
|___/
*/
case BLARING_ALARM:
if (!is_alarm_time_now()) { // means a minute has passed and it's no longer alarm time
snooze_alarm();
Serial.println("auto_snooze_alarm");
global_state = SHOWING_TIME;
return;
}
if (is_longpress(3000)) {
// longpress - off, short press - snooze
turn_alarm_off();
Serial.println("turn_alarm_off");
global_state = SHOWING_TIME;
return;
}
if (btn_snooze.isReleased()) {
snooze_alarm();
Serial.println("snooze_alarm");
global_state = SHOWING_TIME;
return;
}
sound_the_alarm();
break;
/***
_ _ _ _ _
| | | | (_) | | (_)
___ ___ | |_ | |_ _ _ __ __ _ | |_ _ _ __ ___ ___
/ __| / _ \| __|| __|| || '_ \ / _` | | __|| || '_ ` _ \ / _ \
\__ \| __/| |_ | |_ | || | | || (_| | | |_ | || | | | | || __/
|___/ \___| \__| \__||_||_| |_| \__, | \__||_||_| |_| |_| \___|
__/ |
|___/
*/
case SETTING_TIME:
if (which_digit_is_being_set == 0) { // until the dial is dialed blink current time
DateTime now = real_time_clock.now();
blink_number(now.hour() * 100 + now.minute());
if (btn_snooze.isPressed()) {
global_state = SHOWING_TIME;
return;
}
}
if (btn_phone_dial_is_spinning.isReleased()) {
int reported_count = btn_phone_number.getCount();
btn_phone_number.resetCount();
if (which_digit_is_being_set == 0) {
currently_setting_time = 0; // start
reported_count++; // somewhy the first number you dial in always gets registered as less by one
}
if (reported_count == 10) {
reported_count = 0;
}
if (which_digit_is_being_set == 0) {
if (reported_count > 2) {
reported_count = 2;
}
reported_count = reported_count * 1000;
}
if (which_digit_is_being_set == 1) {
if (currently_setting_time > 1000 && reported_count > 3) {
reported_count = 3;
}
reported_count = reported_count * 100;
}
if (which_digit_is_being_set == 2) {
if (reported_count > 5) {
reported_count = 5;
}
reported_count = reported_count * 10;
}
if (which_digit_is_being_set == 3) {
}
currently_setting_time += reported_count;
display.showNumberDecEx(currently_setting_time, DISPLAY_DOUBLE_COLON, true);
which_digit_is_being_set++;
if (which_digit_is_being_set >= 4) {
real_time_clock.adjust(
DateTime(
2000, 12, 31, // date - we don't care about it
currently_setting_time / 100, // hour
currently_setting_time % 100, // minute
0 // second
)
);
currently_setting_time = 0;
which_digit_is_being_set = 0;
global_state = SHOWING_TIME;
}
}
break;
}
}
/***
_ _
| | | |
| |__ ___ | | _ __ ___ _ __ ___
| '_ \ / _ \| || '_ \ / _ \| '__|/ __|
| | | || __/| || |_) || __/| | \__ \
|_| |_| \___||_|| .__/ \___||_| |___/
| |
|_|
*/
bool is_longpress(int ms) {
// snooze_button_pressed stores time since button was depressed, and is set to 0 when released
return snooze_button_pressed && millis() - snooze_button_pressed >= ms;
}
void switch_to_showing_alarm_time() {
global_state = SHOWING_ALARM_TIME;
alarm_time_displayed_since = millis();
}
void show_current_time() {
// Get current date and time
DateTime now = real_time_clock.now();
// Create time format to display:
int displayTime = (now.hour() * 100) + now.minute();
display.showNumberDecEx(
displayTime,
now.second() % 2 == 0 ? DISPLAY_DOUBLE_COLON
: 0, // display center double colon only every other second; i.e. blink
true
);
}
void blink_number(int number) {
if (led_blink_timer == 0) {
led_blink_timer = millis();
}
if (millis() - led_blink_timer >= LED_BLINK_TIME_MS) {
last_led_state_on = !last_led_state_on;
led_blink_timer = millis();
}
if (last_led_state_on) {
display.showNumberDecEx(number, DISPLAY_DOUBLE_COLON, true);
} else {
display.clear();
}
}
void snooze_alarm() {
is_alarm_snoozed = true;
// now set the snooze time
unsigned int mins = real_time_clock.now().minute();
unsigned int hrs = real_time_clock.now().hour();
mins += ALARM_SNOOZE_TIME_MINS;
if (mins >= 60) {
hrs += mins / 60;
mins = mins % 60;
}
snoozed_alarm_time = (hrs * 100) + mins;
// limit maximum snooze time
if (snoozed_alarm_time - alarm_time > MAX_ALARM_SNOOZE_TIME) {
Serial.println("MAX_ALARM_SNOOZE_TIME");
turn_alarm_off();
} else {
if (hrs > 23) { // handle 24hrs
snoozed_alarm_time = mins;
}
}
Serial.print("snoozed_alarm_time ");
Serial.println(snoozed_alarm_time);
}
void turn_alarm_off() {
// simply snooze the alarm but don't set the next ring time
is_alarm_snoozed = true;
snoozed_alarm_time = -1;
Serial.println("turned alarm off for today");
}
bool is_alarm_time_now() {
DateTime now = real_time_clock.now();
return (now.hour() == (alarm_time / 100)) && (now.minute() == (alarm_time % 100))
|| (now.hour() == (snoozed_alarm_time / 100))
&& (now.minute() == (snoozed_alarm_time % 100));
}
void sound_the_alarm() {
unsigned int since_start = millis() - ringing_timer;
DateTime now = real_time_clock.now();
blink_number(now.hour() * 100 + now.minute());
if (since_start < 10) {
digitalWrite(skambucio_rite_a, HIGH);
digitalWrite(skambucio_rite_b, LOW);
} else if (since_start < 20) {
digitalWrite(skambucio_rite_a, LOW);
digitalWrite(skambucio_rite_b, LOW);
} else if (since_start < 30) {
digitalWrite(skambucio_rite_a, LOW);
digitalWrite(skambucio_rite_b, HIGH);
} else if (since_start < 38) {
digitalWrite(skambucio_rite_a, LOW);
digitalWrite(skambucio_rite_b, LOW);
} else {
ringing_timer = millis();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment