Mirko Pavleski
Published © GPL3+

LCD Alarm Clock and Thermometer Controlled by IR Remote

How to build an Arduino-based big LCD clock with two alarms and temperature monitor controlled by IR TV remote.

BeginnerFull instructions provided7,860
LCD Alarm Clock and Thermometer Controlled by IR Remote

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
20x4 I2C LCD display
×1
DS3231M - ±5ppm, I2C Real-Time Clock
Maxim Integrated DS3231M - ±5ppm, I2C Real-Time Clock
×1
IR receiver (generic)
×1
LED (generic)
LED (generic)
×1
Buzzer
Buzzer
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

schematic diagram

Code

Clock Code

C/C++
/* Arduino real time clock and calendar with 2 alarm functions, temperature monitor and RC-5 IR remote control
   Read DS3231 RTC datasheet to understand the code
   DS3231 interrupt pin is connected to Arduino external interrupt pin (pin #2).
*/
 
// Define number of Timer1 ticks (with a prescaler of 1/8)
#define short_time     1400                      // Used as a minimum time for short pulse or short space ( ==>  700 us)
#define   med_time     2400                      // Used as a maximum time for short pulse or short space ( ==> 1200 us)
#define  long_time     4000                      // Used as a maximum time for long pulse or long space   ( ==> 2000 us)
 
// include LCD library code
#include <LiquidCrystal_I2C.h>
// include Wire library code (needed for I2C protocol devices)
#include <Wire.h>
 
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 20, 4);
 
const int alarm_pin = 12;                     // Alarms pin number
const int buzzer = 13;
void setup() {
  pinMode(alarm_pin, OUTPUT);
  pinMode(buzzer, OUTPUT);
  digitalWrite(alarm_pin, LOW);
  lcd.begin();
  lcd.backlight(); 
  // Timer1 module configuration
  TCCR1A = 0;
  TCCR1B = 0;                                    // Disable Timer1 module
  TCNT1  = 0;                                    // Set Timer1 preload value to 0 (reset)
  TIMSK1 = 1;                                    // enable Timer1 overflow interrupt
  Wire.begin();                                  // Join i2c bus
  attachInterrupt(1, RC5_read, CHANGE);          // Enable external interrupt (INT1)
  attachInterrupt(0, Alarm, FALLING);            // Enable external interrupt (INT0)
}
// RC5 remote control variables
boolean rc5_ok = 0, toggle, last_toggle;
byte rc5_state = 0, j;
unsigned int rc5_code;
// RTC Variables declaration
bool alarm1_status, alarm2_status;
char Time[]     = "  :  :  ",
     calendar[] = "      /  /20  ",
     alarm1[]   = "A1:   :  :00", alarm2[]   = "A2:   :  :00",
     temperature[] = "T:   .   C";
byte  i, second, minute, hour, day, date, month, year,
      alarm1_minute, alarm1_hour, alarm2_minute, alarm2_hour,
      status_reg;
 
void RC5_read() {
unsigned int timer_value;
  if(rc5_state != 0){
    timer_value = TCNT1;                         // Store Timer1 value
    TCNT1 = 0;                                   // Reset Timer1
  }
  switch(rc5_state){
   case 0 :                                      // Start receiving IR data (initially we're at the beginning of mid1)
    TCNT1  = 0;                                  // Reset Timer1
    TCCR1B = 2;                                  // Enable Timer1 module with 1/8 prescaler ( 2 ticks every 1 us)
    rc5_state = 1;                               // Next state: end of mid1
    j = 0;
    return;
   case 1 :                                      // End of mid1 ==> check if we're at the beginning of start1 or mid0
    if((timer_value > long_time) || (timer_value < short_time)){         // Invalid interval ==> stop decoding and reset
      rc5_state = 0;                             // Reset decoding process
      TCCR1B = 0;                                // Disable Timer1 module
      return;
    }
    bitSet(rc5_code, 13 - j);
    j++;
    if(j > 13){                                  // If all bits are received
      rc5_ok = 1;                                // Decoding process is OK
      toggle = bitRead(rc5_code, 11);            // Toggle bit is bit number 11
      rc5_code &= 0x07FF;                        // Remove the two start bits and the toggle bit from the code message
      detachInterrupt(1);                        // Disable external interrupt (INT1)
      return;
    }
      if(timer_value > med_time){                // We're at the beginning of mid0
        rc5_state = 2;                           // Next state: end of mid0
        if(j == 13){                             // If we're at the LSB bit
          rc5_ok = 1;                            // Decoding process is OK
          bitClear(rc5_code, 0);                 // Clear the LSB bit
          toggle = bitRead(rc5_code, 11);        // Toggle bit is bit number 11
          rc5_code &= 0x07FF;
          detachInterrupt(1);                    // Disable external interrupt (INT1)
          return;
        }
      }
      else                                       // We're at the beginning of start1
        rc5_state = 3;                           // Next state: end of start1
      return;
   case 2 :                                      // End of mid0 ==> check if we're at the beginning of start0 or mid1
    if((timer_value > long_time) || (timer_value < short_time)){
      rc5_state = 0;                             // Reset decoding process
      TCCR1B = 0;                                // Disable Timer1 module
      return;
    }
    bitClear(rc5_code, 13 - j);
    j++;
    if(timer_value > med_time)                   // We're at the beginning of mid1
      rc5_state = 1;                             // Next state: end of mid1
    else                                         // We're at the beginning of start0
      rc5_state = 4;                             // Next state: end of start0
    return;
   case 3 :                                      // End of start1 ==> check if we're at the beginning of mid1
    if((timer_value > med_time) || (timer_value < short_time)){           // Time interval invalid ==> stop decoding
      TCCR1B = 0;                                // Disable Timer1 module
      rc5_state = 0;                             // Reset decoding process
      return;
    }
    else                                         // We're at the beginning of mid1
      rc5_state = 1;                             // Next state: end of mid1
    return;
   case 4 :                                      // End of start0 ==> check if we're at the beginning of mid0
    if((timer_value > med_time) || (timer_value < short_time)){           // Time interval invalid ==> stop decoding
      TCCR1B = 0;                                // Disable Timer1 module
      rc5_state = 0;                             // Reset decoding process
      return;
    }
    else                                         // We're at the beginning of mid0
      rc5_state = 2;                             // Next state: end of mid0
    if(j == 13){                                 // If we're at the LSB bit
      rc5_ok = 1;                                // Decoding process is OK
      bitClear(rc5_code, 0);                     // Clear the LSB bit
      toggle = bitRead(rc5_code, 11);            // Toggle bit is bit number 11
      rc5_code &= 0x07FF;
      detachInterrupt(1);                        // Disable external interrupt (INT1)
    }
  }
}
ISR(TIMER1_OVF_vect) {                           // Timer1 interrupt service routine (ISR)
  rc5_state = 0;                                 // Reset decoding process
  TCCR1B = 0;                                    // Disable Timer1 module
}
 
void Alarm(){
  digitalWrite(alarm_pin, HIGH);
  tone(buzzer, 1000);
}
void DS3231_read(){                             // Function to read time & calendar data
  Wire.beginTransmission(0x68);                 // Start I2C protocol with DS3231 address
  Wire.write(0);                                // Send register address
  Wire.endTransmission(false);                  // I2C restart
  Wire.requestFrom(0x68, 7);                    // Request 7 bytes from DS3231 and release I2C bus at end of reading
  second = Wire.read();                         // Read seconds from register 0
  minute = Wire.read();                         // Read minuts from register 1
  hour   = Wire.read();                         // Read hour from register 2
  day    = Wire.read();                         // Read day from register 3
  date   = Wire.read();                         // Read date from register 4
  month  = Wire.read();                         // Read month from register 5
  year   = Wire.read();                         // Read year from register 6
}
void alarms_read_display(){                     // Function to read and display alarm1, alarm2 and temperature data
  byte control_reg, temperature_lsb;
  char temperature_msb;
  Wire.beginTransmission(0x68);                 // Start I2C protocol with DS3231 address
  Wire.write(0x08);                             // Send register address
  Wire.endTransmission(false);                  // I2C restart
  Wire.requestFrom(0x68, 11);                   // Request 11 bytes from DS3231 and release I2C bus at end of reading
  alarm1_minute = Wire.read();                  // Read alarm1 minutes
  alarm1_hour   = Wire.read();                  // Read alarm1 hours
  Wire.read();                                  // Skip alarm1 day/date register
  alarm2_minute = Wire.read();                  // Read alarm2 minutes
  alarm2_hour   = Wire.read();                  // Read alarm2 hours
  Wire.read();                                  // Skip alarm2 day/date register
  control_reg = Wire.read();                    // Read the DS3231 control register
  status_reg  = Wire.read();                    // Read the DS3231 status register
  Wire.read();                                  // Skip aging offset register
  temperature_msb = Wire.read();                // Read temperature MSB
  temperature_lsb = Wire.read();                // Read temperature LSB
    // Convert BCD to decimal
  alarm1_minute = (alarm1_minute >> 4) * 10 + (alarm1_minute & 0x0F);
  alarm1_hour   = (alarm1_hour   >> 4) * 10 + (alarm1_hour & 0x0F);
  alarm2_minute = (alarm2_minute >> 4) * 10 + (alarm2_minute & 0x0F);
  alarm2_hour   = (alarm2_hour   >> 4) * 10 + (alarm2_hour & 0x0F);
    // End conversion
  alarm1[8]     = alarm1_minute % 10  + 48;
  alarm1[7]     = alarm1_minute / 10  + 48;
  alarm1[5]     = alarm1_hour   % 10  + 48;
  alarm1[4]     = alarm1_hour   / 10  + 48;
  alarm2[8]     = alarm2_minute % 10  + 48;
  alarm2[7]     = alarm2_minute / 10  + 48;
  alarm2[5]     = alarm2_hour   % 10  + 48;
  alarm2[4]     = alarm2_hour   / 10  + 48;
  alarm1_status = bitRead(control_reg, 0);      // Read alarm1 interrupt enable bit (A1IE) from DS3231 control register
  alarm2_status = bitRead(control_reg, 1);      // Read alarm2 interrupt enable bit (A2IE) from DS3231 control register
  if(temperature_msb < 0){
    temperature_msb = abs(temperature_msb);
    temperature[2] = '-';
  }
  else
    temperature[2] = ' ';
  temperature_lsb >>= 6;
  temperature[4] = temperature_msb % 10  + 48;
  temperature[3] = temperature_msb / 10  + 48;
  if(temperature_lsb == 0 || temperature_lsb == 2){
    temperature[7] = '0';
    if(temperature_lsb == 0) temperature[6] = '0';
    else                     temperature[6] = '5';
  }
  if(temperature_lsb == 1 || temperature_lsb == 3){
    temperature[7] = '5';
    if(temperature_lsb == 1) temperature[6] = '2';
    else                     temperature[6] = '7';
  }
  temperature[8]  = 223;                        // Put the degree symbol
  lcd.setCursor(10, 0);
  lcd.print(temperature);                       // Display temperature
  lcd.setCursor(0, 2);
  lcd.print(alarm1);                            // Display alarm1
  lcd.setCursor(17, 2);
  if(alarm1_status)  lcd.print("ON ");          // If A1IE = 1 print 'ON'
  else               lcd.print("OFF");          // If A1IE = 0 print 'OFF'
  lcd.setCursor(0, 3);
  lcd.print(alarm2);                            // Display alarm2
  lcd.setCursor(17, 3);
  if(alarm2_status)  lcd.print("ON ");          // If A2IE = 1 print 'ON'
  else               lcd.print("OFF");          // If A2IE = 0 print 'OFF'
}
void calendar_display(){                        // Function to display calendar
  switch(day){
    case 1:  strcpy(calendar, "Sun   /  /20  "); break;
    case 2:  strcpy(calendar, "Mon   /  /20  "); break;
    case 3:  strcpy(calendar, "Tue   /  /20  "); break;
    case 4:  strcpy(calendar, "Wed   /  /20  "); break;
    case 5:  strcpy(calendar, "Thu   /  /20  "); break;
    case 6:  strcpy(calendar, "Fri   /  /20  "); break;
    case 7:  strcpy(calendar, "Sat   /  /20  "); break;
    default: strcpy(calendar, "Sat   /  /20  ");
  }
  calendar[13] = year  % 10 + 48;
  calendar[12] = year  / 10 + 48;
  calendar[8]  = month % 10 + 48;
  calendar[7]  = month / 10 + 48;
  calendar[5]  = date  % 10 + 48;
  calendar[4]  = date  / 10 + 48;
  lcd.setCursor(0, 1);
  lcd.print(calendar);                          // Display calendar
}
void DS3231_display(){
  // Convert BCD to decimal
  second = (second >> 4) * 10 + (second & 0x0F);
  minute = (minute >> 4) * 10 + (minute & 0x0F);
  hour = (hour >> 4) * 10 + (hour & 0x0F);
  date = (date >> 4) * 10 + (date & 0x0F);
  month = (month >> 4) * 10 + (month & 0x0F);
  year = (year >> 4) * 10 + (year & 0x0F);
  // End conversion
  Time[7]     = second % 10  + 48;
  Time[6]     = second / 10  + 48;
  Time[4]     = minute % 10  + 48;
  Time[3]     = minute / 10  + 48;
  Time[1]     = hour   % 10  + 48;
  Time[0]     = hour   / 10  + 48;
  calendar_display();                           // Call calendar display function
  lcd.setCursor(0, 0);
  lcd.print(Time);                              // Display time
}
void Blink(){
  byte k = 0;
  while((!rc5_ok || (rc5_code != 0x10) && (rc5_code != 0x11) && (rc5_code != 0x20) && (rc5_code != 0x21)) && (k < 10)){
    k++;
    delay(25);
  }
}
byte edit(byte x, byte y, byte parameter){
  char text[3];
  rc5_reset();
  while(true){
    if(rc5_ok && (rc5_code == 0x20 || rc5_code == 0x21)){     // If RC5 code received
      if(rc5_code == 0x20){
        parameter++;
        if(((i == 0) || (i == 5)) && parameter > 23)          // If hours > 23 ==> hours = 0
          parameter = 0;
        if(((i == 1) || (i == 6)) && parameter > 59)          // If minutes > 59 ==> minutes = 0
          parameter = 0;
        if(i == 2 && parameter > 31)                          // If date > 31 ==> date = 1
          parameter = 1;
        if(i == 3 && parameter > 12)                          // If month > 12 ==> month = 1
          parameter = 1;
        if(i == 4 && parameter > 99)                          // If year > 99 ==> year = 0
          parameter = 0;
        if(i == 7 && parameter > 1)                           // For alarms ON or OFF (1: alarm ON, 0: alarm OFF)
          parameter = 0;
      }
      if(rc5_code == 0x21){
        if(((i == 0) || (i == 5)) && parameter < 1)
          parameter = 24;
        if(((i == 1) || (i == 6)) && parameter < 1)
          parameter = 60;
        if(i == 2 && parameter < 2)
          parameter = 32;
        if(i == 3 && parameter < 2)
          parameter = 13;
        if(i == 4 && parameter < 1)
          parameter = 100;
        if(i == 7 && parameter < 1)
          parameter = 2;
        parameter--;
      }
      lcd.setCursor(x, y);
      if(i == 7){                                           // For alarms ON & OFF
        if(parameter == 1)  lcd.print("ON ");
        else                lcd.print("OFF");
      }
      else{
        sprintf(text,"%02u", parameter);
        lcd.print(text);
      }
    }
    if(rc5_ok){
      delay(200);
      rc5_reset();
    }
    lcd.setCursor(x, y);
    lcd.print("  ");                            // Print two spaces
    if(i == 7) lcd.print(" ");                  // Print space (for alarms ON & OFF)
    Blink();                                    // Call Blink function
    lcd.setCursor(x, y);
    if(i == 7){                                 // For alarms ON & OFF
      if(parameter == 1)  lcd.print("ON ");
      else                lcd.print("OFF");
    }
    else{
      sprintf(text,"%02u", parameter);
      lcd.print(text);
    }
    Blink();
    if(i >= 5){
      DS3231_read();
      DS3231_display();
    }
    if(rc5_ok && last_toggle != toggle && ((rc5_code == 0x10 && i < 5) || (rc5_code == 0x11 && i >= 5))){
      i++;                                     // Increment 'i' for the next parameter
      return parameter;                         // Return parameter value and exit
    }
  }
}
void rc5_reset(){
  rc5_ok = 0;                                    // Reset decoding process
  rc5_state = 0;
  last_toggle = toggle;                          // Save toggle bit
  attachInterrupt(1, RC5_read, CHANGE);          // Enable external interrupt (INT1)
}
 
void loop() {
  if(rc5_ok){                                   // If RC5 code received
    if(rc5_code == 0x10){                       // If clock/calendar set button code received
      i = 0;
      hour   = edit(0, 0, hour);
      minute = edit(3, 0, minute);
      rc5_reset();
      while(true){
        if(rc5_ok && (rc5_code == 0x20)){       // If button up button code received
          day++;                                // Increment day
          if(day > 7) day = 1;
          calendar_display();                   // Call display_calendar function
          lcd.setCursor(0, 1);
          lcd.print(calendar);                  // Display calendar
        }
        if(rc5_ok){
          delay(200);
          rc5_reset();
        }
        lcd.setCursor(0, 1);
        lcd.print("   ");                       // Print 3 spaces
        Blink();
        lcd.setCursor(0, 1);
        lcd.print(calendar);                    // Print calendar
        Blink();                                // Call Blink function
        if(rc5_ok && last_toggle != toggle && (rc5_code == 0x10))               // If button B1 is pressed
         break;
      }
      date = edit(4, 1, date);                  // Edit date
      month = edit(7, 1, month);                // Edit month
      year = edit(12, 1, year);                 // Edit year
      // Convert decimal to BCD
      minute = ((minute / 10) << 4) + (minute % 10);
      hour = ((hour / 10) << 4) + (hour % 10);
      date = ((date / 10) << 4) + (date % 10);
      month = ((month / 10) << 4) + (month % 10);
      year = ((year / 10) << 4) + (year % 10);
      // End conversion
      // Write time & calendar data to DS3231 RTC
      Wire.beginTransmission(0x68);             // Start I2C protocol with DS3231 address
      Wire.write(0);                            // Send register address
      Wire.write(0);                            // Reset sesonds and start oscillator
      Wire.write(minute);                       // Write minute
      Wire.write(hour);                         // Write hour
      Wire.write(day);                          // Write day
      Wire.write(date);                         // Write date
      Wire.write(month);                      +  // Write month
      Wire.write(year);                         // Write year
      Wire.endTransmission();                   // Stop transmission and release the I2C bus
    }
    if(rc5_code == 0x11){                       // If alarm set button code received
      i = 5;
      alarm1_hour   = edit(4,  2, alarm1_hour);
      alarm1_minute = edit(7,  2, alarm1_minute);
      alarm1_status = edit(17, 2, alarm1_status);
      i = 5;
      alarm2_hour   = edit(4,  3, alarm2_hour);
      alarm2_minute = edit(7,  3, alarm2_minute);
      alarm2_status = edit(17, 3, alarm2_status);
      alarm1_minute = ((alarm1_minute / 10) << 4) + (alarm1_minute % 10);
      alarm1_hour   = ((alarm1_hour   / 10) << 4) + (alarm1_hour % 10);
      alarm2_minute = ((alarm2_minute / 10) << 4) + (alarm2_minute % 10);
      alarm2_hour   = ((alarm2_hour   / 10) << 4) + (alarm2_hour % 10);
      // Write alarms data to DS3231
      Wire.beginTransmission(0x68);               // Start I2C protocol with DS3231 address
      Wire.write(7);                              // Send register address (alarm1 seconds)
      Wire.write(0);                              // Write 0 to alarm1 seconds
      Wire.write(alarm1_minute);                  // Write alarm1 minutes value to DS3231
      Wire.write(alarm1_hour);                    // Write alarm1 hours value to DS3231
      Wire.write(0x80);                           // Alarm1 when hours, minutes, and seconds match
      Wire.write(alarm2_minute);                  // Write alarm2 minutes value to DS3231
      Wire.write(alarm2_hour);                    // Write alarm2 hours value to DS3231
      Wire.write(0x80);                           // Alarm2 when hours and minutes match
      Wire.write(4 | alarm1_status | (alarm2_status << 1));      // Write data to DS3231 control register (enable interrupt when alarm)
      Wire.write(0);                              // Clear alarm flag bits
      Wire.endTransmission();                     // Stop transmission and release the I2C bus
    }
    if(rc5_code == 0x0C && digitalRead(alarm_pin)){         // If rset alarm button code is received with alarm (Reset and turn OFF the alarm)
      digitalWrite(alarm_pin, LOW);    // Turn OFF the alarm indicator
      noTone(buzzer);
    
      Wire.beginTransmission(0x68);               // Start I2C protocol with DS3231 address
      Wire.write(0x0E);                           // Send register address (control register)
      // Write data to control register (Turn OFF the occurred alarm and keep the other as it is)
      Wire.write(4 | (!bitRead(status_reg, 0) & alarm1_status) | ((!bitRead(status_reg, 1) & alarm2_status) << 1));
      Wire.write(0);                              // Clear alarm flag bits
      Wire.endTransmission();                     // Stop transmission and release the I2C bus
    }
    rc5_reset();
  }
  DS3231_read();                                // Read time and calendar parameters from DS3231 RTC
  alarms_read_display();                        // Read and display alarms parameters
  DS3231_display();                             // Display time & calendar
  delay(50);                                    // Wait 50ms
}
// End of code

IR Protocol finder Code

C/C++
#include <IRremote.h>

const int RECV_PIN = 3;
IRrecv irrecv(RECV_PIN);
decode_results results;

void setup(){
  Serial.begin(9600);
  irrecv.enableIRIn();
  irrecv.blink13(true);
}

void loop(){
  if (irrecv.decode(&results)){
        Serial.println(results.value, HEX);
        switch (results.decode_type){
            case NEC: Serial.println("NEC"); break ;
            case SONY: Serial.println("SONY"); break ;
            case RC5: Serial.println("RC5"); break ;
            case RC6: Serial.println("RC6"); break ;
            case DISH: Serial.println("DISH"); break ;
            case SHARP: Serial.println("SHARP"); break ;
            case JVC: Serial.println("JVC"); break ;
            case SANYO: Serial.println("SANYO"); break ;
            case MITSUBISHI: Serial.println("MITSUBISHI"); break ;
            case SAMSUNG: Serial.println("SAMSUNG"); break ;
            case LG: Serial.println("LG"); break ;
            case WHYNTER: Serial.println("WHYNTER"); break ;
            case AIWA_RC_T501: Serial.println("AIWA_RC_T501"); break ;
            case PANASONIC: Serial.println("PANASONIC"); break ;
            case DENON: Serial.println("DENON"); break ;
          default:
            case UNKNOWN: Serial.println("UNKNOWN"); break ;
          }
        irrecv.resume();
 }
}

library

C/C++
No preview (download only).

Credits

Mirko Pavleski
172 projects • 1391 followers
Contact

Comments

Please log in or sign up to comment.