wolfidan
Published © GPL3+

Arduino Wake Up Light Alarm Clock

Turn your bedside lamp into a wake light with this cool device.

IntermediateFull instructions provided9,374
Arduino Wake Up Light Alarm Clock

Things used in this project

Hardware components

Power MOSFET N-Channel
Power MOSFET N-Channel
×1
Graphic OLED, 128 x 32 Pixels
Graphic OLED, 128 x 32 Pixels
×1
LED switch button (blue)
×1
LED switch button (green)
×1
DC-DC Buck (Step Down) Regulator, Adjustable
DC-DC Buck (Step Down) Regulator, Adjustable
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
×1
Arduino Nano R3
Arduino Nano R3
×1
High Accuracy Pi RTC (DS3231)
Seeed Studio High Accuracy Pi RTC (DS3231)
×1
LED Light Bulb, Frosted GLS
LED Light Bulb, Frosted GLS
×1

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Solder Flux, Soldering
Solder Flux, Soldering

Story

Read more

Schematics

Electrical diagram

Code

wakeup.c

C/C++
Main routine for the wakeup clock
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h> //I2C library
#include <RtcDS3231.h> //RTC library
#include <EEPROM.h>
// CONSTANTS
//---------------------------------
#define BUTTON_L 3
#define BUTTON_R 2
#define SDA A4
#define SLC A5
#define FET_OUT 9
#define POT_IN A2
#define BUTTON_LED_OUT 6
#define BUTTON_TIME 3000 // Time after which a click is considered as a long click
#define BLINK_DURATION 1000 // Blink duration of digits when setting time/date
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
// calendar image
const static unsigned char PROGMEM calendar[] = {48, 12, 48, 12, 127, 254, 255, 255, 255, 255, 192, 3, 219, 109, 219, 109, 192, 1, 203, 109, 219, 109, 192, 1, 219, 109, 219, 109, 96, 2, 63, 252};
// bell image
const static unsigned char PROGMEM bell[] = {1, 128, 1, 128, 3, 192, 6, 96, 12, 16, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 48, 12, 112, 14, 96, 6, 96, 14, 60, 60, 3, 192};
// clockhour image
const static unsigned char PROGMEM clockhour[] = {3, 192, 12, 48, 16, 8, 33, 132, 65, 130, 65, 130, 129, 129, 129, 129, 129, 1, 130, 1, 68, 3, 72, 2, 32, 4, 16, 8, 12, 48, 3, 192};
// smiley image
const static unsigned char PROGMEM smiley[] = {0, 15, 240, 0, 0, 127, 254, 0, 1, 255, 255, 128, 3, 240, 31, 192, 7, 0, 1, 224, 14, 0, 0, 240, 28, 240, 15, 120, 57, 8, 16, 188, 48, 96, 6, 28, 112, 240, 15, 14, 112, 240, 15, 14, 96, 240, 15, 6, 224, 240, 15, 7, 224, 96, 6, 7, 224, 0, 0, 7, 224, 0, 0, 7, 226, 0, 0, 71, 227, 128, 3, 199, 227, 248, 31, 199, 225, 255, 255, 135, 97, 255, 255, 134, 112, 255, 255, 6, 112, 127, 254, 14, 56, 63, 252, 12, 56, 31, 248, 28, 28, 15, 240, 56, 14, 0, 0, 112, 7, 0, 0, 224, 3, 192, 3, 192, 1, 240, 15, 128, 0, 127, 254, 0, 0, 31, 248, 0};
// hourglass image
const static unsigned char hourglass[] PROGMEM = {63, 254, 31, 252, 16, 4, 11, 232, 11, 232, 11, 200, 4, 144, 2, 32, 2, 32, 4, 16, 8, 8, 8, 8, 8, 8, 16, 4, 31, 252, 63, 254};
// days per month
const unsigned char daysPerMonth[12] = {31, 30, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// Functions
boolean adjustDST(RtcDateTime& time_);
unsigned char readButtons();
unsigned char readButtons_setTime();
void setDuration(unsigned char& curDuration);
void setTime(unsigned char& curHour, unsigned char& curMin, boolean setAlarm = false);
void setDate(unsigned short& curYear, unsigned char& curMonth, unsigned char& curDay);
void u8gInfo(char info[]);
void displayInfo(char info[],  const unsigned char *picture);
void displayInfo(char info[], short  blink_index[]);
void displayInfo(char info[], const unsigned char *picture, short  blink_index[]);
void displayInfo(char info[], char info2[]);
void  displayInfo(char info[], char info2[], const unsigned char *picture);
// VARIABLES
//---------------------------------
// Initialize flags
boolean buttonActive = false; // if a button is clicked
boolean longPressActive = false; // if long click
boolean button1Active = false; // if button 1 clicked
boolean button2Active = false; // if button 2 clicked
boolean mainLightOn = false; // if main led on
boolean updateTime = false; // if new time has been set
boolean updateAlarm = false; // if new alarm has been set
boolean alarmEnabled = false;
boolean alarmRunning = false;
boolean isDST = false; // if daylight saving time is on
boolean displayOn = true; // whether or not display on screen
// Initialize counters
unsigned short potLevel; //pot level
unsigned long buttonTimer = 0; // current click duration
unsigned char currentMenu = 0; // current choosen menu
char choice = '0'; // current user choice in menu (day, month, year, hour,...)
long remainingTime; // remaining time before alarm (in seconds)
unsigned short currentYearClock;
char currentMonthClock;
char currentDayClock;
char currentMinClock;
char currentHourClock;
char currentMinAlarm;
char currentHourAlarm;
unsigned char currentDurationAlarm;
boolean adjustDST(RtcDateTime& time_) {
 // ********************* Calculate offset for Sunday *********************
 boolean DST;
 int y = time_.Year();                          // DS3231 uses two digit year (required here)
 int x = (y + y / 4 + 2) % 7;    // remainder will identify which day of month
 // is Sunday by subtracting x from the one
 // or two week window.  First two weeks for March
 // and first week for November
 // *********** Test DST: BEGINS on 2nd Sunday of March @ 2:00 AM *********
 if (time_.Month() == 3 && time_.Day() == (14 - x) && time_.Hour() >= 2)
 {
   DST = true;                           // Daylight Savings Time is TRUE (add one hour)
 }
 if ((time_.Month() == 3 && time_.Day() > (14 - x)) || time_.Month() > 3)
 {
   DST = true;
 }
 // ************* Test DST: ENDS on 1st Sunday of Nov @ 2:00 AM ************
 if (time_.Month() == 11 && time_.Day() == (7 - x) && time_.Hour() >= 2)
 {
   DST = false;                            // daylight savings time is FALSE (Standard time)
 }
 if ((time_.Month() == 11 && time_.Day() > (7 - x)) || time_.Month() > 11 || time_.Month() < 3)
 {
   DST = false;
 }
 if (DST == true)                       // Test DST and add one hour if = 1 (TRUE)
 {
   time_ = time_ + 3600;
 }
 return DST;
}
void displayInfo(char info[]) {
 // ********************* Displays a text string *********************
 display.clearDisplay();
 display.setTextSize(2);             // 2:1 pixel scale
 display.setTextColor(WHITE);        // Draw white text
 display.setCursor(4 * (10 - strlen(info)), 15); // Centers the text horizontally
 display.println(info);
 return;
}
void displayInfo(char info[], char info2[]) {
 // ********************* Displays two strings *********************
 display.clearDisplay();
 display.setTextSize(2);             // 2:1 pixel scale
 display.setTextColor(WHITE);        // Draw white text
 display.setCursor(4 * (10 - strlen(info)), 15);  // Centers the text horizontally
 display.println(info);
 display.setTextSize(1);             // Normal 1:1 pixel scale
 display.setCursor(4 * (10 - strlen(info)), 0);  // Centers the text horizontally
 display.println(info2);
 return;
}
void displayInfo(char info[], char info2[], const unsigned char *picture) {
 // ********************* Displays two strings and a picture *********************
 displayInfo(info,info2);
 display.drawBitmap(
   (display.width()  - 16 - 10 ),
   (display.height() - 16 - 8),
   picture, 16, 16, 1);
 return;
}
void displayInfo(char info[], const unsigned char *picture) {
 // ********************* Displays one string and a picture *********************
 displayInfo(info);
 display.drawBitmap(
   (display.width()  - 16 - 10 ),
   (display.height() - 16 - 8),
   picture, 16, 16, 1);
 return;
}
void displayInfo(char info[], short  blink_index[]) {
 // ********************* Displays a string and blinks specified digits *********************
 char info_cp[20];
 strcpy(info_cp, info);
 if ((millis() % BLINK_DURATION) > BLINK_DURATION / 2) {
   for (int i = blink_index[0]; i <= blink_index[1]; i++)
     info_cp[i] = ' '; // to blink we replace temporarily the digits with spaces
 }
 displayInfo(info_cp);
}
void displayInfo(char info[], const unsigned char *picture, short  blink_index[]) {
 // ********************* Displays a string, a picture and blinks specified digits *********************
 displayInfo(info, blink_index);
 display.drawBitmap(
   (display.width()  - 16 - 10 ),
   (display.height() - 16 - 8),
   picture, 16, 16, 1);
}
void welcome(){
 display.clearDisplay();
 for (int posx = 0; posx <= 128 - 32; posx += 8) {
   display.drawBitmap(
     (posx),
     (0),
     smiley, 32, 32, 1);
   display.display();
   delay(100);
   display.clearDisplay();
 }
 for (int posx = 128 - 32; posx >= 0; posx -= 8) {
   display.drawBitmap(
     (posx),
     (0),
     smiley, 32, 32, 1);
   display.display();
   delay(100);
   display.clearDisplay();
 }
 delay(2000);
}
unsigned char readButtons_setTime() {
 // ********************* Reads the two buttons in the set time context *********************
 unsigned char button_press = 0;
 // Possibilities are 00 : None - None, 10 : Press - None, 01 : None - Press, 11 : Press - Press
 //////////////////////////////////////////////////////////
 // Read buttons
 if (digitalRead(BUTTON_L) == LOW && digitalRead(BUTTON_R) == LOW) {
   button_press = 11;
 }
 else if (digitalRead(BUTTON_L) == LOW) {
   button_press = 10;
 }
 else if (digitalRead(BUTTON_R) == LOW) {
   button_press = 1;
 }
 else {
   button_press = 0;
 }
 //////////////////////////////////////////////////////////
 return button_press;
}
unsigned char readButtons() {
 // ********************* Reads the two buttons in the standard menu context *********************
 unsigned char button_press = 0;
 // Possibilities are 00 : None - None, 01 : None - ShortPress, 10 : ShortPress - None, 
 // 11 : ShortPress - ShortPress, 02 : None - LongPress, 20 : LongPress - None, 22 : LongPress - LongPress
 //////////////////////////////////////////////////////////
 // Read buttons
 if (digitalRead(BUTTON_L) == LOW) {
   if (buttonActive == false) {
     buttonActive = true;
     buttonTimer = millis();
   }
   button1Active = true;
 }
 if (digitalRead(BUTTON_R) == LOW) {
   if (buttonActive == false) {
     buttonActive = true;
     buttonTimer = millis();
   }
   button2Active = true;
 }
 if ((buttonActive == true) && ((unsigned long)(millis() - buttonTimer) > BUTTON_TIME) && (longPressActive == false)) {
   longPressActive = true;
   if ((button1Active == true) && (button2Active == true)) {
     button_press = 22;
   } else if ((button1Active == true) && (button2Active == false)) {
     button_press = 20;
   } else {
     button_press = 2;
   }
 }
 if ((buttonActive == true) && (digitalRead(BUTTON_L) == HIGH) && (digitalRead(BUTTON_R) == HIGH)) {
   if (longPressActive == true) {
     longPressActive = false;
   } else {
     if ((button1Active == true) && (button2Active == true)) {
       button_press = 11;
     } else if ((button1Active == true) && (button2Active == false)) {
       button_press = 10;
     } else {
       button_press = 1;
     }
   }
   buttonActive = false;
   button1Active = false;
   button2Active = false;
 }
 return button_press;
}
void setup() {
 // Setup code that is run only once
 //Serial.begin(9600);  //Starts serial connection
 //Serial.setTimeout(2000);
 pinMode(BUTTON_LED_OUT, OUTPUT); // Controls button led brightness
 pinMode(FET_OUT, OUTPUT); // Controls main led brightness
 analogWrite(FET_OUT, 0); // turn off main led
 pinMode(BUTTON_R, INPUT_PULLUP); // Input right button
 pinMode(BUTTON_L, INPUT_PULLUP); // Input left button
 // Initialize RTC module
 rtcObject.Begin();    //Starts I2C
 // Retrieve user specifications from EEPROM
 if (EEPROM.read(0) == 126) { // Indicates that it has been written already
   currentHourAlarm = EEPROM.read(1);
   currentMinAlarm = EEPROM.read(2);
   currentDurationAlarm = EEPROM.read(3);
 }
 else { // Set defaults, since nothing in memory
   currentHourAlarm = 7;
   currentMinAlarm = 30;
   currentDurationAlarm = 30;
 }
 currentMenu = 0; // start with menu zero 
 // Start small welcoming animation (smiley)
  welcome();
}
void setDuration(unsigned char& curDuration) {
 // ********************* Sets the duration of the alarm *********************
 unsigned char  button_press = readButtons_setTime(); // Read buttons
 // Left button => decrease value, right button => increase value
 // Both buttons => Accept value and pursue
 if (button_press == 1) {
   curDuration += 1;
 }
 else if (button_press == 10) {
   curDuration -= 1;
 }
 else if (button_press == 11) {
   currentMenu = 0; // Return to main menu
   updateAlarm = true; //Flag to inform that alarm has been updated
 }
 // Display current duration value and hourglass image
 char str[3];   //declare a string as an array of chars
 sprintf(str, "%d",     //%d allows to print an integer to the string
         curDuration);
 if (displayOn) {
   displayInfo(str, hourglass);
   display.display();
 }
 return;
}
void setDate(unsigned short& curYear, char& curMonth, char& curDay) {
 // ********************* Sets the current date *********************
 short blink_index[2]; // will be updated
 unsigned char  button_press = readButtons_setTime(); // Read buttons
 if (choice == 'y') { // year setup step
   blink_index[0] = 0; //blink from digit 0
   blink_index[1] = 3; //        to digit 3 
   if (button_press == 1) {
     curYear += 1;
   }
   else if (button_press == 10) {
     curYear -= 1;
   }
   else if (button_press == 11) {
     choice = 'm'; // jump to month setup step
     delay(1000); // wait a bit to avoid fast clicks
   }
 }
 else if (choice == 'm') { // month setup step
   blink_index[0] = 5; //blink from digit 5
   blink_index[1] = 6; //        to digit 6
   if (button_press == 1) {
     curMonth += 1;
     if (curMonth > 12) {
       curMonth = 1; // avoid month > 12
     }
   }
   else if (button_press == 10) {
     curMonth -= 1;
     if (curMonth < 1) {
       curMonth = 12; // avoid month < 1
     }
   }
   else if (button_press == 11) {
     choice = 'd'; // jump to day setup step
     delay(1000); // wait a bit to avoid fast clicks
   }
 }
 else if (choice == 'd') {
   blink_index[0] = 8; //blink from digit 8
   blink_index[1] = 9; //        to digit 9
   if (button_press == 1) {
     curDay += 1;
     if (curDay > daysPerMonth[curMonth]) {
       curDay = 1; // avoid impossible day
     }
   }
   else if (button_press == 10) {
     curDay -= 1;
     if (curDay < 1) { 
       curDay = 1; // avoid day < 1
     }
   }
   else if (button_press == 11) {
     choice = 'h'; // jump to hour setup step
     delay(1000); // wait a bit to avoid fast clicks
     currentMenu = 2; // go to menu 2 (time setup)
   }
 }
 // Create date string in the format YYYY-MM-DD, ex. 2019-06-30
 char str[11];   //declare a string as an array of chars
 sprintf(str, "%04d-%02d-%02d",     //%d allows to print an integer to the string
         curYear,
         curMonth,
         curDay
        );
 if (displayOn) {
   displayInfo(str, blink_index);
   display.display();
 }
 return;
}
void setTime(char& curHour, char& curMin, boolean setAlarm = false) {
 short blink_index[2];
 unsigned char button_press = readButtons_setTime();
 if (choice == 'h') {
   blink_index[0] = 0;
   blink_index[1] = 1;
   if (button_press == 1) {
     curHour += 1;
     if (curHour > 23) {
       curHour = 0;
     }
   }
   else if (button_press == 10) {
     curHour -= 1;
     if (curHour < 0) {
       curHour = 23;
     }
   }
   else if (button_press == 11) {
     choice = 'm';
     delay(1000);
   }
 }
 else if (choice == 'm') {
   blink_index[0] = 3;
   blink_index[1] = 4;
   if (button_press == 1) {
     curMin += 1;
     if (curMin > 59) {
       curMin = 0;
     }
   }
   else if (button_press == 10) {
     curMin -= 1;
     if (curMin < 0) {
       curMin = 59;
     }
   }
   else if (button_press == 11) {
     if (setAlarm) {
       currentMenu = 4;
     }
     else {
       updateTime = true;
       currentMenu = 0;
     }
   }
 }
 char str[11];   //declare a string as an array of chars
 sprintf(str, "%02d:%02d",     //%d allows to print an integer to the string
         curHour,   //get hour method
         curMin //get minute method
        );
 if (displayOn) {
   displayInfo(str, clockhour, blink_index);
   display.display();
 }
 return;
}
void loop() {
 // Main code that is run repeatedly
 // ADJUSTING TIME AND ALARM 
 //--------------------------------
 // Start by updating RTC module if new time has been chosen
 if (updateTime) {
   RtcDateTime currentTime = RtcDateTime(currentYearClock, currentMonthClock, currentDayClock,
                                         currentHourClock, currentMinClock, 0); //define date and time object
   if (isDST) { // correct for DST if needed
     currentTime -= 3600;
   }
   rtcObject.SetDateTime(currentTime);
   updateTime = false;
 }
 // Also update alarm in EEPROM if new alarm setup has been chosen
 if (updateAlarm) {
   EEPROM.write(1, currentHourAlarm);
   EEPROM.write(2, currentMinAlarm);
   EEPROM.write(3, currentDurationAlarm);
   updateAlarm = false;
 }
 // ADJUSTING BUTTON LED AND OLED BRIGHTNESS
 //--------------------------------
 // Now we read the value of the button led pot 
 // We get average of 5 measurements
 int niter = 5;
 float potLevel = 0;
 for (int i = 0; i < niter; i++) {
   potLevel += float(analogRead(POT_IN));
   delay(5);
 }
 potLevel /= niter;
 // Normalize pot level between 0 and 1
 float level =  (float)potLevel / 1023.;
 level  = pow(level, 2); // Use power of two to get more smooth transition
 // if pot level below a certain value, we disable oled screen
 if (level < 0.02) {
   displayOn = false;
 }
 else {
   displayOn = true;
 }
 // Finally set button led brightness value
 analogWrite(BUTTON_LED_OUT, int(level * 255));
 // Also control OLED brightness (doesn't work very well)
 display.ssd1306_command(0x81);
 display.ssd1306_command(int(level * 160)); //max 160
 display.ssd1306_command(0xD9);
 display.ssd1306_command(int(level * 34));  //max 34
 // MANAGE ALARM
 //--------------------------------
 RtcDateTime currentTime = rtcObject.GetDateTime();    //get the time from the RTC
 // COrrect  for daylight saving time
 isDST = adjustDST(currentTime); // Get DST
 if (currentMenu != 1 && currentMenu != 2) { // menu 1 and 2 set the date and time manually, so don't update from RTC
   currentYearClock = currentTime.Year();
   currentMonthClock = currentTime.Month();
   currentDayClock = currentTime.Day();
   currentHourClock = currentTime.Hour();
   currentMinClock = currentTime.Minute();
 }
 // Compute remaining time until alarm starts in seconds
 remainingTime = 3600L * (int(currentHourAlarm) - int(currentHourClock)) + 60L * (int(currentMinAlarm) - int(currentMinClock)) - int(currentTime.Second());
 if (remainingTime < 0) {
   remainingTime += (24 * 3600L);
 } 
 // Alarm starts only if time until specified alarm time is less than specified duration
 // and in main menu (user is not changing settings) and alarm is enabled
 if ((remainingTime < currentDurationAlarm * 60 && currentMenu == 0 && alarmEnabled)) {
   //Start to light the led
   float level = 1. - float(remainingTime) / (60 * currentDurationAlarm); // fraction of time remaining until alarm
   level = 255 * pow(level, 2); // power of 2 for smoother transition
   if (level < 1) { 
     level = 1; // ensure minimal brightness
   }
   analogWrite(FET_OUT, level);
   alarmRunning = true; 
 }
 else { // if alarm is running continue to light LED at max brightness even after alarm time has passed
        // (it makes no sense to stop it immediately as person might not yet have woken up)
   if (alarmRunning) {
     analogWrite(FET_OUT, 255);
   }
 }
 // MANAGE MENUS
 //--------------------------------
 if (currentMenu == 0) {
   // Main menu
   unsigned char buttonPress = readButtons();
   // 4 possibilities
   if (buttonPress == 10) { // short press left
     // Toggle on/off light
     if (mainLightOn) {
       analogWrite(FET_OUT, 0);
       mainLightOn = false;
     }
     else {
       analogWrite(FET_OUT, 255);
       mainLightOn = true;
     }
   }
   else if (buttonPress == 1 ) { // short press right
     // Toggle on/off alarm
     if (alarmEnabled) {
       alarmEnabled = false;
       // turn off lamp
       analogWrite(FET_OUT, 0);
     }
     else {
       alarmEnabled = true;
     }
   }
   else if (buttonPress == 20) { // long press left
     // Jump into date selection menu
     choice = 'y'; // Start with year selection
     delay(1000);
     currentMenu = 1;
   }
   else if (buttonPress == 2) { // short press right
     // Jump into alarm selection menu
     choice = 'h'; // Start with hour
     currentMenu = 3;
     delay(1000);
   }
   // DISPLAY INFO
   //--------------------------------
   char str[5];   //declare a string as an array of chars
   sprintf(str, "%02d:%02d",     //%d allows to print an integer to the string
           currentTime.Hour(),   //get hour method
           currentTime.Minute());
   if (displayOn) {
     if (alarmEnabled) {// time + bell image
       char str2[5];   //declare a string as an array of chars
       sprintf(str2, "%02d:%02d",     //%d allows to print an integer to the string
               currentHourAlarm,   //get hour method
               currentMinAlarm //get minute method
              );
       displayInfo(str, str2, bell);
     }
     else {// only time
       displayInfo(str);
     }
   }
   else { // Display empty string to show nothing on screen
     displayInfo("");
   }
   display.display();
   // UPDATE USER CHANGES
   //--------------------------------
   if (updateTime) { // set new time on RTC
     RtcDateTime currentTime = RtcDateTime(currentYearClock, currentMonthClock, currentDayClock,
                                           currentHourClock, currentMinClock, 0); //define date and time object
     if (isDST) {
       currentTime -= 3600;
     }
     rtcObject.SetDateTime(currentTime);
     updateTime = false;
   }
   if (updateAlarm) { // write alarm setup in EEPROM
     EEPROM.write(1, currentHourAlarm);
     EEPROM.write(2, currentMinAlarm);
     EEPROM.write(3, currentDurationAlarm);
     updateAlarm = false;
   }
   if (!alarmEnabled) {
     // turn off alarm if it is started
     if (alarmRunning) {
       alarmRunning = false;
     }
   }
 }
 // THINGS TO DO IN OTHER MENUS
 //--------------------------------
 else if (currentMenu == 1) {
   // set clock
   setDate(currentYearClock, currentMonthClock, currentDayClock);
 }
 else if (currentMenu == 2) {
   setTime(currentHourClock, currentMinClock, false);
 }
 else if (currentMenu == 3) {
   setTime(currentHourAlarm, currentMinAlarm, true);
 }
 else if (currentMenu == 4) {
   setDuration(currentDurationAlarm);
 }
 delay(200);
}

Credits

wolfidan

wolfidan

0 projects • 1 follower

Comments