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.
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
:
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:
Hours, Minutes.
Minutes, Secs.
Day, Month.
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
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!).
A PIR sensor lets your Arduino sense movement without contact. This tutorial covers PIR sensor basics, connecting one to an Arduino board and coding a motion detector.
Arduino Hall Effect Sensor: Add magnetic sensing superpowers to your Arduino projects with an easy-to-use hall effect sensor. With full code and layout...
Get started with an Arduino humidity sensor using the DHT11, which reports both humidity and temperature. Complete guide with full code for using this sensor
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.