# Pic Lab, PIC16, Experiment #15, Real-time clock (RTC) DS1307

I got the package from China with some ICs I wanted to try before embedding them to the clock project I want to make. Have to warn: my point of interest was just a time, the date was out of the scope, so nothing about it will be investigated further.

Goal: To write the value of the time/read the time and output it thru UART from the DS1307 chip

Tools: PIC16f628a, DS1307, MAX232 level converter, devboard, proteus.

The DS1307 IC is a real-time clock module, controlled by the I2C protocol. There are plenty of microcontrollers with the RTC on board, but I wanted to try the most popular one with tons of info on the internet. First thing first – the datasheet application scheme and repeating it in proteus:

Also from the datasheet – the memory structure, we will need it for sure:

As I mentioned already, I am interested just in the time. Don’t care about the date, the month, or the year. Therefore, we need just 3 addresses – 00h, 01h and 02h. The data is saved in a BCD (binary coded decimal) format.

Shortly about BCD: The mixed data format: the 8 number is split into two parts, in the least significant part we have the usual binary representation, in the most significant part can be thought of as a digit in binary form, right shifted by 4 bits and multiplied by 10. The easiest way to think about the format is that each decimal position can be represented as 4 bits. Almost as hexadecimal, just here we are limited to 0..9. An example – 0b00110101 => 0b0011 = 3*10 = 30,  0b0101 = 5 – the resulting digit = 35.

Thus, we should preprocess our normal number before writing it to the register or reading it back. I have created two functions for this purpose: one is for decimal to BCD and another is for, obviously, converting BCD to decimal.

BCD to decimal conversion:

unsigned char BCDconv (unsigned char source) {

unsigned char temp_min=0;    //ones
unsigned char temp_maj=0;    //tens
temp_min = source&15;        //lsb to variable (15 = 0b00001111)
temp_maj = source >> 4;
temp_maj *= 10;
return temp_maj+temp_min;    //return the decimal number
}

Decimal to BCD:

unsigned char DCBconv (unsigned char source) {

unsigned char temp_min=0;
unsigned char temp_maj=0;
temp_maj = source/10 ;
temp_min = source - temp_maj*10;
temp_maj <<= 4;
return temp_maj+temp_min;
}

The next thing is to learn how to write/read to/from RTC IC. Again, the datasheet has a pretty picture:

This is a writing procedure, that looks fairly simple. Before that we need to know how to work with i2c standard, luckily I have it already.

void SetHour(unsigned char hours) {

i2c_start();
i2c_tx(0b11010000);
i2c_tx(0x02);                                            //rtc hour address
i2c_tx(DCBconv(hours));                     //push the right value
i2c_stop();
}

void SetMin(unsigned char minutes) {        //write minutes

i2c_start();
i2c_tx(0b11010000);
i2c_tx(0x01);                                             //rtc minuntes address
i2c_tx(DCBconv(minutes));                    //push the right value
i2c_stop();
}

void SetSeconds(unsigned char seconds) {     //write seconds
i2c_start();
i2c_tx(0b11010000);
i2c_tx(0x00);                                             //rtc seconds address
i2c_tx(DCBconv(seconds));                    //push the right value
i2c_stop();

}

Reading has one tiny thing I hadn’t noticed at the moment:

The datasheet says: The DS1307 must receive a Not Acknowledge to end a read.

I completely missed that and actually was sending the ACK bitА. To just bring your attention again to it, there is a picture from the datasheet:

Remember: read the datasheet carefully, don’t be like me!

The time for reading procedures has come:

unsigned char ReadHour() {

unsigned char temp = 0;
i2c_start();
i2c_tx(0b11010000);
i2c_tx(0x02);
i2c_start();
i2c_tx(0b11010001);
temp = i2c_rx(0);          //nack
i2c_stop();

return BCDconv(temp);

}

unsigned char ReadMin() {

unsigned char temp = 0;
i2c_start();
i2c_tx(0b11010000);
i2c_tx(0x01);
i2c_start();
i2c_tx(0b11010001);
temp = i2c_rx(0);
i2c_stop();

return BCDconv(temp);

}

unsigned char ReadSeconds() {

unsigned char temp = 0;
i2c_start();
i2c_tx(0b11010000);
i2c_tx(0x00);
i2c_start();
i2c_tx(0b11010001);
temp = i2c_rx(0);

i2c_stop();

return BCDconv(temp);

}

Now we need something to understand if we have a proper time, I chose the USART (UART) protocol for this procedure. First, something to display the time:

void ShowTime() {

printf("\fTime - %d :", ReadHour());
printf(" %d :", ReadMin());
printf(" %d ", ReadSeconds());
printf("\r\n*********************");
printf("\r\n 1 - Reload time");
printf("\r\n 2 - Setup time");

}

The function displays the next menu:

“1” – the time is refreshed, I am not refreshing the data constantly for purpose, did not want to catch any extra bugs.

“2” – the setup time procedure call.

void SetupTime() {

unsigned char state = 1;
unsigned char temp_min = 0;
unsigned char temp_maj = 0;
unsigned char temp = 0;

while(state){                //set hours
printf("\fEnter hours - ");
temp_maj = getch();
printf(" %c", temp_maj);
temp_min = getch();
printf("%c", temp_min);
temp = getch();
if (temp == 13) {
temp_maj -= 48;
temp_min -= 48;
temp = temp_maj*10 + temp_min;
if (temp < 24) {
SetHour(temp);
state = 0;
}
}
}

state = 1;
while(state){              //set minutes
printf("\fEnter minutes - ");
temp_maj = getch();
printf(" %c", temp_maj);
temp_min = getch();
printf("%c", temp_min);
temp = getch();
if (temp == 13) {
temp_maj -= 48;
temp_min -= 48;
temp = temp_maj*10 + temp_min;
if (temp < 61) {
SetMin(temp);
state = 0;
}
}
}

state = 1;
while(state){              //set seconds
printf("\fEnter seconds - ");
temp_maj = getch();
printf(" %c", temp_maj);
temp_min = getch();
printf("%c", temp_min);
temp = getch();
if (temp == 13) {
temp_maj -= 48;
temp_min -= 48;
temp = temp_maj*10 + temp_min;
if (temp < 61) {
SetSeconds(temp);
state = 0;
}
}
}

ShowTime();
}

and the main.c file of course:

void main(void){

unsigned char input;
CMCON = 0x07;
INTCON=0;   // no interrups
init_comms();   // USART init
SetHour(15);    // random value for the time
SetMin(38);
SetSeconds(23);
ShowTime();     //show the men

for(;;){
input = getch(); //key polling
switch (input) {
case 49 : ShowTime();  //1 pressed - refresh the screen
break;
case 50 : SetupTime(); //2 pressed - setup the time
break;
}
}
}

That’s it, the video with real device work example is shown below: