Время NTP на ESP32: интернет-часы, часовой пояс, OLED и резервный RTC
Как получить время NTP на ESP32: configTzTime и часовые пояса, getLocalTime, strftime, часы с OLED, резервный DS3231, ежедневные задачи по расписанию и ntptime на Pico W.
Получать время с помощью ESP32 NTP — значит подключать плату к серверам времени в интернете и узнавать дату и время с точностью до нескольких десятков миллисекунд без отдельного модуля часов. После подключения ESP32 к Wi-Fi один вызов configTzTime() получает время и применяет твой часовой пояс, а дальше плата отсчитывает время самостоятельно. Настольные часы, полив по расписанию, временные метки при записи данных, утренний будильник или освещение, включающееся в заданное время, больше не требуют отдельного модуля RTC.
В этом руководстве мы пошагово разберём, как работает NTP, как настроить правильный часовой пояс, записать дату в любом формате с помощью getLocalTime и strftime, вывести время на OLED-дисплей, сохранить его с помощью модуля DS3231 при потере Wi-Fi или отключении питания и запускать задачу каждый день в заданное время. В последнем разделе выполним ту же задачу на Raspberry Pi Pico W с модулем ntptime из MicroPython.

Что такое NTP и как ESP32 получает время?
NTP (Network Time Protocol) — протокол синхронизации часов компьютеров и устройств через интернет, используемый с 1980-х годов. Серверы организованы в уровни, называемые стратами: серверы stratum 1 подключены непосредственно к атомным часам или GPS, серверы stratum 2 получают время от них и так далее. ESP32 запрашивает время небольшим пакетом через UDP-порт 123; сервер отвечает временной меткой, содержащей число секунд с 1900 года и долю секунды. ESP32 учитывает время прохождения пакета туда и обратно и устанавливает свои часы. При домашнем подключении точность обычно составляет от 10 до 50 миллисекунд, чего более чем достаточно для проекта часов.
Сетевой стек lwIP внутри ESP32 использует SNTP, упрощённую версию NTP. Получив время, ESP32 сохраняет его как системное и продолжает отсчёт таймером, работающим от кварцевого резонатора; она не обращается к интернету каждую секунду. В фоновом режиме плата повторно запрашивает сервер через равные промежутки времени (по умолчанию каждые 3 часа в ядре ESP32 для Arduino) и исправляет небольшие отклонения. Системное время хранится в UTC; отображаемое местное время вычисляется из него по правилу часового пояса.
| Понятие | Значение / объяснение |
|---|---|
| Протокол и порт | SNTP, UDP 123 |
| Временная метка | Секунды с 1900 года (ESP32 преобразует их во время Unix с отсчётом от 1970 года) |
| Обычная точность | 10–50 мс при домашнем подключении |
| Повторная синхронизация | По умолчанию 3 часа в Arduino ESP32; меняется через sntp_set_sync_interval() (минимум 15 с) |
| Максимальное число серверов | 3 (имена серверов, передаваемые в configTzTime) |
| Интернет-трафик | Запрос и ответ — пакеты NTP по 48 байт каждый; не более нескольких килобайт в день |
| Что понадобится | ESP32, сеть Wi-Fi 2,4 GHz и подключение к интернету |
configTime и configTzTime: настройка часового пояса
В ядре ESP32 для Arduino есть два способа запустить NTP. configTime(gmtOffset_sec, daylightOffset_sec, server1, server2, server3) принимает смещение относительно UTC и смещение летнего времени в секундах. Например, для Турции нужно написать configTime(10800, 0, "pool.ntp.org"), поскольку 3 часа — это 10 800 секунд, а с 2016 года Турция круглый год живёт по UTC+3 без перехода на летнее время. Этот способ прост, но не знает дат перехода на летнее время в твоём регионе; там, где переводят часы, например в Европе или США, переход произойдёт не в тот день, поэтому используй configTzTime.
Более удобный способ — configTzTime(TZ, server1, server2, server3). Эта функция принимает строку часового пояса в формате POSIX, содержащую также правила перехода на летнее время, и ESP32 каждый год выполняет переход автоматически. Для Турции строка имеет вид «<+03>-3»: +03 в угловых скобках — обозначение, выводимое на экран, а -3 означает, что для получения UTC из местного времени нужно вычесть 3 часа. В таблице ниже приведены строки для распространённых регионов.
| Регион | Строка TZ (configTzTime) | Эквивалент configTime |
|---|---|---|
| Турция (UTC+3 круглый год) | <+03>-3 | configTime(10800, 0, ...) |
| Германия, Франция, Италия, Испания | CET-1CEST,M3.5.0,M10.5.0/3 | Не подходит, используй configTzTime |
| Великобритания, Португалия | GMT0BST,M3.5.0/1,M10.5.0 | Не подходит, используй configTzTime |
| Восток США (Нью-Йорк) | EST5EDT,M3.2.0,M11.1.0 | Не подходит, используй configTzTime |
| Азербайджан, Грузия (UTC+4) | <+04>-4 | configTime(14400, 0, ...) |
| UTC (для временных меток) | UTC0 | configTime(0, 0, ...) |
Что понадобится и как подключить
Чтобы увидеть время в мониторе порта, достаточно ESP32 и сети Wi-Fi. Далее в руководстве мы используем OLED-дисплей SSD1306 диагональю 0,96 дюйма для отображения времени, модуль RTC DS3231 для его сохранения при отключениях питания и модуль реле для примера задачи по расписанию. OLED и DS3231 работают на одной шине I2C, поэтому оба подключаются к одним и тем же двум пинам. Распиновку ESP32 можно посмотреть в утилите распиновки.
| Пин компонента | ESP32 | Raspberry Pi Pico W | Примечание |
|---|---|---|---|
| OLED / DS3231 VCC | 3V3 | 3V3 (OUT) | Оба работают от 3,3 V |
| OLED / DS3231 GND | GND | GND | Общая земля |
| OLED / DS3231 SDA | GPIO 21 | GP4 | Линия данных I2C |
| OLED / DS3231 SCL | GPIO 22 | GP5 | Линия тактового сигнала I2C |
| IN модуля реле | GPIO 26 | GP15 | Пример задачи по расписанию |
| VCC модуля реле | 5V (VIN) | VBUS | Большинству катушек реле нужно 5 V |
Пошагово: твой первый скетч NTP для ESP32
- Убедись, что в Arduino IDE установлен пакет плат ESP32, и выбери ESP32 Dev Module в меню «Инструменты» (Tools). Если не знаешь, как это сделать, загляни в статью «Что такое ESP32».
- Дополнительная библиотека не нужна: WiFi.h и time.h входят в ядро ESP32.
- Замени значения WIFI_SSID и WIFI_PASS данными своей сети, а TZ_INFO — строкой своего часового пояса из таблицы выше.
- Загрузи скетч и открой «Монитор порта» (Serial Monitor) на скорости 115200 бод.
- Сначала должны появиться точки подключения к Wi-Fi, а через несколько секунд — дата и время, обновляемые каждую секунду.
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi"; // Wi-Fi adı / Wi-Fi name
5const char* WIFI_PASS = "Sifre123"; // Wi-Fi şifresi / Wi-Fi password
6const char* TZ_INFO = "<+03>-3"; // Türkiye: UTC+3, yaz saati yok / Turkey: UTC+3, no DST
7
8void setup() {
9 Serial.begin(115200);
10 WiFi.begin(WIFI_SSID, WIFI_PASS);
11 Serial.print("Wi-Fi");
12 while (WiFi.status() != WL_CONNECTED) { // bağlanana kadar bekle / wait until connected
13 delay(500);
14 Serial.print(".");
15 }
16 Serial.println(" OK");
17
18 // NTP sunucuları + saat dilimi / NTP servers + time zone
19 configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
20
21 struct tm t;
22 while (!getLocalTime(&t)) { // ilk eşitlemeyi bekle (5 sn deneme) / wait for the first sync (5 s per try)
23 Serial.println("NTP bekleniyor / waiting for NTP");
24 }
25 Serial.println("Saat alindi / time received");
26}
27
28void loop() {
29 struct tm t;
30 if (getLocalTime(&t)) {
31 Serial.println(&t, "%d.%m.%Y %H:%M:%S"); // ör. 16.11.2026 14:05:09 / e.g. 16.11.2026 14:05:09
32 }
33 delay(1000);
34}
35| Строка | Что она делает? |
|---|---|
| configTzTime(TZ_INFO, ...) | Запускает клиент SNTP, задаёт серверы и часовой пояс; возвращается сразу, время приходит в фоновом режиме |
| getLocalTime(&t) | Заполняет struct tm местным временем; если время ещё не получено, ждёт до 5 секунд и возвращает false |
| Serial.println(&t, "...") | Класс Print в ESP32 умеет напрямую выводить struct tm по формату strftime |
| time(nullptr) | Возвращает число секунд с 1970 года (время Unix, UTC) |
Здесь есть важная деталь: getLocalTime() определяет, «достоверно» ли время, по году. При запуске ESP32 системное время — 1 января 1970 года, и функция возвращает false, пока год не станет больше 2016. Поэтому код, записывающий данные или запускающий задачи до получения NTP, не создаст случайно записи с датой 1970 года, если ты проверяешь возвращаемое значение. Второй параметр — таймаут в миллисекундах; чтобы не блокировать loop(), напиши getLocalTime(&t, 0), и функция сделает только одну попытку.
struct tm: составляющие времени
getLocalTime() записывает время в struct tm, разделяя его на составляющие. Эти поля нужны для сравнения часов, дней или месяцев при создании задач по расписанию. Два поля часто сбивают новичков с толку: месяцы отсчитываются с 0, а годы — с 1900.
| Поле | Диапазон | Пример (понедельник, 16 ноября 2026 года, 14:05:09) |
|---|---|---|
| tm_sec | 0–59 (60 при секунде координации) | 9 |
| tm_min | 0–59 | 5 |
| tm_hour | 0–23 | 14 |
| tm_mday | 1–31 | 16 |
| tm_mon | 0–11 (0 = январь) | 10 |
| tm_year | Годы с 1900 года | 126 (1900 + 126 = 2026) |
| tm_wday | 0–6 (0 = воскресенье) | 1 |
| tm_yday | 0–365 (день года, начиная с 0) | 319 |
Форматирование даты и времени с помощью strftime
strftime() записывает время из struct tm в символьный массив по заданному шаблону. Коды со знаком процента в шаблоне заменяются соответствующими значениями, а остальные символы остаются как есть. Размер массива должен равняться длине самого длинного результата плюс один символ для завершающего нуля: «14:05:09» — это 8 символов, поэтому массив должен занимать не менее 9 байт. Наиболее распространённые коды:
| Код | Значение | Пример вывода |
|---|---|---|
| %H / %M / %S | Часы (00–23) / минуты / секунды | 14 / 05 / 09 |
| %I и %p | 12-часовой формат и AM/PM | 02 PM |
| %d / %m / %Y | День / месяц (01–12) / год из четырёх цифр | 16 / 11 / 2026 |
| %y | Год из двух цифр | 26 |
| %A / %a | Полное / краткое название дня недели (на английском) | Monday / Mon |
| %B / %b | Полное / краткое название месяца (на английском) | November / Nov |
| %j | День года (001–366) | 320 |
| %F / %T | Сокращения для %Y-%m-%d / %H:%M:%S | 2026-11-16 / 14:05:09 |
| %Z | Сокращённое обозначение часового пояса | +03 |
Библиотека C на ESP32 знает названия дней недели и месяцев только на английском; %A всегда выводит «Monday». Для дат на другом языке храни названия дней и месяцев в собственных массивах и используй tm_wday и tm_mon как индексы массивов. В примере ниже это сделано с турецкими названиями; просто замени их названиями на своём языке. Монитор порта поддерживает UTF-8, поэтому буквы с диакритическими знаками отображаются в нём правильно. Скетч формирует и удобочитаемую дату, и формат ISO (2026-11-16 14:05:09), пригодный для сортировки записей.
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi";
5const char* WIFI_PASS = "Sifre123";
6const char* TZ_INFO = "<+03>-3";
7
8// tm_wday: 0 = Pazar ... 6 = Cumartesi / 0 = Sunday ... 6 = Saturday
9const char* GUNLER[] = {"Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"};
10const char* AYLAR[] = {"Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran",
11 "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"};
12
13void setup() {
14 Serial.begin(115200);
15 WiFi.begin(WIFI_SSID, WIFI_PASS);
16 while (WiFi.status() != WL_CONNECTED) delay(500);
17 configTzTime(TZ_INFO, "pool.ntp.org");
18}
19
20void loop() {
21 struct tm t;
22 if (!getLocalTime(&t)) return; // saat henüz yok / time not set yet
23
24 char saat[9]; // "HH:MM:SS" + '\0'
25 char iso[20]; // "YYYY-MM-DD HH:MM:SS" + '\0'
26 strftime(saat, sizeof(saat), "%H:%M:%S", &t);
27 strftime(iso, sizeof(iso), "%Y-%m-%d %H:%M:%S", &t);
28
29 // Türkçe tarih: 16 Kasım 2026 Pazartesi / Turkish date
30 Serial.printf("%d %s %d %s %s\n", t.tm_mday, AYLAR[t.tm_mon], t.tm_year + 1900, GUNLER[t.tm_wday], saat);
31 Serial.printf("Kayit / log: %s epoch: %ld\n", iso, (long)time(nullptr));
32 delay(1000);
33}
34Вывод времени и даты на OLED-дисплей
Чтобы превратить вывод в монитор порта в настоящие настольные часы, отлично подходит OLED-дисплей SSD1306 диагональю 0,96 дюйма: он недорогой, подключается двумя проводами и чётко отображает крупные цифры. Скетч ниже показывает день недели и дату сверху, время крупным шрифтом посередине и состояние Wi-Fi снизу. Экран перерисовывается только при смене секунды, а не на каждом проходе цикла, поэтому шина I2C не занята впустую. Установи библиотеки Adafruit SSD1306 и Adafruit GFX через «Менеджер библиотек» (Library Manager); сам дисплей разобран в статье «OLED-дисплей (SSD1306)».
1#include <WiFi.h>
2#include <Wire.h>
3#include <Adafruit_GFX.h>
4#include <Adafruit_SSD1306.h>
5#include <time.h>
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9const char* TZ_INFO = "<+03>-3";
10// OLED yazı tipinde Türkçe harf yok / the OLED font has no Turkish letters
11const char* GUNLER[] = {"Pazar", "Pazartesi", "Sali", "Carsamba", "Persembe", "Cuma", "Cumartesi"};
12
13Adafruit_SSD1306 display(128, 64, &Wire, -1);
14int sonSaniye = -1; // son çizilen saniye / last drawn second
15
16void yaz(const char* mesaj) { // ekrana tek satır / one line on screen
17 display.clearDisplay();
18 display.setTextSize(1);
19 display.setCursor(0, 0);
20 display.print(mesaj);
21 display.display();
22}
23
24void setup() {
25 Wire.begin(21, 22); // SDA = 21, SCL = 22
26 display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
27 display.setTextColor(SSD1306_WHITE);
28 yaz("Wi-Fi baglaniyor...");
29
30 WiFi.begin(WIFI_SSID, WIFI_PASS);
31 while (WiFi.status() != WL_CONNECTED) delay(300);
32 configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
33 yaz("NTP bekleniyor...");
34}
35
36void loop() {
37 struct tm t;
38 if (!getLocalTime(&t, 0)) { // 0 = beklemeden dene / try without waiting
39 delay(200);
40 return;
41 }
42 if (t.tm_sec == sonSaniye) { // saniye değişmediyse çizme / redraw only on a new second
43 delay(20);
44 return;
45 }
46 sonSaniye = t.tm_sec;
47
48 char saat[9], tarih[11];
49 strftime(saat, sizeof(saat), "%H:%M:%S", &t);
50 strftime(tarih, sizeof(tarih), "%d.%m.%Y", &t);
51
52 display.clearDisplay();
53 display.setTextSize(1);
54 display.setCursor(0, 0);
55 display.print(GUNLER[t.tm_wday]); // gün adı / day name
56 display.setCursor(68, 0);
57 display.print(tarih); // 10 karakter = 60 piksel / 10 chars = 60 px
58 display.setTextSize(2);
59 display.setCursor(16, 26);
60 display.print(saat); // 8 x 12 = 96 piksel genişlik / 96 px wide
61 display.setTextSize(1);
62 display.setCursor(0, 56);
63 display.print(WiFi.status() == WL_CONNECTED ? "Wi-Fi: var" : "Wi-Fi: yok");
64 display.display();
65}
66Обрати внимание: названия дней написаны без букв с диакритическими знаками. В стандартном шрифте Adafruit GFX нет символов вроде ş, ç или é, и вместо них появляются бессмысленные знаки. При размере текста 2 каждый символ имеет ширину 12 пикселей; строка «14:05:09» из 8 символов занимает 96 пикселей, поэтому начало в пикселе 16 точно центрирует её на экране шириной 128 пикселей. Если предпочитаешь символьный LCD 16x2, просто выведи тот же результат strftime через lcd.print(); подключение объясняется в статье «LCD-дисплей с I2C».
Что происходит со временем при потере Wi-Fi? Резервный RTC DS3231
Самый частый вопрос: если пропадёт Wi-Fi, пропадёт ли и время? Нет. Получив время по NTP, ESP32 продолжает отсчёт собственным таймером; потеря Wi-Fi не останавливает часы. Этот таймер работает от кварцевого резонатора 40 MHz, поэтому отклонение составляет примерно одну-две секунды в день, а следующая синхронизация исправляет его после восстановления Wi-Fi. Настоящая проблема — отключение питания или перезапуск кнопкой EN: системное время тогда возвращается к 1970 году, и без Wi-Fi ESP32 неоткуда узнать время.
| Ситуация | Сохраняется ли время? | Что делать |
|---|---|---|
| Wi-Fi ненадолго пропадает | Да, ESP32 продолжает отсчёт | Ничего; NTP скорректирует время после восстановления Wi-Fi |
| Глубокий сон | Да, таймер RTC продолжает отсчёт | При длительном сне периодически синхронизируй время по NTP после пробуждения |
| Отключение питания или кнопка EN | Нет, часы возвращаются к 1970 году | Добавь модуль RTC с резервным питанием от батарейки, например DS3231 |
| Интернета вообще нет (поле, склад) | Время NTP не приходит | Получай время только с модуля RTC |
Во время глубокого сна время отсчитывает не основной кварцевый резонатор, а менее точный RC-генератор внутри микросхемы. На него влияет температура, поэтому при длительном сне часы могут отклониться на несколько секунд или больше; в проектах с батарейным питанием, спящих часами, полезно запускать синхронизацию NTP через каждые несколько пробуждений. Подробности — в статье «Глубокий сон ESP32».

Лучший способ сделать часы, переживающие отключения питания, — объединить оба подхода: при запуске сразу получить время с модуля DS3231, скорректировать его по NTP после подключения Wi-Fi и обновлять DS3231 при каждой синхронизации NTP. В скетче ниже DS3231 хранит UTC, а не местное время; так правило часового пояса находится в одном месте (строке TZ), и переходы на летнее время не сбивают RTC. Для отслеживания синхронизации NTP через sntp_set_time_sync_notification_cb() регистрируется функция обратного вызова. Она выполняется внутри сетевой задачи, поэтому операций I2C в ней нет; она лишь устанавливает флаг, а записью в DS3231 занимается loop(). В скетче используется библиотека RTClib.
1#include <WiFi.h>
2#include <Wire.h>
3#include <RTClib.h>
4#include <sys/time.h>
5#include "esp_sntp.h"
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9const char* TZ_INFO = "<+03>-3";
10
11RTC_DS3231 rtc;
12volatile bool ntpGeldi = false; // NTP eşitlemesi oldu mu? / did an NTP sync happen?
13
14void ntpCallback(struct timeval* tv) { // her NTP eşitlemesinde çağrılır / called on every NTP sync
15 ntpGeldi = true; // I2C işini loop() yapsın / let loop() do the I2C work
16}
17
18void setup() {
19 Serial.begin(115200);
20 Wire.begin(21, 22);
21 setenv("TZ", TZ_INFO, 1); // NTP gelmese de saat dilimi geçerli / TZ valid even without NTP
22 tzset();
23
24 // 1) Sistem saatini önce DS3231'den kur (DS3231'de UTC saklanır) / set system time from the DS3231 (stores UTC)
25 if (rtc.begin() && !rtc.lostPower()) {
26 struct timeval tv = { (time_t)rtc.now().unixtime(), 0 };
27 settimeofday(&tv, nullptr);
28 Serial.println("Saat DS3231'den / time from DS3231");
29 }
30
31 // 2) Wi-Fi gelince NTP saati düzeltir; bağlantı sonra gelse de SNTP yeniden dener
32 // NTP corrects the clock once Wi-Fi is up; SNTP keeps retrying if the link comes later
33 sntp_set_time_sync_notification_cb(ntpCallback);
34 WiFi.begin(WIFI_SSID, WIFI_PASS);
35 configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
36}
37
38void loop() {
39 if (ntpGeldi) {
40 ntpGeldi = false;
41 rtc.adjust(DateTime((uint32_t)time(nullptr))); // DS3231'e UTC yaz / write UTC to the DS3231
42 Serial.println("DS3231 NTP ile guncellendi / DS3231 updated from NTP");
43 }
44
45 struct tm t;
46 if (getLocalTime(&t, 0)) {
47 Serial.println(&t, "%d.%m.%Y %H:%M:%S");
48 } else {
49 Serial.println("Saat yok: NTP ve DS3231 bekleniyor / no time yet");
50 }
51 delay(1000);
52}
53Ежедневный запуск задачи в заданное время
Самое распространённое применение времени NTP — задача по расписанию: включить насос полива на 10 минут в 07:30, включить садовое освещение в 19:00, отправлять показания в начале каждого часа. Способ прост: читай время в loop() и запускай задачу, когда часы и минуты совпадут с заданными. Главная тонкость — не допустить сотен повторных запусков в течение одной минуты. Для этого день выполнения задачи (tm_yday) сохраняется в переменной, и задача не запускается дважды в один день. Скетч ниже каждое буднее утро в 07:30 включает реле на GPIO 26 на 10 минут; поскольку длительность измеряется через millis(), код не блокируется.
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi";
5const char* WIFI_PASS = "Sifre123";
6const char* TZ_INFO = "<+03>-3";
7
8const int ROLE_PIN = 26; // röle modülü IN / relay module IN
9const int GOREV_SAAT = 7; // her gün 07:30 / every day at 07:30
10const int GOREV_DAKIKA = 30;
11const unsigned long SURE_MS = 10UL * 60 * 1000; // 10 dakika çalış / run for 10 minutes
12
13int sonGun = -1; // görevin çalıştığı son gün (tm_yday) / last day the task ran
14bool roleAcik = false;
15unsigned long acilisMs = 0;
16
17void setup() {
18 Serial.begin(115200);
19 pinMode(ROLE_PIN, OUTPUT);
20 digitalWrite(ROLE_PIN, LOW);
21 WiFi.begin(WIFI_SSID, WIFI_PASS);
22 while (WiFi.status() != WL_CONNECTED) delay(500);
23 configTzTime(TZ_INFO, "pool.ntp.org");
24}
25
26void loop() {
27 struct tm t;
28 if (getLocalTime(&t, 0)) { // saat geçerliyse / only when the time is valid
29 bool haftaici = (t.tm_wday >= 1 && t.tm_wday <= 5); // Pzt-Cuma / Mon-Fri
30 if (haftaici && t.tm_hour == GOREV_SAAT && t.tm_min == GOREV_DAKIKA && t.tm_yday != sonGun) {
31 sonGun = t.tm_yday; // bugün bir kez / once per day
32 digitalWrite(ROLE_PIN, HIGH);
33 roleAcik = true;
34 acilisMs = millis();
35 Serial.println(&t, "%H:%M gorev basladi / task started");
36 }
37 }
38 if (roleAcik && millis() - acilisMs >= SURE_MS) { // süre dolunca kapat / switch off after the period
39 digitalWrite(ROLE_PIN, LOW);
40 roleAcik = false;
41 Serial.println("Gorev bitti / task finished");
42 }
43 delay(200);
44}
45- Проверка секунд: условие вроде t.tm_sec == 0 пропустит задачу, если цикл пропустит эту секунду (во время переподключения Wi-Fi или долгой задержки). Надёжнее проверять минуту и день.
- Проверка достоверности времени: не запускай задачи, пока getLocalTime() возвращает false, иначе сразу после запуска решения будут ошибочно приниматься на основе 1970 года.
- Пропущенные задачи: если питание пропадёт в 07:29 и вернётся в 07:35, в этот день задача не выполнится. Если это важно, задай условие «уже позже 07:30, а сегодня задача ещё не выполнялась» и сохраняй sonGun во флеш-память через Preferences.
- Несколько задач: храни часы, минуты и длительность в массиве, а для каждой задачи используй отдельную переменную дня последнего запуска.
- Нагрузки с большим током: управляй насосом или лампой через модуль реле или MOSFET, никогда не подключай их напрямую к пину.
Изменение интервала синхронизации и отслеживание NTP
Стандартный интервал синхронизации в 3 часа подходит большинству проектов. Чтобы синхронизироваться чаще (например, на улице при больших перепадах температуры) или сократить трафик, передай новый интервал в миллисекундах в sntp_set_sync_interval(). Вызови её до configTzTime(), поскольку новый интервал вступает в силу при следующем запуске или по истечении текущего интервала. По правилам протокола интервал не может быть меньше 15 секунд. sntp_get_sync_status() один раз возвращает SNTP_SYNC_STATUS_COMPLETED после завершения синхронизации, а затем сбрасывает состояние, позволяя отследить каждую новую синхронизацию.
1#include <WiFi.h>
2#include <time.h>
3#include "esp_sntp.h"
4
5const char* TZ_INFO = "<+03>-3";
6
7void setup() {
8 Serial.begin(115200);
9 WiFi.begin("AgAdi", "Sifre123");
10 while (WiFi.status() != WL_CONNECTED) delay(500);
11
12 sntp_set_sync_interval(6UL * 60 * 60 * 1000); // 6 saatte bir eşitle (en az 15 sn) / sync every 6 hours (min 15 s)
13 configTzTime(TZ_INFO, "pool.ntp.org"); // ayar sntp_init() öncesi verilir / set before sntp_init()
14}
15
16void loop() {
17 if (sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
18 Serial.println("Yeni NTP esitlemesi / new NTP sync");
19 }
20 delay(1000);
21}
22Какой сервер NTP выбрать?
Самый распространённый выбор — pool.ntp.org: пул из тысяч серверов, которые поддерживают добровольцы по всему миру. При каждом запросе он выдаёт адрес ближайшего к тебе сервера. Поддомены стран (uk.pool.ntp.org, de.pool.ntp.org) помогают выбрать сервер ещё ближе. configTzTime() принимает до трёх имён серверов; если первый не отвечает, она переходит к следующему.
| Сервер | Особенность | Примечание |
|---|---|---|
| pool.ntp.org (адрес NTP-сервера) | Пул добровольцев, ближайший сервер | Основной выбор для обычного использования |
| uk.pool.ntp.org / europe.pool.ntp.org (адреса NTP-серверов по странам или континентам) | Пул страны или континента | Немного меньшая задержка |
| time.cloudflare.com (NTP-сервер Cloudflare) | Инфраструктура крупной компании | Подходит как резервный сервер |
| time.google.com (NTP-сервер Google) | Распределяет секунды координации во времени (leap smear) | Не смешивай с другими серверами |
| IP-адрес твоего роутера | Некоторые роутеры предоставляют локальный NTP | Полезно в локальной сети без интернета |
NTP на Raspberry Pi Pico W: ntptime
На Raspberry Pi Pico W (и Pico 2 W) ту же задачу выполняет модуль ntptime из MicroPython. ntptime.settime() запрашивает сервер NTP и устанавливает внутренние часы RTC платы Pico по UTC. В MicroPython нет строки часового пояса, как у ESP32; для местного времени к UTC прибавляется смещение в секундах (в примере 3 × 3600 для UTC+3). ntptime получает время только один раз и не синхронизируется повторно автоматически; в долго работающих проектах вызов settime() один-два раза в день устраняет отклонение. RTC платы Pico W тоже сбрасывается при отключении питания, поэтому резервный DS3231 пригодится и здесь. Подключение Pico W к Wi-Fi подробно разобрано в статье «Wi-Fi и веб-сервер на Pico W».
1import network
2import ntptime
3import time
4
5SSID = "AgAdi"
6PASS = "Sifre123"
7UTC_OFFSET = 3 * 3600 # Türkiye UTC+3 / Turkey UTC+3
8# localtime(): 0 = Pazartesi / 0 = Monday
9GUNLER = ("Pazartesi", "Sali", "Carsamba", "Persembe", "Cuma", "Cumartesi", "Pazar")
10
11wlan = network.WLAN(network.STA_IF)
12wlan.active(True)
13wlan.connect(SSID, PASS)
14while not wlan.isconnected(): # bağlanana kadar bekle / wait until connected
15 time.sleep(0.5)
16
17ntptime.host = "pool.ntp.org"
18for deneme in range(5): # NTP bazen zaman aşımına uğrar / NTP may time out
19 try:
20 ntptime.settime() # dahili RTC'yi UTC'ye ayarlar / sets the internal RTC to UTC
21 break
22 except OSError:
23 time.sleep(2)
24
25def yerel_saat(): # UTC + 3 saat / UTC + 3 hours
26 return time.localtime(time.time() + UTC_OFFSET)
27
28son_gun = -1
29while True:
30 yil, ay, gun, saat, dk, sn, hgun, ygun = yerel_saat()
31 print("{:02d}.{:02d}.{} {} {:02d}:{:02d}:{:02d}".format(gun, ay, yil, GUNLER[hgun], saat, dk, sn))
32 if saat == 7 and dk == 30 and ygun != son_gun: # her gün 07:30'da bir kez / once a day at 07:30
33 son_gun = ygun
34 print("Gorev calisti / task ran")
35 time.sleep(1)
36В MicroPython time.localtime() возвращает кортеж из восьми элементов: год, месяц (1–12), день, часы, минуты, секунды, день недели и день года. От struct tm на ESP32 он отличается в двух отношениях: месяцы начинаются с 1, а отсчёт дней недели идёт как 0 = понедельник. При сетевом таймауте ntptime.settime() вызывает исключение OSError, поэтому в скетче есть цикл из пяти попыток.
Идеи проектов часов NTP на ESP32
- Настольные часы с Wi-Fi: показывай время, дату и комнатную температуру на одном OLED-дисплее и уменьшай яркость ночью.
- Огромные светодиодные часы: выводи время NTP бегущей строкой на светодиодной матрице MAX7219.
- Полив растений по расписанию: каждое утро проверяй влажность почвы и при необходимости включай мини-насос для воды на заданное время.
- Запись данных с временными метками: сохраняй показания датчиков с датами в формате ISO на SD-карту.
- Часы с погодой: добавь рядом со временем погоду, полученную из интернета; продолжение — в статье «Погода на ESP32».
- Утренний отчёт: каждый день в 08:00 отправляй на телефон через Telegram температуру дома и сведения о движении, обнаруженном за ночь.
Частые ошибки и их исправление
| Симптом | Вероятная причина | Решение |
|---|---|---|
| Отображается 1970 год или время не приходит | Wi-Fi не подключён или порт NTP (UDP 123) заблокирован | Сначала проверь WiFi.status(); в школьной или офисной сети попробуй другую сеть или точку доступа на телефоне |
| Время отличается ровно на смещение твоего пояса относительно UTC | Часовой пояс не задан, отображается UTC | configTzTime("<+03>-3", ...) или строка для твоего часового пояса |
| Время отличается на удвоенное смещение | Перепутан знак в строке TZ («UTC+3») | Для UTC+3 используй «<+03>-3» или «UTC-3» |
| Месяц на единицу меньше, год — 126 | tm_mon отсчитывается с 0, tm_year — с 1900 | Используй tm_mon + 1, tm_year + 1900 или strftime |
| Задача срабатывает много раз за одну минуту | Нет защиты от повторного запуска | Сохраняй день выполнения (tm_yday) и проверяй его |
| Цикл зависает на 5 секунд при каждом проходе | Без достоверного времени getLocalTime() по умолчанию ждёт 5000 мс | Используй getLocalTime(&t, 0) внутри loop() |
| Странные символы в названии дня на OLED | В шрифте нет букв с диакритическими знаками | Пиши названия дней обычными символами ASCII или перейди на библиотеку U8g2 |
NTP на ESP32 — самый дешёвый и простой способ добавить точное время в проект: несколько строк кода позволяют получить время с точностью до секунды, автоматически учесть часовой пояс и переход на летнее время, а с DS3231 — пережить отключения питания. В приложении BlueGrays есть пошаговые проекты со схемами и кодом: часы реального времени через NTP на ESP32, цифровые часы с RTC DS1307, отображение погоды на OLED и регистратор температуры с временными метками.
Частые вопросы
Может ли ESP32 отсчитывать время без интернета?
Да, после получения времени по NTP: ESP32 продолжает отсчитывать системное время собственным таймером, и потеря Wi-Fi не останавливает часы. Однако при отключении питания или перезапуске платы время возвращается к 1970 году. Для мест без интернета или часов, переживающих отключения питания, добавь модуль RTC с резервным питанием от батарейки, например DS3231.
Почему время NTP на ESP32 отличается на несколько часов?
Если часовой пояс не задан, ESP32 показывает UTC, поэтому часы ошибаются ровно на величину смещения твоего часового пояса относительно UTC. Используй configTzTime с правильной строкой POSIX, например «CET-1CEST,M3.5.0,M10.5.0/3» для Центральной Европы или «GMT0BST,M3.5.0/1,M10.5.0» для Великобритании. Если ошибка равна удвоенному смещению, в строке TZ перепутан знак.
Чем отличаются configTime и configTzTime?
configTime принимает смещение относительно UTC и смещение летнего времени в секундах, но не знает, когда в твоём регионе начинается и заканчивается летнее время. configTzTime принимает строку часового пояса POSIX с правилами перехода, поэтому ESP32 автоматически переключается между летним и зимним временем. Для любого региона с переводом часов правильный выбор — configTzTime.
Как часто ESP32 синхронизируется по NTP?
В ядре ESP32 для Arduino SNTP по умолчанию повторно запрашивает сервер каждые 3 часа, а в промежутках отсчитывает время собственным таймером. Интервал можно изменить в миллисекундах через sntp_set_sync_interval(), но по правилам протокола он не может быть меньше 15 секунд.
Нужен ли модуль RTC для ESP32?
Если устройство постоянно получает питание и имеет доступ к Wi-Fi, нет: NTP даёт более точное время. Если правильное время нужно после отключения питания ещё до восстановления интернета или устройство будет работать в месте без интернета, добавь модуль RTC с резервным питанием от батарейки, например DS3231.
Как получить время NTP на Raspberry Pi Pico W?
В MicroPython после подключения к Wi-Fi достаточно выполнить import ntptime и ntptime.settime(); это установит внутренние часы RTC платы Pico по UTC. Для местного времени прибавь смещение в секундах, например time.localtime(time.time() + 3600) для UTC+1. ntptime не выполняет повторную синхронизацию автоматически, поэтому вызывай settime() снова один-два раза в день.
Что собрать после прочтения
Все проекты
Точное время из интернета через NTP на ESP32
ESP32 подключается к Wi-Fi и получает точное время с точностью до миллисекунд от серверов NTP (Network Time Protocol). Благодаря функции configTzTime() автоматически применяется часовой пояс Турции (TRT, UTC+3), а в странах с летним временем переходы обрабатываются автоматически по строке POSIX TZ. Время выводится в монитор порта и на необязательный I2C LCD. configTzTime() запускает службу SNTP, которая периодически пересинхронизирует часы.

Цифровые часы на ESP32 и DS1307 RTC (I2C + LCD)
В этом проекте время и дата из модуля часов реального времени (RTC) DS1307 выводятся на I2C LCD-экран 16x2. DS1307 — RTC с резервной батарейкой, который хранит время даже при выключенной ESP32. Оба модуля делят одну шину I2C (SDA/SCL), и ты узнаёшь, как управлять несколькими устройствами всего по двум сигнальным проводам. Проект даёт практические знания о протоколе I2C, работе модуля RTC и динамическом выводе данных на LCD-экран.

Подключение ESP32 к Wi-Fi в режиме STA (с экраном LCD)
В этом проекте ESP32 надёжно и устойчиво к сбоям подключается к домашней сети Wi-Fi в режиме станции (STA). SSID и пароль хранятся в отдельном файле secrets.h, чтобы не утекли в Git; настроено автоматическое переподключение, а уровень сигнала RSSI и IP-адрес выводятся на I2C LCD-экран. Встроенный светодиод служит индикатором состояния. Это менеджер подключения уровня реальных устройств.

ESP32 HTTPClient + API погоды (с OLED-дисплеем SSD1306)
В этом проекте ESP32 через HTTPClient.h делает вызов REST API к сервису погоды (wttr.in). В отличие от прошлой версии с I2C LCD, здесь используется OLED SSD1306 (128×64), который позволяет крупные шрифты, векторные значки и богатую картинку на одном экране. Семь векторных значков погоды (солнечно, переменная облачность, облачно, дождь, снег, гроза, туман) рисуются во время работы (растровые файлы не нужны). Температура выводится крупными цифрами.

ESP32 HTTPClient + API погоды (с экраном LCD)
В этом проекте ESP32 через HTTPClient.h делает вызов REST API к сервису погоды (wttr.in) через интернет. Полученный ответ в JSON разбирается с помощью ArduinoJson v7, а город, температура, влажность, погода и ветер выводятся на I2C LCD на турецком и английском. wttr.in бесплатен и не требует ключа API — достаточно указать любое название города, чтобы получить текущую погоду. Данные автоматически обновляются каждые 30 минут.

Глубокий сон ESP32: экономия батареи (OLED + счётчик в RTC RAM)
Режим глубокого сна (Deep Sleep) у ESP32 снижает потребление до ~10 µA, выключая процессор и большую часть периферии, — это важно для IoT-устройств на батарейках. В этом проекте используются три источника пробуждения: таймер (15 секунд), внешний пин (EXT0 — кнопка GPIO 33) и касание (T0/GPIO 4). Переменные с RTC_DATA_ATTR сохраняются во время глубокого сна — счётчики загрузок, пробуждений по таймеру и по кнопке ведутся непрерывно.

Регистратор температуры на ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
Собери на ESP32 регистратор температуры с отметками времени! Каждую минуту читай температуру и влажность с DHT22, бери реальное время из DS1307 RTC и записывай всё на карту microSD в формате CSV. OLED показывает текущую температуру (крупным шрифтом), влажность в процентах, время и счётчик записей. Файл /temp_log.csv на SD-карте открывается в Excel, Google Sheets или Python pandas — идеально для теплиц, комнат, лабораторий и хранения продуктов.

I2C LCD-дисплей (16x2) на ESP32
В этом проекте символьный LCD-экран 16x2 подключается к ESP32 по протоколу I2C и выводит текст. Благодаря I2C экраном можно управлять всего двумя пинами данных (SDA и SCL), не тратя 6+ пинов, как при классическом параллельном подключении. Код печатает постоянное сообщение в первой строке и счётчик, растущий каждую секунду, во второй. Кроме того, при запуске работает сканер I2C, который выводит адрес дисплея в монитор порта.
Другие статьи
Приложение BlueGrays
Собери эту схему шаг за шагом в телефоне
Схема подключения, код и список деталей на одном экране. 200+ проектов на Arduino, ESP32 и Pico готовы к сборке.
Наведи камеру телефона
Наведи камеру на код — откроется магазин приложений на телефоне.
или открыть магазин

