In this article, we are going to make a Digital Clock with PIC16F676 and RTC DS1307. Seven Segment Displays will be used as our display. Using this small microcontroller, we can make our Date and Time clock. For this, shift registers will be used to extend the pins and to drive the displays. So let’s see in detail.

Disclaimer:

Advertisements

What is RTC?

A real-time clock is an electronic device that measures the passage of time. Although the term often refers to the devices in personal computers, servers, and embedded systems, RTCs are present in almost any electronic device which needs to keep the accurate time of day. Wikipedia

Here, in our digital clock, we will use the common RTC IC: DS1307.

Interfacing RTC DS1307 with microcontroller:

DS1307 has an I2C interface with SCL and SDA pins. Only two 4K7 resistors are required for this interface with a microcontroller.

RTC DS1307 interface

A crystal of 32.768KHz and a Battery are required for this RTC too. The battery is necessary for backup power during power failure. A CR2032 battery is enough for 10 years of time keeping.

About microcontroller PIC16F676:

PIC16F676 is a 14pin 1KB 8bit small microcontroller. And it’s enough for our project.

PIC16F676

Why PIC16F676?

In our project, we are making a small clock for learning purposes. That is why we do not need more memories and more pins as well. Besides, we are using shift registers which helps reducing pin consumption. That is why we selected PIC16F76 for our project.

Why shift register?

This question comes to mind first, why we are using shift register rather than using the multiplexed seven segment displays? Yes, we can use the multiplexing method for digital clocks. But the problem is timing. When you need to handle more than 4 digits, the timing issue comes in display performance. Besides, using a shift register can allow a full DC display which actually makes the digits clear to see. Also, if you want to use large digits, it becomes easier as well. Considering all these, shift registers are used in multi digits displays like a digital clock. It also helps reducing pin consumption. So small microcontrollers can be used to operate many displays at a time.

Use of Shift register 74HC595 in our Digital clock:

Shift registers are very common in digital electronics. It helps reducing pin consumption and helps to control a large number of pins with some serial signal pattern. There are several types of shift registers out there, but here in our project, we are using 74HC595 serial in parrel our shift register.

74hc595 shift register

Shift register to interface Seven Segment:

We can use small seven segments directly with the shift register. But for large segments, we may need to use driver ICs like ULN2003A or similar.

shift register with seven segment

In our project, we used the segment directly. But you should use a small resistor between the 74HC595 and Seven Segment pins to reduce current flow. A 220R 0.25W resistor is enough.

You may read this: How to interface 8 digits 7-segment with shift register 74HC595 and PIC16F877A

Circuit diagram of our Digital clock:

Here is the circuit diagram of our Digital Clock with PIC16F676:

Digital clock with PIC microcontroller and shift register
Digital Clock with PIC16F676

The circuit is very simple. Just some ICs and connections. I used a wireless connection while drawing the diagram to keep it clean. I think you’ll understand.

You may read this: Digital Clock using a PIC microcontroller & RTC DS1307

Coding:

I used mikroC pro for PIC as the compiler. But before coding in full, we have to divide it into several parts to understand the operation.

The first one is to use the shift register. We must know how we can operate the shift register. From the datasheet, it is not easy to understand the operation at all. But if you think it in another way, it may be easy to understand.

Just think about 8 serial boxes with closed doors. There is a hole on the back through which, you can push the balls.

shift register

All the lead or the doors are connected with a rod. Now, you can put one ball at a time serially to each box. If you want to keep it blank you can escape that box. This way, you need 8 clock pulse to do the filling job for 8 boxes. Now, once it is ready, you can pull the door rod at once for all these 8 boxes. Then the boxes with the ball will deliver a ball on the other side and those who had no balls will deliver nothing. Did you catch the point?

OK, you need a clock pulse to shift your data to the shift register. When you have done all the 8 data bits, you need one latch clock to deliver those data to the output. That’s it. The only thing you should keep in mind is that these shift registers can deliver the serial data to the next one. That means, if you have 8 shift registers of 8bit to operate, then you need 8X8 clock pulses as well as data for those 8×8 pins. Then only one latch pulse delivers it to the output.

Let’s code for this part

Shift Register operation function:

// PIN declaration of shift registers
sbit shift_latch   at RA0_bit;
sbit shift_sck     at RA1_bit;
sbit shift_data    at RA2_bit;

sbit shift_latch_Dirrection   at TRISA0_bit;
sbit shift_sck_Dirrection     at TRISA1_bit;
sbit shift_data_Dirrection    at TRISA2_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);
}

After pin declaration, the WriteByteToShift() function is doing the clocking and data transferring job. One clock (SCK) and one data are transferred. After transferring all the data, just one signal for the latch will deliver the output.

Print Function:

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)
{
    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();
}

The mask() function simply setting the pin outputs for each digit. Like for ‘0’ on the segment, we need to set the output as 11111100. Similarly, other digits are configured. Then in the disp_time() function, we are simply extracting the hour, minute, and second values. But in the reverse order. As this is a shift register, the last digit (MSB) is being sent last.

Clock Function:

Now, we need to interface the RTC with our microcontroller to read and write the time. Here are the functions related to RTC.

// Software I2C connections
sbit Soft_I2C_Scl           at RC0_bit;
sbit Soft_I2C_Sda           at RC1_bit;
sbit Soft_I2C_Scl_Direction at TRISC0_bit;
sbit Soft_I2C_Sda_Direction at TRISC1_bit;
// End Software I2C connections


int second,minute,hour;
int hr,day,dday,month,year,ap,app;

unsigned short read_ds1307(unsigned short address)
{
  unsigned short r_data;
  Soft_I2C_Start();
  Soft_I2C_Write(0xD0); //address 0x68 followed by direction bit (0 for write, 1 for read) 0x68 followed by 0 --> 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_ds1307(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
}


char time[3];
char date[3];

void get_time()
{
    second = read_ds1307(0);
    minute = read_ds1307(1);
    hour = read_ds1307(2);
    day = read_ds1307(4);
    month = read_ds1307(5);
    year = read_ds1307(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;
    }
    else
    {
       ap = 0;
    }
    
    time[0] = hour;
    time[1] = minute;
    time[2] = second;
    
    date[0] = day;
    date[1] = month;
    date[2] = year;
}

After pin declaration, the read and write function is common for our previous article where we made Digital Clock with PIC16F73 and printed the data on LCD. Like that program, we are using the software I2C library of mikroC here too.

On the get_time() function, we are reading time from ds1307. Besides, AM/PM checking is added.

The while(1) loop:

Inside the infinity while(1) loop, we are doing the rest of the works.

cnt_disp_page++;
        if(cnt_disp_page>500)
        {
           cnt_disp_page = 0;
           disp_page=~disp_page;
        }

        if(!disp_page)
        {
           RA5_bit = 1; //time
           get_time();
           disp_time(time[0],time[1],time[2]);
           RA4_bit =  ap;//am/pm
        }
        else
        {
           RA5_bit = 0; //time
           get_time();
           disp_time(date[0],date[1],date[2]);
           RA4_bit =  0;//am/pm
        }

As we are using 3 pairs of segments, so digits are limited. That is why we divided the time into two different modes. In one mode, we are displaying time and in the other mode, we are displaying data. LEDs for AM/PM and Time/Date is used to indicate.

Complete code:

Now we can combine all the parts into one to complete the code for our Digital Clock with PIC16F676:

/*******************************************************************************
Programming for digital clock
Program written by_ Engr. Mithun K. Das|| mithun060@gmail.com
MCU: PIC16F676; Xtal: 4MHz (internal); Compiler: mikroC pro for PIC v7.6.0
Date: 23-07-2021
*******************************************************************************/

// PIN declaration of shift registers
sbit shift_latch   at RA0_bit;
sbit shift_sck     at RA1_bit;
sbit shift_data    at RA2_bit;

sbit shift_latch_Dirrection   at TRISA0_bit;
sbit shift_sck_Dirrection     at TRISA1_bit;
sbit shift_data_Dirrection    at TRISA2_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)
{
    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();
}


// Software I2C connections
sbit Soft_I2C_Scl           at RC0_bit;
sbit Soft_I2C_Sda           at RC1_bit;
sbit Soft_I2C_Scl_Direction at TRISC0_bit;
sbit Soft_I2C_Sda_Direction at TRISC1_bit;
// End Software I2C connections


int second,minute,hour;
int hr,day,dday,month,year,ap,app;

unsigned short read_ds1307(unsigned short address)
{
  unsigned short r_data;
  Soft_I2C_Start();
  Soft_I2C_Write(0xD0); //address 0x68 followed by direction bit (0 for write, 1 for read) 0x68 followed by 0 --> 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_ds1307(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
}


char time[3];
char date[3];

void get_time()
{
    second = read_ds1307(0);
    minute = read_ds1307(1);
    hour = read_ds1307(2);
    day = read_ds1307(4);
    month = read_ds1307(5);
    year = read_ds1307(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;
    }
    else
    {
       ap = 0;
    }
    
    time[0] = hour;
    time[1] = minute;
    time[2] = second;
    
    date[0] = day;
    date[1] = month;
    date[2] = year;
}


bit operation_mode;
bit disp_page;
unsigned int cnt_disp_page=0;


void main() 
{
  TRISA = 0x00;//all output
  TRISC = 0xFF;//all input
  ANSEL = 0x00;
  ADCON1 = 0x07;
  CMCON = 0x07;
  PORTA = 0x00;
  Soft_I2C_Init();
  operation_mode = 0;//run mode
  disp_page = 0;
  
  //to set time once
  
  /*hour = 15;//in 24 hr mode
  minute = 45;
  day = 23;
  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));*/

  while(1)
  {

        cnt_disp_page++;
        if(cnt_disp_page>500)
        {
           cnt_disp_page = 0;
           disp_page=~disp_page;
        }

        if(!disp_page)
        {
           RA5_bit = 1; //time
           get_time();
           disp_time(time[0],time[1],time[2]);
           RA4_bit =  ap;//am/pm
        }
        else
        {
           RA5_bit = 0; //time
           get_time();
           disp_time(date[0],date[1],date[2]);
           RA4_bit =  0;//am/pm
        }

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




//

To set the time, we did not use any buttons. Why? PIC16F676 has only 1KB of memory. All functions can not be fit in this. That is why we kept a small code that can be used once to fix the time.

/*hour = 15;//in 24 hr mode
  minute = 45;
  day = 23;
  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));*/

Mask all codes in the while(1) loop first, then unmask this code. Then burn the hex to the MCU or use ICSP. To burn the file, you can get help from this: PICKit2 colon Simplified.


Now our code is complete and ready for your desk clock. Let’s test the project in simulation.

Simulation result:

Conclusion:

The purpose of this Digital Clock with PIC16F676 was to demonstrate the basic operation of a digital clock using the seven-segment display. If you learn it clearly, it will help you making your own clock adding more features. You just need to read it carefully several times. If you still have any confusion or have any questions, don’t forget to comment below. Thank you.

For Professional Designs or Help:

Loading

Check this out: 5 coolest multimeters you can buy


MKDas

Mithun K. Das; B. Sc. in EEE from KUET; Head of R&D @ M's Lab Engineering Solution. "This is my personal blog. I post articles on different subjects related to electronics in the easiest way so that everything becomes easy for all, especially for beginners. If you have any questions, feel free to ask through the contact us page." Thanks.

8 Comments

jito · 24/07/2021 at 7:03 am

What should change in this code for DS3231 RTC?

    MKDas · 24/07/2021 at 7:30 am

    No such significant changes. Almost same.

rakesh more · 06/10/2021 at 2:43 pm

please make one using adruino.

    MKDas · 06/10/2021 at 3:01 pm

    Thanks for your suggestion.

Pandian · 26/12/2021 at 7:45 am

Sir what the code changes to be done for 0 (zero) suppression on leading display such that instead of displaying 08:00 it can be display as 8:00.thanks in advance

    MKDas · 26/12/2021 at 11:11 am

    Simply use the blank digits while printing that segment.

JatinderBir Singh · 13/05/2023 at 1:51 am

My Simple question , Doyou think this is best method for Multiplexing 7Seg Displays?
Can 18(Eighteen) SSD be used simultaneously with above mentioned configuration?

    MKDas · 13/05/2023 at 1:11 pm

    It depends on CPU clock speed. Use higher clock, it can be possible.

Leave a Reply

Avatar placeholder

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