ESP32: Пробуждение из глубокого сна с помощью внешних будильников (DS3231 RTC)

В этом руководстве вы узнаете, как использовать модуль реального времени DS3231 RTC для пробуждения ESP32 из глубокого сна (deep sleep). Модуль DS3231 RTC можно настроить с будильниками, и когда будильник срабатывает, он может генерировать внешнее прерывание, которое пробуждает ESP32.

ESP32 можно пробудить из сна, используя различные источники пробуждения. Одним из таких источников является внешнее пробуждение (external wake-up). Внешнее пробуждение позволяет ESP32 просыпаться при изменении состояния одного или нескольких GPIO.

С другой стороны, DS3231 можно настроить с будильниками. Когда будильник срабатывает, модуль изменяет состояние вывода SQW с HIGH на LOW.

Модуль RTC DS3231

Модуль RTC DS3231

Подключив вывод SQW к ESP32 и отслеживая его состояние во время глубокого сна, мы можем пробудить ESP32 при срабатывании будильника.

ESP32 с модулем DS3231 RTC - настройка будильников

ESP32 с модулем DS3231 RTC - настройка будильников

Установка библиотеки RTCLib

Существует несколько библиотек для взаимодействия с модулем DS3231 RTC. Мы будем использовать RTCLib от Adafruit, которая совместима с модулями RTC DS1307, DS3231 и PCF8523.

В Arduino IDE перейдите в Sketch > Include Library > Manage Libraries. Найдите RTCLib и установите библиотеку от Adafruit. Мы используем версию 2.1.4.

Arduino IDE установка библиотеки RTCLib

Arduino IDE установка библиотеки RTCLib

Подключение схемы

Для тестирования примера из этого руководства вам нужно подключить модуль DS3231 RTC к ESP32. Он взаимодействует по протоколу :doc:`I2C <../esp32-i2c-communication-arduino-ide/index>`_.

Вот список необходимых компонентов:

Подключите DS3231 к ESP32 по следующей таблице или схеме подключения.

Модуль DS3231 RTC

ESP32

SQW

GPIO 4*

SCL

GPIO 22

SDA

GPIO 21

VCC

3V3

GND

GND

* вы можете использовать любой другой цифровой вывод, если это RTC GPIO – :doc:`проверьте распиновку ESP32 здесь <../esp32-pinout-reference-gpios/index>`_.

ESP32 с модулем DS3231 RTC - схема подключения

ESP32 с модулем DS3231 RTC - схема подключения

Рекомендуемое чтение: :doc:`ESP32 Pinout Reference: Which GPIO pins should you use? <../esp32-pinout-reference-gpios/index>`_

Пробуждение ESP32 с помощью внешнего будильника

Следующий код настраивает будильник с модулем DS3231 RTC. ESP32 переходит в режим глубокого сна. Когда будильник срабатывает, ESP32 просыпается, мигает встроенным светодиодом и увеличивает номер загрузки (чтобы мы знали, сколько раз ESP32 просыпался). Затем ESP32 снова засыпает до следующего срабатывания будильника.

/*********
  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 IRAM_ATTR 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!");
}

Просмотреть исходный код

Как работает код?

Сначала подключите библиотеку RTCLib для связи с модулем DS3231 RTC.

#include <RTClib.h>

Также необходимо подключить rtc_io для настройки подтягивающих резисторов RTC GPIO, используемого в качестве источника пробуждения.

#include "driver/rtc_io.h"

Настройте GPIO, который будет подключен к выводу SQW модуля реального времени. Этот вывод должен быть определен следующим образом, поскольку он будет отвечать за пробуждение ESP32. Вы можете выбрать любой другой вывод, если это RTC GPIO.

#define CLOCK_INTERRUPT_PIN              GPIO_NUM_4     // Only RTC IO are allowed

Мы определяем GPIO для внешнего светодиода, чтобы получить визуальную индикацию пробуждения ESP32. В данном случае мы управляем встроенным светодиодом (GPIO 2 на большинстве плат ESP32).

const int ledPin = 2;

Мы создаем переменную bootCount, которая будет сохранена в RAM (RTC_DATA_ATTR – она сохранится после глубокого сна, но не после сброса), чтобы подсчитать, сколько раз ESP32 просыпался.

RTC_DATA_ATTR int bootCount = 0;

Создайте экземпляр для DS3231 с именем rtc.

RTC_DS3231 rtc;

Определите объект DateTime для установки времени будильника. Объект DateTime принимает следующие элементы времени по порядку: год, месяц, день, час, минута, секунда.

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

Создайте функцию, которая определит и выведет причину пробуждения ESP32.

// 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;
  }
}

Создайте функцию (ISR), которая будет вызвана при возникновении прерывания (когда срабатывает будильник). В данном случае мы просто выводим сообщение в Serial Monitor, но вы можете изменить её под свои нужды.

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

Вы можете узнать больше о прерываниях ESP32 и функциях ISR в этом руководстве: :doc:`ESP32 with PIR Motion Sensor using Interrupts and Timers <../esp32-pir-motion-sensor-interrupts-timers/index>`_.

В setup() установите ledPin как OUTPUT.

pinMode (ledPin, OUTPUT);

Выведите причину пробуждения ESP32, вызвав функцию print_wakeup_reason(), определенную ранее.

print_wakeup_reason();

Мигните светодиодом при пробуждении/сбросе ESP32.

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

Инициализируйте модуль RTC.

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

Настройте время RTC, если питание было потеряно.

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

Если вам нужно настроить время в любом случае, вы можете раскомментировать следующую строку. Она установит время RTC на время компиляции скетча.

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

Нам не нужен вывод 32K, поэтому отключим его.

rtc.disable32K();

Далее определите GPIO, подключенный к выводу SQW модуля RTC, как прерывание. Вывод SQW переходит в состояние LOW при срабатывании будильника, поэтому мы должны использовать режим FALLING (срабатывание прерывания при переходе уровня вывода с HIGH на LOW).

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

Затем очистите все будильники перед установкой нового.

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

Мы не будем использовать функцию генерации прямоугольных волн на выводе SQW, поэтому отключим её следующим образом.

rtc.writeSqwPinMode(DS3231_OFF);

Отключите будильник 2, если он был включен из предыдущего проекта, так как одновременно может быть активен только один будильник.

rtc.disableAlarm(2);

Наконец, запланируйте будильник с помощью функции setAlarm1() объекта rtc. Передайте в качестве аргументов время для будильника 1 и режим будильника. В данном случае мы устанавливаем DS3231_A1_Minute, что означает, что будильник сработает, когда минуты совпадут.

// 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");
}

Вы можете использовать все следующие режимы для будильника 1 (для более подробной информации ознакомьтесь с нашим :doc:`руководством по DS3231 для ESP32 <../esp32-ds3231-real-time-clock-arduino/index>`_):

Будильник

Режим

Значение (Сработать…)

Alarm 1

DS3231_A1_PerSecond

каждую секунду

Alarm 1

DS3231_A1_Second

когда совпадают секунды

Alarm 1

DS3231_A1_Minute

когда совпадают минуты

Alarm 1

DS3231_A1_Hour

когда совпадает час

Alarm 1

DS3231_A1_Date

когда совпадает дата

Alarm 1

DS3231_A1_Day

когда совпадает день

Увеличьте и выведите номер загрузки при каждом пробуждении.

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

Установите GPIO прерывания, подключенный к SQW модуля RTC, в качестве источника пробуждения с помощью функции esp_sleep_enable_ext0_wakeup(). Вывод SQW активен в состоянии LOW. Это означает, что он изменит свое состояние на LOW при срабатывании будильника. Чтобы предотвратить ложные срабатывания, мы включаем внутренний подтягивающий резистор и отключаем внутренний стягивающий резистор.

// 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);

Узнайте больше о глубоком сне ESP32 с внешним пробуждением: ESP32 External Wake Up from Deep Sleep.

Наконец, мы вызываем функцию esp_deep_sleep_start() для перевода ESP32 в режим глубокого сна.

esp_deep_sleep_start();

Когда наступит время срабатывания будильника, вывод SQW изменит свое состояние на LOW, это пробудит ESP32 и вызовет функцию onAlarm(). Когда ESP32 просыпается из глубокого сна, он начинает выполнять код с самого начала.

ESP32 никогда не доходит до loop(), потому что он засыпает до этого.

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!");
}

Вот как работает этот код.

Демонстрация

Загрузите код на ESP32. Убедитесь, что вы установили будильник на время, близкое к моменту тестирования проекта, чтобы увидеть его в действии.

После загрузки откройте Serial Monitor на скорости 115200 бод и нажмите кнопку RST.

ESP32 пробуждение с помощью внешнего будильника DS3231 RTC

ESP32 пробуждение с помощью внешнего будильника DS3231 RTC

Подождите, пока будильник сработает. Он выведет сообщение в Serial Monitor и встроенный светодиод мигнет. ESP32 останется в режиме глубокого сна до следующего срабатывания будильника.

ESP32 встроенный светодиод включен

ESP32 встроенный светодиод включен

Заключение

В этом руководстве вы узнали, как настроить будильники модуля DS3231 RTC для пробуждения ESP32 из глубокого сна. Эта функция может быть чрезвычайно полезна в проектах, где важно энергопотребление. Вы можете перевести ESP32 в режим сна на определенное время в течение дня, а затем пробудить его с помощью будильника для выполнения нужной задачи.

Надеемся, это руководство было полезным.

Узнайте больше об ESP32 с нашими ресурсами: