https://dronebotworkshop.com/real-time-clock-arduino/
Introduction
The Arduino is an amazing device. It’s useful for prototyping and can also be used to construct a complete project. It has an analog to digital converters (ADC), digital I/O pins, it handles interrupts and it can communicate via a serial port, SPI, and I2C.
But there is one thing an Arduino cannot do – it can’t tell the time.
There are several time-related functions you can use when programming an Arduino:
- The delay function, which can delay program execution for a specified number of milliseconds.
- The delayMicroseconds function which is like the delay function except you specify the time in microseconds.
- The millis function, which counts the elapsed milliseconds since the Arduino was powered up or reset.
- The micros function, like the millis function, except it measures the time the Arduino has been running in microseconds.
You can use the above functions to insert time delays or measure elapsed time. But you can’t get the time of day or date from them.
To do that you’ll need to add an external component – a “real time clock”.
Real Time Clocks
A “real time clock” is essentially a digital clock whose output can be read by a computer or microcontroller. It has its own oscillator that it uses to count time and it has registers that allow you to set the current time and date.
The device you are using to read this article likely has a real time clock and if you are attached to the Internet you probably are synchronizing this to a network time server (if your device is a phone it may be time synchronized to your telephone companies carrier signal instead).
Most real time clock modules have provision for a battery backup, so that they can keep time when the rest of the system is powered down.
Unix Time
Most computers keep time in what might seem to be a strange system, they use “Unix time”.
Unix time is the number of seconds that have elapsed since midnight January 1, 1970 (which was a Thursday if you’re interested). If you are on Linux (or at the terminal on a Mac, which uses Unix as its underlying operating system) you can type “date +%s” to get the current Unix time.
Unix time is also called “POSIX Time” or “UNIX Epoch time”.
Unix time is useful when you are writing a program that needs to calculate the time difference between two dates and times, as it involves simple addition and subtraction, without having to worry about days, months and years.
But Unix time also has a few drawbacks:
- It does not take into account leap seconds, and there have been several of these since January 1, 1970.
- It uses a 32-bit numbering system and will run out of digits on January 19, 2038. This is sometimes referred to as the “Year 2038 problem”.
The issue with leap seconds is generally taken care of by synchronizing your real time clock with a local or network time source, which itself will have compensated for leap seconds.
The problem with 2038 is likely to be resolved within the next 19 years, I suspect just shifting to a 64-bit standard would resolve this for several millennia!
DS1307 Real Time Clock
There are a few very popular real time clock integrated circuits that are used in real time clock modules.
One chip is the DS3231, a highly accurate real time clock that uses an I2C interface. This chip has its own internal crystal oscillator, so no external crystal is required.
Another very popular chip is the DS1307. Like the DS3231 it also communicates using the I2C bus, however, this chip requires an external timing crystal.
The experiment I’ll be performing today use a module that has a DS1307 chip. They can be easily modified to use the DS3231.
Tiny RTC Board
I will be using a module called the “Tiny RTC”, this is a very common and inexpensive module.
The board contains the DS1307 chip and all the support electronics, including the timing crystal. It also has a battery holder for a coin cell, this sits underneath the board.
If you order your Tiny RTC online from an overseas source it likely won’t have the battery installed, this is due to international shipping regulations regarding lithium batteries. The module uses a widely available coin cell battery.
The Tiny RTC module has the following features:
- Counts time in hours, minutes and seconds
- Counts date, day of week, month and year.
- Has Leap Year compensation
- Has a programmable square wave output.
- Has battery backup with automatic power failure detection and switchover.
- Consumes less than 500nA in battery backup mode.
The Tiny RTC module interfaces using the I2C bus.
The module has two sets of connectors, the I2C connections are repeated on both of them and you can use either of them to make your connections.
The device has the following connections:
- DS – Device Select, this allows you to enable or disable the device.
- SCL – The I2C clock or SCL connection.
- SDA – The I2C Serial Data or SDA connection.
- VCC – The +5-volt power supply.
- GND – The Ground connection.
- SQ – The programmable Square Wave output.
- BAT – Battery connection.
Now that you have seen the module let’s hook it up to an Arduino.
Tiny RTC Basic Arduino Hookup
As the Tiny RTC uses the I2C bus it is very easy to hook it up to an Arduino. The I2C bus provides power, clock and data signals, so only four connections are required.
Before you hook up your real time clock module it would be a good idea to install the coin cell battery, if you haven’t already. That way you will ensure that the device will keep time even after you power down the Arduino.
Arduino Hookup
Here is the hookup diagram for the Tiny RTC module.
The two I2C connections are made to two of the Arduino analog inputs.
- Analog pin A4 is the SDA connection.
- Analog pin A5 is the SCL connection.
Some Arduino clones also have separate SDA and SCL pins, usually located on the same side as the digital I/O pins above the AREF pin. You can use these connections instead of the analog pins if you wish, they are actually just duplicates.
Arduino Libraries
There are several libraries available that will work with the Tiny RTC. I’m going to use two libraries that were contributed by Paul Stoffregen, a well-known figure in the Arduino community.
- DS1307RTC Library – This library will function with any module that uses the DS1307 RTC chip, including the Tiny RTC.
- Time Library – This library compliments the DS1307RTC library and adds a number of time-specific functions.
The Arduino Library Manager can be also used to install two updated versions of these libraries, these were updated by Michael Margolis. You can find them as follows:
- Open the Arduino IDE.
- Click on the Sketch menu item at the top of the IDE window.
- Click on Include Library. A sub-menu will open.
- Select Manage Libraries from the sub-menu.
- The Arduino Library Manager will open.
- In the search box in the Library Manager type “DS1307RTC”
- You should get two libraries in your results, the DS1307RTC library, and the Time library.
- Click each selection to install the libraries.
- You can now close the Library Manager window.
Once you have the libraries installed open your Arduino IDE and select the File menu item and select Examples. A sub-menu will appear, scroll down until you get to the Examples from Custom Libraries section.
Look for DS1307RTC and highlight it. You will see a sub-menu with two sketches, SetTime and ReadTest.
We will run both of these, starting with SetTime.
SetTime Sketch
As you might have guessed from its name the SetTime sketch sets the time on the Tiny RTC module. You’ll need to run this, or something similar, before you can use the clock.
#include <Wire.h>
#include <Time.h>
#include <DS1307RTC.h>
const char *monthName[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
tmElements_t tm;
void setup() {
bool parse=false;
bool config=false;
// get the date and time the compiler was run
if (getDate(__DATE__) && getTime(__TIME__)) {
parse = true;
// and configure the RTC with this info
if (RTC.write(tm)) {
config = true;
}
}
Serial.begin(9600);
while (!Serial) ; // wait for Arduino Serial Monitor
delay(200);
if (parse && config) {
Serial.print("DS1307 configured Time=");
Serial.print(__TIME__);
Serial.print(", Date=");
Serial.println(__DATE__);
} else if (parse) {
Serial.println("DS1307 Communication Error :-{");
Serial.println("Please check your circuitry");
} else {
Serial.print("Could not parse info from the compiler, Time=\"");
Serial.print(__TIME__);
Serial.print("\", Date=\"");
Serial.print(__DATE__);
Serial.println("\"");
}
}
void loop() {
}
bool getTime(const char *str)
{
int Hour, Min, Sec;
if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
tm.Hour = Hour;
tm.Minute = Min;
tm.Second = Sec;
return true;
}
bool getDate(const char *str)
{
char Month[12];
int Day, Year;
uint8_t monthIndex;
if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
for (monthIndex = 0; monthIndex < 12; monthIndex++) {
if (strcmp(Month, monthName[monthIndex]) == 0) break;
}
if (monthIndex >= 12) return false;
tm.Day = Day;
tm.Month = monthIndex + 1;
tm.Year = CalendarYrToTm(Year);
return true;
}
SetTime uses two functions, getTime and getDate, to retrieve the time and date respectively from your computer clock. As most Internet-connected computers synchronize to a network time protocol (NTP) server this will probably be very accurate.
One statement in the code that may be new to you is the 10th line, tmElements_t tm; . This is a “data structure” that represents the time.
Without using a data structure the time will be reported in Unix time, which I described earlier. The tmElements_t data structure breaks this down into elements like seconds, minutes, hours, days, months and years.
Otherwise, the sketch is fairly straightforward. It gets the current system time from the computer running the IDE and writes it to the DS1307 chip on the Tiny RTC module. It does all of this in the Setup section so there is no code in the loop.
Load this sketch and run it to set the time in your real time clock. Then move on to the next sketch.
ReadTest Sketch
The ReadTest sketch will read the time from the Tiny RTC module or any module based upon the DS1307 real time clock chip.
#include <Wire.h>
#include <Time.h>
#include <DS1307RTC.h>
void setup() {
Serial.begin(9600);
while (!Serial) ; // wait for serial
delay(200);
Serial.println("DS1307RTC Read Test");
Serial.println("-------------------");
}
void loop() {
tmElements_t tm;
if (RTC.read(tm)) {
Serial.print("Ok, Time = ");
print2digits(tm.Hour);
Serial.write(':');
print2digits(tm.Minute);
Serial.write(':');
print2digits(tm.Second);
Serial.print(", Date (D/M/Y) = ");
Serial.print(tm.Day);
Serial.write('/');
Serial.print(tm.Month);
Serial.write('/');
Serial.print(tmYearToCalendar(tm.Year));
Serial.println();
} else {
if (RTC.chipPresent()) {
Serial.println("The DS1307 is stopped. Please run the SetTime");
Serial.println("example to initialize the time and begin running.");
Serial.println();
} else {
Serial.println("DS1307 read error! Please check the circuitry.");
Serial.println();
}
delay(9000);
}
delay(1000);
}
void print2digits(int number) {
if (number >= 0 && number < 10) {
Serial.write('0');
}
Serial.print(number);
}
This is actually a pretty simple sketch, and it does a good job of illustrating how you can use these libraries to write a sketch of your own.
We start by including the Wire library, which is the built-in library that facilitates communications using I2C. We then include the two libraries we installed earlier.
In the Setup section we just set up the serial monitor and write a few lines to it.
The Loop starts with our data structure to derive the time values from.
Al that is left is to read those time values and print them to the serial monitor. A function called print2digits is used for the hours, minutes and seconds to format the display nicely, with leading zeros for numbers below 10.
I’ll be reusing a lot of this code later when I create a sketch of my own.
Tiny RTC Square Wave Interrupt
The two sketches we have just looked at illustrate how to set and read the time from the Tiny RTC module, and they accomplish this very well. But the module has an additional function, the ability to output a square wave.
You can use this square wave as a timing source for another circuit. It could be used to drive a stepper motor to create an analog clock. And, as I will show you here, it can be used to generate an interrupt for your Arduino.
Programming the Tiny RTC Square Wave Frequency
The DS1307 chip used in the Tiny RTC module is capable of generating square waves at the following preset frequencies:
- 1 Hz
- 4 KHz
- 8 KHz
- 32 KHz
These frequencies are selected by writing to an internal control register in the DS1307. By default, the device is programmed at the factory for a 32 KHz frequency.
Square Wave Arduino Hookup
To use the square wave output as an interrupt for your Arduino you will need to connect the SQ output on the Tiny RTC module to one of the interrupt pins on the Arduino.
The following diagram shows the hookup using an Arduino Uno.
The SQ output is connected to pin 2, which corresponds to interrupt 0 in the Arduino Uno.
An LED is also connected to the Arduino to indicate when an interrupt is being serviced. This is actually optional as it is connected to pin 13 and the Uno also has a built-in LED connected to that pin. If you wish you can leave it out and just monitor the LED on your Uno board.
Square Wave Interrupt Demo Sketch
I found a great sketch that shows how to use the square wave output, it was originally published by Robert Ulbricht on the Arduino Slovakia website. Fortunately, there is an English translation available.
/**
Arduino DS1307 Set SQW Interrupt
v. 1.0
Copyright (C) 2017 Robert Ulbricht
http://www.arduinoslovakia.eu
Set Square Wave Output 1Hz.
Blink with internal LED using interrupt.
IDE: 1.8.3 or higher
Board: Arduino Uno, Arduino Pro Mini, Arduino Mega 2560
Libraries: none
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wire.h>
#define DS1307_CTRL_ID 0x68
#define ledPin LED_BUILTIN
void setSQW(uint8_t value) {
Wire.beginTransmission(DS1307_CTRL_ID);
Wire.write(7);
Wire.write(value);
Wire.endTransmission();
}
void handleInt() {
digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}
void setup() {
Serial.begin(9600);
while (!Serial) ; // wait for serial
delay(200);
Serial.println("DS1307RTC Set SQW 1 Hz");
Serial.println("Attach interrupt on D2");
Serial.println("----------------------");
pinMode(ledPin,OUTPUT);
// D2 on Arduino Uno
// D2 on Arduino Mega 2560
attachInterrupt(0,handleInt,FALLING);
// 1Hz
setSQW(0x10);
}
void loop() {
}
The sketch only requires the Wire library, it uses this to write directly to the registers on the DS1307 chip.
The constant DS1307_CTRL_ID defines the I2C address of the real time clock chip.
The function setSQW is really where the “action” is. This function writes to the control register in the DS1307 and sets the square wave frequency to 1Hz.
The handleInt function is the interrupt handler. Every time a pulse is received on the D2 pin it will be called. This is set up in the Setup function using the Arduino attachInterrupt function.
The result is that the LED will flash every second. In operation, it looks a lot like the Blink sketch!
Tiny RTC Temperature and Humidity Sketch
The previous sketch illustrated how to use the SQ square wave output from the Tiny RTC module as an interrupt. And while it does a great job of displaying interrupt operation it really doesn’t have many practical uses.
After all, there are many simpler methods of blinking an LED. In fact, if you really wanted to use the Tiny RTC to blink an LED you could just attach it directly to the SQ output, eliminating the Arduino entirely (although you’d need the Arduino to set the SQ output to 1Hz first).
Let’s look at a more practical example of using interrupts from the real time clock module.
Using Interrupts to Solve a Problem
Take another look at the ReadTest sketch from the DS1307RTC examples. You’ll notice that it reads the time and then adds two delays that total exactly one second. It then does it all over again.
If you’re just building a clock this will work well, as every second you’ll read the time and it will have advanced one second. But what if you want to do something else in the Loop after you read and display the time?
Whatever your “something else” is it probably won’t take exactly one second. And this will cause an erratic clock display.
- If the ”something else” takes less than a second then you’ll be displaying the same time more than once. On a display like an LCD or OLED this is not such a bad thing as you might never notice it, but on the serial monitor it will stand out.
- If the “something else” takes more than one second you’ll miss reading the clock and it wil skip one or more seconds. You can alleviate this problem somewhat by simply not displaying seconds but still, your minutes may not change at precisely the right time.
To solve this timing problem we can use interrupts.
AM2320 Temperature & Humidity Sensor
To illustrate how to take advantage of interrupts to solve the timing problem I’m going to build a temperature and humidity meter that can also tell time. I’m going to stick to the serial monitor for my display, but you could easily modify the code to use an OLED or LCD display.
The AM2320 is an I2C temperature and humidity sensor. Physically it looks identical to a DHT11 or DHT22, the difference is that it uses the I2C bus to send data to its host.
If you wish you could modify the sketch to use the DHT22 or DHT11, I used the AM2320 because I was already using I2C for the real time clock and because I had one handy!
No matter which sensor you choose for your design you’ll encounter the same dilemma – these temperature and humidity sensors require at least two seconds between readings to stabilize. And so if you read it in the Loop you’ll get an erratic display.
This looks like a job for an interrupt!
Temperature and Humidity Clock Hookup
Let’s begin by adding an AM2320 to our existing circuit. As you can see it’s pretty easy.
Pay attention to the connections on the AM2320, looking at the unit with the “grid holes” facing you they are as follows, from left to right:
- VCC – Goes to the +5 Volt output from the Arduino.
- SDA – To Arduino pin A4 (or SDA pin if you have one)
- GND – To Arduino Ground.
- SCL – To Arduino pin A5 (or SCL pin if you have one)
Now that you have it hooked up let’s take a look at the sketch I came up with to make it all work.
Temperature and Humidity Clock Sketch
You’ll probably notice many elements from the previous sketches in this sketch.
/*
Time, Temperature and Humidity
time_temp_humid.ino
Displays results on Serial Monitor
Uses AM2320 I2C Temperature and Humidity sensor
Uses DS1307 Real Time Clock
Uses TimeLib Library
Uses DS1307RTC Library
Uses Adafruit AM2320 Library
DroneBot Workshop 2019
https://dronebotworkshop.com
*/
// Include required libraries
#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include <Adafruit_AM2320.h>
// Define constant for RTC I2C Address
#define DS1307_CTRL_ID 0x68
// Define object am2320
Adafruit_AM2320 am2320 = Adafruit_AM2320();
// Variables to count square wave pulses
int ticks = 0;
int old_tick_value = 0;
// Variables for Humidity and Temperature
float h;
float t;
void setup() {
Serial.begin(9600);
// Attach Interrupt to pin D2
attachInterrupt(0,handleInt,FALLING);
// Set Square Wave at 1Hz
setSQW(0x10);
// Initialize Temp & Humid Sensor
am2320.begin();
delay(2000);
// Read Humidity
h = am2320.readHumidity();
// Read temperature as Celsius
t = am2320.readTemperature();
}
void loop() {
// Read Temp Humid sensor every 10 seconds
if (ticks ==10){
// Read Humidity
h = am2320.readHumidity();
// Read temperature as Celsius
t = am2320.readTemperature();
ticks = 0;
}
// Update serial monitor display if a second has elapsed
if (ticks != old_tick_value) {
old_tick_value = ticks;
printCurrentTime(h,t);
}
}
// Interrupt Handler
void handleInt() {
ticks++;
}
// Setup RTC Module for 1 Hz square wave
void setSQW(uint8_t value) {
Wire.beginTransmission(DS1307_CTRL_ID);
Wire.write(7);
Wire.write(value);
Wire.endTransmission();
}
// Format numbers as 2-digit numbers
void print2digits(int number) {
if (number >= 0 && number < 10) {
Serial.print('0');
}
Serial.print(number);
}
// Print to the serial monitor
void printCurrentTime(float hum, float tem){
tmElements_t tm;
if (RTC.read(tm)) {
print2digits(tm.Hour);
Serial.print(':');
print2digits(tm.Minute);
Serial.print(':');
print2digits(tm.Second);
Serial.print(" - ");
Serial.print(tmYearToCalendar(tm.Year));
Serial.print('-');
print2digits(tm.Month);
Serial.print('-');
print2digits(tm.Day);
Serial.print(" Hum: ");
Serial.print(hum);
Serial.print("% - Temp: ");
Serial.print(tem);
Serial.print(" C");
Serial.println();
}
}
You’ll need another couple of libraries to run this sketch to handle the temperature and humidity sensor. Both can be installed using the Library Manager.
- The Adafruit AM2320 Library
- The Adafruit Unified Sensor Library.
The latter library is not called directly in the sketch, instead, it is used by the AM2320 Library. Without it installed your sketch will fail to compile.
If you’ve used other Adafruit sensor libraries you may already have the Unified Sensor library installed.
We begin the sketch by including the libraries. We then define the constant we used before for the Tiny RTC I2C address.
We also create an object to represent our sensor.
Next, we create a couple of variables that count “ticks”. I’m defining a “tick” as a one second period. The old_tick_value variable will be used to check if the tick value has changed since we last used it.
A couple of floats are defined to hold the temperature and humidity values.
In the Setup routine we attach the interrupt to the interrupt handler, as we did earlier. I kept the same name for the interrupt handler but have changed its function.
We also setup the square wave as 1 Hz.
Next we initialize the temperature and humidity sensor, delay for two seconds and then read the temperature and humidity values. This is so we already have a first reading before we enter the loop.
Let’s look down to some of the functions before we examine the Loop.
The printCurrentTime function prints the time, date, temperature and humidity to the serial monitor. It takes the temperature and humidity as an input and reads the real time clock, it then writes everything to the serial monitor.
The interrupt handler handleInt increments the tick count every time it is run.
You’ve seen the other two functions already, so let’s go back up to the loop.
In the Loop we first check the number of ticks by reading the tick count. If it is 10 then we read the humidity and temperature, then reset the tick counter to zero.
By doing this we only read the temperature and humidity sensor every 10 seconds, which gives it plenty of time to stabilize. You can reduce this number if you wish, but don’t go below two seconds.
We then check to see if the tick count is the same as it was before. If it is then we don’t do anything. If it isn’t then it means a second has elapsed, so we call printCurrentTime to read the time and write everything to the serial monitor.
Load the sketch and give it a test. Note that since the temperature and humidity are only updated every 10 seconds it won’t immediately respond to a change in these values. In normal situations this shouldn’t really be an issue.
Conclusion
Adding a real time clock to an Arduino makes it possible to build devices that are aware of the current time and date. This can allow you to create fancy timers and delay circuits, or just build a really cool and unique digital clock.
Hopefully you’ll see some uses for a real time clock in your own projects. There no time like the present to get started!
Resources
Code In this Article – The sketches in this article in a ZIP file.
'사물인터넷' 카테고리의 다른 글
Control Large DC Gearmotors with PWM & Arduino (0) | 2021.07.04 |
---|---|
SD Card Experiments with Arduino (0) | 2021.07.04 |
Using Rotary Encoders with Arduino (0) | 2021.07.04 |
Build a Digital Level with MPU-6050 and Arduino (0) | 2021.07.04 |
Using BIG Stepper Motors with Arduino (0) | 2021.07.04 |