ESP8266: DS3231 Alarm when seconds match

The objective of this post is to explain how to use the DS3231 Real Time Clock to trigger an alarm interruption when the time matches a specified value for the seconds.


Introduction

The objective of this post is to explain how to use the DS3231 Real Time Clock to trigger an alarm interruption when the time matches a specified value for the seconds. We will use the ESP8266 interrupts to handle the alarm triggering.

For this tutorial, we consider the use of the DS3231 Real Time Clock (described in more detail in this previous post) integrated in an easy to use board, which can be bought at eBay for less than 1 euro. This module is shown in figure 1.

ds3231

Figure 1 – DS3231 RTC module.

We also assume the use of the ESP8266 libraries for the Arduino IDE. You can check here how to configure the Arduino IDE to support the ESP8266.

Finally, we will use the DS3231 libraries indicated here, which can be installed via library manager of the Arduino IDE. You can check how to install them in this previous tutorial.


The alarms of the DS3231

The DS3231 has two programmable alarms which can operate in different modes [1]. One of the alarms (alarm one) can operate with a precision of seconds and the other (alarm 2) can operate with a precision of minutes [1].

These alarms generate active-low interrupts in one of the pins of the DS3231, which is also shared with the configurable square wave output functionality. Naturally, only the alarms or the square wave can be activated at each time. Nevertheless, both alarms can operate simultaneously [1].

All of these functionalities are controlled by the internal registers of the DS3231, which we can manipulate via I2C. More details about these registers can be seen in the datasheet of the device.

Fortunately, the RTC library we are considering has simple easy to use classes and methods that abstract from us the more complex details of interfacing with the RTC.


The hardware

The connection diagram for this tutorial is illustrated in figure 2 and is the same used in the previous DS3231 post. We just need to connect the SCL and SDA pins (I2C) of the DS3231 module to the corresponding pins of the ESP8266.

Additionally, we need to connect the square wave / alarm pin (SQW) of the DS3231 to the GPIO of the ESP8266 where we want the interrupt to be triggered. In this example, we will be using GPIO13.

esp8266-ds3231-connection-design

Figure 2 – Connection diagram between the ESP8266 and the DS3231 module.

As described in the documentation of the ESP8266 libraries for the Arduino IDE, the default pins for the I2C in the Wire library  are pins 4 (SDA) and 5 (SDL). If you are using a NodeMCU board, take into consideration that the board pins don’t correspond to the ESP8266 pins (check here the correct mapping). So, if you are using a NodeMCU, the correct pin mapping is the following:

  • GPIO5 = D1
  • GPIO4 = D2
  • GPIO 13 = D7

 

The Setup code

First, we need to include the RTC library that allows us to interact with the DS3231 and also the Arduino library that implements the communication with I2C devices (wire library).

#include <Wire.h>
#include <RtcDS3231.h>

Then, we declare some global variables for our program. The first one will simply hold the number of the pin where we will attach our interruption, so its value is easy to change.

Additionally, we will declare a Boolean variable that will be used in our interrupt service routine to signal that an interrupt has occurred. Since interrupt service routines should run as fast as possible, we will handle the alarm interrupt in the main code, and this routine will only signal the interrupt occurrence by setting this flag to true.

It’s important to say that if we would be doing a lot of processing in our program, it would be a good idea to use an interrupt counter instead of a flag. So, if two interrupts occurred without the main program being able to handle the first one, storing the information in the counter instead of a flag would guarantee that the events would not be lost. But since our program will be very simple and the interrupt only occurs once every minute, we can use a flag.

const byte interruptPin = 13;
volatile bool alarm = 0;

Note that the Boolean variable is declared as volatile since it will be modified in the interrupt service routine.

We also need to declare an object of class RtcDS3231, which provides access to all the functions of the DS3231 module [2].

RtcDS3231<TwoWire> rtcObject(Wire);

This class uses the C++ concept of templates. In our declaration, we are telling to the RTC library that we will be using the TwoWire class from the Wire.h I2C library, and passing to it an instance of this class: the Wire object. But these are implementation details that are kept hidden from us by the library.

In the setup function, we will first start the serial connection, so we can send to the serial port the outputs of our program. Then, we will attach the interrupt to our previously declared pin and specify the name of the interrupt service routine that will handle it. You can check here mode details on how to use interrupts in the ESP8266.

Serial.begin(115200);

pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

Note that the interrupt mode is FALLING because the alarm interrupt in the DS3231 is active-low [1].

After this, we will call the Begin method on the RTC object, so it starts the I2C connection.

Then, in order for our alarm to work, we need to set the time of the DS3231 RTC. To do so, we create an object of class RtcDateTime, which allows us to specify the date and time to configure our real time clock in a simple and compact structure.

The constructor of this class receives the values from years to seconds. We can use the __TIME__ and __DATE__ macros to get the time and date of compilation of the program [3], and use it to set our real time clock. If you don’t want to use these values, you can manually set the time and date by passing the 6 date and time corresponding integers, as shown here.

To actually set the time and date on the device, we call the SetDateTime method on the RtcDS3231 object and pass it this RtcDateTime object that holds the date and time information.

RtcDateTime timestamp = RtcDateTime(__DATE__, __TIME__);
rtcObject.SetDateTime(timestamp);

Since we are not going to use it, we can disable the 32 kHz output pin of the clock with the Enable32kHzPin method, passing as input the value false.

Then, we need to specify that the square wave / alarm pin of the DS3231 will be working in alarm mode, since the same output pin is shared between different functionalities [1]. To do so, we use the SetSquareWavePin method, which receives as input the mode in which the pin will operate. We want the DS3231SquareWavePin_ModeAlarmOne mode, which indicates that the pin will trigger when the alarm one of the RTC triggers [4].

rtcObject.Enable32kHzPin(false);
rtcObject.SetSquareWavePin(DS3231SquareWavePin_ModeAlarmOne);

Finally, we will configure the alarm one. To do so, we will use an object of class DS3231AlarmOne. It will have the settings we want to configure for alarm one.

The constructor for this class receives as input the day, the hour, the minute and the second. Additionally, it receives a control value to indicate how the alarm will operate.

In our case, since we want our alarm to trigger once a minute when the value of seconds matches the value we specify, we will specify the DS3231AlarmOneControl_SecondsMatch for the control parameter. You can check here the other operating modes.

Although the constructor dictates that we need to pass all the values mentioned before, we just need to worry about the seconds value, since it will be the one used by the RTC to compare with the current time and trigger the alarm. Let’s assume that we want to trigger it at the second 22 of each minute. Check the declaration of the object bellow.

DS3231AlarmOne alarm1(
    0,
    0,
    0,
    22,
    DS3231AlarmOneControl_SecondsMatch);

To set the alarm, we then call the SetAlarmOne method on the rtcObject variable and pass it the previously defined DS3231AlarmOne object, which we called alarm1.

We also call the LatchAlarmsTriggeredFlags method to guarantee that our interrupt alarm is clear on the RTC. This method must be called after an alarm is triggered or otherwise it will not trigger again [5].

rtcObject.SetAlarmOne(alarm1);
rtcObject.LatchAlarmsTriggeredFlags();

You can check bellow the full setup function.

void setup() {

  Serial.begin(115200);

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

  rtcObject.Begin();

  RtcDateTime timestamp = RtcDateTime(__DATE__, __TIME__);
  rtcObject.SetDateTime(timestamp);

  rtcObject.Enable32kHzPin(false);
  rtcObject.SetSquareWavePin(DS3231SquareWavePin_ModeAlarmOne);

  DS3231AlarmOne alarm1(
    0,
    0,
    0,
    22,
    DS3231AlarmOneControl_SecondsMatch);

  rtcObject.SetAlarmOne(alarm1);
  rtcObject.LatchAlarmsTriggeredFlags();

}


The interrupt service routine

As we stated, the interrupt service routine will only signal the main loop function that the alarm has triggered and it needs to be handled. So, it will be very short, as interrupt service routines should be. Check the code bellow.

void handleInterrupt() {
   alarm = true;
}


The main loop

Our main loop will check for the value of the alarm signaling flag and execute an handling function that prints the current time when the alarm is triggered. In order to keep our code readable, we will declare an auxiliary function to perform this handling.

So, the main loop will be as indicated bellow.

void loop() {

  if (alarm == true) {
    handleAlarm();
  }

}

Our handling function, called handleAlarm, will read the current time from the RTC and print it to the serial port. You can check this previous post that explains in detail how to read the current time of the RTC.

First, we will set our alarm variable to false, to signal that the interrupt was handled. Then, we call the GetDateTime method on our rtcObject, which will return the current date and time value. This method will return a RtcDateTime object, as we used before in the setup function.

The RtcDateTime class has a method for getting each of the parameters of date and time. We will use those methods to get them and print them to a string (using the sprintf function). For simplicity, we will just get the current time (hours, minutes and seconds).

In the end, we must not forget to call the previously mentioned LatchAlarmsTriggeredFlags method, so the alarm can be triggered again.

You can check the full code bellow, including this handling function.

#include <Wire.h>
#include <RtcDS3231.h>

const byte interruptPin = 13;
volatile bool alarm = 0;

RtcDS3231<TwoWire> rtcObject(Wire);

void setup() {

  Serial.begin(115200);

  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

  rtcObject.Begin();

  RtcDateTime timestamp = RtcDateTime(__DATE__, __TIME__);
  rtcObject.SetDateTime(timestamp);

  rtcObject.Enable32kHzPin(false);
  rtcObject.SetSquareWavePin(DS3231SquareWavePin_ModeAlarmOne);

  DS3231AlarmOne alarm1(
    0,
    0,
    0,
    22,
    DS3231AlarmOneControl_SecondsMatch);

  rtcObject.SetAlarmOne(alarm1);
  rtcObject.LatchAlarmsTriggeredFlags();

}

void loop() {

  if (alarm == true) {
    handleAlarm();
  }

}

void handleAlarm() {
  alarm = false;

  RtcDateTime timestamp = rtcObject.GetDateTime();

  Serial.print("time interrupt at: ");
  char time[10];

  sprintf(time, "%d:%d:%d",
          timestamp.Hour(),
          timestamp.Minute(),
          timestamp.Second()
         );
Serial.println(time);

  rtcObject.LatchAlarmsTriggeredFlags();

}

void handleInterrupt() {
  alarm = true;
}


Testing the code

To test the code, just upload it to the ESP8266 and open the serial console. You should get an output similar to the one shown in figure 3.

esp8266-ds3231-alarm-1-seconds-match

Figure 3 – Output of the program

As we can see, the alarm triggered every minute at the second 22, as we configured before.


Final notes

As we cans see through this post, the DS3231 is a very useful and versatile piece of hardware, which has capabilities beyond just simple timekeeping. In the case of the alarms, it supports some more operating modes, that we will be exploring latter.

The alarm functionality is very useful since we can now program the device to periodically warn the ESP8266 (or other microcontroller). This can be very useful implement sensor/monitoring devices, avoiding the need to constantly poll the RTC or keep an internal clock on the microcontroller.

 

Related posts

 

Related content


References

[1] https://web.wpi.edu/Pubs/E-project/Available/E-project-010908-124414/unrestricted/DS3231-DS3231S.pdf

[2] https://github.com/Makuna/Rtc/wiki/RtcDS3231-object

[3] http://forum.arduino.cc/index.php?topic=158014.0

[4] https://github.com/Makuna/Rtc/wiki/RtcDS3231-object#void-setsquarewavepinds3231squarewaveout-pinmode

[5] https://github.com/Makuna/Rtc/wiki/RtcDS3231-object#ds3231alarmflag-latchalarmstriggeredflags

 

Technical details

  • ESP8266 libraries: v2.3.0
  • RTC library: V2.0.0

22 thoughts on “ESP8266: DS3231 Alarm when seconds match”

  1. Hello,
    I used your code to trigger an arduino mega, I can correctly read the DS3231 dateTime, but the alarm doesn’t trigger the Arduino, and I can’t find why ! (I wired DS3231 SQW output to pin2 of the Arduino)

  2. That’s ok now, I’ve forgot to call “rtcObject.LatchAlarmsTriggeredFlags();” in my triggered function !
    Note about Makuta’s library : I had update it because DayOfWeek was setting as well (just drop the modified day for Sunday), but it was’nt read from DS3231, so I add the read option.

    1. Hi! sorry for the delay in the response, I’m glad you have been able to make it work 🙂

      And thanks for sharing the information!

      Best regards,
      Nuno Santos

  3. That’s ok now, I’ve forgot to call “rtcObject.LatchAlarmsTriggeredFlags();” in my triggered function !
    Note about Makuta’s library : I had update it because DayOfWeek was setting as well (just drop the modified day for Sunday), but it was’nt read from DS3231, so I add the read option.

    1. Hi! sorry for the delay in the response, I’m glad you have been able to make it work 🙂
      And thanks for sharing the information!
      Best regards,
      Nuno Santos

  4. Is it possible to use this code (or adjusted code?) to wake up an ESP8266 from deep sleep by triggering low the RST pin form the SQW pin of the DS3231? If so, how? Thanks!

    1. Hi!

      Unfortunately I haven’t yet explored the deep sleep modes of the ESP8266, so I’m not sure how they work :/

      My suggestion is to ask around the ESP8266 forum:
      https://www.esp8266.com/

      Maybe someone there can help 🙂

      Let us know if you find an answer.

      Best regards,
      Nuno Santos

      1. Meanwhile I had found out that the DS3231 INT/SQW output remains low at alarm trigger. This requires a decoupling capacitor (about 470nF) with the RST input to prevent its input to remain low, to allow the program upon restart to reset the INT/SQW output to be reset.

  5. Is it possible to use this code (or adjusted code?) to wake up an ESP8266 from deep sleep by triggering low the RST pin form the SQW pin of the DS3231? If so, how? Thanks!

    1. Hi!
      Unfortunately I haven’t yet explored the deep sleep modes of the ESP8266, so I’m not sure how they work :/
      My suggestion is to ask around the ESP8266 forum:
      https://www.esp8266.com/
      Maybe someone there can help 🙂
      Let us know if you find an answer.
      Best regards,
      Nuno Santos

      1. Meanwhile I had found out that the DS3231 INT/SQW output remains low at alarm trigger. This requires a decoupling capacitor (about 470nF) with the RST input to prevent its input to remain low, to allow the program upon restart to reset the INT/SQW output to be reset.

Leave a Reply

Discover more from techtutorialsx

Subscribe now to keep reading and get access to the full archive.

Continue reading