In this tutorial you will learn how to use alarms with the Real-Time-Clock (RTC) DS3231 and an Arduino.
The DS3231 module is an external clock. It has its own battery and keeps track of the current time and date even when the main power is turned off. It furthermore has two alarm registers you can set to raise alarms at specified times and dates.
A Real-Time-Clock is especially useful when you want to let the Arduino sleep for longer periods of time. The maximum sleep duration for an Arduino Uno is 8 seconds. But with an RTC you can have arbitrary long sleep durations, which great to extend the battery life of data loggers, for instance.
Required Parts
You will need an DS3231 RTC Module and an Arduino for this project. I used an Arduino Uno but any other Arduino will work as well. Finally, in one of the code examples of this tutorial, I am using a passive buzzer to sound an alarm.
DS3231 RTC Module
Arduino Uno
USB Cable for Arduino UNO
Passive Buzzer
Dupont Wire Set
Breadboard
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Basics of the DS3231 RTC Module
The DS3231 RTC Module is an accurate Real-Time-Clock based in the DS3231 Chip. In addition to the DS3231 it has a AT24C32 EEPROM, and a holder for a CR2032 battery.
Because of its battery the DS3231 will maintain accurate time even if the Arduino looses power, restarts or goes into sleep mode. The picture below shows the front and back of a typical DS3231 RTC Module:
If you need more details on the DS3231 RTC Module have a look at our Arduino and RTC Module DS3231 tutorial.
Pinout of DS3231 RTC Module
The pinout of the DS3231 RTC Module is as follows: VCC and GND are for power supply, with a supply voltage of 3.3–5.5V. SCL and SDA are the pins for the I2C interface. The 32K and SQW are outputs for the 32kHZ clock signal and a programmable square wave signal. The SQW output can also be configured as an interrupt signal output, which we will be using later on.
While the DS3231 RTC Module runs with 5V, you should use 3.3V as power supply if you have an CR2032 battery inserted. The module has a very basic charging circuit that will damage the battery, if you run the module on 5V. For more information read the Arduino and RTC Module DS3231 tutorial and the Battery charging circuit of DS3231 module article.
Connecting the DS3231 RTC Module to Arduino
Connecting the DS3231 RTC Module to an Arduino is simple. First, connect the SCL and SDA pins of the DS3231 RTC Module to the corresponding pins on the Arduino board as shown below. Next, connect ground to GND and 3.3V to VCC of the DS3231 RTC Module and that’s it.
Remember to run DS3231 RTC Module on 3.3V, otherwise an inserted CR2032 battery might get damaged.
Simple test code for DS3231 RTC Module
You should use a software library to communicate with the DS3231 RTC Module. There are several ones but here I use the Arduino-DS3231 library by Korneliusz Jarzębski. To install it go to the github repo and click the green “Code” button. Then select “Download Zip” from the menu as shown below:
This will download a file named “Arduino-DS3231-dev.zip
” to your computer. To install this ZIP library follow the usual steps. Click on Sketch
-> Add .ZIP Library
, and then select the file Arduino-DS3231-dev.zip
, you just downloaded.
With the library installed, you can test the function of the DS3231 RTC Module. The following simple code example sets the time and date in RTC Module to the compilation time of the sketch and then prints time and date in a loop:
#include "Wire.h" #include "DS3231.h" DS3231 rtc; void setup() { Serial.begin(9600); rtc.begin(); rtc.setDateTime(__DATE__, __TIME__); } void loop() { RTCDateTime dt = rtc.getDateTime(); Serial.println(rtc.dateFormat("d-m-Y H:i:s", dt)); delay(1000); }
The code starts by including the Wire.h
library for I2C communication and the DS3231.h
library for communicating with the DS3231 RTC.
Next, we create an instance of the DS3231
class. In the setup()
function, we initialize RTC and set the current date and time using rtc.setDateTime()
. The macros __DATE__
and __TIME__
automatically insert the date and time when the sketch was compiled. Note that the time will not be current, when the Arduino is restarted after the code was compiled and uploaded.
In the loop function, we retrieve the date and time from the RTC using RTCDateTime dt = rtc.getDateTime()
, format the date and time as a string and print it to the Serial Monitor with Serial.println(rtc.dateFormat("d-m-Y H:i:s", dt))
. The format specified here is day-month-year hours:minutes:seconds
. See the DS3231_dateformat.ino file of the Arduino-DS3231 library for more formatting examples.
Finally, we introduce a delay of 1 second,
preventing the Serial Monitor from being flooded with messages.
Output example
If you upload the code and open the Serial Monitor, you should see the date and time being printed as follows:
After having ensured that the DS3231 is correctly wired and works, let’s talk about the alarm function of the DS3231.
Alarms registers of the DS3231
The DS3231 RTC module has two alarm registers: Alarm 1 and Alarm 2. These two alarms can trigger events based on specific time conditions. The following table shows you the bit settings for these conditions:
As you can see Alarm 1 offers more configuration options. You can set it to trigger based on seconds, minutes, hours, and day/date. Alarm 2 only allows you to set minutes, hours, and day/date. Also Alarm 1 can be configured to trigger once every minute, daily, or weekly, while Alarm 2 can trigger only once or every minute.
However, none of the two Alarms allow you trigger an alarm periodically, if it doesn’t match a base unit. For instance, you can trigger an alarm every minute, or at the 10ths second of every minute, but you cannot trigger an alarm every 10 seconds using the alarm registers. But, I will show you how to get around this limitation.
Code examples for Alarms with DS3231
In this section we will discuss various code examples to trigger or use alarms.
Trigger events without using DS3231 Alarm registers
Let’s start with an example that actually does not use the Alarm register. Instead we are continuously polling the DS3231 and check its time and date. If it meets some specified conditions, we execute an action. The following code example prints the current time every 10 seconds:
#include "Wire.h" #include "DS3231.h" DS3231 rtc; void setup() { Serial.begin(9600); rtc.begin(); rtc.setDateTime(__DATE__, __TIME__); } void loop() { RTCDateTime dt = rtc.getDateTime(); if (dt.second % 10 == 0) { Serial.println(rtc.dateFormat("H:i:s", dt)); delay(1000); } }
The critical line is the following, where we check if the seconds are divisible by 10 using the modulo (%) operator:
if (dt.second % 10 == 0)
If that is the case we print the time. Note that you must add a delay of at least 1 second (delay(1000)
), otherwise the time is printed multiple times, since the loop runs faster than 1 second.
The advantage of triggering alarms this way is that you are not limited to the trigger conditions supported by Alarm registers. For instance, if you want to print the time every 10 seconds but only on Weekends (dt.dayOfWeek >= 6), you can write the following condition:
if ((dt.dayOfWeek >= 6) && (dt.second % 10 == 0)) { Serial.println(rtc.dateFormat("H:i:s", dt)); delay(1000); }
The RTCDateTime struct of the Arduino-DS3231 library has the following time and date members you can use when implementing your own trigger condition:
struct RTCDateTime { uint16_t year; uint8_t month; uint8_t day; uint8_t hour; uint8_t minute; uint8_t second; uint8_t dayOfWeek; uint32_t unixtime; };
While this gives you complete freedom, implementing the trigger conditions correctly can be quite tricky and you cannot use interrupts this way. More about interrupts later.
In the next section, I show you how to use Alarm registers to trigger event. The conditions are more limited but the code will be shorter and more robust.
Trigger events using DS3231 Alarm registers
The following code example prints the current RTC time every hour using Alarm 2:
#include "Wire.h" #include "DS3231.h" DS3231 rtc; void setup() { Serial.begin(9600); rtc.begin(); rtc.setDateTime(__DATE__, __TIME__); rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M); } void loop() { if (rtc.isAlarm2()) { // also clears alarm RTCDateTime dt = rtc.getDateTime(); Serial.println(rtc.dateFormat("H:i:s", dt)); } }
There are two new lines of code. Firstly we set the trigger condition for Alarm 2 in the setup
function via
rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M);
This condition fires when the minute matches 0 (DS3231_MATCH_M
), which is once every hour.
Secondly, we check if the condition is triggered for Alarm 2 in the loop function via
if (rtc.isAlarm2())
This is shorter and more robust, compared to checking the condition ourselves. For instance, you would have to write the following code to trigger every minute, if you check the time and date fields yourself:
if ((rtc.minute==0) & (rec.second==0)) { RTCDateTime dt = rtc.getDateTime(); Serial.println(rtc.dateFormat("H:i:s", dt)); delay(1000); }
By using the isAlarm2()
function the condition becomes simpler and we don’t need the delay() anymore, since isAlarm2()
, clears the alarm once is has been triggered. You can deactivate this behaviour by calling isAlarm2(false)
, instead.
Time and date matching
Trigger conditions are defined by specifying which time and date parts should match given values. For instance, if you want to trigger an alarm every Monday (=1) at 10:45:30 you would set the following alarm condition:
setAlarm1(1, 10, 45, 30, DS3231_MATCH_DY_H_M_S);
Note that only Alarm 1 supports the matching of seconds. The enum below shows the matching constant with their bit masks supported by the Arduino-DS3231 library for Alarm 1:
typedef enum { DS3231_EVERY_SECOND = 0b00001111, DS3231_MATCH_S = 0b00001110, DS3231_MATCH_M_S = 0b00001100, DS3231_MATCH_H_M_S = 0b00001000, DS3231_MATCH_DT_H_M_S = 0b00000000, DS3231_MATCH_DY_H_M_S = 0b00010000 } DS3231_alarm1_t;
and here are the ones for Alarm 2:
typedef enum { DS3231_EVERY_MINUTE = 0b00001110, DS3231_MATCH_M = 0b00001100, DS3231_MATCH_H_M = 0b00001000, DS3231_MATCH_DT_H_M = 0b00000000, DS3231_MATCH_DY_H_M = 0b00010000 } DS3231_alarm2_t;
You can find more examples on how to match certain times and dates in the DS3231_alarm.ino example file of the Arduino-DS3231 library.
Regardless if you use isAlarm1()
, isAlarm2()
or compare date and time fields yourself (e.g. dt.second==10
), it always involves continuous polling in the loop
function to detect if an alarm has been triggered.
Depending on how much other work you do in the loop function and how long it takes this may delay the detection of an alarm. For instance, if the code in the loop function takes 30 seconds to execute but you want to react to an alarm within a second that will not be possible.
Interrupts are a way around this issue and will be the topic of the next section.
Enable Interrupt Output
The DS3231 supports interrupts on the SQW pin. Per default this pin emits a Square Wave (SQW) signal with a configurable frequency but can be set to send an interrupt signal, if an alarm is triggered.
To try this out, connect an LED with a current limiting resistor of 220Ω or so to the SQW pin of the DS3231 as shown below:
The following code sets Alarm 1 to trigger every 10ths second (setAlarm1(0, 0, 0, 10, DS3231_MATCH_S)
) :
#include "Wire.h" #include "DS3231.h" DS3231 rtc; void setup() { Serial.begin(9600); rtc.begin(); rtc.setDateTime(__DATE__, __TIME__); rtc.setAlarm1(0, 0, 0, 10, DS3231_MATCH_S); rtc.enableOutput(false); } void loop() { RTCDateTime dt = rtc.getDateTime(); Serial.println(rtc.dateFormat("H:i:s", dt)); delay(1000); }
The important line in this code is
rtc.enableOutput(false);
which tells the DS3231 to disable the output of the Square Wave signal at the SQW pin and to send an interrupt signal instead. Without it, you will see the LED blinking with a constant frequency of 1 Hz.
The code prints out the time every second but does not check for the trigger in the loop
function. If you run this code and watch the Serial Monitor and the LED you will see that the LED goes off (and stays off), once the 10ths second has been reached. So, interrupts are signalled by SQW going LOW!
The LED stays off because we do not clear the alarm. If you want to the LED to go off for, let’s say 5 seconds (delay(5000)
), every 10 seconds, you could change the loop function as follows:
void loop() { if (rtc.isAlarm1(false)) { delay(5000); rtc.clearAlarm1(); } }
The code checks the alarm without clearing it. If the alarm is triggered it waits for 1 second, during time which the LED is off and then clears the alarm, which clears the interrupt signal at SQW and switches the LED back on.
Wake-up from Sleep using DS3231 Interrupts
The most common use case for interrupts is to wake up an Arduino from deep sleep in a regular interval to perform some actions such as data logging. For instance, you might want to measure the ambient temperature every 30 minutes but want the Arduino to sleep in between to preserve battery power.
To do this we remove the LED connected to the SQW pin and instead connect the SQW pin of the DS3231 to pin 2 of the Arduino as shown below:
Note that you must use an Arduino pin that supports interrupts. In case of an Arduino Uno only pins 2 and 3 can be used for interrupts.
With the interrupt connection in place, we can now wake up the Arduino from deep sleep when an alarm triggers. The following code example puts the Arduino into deep sleep, wakes it up every minute and then prints then prints the time:
#include "Wire.h" #include "DS3231.h" #include "avr/sleep.h" const int intPin = 2; DS3231 rtc; void wakeUp() { } void setup() { Serial.begin(9600); pinMode(intPin, INPUT); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); rtc.begin(); rtc.setDateTime(__DATE__, __TIME__); rtc.setAlarm1(0, 0, 0, 0, DS3231_MATCH_S); rtc.enableOutput(false); } void loop() { attachInterrupt(digitalPinToInterrupt(intPin), wakeUp, LOW); Serial.println("sleeping..."); delay(100); // finish printing sleep_cpu(); // <= go to sleep // waking up here when interrupt detected detachInterrupt(digitalPinToInterrupt(intPin)); rtc.clearAlarm1(); RTCDateTime dt = rtc.getDateTime(); Serial.println(rtc.dateFormat("H:i:s", dt)); delay(100); // finish printing }
Let’s break down the code into its components for a better understanding.
Includes
We start by including the necessary libraries for our project: Wire.h
for I2C communication, DS3231.h
for interfacing with the DS3231 RTC, and avr/sleep.h
for managing sleep modes.
#include "Wire.h" #include "DS3231.h" #include "avr/sleep.h"
Constants and Variables
Next, we define a constant for the interrupt pin and create an instance of the DS3231
class to interact with the RTC.
const int intPin = 2; DS3231 rtc;
Wake Up Function
The wakeUp()
function is defined but remains empty. It is required by the attachInterrupt()
function and can’t be null. This function will be called when the interrupt is triggered.
void wakeUp() { }
You could do simple things there, like incrementing a variable but we don’t need it and won’t use. Though be careful, you should not do anything complex there that is related to interrupts, which includes Serial.print()
!
Setup Function
In the setup()
function, we initialize the serial communication, configure the interrupt pin, and set the Arduino sleep mode.
void setup() { Serial.begin(9600); pinMode(intPin, INPUT); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); rtc.begin(); rtc.setDateTime(__DATE__, __TIME__); rtc.setAlarm1(0, 0, 0, 0, DS3231_MATCH_S); rtc.enableOutput(false); }
We also set time and date of the RTC, set Alarm 1 to be triggered every minute (second = 0) and switch output (SQW) to send interrupts.
Loop Function
In the loop()
function, we attach an interrupt to the interrupt pin, allowing the microcontroller to wake up when the pin 2 goes LOW. We print “sleeping…” to the Serial Monitor and then put the Arduino into sleep mode.
void loop() { attachInterrupt(digitalPinToInterrupt(intPin), wakeUp, LOW); Serial.println("sleeping..."); delay(100); // finish printing sleep_cpu(); detachInterrupt(digitalPinToInterrupt(intPin)); rtc.clearAlarm1(); RTCDateTime dt = rtc.getDateTime(); Serial.println(rtc.dateFormat("H:i:s", dt)); delay(100); // finish printing }
If an interrupt occurs (pin 2 goes LOW), we wake up (right after the line with sleep_cpu()
). First, we disable the interrupt to prevent it from being triggered again immediately. We also clear the alarm to bring the interrupt pin SQW of the DS3231 back to HIGH.
Then we print the current time and wait a bit to let the Serial Monitor to finish the printing. Without the delay output to the Serial Monitor may be truncated.
Output Example
If you upload this code you should see the following output on your Serial Monitor:
Note that the Arduino goes to sleep, wakes up every minute and prints the time.
We did not have to check if an alarm was triggered by using isAlarm1()
, since we know that an interrupt is only fired when the set Alarm 1 is triggered. However, if you set Alarm 1 and Alarm 2, which you can do, then you can identify which one was triggered by checking isAlarm1()
and isAlarm2()
.
I show you an example in the following section, were we also connect a buzzer to signal that an alarm has been triggered.
Distinguishing Alarms and Buzzing
We start by connecting an active buzzer to pin 3 of the Arduino. Any other pin that supports PWM would work as well.
Note that for this example I removed the interrupt signal wire from SQW, since I am not going to use interrupts here. But you could.
The following code causes the Arduino to sound an alarm every minute and every hour using the buzzer:
#include "Wire.h" #include "DS3231.h" const int buzzerPin = 3; DS3231 rtc; void setup() { rtc.begin(); rtc.setDateTime(__DATE__, __TIME__); rtc.setAlarm1(0, 0, 0, 0, DS3231_MATCH_S); // every minute rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M); // every hour } void loop() { if (rtc.isAlarm1() && !rtc.isAlarm2()) { tone(buzzerPin, 1000, 200); } if (rtc.isAlarm2()) { tone(buzzerPin, 2000, 200); } }
In the setup
function we define the two alarms. Alarm 1 matches the seconds to be equal to zero, which means it will get triggered every minute. Alarm 2 matches the minutes to be equal to zero, which means it will get triggered every hour:
void setup() { ... rtc.setAlarm1(0, 0, 0, 0, DS3231_MATCH_S); // every minute rtc.setAlarm2(0, 0, 0, DS3231_MATCH_M); // every hour }
In the loop
function we check which alarm was triggered and sound a different tone:
void loop() { if (rtc.isAlarm1() && !rtc.isAlarm2()) { tone(buzzerPin, 1000, 200); } if (rtc.isAlarm2()) { tone(buzzerPin, 2000, 200); } }
Since, at the start of every hour the minutes and the seconds are equal to 0 both alarms are triggered. But we want to only play the sound of Alarm 2 at the hour mark. Therefore we only sound Alarm 1, if Alarm 2 is not triggered (rtc.isAlarm1() && !rtc.isAlarm2()
).
You could also implement this more elegantly as:
if (rtc.isAlarm1()) { tone(buzzerPin, rtc.isAlarm2() ? 2000 : 1000, 200); }
And that’s it. I hope by now you know a lot more about the alarm function of the DS3231.
Conclusions
In this tutorial you learned how to use alarms with the Real-Time-Clock (RTC) DS3231 and an Arduino.
We did not talk about how to set the time and date of the RTC. We simply took the compile time to initialize the RTC, which fine as long as you don’t restart the Arduino. If you want to set or adjust the time after a reset or while the Arduino is running have a look at the Arduino and RTC Module DS3231 tutorial, where we use push buttons to do that. There you can also learn how to display the time on an LCD.
If you want to learn a bit more about the different sleep modes, have a look at the How Do I Wake Up My Arduino From Sleep Mode? tutorial. However, for battery powered projects you might be better off with an ESP32.
Furthermore, since an ESP32 supports Wi-Fi, synchronizing the RTC clock with an internet time server is easy and eliminates the need for buttons to adjust the time. It also solves the problem with daylight savings time. Have a look at the Real-Time-Clock DS3231 with ESP32 tutorial.
If you have any questions feel free to leave them in the comment section.
Happy tinkering ; )
Stefan is a professional software developer and researcher. He has worked in robotics, bioinformatics, image/audio processing and education at Siemens, IBM and Google. He specializes in AI and machine learning and has a keen interest in DIY projects involving Arduino and 3D printing.