Saturday, January 5, 2013

Arduino Uno and DS1307 real time clock.


I must admit the Arduino stuff pulled me in recently and distracted me from my main project that I want to finish a.s.a.p. - the MKHBC-8-R1 home-brew computer. But I am only a nerdy human. I could not help myself.

Here I'd like share with hobbyists community a simple project that can be used as a base to learn Arduino programming - the electronic clock with RTC (Real Time Clock) module and LCD/keypad shield.


Hardware:

1) MINI Arduino I2C RTC DS1307 AT24C32 module.

        eBay item# 180646747674 by seller: e_goto
        Serial: SKU 00100-049

2) Keypad Shield 1602 LCD For Arduino MEGA 2560 1280 UNO R3 A005

       eBay item# 261039184894 by seller: womarts

The LCD/keypad shield you just put on top of the Arduino Uno compatible board. The RTC module is connected as outlined below:

RTC module -> Arduino Uno

SCL -> A5
SDA -> A4
VCC -> A3
GND -> A2

Note: the power to RTC (i2c_ds1307_at24c32) module is provided via Arduino's A2, A3 pins. The program code ensures proper logic levels/polarity on these pins so the RTC module gets the power through them.

Keypad functions:

SELECT - Switch between Clock Set/Clock Run modes. Select the date/time
             parameter to setup in the Clock Set mode.

LEFT - Immediately exit Clock Set mode and set the clock.

UP/DOWN - Increment/decrement selected date/time parameter.

The code:



/*
 * Date and time functions using a DS1307 RTC connected via I2C
 * and Wire lib
 * It is a simple clock application.
 * Author: Marek Karcz 2013. All rights reserved.
 * License: Freeware.
 * Disclaimer: Use at your own risk.
 * Hardware:
 *    1) MINI Arduino I2C RTC DS1307 AT24C32 module.
 *       eBay item# 180646747674 by seller: e_goto
 *       Serial: SKU 00100-049
 *    2) Keypad Shield 1602 LCD For Arduino MEGA 2560 1280 UNO 
 *       R3 A005
 *       eBay item# 261039184894 by seller: womarts
 */

/*
  The LCD circuit:
 * LCD RS pin to digital pin 8
 * LCD Enable pin to digital pin 9
 * LCD D4 pin to digital pin 4
 * LCD D5 pin to digital pin 5
 * LCD D6 pin to digital pin 6
 * LCD D7 pin to digital pin 7
 * LCD BL pin to digital pin 10
 * KEY pin to analog pin 0
 */

#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal.h>

#define LOOP_DELAY  2000

LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);
RTC_DS1307 RTC;
boolean bBlink = true;
const char *appVer = "MKHBC RTC 1.3   ";
const char *modTxt = "Set clock ...   ";

// Global variables for time setup/displaying purposes.
uint16_t set_Year;
uint8_t set_Month;
uint8_t set_Day;
uint8_t set_Hour;
uint8_t set_Minute;

/*
 * Keypad shield uses resistors array and single analog input.
 * The values in adc_key_val array help to determine which 
 * key on the shield was pressed by checking the analog input 
 * read value.
 */
const int adc_key_val[5] ={50, 200, 400, 600, 800 };
int adc_key_in;

// keypad keys definitions
enum KP
{
  KEY_RIGHT = 0,
  KEY_UP,
  KEY_DOWN,
  KEY_LEFT,
  KEY_SELECT,
  KEY_NUMKEYS, // mark the end of key definitions
  KEY_NONE     // definition of none of the keys pressed
};

// Finite Machine States
enum FMS
{
  RUN = 0,
  SETCLOCK,
  SETYEAR,
  SETMONTH,
  SETDAY,
  SETHOUR,
  SETMINUTE
} ClockState;

// Finite Machine State Transitions Table.
// Defines the flow of the application modes from one to another.
enum FMS StateMachine[] =
{
   /* RUN        -> */ SETCLOCK,
   /* SETCLOCK   -> */ SETYEAR,
   /* SETYEAR    -> */ SETMONTH,
   /* SETMONTH   -> */ SETDAY,
   /* SETDAY     -> */ SETHOUR,
   /* SETHOUR    -> */ SETMINUTE,
   /* SETMINUTE  -> */ RUN
};

enum KP key = KEY_NONE;

// Array of the numbers of month days.
const unsigned int month_days [] = 
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
const char *daysOfWeek[] = 
{ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };

int nDelay = 0; // controlling loops/key press latency

void setup () 
{
    nDelay = 0;
    key = KEY_NONE;
  // initialize LCD keypad module
    lcd.clear(); 
    lcd.begin(16, 2);
    lcd.setCursor(0,0); 
    lcd.print(appVer);       
  // power to i2c_ds1307_at24c32 module provided via A2, A3 pins
    pinMode(A3, OUTPUT); 
    digitalWrite(A3, HIGH);
    pinMode(A2, OUTPUT);
    digitalWrite(A2, LOW);
  // start communication, I2C and RTC
    Wire.begin();
    RTC.begin();
    
    ClockState = RUN;
    
    readKey();
 // if key SELECT is held at RESET/Start up procedure
    if (key == KEY_SELECT)
    {
        setClock(RTC.isrunning());
    }
    else if (key == KEY_LEFT) // if key LEFT is held at RESET
    {
 // following line sets the RTC to the date & time this sketch
 // was compiled
        RTC.adjust(DateTime(__DATE__, __TIME__));
    }
    else if (! RTC.isrunning()) // if battery was changed
    {
        setClock(false);
    }
 }

void loop () 
{
    readKey();
    if (key == KEY_SELECT)
    {
      ClockState = StateMachine[ClockState]; // switch mode
      delay(330);
    }
    
    switch (ClockState)
    {
       case RUN: 
          if (nDelay <= 0)  
          {
            DateTime now = RTC.now();

            dispTime(now.year(), 
                     now.month(), 
                     now.day(), 
                     now.hour(), 
                     now.minute(), 
                     now.dayOfWeek());
            bBlink = ((bBlink) ? false : true);
          }
          break;
          
       case SETCLOCK:

          nDelay = 0;
          lcd.setCursor(0,0); 
          lcd.print(modTxt);             
          setClock(RTC.isrunning());
          lcd.setCursor(0,0); 
          lcd.print(appVer);
          break;
          
       default: break;
   
    }
    if (nDelay <= 0)
      nDelay = LOOP_DELAY;
    else
      nDelay--;
}

/*
 * Functions to aid displaying the date/time.
 */
void dispYear(uint16_t yr)
{
  if (ClockState == SETYEAR)
  {
    if (bBlink)
      lcd.print(yr, DEC);
    else
      lcd.print("    ");
  }
  else
    lcd.print(yr, DEC);  
}

void dispMonth(uint8_t mo)
{
  if (ClockState == SETMONTH)
  {
    if (bBlink)
      lcd.print(mo, DEC);
    else
    {
      lcd.print(' ');
      if (mo >= 10)
        lcd.print(' ');
    }
  }
  else
    lcd.print(mo, DEC);  
}

void dispDay(uint8_t dy)
{
  if (ClockState == SETDAY)
  {
    if (bBlink)
      lcd.print(dy, DEC);
    else
    {
      lcd.print(' ');
      if (dy >= 10)
        lcd.print(' ');
    }
  }
  else
    lcd.print(dy, DEC);  
}

void dispHour(uint8_t hr)
{
  if (hr < 10)
  {
    if (ClockState == SETHOUR)
    {
      if (bBlink)
        lcd.print('0');
      else
        lcd.print(' ');
    }
    else
      lcd.print('0');
  }
  if (ClockState == SETHOUR)
  {
    if (bBlink)
      lcd.print(hr, DEC);
    else
    {
      lcd.print(' ');
      if (hr >= 10)
        lcd.print(' ');
    }
  }
  else
    lcd.print(hr, DEC);  
}

void dispMinute(uint8_t mn)
{
  if (mn < 10)
  {
    if (ClockState == SETMINUTE)
    {
      if (bBlink)
        lcd.print('0');
      else
        lcd.print(' ');
    }
    else
      lcd.print('0');
  }
  if (ClockState == SETMINUTE)
  {
    if (bBlink)
      lcd.print(mn, DEC);
    else
    {
      lcd.print(' ');
      if (mn >= 10)
        lcd.print(' ');
    }
  }
  else
    lcd.print(mn, DEC);  
}

void dispTime(uint16_t  yr,
              uint8_t   mo,
              uint8_t   dy,
              uint8_t   hr,
              uint8_t   mn,
              uint8_t   dow)
{
  lcd.setCursor(0,1);
  dispYear(yr);
  lcd.print('/');
  dispMonth(mo);
  lcd.print('/');
  dispDay(dy);
  lcd.print(' ');
  lcd.print(' ');    
  lcd.setCursor(11,1);
  dispHour(hr);
  if (ClockState == RUN)
  {
    if (bBlink)
       lcd.print(':');
    else
       lcd.print(' ');
  }
  else
    lcd.print(':');
  dispMinute(mn);
  lcd.setCursor (14, 0);
  lcd.print(daysOfWeek[dow]);
}

/*
 * Functions to aid setting date/time.
 */
void setYear(boolean incdec)
{
  if (incdec)
  {
    if (set_Year < 2100)
      set_Year++;  
  }
  else
  {
    if (set_Year > 2000)
      set_Year--;    
  }
}

void setMonth(boolean incdec)
{
  if (incdec)
  {
    if (set_Month < 12)
      set_Month++;  
  }
  else
  {
    if (set_Month > 1)
      set_Month--;    
  }
}

void setDay(boolean incdec)
{
  if (incdec)
  {
    if ((set_Month != 2 && set_Day < month_days[set_Month])
        || 
        (set_Month == 2 && set_Day < 28)
        ||
        (set_Month == 2 && set_Day == 28 && isLeapYear(set_Year))
       )
      set_Day++;   
  }
  else
  {
    if (set_Day > 1)
      set_Day--;     
  }
}

void setHour(boolean incdec)
{
  if (incdec)
  {
    if (set_Hour < 23)
      set_Hour++;
    else
      set_Hour = 0;  
  }
  else
  {
    if (set_Hour > 1)
      set_Hour--;
    else
      set_Hour = 23;    
  }
}

void setMinute(boolean incdec)
{
  if (incdec)
  {
    if (set_Minute < 59)
      set_Minute++;
    else
      set_Minute = 0;  
  }
  else
  {
    if (set_Minute > 1)
      set_Minute--;
    else
      set_Minute = 59;    
  }
}

void setDateTime(boolean incdec)
{
  if (incdec)
  {
    switch (ClockState)
    {
      case SETYEAR:
      
        setYear(true);    // increment year
        break;
       
      case SETMONTH:
     
        setMonth(true);   // increment month
        break;
       
      case SETDAY:
  
        setDay(true);     // increment day
        break;
        
      case SETHOUR:
      
        setHour(true);    // increment hour
        break;
        
      case SETMINUTE:
      
        setMinute(true);  // increment minute
        break;
        
        default: break;
       
    }    
  }
  else
  {
    switch (ClockState)
    {
      case SETYEAR:
      
        setYear(false);    // decrement year
        break;
       
      case SETMONTH:
     
        setMonth(false);   // decrement month
        break;
       
      case SETDAY:
  
        setDay(false);     // decrement day
        break;
        
      case SETHOUR:
      
        setHour(false);    // decrement hour
        break;
        
      case SETMINUTE:
      
        setMinute(false);  // decrement minute
        
        default: break;
       
    }            
  }
}

void setClock(boolean readrtc)
{
    DateTime now = DateTime(2013,1,1,0,0,0);
    
    if (readrtc)
      now = RTC.now();

    delay(500);   
    set_Year = now.year();
    set_Month = now.month();
    set_Day = now.day();
    set_Hour = now.hour();
    set_Minute = now.minute();
    
    ClockState = SETYEAR;
    
    while (ClockState >= SETCLOCK)
    {
      if (nDelay <= 0)
      {
        dispTime(set_Year, 
                 set_Month, 
                 set_Day, 
                 set_Hour, 
                 set_Minute, 
                 now.dayOfWeek());
        bBlink = ((bBlink) ? false : true);
      }

      readKey();
      if (key == KEY_UP || key == KEY_DOWN)
        bBlink = true;
      if (key == KEY_SELECT)
      {
        ClockState = StateMachine[ClockState];
        delay(330);
      }
      else 
      {
        if (nDelay <= 0)
        {
          if (key == KEY_UP)
          {
            setDateTime(true);  // increment
          }
          else if (key == KEY_DOWN)
          {
            setDateTime(false);  // decrement
          }
          else if (key == KEY_LEFT)
          {
            ClockState = RUN;    // exit set clock mode
          }
          nDelay = LOOP_DELAY;
        }
      }
      if (nDelay <= 0)
        nDelay = LOOP_DELAY;
      else
        nDelay--;
      //delay(330);
    }  
    
    RTC.adjust(DateTime(set_Year, 
                        set_Month, 
                        set_Day, 
                        set_Hour, 
                        set_Minute, 
                        0));
}

// Determine the leap year.
boolean isLeapYear(uint16_t yr)
{
  if ((yr%400)==0)
    return true;
  else if ((yr%100)==0)
    return false;
  else if ((yr%4)==0)
    return true;
   
  return false;
}

// Get key code from analog input.
unsigned int get_key(unsigned int input)
{
   unsigned int k;
   for (k = KEY_RIGHT; k < KEY_NUMKEYS; k++)
   {
      if (input < adc_key_val[k])
      {
         return k;
      }
   }
   if (k >= KEY_NUMKEYS) k = KEY_NONE; // No valid key pressed
      
   return k;
}

// Read analog input 0 to obtain key code in global variable: key
void readKey(void)
{
    key = KEY_NONE;
    adc_key_in = analogRead(0); // read the value from the sensor    
    // convert into key press  
    key = (enum KP) get_key(adc_key_in); 
}


Some pictures of the working application/hardware:




Thanks for looking.

Marek Karcz

2013/1/5