Saturday, April 5, 2014

The simplest Arduino LCD Clock

I wrote up this post in response to the many inquiries I get about Arduino school projects.

Here I describe how to build the most basic (and probably the lowest cost as well) Arduino clock, using a real time clock (RTC) chip and a 1602 LCD display (2 lines of 16 characters). Unlike most of the LCD clocks out there (the only notable exception being Kevin Rye's LCD clock), this one is also encased and ready to be used in real life. Actually, the build starts with the enclosure (the very affordable adafruit LCD stand), which drives the requirements for the rest of the components.

Note: This LCD clock does not have any buttons. The time and date are set with the serial monitor (or Hyperterminal or any other serial console), by sending specific commands. I was thinking of adding the Bluetooth module, but then the process would be the same (or maybe even more complex, if you consider the pairing step), and it would only replace the PC with an Android phone or tablet.
Using serial monitor makes sense because the Pro Micro already has on-board USB interface, that is, one doesn't need the extra FTDI cable to communicate with the PC (also requiring attention to the connector's orientation when plugging it in).


List of materials
  • LCD stand - from adafruit;
  • 16x2 LCD - from various sources: adafruit, sparkfun, ebay;
  • Pro Micro (sparkfun's original or clones from ebay) - board compatible with Arduino Leonardo, chosen because it is the only one fitting the LCD stand, both in size and the position of the USB connector;
  • prototyping PCB - I used this, but many others can be found on ebay;
  • 10k trimpot (for adjusting the contrast on LCD);
  • DS1307 real time clock IC + 32kHz crystal - from ebay;
  • CR1220 coin battery + battery holder - from ebay;
  • two 10k resistors (pull ups for I2C lines);
  • a bunch of wires;
  • micro USB cable - from ebay;
  • 16-pin male header and 16-pin female header - from ebay;
  • USB power adapter.

Depending on the component sourcing, the cost for the whole project can vary between $20 (ebay) and $45 (adafruit, sparkfun).


Step 1
Solder the 16-pin male header onto the LCD display.

Step 2
Assemble the LCD stand including the LCD as well, selectively following the manufacturer's instructions.


Step 3
Cut a piece of prototyping PCB to match the size of the LCD. This will be used as a "backpack" (plugged into the LCD) holding the Pro Micro board, the RTC and the battery.

Step 4
Solder the female header onto the backpack in the appropriate position (so that it can be plugged into the LCD).

Step 5
Solder the small Pro Micro board on the PCB so that the micro USB connector matches the side opening of the stand, also making sure that the micro USB cable can be plugged in through that opening.
Then wire the Pro Micro to the header of the LCD display, as follows:
  • pin A1 of Pro Micro to pin 4 (RS) of LCD
  • pin A0 of Pro Micro to pin 6 (EN) of LCD
  • pin 16 of Pro Micro to pin 11 (D4) of LCD
  • pin 10 of Pro Micro to pin 12 (D5) of LCD
  • pin 15 of Pro Micro to pin 13 (D6) of LCD
  • pin 14 of Pro Micro to pin 14 (D7) of LCD
  • LCD's pins 1, 5 and 16 are wired to ground
  • LCD's pin 2 is connected to Vcc (5V)
  • LCD's pin 3 is connected to the 10k trimpot slider (middle pin), used for adjusting the contrast
  • LCD's pin 15 (backlight LED) is connected to Pro Micro's pin 9.
A good tutorial on connecting a 16x2 LCD to Arduino can be found here.

Step 6
Solder the RTC, battery holder and crystal, then connect the I2C lines (SDA and SCL) to the Pro Micro, according the the schematic below.


DS1307 talks to any microcontroller on I2C, through 2 wires named SDA and SCL. Unlike Arduino 2009 or UNO (with ATmega328) where SDA and SCL are on pins A4 and A5 respectively, Leonardo and Pro Micro (with ATmega32u4) use pins D2 and D3 as SDA and SCL.

We also need to pullup the I2C lines (SDA and SCL), by connecting D2 and D3 to Vcc through the 10k resistors. The final result is shown in the photo below.


I used the SMD version of DS1307, soldered on the PCB's plated side (see photo below). Also note the trimpot, soldered on the same side for accessibility.


Step 7
Familiarize yourself with the Pro Micro (or the equivalent Arduino Leonardo). Read about how to install the board's driver, if this is the first time you use it.

Step 8
Download the sample sketch, compile (in Arduino IDE 1.04 or later, after you made sure that "Arduino Leonardo" is selected as the current board), then upload.

Step 9
Open the serial monitor and set the current time by sending the command TIME=hh:mm:ss, where hh is the actual hour and mm is the actual minute (e.g. TIME=08:25:00 or TIME=15:42:38).
As well, set the date by sending the command DATE=yy/mm/dd, e.g. DATE=14/04/03.

Step 10
Disconnect the USB cable from the computer and power the clock from the USB power adapter.
The time and date are displayed as shown in the photo below.



Troubleshooting
  • If the LCD does not show anything:
    • adjust the trimpot for contrast;
    • check the wiring, by making sure the connections are right, according to the list in step 5;
    • check that there are no shorts between pins.
  • If the date and/or time show 0:
    • check that the DS1307 is connected correctly to pins D2 and D3 and to the rest of components, according to the schematic in step 6;
    • check that the backup battery provides 3V and is inserted properly.

11 comments:

  1. Simple, elegant, functional...

    Nice work

    ReplyDelete
  2. Hello,
    Do you have the skecth ?
    Thanks,
    Regards,
    Maxime

    ReplyDelete
    Replies
    1. I updated step 8 with the link to the sketch:
      https://drive.google.com/file/d/0B01cjIbSk11NaUdQcUtQTFdfZEk/view?usp=sharing

      Delete
  3. How can it be done without the time chip?

    ReplyDelete
    Replies
    1. Without RTC (which is $1 or so), the ATmega328 will have to keep the time. But it's not that accurate, and once power is disconnected, the time is lost.

      Delete
  4. hello sir
    where i can download that DS1307.h header file?
    i downloaded some ds1307 lib but when i want to verify the sketch,some errors occured that tells some functions did not declared.

    ReplyDelete
    Replies
    1. Here it is:

      /*
      DS1307.h - library for DS1307 rtc
      Created by matt.joyce@gmail.com, December, 2007.
      Released into the public domain.
      */

      // ensure this library description is only included once
      #ifndef _DS1307_H_
      #define _DS1307_H_


      // include types & constants of Wiring core API
      #include

      // include types & constants of Wire ic2 lib
      #include <../Wire/Wire.h>

      #define DS1307_SEC 0
      #define DS1307_MIN 1
      #define DS1307_HR 2
      #define DS1307_DOW 3
      #define DS1307_DATE 4
      #define DS1307_MTH 5
      #define DS1307_YR 6

      #define DS1307_BASE_YR 2000

      #define DS1307_CTRL_ID B1101000 //DS1307

      // Define register bit masks
      #define DS1307_CLOCKHALT B10000000

      #define DS1307_LO_BCD B00001111
      #define DS1307_HI_BCD B11110000

      #define DS1307_HI_SEC B01110000
      #define DS1307_HI_MIN B01110000
      #define DS1307_HI_HR B00110000
      #define DS1307_LO_DOW B00000111
      #define DS1307_HI_DATE B00110000
      #define DS1307_HI_MTH B00110000
      #define DS1307_HI_YR B11110000

      #define DS1307_DATASTART 0x08

      // library interface description
      class DS1307
      {
      // user-accessible "public" interface
      public:
      DS1307();
      void get(int *, boolean);
      int get(int, boolean);
      int min_of_day(boolean);
      void set(int, int);
      void start(void);
      void stop(void);
      void get_sram_data(byte *);
      void set_sram_data(byte *);
      byte get_sram_byte(int);
      void set_sram_byte(byte, int);
      byte enableSQW();
      // library-accessible "private" interface
      private:
      byte rtc_bcd[7]; // used prior to read/set ds1307 registers;
      void read_rtc(void);
      void save_rtc(void);
      };


      extern DS1307 RTC_DS1307;

      #endif // _DS1307_H_


      Delete
    2. And the DS1307.cpp, part 1:


      /*
      DS1307.cpp - library for DS1307 rtc
      Created by matt.joyce@gmail.com, December, 2007.
      Released into the public domain.
      */


      #include "DS1307.h"
      #include


      DS1307::DS1307()
      {
      Wire.begin();
      }


      // Aquire data from the RTC chip in BCD format
      // refresh the buffer
      void DS1307::read_rtc(void)
      {
      // use the Wire lib to connect to tho rtc
      // reset the register pointer to zero
      Wire.beginTransmission(DS1307_CTRL_ID);
      Wire.write(0x00);
      Wire.endTransmission();

      // request the 7 bytes of data (secs, min, hr, dow, date. mth, yr)
      Wire.requestFrom(DS1307_CTRL_ID, 7);
      for(int i=0; i<7; i++)
      {
      // store data in raw bcd format
      rtc_bcd[i]=Wire.read();
      }
      }

      // update the data on the IC from the bcd formatted data in the buffer
      void DS1307::save_rtc(void)
      {
      Wire.beginTransmission(DS1307_CTRL_ID);
      Wire.write(0x00); // reset register pointer
      for(int i=0; i < 7; i++)
      {
      Wire.write(rtc_bcd[i]);
      }
      Wire.endTransmission();
      }


      // PUBLIC FUNCTIONS
      void DS1307::get(int *rtc, boolean refresh) // Aquire data from buffer and convert to int, refresh buffer if required
      {
      if(refresh) read_rtc();
      for(int i=0;i<7;i++) // cycle through each component, create array of data
      {
      rtc[i]=get(i, 0);
      }
      }

      int DS1307::get(int c, boolean refresh) // aquire individual RTC item from buffer, return as int, refresh buffer if required
      {
      if(refresh) read_rtc();
      int v=-1;
      switch(c)
      {
      case DS1307_SEC:
      v=(10*((rtc_bcd[DS1307_SEC] & DS1307_HI_SEC)>>4))+(rtc_bcd[DS1307_SEC] & DS1307_LO_BCD);
      break;
      case DS1307_MIN:
      v=(10*((rtc_bcd[DS1307_MIN] & DS1307_HI_MIN)>>4))+(rtc_bcd[DS1307_MIN] & DS1307_LO_BCD);
      break;
      case DS1307_HR:
      v=(10*((rtc_bcd[DS1307_HR] & DS1307_HI_HR)>>4))+(rtc_bcd[DS1307_HR] & DS1307_LO_BCD);
      break;
      case DS1307_DOW:
      v=rtc_bcd[DS1307_DOW] & DS1307_LO_DOW;
      break;
      case DS1307_DATE:
      v=(10*((rtc_bcd[DS1307_DATE] & DS1307_HI_DATE)>>4))+(rtc_bcd[DS1307_DATE] & DS1307_LO_BCD);
      break;
      case DS1307_MTH:
      v=(10*((rtc_bcd[DS1307_MTH] & DS1307_HI_MTH)>>4))+(rtc_bcd[DS1307_MTH] & DS1307_LO_BCD);
      break;
      case DS1307_YR:
      v=(10*((rtc_bcd[DS1307_YR] & DS1307_HI_YR)>>4))+(rtc_bcd[DS1307_YR] & DS1307_LO_BCD)+DS1307_BASE_YR;
      break;
      } // end switch
      return v;
      }

      void DS1307::set(int c, int v) // Update buffer, then update the chip
      {
      switch(c)
      {
      case DS1307_SEC:
      if(v<60 && v>-1)
      {
      //preserve existing clock state (running/stopped)
      int state=rtc_bcd[DS1307_SEC] & DS1307_CLOCKHALT;
      rtc_bcd[DS1307_SEC]=state | (((v / 10)<<4) + (v % 10));
      }
      break;
      case DS1307_MIN:
      if(v<60 && v>-1)
      {
      rtc_bcd[DS1307_MIN]=((v / 10)<<4) + (v % 10);
      }
      break;
      case DS1307_HR:
      // TODO : AM/PM 12HR/24HR
      if(v<24 && v>-1)
      {
      rtc_bcd[DS1307_HR]=((v / 10)<<4) + (v % 10);
      }
      break;
      case DS1307_DOW:
      if(v<8 && v>-1)
      {
      rtc_bcd[DS1307_DOW]=v;
      }
      break;
      case DS1307_DATE:
      if(v<32 && v>-1)
      {
      rtc_bcd[DS1307_DATE]=((v / 10)<<4) + (v % 10);
      }
      break;
      case DS1307_MTH:
      if(v<13 && v>-1)
      {
      rtc_bcd[DS1307_MTH]=((v / 10)<<4) + (v % 10);
      }
      break;
      case DS1307_YR:
      if(v<99 && v>-1)
      {
      rtc_bcd[DS1307_YR]=((v / 10)<<4) + (v % 10);
      }
      break;
      } // end switch
      save_rtc();
      }

      Delete
    3. DS1307.cpp part 2:


      int DS1307::min_of_day(boolean refresh)
      {
      // return minutes of day (0-1440)
      if(refresh) read_rtc();
      int MoD=(get(DS1307_HR,false)*60)+get(DS1307_MIN,false);
      return MoD;
      }

      void DS1307::stop(void)
      {
      // set the ClockHalt bit high to stop the rtc
      // this bit is part of the seconds byte
      rtc_bcd[DS1307_SEC]=rtc_bcd[DS1307_SEC] | DS1307_CLOCKHALT;
      save_rtc();
      }

      void DS1307::start(void)
      {
      // unset the ClockHalt bit to start the rtc
      // TODO : preserve existing seconds
      rtc_bcd[DS1307_SEC]=0;
      save_rtc();
      }

      void DS1307::get_sram_data(byte *sram_data)
      {
      // set the register to the sram area and read 56 bytes
      Wire.beginTransmission(DS1307_CTRL_ID);
      Wire.write(DS1307_DATASTART);
      Wire.endTransmission();

      for(int i=0;i<56;i++)
      {
      Wire.requestFrom(DS1307_CTRL_ID, 56);
      sram_data[i]=Wire.read();
      }
      }

      void DS1307::set_sram_data(byte *sram_data)
      {
      // set the register to the sram area and save 56 bytes
      Wire.beginTransmission(DS1307_CTRL_ID);
      Wire.write(DS1307_DATASTART);

      for(int i=0;i<56;i++)
      {
      Wire.write(sram_data[i]);
      }
      Wire.endTransmission();
      }

      byte DS1307::get_sram_byte(int p)
      {
      // set the register to a specific the sram location and read a single byte
      Wire.beginTransmission(DS1307_CTRL_ID);
      Wire.write(DS1307_DATASTART+p);
      Wire.endTransmission();
      Wire.requestFrom(DS1307_CTRL_ID, 1);
      return Wire.read();
      }

      void DS1307::set_sram_byte(byte b, int p)
      {
      // set the register to a specific the sram location and save a single byte
      Wire.beginTransmission(DS1307_CTRL_ID);
      Wire.write(DS1307_DATASTART+p);
      Wire.write(b);
      Wire.endTransmission();
      }


      //******************************************************************************************
      byte DS1307::enableSQW()
      {
      Wire.beginTransmission(DS1307_CTRL_ID);
      Wire.write((uint8_t) 0x0E); // control register
      Wire.endTransmission();
      Wire.requestFrom(DS1307_CTRL_ID, 1);

      byte ctrl;
      if(Wire.available())
      {
      ctrl = Wire.read();
      }

      Wire.beginTransmission(DS1307_CTRL_ID);
      Wire.write((uint8_t) 0x0E); // control register
      Wire.write((uint8_t) (ctrl & B11100011)); // all bits 0;
      Wire.endTransmission();

      return ctrl;
      }



      DS1307 RTC_DS1307;

      Delete
    4. Cut and concatenate the 2 parts into DS1307.cpp file.
      (The content of DS1307.cpp is longer the 4096 chars allowed for one comment.)

      Delete