ESP8266: DS3231 1Hz Square wave generator

The objective of this post is to explain how to use the square wave pin of the DS3231 to trigger interrupts with a period of 1 second in the ESP8266.


Introduction

The objective of this post is to explain how to use the square wave pin of the DS3231 to trigger interrupts with a period of 1 second in the ESP8266.

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.

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.

We will use the I2C interface of the ESP8266 to communicate with the device.


The hardware

The connection diagram for this tutorial is very simple and is illustrated in figure 1. 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 pin (SQW) of the DS3231 to the GPIO of the ESP8266 where we want the interrupt to be triggered.

esp8266-ds3231-connection-design

Figure 1 – 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 function

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> //I2C library
#include <RtcDS3231.h> //RTC library

Then, we will declare some global variables. The first one will be an object of class RtcDS3231, which provides access to all the functions of the DS3231 module [1].

This class uses the C++ concept of templates and explaining it in detail is outside the scope of this post. You can read more about templates here and here. Note that this changed from version 1.0.1 of the library, as can be seen in this previous post, where the instantiation of the RtcDS3231 object was different.

Nevertheless, this is a detail, since the declaration of the object is still very simple, as can be seen bellow. Basically, after the name of the RtcDs3231 class, we write <TwoWire>, which means that, internally, the RTC library will be using the TwoWire class from the Wire.h I2C library.

Then, we pass an instance of the TwoWire class, which is the Wire object, an extern variable of the Wire.h library that implements the methods to use the I2C protocol. You can check the .h file of the Wire library here.

RtcDS3231 <TwoWire> rtcObject(Wire);

We will also declare some more global variables, to handle the interrupts of the square wave. The interruptPin variable will be the pin number where we will attach the interrupt. We will use GPIO 13, as previously indicated in figure 1.

The seconds variable will be a value incremented when an interrupt occurs, and decremented after it is handled by the main code. Since this variable will be changed in the Interrupt Service Routine, we should declare it as volatile. The totalSeconds will be a global counter of the number of interrupts triggered.

const byte interruptPin = 13;
volatile int seconds = 0;
int totalSeconds = 0;

Now, in the setup function, we will attach an interrupt to the pin and specify the handling function.

First, we will declare the interruptPin as input and use the input pull-up mode, so we know that it will be in a known state when no input is connected to it.

Then we attach the interrupt to the pin with the attachInterrupt function. It receives as first argument the interrupt number, as second argument the interrupt service routine, and as third the interrupt mode.

In the first argument, we will use the digitalPinToInterrupt function, which receives as input the interrupt pin, to translate the actual digital pin to the specific interrupt number.

As the second argument, we will pass the handling function we will define latter.

As third argument, we can pass one of the 3 supported interrupt types. In our case, we will assume that we want to detect a falling edge in the signal, so we pass FALLING. Note that using RISING for triggering the interrupt at rising edges of the signal would have the same effect, since the output of the DS3231 pin is a square wave.

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

You can read a more detailed explanation on how to handle interrupts with the ESP8266 in this previous post.

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

Then, we will call the SetSquareWave method to specify the mode of the Square Wave pin. You can check here the supported values for the mode. In our case, we want the DS3231SquareWavePin_ModeClock, so the pin will output a square wave with the frequency specified in the SetSquareWavePinClockFrequency method.

So, we then call the previously mentioned method to specify the frequency of the output square wave of the pin. In our case, we want the DS3231SquareWaveClock_1Hz mode, which corresponds to a 1Hz square wave. You can check here the other supported values.

rtcObject.Begin(); //Starts I2C
rtcObject.SetSquareWavePin(DS3231SquareWavePin_ModeClock); //Sets pin mode
rtcObject.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); //Sets frequency

Check the full setup function bellow, which includes the start of the serial connection.

void setup() {
Serial.begin(115200); //Starts serial connection

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

rtcObject.Begin(); //Starts I2C
rtcObject.SetSquareWavePin(DS3231SquareWavePin_ModeClock); //Sets pin mode
rtcObject.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); //Sets frequency

}


The interrupt handler

The interrupt handling function will be very simple. As stated in the previous post about interrupts, an interrupt service routine should execute as fast as possible.

So, we will only increment the seconds variable and immediately return. The actual handling of the interrupt will be done in the main loop.

void handleInterrupt() {
seconds++;
}

We use a counter and not a flag because, in more complex code, multiple interrupts may occur before we are able to handle them in the main code. So, if we use a counter, we don’t loose track of how many interrupts occurred and we are sure that we will handle them all.


The main loop

The main loop will also be very simple. So, we will basically check if the seconds variable has a value greater than zero. If so, we just decrement it, increment the totalSeconds variable and print it to the serial port.

void loop() {

  if (seconds > 0) {

    seconds--;
    totalSeconds++;

    Serial.print("Total seconds: ");
    Serial.println(totalSeconds);

  }

}

The complete code for this tutorial can be seen bellow.

#include <Wire.h>       //I2C library
#include <RtcDS3231.h>  //RTC library

RtcDS3231 <TwoWire> rtcObject(Wire);

const byte interruptPin = 13;
volatile int seconds = 0;
int totalSeconds = 0;

void setup() {
  Serial.begin(115200); //Starts serial connection

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

  rtcObject.Begin(); //Starts I2C
  rtcObject.SetSquareWavePin(DS3231SquareWavePin_ModeClock); //Sets pin mode
  rtcObject.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); //Sets frequency
}

void handleInterrupt() {
  seconds++;
}

void loop() {

  if (seconds > 0) {

    seconds--;
    totalSeconds++;

    Serial.print("Total seconds: ");
    Serial.println(totalSeconds);

  }

}


Testing the code

To test the code, just upload it to your ESP8266 and open the serial port. You should get a result similar to the one indicated in figure 2. The interruptions should trigger each second, as we specified in the code.

esp8266-ds3231-square-wave

Figure 2 – Output of the program.


Related posts


References

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


Technical details

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

5 thoughts on “ESP8266: DS3231 1Hz Square wave generator”

  1. Pingback: ESP8266: DS3231 Alarm when seconds match | techtutorialsx

  2. Pingback: ESP8266: DS3231 Alarm when seconds match | techtutorialsx

  3. Pingback: ESP8266: DS3231 alarms once per second | techtutorialsx

  4. Pingback: ESP8266: DS3231 alarms once per second | techtutorialsx

  5. Is the square wave generator synchronised with the clock? If so, is it the leading edge of the pulse that is so synchronised? Sometimes it’s enough to know that something happens at 1Hz, but sometimes e.g. with a clock, it’s important to know that that thing happens exactly as the second turns over.

  6. Is the square wave generator synchronised with the clock? If so, is it the leading edge of the pulse that is so synchronised? Sometimes it’s enough to know that something happens at 1Hz, but sometimes e.g. with a clock, it’s important to know that that thing happens exactly as the second turns over.

  7. I am answering my own question here but I stumbled across the answer elsewhere so I shall leave the information here in case it is useful to anyone else.

    The clock is indeed synchronised with the square wave (whether it’s outputting or not) but it changes on the falling edge of the pulse, not the leading edge.

    1. Hi! Sorry for the delay on the answer, I’ve been very busy this week and could only catch up with the comments today.

      I’m glad you already found the answer and thanks for sharing, so others may see 🙂

      Best regards,
      Nuno Santos

  8. I am answering my own question here but I stumbled across the answer elsewhere so I shall leave the information here in case it is useful to anyone else.
    The clock is indeed synchronised with the square wave (whether it’s outputting or not) but it changes on the falling edge of the pulse, not the leading edge.

    1. Hi! Sorry for the delay on the answer, I’ve been very busy this week and could only catch up with the comments today.
      I’m glad you already found the answer and thanks for sharing, so others may see 🙂
      Best regards,
      Nuno Santos

Leave a Reply

Discover more from techtutorialsx

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

Continue reading