This MPLAB project shows how to build a simple real time clock using PIC12F1822 microcontroller and DS3231 RTC chip (or DS1307 RTC) where time and date are displayed on 16×2 LCD. The DS3231 as well as the 16×2 LCD are connected to the same I2C bus with the PIC12F1822 microcontroller. The LCD is provided with PCF8574 I/O expander which makes it an I2C LCD. This project works also with DFRobot I2C LCD displays.
The compiler used in this project is Microchip MPLAB XC8 (MPLAB X IDE with MPLAB XC8 compiler).
Last time I made an interfacing of PIC12F1822 and I2C LCD, project link is below:
Interfacing I2C LCD with PIC microcontroller | MPLAB Projects
In this project the DS3231 (DS1307) and the I2C LCD share the same I2C bus which means the SDA lines of the two devices are connected together and the SCL lines also, this connection minimizes number of pins used.
Time and date are displayed on the 16×2 LCD and they can be set with two push buttons connected to the PIC12F1822 microcontroller. With two pins for the I2C bus and two other pins for the push buttons, the microcontroller will use 4 pins (if internal oscillator is used).
Hardware Required:
The components required for this project are listed below.
- PIC12F1822 microcontroller
- DS3231 (or DS1307) board
- 16×2 LCD screen
- PCF8574 I/O expander (or PCF8574A) —-> PCF8574 datasheet
- 2 x pushbutton
- 5 x 10k ohm resistor
- 330 ohm resistor
- 10k ohm variable resistor or potentiometer
- 3V coin cell battery
- 5V source
- Breadboard
- Jumper wires
The Circuit:
The following image shows project circuit diagram with DS3231 board.
(All grounded terminals are connected together)
The PIC12F1822 microcontroller has one hardware I2C module with SDA on pin RA2 (#5) and SCL on pin RA1 (#6).
The I2C LCD and the DS3231 (or DS1307) board share the same I2C bus which means the SDA line of the I2C LCD (presented by PCF8574) and the DS3231 RTC chip are connected together with RA2 pin of the PIC12F1822 MCU, the SCL line of the I2C LCD and the SCL line of the DS3231 are connected together with RA1 pin of the PIC12F1822 MCU.
The two push buttons in the circuit are used to set time and date of the real time clock, button 1 (B1) is connected to RA5 pin (#2) and button 2 (B2) is connected to RA4 pin (#3) of the PIC12F1822 MCU.
PIC12F1822 internal oscillator is used (@8MHz) and MCLR pin (RA3) is configured as an input pin.
The C Code:
The C code below is for MPLAB XC8 compiler, it was tested with version 2.00 installed on MPLAB X IDE version 5.05.
RAM and ROM usage:
When optimization level is 1 the percentage of memory usage is:
RAM used: 39 bytes (30%)
ROM used: 1725 words (84%)
and with optimization level = 0:
RAM used: 39 bytes (30%)
ROM used: 2009 words (98%)
To be able to compile the C code, a small I2C LCD library for MPLAB XC8 compiler is required which can be downloaded from the following link:
I2C LCD driver for MPLAB XC8 compiler
after the download, add the library file (I2C_LCD.c) to project folder.
The I2C LCD driver file is included with the line:
#include “I2C_LCD.c”
The hardware I2C module of the PIC12F1822 is initialized with a clock frequency of 100KHz (100000Hz):
I2C_Init(100000);
The I2C LCD is initialized with an I2C address of 0x4E:
LCD_Begin(0x4E);
The two buttons are connected to RA4 and RA5, they are defined as:
1 2 3 | // button definitions #define button1 RA5 // button B1 is connected to RA5 pin #define button2 RA4 // button B2 is connected to RA4 pin |
Functions used in the C code:
The RTC chip (DS1307 or DS3231) works with BCD format only, to convert BCD to decimal and vise versa I used the 2 functions below. Before displaying (after reading from RTC IC), the data have to be converted from BCD to decimal, and before writing to the RTC IC (after editing the parameters) the data have to be converted from decimal to BCD:
uint8_t bcd_to_decimal (uint8_t number);
uint8_t decimal_to_bcd (uint8_t number);
Each function returns the converted value of the variable number.
void RTC_display() : displays time and date, before printing time and date on the screen, they are converted from BCD format to decimal format using the function bcd_to_decimal(uint8_t number) .
uint8_t edit(uint8_t x, uint8_t y, uint8_t parameter) : I used this function to edit time and date parameters (minutes, hours, date, month and year). I used a variable named i to distinguish between the parameters:
i = 0, 1 : hours and minutes respectively
i = 2, 3, 4: date, month and year respectively
After the edit of time and date, the data have to be converted back to BCD format using the function decimal_to_bcd(char number) and written to the RTC chip.
void delay() : this small function works as a delay except that it can be interrupted by buttons B1 (connected to RA5) and B2 (connected to RA4). When called and without pressing any button the total time is approximately 250 milliseconds. With this function we can see the blinking of the selected parameter with a frequency of 2 Hz. So a delay of 250ms comes after the print of the selected parameter and after that delay, 2 spaces are printed which make the parameter disappears from the screen and another 250ms delay comes after the print of the 2 spaces.
In this function I used Timer1 module (16-bit timer) for the 250 ms delay. Timer1 module is configured to increment by 1 every 4 microseconds (1 tick per 4 us). That means Timer1 input clock frequency is 250kHz. The configuration is shown below:
1 2 3 | // Timer1 module configuration T1CON = 0x30; // set Timer1 clock source to internal with 1:8 prescaler // Timer1 clock frequency = 250kHz |
For the calculation of Timer1 clock frequency I used the following function:
Frequency = Fosc / ( 4 x prescaler) = 8MHz / ( 4 x 8) = 0.25MHz = 250kHz.
So, to get a delay of 250ms we need Timer1 to increment 62500 times (62500 ticks):
250ms x 1000 / 4 = 62500 ticks
Timer1 registers are TMR1H and TMR1L.
The microcontroller used in this example is PIC12F1822, configuration words are:
1 2 | #pragma config CONFIG1 = 0x3F84 #pragma config CONFIG2 = 0x1613 |
Where:
- Fail-Safe Clock Monitor enabled
- Internal/External Switchover mode enabled
- CLKOUT function is disabled, I/O function on the CLKOUT pin
- Brown-out Reset (BOR) enabled
- Data memory code protection disabled
- Program memory code protection disabled
- RE3/MCLR pin function is digital input, MCLR internally tied to VDD
- Power-up Timer (PWRT) disabled
- Watchdog Timer (WDT) disabled
- INTOSCIO oscillator: I/O function on CLKIN pin
- Low voltage programming disabled
- In-Circuit Debugger disabled
- Brown-out Reset voltage (Vbor), low trip point selected
- Stack Overflow or Underflow will cause a Reset
- 4xPLL disabled
- Flash Program Memory Self Write disabled
MPLAB XC8 code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | /* * Real time clock with PIC12F1822, DS3231 and I2C LCD. * This code works also with DS1307 & DS3232. * C Code for MPLAB XC8 compiler. * Internal oscillator used @ 8MHz. * This is a free software with NO WARRANTY. * http://simple-circuit.com/ */ // set configuration words #pragma config CONFIG1 = 0x3F84 #pragma config CONFIG2 = 0x1613 // button definitions #define button1 RA5 // button B1 is connected to RA5 pin #define button2 RA4 // button B2 is connected to RA4 pin #include <xc.h> #define _XTAL_FREQ 8000000 #include <stdint.h> // include stdint header #include "I2C_LCD.c" // include I2C LCD driver source file // variables declaration uint8_t i, second, minute, hour, m_day, month, year; // a small function for button1 (B1) debounce __bit debounce () { uint8_t count = 0; for(uint8_t i = 0; i < 5; i++) { if (button1 == 0) count++; __delay_ms(10); } if(count > 2) return 1; else return 0; } /********************** RTC functions *********************/ // convert BCD to decimal function uint8_t bcd_to_decimal(uint8_t number) { return((number >> 4) * 10 + (number & 0x0F)); } // convert decimal to BCD function uint8_t decimal_to_bcd(uint8_t number) { return(((number / 10) << 4) + (number % 10)); } // display time and date function void RTC_display() { // convert data from BCD format to decimal format second = bcd_to_decimal(second); minute = bcd_to_decimal(minute); hour = bcd_to_decimal(hour); m_day = bcd_to_decimal(m_day); month = bcd_to_decimal(month); year = bcd_to_decimal(year); // end conversion // print seconds LCD_Goto(13, 1); LCD_PutC( (second / 10) % 10 + '0'); LCD_PutC( second % 10 + '0'); // print minutes LCD_Goto(10, 1); LCD_PutC( (minute / 10) % 10 + '0'); LCD_PutC( minute % 10 + '0'); // print hours LCD_Goto(7, 1); LCD_PutC( (hour / 10) % 10 + '0'); LCD_PutC( hour % 10 + '0'); // print day of the month LCD_Goto(7, 2); LCD_PutC( (m_day / 10) % 10 + '0'); LCD_PutC( m_day % 10 + '0'); // print month LCD_Goto(10, 2); LCD_PutC( (month / 10) % 10 + '0'); LCD_PutC( month % 10 + '0'); // print year LCD_Goto(15, 2); LCD_PutC( (year / 10) % 10 + '0'); LCD_PutC( year % 10 + '0'); } // make editing parameter blinks function void delay() { TMR1H = TMR1L = 0; // reset Timer1 TMR1ON = 1; // enable Timer1 module // wait for 250ms or at least one button press while ( ((uint16_t)(TMR1H << 8) | TMR1L) < 62500 && button1 && button2) ; TMR1ON = 0; // disable Timer1 module } // edit time and date function uint8_t edit(uint8_t x, uint8_t y, uint8_t parameter) { while(debounce()); // call debounce function (wait for B1 to be released) while(1) { while(!button2) // if button B2 is pressed { parameter++; if(i == 0 && parameter > 23) // if hours > 23 ==> hours = 0 parameter = 0; if(i == 1 && parameter > 59) // if minutes > 59 ==> minutes = 0 parameter = 0; if(i == 2 && parameter > 31) // if date > 31 ==> date = 1 parameter = 1; if(i == 3 && parameter > 12) // if month > 12 ==> month = 1 parameter = 1; if(i == 4 && parameter > 99) // if year > 99 ==> year = 0 parameter = 0; LCD_Goto(x, y); // move cursor to column x, row y LCD_PutC(parameter / 10 + '0'); LCD_PutC(parameter % 10 + '0'); __delay_ms(200); } LCD_Goto(x, y); // move cursor to column x, row y LCD_Print(" "); // print 2 spaces delay(); LCD_Goto(x, y); LCD_PutC(parameter / 10 + '0'); LCD_PutC(parameter % 10 + '0'); delay(); if(!button1) // if button B1 is pressed if(debounce()) // call debounce function (make sure B1 is pressed) { i++; // increment 'i' for the next parameter return parameter; // return parameter value and exit } } } /********************** end RTC functions *****************/ // main function void main(void) { OSCCON = 0x70; // set internal oscillator to 8MHz ANSELA = 0; // configure all PORTA pins as digital // enable RA4 and RA5 internal pull ups nWPUEN = 0; // clear WPUEN bit (OPTION_REG.7) WPUA = 0x30; // WPUA register = 0b00110000 // Timer1 module configuration T1CON = 0x30; // set Timer1 clock source to internal with 1:8 prescaler // Timer1 clock frequency = 250kHz TMR1H = TMR1L = 0; // reset Timer1 I2C_Init(100000); // initialize I2C bus with clock frequency of 100kHz LCD_Begin(0x4E); // initialize LCD module with I2C address = 0x4E LCD_Goto(1, 1); // move cursor to column 1, row 1 LCD_Print("TIME: 00:00:00"); LCD_Goto(1, 2); // move cursor to column 1, row 2 LCD_Print("DATE: 00/00/2000"); while(1) { if(!button1) // if button B1 is pressed if(debounce()) // call debounce function (make sure B1 is pressed) { i = 0; hour = edit(7, 1, hour); minute = edit(10, 1, minute); m_day = edit(7, 2, m_day); month = edit(10, 2, month); year = edit(15, 2, year); while(debounce()); // call debounce function (wait for button B1 to be released) // convert decimal to BCD minute = decimal_to_bcd(minute); hour = decimal_to_bcd(hour); m_day = decimal_to_bcd(m_day); month = decimal_to_bcd(month); year = decimal_to_bcd(year); // end conversion // write data to RTC chip I2C_Start(); // start I2C I2C_Write(0xD0); // RTC chip address I2C_Write(0); // send register address I2C_Write(0); // reset seconds and start oscillator I2C_Write(minute); // write minute value to RTC chip I2C_Write(hour); // write hour value to RTC chip I2C_Write(1); // write day value (not used) I2C_Write(m_day); // write date value to RTC chip I2C_Write(month); // write month value to RTC chip I2C_Write(year); // write year value to RTC chip I2C_Stop(); // stop I2C __delay_ms(200); } // read current time and date from the RTC chip I2C_Start(); // start I2C I2C_Write(0xD0); // RTC chip address I2C_Write(0); // send register address I2C_Repeated_Start(); // restart I2C I2C_Write(0xD1); // initialize data read second = I2C_Read(1); // read seconds from register 0 minute = I2C_Read(1); // read minutes from register 1 hour = I2C_Read(1); // read hour from register 2 I2C_Read(1); // read day from register 3 (not used) m_day = I2C_Read(1); // read date from register 4 month = I2C_Read(1); // read month from register 5 year = I2C_Read(0); // read year from register 6 I2C_Stop(); // stop I2C RTC_display(); // print time & date __delay_ms(50); // wait 50 ms } } // end of code. |
Hardware circuit result of this project should be as shown in the following video where DS3231 board is used:
and Proteus simulation should be as shown in the video below (using DS1307):
Proteus simulation file download:
PIC12F1822 + DS1307 + I2C LCD
Discover more from Simple Circuit
Subscribe to get the latest posts sent to your email.
ROM used: 2009 words (98%)?
i currently have 49% of used rom, if you want i can send you a patch tool for xc8/xc16/xc32 to get a healthier size of used rom!
As you like!