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
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
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.
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.
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; }
This is how it all looks like. If you have any query, let me know. Till then, happy coding!
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.