A real-time clock (RTC) is a circuit that keeps track of time by counting the precise oscillations of a crystal oscillator of known frequency very accurately. For example, a typical frequency of a quartz crystal oscillator used in RTC applications is 32.768 kHz. That frequency is not arbitrary but is exactly 215 cycles per second. That means we can use a 15-bit (16-bit in practical) counter to count the oscillations and produce a digital signal every second. A set of such digital counters can be cascaded and be used to count minutes, hours, days and so on. Quartz crystal oscillators (a piezo-electric material) produce very precise oscillations and therefore used in almost all the consumer applications including wrist watches, smartphones, TVs etc. You can learn more about how quartz crystals work and how they're manufactured for time-keeping applications in this video by Steve Mould.
To make the application easier, semiconductor companies produce standalone integrated circuits that implement all the required circuitry for an RTC in a single IC package. These are called RTC ICs. A popular such RTC IC is the DS1307 from Maxim semiconductor. But there are better RTCs available from both Maxim and other manufactures. In this project, I will show you how does a generic RTC IC work and how to interface the ISL1208 RTC IC from Intersil (a subsidiary of Renesas Electronics) with a microcontroller.
We will interface the ISL1208 with an Arduino and write a library for it. The library is released under MIT License and has now been added to the official Arduino library list. You can install the latest version of the library from within your Arduino IDE itself. Instructions can be found further below.
The Intersil ISL1208 is a low power RTC chip with I2C interface. It uses an external 32.768KHz crystal to keep track of the time and has month-date-hour-min-sec alarm registers. It only consumes around 400nA in battery (VBAT) operation and a maximum of 1.2uA on external supply voltage (VDD). The operating voltage is from 1.8V to 5.5V. What makes this a good candidate are the low power consumption and the month-date alarm feature. Normal RTCs such as DS1307 do not have a month setting in alarm register. I used the month alarm feature of ISL1208 in my Arduino based Birthday Reminder project.
Pin | Pin Name | Function |
1 | X1 | Crystal 1 |
2 | X2 | Crystal 2 |
3 | VBAT | Battery Supply |
4 | GND | Ground |
5 | SDA | Serial Data (I2C) |
6 | SCL | Serial Clock (I2C) |
7 | IRQ/FOUT | Interrupt/Frequency Out |
8 | VDD | Positive Supply |
All RTC ICs have more or less the same internal design. Basically, there will be an internal square wave oscillator that uses an external 32.768 KHz crystal to produce the oscillations. The RTC Divider is simply a set of digital counters that count the oscillations and produce a digital signal at the output each time the counter buffer overflows. In this case, it will include counters that keep track of seconds, minutes, hours, day of week, date. month and year, and the values of the counters are saved to the time registers at a specific rate. There will be logic circuits to apply compensations such as leap year events. The output of the RTC Divider also goes to a Frequency Output section from where we can direct the output to the pin 7 of the RTC which has the function IRQ/FOUT. This pin has dual functions and therefore is multiplexed. It serves as either an interrupt output pin (such as the alarm interrupt) or a frequency output, producing clock signal of desired frequency. The output clock frequency can be programmed via registers.
The RTC Control Logic supervises all the communications via an I2C block and also the read and write of internal time and configuration registers. The ISL1208 will act as a slave on an I2C bus with an address 0x6F. The I2C interface supports data rates up to 400KHz. This block is disabled in the battery backup mode to save power. Therefore, we always need proper voltage level at VDD pin in order to read or write the RTC registers.
Another function of the RTC Control Logic is to check if the current time matches any of the alarm register values. When they do match, it will activate the Alarm block to produce an interrupt signal in interrupt mode or an active low signal for the single event mode at pin 7. The duration of the alarm interrupt signal is 250ms.
There are two ways we can power the RTC - a VDD between 2-5.5VDC or a VBAT of 1.8-5.5V. There is an internal switching circuit that selects the appropriate power source automatically when certain conditions are satisfied. This section also monitor for a total power failure and update the Real Time Clock Failure Bit (RTCF) in the register. These are well described in the Functional Description section of the datasheet.
Since an RTC chip consumes very low current in the ranges of nano amperes, a coin cell is able to operate an RTC for a long time independent of a main supply. 3V Lithium coin cells are used for such applications. Lithum cells have long charge retentivity, up to 10 years.
The register map gives all the details a programmer needs to write software for interfacing the RTC chip. Just like any other I2C devices, you can first send the slave address of the RTC followed by the register address you want to read or write. I have included all these addresses in the Arduino library. Note that, the RTC registers keep the values in BCD (Binary Coded Digits) format. So you need to convert to and from BCD when reading and writing those registers. The library also makes it easy to do this. Full description of the registers can be found on the datasheet.
There are more functions available for the RTC than what I have included in my library. Such features can be configured easily if you know the registers. These are some of the important registers you might want to know about.
1
if you want to access the registers. The default value is 0
. The RTC will not start counting until you reset this bit to 1
. The function begin()
sets this bit to 1
when RTC is initialized.1
when both VDD and VBAT fall to 0V. So when you first power up the RTC, it will be 1
. It will be reset to 0
when you first write any of the registers.1
when the alarm time matches the real-time clock. This is one way of determining the alarm condition. The other way is using the interrupt output from IRQ pin which will produce a 250ms signal at the same time the ALM bit is set.
The ISL1208 needs very few external components to work. When using the IC on an actual application circuit, it's a good practice to place a 0.1uF (100nF) power supply bypass capacitor closer to the VDD pin. The above schematic shows Arduino Nano as example. You can connect the RTC to I2C port of any of the Arduino boards. The R1 and R5 are common pull-up resistors for the I2C bus. For the battery backup, I have included the CR2450 3V Lithium cell which has a capacity of 540mAh and can easily run the RTC circuit for more than 10 years. For demonstrating the interrupt output, I have tied the IRG/FREQ pin of the RTC to digital pin 2 of the Arduino with interrupt capability. R3 is a pull-up for the interrupt pin but you can exclude this and use the internal pull-up instead.
This library supports all official Arduino boards as well as other boards that have the Arduino core avaialable such as ESP8266, ESP32, STM32 Nucleo etc.
You can install the library from within the Arduino IDE itself. Open the library manager from Tools > Manage Libraries.. or press Ctrl+Shift+I
. In the library manager search for "ISL1208" and install the latest version. The library folders and files will be added to your default folder. If you want to install it manually, go to the latest release page, where you can download the latest release as a ZIP file. Extract the contents to your default library folder and restart the Arduino IDE.
Once you have installed the library, you can open the example sketch from the Examples menu.
1. stdint.h
2. Arduino.h
3. Wire.h
#define ISL1208_ADDRESS 0x6F //I2C slave addess of RTC IC
#define ISL1208_SC 0x00 //seconds register
#define ISL1208_MN 0x01 //minutes register
#define ISL1208_HR 0x02 //hours register
#define ISL1208_DT 0x03 //date register
#define ISL1208_MO 0x04 //month register
#define ISL1208_YR 0x05 //year register
#define ISL1208_DW 0x06 //day of the week register
#define ISL1208_SR 0x07 //status register
#define ISL1208_INT 0x08 //interrupt register
#define ISL1208_ATR 0x0A //analog trimming register
#define ISL1208_DTR 0x0B //digital trimming register
#define ISL1208_SCA 0x0C //alarm seconds register
#define ISL1208_MNA 0x0D //alarm minutes register
#define ISL1208_HRA 0x0E //alarm hours register
#define ISL1208_DTA 0x0F //alarm date register
#define ISL1208_MOA 0x10 //alarm month register
#define ISL1208_DWA 0x11 //alarm day of the week register
#define ISL1208_USR1 0x12 //user memory 1
#define ISL1208_USR2 0x13 //user memory 2
1. ISL1208_RTC
bool rtc_debug_enable; //enable this to get verbose output at serial monitor
byte yearValue; //least significant digits of a year (eg. 18 for 2018, range is from 00 to 99)
byte monthValue; //month (eg. 01 for January, range is 01 to 12)
byte dateValue; //date (eg. 24, range is 01 to 31)
byte dayValue; //date (eg. 3, range is 0 to 6)
byte hourValue; //hours (eg. 06, range is 01 to 12 for 12 hour format)
byte minuteValue; //minutes (eg. 55, range is 00 to 59)
byte secondValue; //seconds (eg. 30, range is 00 to 59)
byte periodValue; //period of the day for 12 hour format (0 = AM, 1 = PM)
byte monthValueAlarm; //same as time values
byte dateValueAlarm;
byte dayValueAlarm;
byte hourValueAlarm;
byte minuteValueAlarm;
byte secondValueAlarm;
byte periodValueAlarm;
byte startOfTheWeek; //starting day of week (eg. 3, range is 0 to 6)
byte tempByte;
ISL1208_RTC(); //constructor
void begin(); //alternate initializer
bool isRtcActive(); //checks if the RTC is available on the I2C bus
bool updateTime(); //update time registers from variables
bool setTime(String); //updates time registers from a formatted time string
bool updateAlarmTime(); //updates alarm registers from variables
bool setAlarmTime(String); //updates alarm registers from a formatted alarm time string
bool fetchTime(); //reads RTC time and alarm registers and updates the variables
int getHour(); //returns the 12 format hour in DEC
int getMinute(); //returns minutes in DEC
int getSecond(); //returns seconds value
int getPeriod(); //returns time period. 0 = AM, 1 = PM
int getDate(); //returns date
int getDay(); //returns day (0 to 6)
int getMonth(); //returns month (0 to 12)
int getYear(); //returns year (00 = 2000, 99 = 2099)
int getAlarmHour();
int getAlarmMinute();
int getAlarmSecond();
int getAlarmPeriod(); //0 = AM, 1 = PM
int getAlarmDate();
int getAlarmDay();
int getAlarmMonth();
String getTimeString(); //returns formatted time string (hh:mm:ss pp)
String getDateString(); //returns formatted date string (DD-MM-YYYY)
String getDayString(); //returns the full name of day
String getDayString(int n); //returns the first n chars of day string (n = 1 to 9)
String getDateDayString(); //returns a formatted date string with day name (DD-MM-YYYY DAY)
String getDateDayString(int n); //returns a formatted date string with n truncated day name
String getTimeDateString(); //returns a formatted time date string
String getTimeDateDayString(); //does what it says!
String getTimeDateDayString(int n); //returns a time, date string with n truncated day string
String getAlarmString();
bool printTime(); //prints time to the serial monitor
bool printAlarmTime(); //prints the alarm time to serial monitor
byte bcdToDec(byte); //converts a BCD value to DEC
byte decToBcd(byte); //converts a DEC value to BCD
1. void begin();
2. bool isRtcActive();
true
if RTC was found and false
if it was not found on the bus.
3. bool updateTime();
4. bool updateTime(String);
TYYMMDDhhmmssp#
is the format for time string, where,
T1801050835120#
where,
5. bool updateAlarmTime();
6. bool updateAlarmTime(String);
AMMDDhhmmssp#
where,
7. bool fetchTime();
true
is the operation was a success, or false
is the RTC was not found on the I2C bus.
8. int getHour();
fetchTime()
every time. getHour()
returns the hours in 12 hour format from 1 to 12 (DEC). 24 hour support will be added later.
9. int getMinute();
10. int getSecond();
11. int getPeriod();
12. int getDate();
13. int getDay();
14. int getMonth();
15. int getYear();
16. int getAlarmHour();
17. int getAlarmMinute();
18. int getAlarmSecond();
19. int getAlarmPeriod();
20. int getAlarmDate();
21. int getAlarmDay();
22. int getAlarmMonth();
23. String getTimeString();
24. String getDateString();
25. String getDayString();
26. String getDayString(int n);
27. String getDateDayString();
28. String getDateDayString(int n);
29. String getTimeDateString();
30. String getTimeDateDayString();
31. String getTimeDateDayString(int n);
32. String getAlarmString();
33. bool printTime();
true
if the operation was success, or false
if RTC could not be read.
34. bool printAlarmTime();
true
if the operation was success, or false
if RTC could not be read.
35. byte bcdToDec(byte);
36. byte decToBcd(byte);
Below is an example sketch to demonstrate some functions of this library. Upload it to your Arduino and open any serial monitor with a baudrate 115200.
View this at GitHub
//========================================================================// | |
// // | |
// ## ISL1208-RTC-Library Arduino Example ## // | |
// // | |
// ISL1208 is an RTC from Intersil. This is an Arduino compatible // | |
// library for ISL1208 // | |
// // | |
// Filename : ISL1208_RTC_Test.ino // | |
// Description : Example Arduino sketch. // | |
// Library version : 1.4.2 // | |
// Author : Vishnu M Aiea // | |
// Source : https://github.com/vishnumaiea/ISL1208-RTC-Library // | |
// Author's website : www.vishnumaiea.in // | |
// Initial release : IST 11:49:42 AM, 27-05-2018, Sunday // | |
// License : MIT // | |
// // | |
// File last modified : IST 11:06 AM 25-05-2019, Saturday // | |
// // | |
//========================================================================// | |
#include <ISL1208_RTC.h> | |
ISL1208_RTC myRtc = ISL1208_RTC(); //create a new object | |
//========================================================================// | |
//Arduino setup function executes once | |
void setup() { | |
Serial.begin(115200); //to print debug info | |
Wire.begin(); //initialize I2C | |
myRtc.begin(); //initialize RTC | |
// myRtc.updateTime("T1801050835120#"); //send the time update string | |
Serial.println(); | |
Serial.println("## ISL1208 RTC Example ##"); | |
Serial.println("Author : Vishnu M Aiea (@vishnumaiea)"); | |
Serial.println("====================================="); | |
Serial.println(); | |
if(myRtc.isRtcActive()) { | |
Serial.println("RTC found on the bus."); | |
Serial.println(); | |
} | |
} | |
//========================================================================// | |
//infinite loop | |
void loop() { | |
String inputString = ""; | |
String commandString = ""; | |
String firstParam = ""; | |
String secondParam = ""; | |
String thirdParam = ""; | |
//send commands and parameters for each operation | |
//items are separated by single whitespace | |
//you can send up to 3 parameters | |
if(Serial.available()) { //monitor the serial interface | |
inputString = Serial.readString(); //read the contents of serial buffer as string | |
Serial.println(); | |
Serial.print("Input String : "); | |
Serial.println(inputString); | |
//-------------------------------------------------------------------------// | |
//the follwing loop extracts the commands and parameters separated by whitespace | |
uint8_t posCount = 0; //the position token of each whitespace | |
int indexOfSpace = 0; //locations of the whitespaces | |
while(inputString.indexOf(" ") != -1) { //loop until all whitespace chars are found | |
indexOfSpace = inputString.indexOf(" "); //get the position of first whitespace | |
if(indexOfSpace != -1) { //if a whitespace is found | |
if(posCount == 0) //the first one will be command string | |
commandString = inputString.substring(0, indexOfSpace); //end char is exclusive | |
if(posCount == 1) //second will be second param | |
firstParam = inputString.substring(0, indexOfSpace); | |
if(posCount == 2) //and so on | |
secondParam = inputString.substring(0, indexOfSpace); | |
else if(posCount == 3) | |
thirdParam = inputString.substring(0, indexOfSpace); | |
inputString = inputString.substring(indexOfSpace+1); //trim the input string | |
posCount++; | |
} | |
} | |
//saves the last part of the string if no more whitespace is found | |
if(posCount == 0) //means there's just the command | |
commandString = inputString; | |
if(posCount == 1) | |
firstParam = inputString; | |
if(posCount == 2) | |
secondParam = inputString; | |
if(posCount == 3) | |
thirdParam = inputString; | |
//-------------------------------------------------------------------------// | |
//separate and print the received command and parameters | |
Serial.print("Command string = "); | |
Serial.println(commandString); | |
if(firstParam.length() > 0) { //only print if there's a valid first parameter | |
Serial.print("First param = "); | |
Serial.println(firstParam); | |
} | |
if(secondParam.length() > 0) { //same for other parameters | |
Serial.print("Second param = "); | |
Serial.println(secondParam); | |
} | |
if(thirdParam.length() > 0) { | |
Serial.print("Third param = "); | |
Serial.println(thirdParam); | |
} | |
Serial.println(); | |
//-------------------------------------------------------------------------// | |
//prints the time | |
if(commandString == "printtime") { | |
myRtc.printTime(); | |
} | |
//-------------------------------------------------------------------------// | |
//prints the alarm time | |
else if(commandString == "printalarmtime") { | |
myRtc.printAlarmTime(); | |
} | |
//-------------------------------------------------------------------------// | |
//send a time string to set time | |
else if(commandString == "settime") { | |
myRtc.setTime(firstParam); //first param should be time string | |
} | |
//-------------------------------------------------------------------------// | |
//send a time string to set alarm | |
else if(commandString == "setalarm") { | |
myRtc.setAlarmTime(firstParam); //first param should be time string | |
} | |
//-------------------------------------------------------------------------// | |
//prints formatted date | |
else if(commandString == "printdate") { | |
Serial.println(myRtc.getDateString()); | |
} | |
//-------------------------------------------------------------------------// | |
//prints date and day | |
else if(commandString == "printdateday") { | |
Serial.println(myRtc.getDateDayString()); | |
} | |
//-------------------------------------------------------------------------// | |
//prints day | |
else if(commandString == "printday") { | |
Serial.println(myRtc.getDayString()); | |
} | |
//-------------------------------------------------------------------------// | |
//prints formatted time and date | |
else if(commandString == "printtimedate") { | |
Serial.println(myRtc.getTimeDateString()); | |
} | |
//-------------------------------------------------------------------------// | |
//prints formatted time, data and day | |
else if(commandString == "printtimedateday") { | |
Serial.println(myRtc.getTimeDateDayString()); | |
} | |
//-------------------------------------------------------------------------// | |
//prints formatted time, data and day | |
else { | |
Serial.println("Unknown command."); | |
Serial.println(); | |
} | |
} | |
} | |
//========================================================================// |
The code is straightforward. When you first open the serial monitor, you'll see the status message.
Then send a command with one or two parameters with each separated with a whitespace.
As I had an SMD version of the ISL1208, I made a small breakout board as shown below. I snatched the crystal from an old quartz clock's PCB.
The current consumption of the ISL1208 in low power mode is only 400nA and at VDD is 1.2uA at max. So it's good for portable applications that are battery powered. Having a wide range of voltage from 1.8-5V means, you can directly interface it with any microcontrollers that work on 5V such as some Arduino boards, or 3.3V ones such as STM32 Nucleo boards. This is for example, not possible with DS1307, because it needs a minimum supply voltage of 4.5V in order to access the registers via I2C, as per the datasheet. Therefore, ISL1208 is a better RTC than DS1307. An alternative option is the DS1308 which has a time-keeping current of 250nA which is better than ISL1208, but has no alarm feature which is a bummer.