Перевод статьи ESP32 NTP Digital Clock with Timezone and Daylight Saving TimeRui Santos & Sara Santos, Random Nerd Tutorials.

В этом руководстве мы соберём на ESP32 цифровые часы с подключением к интернету: их можно настроить под свой часовой пояс, а переход на летнее время они отрабатывают автоматически. ESP32 получает текущее время с NTP-сервера и выводит его на четырёхразрядный семисегментный индикатор TM1637 — как в классических электронных часах. Прошивать плату будем из Arduino IDE.

Отличный повод разобраться и с семисегментным индикатором TM1637, и с получением времени по NTP. Если нужен подробный разбор по каждой из тем — вот отдельные статьи:

Что нужно знать заранее#

Прежде чем двигаться дальше, убедитесь, что у вас установлена Arduino IDE и добавлена поддержка плат ESP32. Если ещё нет — начните с этого руководства:

Обзор проекта#

Задача простая: забрать время с NTP-сервера и вывести его на четырёхразрядный семисегментный индикатор TM1637.

ESP32 выводит время на индикатор TM1637

ESP32 показывает время на TM1637.

Индикатор TM1637 (4 разряда, 7 сегментов)#

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

Четырёхразрядный семисегментный индикатор TM1637

Модуль TM1637 с двоеточием посередине.

NTP (Network Time Protocol)#

Время для своего часового пояса, с поправкой на переход на летнее время, мы получим по NTP.

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

Схема работы NTP: клиент и сервер

ESP32 выступает NTP-клиентом и запрашивает время у NTP-сервера.

Существуют публичные NTP-серверы вроде pool.ntp.org, к которым любой может обратиться в роли клиента. В нашем случае NTP-клиент — это ESP32, а NTP-сервер — pool.ntp.org.

Подробнее про получение времени на ESP32:

Порядок работы#

Коротко по шагам, которые нужно проделать в проекте:

  • Инициализировать индикатор.
  • Инициализировать Wi-Fi и подключить ESP32 к локальной сети, чтобы плата могла получать данные из интернета.
  • Инициализировать время для заданного часового пояса (с учётом перехода на летнее время).
  • Выводить время на индикатор (мигая двоеточием раз в секунду).
  • Раз в час обращаться к NTP-серверу и пересинхронизировать время — чтобы внутренние часы ESP32 не уходили.

Что понадобится#

Для этого проекта нужны следующие детали:

Детали для часов: ESP32, TM1637, провода

Весь комплект: плата, индикатор и несколько проводов.

Подключение TM1637 к ESP32#

Выводы модуля TM1637: CLK, DIO, VCC, GND

У модуля всего четыре вывода: CLK, DIO, VCC и GND.

Схема подключения предельно простая: индикатору нужны всего два цифровых вывода — CLK и DI/O.

TM1637ESP32
CLKлюбой цифровой пин (например, GPIO 19)*
DIOлюбой цифровой пин (например, GPIO 18)*
VCCVIN
GNDGND

* можно взять любые другие подходящие GPIO. Сверяйтесь с распиновкой:

Мы посадим CLK на GPIO 19, а DIO — на GPIO 18, но комбинация пинов может быть и другой.

Схема подключения TM1637 к ESP32

Схема соединений.

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

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

Устанавливается через менеджер библиотек Arduino IDE: ищем TM1637 и ставим библиотеку от avishorp.

Установка библиотеки TM1637 в Arduino IDE

Менеджер библиотек 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);
}

Исходник скетча на GitHub.

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

Пробежимся по коду и посмотрим, что в нём происходит.

Подключаем библиотеки#

Начинаем с подключения нужных библиотек.

#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 и TM1637

Часы в работе.

Двоеточие мигает раз в секунду — как в старых электронных часах. Дополнительно раз в час ESP32 переподключается к NTP-серверу, запрашивает время и синхронизируется.

Итого#

В этом руководстве вы собрали интернет-часы на ESP32 и семисегментном индикаторе TM1637. Проект легко переделать под другой экран — OLED, LCD или даже TFT.

Похожие проекты: