ESP32: Пробуждение из глубокого сна с помощью внешних будильников (DS3231 RTC)
В этом руководстве вы узнаете, как использовать модуль реального времени DS3231 RTC для пробуждения ESP32 из глубокого сна (deep sleep). Модуль DS3231 RTC можно настроить с будильниками, и когда будильник срабатывает, он может генерировать внешнее прерывание, которое пробуждает ESP32.
ESP32 можно пробудить из сна, используя различные источники пробуждения. Одним из таких источников является внешнее пробуждение (external wake-up). Внешнее пробуждение позволяет ESP32 просыпаться при изменении состояния одного или нескольких GPIO.
С другой стороны, DS3231 можно настроить с будильниками. Когда будильник срабатывает, модуль изменяет состояние вывода SQW с HIGH на LOW.
Модуль RTC DS3231
Подключив вывод SQW к ESP32 и отслеживая его состояние во время глубокого сна, мы можем пробудить ESP32 при срабатывании будильника.
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
Подключение схемы
Для тестирования примера из этого руководства вам нужно подключить модуль DS3231 RTC к ESP32. Он взаимодействует по протоколу :doc:`I2C <../esp32-i2c-communication-arduino-ide/index>`_.
Вот список необходимых компонентов:
Плата ESP32 – читайте Лучшие платы разработки ESP32
Подключите 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 - схема подключения
Рекомендуемое чтение: :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
Подождите, пока будильник сработает. Он выведет сообщение в Serial Monitor и встроенный светодиод мигнет. ESP32 останется в режиме глубокого сна до следующего срабатывания будильника.
ESP32 встроенный светодиод включен
Заключение
В этом руководстве вы узнали, как настроить будильники модуля DS3231 RTC для пробуждения ESP32 из глубокого сна. Эта функция может быть чрезвычайно полезна в проектах, где важно энергопотребление. Вы можете перевести ESP32 в режим сна на определенное время в течение дня, а затем пробудить его с помощью будильника для выполнения нужной задачи.
Надеемся, это руководство было полезным.
Узнайте больше об ESP32 с нашими ресурсами:
Learn ESP32 with Arduino IDE (eBook)
Примечание
Источник: :doc:`Random Nerd Tutorials - ESP32: Wake-Up From Deep Sleep using External Alarms (DS3231 RTC) <../esp32-wake-up-deep-sleep-external-alarms-ds3231/index>`_. Авторы: Rui Santos & Sara Santos.