ESP32: Wake-Up From Deep Sleep using External Alarms (DS3231 RTC)

In this tutorial, you’ll learn how to wake up the ESP32 from deep sleep using an external scheduled alarm. The DS3231 RTC module will trigger an alarm at a specified time that will trigger an output to wake up the ESP32 (using an external wake-up).

ESP32: Wake-Up From Deep Sleep using External Alarms (DS3231 RTC)

Before proceeding with this tutorial, we recommend that you that a look at our deep sleep guide as well as our getting-started guide for the DS3231 RTC module:

Prerequisites

This tutorial focuses on programming the ESP32 using the Arduino core. Before proceeding, you should have the ESP32 Arduino core installed in your Arduino IDE. Follow the next tutorial to install the ESP32 on the Arduino IDE, if you haven’t already.

Project Overview

The ESP32 can be awakened from sleep using different wake-up sources. One of those wake-up sources includes an external wake-up. This external wake-up allows the ESP32 to be awakened when the state of one or several GPIOs changes.

On the other hand, the DS3231 can be configured with alarms. When an alarm is fired, the module changes the state of the SQW pin from HIGH to LOW.

RTC DS3231 Module

By connecting the SQW pin to the ESP32 and monitoring its state during deep sleep, we can use wake-up the ESP32 when the alarm is triggered.

ESP32 with DS3231 RTC Module Setting Alarms

Installing the RTCLib Library

There are several libraries to interface with the DS3231 RTC module. We’ll use the RTCLib from Adafruit that is compatible with DS1307, DS3231, and PCF8523 RTC modules.

In the Arduino IDE, go to Sketch > Include Library > Manage Libraries. Search for RTCLib and install the library by Adafruit. We’re using version 2.1.4.

Arduino IDE install RTCLib Library

Wiring the Circuit

To test the example in this tutorial, you need to connect the DS3231 RTC module to the ESP32. It communicates using I2C communication protocol.

Here’s a list of the parts needed:

Wire the DS3231 to the ESP32 by following the next table or the schematic diagram as a reference.

DS3231 RTC ModuleESP32
SQWGPIO 4*
SCLGPIO 22
SDAGPIO 21
VCC3V3
GNDGND

* you can use any other digital pin as long as it is an RTC GPIO — check the ESP32 pinout here.

ESP32 with DS3231 RTC Module Circuit Diagram

Recommended reading: ESP32 Pinout Reference: Which GPIO pins should you use?

Wake-Up the ESP32 Using an External Alarm

The following code sets up an alarm with the DS3231 RTC module. The ESP32 goes into deep sleep mode. When the alarm is fired, the ESP32 wakes up, blinks the onboard LED, and increases the boot number (so we have an idea of how many times the ESP32 has woken up). Then, the ESP32 goes back to sleep until it is awakened by the alarm again.

/*********
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete instructions at https://RandomNerdTutorials.com/esp32-wake-up-deep-sleep-external-alarms-ds3231/
*********/

#include <RTClib.h>
#include "driver/rtc_io.h"

// Define the DS3231 Interrupt pin (will wake-up the ESP32 - must be an RTC GPIO)
#define CLOCK_INTERRUPT_PIN              GPIO_NUM_4  // Only RTC IO are allowed

// LED for visual indication
const int ledPin = 2;

// Save how many times the ESP32 woke-up
RTC_DATA_ATTR int bootCount = 0;

// Instance for the RTC
RTC_DS3231 rtc;

// Set the alarm
DateTime alarm1Time = DateTime(2024, 12, 18, 12, 25, 0);

// Method to print the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason() {
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch (wakeup_reason) {
    case ESP_SLEEP_WAKEUP_EXT0:     Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1:     Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER:    Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP:      Serial.println("Wakeup caused by ULP program"); break;
    default:                        Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break;
  }
}

void onAlarm(){
  Serial.print("Alarm occurred!");
}

void setup() {
  Serial.begin(115200);
  pinMode (ledPin, OUTPUT);
  
  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  // Blink the LED when the ESP32 wakes-up
  digitalWrite(ledPin, HIGH);
  delay(1000);
  digitalWrite(ledPin, LOW);

  // Initialize the RTC
  if(!rtc.begin()) {
    Serial.println("Couldn't find RTC!");
    Serial.flush();
    while (1) delay(10);
  }

  if(rtc.lostPower()) {
      // this will adjust to the date and time at compilation
      rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
  
  // Uncomment if you need to define the time of the RTC
  //rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  // We don't need the 32K Pin, so disable it
  rtc.disable32K();

  // The alarm will trigger an interrupt
  pinMode(CLOCK_INTERRUPT_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(CLOCK_INTERRUPT_PIN), onAlarm, FALLING);

  // Set alarm 1, 2 flag to false (so alarm 1, 2 didn't happen so far)
  // if not done, this easily leads to problems, as both register aren't reset on reboot/recompile
  rtc.clearAlarm(1);
  rtc.clearAlarm(2);

  // Stop oscillating signals at SQW Pin otherwise setAlarm1 will fail
  rtc.writeSqwPinMode(DS3231_OFF);

  // Turn off alarm 2 (in case it isn't off already)
  // again, this isn't done at reboot, so a previously set alarm could easily go overlooked
  rtc.disableAlarm(2);

  // Schedule an alarm
  if(!rtc.setAlarm1(alarm1Time, DS3231_A1_Minute)) {  // this mode triggers the alarm when the minutes match
      Serial.println("Error, alarm wasn't set!");
  }else {
      Serial.println("Alarm will happen at specified time");
  }
  
  // Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  // Configure external wake-up
  esp_sleep_enable_ext0_wakeup(CLOCK_INTERRUPT_PIN, 0);  //1 = High, 0 = Low
  // Configure pullup/downs via RTCIO to tie wakeup pins to inactive level during deepsleep.
  // The RTC SQW pin is active low
  rtc_gpio_pulldown_dis(CLOCK_INTERRUPT_PIN);
  rtc_gpio_pullup_en(CLOCK_INTERRUPT_PIN);

  //Go to sleep now until an alarm fires
  Serial.println("Going to sleep now");
  esp_deep_sleep_start();
}

void loop() {
  // The code never reaches the loop, because the ESP32 goes to sleep at the end of setup
  Serial.print("This will never be printed!");
}

View raw code

How Does the Code Work?

First include the RTCLib library to communicate with the DS3231 RTC module.

#include <RTClib.h>

You also need to include the rtc_io to set up the pull-up and pull-down resistors of the RTC GPIO used as a wake-up source.

#include "driver/rtc_io.h"

Set up the GPIO that will be connected to the real-time clock SQW pin. This pin must be defined as follows because it will be responsible for waking up the ESP32. You can choose any other pin as long as it is a RTC GPIO.

#define CLOCK_INTERRUPT_PIN              GPIO_NUM_4     // Only RTC IO are allowed

We’re defining a GPIO for an external LED to give us a visual indication of when the ESP32 wakes up. In this case, we’re controlling the onboard LED (GPIO 2 on most ESP32 boards).

const int ledPin = 2;

We create a variable called bootCount that will be saved in RAM (RTC_DATA_ATTR — it will persist across deep sleep, but not after a reset) to count how many times te ESP32 has awakened.

RTC_DATA_ATTR int bootCount = 0;

Create an instance for the DS3231 called rtc.

RTC_DS3231 rtc;

Define a DateTime object to set the time for the alarm. The date time object accepts these time elements in order: year, month, day, hour, minute, second.

DateTime alarm1Time = DateTime(2024, 12, 18, 12, 59, 0);

Create a function that will detect and print the ESP32 wake-up cause.

// Method to print the reason by which ESP32 has been awaken from sleep
void print_wakeup_reason() {
  esp_sleep_wakeup_cause_t wakeup_reason;

  wakeup_reason = esp_sleep_get_wakeup_cause();

  switch (wakeup_reason) {
    case ESP_SLEEP_WAKEUP_EXT0:     Serial.println("Wakeup caused by external signal using RTC_IO"); break;
    case ESP_SLEEP_WAKEUP_EXT1:     Serial.println("Wakeup caused by external signal using RTC_CNTL"); break;
    case ESP_SLEEP_WAKEUP_TIMER:    Serial.println("Wakeup caused by timer"); break;
    case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break;
    case ESP_SLEEP_WAKEUP_ULP:      Serial.println("Wakeup caused by ULP program"); break;
    default:                        Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break;
  }
}

Create a function (ISR) that will be called when an interrupt happens (when the alarm fires). In this case, we’re just printing a message to the Serial Monitor, but you can modify it to suit your needs.

void onAlarm(){
  Serial.print("Alarm occured!");
}

You can learn more about interrupts with the ESP32 and ISR functions in this tutorial: ESP32 with PIR Motion Sensor using Interrupts and Timers.

In the setup(), set the ledPin as an OUTPUT.

pinMode (ledPin, OUTPUT);

Print the ESP32 wake-up reason by calling the print_wakeup_reason() function defined earlier.

print_wakeup_reason();

Blink the LED when the ESP32 wakes up/resets.

digitalWrite(ledPin, HIGH);
delay(1000);
digitalWrite(ledPin, LOW);

Initialize the RTC module.

if(!rtc.begin()) {
  Serial.println("Couldn't find RTC!");
  Serial.flush();
  while (1) delay(10);
}

Adjust the time of the RTC if it has lost power.

if(rtc.lostPower()) {
  // this will adjust to the date and time at compilation
  rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}

If you need to adjust the time regardless, you can uncomment the following line. It will set the time for the RTC as the time of compiling the sketch.

//rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

We need to disable the 32K pin because we won’t use it.

rtc.disable32K();

Next, define the GPIO that is connected to the RTC SQW pin as an interrupt. The SQW pin will go LOW when an alarm fires, so we must use the FALLING mode (trigger the interrupt when the level of the pin goes from HIGH to LOW).

// The alarm will trigger an interrupt
pinMode(CLOCK_INTERRUPT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(CLOCK_INTERRUPT_PIN), onAlarm, FALLING);

Then, clear all the alarms before setting a new one.

rtc.clearAlarm(1);
rtc.clearAlarm(2);

We won’t use the feature of outputting square waves on the SQW pin, so disable it as follows.

rtc.writeSqwPinMode(DS3231_OFF);

Disable alarm 2 in case it is enabled from any previous project as you can only have one alarm enabled at a time.

rtc.disableAlarm(2);

Finnally, schedule your alarm using the setAlarm1() function on the rtc object. Pass as arguments the time for alarm 1 and the alarm mode. In this case, we’re setting it to DS3231_A1_Minute, which means the alarm will fire when the minutes match.

// Schedule Alarm1 to fire when the minutes match
if(!rtc.setAlarm1(alarm1Time, DS3231_A1_Minute)) {  // this mode triggers the alarm when the minutes match
    Serial.println("Error, alarm wasn't set!");
}else {
    Serial.println("Alarm 1 will happen at specified time");
}

You can use all the following modes for alarm 1 (for more details check out our DS3231 guide for the ESP32):

AlarmModeMeaning (Trigger the alarm…)
Alarm 1DS3231_A1_PerSecondevery second
Alarm 1DS3231_A1_Secondwhen the seconds match
Alarm 1DS3231_A1_Minutewhen the minutes match
Alarm 1DS3231_A1_Hourwhen the hour matches
Alarm 1DS3231_A1_Datewhen the date matches
Alarm 1DS3231_A1_Daywhen the day matches

Increment and print the boot number with every wake-up.

++bootCount;
Serial.println("Boot number: " + String(bootCount));

Set the interrupt GPIO connected to the SQW of the RTC module as the wake-up source using the esp_sleep_enable_ext0_wakeup() function. The SQW pin is active LOW. This means it will change its state to LOW when the alarm fires. To prevent any false positives/negatives, we’re enabling its internal pull-up resistor and disabling the internal pull-down resistor.

// Configure external wake-up
esp_sleep_enable_ext0_wakeup(CLOCK_INTERRUPT_PIN, 0);  //1 = High, 0 = Low
// Configure pullup/downs via RTCIO to tie wakeup pins to inactive level during deepsleep.
// The RTC SQW pin is active low
rtc_gpio_pulldown_dis(CLOCK_INTERRUPT_PIN);
rtc_gpio_pullup_en(CLOCK_INTERRUPT_PIN);

Learn more about ESP32 deep sleep with external wake-up: ESP32 External Wake Up from Deep Sleep.

Finally, we call the esp_deep_sleep_start() function to put the ESP32 in deep sleep mode.

esp_deep_sleep_start();

When it’s time to fire the alarm, the SQW pin will change its state to LOW, this will wake up the ESP32 and trigger the onAlarm() function. When the ESP32 wakes-up from deep sleep, it will start running the code from the start.

The ESP32 never reaches the loop() because it goes to sleep before that.

void loop() {
  // The code never reaches the loop, because the ESP32 goes to sleep at the end of setup
  Serial.print("This will never be printed!");
}

And that’s pretty much how the code works.

Demonstration

Upload the code to your ESP32. Make sure to set up an alarm close to the time you’re testing this project so that you can see it in action.

After uploading, open the Serial Monitor at a baud rate of 115200 and press the RST button.

ESP32 Wake-Up Using External Alarm from DS3231 RTC Module

Wait for the alarm to be triggered. It will print a message on the Serial Monitor and the onboard LED will blink. The ESP32 will remain in deep sleep mode until the alarm fires again.

ESP32 board Built in LED turned on HIGH

Wrapping Up

In this tutorial, you learned how to set up alarms with the DS3231 RTC module to wake up the ESP32 from deep sleep. This feature can be extremely useful in projects in which power consumption is a concern. You can wake up the ESP32 at specified times throughout the day and then wake it up with an alarm to perform a desired task.

We hope you’ve found this guide useful.

Learn more about the ESP32 with our resources.

Thanks for reading.



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

4 thoughts on “ESP32: Wake-Up From Deep Sleep using External Alarms (DS3231 RTC)”

  1. I’m trying to think of the benefit of using this rather than the build in wake timer. Is it so that you can set an actual time to wake up rather than in X number of MS?

    It seems the deep sleep power consumption of ESP32 is less than that of the DS3231.

    Reply
  2. I have used this on an ESP8266-01 for the simple reason that pin 16 is not easily available on that module. On an ESP32 I do not see much advantage, but it indeed does illustrate a possibility

    Reply
  3. Hello,

    I am using an ESP32 that does WIFI + connection to MQTT server + send Temperature data and then does a deep sleep for 1 minute. The ESP32 is on sector (the future version it will work on battery). This code works randomly for 5 hours to 10 days. And then the ESP32 does not wake up. Some other user seems to have the same problem.
    How long have you test the wakeup via external signal (not internal clock).
    Have you some pointers to solve this problem ?

    Reply

Leave a Comment

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.