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 Menu"); 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: