مقدمه
در پروژه های الکترونیکی انجام عملیات در یک زمان خاص یا نشان دادن زمان به کاربر همیشه یکی از چالش های پیش رو بوده است. معمولا اولین راه حل برای عبور از این چالش استفاده از آیسی های RTC مانند DS1307 می باشد (برای اطلاعات بیشتر می توانید به “چگونه از DS1307 با آردوینو استفاده کنیم و یک یادآور هوشمند بسازیم؟” مراجعه کنید)، که معمولا این آیسی ها دقت خیلی بالایی ندارند. یکی دیگر از راه حل های عبور از این چالش که نسبت به مورد قبلی جدیدتر و البته خیلی دقیق تر است، دریافت ساعت و تاریخ از یک سرور مرکزی از طریق اینترنت می باشد.
آنچه در این آموزش یاد می گیرید
- آشنایی با مفهوم NTP
- ساخت ساعت NTP با Nodemcu
- ساخت ساعت NTP با ESP32 و نمایشگر OLED
NTP چیست و چگونه کار می کند؟
کلمه NTP مخفف Network Time Protocol است و یک استاندارد جهانی اینترنتی برای همگام سازی ساعت دستگاه ها می باشد. NTP ساعت دستگاه ها را بر اساس ساعت UTC با 50 میلی ثانیه خطا بر اساس موقعیت جغرافیایی و قانون ذخیره روشنایی روز (day light saving)، تنظیم می کند.
اما NTP چگونه کار می کند؟
NTP یک ساختار سلسله مراتبی دارد، در سطح اول ساعت دقیق از ابزارهای فوق دقیقی مانند ساعت GMT یا ساعت های اتمی دریافت می شود، سطح دوم بعنوان یک سطح واسط ساعت دریافت شده از سطح اول را در بستر اینترنت قرار می دهد، سطح سوم هم دستگاه های درخواست کننده می باشند که از سطح دو ساعت و تاریخ را طلب می کنند.
پکیج ارسالی از سمت سطح دو به سطح سه را با توجه به جدول زیر می توان ترجمه کرد:
لوازمی که به آن احتیاج دارید
قطعات مورد نیاز
نرم افزار های مورد نیاز
دریافت ساعت و تاریخ از سرور NTP با ESP8266
حال کد زیر را روی برد ESP8266 خود ریخته و نتیجه را در Serial Monitor مشاهده کنید:
/*
NTP Clock with NodeMCU
modified on 18 DEC 2019
by Saeed Hosseini @ Electropeak
Home<iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted" style="position: absolute; clip: rect(1px, 1px, 1px, 1px);" title="“Home” — Electropeak" src="https://electropeak.com/learn/embed/#?secret=kx5guT75TS" data-secret="kx5guT75TS" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
*/
#include <NTPClient.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
const char *ssid = "YOUR SSID";
const char *password = "YOUR SSID PASSWORD";
const long utcOffsetInSeconds = 12600;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);
void setup(){
Serial.begin(115200);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED ) {
delay ( 500 );
Serial.print ( "." );
}
timeClient.begin();
}
void loop() {
timeClient.update();
Serial.print(daysOfTheWeek[timeClient.getDay()]);
Serial.print(", ");
Serial.print(timeClient.getHours());
Serial.print(":");
Serial.print(timeClient.getMinutes());
Serial.print(":");
Serial.println(timeClient.getSeconds());
//Serial.println(timeClient.getFormattedTime());
delay(1000);
}
در ابتدا کتابخانه های لازم را به کد اضافه می کنیم، سپس نام و رمز روتر وایفای خود را درج می کنیم.
متغیر utcOffsetInSeconds را با توجه به موقعیت مکانی خود تغییر دهید، می توانید ضریب UTC کشور خود را در اینجا پیدا کنید.
بعنوان مثال ضریب UTC برای کشور آمریکا به این صورت محاسبه می شود:
UTC = -11:00
utcOffsetInSeconds = -11*60*60 = -39600
سرور ما برای دریافت NTP سرور pool.ntp.org می باشد.
در ادامه پس از اتصال به اینترنت با شی timeclient می توانید تاریخ و ساعت را دریافت کنیم.
دریافت ساعت و تاریخ از سرور NTP با ESP32
اگر با بردهای ESP32 و نحوه اضافه کردن و برنامه نویسی آنها با Arduino IDE آشنا نیستید به ” راه اندازی ماژول وای فای و بلوتوث ESP32 و نصب آن بر IDE آردوینو” مراجعه کنید.
حال کد زیر را روی ESP32 خود ریخته و نتیجه را Serial Monitor مشاهده کنید.
#include <WiFi.h>
#include "time.h"
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASS";
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 12600;
const int daylightOffset_sec = 3600;
void printLocalTime()
{
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
return;
}
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}
void setup()
{
Serial.begin(115200);
//connect to WiFi
Serial.printf("Connecting to %s ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" CONNECTED");
//init and get the time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
printLocalTime();
//disconnect WiFi as it's no longer needed
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
void loop()
{
delay(1000);
printLocalTime();
}
در اینجا نیز مانند پروژه NodeMcu ابتدا تنظیمات وایفای را انجام می دهیم.
مانند قبل gmtOffset_sec را محاسبه کرده و تغییر می دهیم، درمورد متغیر daylightOffset_sec اگر کشور شما جزو کشورهایی است که در آن قانون تغییر ساعت وجود دارد (می توانید لیست این کشورها را در اینجا ببینید) مقدار آن را 3600 قرار دهید و درغیر اینصورت مقدار 0 قرار دهید. سرور NTP نیز مانند قبل pool.ntp.org می باشد. تابع printLocalTime() نیز طبق جدول ساختار NTP اطلاعات لازم را از شی timeinfo استخراج می کند و نمایش می دهد.
ساخت ساعت دقیق با استفاده از OLED و ESP32
برای زیباتر و عملی تر شدن پروژه ساعت با NTP آنرا روی نمایشگر OLED 0.96” I2C نمایش می دهیم.
سیم بندی
کد
کد زیر را روی ESP32 خود آپلود کنید و نتیجه را روی نمایشگر ملاحظه کنید.
/*
NTP Clock with ESP32 and OLED
modified on 18 DEC 2019
by Saeed Hosseini @ Electropeak
Home<iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted" style="position: absolute; clip: rect(1px, 1px, 1px, 1px);" title="“Home” — Electropeak" src="https://electropeak.com/learn/embed/#?secret=kx5guT75TS" data-secret="kx5guT75TS" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "logo.h"
#include <WiFi.h>
#include "time.h"
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const char* ssid = "CafeRobot Inc.";
const char* password = "caferobot.ir";
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 12600;
const int daylightOffset_sec = 3600;
void diplay_logo(int x, int y, const uint8_t *bitmap, int w, int h) {
display.drawBitmap(x, y, bitmap, w, h, WHITE);
}
void display_text(int sz, int x, int y, String str) {
display.setTextSize(sz);
display.setTextColor(WHITE);
display.setCursor(x, y);
display.println(str);
}
void display_number(int sz, int x, int y, double num) {
display.setTextSize(sz);
display.setTextColor(WHITE);
display.setCursor(x, y);
display.println(num);
}
void system_setup() {
Serial.begin(115200);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
diplay_logo(13, 20, Electropeak, 128, 32);
display.display();
delay(1000);
display.clearDisplay();
// while (WiFi.status() != WL_CONNECTED) {
// loading();
// }
}
void loading() {
display_text(2, 0, 25, "Loading");
display_text(2, 85, 25, ".");
display.display();
delay(500);
display_text(2, 95, 25, ".");
display.display();
delay(500);
display_text(2, 105, 25, ".");
display.display();
delay(500);
display.fillRect(85, 25, 40, 20, BLACK);
display.display();
delay(500);
}
void printLocalTime()
{
String s = "";
char h [80];
char m [80];
char p [80];
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
Serial.println("Failed to obtain time");
return;
}
Serial.println(&timeinfo, "%H:%M:%S");
strftime (h, 80, "%I", &timeinfo);
strftime (m, 80, "%M", &timeinfo);
strftime (p, 80, "%p", &timeinfo);
display_text(3, 15, 30, h);
display_text(3, 50, 30, ":");
display_text(3, 65, 30, m);
display_text(1, 108, 45, p);
display.display();
}
void setup() {
system_setup();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
loading();
}
display.clearDisplay();
diplay_logo(0, 0, Wifi, 16, 16);
display.display();
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
printLocalTime();
//disconnect WiFi as it's no longer needed
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
void loop() {
printLocalTime();
delay(10000);
display.clearDisplay();
diplay_logo(0, 0, Wifi, 16, 16);
display.display();
}
تنها تفاوت این کد با کد قبل در این است که با شی strftime مقادیر ساعت، دقیقه و AM/PM را از timeinfo جدا کرده و در رشته ای ذخیره می کنیم و سپس آنها را با فرمت ساعت در نمایشگر نشان می دهیم.
یک گام جلوتر
- سعی کنید تاریخ را نیز درنمایشگر نشان دهید.
- سعی کنید با ساعت NTP یک آلارم هوشمند بسازید.
Comments (19)
سلام
مممنون پروژه خیلی بدرد بخوری بود فقط ، یک راهنمایی می کنید چطور UTC ایران رو از همون ویکی پدیا برداریم . و لینک <> هم کار نمی کنه .
ممنون از کافه ربات 🙂
سلام.
UTC به این معناست که منطقه و کشوری که شخص زندگی میکنه چند ساعت با مبدا جهانی که گرینویچ هست اختلاف داره. برای ایران این عدد 3:30 هست. به این معنا که ما 3ساعت و نیم از گرینویچ جلوتر هستیم. این رو میتونید با سرچ عبارت iran UTC در گوگل هم ببینید. در لینک ویکیپدیا گذاشته شده در آموزش هم این عدد پیدا میشه. برای محاسبه اون متغیر utcOffsetInSeconds هم کافیه که 3ساعت و نیم رو به ثانیه تبدیل کنید: 3.5 * 60 * 60 که همون 12600 میشه. منظورتون از لینک <> رو متوجه نشدم.
در خط 79 برای نمایش در oled دستور getlocaltime ارور not declared داده میشه میخواستم راهنمایی کنید
سلام،
مشکلی در کد وجود نداره. این ایراد میتونه از اشتباه انتخاب شدن بُرد باشه، بررسی کنید که بردتون رو به درستی برد ESP32 انتخاب کرده باشید.
بله برد esp 32است
چگونه میشه برد را ریست کرد؟
منظور از انتخاب درست برد، انتخاب اون در نرمافزار Arduino IDE هست. برد رو میتونید در قسمت Tools -> Board -> ESP32 Arduino انتخاب کنید. اگر در این حالت هم مشکلتون برطرف نشد، میتونید برای بررسی بیشتر مشکلتون رو همراه با فرستادن عکس و اطلاعات لازم در قسمت انجمن مطرح کنید. آدرس انجمن: https://thecaferobot.com/forum/
برای ریست کردن برد ESP32 یک دکمه روی خود برد تعبیه شده که میتونید اون رو فشار بدید.
سلام وقت بخیر
ممنون بابت این پروژه عالی
چطور میشه با همین تایم سرور یا سرور ایرانیش (ir.pool.ntp.org) تاریخ شمسی رو دریافت کرد؟
و اینکه چطور میشه از اعداد ساعت و دقیقه برای دستورات if استفاده کرد؟
با تشکر.
سلام.
متاسفانه تجربهای در رابطه با دریافت تاریخ شمسی با ماژول ESP32 ندارم، اما روشهایی برای تبدیل تاریخ میلادی به شمسی وجود داره که میتونید در صورتی که نتونستید تاریخ شمسی رو به طور مستقیم دریافت کنید خودتون اون رو محاسبه کنید.
برای استفاده از اعداد ساعت و دقیقه هم، موقعی که زمان رو از سرور دریافت میکنید به صورت یک struct دریافت میشه و داخل اون به مقدار ساعت و دقیقه هم دسترسی دارید. همونطوری که از اونها در کد دوم برای نوشتن روی نمایشگر استفاده شده. با دسترسی به این مقادیر میتونید به راحتی ازشون در دستورات if یا سایر دستورات استفاده کنید.
سلام ممنون از راهنماییتون.
وقتی میخوام m رو در دستور if قرار بدم که اگر برابر با 20 بود یک ال ای دی روشن کنه با این ارور مواجه میشم
ISO C++ forbids comparison between pointer and integer [-fpermissive]
در کد هایی که قرار دادید m به این صورت تعریف شده strftime (m, 80, “%M”, &timeinfo);
خیلی از راه ها رو امتحان کردم اما باز هم با ارور اینکه char با int قابل مقایسه نیست برخورد کردم.
اعداد رو داخل “20” و ’20’ گذاشتم فقط ارور رو برطرف کرد اما همچنان برنامه به درستی اجرا نشد.
بنظرتون از چه روش دیگری می تونم دقیقه و ساعت رو به صورت int و یا عدد تعریف کنم که بتونم در دستورات if قرار بدم؟
سلام.
خواهش میکنم.
از لینک زیر میتونید کمک بگیرید احتمالا مشکتون رو برطرف کنه.
https://www.delftstack.com/howto/arduino/arduino-char-to-int/
با این روش ابتدا char رو به string و بعد از اون string رو میتونید به int تبدیل کنید.
با سلام / احتراما برنامه دریافت زمان و تاریخ از اینترنت برای برد esp-32 رو اجرا کردم و به وای فای نیز متصل میشود ولی متاسفانه پیام خطای Failed to obtain time رو می دهد . میشه راهنمایی بفرمائید آیا باید در مودم با تنظیمات خاصی انجام داد یا خیر . ممنون
با سلام
برای استفاده ساعت و دقیقه در شرط مشکلی ندارم
ولی استفاده از روز در شرط مشکلی هست به نظر شما اگر خواسته باشیم روز را در شرط استفاده کنیم به صورت این کار رو انجام بدم
با تشکر
سلام
شما از همون زیر تابع getDay میتونید استفاده کنید
سلام میتونم کد برد ESP32 را برای node mcu esp8266 استفاده کنم
با سلام
کد های شبکه wifi در ماژول های esp8266 یا esp32 نزدیک به هم میباشند اما فرق دارند از این رو نمیتوانید مستقیم اجرا کنید.
آموزش را توجه کنید کد را برای هر دو مدل قرار داده شده است.
چطوری میتونم به ساعت تاریخی هم اضافه کنم
دیتا های دریافت تاریخ نیز به صورت سریال نمایش داده می شود.
در صورتی که میخواهید در نمایشگر نیز نمایش داده شود کافی است مشابه رقم های ساعت آنها را نیز در جایی از صفحه قرار بدهید و نمایش بدهید.
سلام
میشه مقدارهای دریافتی رو به انواع دیگه مثل uint8_t تبدیل کرد؟ میخوام روی ماژول rtc ست کنم
با سلام
بله می توانید تغییر بدهید. البته این را نیز در نظر داشته باشید که برای ست کردن در RTC شما می توانید از مقدار unix Time یا همون Epoch نیز استفاده کنید.