#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "RTClib.h"
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define BUTTON_PIN 2
#define BUZZER_PIN 9
#define DEBOUNCE_SAFETY_MARGIN 10
#define MODE_CHANGE_DELAY 5000
#define ALARM_MODE_CHANGE_DELAY 2000
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
#define BLINK_DURATION 500
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
RTC_DS1307 rtc;
enum Mode{
CLOCK,
TIMER,
ALARM
};
enum AlarmSetMode{
NONE,
HOUR,
MINUTE,
};
char type;
unsigned long state_change_t = 0;
unsigned long blink_state_change_t = 0;
Mode currMode = CLOCK;
AlarmSetMode currAlarmSetMode = HOUR;
boolean stopwatch_running = false; // is the stopwatch is running.
boolean alarmOn = false;
boolean blinkToggle = false;
DateTime stopwatch_time; //time since the stopwatch was resumed.
int alarmHour, alarmMin;
TimeSpan saved_stopwatch_time; // saves the elapsed time when the stopwatch is paused.
TimeSpan elapsed_time; // total elapsed time.
void setup() {
Serial.begin(9600);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
while(1); // Don't proceed, loop forever
}
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
while(1);
}
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), button_down, FALLING);
//Select Analogue or digital
while(type != 'A' && type != 'D'){
Serial.println("Select Analogue or Digital (A/D):");
while(Serial.available() == 0);
type = Serial.read();
}
DateTime alarmSetTime = rtc.now() + TimeSpan(0,0,1,0);
alarmHour = alarmSetTime.hour();
alarmMin = alarmSetTime.minute();
}
void loop() {
display.clearDisplay();
int hour;
int minute;
int second;
DateTime now = rtc.now();
switch(currMode){
case CLOCK:
{
hour = now.hour();
minute = now.minute();
second = now.second();
}
break;
case TIMER:
{
if(!stopwatch_running){
stopwatch_time = now; // stops the stopwatch by setting time difference to zero.
}
elapsed_time = now - stopwatch_time + saved_stopwatch_time;
hour = elapsed_time.hours();
minute = elapsed_time.minutes();
second = elapsed_time.seconds();
}
break;
default:
{
hour = alarmHour;
minute = alarmMin;
second = 0;
}
break;
}
if(alarmOn && alarmHour == now.hour() && alarmMin == now.minute()){
tone(BUZZER_PIN, 868, 250);
}
// displays digital or analogue clock
if(type == 'A'){
draw_analog_time(hour, minute, second);
}else{
draw_digital_time(hour, minute, second);
}
if(alarmOn){
drawAlarmSymbol(0,0);
}
if(blink_state_change_t + BLINK_DURATION < millis()){
blinkToggle = !blinkToggle;
blink_state_change_t = millis();
}
display.display();
}
void drawAlarmSymbol(int startX, int startY){
int radius = 5;
display.drawCircle(startX + radius, startY + radius, radius, WHITE);
display.drawLine(startX + radius, startY + 2, startX + radius, startY + radius, WHITE);
display.drawLine(startX + radius, startY + radius, startX + radius + (radius - 2) * 0.707 ,startY + radius + (radius - 2) * 0.707, WHITE);
}
void button_up(){
// debouncing.
if(state_change_t + DEBOUNCE_SAFETY_MARGIN > millis()){
return;
}
if (millis() > state_change_t + MODE_CHANGE_DELAY){
if(currMode == CLOCK){
currMode = TIMER;
}else if(currMode == TIMER){
currMode = ALARM;
}else{
currMode = CLOCK;
}
stopwatch_running = false;
saved_stopwatch_time = 0;
}else if(millis() > state_change_t + ALARM_MODE_CHANGE_DELAY && currMode == ALARM){
if(currAlarmSetMode == NONE){
currAlarmSetMode = HOUR;
}else if(currAlarmSetMode == HOUR){
currAlarmSetMode = MINUTE;
}else{
currAlarmSetMode = NONE;
}
}else{
//short presss
switch(currMode){
case TIMER:
{
if(stopwatch_running){
//saves the stopwatch time as it is about to be paused.
saved_stopwatch_time = elapsed_time;
}
// pauses or resumes the stopwatch.
stopwatch_running = !stopwatch_running;
}
break;
case ALARM:
{
switch(currAlarmSetMode){
case NONE:
{
alarmOn = !alarmOn;
}
break;
case HOUR:
{
alarmHour = (alarmHour + 1) % 24;
}
break;
case MINUTE:
{
alarmMin = (alarmMin + 1) % 60;
}
break;
}
}
break;
}
}
state_change_t = millis(); //records the time of the state changed. used for debouncing and long press.
//starts looking for button down.
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), button_down, FALLING);
}
void button_down(){
// debouncing
if(state_change_t + DEBOUNCE_SAFETY_MARGIN > millis()){
return;
}
state_change_t = millis();
//starts looking for button up.
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), button_up, RISING);
}
void drawCentreString(const char *buf, int x, int y)
{
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(buf, x, y, &x1, &y1, &w, &h); //calc width of new string
display.setCursor(x - w/2.5, y-h/2.5);
display.print(buf);
}
void draw_digital_time(int hour, int minute, int second){
display.setTextSize(2.5);
display.setTextColor(SSD1306_WHITE);
char displayOutput[9];
sprintf(displayOutput, "%d:%d:%d",hour, minute, second);
drawCentreString(displayOutput, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
}
void draw_analog_time(int hour, int minute, int second){
int clock_radius = (SCREEN_HEIGHT - 2)/2;
int clock_minute = clock_radius - 5;
int clock_hour = clock_minute / 2.0;
int clock_second = clock_minute - 2;
float hour_ang = ((hour % 12) * 60.0 + minute)/ 720 * 2 * PI;
float min_ang = minute / 60.0 * 2 * PI;
float sec_ang = second / 60.0 * 2 * PI;
float hour_x = SCREEN_WIDTH /2 + clock_hour * sin(hour_ang);
float hour_y = SCREEN_HEIGHT/2 - clock_hour * cos(hour_ang);
float min_x = SCREEN_WIDTH /2 + clock_minute * sin(min_ang);
float min_y = SCREEN_HEIGHT/2 - clock_minute * cos(min_ang);
float sec_x = SCREEN_WIDTH /2 + clock_second * sin(sec_ang);
float sec_y = SCREEN_HEIGHT/2 - clock_second * cos(sec_ang);
display.drawCircle(SCREEN_WIDTH/2, SCREEN_HEIGHT /2, clock_radius , SSD1306_WHITE);
if(!(currMode == ALARM && currAlarmSetMode == HOUR && blinkToggle)){
display.drawLine(SCREEN_WIDTH/2, SCREEN_HEIGHT /2, hour_x, hour_y, SSD1306_WHITE );
}
if(!(currMode == ALARM && currAlarmSetMode == MINUTE && blinkToggle)){
display.drawLine(SCREEN_WIDTH/2, SCREEN_HEIGHT /2, min_x, min_y, SSD1306_WHITE );
}
display.drawLine(SCREEN_WIDTH/2, SCREEN_HEIGHT /2, sec_x, sec_y, SSD1306_WHITE );
}