Interface DS1307 RTC with PIC16f877 through I2C

Previous post introduce you the I2C communication protocol in briefly and present a complete set of functions to perform I2C communication as a 'master' (from hi tech C compiler). This post will be dedicated to introduce a RTC(Real Time Clock) IC DS1307 and a simple code to perform read/write operations on RTC through I2C. This code also implemented using Hi tech C compiler and PIC16f877 as the MCU.

What is RTC? yaaa RTC stands for Real Time Clock. Most of the devices those maintaining a clock for their functions use these kind of ICs.Also DS1307 has its own capability to use backup battery(3V coin) power when main power source shutdown.
 
You can download  Datasheet for DS1307.

The features can be listed bellow,

  • Real-time clock (RTC) counts seconds, minutes, hours, date of the month, month, day of the week, and year with leap-year compensation valid up to 2100
  • 56-byte, battery-backed, nonvolatile (NV) RAM for data storage
  • Two-wire serial interface (I2C)
  • Programmable square wave output signal
  • Automatic power-fail detects and switches circuitry
  • Consumes less than 500nA in battery backup mode with oscillator running
  • Optional industrial temperature range: -40°C to +85°C
  • Available in 8-pin DIP or SOIC

Most significant thing in the IC is the battery backup power because if it a real time clock it should be run always to get the real time from it. Therefore you do non need to worry about clock it will run independently from your MCU.


Source code for DS1307 RTC

So let's move to source code. This code uses the functions implemented on previous post. Therefore it is better to download those codes and create a header file for those functions so you can re-use them latter. Don't worry a complete source code can be found at the end of this post. Here DS1307 act as a slave on the bus and our MCU act as the master on the bus.

Data Write - Slave Receiver Mode
First let's try to write a function to write a byte to DS1307's EEPROM. I refer following diagram which I grab from DS1307's datasheet to code this function.


So lets implement this diagram into code!

void ds1307Write(unsigned char address, unsigned char data){
    i2CStart();//start the i2c bus
    i2CSend(208);//send DS1307 write command, DS1307 address + direction bit(R/nW) 1101000 + 0 = 11010000 = 208
    i2CSend(address);//send the address that you need to write data on.
    i2CSend(data);//write data on the address
    i2CStop();//close the I2C bus
}

Code explained

i2CStart();//start the i2c bus

This is the function call which we create in previous post to send start condition through I2C bus. So this code is to send the start condition to slave device.


i2CSend(208);//send DS1307 write command, DS1307 address + direction bit(R/nW) 1101000 + 0 = 11010000 = 208

If you can remember, I2C communication can be done by sending 7 bits wide address and R/W bit immediately following start condition on the bus. So this is the sending of address and the direction bit to the DS1307. The address for DS1307 is 1101000 (in binary) then the direction bit R/W (1 for read 0 for write). Then whole byte is the concatenated bits of address and R/W bit,

1101000 + 0 = 11010000 = 208 (in decimal)


i2CSend(address);//send the address that you need to write data on.

Then you need to send address which you want to write data on EEPROM of DS1307.


i2CSend(data);//write data on the address

Then the data to be written on above specified address.


i2CStop();//close the I2C bus

Then stop the I2C communication.


Data Read—Slave Transmitter Mode
Now the read function. This will be some what complicated, because you need to point the address where you want to read from before initiate the read operation. I refer following diagram from datasheet to implement the function. This function will return a byte at once which is located on address specified.



unsigned char ds1307Read(unsigned char address){
    unsigned char data;

    i2CStart();//send start signal on I2C bus
    i2CSend(208);//send DS1307 dummy write command to get the pointer to our location
                //DS1307 address + direction bit(R/nW) 1101000 + 0 = 11010000 = 208
    i2CSend(address);//set the address pointer
    i2CRestart();//send stop+start signal to bus
    i2CSend(209);//send read command to DS1307
    data = i2CRead();//read the buffer for received data
    i2CStop();//close the i2c bus
    return data;
}

Code explained
Initially send the start condition to the bus as explained in previous function to initiate the I2C bus for communication.


i2CSend(208);//send DS1307 dummy write command to get the pointer to our location
             //DS1307 address + direction bit(R/nW) 1101000 + 0 = 11010000 = 208

Then we send a dummy write command to get the EEPROM's pointer to the location that we need to read from. Therefore this byte will send to the DS3107 with the write operation direction bit (same as previous function). But we do not write anything on it.


i2CSend(address);//set the address pointer

Then send the address that we need to point on to set the EEPROM's pointer to the location.


i2CRestart();//send stop+start signal to bus

As I explained earlier we are not going to write anything, only purpose to issue write operation is set the pointer to the location.Therefore send the 'repeated start' condition to the bus.


i2CSend(209);//send read command to DS1307

Now we can send the read operation to DS1307. The value can be calculated as follows,
DS1307 address (1101000) + direction bit (1 for read) = 1101001 = 209 (in decimal


data = i2CRead();//read the buffer for received data

Now you can read data from DS1307 and hold these data on "data" variable. If you have any doubt about these function which are calling in this post please refer previous post. Because, none of the function that we calling here are inbuilt functions.


i2CStop();//close the i2c bus
return data;


Then close the I2C bus by sending stop condition and return the data which read from DS1307's EEPROM

DS1307's memory
Now you have functions for read and write operations. Now let's try to extract data from DS1307. We will need its memory map to do that. Memory map is,


Now let's try to read seconds from the memory as you can see here the Seconds data contain in 3 parts. Those are CH bit, 10 Seconds and Seconds. If you read the datasheet, you can find the functionality of CH bit. It is the clock enable bit. If you set CH = 0 the clock is enabled and running, if you set CH = 1 the clock will be disable. I will explain 10 Seconds and Second thing from a example,

Let's say the seconds on the clock is 36, then,
10 Seconds  = 3 (011)
Seconds = 6 (110)
and let CH = 0

Now the byte on the 0th position is 0 + 011 + 110 = 0011110 (plus mean concatenate). So I think you got the trick or still complicated...! Don't worry I have created functions to get and set Seconds, Minutes, Hours (in 12 hour or 24 hour mode), and Day. You can code your own functions for other things (Date, month and year). Only thing is to understand the memory map of the IC and code it.

You can download a complete source code and test program at the end of this tutorial.
The complete code is,

/**
 * Function will read a byte from the @address location of the memory
 * @param address - address that you need to read
 * @return received data byte from DS1307
 */
unsigned char ds1307Read(unsigned char address){
    unsigned char data;

    i2CStart();//send start signal on I2C bus
    i2CSend(208);//send DS1307 dummy write command to get the pointer to our location
                //DS1307 address + direction bit(R/nW) 1101000 + 0 = 11010000 = 208
    i2CSend(address);//set the address pointer
    i2CRestart();//send stop+start signal to bus
    i2CSend(209);//send read commad to DS1307
    data = i2CRead();//read the buffer for received data
    i2CStop();//close the i2c bus
    return data;
}

/**
 * Write to DS1307's eeprom
 * @param address - address to write data (0 - 7)
 * @param data - 8bit data to be written
 */
void ds1307Write(unsigned char address, unsigned char data){
    i2CStart();//start the i2c bus
    i2CSend(208);//send DS1307 write command, DS1307 address + direction bit(R/nW) 1101000 + 0 = 11010000 = 208
    i2CSend(address);//send the address that you need to write data on.
    i2CSend(data);//write data on the address
    i2CStop();//close the I2C bus
}

/**
 * get seconds from DS1307's eeprom
 * @return seconds on DS1307
 */
unsigned char ds1307GetSec(){
    unsigned char sec10;
    unsigned char sec1;

    unsigned char bcdSec = ds1307Read(0);//get the 0th location data(CH + 10Sec + 1Sec)[in BCD format]

    bcdSec = bcdSec & 0b1111111;//ignore CH bit (ignore MSB)
    sec10 = bcdSec >> 4;//shift 4 bits right to ignore 1sec position(ignore 4 LSB)
    sec1 = bcdSec & 0b1111;//ignore 4 MSBs

    return sec10 * 10 + sec1;//return the seconds
}

/**
 * get minutes from DS1307's eeprom
 * @return minutes on DS1307
 */
unsigned char ds1307GetMin(){
    unsigned char min10;
    unsigned char min1;

    unsigned char bcdMin = ds1307Read(1);//get the 1st loaction's data(0 + 10Min + 1Min)[in BCD format]

    bcdMin = bcdMin & 0b1111111;//ignore MSB
    min10 = bcdMin >> 4;//shift 4 bit right to ignore 1min position(ignore 4 LSB)
    min1 = bcdMin &0b1111;

    return min10 * 10 + min1;//return the minnutes
}

/**
 * This function will return the hours in 24 hour format by reading from DS1307
 * @return hours from DS1307 in 24 hours format
 */
unsigned char ds1307GetHours(){
    unsigned char hour10;
    unsigned char hour1;
    unsigned char retHour;

    unsigned char bcdHour = ds1307Read(2);//get the 1st loaction's data(0 + 12 or 24 hour mode + 10hour(first bit) or PM/AM + 10hour(second bit) + 1hour)[in BCD format]
    bcdHour = bcdHour & 0b1111111;//ignore MSB(7th bit)
    if(bcdHour > 63){//is in 12 hours mode?
        bcdHour = bcdHour & 0b111111;//ignore MSB(6th bit)

        hour10 = (bcdHour & 0b11111) >> 4;//get the hour10 position by ignoring MSB(5th bit) and shift 4 bits to right
        hour1 = bcdHour & 0b1111;//get hour1 position by getting only 4 LSBs
        retHour = hour10 * 10 + hour1;//calculate the hours using hour10 and hour1

        if(bcdHour > 31){//is PM?
            if(retHour != 12)
                retHour = retHour + 12;
        }
    }else{
        bcdHour = bcdHour & 0b111111;//ignore MSB(6th bit)
        hour10 = bcdHour >> 4;//shift 4 bit to right to get 5th and 4th bits
        hour1 = bcdHour & 0b1111;//get 4 LSBs

        retHour = hour10 * 10 + hour1;//calculate the hours using hour10 and hour1
    }

    return retHour;
}

/**
 * reads the Day value from DS1307 memory
 * @return the number for the specific day (1=SUN, 2=MON,... 7=SAT)
 */
unsigned char ds1307GetDay(){
    return ds1307Read(3);//read value on 3rd location of DS1307 and return it
}

/**
 * set seconds to DS1307 with CH bit
 * @param newSec - seconds to be set (0 - 79)
 * @param chBit - Clock enable bit (0 = clock enable, 1 = clock disable)
 */
void ds1307SetSecond(unsigned char newSec, unsigned char chBit){
    unsigned char bcdNum;
    if(newSec > 79)
        return;//to avoid writing to CH when writing to second feild

    ds1307separateDigits(newSec);
    bcdNum = digitPlaceVal[1] << 4;//shift 4 bits left
    bcdNum = bcdNum | digitPlaceVal[0];//ORing with placeValue1

    //add CH bit
    if(chBit == 1)
        bcdNum = bcdNum | 0b10000000;

    ds1307Write(0, bcdNum);//write to sec
}

/**
 * Set DS1307 minutes
 * @param newMin - minutes to be set(0 - 127)
 */
void ds1307SetMinutes(unsigned char newMin){
    unsigned char bcdNum;
    if(newMin > 127)
        return;//to avoid overlimit

    ds1307separateDigits(newMin);
    bcdNum = digitPlaceVal[1] << 4;//shift 4 bits left
    bcdNum = bcdNum | digitPlaceVal[0];//ORing with placeValue1

    ds1307Write(1, bcdNum);//write to min
}

/**
 * Set DS1307 hours in 12 hour mode
 * @param newHour - hours in 12 hour mode (0 - 19)
 * @param pm_nAm - AM/PM bit (1 for PM, 0 for AM)
 */
void ds1307SetHours12(unsigned char newHour, unsigned char pm_nAm){
    unsigned char bcdNum;
    if(newHour > 19)
        return;//avoid overlimit

    ds1307separateDigits(newHour);
    bcdNum = digitPlaceVal[1] << 4;//place hour's placeValue10
    bcdNum = bcdNum | digitPlaceVal[0];//ORing with placeValue1

    bcdNum = bcdNum | 0b1000000;//set 12 hour mode
    if(pm_nAm)//PM?
        bcdNum = bcdNum | 0b100000;//set PM

    ds1307Write(2, bcdNum);//write to hours
}

/**
 * Set DS1307 hours in 24 hour mode
 * @param newHour - hours in 24 hour mode (0 - 29)
 */
void ds1307SetHour24(unsigned char newHour){
    unsigned char bcdNum;
    if(newHour > 29)
        return;//avoid overlimit

    ds1307separateDigits(newHour);
    bcdNum = digitPlaceVal[1] << 4;//place hour's placeValue10
    bcdNum = bcdNum | digitPlaceVal[0];//ORing with placeValue1

    ds1307Write(2, bcdNum);//write to hours
}

/**
 * Set day to DS1307 (range 0-7)
 * @param newDay - day to be set (1 = SUN, 2 = MON,...., 7 = SAT)
 */
void ds1307SetDay(unsigned char newDay){
    if(newDay > 7)
        return;//avoid overlimit

    ds1307Write(3, newDay);//write to day
}

/**
 * This function accept 2 digit number and separate it into 10 and 1 place value and assign to digit array
 *
digit[0] = place value 1
 *
digit[1] = place value 10
 * @param num2 - 2 digit number(0 - 99)
 */
void ds1307separateDigits(unsigned char num2){
    digitPlaceVal[1] = num2 / 10;
    digitPlaceVal[0] = num2 - (digitPlaceVal[1] * 10);
}


Download
A complete source code and test program.

7 comments:

  1. I have an doubt ...wat is the output of the read function.ie,type of data....

    ReplyDelete
    Replies
    1. unsigned char is the type which the function return.

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
    Replies
    1. Thank you for info, But please don't use my blog for any business purposes, If you need to send any information for me, Please send me an email to my personal mail which you can find on
      http://milindapro.blogspot.com/p/about-me.html

      Delete