Модули часов реального времени (Real Time Clock, RTC) находят широкое применение в современной электронике для определения текущего времени и даты. Но недостатком данных модулей является то, что они не всегда обеспечивают требуемую точность определения даты и времени. При этом они могут определять только время на локальном устройстве. В данном случае использование решений, основанных на получении точного времени с серверов NTP, обеспечивает большую точность измерения времени и позволяет производить определение точного времени в любой точке земного шара.
Для реализации этой идеи можно использовать Wi-Fi модуль, который будет подключаться к сети интернет и определять время в любой точке Земли с помощью серверов NTP. В данной статье мы рассмотрим использование платы NodeMCU ESP8266 (будет выполнять роль Wi-Fi модуля) для считывания текущего времени и даты с серверов NTP и их отображения на экране OLED дисплея.
Также на нашем сайте вы можете посмотреть все проекты с использованием систем реального времени.
Необходимые компоненты
- NodeMCU ESP8266 (купить на AliExpress).
- SSD1306 0.96” OLED дисплей с 7 контактами (купить на AliExpress).
- Кабель micro USB.
- Макетная плата.
- Соединительные провода «папа-папа».
Протокол NTP
Протокол сетевого времени (Network Time Protocol, NTP) – это самый старый протокол, который появился в IP сетях для синхронизации времени между компьютерными сетями. Он был разработан ученым David L. Mills в университете Delaware в 1981 году. Данный протокол может быть использован для синхронизации времени различных сетей к всеобщему скоординированному времени (Coordinated Universal Time, UTC) с точностью до нескольких миллисекунд. UTC – это основной стандарт времени в современном мире, который регулирует время и дату. Протокол NTP использует UTC в качестве системного времени и обеспечивает точное синхронизированное время во всей сети Интернет.
Протокол NTP работает на основе иерархической модели клиент-сервер. На самом верхнем уровне этой модели используются системные часы известные как “stratum0”, в качестве которых могут использоваться атомные часы, радиоволны, GPS, GSM, которые получают сигналы времени от спутника. Серверы, которые получают сигналы времени от stratum0, называются “stratum1”, а серверы, которые получают сигналы времени от stratum1, называются “stratum2” и т.д. Это приводит к тому, что точность времени уменьшается при переходе вниз по уровням модели. При этом протокол NTP автоматически выбирает самый лучший из доступных серверов времени для синхронизации, что обеспечивает хорошую отказоустойчивость данного протокола.
В данном проекте мы будем получать точное время от сервера NTP с помощью платы NodeMCU ESP8266 и показывать его на экране OLED дисплея. Также на нашем сайте вы можете прочитать статью о подключении OLED дисплея к NodeMCU ESP8266.
Модуль ESP8266 может получать доступ к серверам NTP для считывания с них точного времени используя доступ к сети интернет. В нашем случае мы будем использовать протокол NTP в режиме клиент-сервер. NodeMCU ESP8266 будет работать в качестве клиентского устройства и соединяться с серверами NTP с помощью протокола UDP (User Datagram Protocol – протокол передачи дейтаграмм пользователя). Клиент передает пакет запроса на сервер NTP, который в ответ передает ему пакет с временной меткой, содержащий информацию о точности, временной зоне, временной метке UNIX и т.д. Затем клиент выделяет из этой совокупности информации необходимую ему дату и время и далее использует эту информацию по своему усмотрению (в нашем случае отображает на экране OLED дисплея).
Схема проекта
Схема интернет часов на NodeMCU ESP8266 и OLED дисплее представлена на следующем рисунке.
OLED дисплей | NodeMCU ESP8266 |
GND | GND |
VDD | 3.3V |
SCK | D5 |
MOSI (SPI) или SDA (I2C) | D7 |
RESET | D3 |
DC | D2 |
CS | D8 |
Более подробно о подключении OLED дисплея к NodeMCU ESP8266 можно прочитать в этой статье.
Объяснение программы для модуля ESP8266
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Сначала в программе нам необходимо скачать и установить библиотеку NTP для модуля ESP8266 – таких библиотек достаточно много, вы можете использовать любую из них. Мы в данном проекте использовали библиотеку NTPClient от разработчика Taranais (скачать ее можно по этой ссылке) – она достаточно проста в использовании и содержит функции для получения времени и даты с серверов NTP. Плату ESP8266 NodeMCU мы будем программировать с помощью Arduino IDE.
Для установки библиотеки NTPClient скачайте ее по вышеприведенной ссылке, затем в Arduino IDE откройте пункт меню to Sketch > Include Library > Add .ZIP Library и укажите путь к папке, куда вы скачали Zip архив библиотеки. После этого перезагрузите Arduino IDE.
Библиотека NTPClient содержит примеры, открыть которые можно в пункте меню Examples > NTPClient > Advanced. Код в скетче данного примера позволяет считывать время с NTP сервера и отображать его в окне монитора последовательной связи. Мы возьмем код этого скетча за основу программы для нашего проекта и будем отображать текущее время и дату на OLED дисплее.
Библиотека ESP8266WiFi содержит Wi-Fi функции, позволяющие модулю ESP8266 подключаться к сети интернет. WiFiUDP.h содержит функции для передачи и приема UDP пакетов. Поскольку OLED дисплей мы подключаем к нашей плате NodeMCU ESP8266 по интерфейсу SPI, то нам необходимо в программе подключить библиотеку “SPI.h”. Библиотеки “Adafruit_GFX.h” и “Adafruit_SSD1306.h” будут использоваться для взаимодействия с OLED дисплеем.
1 2 3 4 5 6 |
#include <NTPClient.h> #include <ESP8266WiFi.h> // provides ESP8266 specific Wi-Fi routines we are calling to connect to network #include <WiFiUdp.h> //handles sending and receiving of UDP packages #include <SPI.h> // SPI for interfacing OLED with NodeMCU #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> |
Также в программе нам необходимо задать ширину и высоту экрана OLED дисплея – это 128 и 64 пикселов соответственно. Также зададим контакты, к которым подключен OLED дисплей по интерфейсу SPI.
1 2 3 4 5 6 7 8 9 10 |
#define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels // Declaration for SSD1306 display connected using software SPI (default case): #define OLED_MOSI D7 #define OLED_CLK D5 #define OLED_DC D2 #define OLED_CS D8 #define OLED_RESET D3 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS); |
В следующем фрагменте кода вам необходимо заменить значения параметров “your_ssid” и “your_password” на идентификатор сети (SSID) и пароль для своей сети Wi-Fi.
1 2 |
const char *ssid = "your_ssid"; const char *password = "your_password"; |
Далее с помощью наших SSID и пароля установим соединение с сетью Wi-Fi с помощью функции WiFi.begin. Подключение к сети Wi-Fi отнимает некоторое время, поэтому подождите.
1 2 3 4 5 |
WiFi.begin(ssid, password); while ( WiFi.status() != WL_CONNECTED ) { delay ( 500 ); Serial.print ( "." ); } |
Для запроса времени и даты инициализируйте клиент времени с адресом NTP сервера. Для лучшей точности результатов выберите адрес NTP сервера, самого ближнего к вашей географической области. В нашем случае мы использовали пул “pool.ntp.org”, который обеспечивает доступ к серверам по всему земному шару. Если вы хотите использовать серверы из Азии, вы можете использовать сервер “asia.pool.ntp.org”. Клиент времени (timeClient) также использует временной сдвиг UTC в миллисекундах для вашей временной зоны. К примеру, временной сдвиг UTC для Индии составляет +5:30, поэтому его конвертацию в миллисекунды выполним следующим образом: 5*60*60+30*60 = 19800.
Зона | UTC time offset(hours and minutes) | UTC time offset (миллисекунды) |
Индия | +5:30 | 19800 |
Лондон | 0:00 | 0 |
Нью-Йорк | -5:00 | -18000 |
1 2 |
WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "pool.ntp.org", 19800,60000); |
При инициализации OLED дисплея мы будем использовать параметр SSD1306_SWITCHCAPVCC чтобы напряжение 3.3V для дисплея получать от его внутреннего источника. При инициализации дисплея мы на его экран будем выводить приветственное сообщение “WELCOME TO CIRCUIT DIGEST” на 3 секунд с размером текста равным 2 и синим цветом текста.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
if(!display.begin(SSD1306_SWITCHCAPVCC)) { Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever } display.clearDisplay(); display.setTextSize(2); // Draw 2X-scale text display.setTextColor(BLUE); display.setCursor(5, 2); display.println("WELCOME TO"); display.println(" CIRCUIT"); display.println(" DIGEST"); display.display(); delay(3000); |
Далее мы будем инициализировать NTP клиент с помощью функции timeClient.begin() чтобы в дальнейшем получать время и дату с сервера NTP.
1 |
timeClient.begin(); |
После этого мы будем использовать функцию timeClient.update() чтобы отправить запрос на NTP сервер, в ответе на который сервер передаст нам значения времени и даты.
1 |
timeClient.update(); |
Инициализируем последовательную связь со скоростью 115200 чтобы выводить принимаемые значения времени в окно монитора последовательной связи (serial monitor).
1 2 |
Serial.begin(115200); Serial.println(timeClient.getFormattedTime()); |
Далее мы будем использовать функции getHours(), getMinutes(), getSeconds(), getDay чтобы получить текущие значения часов, минут, секунд и дней с NTP сервера. Также мы будем производить разделение времени на AM и PM (по полудня и после полудня). Если число часов, определяемое с помощью функции getHours(), будет больше 12, мы установим время как PM, иначе как AM.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
int hh = timeClient.getHours(); int mm = timeClient.getMinutes(); int ss = timeClient.getSeconds(); int day = timeClient.getDay(); if(hh>12) { hh=hh-12; display.print(hh); display.print(":"); display.print(mm); display.print(":"); display.print(ss); display.println(" PM"); } else { display.print(hh); display.print(":"); display.print(mm); display.print(":"); display.print(ss); display.println(" AM"); } int day = timeClient.getDay(); display.println("'"+arr_days[day]+"'"); |
Функция getFormattedDate() используется для получения даты в формате “yyyy-mm-dd” с NTP сервера. Эта функция считывает дату и время в формате “yyyy-mm-dd T hh:mm:ss. Но нам нужна только дата, поэтому нам из этой строковой переменной нужно вырезать кусок строки, который расположен слева от символа “T” – эту операцию мы будем осуществлять с помощью функции substring() и затем сохранять полученное значение даты в переменной “date”.
1 2 3 4 5 6 |
date_time = timeClient.getFormattedDate(); int index_date = date_time.indexOf("T"); String date = date_time.substring(0, index_date); Serial.println(date); display.println(date); display.display(); |
На следующем рисунке показан внешний вид работы наших интернет часов на основе платы NodeMCU ESP8266 и OLED дисплея.
Исходный код программы (скетча)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
#include <NTPClient.h> #include <ESP8266WiFi.h> // provides ESP8266 specific Wi-Fi routines we are calling to connect to network #include <WiFiUdp.h> //handles sending and receiving of UDP packages (библиотека для работы с UDP пакетами) #include <SPI.h> // SPI for interfacing OLED with NodeMCU (библиотека для работы с интерфейсом SPI) #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 // OLED display width, in pixels (ширина дисплея) #define SCREEN_HEIGHT 64 // OLED display height, in pixels (высота дисплея) // Declaration for SSD1306 display connected using software SPI (default case): #define OLED_MOSI D7 #define OLED_CLK D5 #define OLED_DC D2 #define OLED_CS D8 #define OLED_RESET D3 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS); const char *ssid = "CircuitLoop"; const char *password = "circuitdigest101"; WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "pool.ntp.org", 19800,60000); String arr_days[]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; String date_time; // You can specify the time server pool and the offset (in seconds, can be // changed later with setTimeOffset() ). Additionaly you can specify the // update interval (in milliseconds, can be changed using setUpdateInterval() ). void setup(){ Serial.begin(115200); WiFi.begin(ssid, password); while ( WiFi.status() != WL_CONNECTED ) { delay ( 500 ); Serial.print ( "." ); } if(!display.begin(SSD1306_SWITCHCAPVCC)) { Serial.println(F("SSD1306 allocation failed")); for(;;); // Don't proceed, loop forever (бесконечный цикл, не продолжаем программу дальше) } display.clearDisplay(); display.setTextSize(2); // Draw 2X-scale text display.setTextColor(WHITE); display.setCursor(5, 2); display.println("WELCOME TO"); display.println(" CIRCUIT"); display.println(" DIGEST"); display.display(); delay(3000); timeClient.begin(); } void loop() { timeClient.update(); display.clearDisplay(); Serial.println(timeClient.getFormattedTime()); display.setTextSize(2); // Draw 2X-scale text display.setTextColor(BLUE); display.setCursor(0, 2); int hh = timeClient.getHours(); int mm = timeClient.getMinutes(); int ss = timeClient.getSeconds(); if(hh>12) { hh=hh-12; display.print(hh); display.print(":"); display.print(mm); display.print(":"); display.print(ss); display.println(" PM"); } else { display.print(hh); display.print(":"); display.print(mm); display.print(":"); display.print(ss); display.println(" AM"); } int day = timeClient.getDay(); display.println("'"+arr_days[day]+"'"); date_time = timeClient.getFormattedDate(); int index_date = date_time.indexOf("T"); String date = date_time.substring(0, index_date); Serial.println(date); display.println(date); display.display(); // Show initial text (показываем первоначальный текст) } |
8 ответов к “Интернет часы на NodeMCU ESP8266 и OLED дисплее с определением времени по протоколу NTP”
Здравствуйте, спасибо за статью! Решил переделать свои часы на ардуино и DS1302, которые безбожно спешили. ESP8266, конечно, огорчает малым количеством «безопасных» пинов. Не понимаю людей, которым не понравилась статья. Как по мне — это отличная база для собственных проектов
Добрый день. Ну люди бывают разные, всем не угодишь. А вам спасибо за «добрый» комментарий
Ни где не могу найти проект с часами синхронизирующимися по спутнику. Может кто ссылку кинет ?
Вы имеете ввиду по GPS?
Еще одна поделка индийского школьника, не имеющая практической ценности. Зачем такое публиковать?..
Посудите сами: жестко зашитые в код WiFi user name-password жестко зашитый в код часовой пояс, отсутствие перехода на декретное время, индийский формат времени “am-pm”. И зачем-то быстрый и дорогой SPI дисплей (а такой был!) вместо недорогого I2C.
Понятно, что все это допиливается. А если человек просто хочет САМ сделать часы и не быть при этом программистом?
Сделает. Включит, покрутит, разочаруется, плюнет, выбросит и пойдет купит дешевые китайские, в Фикспрайсе.
user name-password в коде несложно же изменить. А как вы все это по другому хотели видеть? Чтобы они с клавиатуры вводились? SPI дисплей вроде бы рублей на 50 всего дороже чем I2C дисплей, на мой взгляд не такая существенная разница, видимо в момент создания этого проекта именно он был у них под руками, а на I2C дисплее у них тоже проектов много. И статьи на том индийском сайте публикуют не школьники, а вполне себе взрослые и грамотные люди — я с их сайта перевел много статей и в связи с этим много про них знаю.
Если правильно понял, если Интернет временно не доступен, не работает timeClient.update нет времени, нет часов?
Ну, так и есть. Как говорится, без интернета жизни в современном мире нет )) Если у вас периоды отсутствия интернета происходят достаточно часто, то вы можете дополнить этот проект модулем часов реального времени и когда нет интернета считывать значение времени с этого модуля