// Enter A or D (UpperCase) to switch between Analogue or Digital, respectively.
// This can be done at any stage of the solution; it can be changed later.
// default is the digital clock.
// Hold the button (or key 5 on your keyboard) for 5 seconds to swtich between
// display screen (Clock, Timer).
// On the Timer screen, short press (<5 sec) the button to start and stop the timer
// Included Libraries
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>
#include <SPI.h>
#include <Wire.h>
#include "RTClib.h"
// OLED SCREEN CONSTANTS
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
// PIN CONSTANTS
#define BUTTON_PIN 2
// Button Constants
#define DEBOUNCE_SAFETY_MARGIN 10
#define MODE_CHANGE_DELAY 5000
// OLED Modes
enum Mode{
CLOCK,
TIMER,
};
Adafruit_SSD1306 Display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
RTC_DS1307 Rtc;
Mode CurrentMode = CLOCK;
char ClockType; // A for analogue and D for digital.
// variables to keep track of last state change time.
unsigned long ButtonStateChangeTime = 0;
//Stopwatch variables
boolean StopwatchRunning = false; // is the stopwatch is running.
DateTime StopwatchTime; //time since the stopwatch was resumed.
TimeSpan SavedStopwatchTime; // saves the elapsed time when the stopwatch is paused.
TimeSpan ElapsedTime; // total elapsed time.
void setup() {
setupComponents();
// Used to indicate the first prompt. Prompted repeatedly from the serialEvent.
Serial.print("Select Analogue or Digital (A/D): ");
//Set up the button
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonDown, FALLING);
}
void loop() {
Display.clearDisplay();
DateTime now = Rtc.now();
int hour, minute, second;
getTimeToDisplay(hour, minute, second);
// displays digital or analogue clock
if(ClockType == 'A'){
draw_analog_time(hour, minute, second);
}else{
draw_digital_time(hour, minute, second);
}
Display.display();
}
// Sets the hour, min and sec to display on the screen by setting it to the byref variables.
void getTimeToDisplay(int &hour, int &minute, int &second){
DateTime now = Rtc.now();
switch(CurrentMode){
case CLOCK:
{
hour = now.hour();
minute = now.minute();
second = now.second();
}
break;
case TIMER:
{
if(!StopwatchRunning){
StopwatchTime = now; // stops the stopwatch by setting time difference to zero.
}
ElapsedTime = now - StopwatchTime + SavedStopwatchTime;
hour = ElapsedTime.hours();
minute = ElapsedTime.minutes();
second = ElapsedTime.seconds();
}
break;
}
}
void setupComponents(){
// Setup serial
Serial.begin(9600);
// Setup pins
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Setup the RTC
if (!Rtc.begin()) {
Serial.println("\nCouldn't find RTC");
while(1); // Don't proceed, loop forever
}
// Setup the OLED
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!Display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println("\nSSD1306 allocation failed");
while(1);
}
Display.setTextColor(SSD1306_WHITE);
}
// Triggered when data in the serial buffer.
// Uses user input to change clock display mode.
void serialEvent(){
String text = Serial.readStringUntil('\n');
Serial.println(text);
if(text == "D" || text == "A"){
ClockType = text[0];
Serial.print("Select Analogue or Digital (A/D): ");
}else{
Serial.println("\nInvalid input. Try again");
}
}
// Triggers on button up event.
void buttonUp(){
// debouncing.
if(ButtonStateChangeTime + DEBOUNCE_SAFETY_MARGIN > millis()){
return;
}
if (millis() > ButtonStateChangeTime + MODE_CHANGE_DELAY){
// Change OLED modes
if(CurrentMode == CLOCK){
CurrentMode = TIMER;
}else if(CurrentMode == TIMER){
CurrentMode = CLOCK;
}
StopwatchRunning = false;
SavedStopwatchTime = 0;
}else{
switch(CurrentMode){
case TIMER:
{
if(StopwatchRunning){
//saves the stopwatch time as it is about to be paused.
SavedStopwatchTime = ElapsedTime;
}
// pauses or resumes the stopwatch.
StopwatchRunning = !StopwatchRunning;
}
break;
}
}
ButtonStateChangeTime = millis(); //records the time of the state changed. used for debouncing and long press.
//starts looking for button down.
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonDown, FALLING);
}
// Records the button down event.
void buttonDown(){
// debouncing
if(ButtonStateChangeTime + DEBOUNCE_SAFETY_MARGIN > millis()){
return;
}
ButtonStateChangeTime = millis();
//starts looking for button up.
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonUp, RISING);
}
// Tries to draw the text as close to the center as possible.
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);
}
// Draws the digital clock on the screen. Seconds is optional and will not be shown for alarm.
void draw_digital_time(int hour, int minute, int second){
Display.setTextSize(2.5);
char displayOutput[9];
sprintf(displayOutput, "%02d:%02d:%02d", hour, minute,second);
drawCentreString(displayOutput, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
}
// Draws an alalogue clock on the OLED display. Seconds is optional and will not be shown for Alarm mode
void draw_analog_time(int hour, int minute, int second){
// define some constants for the clock size.
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;
// Calculates the angle of each hand.
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);
// Draws the clock.
Display.drawCircle(SCREEN_WIDTH/2, SCREEN_HEIGHT /2, clock_radius , WHITE);
Display.drawLine(SCREEN_WIDTH/2, SCREEN_HEIGHT /2, hour_x, hour_y, WHITE );
Display.drawLine(SCREEN_WIDTH/2, SCREEN_HEIGHT /2, min_x, min_y, WHITE );
Display.drawLine(SCREEN_WIDTH/2, SCREEN_HEIGHT /2, sec_x, sec_y, WHITE );
}