Our previous tutorial was a detailed guide about what an I2C communication protocol is and how to use it in a PIC microcontroller. In this tutorial, we will build upon the concepts and look at how you can use I2C protocol for communication between an RTC and pic microcontroller.

An RTC is a real-time clock which is responsible for keeping real-time track of date and time. Most of these RTC’s have built-in oscillator circuit which requires external crystal for their operation. These RTC’s also come with an internal battery or a provision for an external battery which keeps the RTC clock running when the system power is turned off, thus keeping an accurate track of time. The RTC which we will be using is PCF8653 developed by NXP.

PCF8563 Real-time clock

Following image shows PCF8563 RTC

PCF8563 RTC

Developed by NXP, the PCF8563 is a Real-time clock based on a 32.768KHz crystal. It comes with one integrated capacitor at OSC0 pin and can communicate with a data rate of 400KHz. PCF8563 has two distinct addresses, i.e Read(A3h) and write(A2h) address. A Master device can initiate communication with PCF8563 by sending A3h for a read operation or A2h for a write operation on the bus.

PCF8563 provides a separate register for each of its timing function, i.e. sec, minutes, hours, days, weekdays, month and year. Along with the timing registers, PCF8563 also provides a set of registers for setting an alarm as well as a timer. All these registers are read/write registers and have auto-incrementing register addresses. The programmer must specify the starting address for a read or write operation. Once the operation is performed, the register address is automatically incremented, and next operation will take place on the subsequent address. It is also important to note, that these registers are coded in binary decimal format(BCD).

Below are a few key points to remember, followed by the associated registers with PCF8563.

Key points about PCF8563

  • Provides real-time track of date and time along with alarm and timer functions.
  • Communicates with a data rate of 400Khz at VDD = 1.8V to 5.5V.
  • One integrated capacitor on OSC0 pin.
  • Provides a programmable clock output for other peripheral devices if needed.
  • A3H read address, A2H write address.
  • PCF8563 clock can operate at a voltage as low as 1V up to 5.5V at room temperature.

 

Associated register with PCF8563

Following image shows register structure in PCF8563 RTC.

Register structure in PCF8563 RTC.

The first two registers contain several RTC control and status function. These registers can be used to enable or disable RTC’s clock, power-on reset override facility as well as enable or disable Alarm or timer Interrupt. These registers also contain Alarm and timer flags.

The clockout control register can be used to output a fixed frequency of 32.768KHz, 1.024Khz, 32Hz or 1Hz from CLKOUT pin which can be used as a clock source for other peripherals.

 

Time Registers

These are a set of 6 registers for date and time. The user must write to these registers which act as a base point for date as well as time. These registers begin incrementing thereafter once the write cycle is completed. The user can then read from these registers to keep track of date as well as time. 

Following image shows time and date registers in PCF8563 RTC

Time and Date registers

In the above table, bits represented by ‘x’ are not implemented. These bits must be written as 0 during a write operation. All the registers in PCF8563 are coded in BCD format.

The first bit in the second register signifies clock integrity. A ‘1’ in bit VL is an indication that the timing ‘might’ be wrong. This bit is set when the power has just been applied or when VDD drops below Vlow. It is also set when the oscillator stops running. The VL bit can only be cleared in software and only when the oscillator is running.

C bit in century_months register signifies a span of 99 years. This bit is set when the year register overflows from 99 to 00.

Apart from these 2 bits, the rest of the bits are pretty straight forward. You simply write the values to these registers and then read from them.

 

Alarm and Timer registers

PCF8563 has 4 alarm registers, Minute_alarm, Hour_alarm, Day_alarm, and Weekday_alarm. All the four registers have their 7th bit assigned for enabling or disabling them. The remaining bits are used to write alarm information in BCD just like in Time registers, with some of the bits being ‘x’ Not implemented.
When an alarm condition is met, the AF flag in control_status_2 reg is set. This bit must be cleared by software. AF flag is again set when the next alarm condition occurs. Alarm Interrupt can be enabled or disabled using AIE bit in control_status_2 reg.

For Timer Function, PCF8563 RTC has 2 registers, Timer control, and Timer. The 7th bit of timer control register is used to enable or disable the timer. The next 5 bits are not implemented. The remaining 2 bits are used to select the clock source frequency for the timer which can be either 4096Hz, 64Hz, 1Hz or 1/60Hz. The second is the timer register which holds the countdown value. The user must write the countdown value to the timer register(00 to FF). When the timer rolls over, TF bit is set and can only be cleared through software. Timer interrupt can be enabled if TIE bit in control_status_2 reg is set.

 

Typical Read/Write sequence for PCF8563

The following is a typical write sequence for setting date and time in PCF8563 RTC.

  • Generate the START condition.
  • 0xA2: Slave address and write.
  • 0x00: Write the address from where you want to start ‘writing’.
  • 0x00: Write to Control status register 1.
  • 0x00: Control status register 2.
  • 0x00: setting seconds to 0.
  • 0x15: setting minutes to 15.
  • 0x10: setting hours to 10.
  • 0x03: setting day 3.
  • 0x06: Setting weekday to Sunday.
  • 0x02 Setting Month to February.
  • 0x19 Setting year to 19.
  • Generate STOP condition.

Read sequence for PCF8563.

  • Generate START condition.
  • 0xA2: Slave address and write.
  • 0x02: Address from where you want to read. (Obviously, you would want to read from seconds).
  • Generate a repeated start condition.
  • Generate the start condition.
  • 0xA3: Slave address and Read.
  • Set RCEN bit in PIC16F887 to enable Reception. (SSPCON2 register).
  • Read seconds received in SSPBUF register. Initiate the acknowledge sequence.
  • Repeat the above 2 steps until you receive years.
  • Initiate No acknowledge sequence.
  • Generate STOP condition.

 

Circuit connections for interfacing PIC16F887 with PCF8563 RTC.

Image shows interfacing of pic microcontroller with rtc.

Interfacing PIC microcontroller with RTC (PCF8563).

The reason we’re only using one capacitor is that PCF8563 already has an integrated capacitor of 25pf at OSC0 pin. The targeted load capacitance is 12.5pf. So it is highly advisable, that the 32.768Khz crystal must have a load capacitance rating of 12.5pf.

 

Program for Interfacing PCF8563 RTC with PIC16F887

The program given below is written in MPLAB X. No RTC or LCD libraries were used.

// Configuration bits
#pragma config FOSC = INTRC_NOCLKOUT
#pragma config WDTE = OFF       
#pragma config PWRTE = OFF      
#pragma config MCLRE = ON       
#pragma config CP = OFF         
#pragma config CPD = OFF        
#pragma config BOREN = ON      
#pragma config IESO = OFF       
#pragma config FCMEN = OFF      
#pragma config LVP = OFF        

#pragma config BOR4V = BOR40V   // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V)
#pragma config WRT = OFF        

#include<xc.h>
#include<pic.h>
#include<pic16f887.h>
#define _XTAL_FREQ 8000000

// LCD functions
void lcd_cmd(unsigned char c);
void lcd_data(unsigned char d);
void lcd_init(void);
void enable(void);


// I2c Functions
void i2c_init(void);            // Function to initialize I2C
void i2c_transmit(void);        // Function to transmit data
void i2c_receive(void);         // Function to receive data
void i2c_busIdle(void);         // Function to check if the bus is idle
void i2c_start(void);           // Start condition function
void i2c_stop(void);            // Stop condition function
void i2c_repeatedStart(void);   // Function to initiate repeated start
void i2c_ack(void);             // Acknowledge function 
void i2c_Noack(void);           // No Acknowledge Function
void receive_enable(void);      // Function to enable Reception of data


void display_clock(void);          // Function to display data 
void display(unsigned int);        // Convert and display received data from HEX to ASCII
void check_weekday(unsigned int);  // Function to check Weekday

unsigned int sec,min,hour,day,weekday,month,year;
void disp_wd(char *wd);


int main()
{
  
    OSCCON = (OSCCON | 0x70);  // Initializing internal oscillator to 8MHz
    TRISC = 0x18;              // Setting SCL and SDA data direction to input 
    TRISD = 0x00;
    TRISB = 0x00;
    PORTB = 0x00;
    PORTD = 0x00;
    lcd_init();                // Function to initialize LCD
    i2c_init();               // I2C initialization
    i2c_transmit();            // Transmit data i.e sec, min, hours etc
 
   while(1)
   {
       lcd_cmd(0x80);
       i2c_receive();          // Receive data  
       display_clock();        // Display date and time
         
       lcd_cmd(0x89);
       if(hour>0x11)
           disp_wd("PM");
       else
           disp_wd("AM");

       lcd_cmd(0xc9);
       check_weekday(weekday);    // Display Weekdays
       
   }        
    return 0;
}

   
   
void i2c_init(void)
{
    SSPCON = 0b00101000;
    SSPCON2 = 0x00;
    SSPSTAT = 0x00;
    SSPADD = 0x04;
    //check status
}

void i2c_busIdle()
{
    while((SSPCON2 & 0x1F) || (SSPSTAT & 0x04));    // Check if bus is idle
}

void i2c_start(void)         // Function to initiate start condition
{
SEN = 1;
i2c_busIdle();
}

void i2c_stop(void)          // Function to initiate stop condition
{
    PEN=1;
    i2c_busIdle();
}

void i2c_repeatedStart(void)    // Initiating repeated start condition
{
    RSEN=1;
    i2c_busIdle();
}

void ack(void)                 // Function to initiate acknowledge sequence 
{
    ACKDT = 0;
    ACKEN = 1;
    i2c_busIdle();
}

void Noack(void)              // Function to initiate No acknowledge sequence
{
    ACKDT = 1;
    ACKEN = 1;
    i2c_busIdle();
}

void receive_enable(void)      // Function to enable reception
{
    RCEN =1;
    i2c_busIdle();
}

void i2c_transmit()          // Function to transmit data
{
    i2c_start();    
    i2c_busIdle();
    
    SSPBUF = 0xA2;           // 'Write' address of pcf8563
    i2c_busIdle();
    SSPBUF = 0x00;           // Register address from where you want to start writing
    i2c_busIdle();
    SSPBUF = 0x00;           // control status reg1
    i2c_busIdle();
    SSPBUF = 0x00;           // control status reg2
    i2c_busIdle();
    SSPBUF = 0x00;           // Setting integrity and Seconds to zero
    i2c_busIdle();
    SSPBUF = 0x15;           // Setting Minutes to 15
    i2c_busIdle();
    SSPBUF = 0x10;           // Setting Hour to 10
    i2c_busIdle();
    SSPBUF = 0x04;           // Setting day to 4th 
    i2c_busIdle();
    SSPBUF = 0x00;           // Setting weekday to monday
    i2c_busIdle();
    SSPBUF = 0x02;           // Setting month to February
    i2c_busIdle();
    SSPBUF = 0x19;           // Setting year to 2019
    
    i2c_busIdle();
    i2c_stop();
}

void i2c_receive(void)        // Function to receive sec,min,... etc
{
  i2c_start();  
    
  SSPBUF = 0xA2;              // Slave address + write
  i2c_busIdle();
  
  SSPBUF = 0x02;              // Write memory address from where you want to start reading 
  i2c_busIdle();
  
  i2c_repeatedStart();        // Repeated start condition
  i2c_start();                // start condition
    
  SSPBUF = 0xA3;              // Slave address + read
  i2c_busIdle();
 

 receive_enable();            // Receive seconds
 sec = SSPBUF;
 i2c_busIdle();
 ack();
 
 receive_enable();            // Receive Minutes
 min = SSPBUF;
 i2c_busIdle();
 ack();   
 
 receive_enable();            // Receive Hours
 hour = SSPBUF;
 i2c_busIdle();
 ack();
 
 receive_enable();            // Receive days
 day = SSPBUF;
 i2c_busIdle();
 ack();
 
 receive_enable();            // Receive Weekdays
 weekday = SSPBUF;
 i2c_busIdle();
 ack();
 
 receive_enable();            // Receive months
 month = SSPBUF;
 i2c_busIdle();
 ack();
 
 receive_enable();            // Receive Years
 year = SSPBUF;
 i2c_busIdle();
 Noack();

 i2c_stop();
}


void display(unsigned int x)    //Function to convert received hex no to Ascii
{                              
    lcd_data((x/16)+48);
    __delay_ms(5);
    lcd_data((x%16)+48);
    __delay_ms(5);
}

void display_clock(void)       // Function to display date and time
{
    
      hour = hour&0x3f; 
      display(hour);
      lcd_data(':');
       
      min = min&0x7f;
      display(min);
      lcd_data(':');
      
      sec = sec&0x7f; 
      display(sec);
       
      
      lcd_cmd(0xc0);
      __delay_ms(5);
       
      day = day&0x3f;
      display(day);
      lcd_data('/');
       
      month = month&0x1f;
      display(month);
      lcd_data('/');
       
      year = year&0xff;
      display(year);
      
}


void check_weekday(unsigned int weekday)  // Function to check the weekday
{
    switch(weekday)
    {
        case 0: 
            disp_wd("MON");
            break;
        case 1: 
            disp_wd("TUE");
            break;
        case 2:
            disp_wd("WED");
            break;
        case 3:
            disp_wd("THU");
            break;
        case 4:
            disp_wd("FRI");
            break;
        case 5: 
            disp_wd("SAT");
            break;
        case 6: 
            disp_wd("SUN");
            break;
    }
}

void disp_wd(char *wd)   // Function to display weekday
{
    while(*wd != '\0')
    {
        lcd_data(*wd);
        wd++;
    }
    
}

// LCD FUNCTIONS

void lcd_init(void)
{
    // LCD initial initialization 
    
    __delay_ms(15);
    lcd_cmd(0x03);
    __delay_ms(5);
    lcd_cmd(0x03);
    __delay_ms(5);
    lcd_cmd(0x03);
    __delay_ms(5); 
    lcd_cmd(0x02);
    __delay_ms(5);
    
    // LCD command setting initialization
    
    lcd_cmd(0x28);       // 4 bit interface length. 2 rows enabled.
    __delay_ms(5);
    
    lcd_cmd(0x10);       // Move cursor or shift display
    __delay_ms(5);
    
    lcd_cmd(0x0c);       // Enable display. Cursor off.
    __delay_ms(5);
    
    lcd_cmd(0x06);      // Increment cursor position after each byte 
    __delay_ms(5);
    
    lcd_cmd(0x01);      // clear display
    __delay_ms(5);
    
}

void lcd_cmd(unsigned char c)
{
    RD7 = 0;
    RD6 = 0;
    PORTB = (PORTB&0x0f)|(0xf0&c);
    enable();
    PORTB = ((PORTB&0x0f)|(0x0f&c)<<4);
    enable();
}

void lcd_data(unsigned char d)
{
    RD7 = 1;
    RD6 = 0;
    PORTB = (PORTB&0x0f)|(0xf0&d);
    enable();
    PORTB = ((PORTB&0x0f)|(0x0f&d)<<4);
    enable();
    
}

void enable(void)
{
    RD5 = 1;
    __delay_ms(5);
    RD5 = 0;
}

Following image show date and time on character LCD using RTC.

Displaying date and time using PCF8563 RTC.

Image shows interfacing of PIC microcontroller with PCF8563 using I2C.

PIC16F887 interfacing with PCF8563 using I2C.

Image shows date and time on character lcd.

Date and Time.

This is how it all looks like. If you have any query, let me know. Till then, happy coding!

 


Moiz

Electronics engineer graduated from M.H. Saboo Siddik college of engineering. Currently working as Jr. Innovative engineer. Skilled in 8051, PIC and ARM microcontrollers. Circuit analyzation and Debugging. Constantly looking to acquire more skills which would help myself to become more proficient in embedded domain. Founder and blogger at techetrx.com LinkedIn profile: https://www.linkedin.com/in/moiz-shaikh-305294137

4 Comments

Jean-Pierre Desrochers · March 7, 2020 at 3:39 am

Thanks for this very informative article ! At last a well c coded program for PIC (not Arduino).
A program made from scratch without any libraries. All these tutorials around about programming the PCF8653 with Arduino c libraries don’t show the actual c code that is ‘behind’ the libraries so when you
program using HiTech or X8 c compile you are left with at least nothing..
Some questions here: So far I’m struglling with the PCF8653 that is part of my chinese demo board for the PIC families. It is connected with a 24C02 I2C EEPROM sharing both present I2C wires. I can easily communicate with the 24C02 with I2C code. No problem. But when I try to communicate with the PCF8653 writing hour/date then reading back in a while(1) loop I only get the first written values
and these values won’t increment ! So I checked my demo board schematic, took some scope measurements to finaly find that the PCF8653 internal 32khz clock is not running. Dead. So I suspected the RTC chip itself and replaced it. Same problem ! So I checked again the board’s schematic to find that the crystal connected to RTC pins OSCI & OSCO as it’s capacitor (22pf)
not connected to pin 1 OSCI but to pin 2 OSCO (???) I then checked the PCF8653 specs and they ask for a small cap connected at OSCI (pin1). What is funny is many web examples show a 22pf cap connected to OSCO (pin2), some to OSCI (pin1).. What is your point on that ? I saw your cap is connected to OSCI.
Thanks for your support.

    Moiz · March 22, 2020 at 5:20 pm

    You’re welcome. I’m Gald you liked it.
    Just to be on the same page, I assume you meant pcf8563 as you’ve written pcf8653. Regarding the capacitor, The datasheet of PCF8653 mentions that it has an integrated capacitor on OSC0 so you’ll only need one capacitor to be connected to OSC1.

Jean-Pierre Desrochers · March 7, 2020 at 5:44 am

Update..
I mounted another PCF8653 (S0-8 package) IC on a DIP soket with small wires
to make some more tests.. Dawned me !! I did not see the CLKOUT pin was –> opendrain !!
I had no pull-up resistors attached. So.. Now I can see a 32khz square wave coming out telling
that the IC internal clock is running. Fine ! I soldered the 22pf to the pin#1 (OSCI) of the RTC
as specified in the specs. Now when I run your program, write the clock’s time registers then
read them back in a while(1) loop I only read high levels from the RTC SDA pins during the read time.
The SDA pin is alive and changing state but it always shows the same values in the hour,min,sec,dow,day,month,year variables which are 0x3F, 0x7F, 0xFF, 0xFF, 0xFF etc..
Again, the SDA pin is not stuck at a high level.. it change states.
Some differences from your listing:
-I use a PIC16F877 (not a PIC16F887)
-I didn’t have a 8mhz crystal, I use a 20Mhz. So I changed these lines in the code:
//#include
#define _XTAL_FREQ 20000000
__CONFIG(FOSC_HS & WDTE_OFF & LVP_OFF);

void i2c_init(void)
{
// Your original I2C setup @ 8Mhz for 400khz I2C bus
/*
SSPCON = 0b00101000;
SSPCON2 = 0x00;
SSPSTAT = 0x00;
SSPADD = 0x04; // 400khz I2C bus @ 8Mhz clk
//check status
*/

// My I2C setup @ 20Mhz for 400khz I2C bus
TRISC3=1; // set SCL and SDA pins as inputs
TRISC4=1;

SSPCON = 0x38; // set I2C master mode //SSPCON = 0b00101000;
SSPCON2 = 0x00;
//SSPSTAT = 0b11000000;
SSPADD = 0x0B; // I2C 400khz bus @ 20Mhz clk –> SSPADD = ((Fosc/BitRate)/4)-1

CKE=1; // use I2C levels worked also with ‘0’
SMP=1; // disable slew rate control worked also with ‘0’
PSPIF=0; // clear SSPIF interrupt flag
BCLIF=0; // clear bus collision flag
}

Do you see something wrong ??
Jaen-Pierre

    Moiz · March 22, 2020 at 6:35 pm

    Is your code exactly the same as mine, apart from the difference you’ve mentioned above? You can try using 0x0C in your SSPADD reg.
    Secondly If you are using a CRO, you can monitor the seconds data during the read sequence(on SDA pin) and check if it increments by 1 bit every second.

Leave a Reply

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