Digital Clock with DS3231 & PIC microcontroller

Published by MKDas on

In this article, we are going to make a complete digital clock with DS3231 and a PIC microcontroller. The RTC ds3231 is extremely accurate as a clock. We are going to use this RTC for our Clock come calendar with temperature and showing these values on Seven-Segment displays. This article is a tutorial but it can be converted into a product with some minor changes. So let’s start our Digital Clock.

Disclaimer: Electricity is always dangerous. Proper skill is required to work with electricity. Do work at your own risk. The author will not be responsible for any misuse or harmful act or any mistake you make. The contents of this website are unique and copyright protected. Kindly don’t do any nonsensical act copying and claiming it as yours. Most of the articles published here are kept as open-source to help you. Take the knowledge for free and use it, but if you are interested you can buy the ready resources offered here. If you need any help or guide feel free to comment below, the author will try to help you. Thanks.

Why DS3231?

DS3231 is a RTC from maxim. This RTC is extremely accurate. The oscillator is kept inside and calibrated. That is why the IC can deliver extreme accuracy.

ds3231

Key features of RTC ds3231:

  • Highly Accurate RTC With Integrated MEMS Resonator Completely Manages All Timekeeping Functions
  • Complete Clock Calendar Functionality Including Seconds, Minutes, Hours, Day, Date, Month, and Year, with Leap-Year Compensation Up to Year 2100
  • Timekeeping Accuracy ±5ppm (±0.432 Second/Day)
  • Two Time-of-Day Alarms
  • 1Hz and 32.768kHz Outputs
  • Digital Temp Sensor with ±3°C Accuracy

Interfacing ds3231 with microcontroller:

Interfacing is similar to DS1307. Pull-Up resistors with SCL and SDA pins which are connected to the microcontroller pins. Besides this, there is an Open-Drain SQW pulse output. We can use this pin to pulse a pair of LEDs. Transistors are used to amplify the current capacity as the output from the DS3231 is not enough for LEDs.

DS3231 interface

& the LEDs are:

You may read these articles too: Digital Clock using a PIC microcontroller & RTC DS1307

& Digital Clock with PIC16F676 & Seven Segment Display

Time & Date display in our digital clock with ds3231:

To show the Time and Date, we are using seven-segment displays. Like the previous article, we are using shift registers here. You know the benefits of using shift registers for this type of multi-display project. If you don’t know, I’ll suggest reading this article first.

seven segment with shift register

Temperature sensing:

DS3231 has a built-in temperature sensor. But that is not accurate like LM35. Here in our project, we are going to use an LM35 temperature sensor. But I’ll discuss about the way you can read the temperature data from ds3231M in this article too.

LM35 for our digital clock
LM35

Circuit Diagram of our Digital Clock with DS3231:

complete digital clock with ds3231

There is no complex part in this circuit. Download for high-resolution view.

Circuit operation:

There are 14 shift registers to drive 14 segments. All these shift registers are connected in cascade mode. The data is being sent from the microcontroller to the first shift register U2. Then the data is serially shifted to the right to U17. Here we did not use any resistors between the shift register and seven-segment. But in practical (PCB) you should use resistors (330R) to drive the seven segments. If the segment is large, use ULN2003A or ULN2803 drivers. Using a transistor will work too but it will make the circuit like a bird’s nest.

Besides the displays, there are 4 buttons for the edit function. The RTC DS3231 is driving 2 LEDs beside its communication through I2C with the MCU. For that, two transistors are used. You can use these small SMD versions as the current to drive the LEDs are not high at all but the IC can’t provide current to drive LEDs.

Programming:

Programming of our Digital Clock with DS3231 is a little bit extra compared to our previous article. But don’t worry, I’ll explain them all serially.

Shift registers to drive displays:

First thing first. We need to drive all the 14 shift registers to show the values on 14 different displays. That is why we need 14 sequel clock shifts and one latch clock.

// PIN declaration of shift registers
sbit shift_latch   at RB7_bit;
sbit shift_sck     at RB5_bit;
sbit shift_data    at RB6_bit;

sbit shift_latch_Dirrection   at TRISB7_bit;
sbit shift_sck_Dirrection     at TRISB5_bit;
sbit shift_data_Dirrection    at TRISB6_bit;
// End of declearation

void WriteByteToShift(unsigned Byte)
{
   unsigned char BitCount = 0;
   for(BitCount=0;BitCount<8;BitCount++)
   {
      shift_data = (((Byte>>BitCount)&0x01)!=0);
      shift_sck = 0;
      Delay_us(30);
      shift_sck = 1;
      Delay_us(10);
   }
}

Latch_display()
{
     shift_latch = 1;
     Delay_us(20);
     shift_latch = 0;
     Delay_us(10);
}


unsigned short mask(int num)
{
   switch (num)
   {
       case 0 : return 0b11111100;// 0
       case 1 : return 0b01100000;// 1
       case 2 : return 0b11011010;// 2
       case 3 : return 0b11110010;// 3
       case 4 : return 0b01100110;// 4
       case 5 : return 0b10110110;// 5
       case 6 : return 0b10111110;// 6
       case 7 : return 0b11100000;// 7
       case 8 : return 0b11111110;// 8
       case 9 : return 0b11110110;// 9
       case 10: return 0x00;//null
   }
}

void disp_time(int hr, int min, int sec, int day, int month, int year, int temp)
{
    WriteByteToShift(mask(temp%10u));
    WriteByteToShift(mask(temp/10u));

    WriteByteToShift(mask(year%10u));
    WriteByteToShift(mask(year/10u));

    WriteByteToShift(mask(month%10u));
    WriteByteToShift(mask(month/10u));

    WriteByteToShift(mask(day%10u));
    WriteByteToShift(mask(day/10u));

    WriteByteToShift(mask(sec%10u));
    WriteByteToShift(mask(sec/10u));

    WriteByteToShift(mask(min%10u));
    WriteByteToShift(mask(min/10u));

    WriteByteToShift(mask(hr%10u));
    WriteByteToShift(mask(hr/10u));

    Latch_display();
}

These functions are common to the previous one. All you see the extra is inside the disp_time() function. Here, all 14 sets of data are being transferred. Then one latch. That’s it.

You can read the previous article to understand this one: Digital Clock with PIC16F676 & Seven Segment Display

After this function, we can call this display function to print the data on the segments using this command.

disp_time(hour,minute,second,day,month,year,temperature);

Reading the RTC:

After the display function, we need to read the RTC. There is no such special difference in reading data from DS3231 compared to DS1307.

// Software I2C connections
sbit Soft_I2C_Scl           at RC3_bit;
sbit Soft_I2C_Sda           at RC4_bit;
sbit Soft_I2C_Scl_Direction at TRISC3_bit;
sbit Soft_I2C_Sda_Direction at TRISC4_bit;
// End Software I2C connections


int second,minute,hour;
int hr,day,dday,month,year,ap,app;
unsigned short read_ds3231(unsigned short address)
{
  unsigned short r_data;
  Soft_I2C_Start();
  Soft_I2C_Write(0xD0);
  Soft_I2C_Write(address);
  Soft_I2C_Start();
  Soft_I2C_Write(0xD1); //0x68 followed by 1 --> 0xD1
  r_data=Soft_I2C_Read(0);
  Soft_I2C_Stop();
  return(r_data);
}

void write_ds3231(unsigned short address,unsigned short w_data)
{
  Soft_I2C_Start(); // issue I2C start signal
  //address 0x68 followed by direction bit (0 for write, 1 for read) 0x68 followed by 0 --> 0xD0
  Soft_I2C_Write(0xD0); // send byte via I2C (device address + W)
  Soft_I2C_Write(address); // send byte (address of DS1307 location)
  Soft_I2C_Write(w_data); // send data (data to be written)
  Soft_I2C_Stop(); // issue I2C stop signal
}


void write_ds3231_oscillator()
{
  Soft_I2C_Start(); // issue I2C start signal
  Soft_I2C_Write(0x68);//address of DS3231
  Soft_I2C_Write(0x0E); // send byte to the control register
  Soft_I2C_Write(0x00); // write the command bit
  Soft_I2C_Stop(); // issue I2C stop signal
}

char time[3],date[3];

void get_time()
{
    second = read_ds3231(0);
    minute = read_ds3231(1);
    hour = read_ds3231(2);
    day = read_ds3231(4);
    month = read_ds3231(5);
    year = read_ds3231(6);

    second = Bcd2Dec(second);
    minute = Bcd2Dec(minute);
    hour = Bcd2Dec(hour);
    day = Bcd2Dec(day);
    month = Bcd2Dec(month);
    year = Bcd2Dec(year);

    if(hour>12)
    {
       hour-=12;
       ap = 1;
       AM = 0;
       PM = 1;
    }
    else
    {
       ap = 0;
       AM = 1;
       PM = 0;
    }

    time[0] = hour;
    time[1] = minute;
    time[2] = second;

    date[0] = day;
    date[1] = month;
    date[2] = year;
}

Except for the oscillator activation part, all other function is same as the DS1307.

void write_ds3231_oscillator()
{
  Soft_I2C_Start(); // issue I2C start signal
  Soft_I2C_Write(0x68);//address of DS3231
  Soft_I2C_Write(0x0E); // send byte to the control register
  Soft_I2C_Write(0x00); // write the command bit
  Soft_I2C_Stop(); // issue I2C stop signal
}

This write_ds3231_oscillator() function simply sending a command to the RTC to configure the SQW as 1Hz output.

And to read the temperature from DS3231 you can use this function:

Temperature reading from DS3231:

unsigned int tmp_ds=0;
unsigned int t_msb=0,t_lsb=0;
void read_temperature()
{
   Soft_I2C_Start(); // issue I2C start signal
   Soft_I2C_Write(0xD0);//address of DS3231
   Soft_I2C_Write(0x11);
   Soft_I2C_Start();
   Soft_I2C_Write(0xD1);
   t_msb = Soft_I2C_Read(1);
   t_lsb = Soft_I2C_Read(0);
   tmp_ds =  t_msb << 8 | (t_lsb & 0xC0);//Shift upper byte, add lower
   tmp_ds = (int)tmp_ds/256;//scale
   Soft_I2C_Stop();
}

The temperature is in floating-point value. I used seven segment displays to show the values where I did not keep any floating-point display. That is why I took only the integer part. You can use floating value to print in LCD or use it for other purposes.

digital clock with ds3231

We can use this temperature data for our clock, but for more accuracy, we used LM35 in our project digital clock with ds3231.

Parts used in this project:

Temperature sensing from LM35:

LM35 is a very common temperature sensor. Output is 10mV/℃. We used this function to read the data. Note that we are using 8bit ADCs.

unsigned int temperature=0;
int i=0;
void Get_temp()
{
   temperature = 0;
   for(i=0;i<5;i++)
   {
      temperature += 2*ADC_Get_Sample(0)-1;
      Delay_ms(20);
   }
   temperature/=5;
   Delay_ms(20);
}

Now, our programming parts are almost completed. We can gather all the parts into the main code.

mikroC code for the main function:

/*******************************************************************************
Program for all in one Digital Clock with DS3231
Program Written by_ Engr. Mithun K. Das
MCU:PIC16F73; X-Tal:8MHz; compiler: mikroC pro for PIC v7.6.0
Date: 24-07-2021; email: mithun060@gmail.com
*******************************************************************************/

#define   AM     RC0_bit
#define   PM     RC1_bit
#define   menu   RB1_bit
#define   plus   RB2_bit
#define   minus  RB3_bit
#define   back   RB5_bit

#include "Shift_register_display.h"
#include "DS3231_function.h"


unsigned int temperature=0;
int i=0;
void Get_temp()
{
   temperature = 0;
   for(i=0;i<5;i++)
   {
      temperature += 2*ADC_Get_Sample(0)-1;
      Delay_ms(20);
   }
   temperature/=5;
   Delay_ms(20);
}

bit display_mode;
short display_page=0;
bit old_state;

void main()
{
  TRISA = 0xFF;//all output
  TRISC = 0x18;//RC3&4 input
  TRISB = 0x1F;
  PORTA = 0x00;
  PORTB = 0x00;
  PORTC = 0x00;
  ADC_Init();
  
  Soft_I2C_Init();
  display_mode = 0;//run mode
  display_page = 0;//normal t&d

  //for the first time setting
  /*hour = 20;//in 24 hr mode
  minute = 5;
  day = 24;
  month = 7;
  year = 21;

  write_ds1307(2, Dec2Bcd(hour));
  write_ds1307(1, Dec2Bcd(minute));
  write_ds1307(0,0x00);
  write_ds1307(4, Dec2Bcd(day));
  write_ds1307(5,Dec2Bcd(month));
  write_ds1307(6, Dec2Bcd(year));
  write_ds1307_oscillator();//for 1Hz output at SQW pin*/



  while(1)
  {


        if(!menu && old_state)
        {
           Delay_ms(200);
           if(!menu)
           {
             old_state = 0;
             display_mode = 1;//activate edit mode
             if(display_page<6)display_page++;
             else display_page = 1;
           }
        }
        if(menu)old_state = 1;
        
        if(!back && display_mode)
        {
           Delay_ms(300);
           if(!back)
           {
              display_mode = 0;//run mode
              display_page = 0;//reset page
           }
        }
        
        //Get_temp();
        read_temperature();
        get_time();
        
        
        if(display_mode)
        {
           if(display_page==1)
           {
              hour = Bcd2Dec(hour);
              if(!plus)
              {
                 if(hour<23)hour++;
                 else hour = 0;
              }
              if(!minus)
              {
                 if(hour>0)hour--;
                 else hour = 23;
              }
              write_ds3231(2, Dec2Bcd(hour));
           }
           if(display_page==2)
           {
              minute = Bcd2Dec(minute);
              if(!plus)
              {
                 if(minute<59)minute++;
                 else minute = 0;
              }
              if(!minus)
              {
                 if(minute>0)minute--;
                 else minute = 59;
              }
              write_ds3231(1, Dec2Bcd(minute));
           }
           if(display_page==3)
           {
              write_ds3231(0,0x00); //Reset second to 0 sec. and start Oscillator
           }
           if(display_page==4)
           {
              day = Bcd2Dec(day);
              if(!plus)
              {
                 if(day<31)day++;
                 else day = 0;
              }
              if(!minus)
              {
                 if(day>0)day--;
                 else day = 31;
              }
              write_ds3231(4, Dec2Bcd(day));
           }
           if(display_page==5)
           {
              month = Bcd2Dec(month);
              if(!plus)
              {
                 if(month<12)month++;
                 else month = 0;
              }
              if(!minus)
              {
                 if(month>0)month--;
                 else month = 12;
              }
              write_ds3231(5, Dec2Bcd(month));
           }
           if(display_page==6)
           {
              year = Bcd2Dec(year);
              if(!plus)
              {
                 if(year<99)year++;
                 else year = 0;
              }
              if(!minus)
              {
                 if(year>0)year--;
                 else year = 0;
              }
              write_ds3231(6, Dec2Bcd(year));
           }
           
           disp_edit_time(hour,minute,second,day,month,year,temperature,display_page);
           Delay_ms(200);
        }
        else
        {
           disp_time(hour,minute,second,day,month,year,tmp_ds);
        }

  }//while(1)
}//void main()

//end

Here, two operation mode is kept. One is for normal operation and another is for the edit function. I converted the data from BCD to Decimal and Decimal to BCD when required to edit the time & date values. And the disp_edit_time() function is used to toggle the edit value.

The header files are here:

Header file 1:

//Shift_register_display.h

// PIN declaration of shift registers
sbit shift_latch   at RB7_bit;
sbit shift_sck     at RB5_bit;
sbit shift_data    at RB6_bit;

sbit shift_latch_Dirrection   at TRISB7_bit;
sbit shift_sck_Dirrection     at TRISB5_bit;
sbit shift_data_Dirrection    at TRISB6_bit;
// End of declearation

void WriteByteToShift(unsigned Byte)
{
   unsigned char BitCount = 0;
   for(BitCount=0;BitCount<8;BitCount++)
   {
      shift_data = (((Byte>>BitCount)&0x01)!=0);
      shift_sck = 0;
      Delay_us(30);
      shift_sck = 1;
      Delay_us(10);
   }
}

Latch_display()
{
     shift_latch = 1;
     Delay_us(20);
     shift_latch = 0;
     Delay_us(10);
}


unsigned short mask(int num)
{
   switch (num)
   {
       case 0 : return 0b11111100;// 0
       case 1 : return 0b01100000;// 1
       case 2 : return 0b11011010;// 2
       case 3 : return 0b11110010;// 3
       case 4 : return 0b01100110;// 4
       case 5 : return 0b10110110;// 5
       case 6 : return 0b10111110;// 6
       case 7 : return 0b11100000;// 7
       case 8 : return 0b11111110;// 8
       case 9 : return 0b11110110;// 9
       case 10: return 0x00;//null
   }
}

void disp_time(int hr, int min, int sec, int day, int month, int year, int temp)
{
    WriteByteToShift(mask(temp%10u));
    WriteByteToShift(mask(temp/10u));

    WriteByteToShift(mask(year%10u));
    WriteByteToShift(mask(year/10u));

    WriteByteToShift(mask(month%10u));
    WriteByteToShift(mask(month/10u));

    WriteByteToShift(mask(day%10u));
    WriteByteToShift(mask(day/10u));

    WriteByteToShift(mask(sec%10u));
    WriteByteToShift(mask(sec/10u));

    WriteByteToShift(mask(min%10u));
    WriteByteToShift(mask(min/10u));

    WriteByteToShift(mask(hr%10u));
    WriteByteToShift(mask(hr/10u));

    Latch_display();
}

It is not possible to show the result in simulation as the proteus is working very slow to simulate this circuit. I suggest you make the project real and then test it.

Header file 2:

//DS3231_function.h

// Software I2C connections
sbit Soft_I2C_Scl           at RC3_bit;
sbit Soft_I2C_Sda           at RC4_bit;
sbit Soft_I2C_Scl_Direction at TRISC3_bit;
sbit Soft_I2C_Sda_Direction at TRISC4_bit;
// End Software I2C connections


int second,minute,hour;
int hr,day,dday,month,year,ap,app;
unsigned short read_ds3231(unsigned short address)
{
  unsigned short r_data;
  Soft_I2C_Start();
  Soft_I2C_Write(0xD0);
  Soft_I2C_Write(address);
  Soft_I2C_Start();
  Soft_I2C_Write(0xD1); //0x68 followed by 1 --> 0xD1
  r_data=Soft_I2C_Read(0);
  Soft_I2C_Stop();
  return(r_data);
}

void write_ds3231(unsigned short address,unsigned short w_data)
{
  Soft_I2C_Start(); // issue I2C start signal
  //address 0x68 followed by direction bit (0 for write, 1 for read) 0x68 followed by 0 --> 0xD0
  Soft_I2C_Write(0xD0); // send byte via I2C (device address + W)
  Soft_I2C_Write(address); // send byte (address of DS1307 location)
  Soft_I2C_Write(w_data); // send data (data to be written)
  Soft_I2C_Stop(); // issue I2C stop signal
}


void write_ds3231_oscillator()
{
  Soft_I2C_Start(); // issue I2C start signal
  Soft_I2C_Write(0xD0);//address of DS3231
  Soft_I2C_Write(0x0E); // send byte to the control register
  Soft_I2C_Write(0x00); // write the command bit
  Soft_I2C_Stop(); // issue I2C stop signal
}

unsigned int tmp_ds=0;
unsigned int t_msb=0,t_lsb=0;
void read_temperature()
{
   Soft_I2C_Start(); // issue I2C start signal
   Soft_I2C_Write(0xD0);//address of DS3231
   Soft_I2C_Write(0x11);
   Soft_I2C_Start();
   Soft_I2C_Write(0xD1);
   t_msb = Soft_I2C_Read(1);
   t_lsb = Soft_I2C_Read(0);
   tmp_ds =  t_msb << 8 | (t_lsb & 0xC0);//Shift upper byte, add lower
   tmp_ds = (int)tmp_ds/256;//scale
   Soft_I2C_Stop();
}

char time[3],date[3];

void get_time()
{
    second = read_ds3231(0);
    minute = read_ds3231(1);
    hour = read_ds3231(2);
    day = read_ds3231(4);
    month = read_ds3231(5);
    year = read_ds3231(6);

    second = Bcd2Dec(second);
    minute = Bcd2Dec(minute);
    hour = Bcd2Dec(hour);
    day = Bcd2Dec(day);
    month = Bcd2Dec(month);
    year = Bcd2Dec(year);

    if(hour>12)
    {
       hour-=12;
       ap = 1;
       AM = 0;
       PM = 1;
    }
    else
    {
       ap = 0;
       AM = 1;
       PM = 0;
    }

    time[0] = hour;
    time[1] = minute;
    time[2] = second;

    date[0] = day;
    date[1] = month;
    date[2] = year;
}

So all the codes are here. You can use all these parts to make a project in mikroC pro for PIC v7.6.0 or higher. I always use a valid license. You should use it too.


Simulation result:

Proteus can not run this circuit at real speed. That is why the circuit did not give us a real speed performance. That is why I’ve ordered the PCB from my PCB partner JLCPCB.com. Once it arrives, I’ll make the circuit in real-time. Till then, please forgive me to check this video as the simulation result.

Conclusion:

It was a long circuit operation comparing to a simple clock. The design is done in a productive way. With some minor changes, this project can be turned into a real product. And I’m doing it here. You can try too. If you need any help with this don’t forget to comment below or directly contact me. I hope this project will help you a lot to make your own digital clock. Thank you.

Don’t forget to subscribe for the next update.

Loading

JLCPCB – Only $2 for PCB Prototype (Any Color)

24 Hours fast turnaround, Excellent quality & Unbeatable prices

$18 Welcome Bonus for new registrations Now!!! https://jlcpcb.com



MKDas

I'm Mithun K. Das; B.Sc. in EEE from KUET, Bangladesh. Blog: https://labprojectsbd.com. "First, electronics was my passion, then it was my education, and finally, electronics is now my profession." I run my own electronics lab, M's Lab (https://mlabsbd.com). Where I work with the creation of new products from ideas to something in real life. Besides this is my personal blog where I write for hobbyists and newcomers in the electronics arena. I also have a YouTube channel where I publish other helpful videos, you can find the link inside the articles. I always try to keep it simple so that it becomes easy to understand. I hope these will help them to learn electronics and apply the knowledge in their real life.

6 Comments

  • Joe Rotello · July 25, 2021 at 5:19 pm

    Interesting…yet, even with high accuracy crystal time-base / MPU control, these clocks usually wind up not being very accurate, too “easily” losing / gaining time, etc.

    If there was an option to “lock” to the “atomic clock” frequency and thus periodically “calibrate” time, these types of clocks would be very useful to a great many more people, and a great deal more accurate as well, especially over time.

    R K Hamy · July 29, 2021 at 4:37 pm

    Thanks for the excellent project

    Md.Araf hossain · September 3, 2021 at 11:56 pm

    osadaron project. ati ami toiri krbo. and kmn holo r result ki seta apnke dekhabo.

    “”””but din gular nam satha add kora thakle jeno pori-purnota lav peto . “‘”‘

    plz help me. code ar part onk gula.
    kontar por konta hobe ? ak page a sajiya dila vlo hoto.

      MKDas · September 4, 2021 at 12:19 pm

      etai ag e complete koren. tahole update korte parben sohoj e.

    Leave a Reply

    Avatar placeholder

    Your email address will not be published. Required fields are marked *