Цифровые часы на ESP32 с NTP: часовой пояс и переход на летнее время

Перевод статьи ESP32 NTP Digital Clock with Timezone and Daylight Saving Time — Rui Santos & Sara Santos, Random Nerd Tutorials.
В этом руководстве мы соберём на ESP32 цифровые часы с подключением к интернету: их можно настроить под свой часовой пояс, а переход на летнее время они отрабатывают автоматически. ESP32 получает текущее время с NTP-сервера и выводит его на четырёхразрядный семисегментный индикатор TM1637 — как в классических электронных часах. Прошивать плату будем из Arduino IDE.
Отличный повод разобраться и с семисегментным индикатором TM1637, и с получением времени по NTP. Если нужен подробный разбор по каждой из тем — вот отдельные статьи:
- ESP32 NTP Time – Setting Up Timezones and Daylight Saving Time
- ESP32 with TM1637 4-Digit LED 7-Segment Display (Arduino IDE)
Что нужно знать заранее#
Прежде чем двигаться дальше, убедитесь, что у вас установлена Arduino IDE и добавлена поддержка плат ESP32. Если ещё нет — начните с этого руководства:
Обзор проекта#
Задача простая: забрать время с NTP-сервера и вывести его на четырёхразрядный семисегментный индикатор TM1637.

ESP32 показывает время на TM1637.
Индикатор TM1637 (4 разряда, 7 сегментов)#
Индикатор TM1637 — это модуль, объединяющий четыре семисегментных разряда в одном корпусе; управляет ими драйвер TM1637. У той версии, что используем мы, между вторым и третьим разрядом стоит двоеточие. Раскладка идеально подходит для часов: часы с одной стороны двоеточия, минуты — с другой.

Модуль TM1637 с двоеточием посередине.
NTP (Network Time Protocol)#
Время для своего часового пояса, с поправкой на переход на летнее время, мы получим по NTP.
NTP — это Network Time Protocol, сетевой протокол синхронизации часов между компьютерными системами. Иными словами, он служит для того, чтобы часы машин в сети шли одинаково.

ESP32 выступает NTP-клиентом и запрашивает время у NTP-сервера.
Существуют публичные NTP-серверы вроде pool.ntp.org, к которым любой может обратиться в роли клиента. В нашем случае NTP-клиент — это ESP32, а NTP-сервер — pool.ntp.org.
Подробнее про получение времени на ESP32:
- ESP32 NTP Client-Server: Get Date and Time (Arduino IDE)
- ESP32 NTP Time – Setting Up Timezones and Daylight Saving Time
Порядок работы#
Коротко по шагам, которые нужно проделать в проекте:
- Инициализировать индикатор.
- Инициализировать Wi-Fi и подключить ESP32 к локальной сети, чтобы плата могла получать данные из интернета.
- Инициализировать время для заданного часового пояса (с учётом перехода на летнее время).
- Выводить время на индикатор (мигая двоеточием раз в секунду).
- Раз в час обращаться к NTP-серверу и пересинхронизировать время — чтобы внутренние часы ESP32 не уходили.
Что понадобится#
Для этого проекта нужны следующие детали:

Весь комплект: плата, индикатор и несколько проводов.
- плата ESP32 — любая модель на ваш выбор;
- четырёхразрядный семисегментный индикатор TM1637;
- провода-джамперы.
Подключение TM1637 к ESP32#

У модуля всего четыре вывода: CLK, DIO, VCC и GND.
Схема подключения предельно простая: индикатору нужны всего два цифровых вывода — CLK и DI/O.
| TM1637 | ESP32 |
|---|---|
| CLK | любой цифровой пин (например, GPIO 19)* |
| DIO | любой цифровой пин (например, GPIO 18)* |
| VCC | VIN |
| GND | GND |
* можно взять любые другие подходящие GPIO. Сверяйтесь с распиновкой:
- ESP32 Pinout Reference: Which GPIO pins should you use?
- ESP32-S3 DevKitC Pinout Reference Guide: GPIOs Explained
Мы посадим CLK на GPIO 19, а DIO — на GPIO 18, но комбинация пинов может быть и другой.

Схема соединений.
Установка библиотеки TM1637#
Библиотек для работы с TM1637 на ESP32 существует несколько. Мы возьмём TM1637.h от avishorp — её не обновляли уже несколько лет, но она по-прежнему прекрасно работает и очень проста в использовании.
Устанавливается через менеджер библиотек Arduino IDE: ищем TM1637 и ставим библиотеку от avishorp.

Менеджер библиотек Arduino IDE.
Код: цифровые часы на ESP32 и TM1637#
Залейте на плату следующий скетч. Перед этим нужно:
- вписать SSID и пароль своей сети;
- вписать строку своего часового пояса — список есть в репозитории nayarsystems.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp32-tm1637-4-digit-7-segment-display-arduino/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <WiFi.h>
#include "time.h"
#include <TM1637Display.h>
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Resync clock
TimerHandle_t syncTimer = NULL;
const unsigned long SYNC_INTERVAL = 3600000UL; // 1 hour (3600000 ms)
// TM1637 pins
#define CLK 19
#define DIO 18
TM1637Display display(CLK, DIO);
// Timezone (Lisbon / Portugal)
// Change if needed - See list of timezones strings: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
const char* TZ_INFO = "WET0WEST,M3.5.0/1,M10.5.0";
// Init Wifi
void initWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
}
// Initialize Time with the specified timezone
void initTime() {
configTime(0, 0, "pool.ntp.org");
setenv("TZ", TZ_INFO, 1);
tzset();
}
// display the time on the screen
void displayTime() {
// blinking colon
static bool colon = false;
colon = !colon;
// get the current time
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return;
}
int hours = timeinfo.tm_hour; // get hours (0–23)
int minutes = timeinfo.tm_min; // get minutes (0–59)
// move hours to the left two digits
int hourPart = hours * 100;
// combine hours and minutes into HHMM
int value = hourPart + minutes;
// control the colon (for blinking colon)
uint8_t colonMask;
if (colon == true) {
colonMask = 0b01000000; // turn colon ON
} else {
colonMask = 0; // turn colon OFF
}
display.showNumberDecEx(
value,
colonMask, // blink colon
true // leading zeros
);
}
void syncTimeCallback(TimerHandle_t xTimer) {
Serial.println("Resyncing time with NTP...");
initTime();
}
void setup() {
Serial.begin(115200);
display.setBrightness(7);
display.clear();
initWiFi();
initTime();
//Create timer to sync time periodically
syncTimer = xTimerCreate(
"syncTimer", // Timer name
pdMS_TO_TICKS(SYNC_INTERVAL), // Period in ticks
pdTRUE, // Auto-reload (periodic timer)
NULL, // Timer ID
syncTimeCallback // Callback function
);
if (syncTimer == NULL) {
Serial.println("Failed to create timer!");
while (1);
}
xTimerStart(syncTimer, 0); // Start timer immediately
}
void loop() {
displayTime();
delay(1000);
}
Как работает код#
Пробежимся по коду и посмотрим, что в нём происходит.
Подключаем библиотеки#
Начинаем с подключения нужных библиотек.
#include <WiFi.h>
#include "time.h"
#include <TM1637Display.h>
Данные сети#
Свои реквизиты Wi-Fi вписываем в эти строки.
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
Хендл таймера FreeRTOS#
Создаём хендл для таймера FreeRTOS, который заведём чуть позже. Этот таймер будет раз в час (SYNC_INTERVAL) вызывать колбэк, который заново запрашивает время у NTP-сервера.
// Resync clock
TimerHandle_t syncTimer = NULL;
const unsigned long SYNC_INTERVAL = 3600000UL; // 1 hour (3600000 ms)
Подробнее про таймеры FreeRTOS — в отдельном руководстве: ESP32 with FreeRTOS: Software Timers/Timer Interrupts (Arduino IDE).
Инициализация индикатора#
Задаём пины TM1637 и создаём экземпляр индикатора с именем display.
// TM1637 pins
#define CLK 19
#define DIO 18
TM1637Display display(CLK, DIO);
Часовой пояс#
Строку своего часового пояса вписываем сюда. Список строк есть в репозитории. В моём случае это Лиссабон/Португалия, поэтому строка выглядит так:
const char* TZ_INFO = "WET0WEST,M3.5.0/1,M10.5.0";
Инициализация Wi-Fi#
Эта функция поднимает Wi-Fi и подключает ESP32 к локальной сети. Её мы вызовем позже, в setup().
// Init Wifi
void initWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
}
Про Wi-Fi-функции ESP32 подробнее — здесь:
Инициализация времени#
Функция initTime() инициализирует время по NTP и приводит его к вашему часовому поясу — обратите внимание на вызовы setenv() и tzset().
// Initialize Time with the specified timezone
void initTime() {
configTime(0, 0, "pool.ntp.org");
setenv("TZ", TZ_INFO, 1);
tzset();
}
Подробнее про получение даты и времени с поправкой на часовой пояс:
Функция displayTime() выводит время на экран.
void displayTime() {
Логическая переменная colon определяет, показывается двоеточие или нет — так мы получаем мигание.
// blinking colon
static bool colon = false;
colon = !colon;
Получаем текущее время и сохраняем его в структуру timeinfo.
// get the current time
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
return;
}
Дальше вытаскиваем часы и минуты:
int hours = timeinfo.tm_hour; // get hours (0–23)
int minutes = timeinfo.tm_min; // get minutes (0–59)
Теперь, чтобы вывести цифры на экран, нужно склеить часы и минуты в одно четырёхразрядное число, которое двоеточие разделит посередине.
Делается это умножением часов на 100 и прибавлением минут.
// move hours to the left two digits
int hourPart = hours * 100;
// combine hours and minutes into HHMM
int value = hourPart + minutes;
Например, пусть часы — 12, а минуты — 30. Нам нужно получить 12:30, то есть число 1230 и двоеточие посередине.
12*100+30 даёт как раз нужное 1230. Это число и сохраняется в переменную value.
Наконец, выставляем битовую маску двоеточия — в зависимости от того, пора его показывать или нет.
// control the colon (for blinking colon)
uint8_t colonMask;
if (colon == true) {
colonMask = 0b01000000; // turn colon ON
} else {
colonMask = 0; // turn colon OFF
}
Для вывода времени на экран используем showNumberDecEx(). В аргументах передаём значение, битовую маску двоеточия и признак ведущих нулей.
display.showNumberDecEx(
value,
colonMask, // blink colon
true // leading zeros
);
Все основные функции объявлены — собрать из них скетч теперь легко.
syncTimeCallback()#
Функцию syncTimeCallback() таймер FreeRTOS будет вызывать раз в час. Она просто вызывает написанную ранее initTime(), чтобы заново синхронизировать время с NTP-сервером. Так внутренние часы ESP32 не будут уходить.
void syncTimeCallback(TimerHandle_t xTimer) {
Serial.println("Resyncing time with NTP...");
initTime();
}
setup()#
В setup() инициализируем последовательный монитор, задаём яркость индикатора, поднимаем Wi-Fi и время.
void setup() {
Serial.begin(115200);
display.setBrightness(7);
display.clear();
initWiFi();
initTime();
Тут же, в setup(), создаём таймер FreeRTOS и запускаем его через xTimerStart().
//Create timer to sync time periodically
syncTimer = xTimerCreate(
"syncTimer", // Timer name
pdMS_TO_TICKS(SYNC_INTERVAL), // Period in ticks
pdTRUE, // Auto-reload (periodic timer)
NULL, // Timer ID
syncTimeCallback // Callback function
);
if (syncTimer == NULL) {
Serial.println("Failed to create timer!");
while (1);
}
xTimerStart(syncTimer, 0); // Start timer immediately
Ещё раз: про таймеры FreeRTOS есть отдельное руководство.
loop()#
Наконец, в loop() достаточно вызывать displayTime() — она и выводит время на четырёхразрядный семисегментный модуль.
void loop() {
displayTime();
delay(1000);
}
Демонстрация#
Вписав реквизиты сети и данные своего часового пояса, заливайте код на ESP32.
Индикатор должен начать показывать текущее время для вашего пояса.

Часы в работе.
Двоеточие мигает раз в секунду — как в старых электронных часах. Дополнительно раз в час ESP32 переподключается к NTP-серверу, запрашивает время и синхронизируется.
Итого#
В этом руководстве вы собрали интернет-часы на ESP32 и семисегментном индикаторе TM1637. Проект легко переделать под другой экран — OLED, LCD или даже TFT.
Похожие проекты:
- Arduino Digital Clock with DS3231 RTC and OLED Display (легко адаптируется под ESP32);
- ESP32 TFT with LVGL: Digital Clock with Time and Date.