A Real Time Clock IC (DS1307) project using the PIC micro.

Making A Real Time Clock (RTC) is simple if you use a helper chip such as a DS1307 because you do not need to keep track of the length of each month or account for leap years. It is all done for you, plus you get the benefit of a battery back up system that means it won't lose the data or time when you turn off main power.

This PIC project uses an I2C (or IIC) Real Time Clock IC (DS1307) and a four digit seven segment display to create a standard desk clock.

Note: If you typed DS1703 Real Time Clock to find this page you probably mis-spelled the chip type. 

Anyway you can find a DS1307 (RTC) Real Time Clock IC project and information on this page.

Note: This RTC project has been updated with easier to use software i.e. the software loaded into the PIC (Note the compiler is free for <2k and currently the code uses about 1550 bytes). There are also two additional modes one for information and one for debugging:

The first new mode is found when pressing key 3 which will display an indication of what the current display is showing (sometimes it is hard to figure that out by just looking at numbers!). The second is when pressing key 4 which will cycle through all the RA0-RA7 output pins and show the LEDs lit on the 7-segment display. This means it is easier to figure out if the RA0-RA7 connections are correct when you wire it up (Note RA5 is not used as it can only be an input - see detail below). The project has also been updated to use the latest MikroC compiler.

RTC DS1307 Real time clock IC circuit diagram

DS1307 Introduction

Although the PIC16F88 has a built in oscillator for a 32kHz watch crystal a DS1307 is easier to use on a bread board. This is because you can control the layout of the circuit more easily.

The RTC also makes the software easier as it takes care of all calendar functions; accounting for leap years etc.

The DS1307 (RTC) Real Time Clock IC (an I2C RTC) is an 8 pin device using an I2C interface (although the data sheet does not mention I2C to avoid royalty payments!). It has 8 read/write registers that store the following information:

Address Register function
0 Seconds 0-59
1 Minutes 0-59
2 Hours 0-24,1-12
3 Day 1-7
4 Date 1-31
5 Month 1-12
6 Year 0-99
7 Control

Note: Addresses 0x08 to 0xf3 are user RAM and if you use a backup battery these are then non volatile ram i.e. they will save their contents after the power is off - so you have an extra 56 bytes of ram to play with! less the one used for storing the year high digits at 0x20- you could change this to not use the RAM but it is also used as an initialisation check - see the code.

Note: Address 3f is used in this project as a check to see if the clock needs initialising and to store the upper year digit (for easier coding).

The last address 0x08 is the CONTROL address and it determines what is generated at the SQW/OUT pin. You can control the level directly via I2C or set it to 1Hz, 4096Hz, 8192Hz, or 32768kHz. In this software it is set to 1Hz and used to drive an LED that can be used as a back light for the 4x7-segment module (if you shine a light through the module you'll see the two central holes (like a colon character) that are between the left and right sets of two 7-segments. This is usually used to flash seconds so placing the LED behind this will achieve that operation.

In the same way as the I2C pins you need to add a pull-up to V+ at the SQW/OUT pin to see any output signal as it is an open drain output! or as in this circuit, an LED and 470R resistor are connected in series and to the +5V power. The other end goes to the SWQ/OUT pin of the DS1307.

DS1307 Specification

Accuracy Watch crystal spec typically 20ppm

Compiler Mikroelectronika MikroC Compiler Free!
Target 16F88 (re-targettable to other PICs that have Analogue input AN0).
Software level Medium.
Software notes Switching between i/p & o/p to read analogue/drive display. Using I2C routines.
Hardware level Easy.
Hardware notes Special care must be taken in placing the DS1307 and the crystal.
Project version 1.02
Project files Enter your details to get the Download Link
and get the microcontroller newsletter:

(Note: Your email is safe it will never be sold or rented).
You will get All theC source code and hex file.

Note: Check your email for the project code download link.


DS1307 Control Bits

There are two specific 'gotcha' type controls embedded in the addresses which make using the chip slightly more complicated.

DS1307 Registers Clock halt (CH)

The most important is the Clock Halt Bit (CH) which is bit 7 of address 0. This is the register that controls 'seconds' and the CH bit has to be preserved otherwise the chip stops the clock. Writing zero to this bit resets the CH bit so that the clock runs. 

Note: You have to reset the CH bit to zero to let the chip operate!

Warning: The default state of the DS1307 is undefined
so you must clear the CH bit to start the oscillator.

In general you should leave this bit at zero and only set it if you have to. This bit is contained within register zero which is also the "minutes" and "seconds" register. In general keep this bit at zero unless you are updating the seconds part of the register (you don't want the seconds changing while you are editing them).

DS1307 24/12 Hour control

The second is the 24/12 hour control which is bit 6 of address 2. It is set high for 12 hour mode and low for 24 hour mode. In this project it is set low for 24 hour mode.

The problem with these two bits is that you have to preserve them when accessing the registers to write data and ignore them when reading out values for display. Its not a big problem and you can see how it's done when you look at the code (see function edit_DS1307() and the 1st 2 case statements for address 0 (CH) and 2 (12H/24H) ).

DS1307 32kHz oscillator

Surprisingly making an accurate 32kHz oscillator is a difficult task (much more than a high speed oscillator e.g. a MHz crystal oscillator). This is because low speed oscillator drivers are designed for low power operation. That means high impedance and therefore low current which makes the driver extremely sensitive to noise (or any nearby signals which can capacitively couple to the crystal wire).

Using the DS1307 lets you put the crystal in the least noisy part of the board. In addition it sets the crystal load capacitance which is critical in making the crystal oscillate at exactly 32kHz - controlling its initial error i.e. for the specified ppm error value the load capacitance must be exact.

Note: A common way of calibrating a crystal (not in this project) is crystal pulling or changing the capacitance at one crystal pin relative to the other - so load capacitance is crucial.

The DS1307 loads the crystal with 12.7pF so you need to buy a crystal that is defined to use this load capacitance. Circuit layout also affects the capacitance at the crystal pins so you must keep the crystal as close as possible to the chip and the tracks from crystal to chip must be short.

To ensure the crystal oscillates correctly you must ensure that :

  • Crystal uses 12.7pf load capacitance (correct crystal type).
  • The crystal is close to the IC.
  • The tracks are short.
  • The chip supply has lots of decoupling (capacitors from +5V to GND). e.g. A 100n and a 10n
  • There are no signal tracks near to the crystal.
  • For a pcb: It has a guard ring and a ground plane and away from digital signals.

If you are doing a board layout there is good advice in the PIC 16F88 datasheet (Timer 1 section) on crystal pcb guard rings. Dallas recommends Application note 58 which I have not read yet.

DS1307 Power failure.

The DS1307 detects a power failure if its input voltage (Vcc) falls below (VBat) and automatically switches to the Vbat supply input (you should use a lithium 3V battery here as the backup battery). It also inhibits I2C control signals until Vcc is 1.25 x Vbat so you won't be able to put bad data into the chip as the power is failing!

Real Time Clock Design

Input keys

To save microcontroller pins there are four input keys which are all connected to a single analogue input pin. This pin also drives one of the seven segment display LEDs so it has to be switched between input (to read the analogue voltage) and output (to drive the led). 

Note: This works because the analogue input is switched to receive analogue for only (150us approx) there is a 100us delay to let the inputs settle (just a guess and could be made lower - but it is not critical in this application). Then the input signal is read. Because of persistence of vision (on which the entire display methodology depends) your eye can not see this missing light pulse so it looks like the display is completely steady!

Each key pulls the analogue input to a different voltage level which you can easily read using the ADC (RA0).

The code that reads the values is :

// Decode the analogue
        if (val>800 && val<860) {val=1;}
   else if (val>490 && val<530) {val=2;}
   else if (val>300 && val<350) {val=3;}
   else if (val<300)            {val=4;}
   else {val=0;}

   return val; 
  
each button connects to a 10 and to 5V. Each of the 4 buttons  pulls down to  47k,10k,4k7,1k respectively resulting in the following values:

Button 1 will give :(47.0/57.0)*5.0 V = 4.122V giving an ADC reading of ~844
Button 2 will give :(10.0/20.0)*5.0 V = 2.500V giving an ADC reading of ~ 512 
Button 3 will give :(4.7/14.7)*5.0 V = 1.599V giving an ADC reading of ~ 327 
Button 4 will give :(1.0/11.0)*5.0 V = 0.455V giving an ADC reading of ~ 93   

Note: Readings are approx since resistor tolerance will be 10% for standard resistors.

7 Segment Display

The display is made up of four 7 segments built into a convenient block where all 8 led drive lines  are connected together. You could wire up individual 7-segments to achieve the same result but doing it on a soldered breadboard is a pain. The block just saves you effort (and errors in wiring).  So this results in 8 data lines and 4 digit select lines.

Initially I used RA0 to RA7 as the data line drivers and RB2,5,6,7. Then I realised that RA5 is only allowed to be an input because it is also multiplexed with the reset pin MCLR. Since it had been wired up this way the simplest solution was to eliminate RA5 but how do you do that without changing all the drive data definitions? 

When eliminating RA5 I moved RA6 and RA7 down to bits b5 and b6 so this leaves the LED drive for the decimal point unconnected but that is OK since we are not using that part of the 7-segment display.

So the solution is to use a helper function that maps the bits 6,7 to bits 5,6 just before outputting the data to the 7-segment LED bus and the function has an obvious name:

move_b6_b7_to_b5_b6(Byte num)

Note: The other solution is to use RB0-RB7 to drive the LED data bus and RA0-RA4 for the column strobes and you can do this if you want to.


TIP: The MikroC IDE has a useful tool for generating the hex codes for segment drives in Menu-->Tools-->Seven Segment Editor.

Digit Select lines

The digit select lines are each driven by a transistor switch that allows more current if needed ( note I have not measured the exact current drawn as it depends on the refresh rate of the display).

Display Refresh and current limit resistors

Yes there are none! i.e. between the RA bus and the LED drives there are no limit resistors.

Why?

In all other circuits you can see them. I chose not to use them since the display is only ever driven in refresh mode. If it were driven directly then the LEDs would blow up. A commercial use of this technique is the MAX7219.

If you are uncomfortable with this then add 8 off of 330R or 470R resistors to limit the maximum current to each LED (e.g. if the are driven permanently during testing) from each drive RA bus to the 4x7-segment display.

The reason that it works is to do with heat generated - as the current flows in the LED the junction heats up and if you drive too much current then it melts the internal wires - which is where the max. current limit comes from.

When driving the display in refresh mode the display is held on for a very short time compared to the time it is held off and this defines an average current that is within the current limit of each LED.

Hey.. don't knock it... it works fine...promise.

Note: The idea comes from chip design where the current drawn by the device depends on the switching speed of the system since for FETs maximum current is drawn when both upper and lower FETs in a switch are on i.e. during the transition from current level to next i.e. when switching.

DS1307 Project Operation

When the system powers up the ram location 0x3f is checked for value 0x20. If this exists then it means that the backup battery has saved contents of the RAM and all the registers have therefore been initialised. So the software skips the initialisation sequence.

Note: I am assuming you are going to be sensible and set the year high digits to 20 as V1.02 allows you to edit this value - just make sure it is 20 if you power down and up otherwise it will set up default values i.e. it will reset the other registers to specific initialisation values.

If 0x20 is not found then the Real Time Clock IC is initialised to the following values:

Address Initialisation value
0 Seconds 0x59 (bit 7 cleared).
1 Minutes 0x59
2 Hours 0x24 (bit 6 cleared).
3 Day 0x07
4 Date 0x31
5 Month 0x12
6 Year 0x99 (low digits)
3f Year 0x20 ( high digits)

These are all Binary Coded Decimal numbers which the Real Time Clock IC works with. Note that the initialisation values have been chosen so you can easily understand what the display is showing but from V1.02 this is not as important as you can hit key 3 to get pseudo text on what is displayed.

Pseudo Text Display

However when re-visiting the project it turns out that it is a difficult task (figuring out exactly what is on the display especially if it is 8.20pm in 2014 i.e. all the numbers are identical) which is why pressing key 3 (when not editing a value) flashes the display at a flicker (slow) rate and indicates what you are looking at with the following 7-segment outputs.

Mode 7-segment code Meaning
MODE_HRS_MINS  Hrnn Hours and minutes
MODE_MINS_SECS  nnSe Minutes and seconds
MODE_DAY_MON dAnn Day and Month
MODE_YEAR yEAr Year
MODE_INIT  InIt Initialisation

Mode button

Pressing mode button cycles the display showing different data after each button press. The following display sequence is followed:

  1. Hours, Minutes.
  2. Minutes, Secs.
  3. Day, Month.
  4. Year.

Edit, Up and Down buttons

To change a value the Edit button must be pressed (Key2) . When it is, the left hand two digits are flashed on and off. Now the Up button (key3) and Down button (Key4) let you edit the value.

Hitting Edit again flashes the two right hand digits and you can change the value as before. Hitting the Edit button or the Mode button again then exits the edit mode.

The clock is only stopped when editing the Minutes/Seconds display and restarted at the end of the edit sequence or when the Mode button is hit (the routine check_start_ds1307 will not change the state or write to the oscillator register if the clock is already running i.e. the clock oscillator is only started if it was previously stopped). This lets you set the seconds accurately but does not disturb the clock in other modes. 

Setting the Clock Accurately

Note I am not sure why but it may be how the DS1307 works you have to set the time 1 second in advance of the desired transition time.

say the clock currently read 1328 13 minutes and 28 seconds to set the time fairly well edit the time to 13 45 then hit the edit key when your clock you are trying to synchronize to is at 1344.  This seems to get the seconds changing at the same time as the source clock. This is probably due to the double register set within the DS1307 updating only at a specific time.

Real Time Clock IC hardware

Real Time Clock IC project
Click digital clock schematic to open a pdf document.

"Right Click Here" to Download the schematic of the Real Time Clock. Either right click and choose save as or click to open in browser (if you have enabled a pdf reader plug-in).

You can use any PIC microcontroller that has an ADC and enough memory to hold the program and enough pins for the display multiplexing.

You can program the PIC in circuit through the ICSP connector even while the program is running and since there is no I2C communication the correct time will be maintained as the DS1307 is not updated.

Note: While programming the PGD and PCK lines are toggled and these connect to the Display so you will see some LEDs light up during the programming sequence - Don't worry about it!

DS1307 Project Block Diagram

Real Time Clock IC hardware block diagram

There is nothing really difficult about the project except for placing the DS1307 away from noise sources and decoupling it as mentioned earlier.

Real Time Clock IC project Software

Project files

Compiler project files
There are a lot more for 6.4.0 (see the download file).

C Source files.
16F88_RTC_DS1307.c
I2C.c

Header files.
bit.h - bit manipulation macros
I2C.h  - definitions for I2C operation.

Output files
16F88_RTC_DS1307.hex

DS1307 Code Description

16F88_RTC_DS1307.c

This contains the main routine and support functions for controlling the DS1307.

I2C.c

This contains the software implementation of I2C (clock stretching has not been tested). To change the PORTs/pins alter the #define statements at the start of I2C.c

Functions

  • init()
  • init_ports()
  • init_ds1307()
  • read_ds1307()
  • write_ds1307()
  • stop_ds1307()
  • start_ds1307()

The above functions are all self explanatory. Those below need a bit of explanation.

Function int2seg()

Converts a number into the required output value for PORTA to drive the seven segment display. The internal array ret[] holds the 7-segment values - these are pure 8 bit values as if all data lines to the 7-segments are connected. The routine:

move_b7_b6_to_b6_b5()


... moves bits around in a byte to account for the fact that RA5 is not used.

Function Read_ANA_keys()

This routine decodes the analogue levels into a specific key press. it returns a value from 1-4 representing keys 1-4. On the schematic key1 is pb1 and should be located on a board at the top.

This calls decode_ANA_keys() to determine the key pressed.

In this routine the analogue input is turned on, a delay  of 100us is used, then the key decoded, then the analogue input returned to a digital output.

Note how this is using the RA0 pin as both an analogue input pin and as a digital output pin.

This works for two reasons:

  • The system is not interrupt driven.
  • The time to read an analogue port is small compared to doing everything else.

Note: If this was interrupt driven code the portA (LED drives) writing interrupt would be required to read the analogue port so that the pin direction was not changed during a read i.e. you would use that interrupt routine since it would know when it is safe to read the analogue value. 

Because it is not interrupt driven the code is always following a set order of operation therefore it can never turn the port back to an output while the analogue pin is being read.

Your eye can not see the difference in 100us when the port is read - it really just extends the led off period by 100us.

Function decode_ANA_keys()

This is a simple range detect that returns the key pressed if the analogue value falls within a specific range.

Function edit_ds1307()

This is used in the "main" routine to allow up/down edit of the current DS1307 register values. It takes as input the DS1307 address and direction desired and increments or decrements the register value depending on dir and also limits the value that is updated to keep it in a valid range. 

Note how it manages the 'problem' addresses that contain CH and 12/24 Hour bits as well as the desired value. 

Also note how the 'one' routine uses different upper limit and lower limit controls to control operation for individual registers i.e. depending on register use the values are constrained to specific limits. 

Function clock_display()

This function provides the main display ability of the system and it works slightly differently to traditional code. This code is designed to be re-entrant and to do a different job every time it is called it is effectively a state machine with the state stored by the static variable 'digit'. 

Each time it is called digit is incremented and this chooses the next segment driver pin on PortB.

Since the function also takes two binary coded values as arguments (one for the left two 7-segment digits and one for the right two 7-segment digits). It can display a different digit every time the function is called. So by calling the function fast enough the 4x7-segment display appears to be constantly on due to persistence of vision.

The complexity in this routine is for selectively blanking left or right pairs of digits so that they can be flashed on and off. Other complexity is preserving the control bits at addresses 0x00 and 0x02. 

Note: Observe the coding of the column pins on PortB since they are not organized linearly.

Function main()

The array idx[] holds two values that are the addresses into the DS1307 clock address space. idx[0] holds an address that allows display on the left two 7-segments while idx[1] holds an address that is used to display data on the right two 7-segments.

By changing the idx values, specific register values in the DS1307 can be displayed. For instance the code for selecting hours and minutes to be displayed is :

case MODE_HRS_MINS  : idx[0] = 2;    idx[1] = 1; break;


This lets the display routine use DS1307 address 2 for the left digits and DS1307 address 1 for the right digits.  If you look at the address map of the DS1307 address 2 is used to hold the hour information while address 1 is used to hold the minutes information.

The routine

clock_display(addr1, addr2, blank)


...is used to update the display using this information.

The variable 'edit' is used both as a flag to indicate that editing is in progress and as an index into the idx array to specify which or the two array values is being incremented or decremented. If 'edit' has the value FINISHED_EDIT (=-1) then editing is not taking place or it has just finished (by the user pressing key two after the right hand digit set has been edited.

You can see how this works in the two case statements case 3 : and case : 4 for up and down editing of a value respectively. If not editing then the test() function or display_mode() function is called instead.

Flashing a digit set is performed by controlling the blankIdx value at the end of the main() function. If it is -1 then no value is displayed using the function clock_display(). The actual timing is controlled by the variable 'flash' that is incremented every pass round the while loop. 'flash' is kept to simple values of 60 and 30 and half the time (up to 30 the digit is on while it is off for the other half).

Note: If you change the update rate of the routines you need to manually adjust this flash rate as it is not interrupt driven and will therefore change.

Key Operation

In main keys are detected and a value returned in the range 1-4

Key actions:
 

Key Key function key action
1 Change mode Selects the displayed data e.g. Hours minutes, year etc.
2 Enter edit mode Enter edit mode for the current data display and edit left.
2 If in edit mode move to next digit or exit mode.
3 If in edit mode Increase the selected digits value.
3 Not in edit mode Display Pseudo-text indicating what is on the display.
4 If in edit mode Decrease the selected digits value.
4 Not in edit mode Enter test mode (each press moves to display diff. led)


Note "Not in edit mode" are extra features.

TEST and supplementary operations for the Real Time Clock

Function test() - Test 7-segment connections.

You can use this to check your 7 segment displays to see if they are connected correctly.

This routine is executed when not in edit mode and key 4 is pressed.

At each press of key4 a different led is lit (the same one in each individual 7-segment).  

Note: The display is flashed at a low rate to indicate test mode.

The order is:

  • a
  • b
  • c
  • d
  • e
  • f
  • g
Note: See the schematic for led names on each 7-segment.

Function display_mode()

This routine is similar to test except that it takes in a mode value that is the one being used in the main routine. It displays pseudo-text on the display to indicate what the current display is showing.

This routine is executed when not in edit mode and key 3 is pressed.

See Pseudo Text Display above for the pseudo-text displayed on the 4x7-segments.

Built in functions

Apparently it is common practice for compilers to automatically include some simple functions and I used them a while ago and could not figure how they were linked into the code since there was no include statement for the following functions:

  • Bcd2Dec()
  • Dec2Bcd()
  • ByteToStr()

These are rather irritating since there is inconsistency in their definition 'To' and '2' in the middle. However they are useful and get the job done. You can search online and find how others have defined these if you want to. For the definitions of their operation and prototype definitions search in the MikroC help system. 

Notes on System Implementation of the Real Time Clock

The update rate is controlled within clock_display() and its delay function call to delay_ms(1) within that routine and it only turns on the display for 1ms but this is for one digit. The routine clock_display() is effectively a re-entrant state machine and on each call services a different 7-segment digit. So effectively the total delay throughout a complete cycle (of 4 calls as there are 4 digits) is 4ms.

At 8MHz the routines are doing a lot and you need 20ms refresh rate to achieve a stable persistence of vision so the rest of the system must be going at about 16ms (not measured) since the output seems stable enough. That means there is not a lot of room to do 'other' stuff.

This is because we are using the internal clock at 8MHz. You can go far faster using an external crystal but you only need to do this if you really have to. If you do need faster operation you may run out of pins, since two would be used to drive the xtal oscillator, which is why on other projects on the site, such as the 7-segment frequency counter, a helper chip is used - in this case it is the 4017 (Johnson counter - walking one).  You could also use a serial to parallel chip such as the 595 to save pins.

However for the task it is required to do at the moment, it works just fine.

Note : The interesting way of using the RA0 pin as both a digital output and as an analogue input. See above discussion on this.

And that's it - enjoy.

An Exercise For You

A simple exercise that you might want to do is to add an alarm function - at the moment 1552 bytes of flash are used up leaving room for quite a lot of additional code 2k -1552 = space left. Adding an alarm function would simply be a matter of adding an additional definition:

e.g. MODE_ALARM

... and using some of the DS1307 RAM to store the alarm setting (since it is preserved by the battery backup when the power fails).

You would need to change the code where the mode setting is updated and the pseudo text code as well. In the main loop you would read the current Hour,Minute setting until it matched the stored value and then generate an alarm.

For output there is a spare pin RB3 - there is a 10k pull-down but this is not useful if the part has been taken out of LVP mode by programming it using the ICSP programmer - so it is free for you to attach a piezo disc (remove the resistor - prob. was not really needed anyway!).


Comments

Have your say about what you just read! Leave me a comment in the box below.

Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.



Privacy Policy | Contact | About Me

Site Map | Terms of Use