🕰️ ساعت اینترنتی با ESP32: گرفتن ساعت دقیق از NTP به وقت تهران
ساعت ساختن با میکروکنترلر معمولاً دو دردسر دارد: باید یکبار ساعت را دستی تنظیم کنی و بعد از چند روز میبینی چند دقیقه عقب افتاده است. ESP32 چون به اینترنت وصل میشود، این مشکل را ندارد: میتواند ساعت دقیق را از سرورهای NTP بگیرد. در این آموزش یک ساعت NTP با ESP32 میسازیم که با configTime به وقت تهران تنظیم میشود، ساعت و تاریخ را با getLocalTime میخواند و هر ثانیه در سریال مانیتور چاپ میکند.
NTP چطور کار میکند؟
NTP (Network Time Protocol) یک روش ساده است: برد از یک سرور زمان میپرسد «ساعت چند است؟» و سرور ساعت جهانی (UTC) را با دقت چند میلیثانیه برمیگرداند. pool.ntp.org مجموعهای از هزاران سرور داوطلب است که خودش نزدیکترین را انتخاب میکند. ESP32 کتابخانهی NTP را درون خودش دارد و لازم نیست چیزی نصب کنی. بعد از اولین جواب، ساعت داخلی برد خودش جلو میرود و تقریباً هر یک ساعت یکبار دوباره با سرور هماهنگ میشود.
منطقهی زمانی تهران
سرور همیشه ساعت جهانی را میدهد و ما باید فاصلهی ایران را اضافه کنیم. تابع configTime سه چیز میگیرد: فاصله با UTC بر حسب ثانیه، مقدار ساعت تابستانی بر حسب ثانیه، و یک تا سه سرور.
- ایران ۳ ساعت و ۳۰ دقیقه جلوتر از UTC است: ۳٫۵ × ۳۶۰۰ = ۱۲۶۰۰ ثانیه.
- از سال ۱۴۰۱ ساعت رسمی ایران تابستانها تغییر نمیکند، پس ساعت تابستانی صفر است. آموزشهای قدیمی که عدد ۳۶۰۰ را برای ساعت تابستانی میگذارند، الان ساعت را یک ساعت جلو نشان میدهند.
پس دستور ما میشود configTime(12600, 0, "pool.ntp.org"). روش دیگر، رشتهی منطقهی زمانی استاندارد است: configTzTime("<+0330>-3:30", "pool.ntp.org"). علامت منفی در این قالب عجیب به نظر میرسد ولی درست است؛ در استاندارد POSIX منفی یعنی شرق گرینویچ.
قطعهها
- یک برد ESP32 و یک مودم یا هاتاسپات ۲٫۴ گیگاهرتز با اینترنت. سیمکشی لازم نیست.
کد کامل
// ESP32 internet clock: NTP time in Tehran (UTC+3:30, no daylight saving).
#include <WiFi.h>
#include <time.h>
const char* ssid = "YOUR_WIFI";
const char* password = "YOUR_PASSWORD";
const long TEHRAN_OFFSET = 12600; // 3.5 hours in seconds
const int DST_OFFSET = 0; // Iran has no daylight saving since 2022
const char* days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.println(" OK");
configTime(TEHRAN_OFFSET, DST_OFFSET, "pool.ntp.org", "time.google.com");
struct tm now;
bool ok = false;
Serial.print("Syncing time");
for (int i = 0; i < 6 && !ok; i++) {
ok = getLocalTime(&now); // each try waits up to 5 s
if (!ok) Serial.print(".");
}
Serial.println(ok ? " done" : " failed: no internet or NTP blocked?");
}
void loop() {
static unsigned long last = 0;
if (millis() - last < 1000) return; // once per second, without delay()
last = millis();
struct tm now;
if (!getLocalTime(&now)) {
Serial.println("Time not available yet");
return;
}
char line[32];
strftime(line, sizeof(line), "%Y-%m-%d %H:%M:%S", &now);
Serial.print(line);
Serial.print(" ");
Serial.println(days[now.tm_wday]);
if (now.tm_min == 0 && now.tm_sec == 0) {
Serial.println("Top of the hour!");
}
}
توضیح بخشهای مهم
#include <time.h>کتابخانهی استاندارد زمان در C است و نوعstruct tmو تابعstrftimeرا میآورد.configTime(...)فقط تنظیم میکند و منتظر جواب نمیماند. سرور دوم (time.google.com) برای وقتی است که اولی جواب ندهد.getLocalTime(&now)ساعت محلی را در متغیرnowمیریزد. اگر هنوز جوابی از سرور نرسیده باشد، تا ۵ ثانیه صبر میکند وfalseبرمیگرداند. حلقهیforحداکثر شش بار تلاش میکند تا برنامه برای همیشه گیر نکند.strftimeساعت را با یک الگو به متن تبدیل میکند:%Yسال،%mماه،%dروز،%Hساعت ۲۴ ساعته،%Mدقیقه و%Sثانیه.- در
loopباmillisهر ثانیه یکبار چاپ میکنیم و برنامه را باdelayقفل نمیکنیم؛ پس میشود کنارش کارهای دیگر هم انجام داد. - شرط
tm_min == 0 && tm_sec == 0نمونهای از کاری است که با ساعت دقیق میشود کرد: زنگ سر ساعت، روشن کردن پمپ آبیاری در ساعت مشخص، یا خاموش کردن چراغها نیمهشب.
فیلدهای struct tm
| فیلد | معنی | نکته |
|---|---|---|
tm_hour، tm_min، tm_sec | ساعت، دقیقه، ثانیه | ساعت از ۰ تا ۲۳ |
tm_mday | روز ماه | از ۱ تا ۳۱ |
tm_mon | ماه میلادی | از ۰ شروع میشود؛ برای عدد ماه یکی اضافه کن |
tm_year | سال | از ۱۹۰۰ شمرده میشود؛ ۱۹۰۰ را اضافه کن |
tm_wday | روز هفته | ۰ یعنی یکشنبه و ۶ یعنی شنبه |
اگر یک عدد ساده برای ثبت زمان لازم داری، time(nullptr) تعداد ثانیههای گذشته از اول ۱۹۷۰ (Unix time) را میدهد. برای ذخیرهی دادهها یا فرستادن به سرور، این عدد از متن تاریخ راحتتر است. تاریخ شمسی هم از همین تاریخ میلادی با یک تابع تبدیل جلالی ساخته میشود که کتابخانههای آمادهی زیادی برایش هست.
نمایش روی صفحه و کار بدون اینترنت
چاپ در سریال مانیتور برای شروع کافی است، ولی ساعت واقعی صفحه میخواهد: یک نمایشگر سونسگمنت TM1637 یا یک OLED کوچک. اگر برد ممکن است بدون اینترنت روشن شود، NTP بهتنهایی کافی نیست؛ ترکیب درست این است که ساعت را از NTP بگیری و در یک ماژول ساعت DS3231 بنویسی تا با باتری سکهای خودش جلو برود. ساعت داخلی ESP32 در دیپ اسلیپ هم کار میکند، پس برد خوابیده بعد از بیدار شدن ساعت را دارد.
تمرین منطق ساعت در شبیهساز
وایفای شبیهساز به شبکهی Wokwi-GUEST وصل میشود ولی به اینترنت واقعی و سرور NTP نمیرسد، پس getLocalTime آنجا جواب نمیدهد. ولی منطق ساعت را میشود جدا تمرین کرد: یک زمان شروع دستی، و شمردن ثانیهها با millis. این کد روی آردوینو اونو و ESP32 کار میکند؛ روی برد واقعی بعداً بهجای زمان شروع دستی، ساعت NTP را میگذاری.
// A clock that counts from a start time using millis().
// Runs in the simulator on Arduino Uno (free) or ESP32.
long startH = 14; // set the start time by hand
long startM = 30;
long startS = 0;
void print2(long v) {
if (v < 10) Serial.print("0");
Serial.print(v);
}
void setup() {
Serial.begin(115200);
}
void loop() {
static unsigned long last = 0;
if (millis() - last < 1000) return;
last = millis();
long total = startH * 3600 + startM * 60 + startS + millis() / 1000;
total = total % 86400; // wrap around at midnight
long h = total / 3600;
long m = (total / 60) % 60;
long s = total % 60;
print2(h);
Serial.print(":");
print2(m);
Serial.print(":");
print2(s);
Serial.println();
}
نسخهی کاملتر یک ساعت دیجیتال با نمایشگر در تینکرکد فارسی آماده است: پروژهی ساعت دیجیتال که روی آردوینو اونو رایگان اجرا میشود و درس ساعت دیجیتال قدمبهقدم توضیحش میدهد. نسخهی با ماژول ساعت واقعی هم درس ساعت واقعی با RTC است؛ خواندنش رایگان است و اجرای ماژول RTC و برد ESP32 در شبیهساز اشتراک Plus میخواهد.
اشکالهای رایج
- ساعت یک ساعت جلوتر است: برای ساعت تابستانی ۳۶۰۰ گذاشتهای. برای ایران این عدد صفر است.
- سال ۱۹۷۰ چاپ میشود: هنوز جوابی از سرور نرسیده و
time.hاز صفر شمرده است. همیشه نتیجهیgetLocalTimeرا چک کن. - Syncing time هیچوقت تمام نمیشود: برد به اینترنت نرسیده است، یا شبکهات پورت UDP شمارهی ۱۲۳ را بسته است. با هاتاسپات گوشی امتحان کن یا سرور NTP دیگری بگذار.
- ماه یکی کمتر نشان داده میشود:
tm_monاز صفر شروع میشود؛ یکی به آن اضافه کن.strftimeاین را خودش درست انجام میدهد. - به وایفای وصل نمیشود: مشکل از ساعت نیست؛ عیبیابی وایفای ESP32 را ببین.
برای یادآوری اصول اتصال، وصل شدن ESP32 به وایفای را بخوان و اگر میخواهی ساعت را کنار دمای هوا روی یک صفحهی وب نشان بدهی، به گرفتن داده از API با HTTPClient و پروژههای وایفای و اینترنت اشیا سر بزن.
منطق ساعت را در شبیهساز امتحان کن ⏰سؤالهای رایج
عدد منطقهی زمانی تهران در configTime چیست؟
ایران سه ساعت و نیم از ساعت جهانی جلوتر است، پس عدد اول ۱۲۶۰۰ ثانیه است. چون از سال ۱۴۰۱ ساعت رسمی ایران تابستانها جلو کشیده نمیشود، عدد دوم (ساعت تابستانی) صفر است: configTime(12600, 0, "pool.ntp.org").
آیا ساعت NTP در شبیهساز کار میکند؟
نه. وایفای شبیهساز به شبکهی Wokwi-GUEST وصل میشود ولی به اینترنت واقعی نمیرسد، پس سرور NTP جواب نمیدهد. منطق ساعت، یعنی شمردن ثانیهها با millis و نمایش آن، را میتوانی در شبیهساز تمرین کنی.
اگر اینترنت بعد از همگامسازی قطع شود، ساعت چه میشود؟
ساعت داخلی ESP32 به کارش ادامه میدهد و فقط در روز چند ثانیه جلو یا عقب میافتد. هر وقت اینترنت برگردد، ESP32 خودش دوباره همگام میشود. ولی اگر برد ریاستارت شود و اینترنت نباشد، ساعت از دست میرود؛ برای آن حالت ماژول DS3231 اضافه کن.